Skip to content

Commit

Permalink
Added jtd validation
Browse files Browse the repository at this point in the history
  • Loading branch information
jefbarn committed Jul 28, 2021
1 parent aefe947 commit cb8dec4
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 66 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pgx = "0.1.20"
pgx-macros = "0.1.20"
serde_json = "1.0.59"
jsonschema = "0.12.0"
#jtd = "0.3"
jtd = "0.3"

[dev-dependencies]
pgx-tests = "0.1.20"
Expand Down
3 changes: 2 additions & 1 deletion sql/load-order.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
lib.generated.sql
json_schema.generated.sql
json_type_def.generated.sql
66 changes: 66 additions & 0 deletions src/json_schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use pgx::*;
use jsonschema::JSONSchema;

#[pg_extern]
fn json_schema_is_valid(schema: JsonB, instance: JsonB) -> bool {
jsonschema::is_valid(&schema.0, &instance.0)
}

#[pg_extern]
fn json_schema_get_errors(
schema: JsonB,
instance: JsonB,
) -> impl std::iter::Iterator<
Item = (
name!(error_value, JsonB),
name!(description, String),
name!(details, String),
name!(instance_path, String),
name!(schema_path, String),
),
> {
let compiled = JSONSchema::compile(&schema.0)
.unwrap_or_else(|err| panic!("Error compiling schema: {:#?}", err));

let result = compiled
.validate(&instance.0)
.err()
.into_iter()
.flat_map(|iter| iter);

let new: Vec<(JsonB, String, String, String, String)> = result
.map(|e| {
let description = e.to_string();
(
JsonB(e.instance.into_owned()),
description,
format!("{:?}", (e.kind)).clone(),
e.instance_path.to_string(),
e.schema_path.to_string(),
)
})
.collect();

new.into_iter()
}

#[cfg(any(test, feature = "pg_test"))]
mod tests {
use pgx::*;

#[pg_test]
fn test_json_schema_is_valid() {
let valid = Spi::get_one::<bool>(
"select json_schema_is_valid('{\"maxLength\": 5}', '\"foobar\"'::jsonb)",
);
assert_eq!(valid, Some(false))
}

#[pg_test]
fn test_json_schema_get_errors() {
let (_value, description) = Spi::get_two::<JsonB, String>(
"select * from json_schema_get_errors('{\"maxLength\": 5}', '\"foobar\"'::jsonb)",
);
assert_eq!(description, Some("\"foobar\" is longer than 5 characters".to_string()))
}
}
97 changes: 97 additions & 0 deletions src/json_type_def.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use jtd::Schema;
use pgx::*;
use serde_json::json;

#[pg_extern]
fn jtd_is_valid(schema: JsonB, instance: JsonB) -> bool {
let schema = Schema::from_serde_schema(serde_json::from_value(schema.0).unwrap()).unwrap();
let result = jtd::validate(&schema, &instance.0, Default::default()).unwrap();

result.is_empty()
}

#[pg_extern]
fn jtd_get_errors(
schema: JsonB,
instance: JsonB,
) -> impl std::iter::Iterator<
Item = (
name!(instance_path, String),
name!(schema_path, String),
),
> {
let schema = Schema::from_serde_schema(serde_json::from_value(schema.0).unwrap()).unwrap();
let result = jtd::validate(&schema, &instance.0, Default::default()).unwrap();

let new: Vec<(JsonB, String, String, String, String)> = result
.into_iter()
.map(|e| {
let (instance_path, schema_path) = e.into_owned_paths();
(
if instance_path.is_empty() {
String::new()
} else {
"/".to_owned() + &instance_path.join("/")
},
if schema_path.is_empty() {
String::new()
} else {
"/".to_owned() + &schema_path.join("/")
},
)
})
.collect();

new.into_iter()
}

#[cfg(any(test, feature = "pg_test"))]
mod tests {
use pgx::*;

#[pg_test]
fn test_jtd_is_valid() {
let valid = Spi::get_one::<bool>(
r#"
select jtd_is_valid('{
"properties": {
"name": { "type": "string" },
"age": { "type": "uint32" },
"phones": {
"elements": {
"type": "string"
}
}
}
}'::jsonb, '{
"age": "43",
"phones": ["+44 1234567", 442345678]
}'::jsonb)"#,
);
assert_eq!(valid, Some(false))
}

#[pg_test]
fn test_jtd_get_errors() {
let (instance_path, schema_path) = Spi::get_two::<String, String>(
r#"
select instance_path, schema_path from jtd_get_errors('{
"properties": {
"name": { "type": "string" },
"age": { "type": "uint32" },
"phones": {
"elements": {
"type": "string"
}
}
}
}', '{
"age": "43",
"phones": ["+44 1234567", 442345678]
}'::jsonb)"#,
);

assert_eq!(instance_path, Some("/age".to_string()));
assert_eq!(schema_path, Some("/properties/age/type".to_string()));
}
}
68 changes: 4 additions & 64 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,71 +1,11 @@
use jsonschema::JSONSchema;
use pgx::*;

pg_module_magic!();

#[pg_extern]
fn json_schema_is_valid(schema: JsonB, instance: JsonB) -> bool {
jsonschema::is_valid(&schema.0, &instance.0)
}

#[pg_extern]
fn json_schema_get_errors(
schema: JsonB,
instance: JsonB,
) -> impl std::iter::Iterator<
Item = (
name!(error_value, JsonB),
name!(description, String),
name!(details, String),
name!(instance_path, String),
name!(schema_path, String),
),
> {
let compiled = JSONSchema::compile(&schema.0)
.unwrap_or_else(|err| panic!("Error compiling schema: {:#?}", err));

let result = compiled
.validate(&instance.0)
.err()
.into_iter()
.flat_map(|iter| iter);
mod json_schema;
mod json_type_def;

let new: Vec<(JsonB, String, String, String, String)> = result
.map(|e| {
let description = e.to_string();
(
JsonB(e.instance.into_owned()),
description,
format!("{:?}", (e.kind)).clone(),
e.instance_path.to_string(),
e.schema_path.to_string(),
)
})
.collect();

new.into_iter()
}

#[cfg(any(test, feature = "pg_test"))]
mod tests {
use pgx::*;
use pgx::*;

#[pg_test]
fn test_json_schema_is_valid() {
let valid = Spi::get_one::<bool>(
"select json_schema_is_valid('{\"maxLength\": 5}', '\"foobar\"'::jsonb)",
);
assert_eq!(valid, Some(false))
}
pg_module_magic!();

#[pg_test]
fn test_json_schema_get_errors() {
let (_value, description) = Spi::get_two::<JsonB, String>(
"select * from json_schema_get_errors('{\"maxLength\": 5}', '\"foobar\"'::jsonb)",
);
assert_eq!(description, Some("\"foobar\" is longer than 5 characters".to_string()))
}
}

#[cfg(test)]
pub mod pg_test {
Expand Down

0 comments on commit cb8dec4

Please sign in to comment.