From d431ab5b7cb901805f4ef95e3365493d9cce5714 Mon Sep 17 00:00:00 2001 From: duguorong Date: Mon, 3 Jul 2023 13:13:51 +0800 Subject: [PATCH 01/11] feat: add the validate utils --- src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index bf8fb8a..8cfc8ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,30 @@ fn jsonb_matches_schema(schema: Json, instance: JsonB) -> bool { jsonschema::is_valid(&schema.0, &instance.0) } +#[pg_extern(immutable, strict)] +fn validate_json_schema(schema: Json, instance: Json) -> Result<(), Vec> { + let compiled = match jsonschema::JSONSchema::compile(&schema.0) { + Ok(c) => c, + Err(e) => Err(vec![e.to_string()]), + }; + match compiled.validate(&instance.0) { + Ok(_) => Ok(()), + Err(e) => Err(e.into_iter().map(String::from).collect::()), + } +} + +#[pg_extern(immutable, strict)] +fn validate_jsonb_schema(schema: Json, instance: JsonB) -> Result<(), Vec> { + let compiled = match jsonschema::JSONSchema::compile(&schema.0) { + Ok(c) => c, + Err(e) => Err(vec![e.to_string()]), + }; + match compiled.validate(&instance.0) { + Ok(_) => Ok(()), + Err(e) => Err(e.into_iter().map(String::from).collect::()), + } +} + #[pg_schema] #[cfg(any(test, feature = "pg_test"))] mod tests { From 760e7b7cadffd9ce168067b08b403e4d118e924c Mon Sep 17 00:00:00 2001 From: duguorong Date: Mon, 10 Jul 2023 15:04:08 +0800 Subject: [PATCH 02/11] fix the "validate_json(b)_schema" --- src/lib.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 9e4b03b..93b52f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,60 @@ fn jsonschema_is_valid(schema: Json) -> bool { } } +#[pg_extern(immutable, strict)] +fn validate_json_schema(schema: Json, instance: Json) -> bool { + let compiled = match jsonschema::JSONSchema::compile(&schema.0) { + Ok(c) => c, + Err(e) => { + // Only call notice! for a non empty instance_path + if e.instance_path.last().is_some() { + notice!( + "Invalid JSON schema at path: {}", + e.instance_path.to_string() + ); + } + false + } + }; + match compiled.validate(&instance.0) { + Ok(_) => true, + Err(e) => { + let _ = e + .into_iter() + .map(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)) + .collect(); + false + } + } +} + +#[pg_extern(immutable, strict)] +fn validate_jsonb_schema(schema: Json, instance: JsonB) -> Result<(), Vec> { + let compiled = match jsonschema::JSONSchema::compile(&schema.0) { + Ok(c) => c, + Err(e) => { + // Only call notice! for a non empty instance_path + if e.instance_path.last().is_some() { + notice!( + "Invalid JSON schema at path: {}", + e.instance_path.to_string() + ); + } + false + } + }; + match compiled.validate(&instance.0) { + Ok(_) => true, + Err(e) => { + let _ = e + .into_iter() + .map(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)) + .collect(); + false + } + } +} + #[pg_schema] #[cfg(any(test, feature = "pg_test"))] mod tests { From 24495e2a2d66742848b0ad06839fe014c3d8b7b7 Mon Sep 17 00:00:00 2001 From: duguorong Date: Mon, 10 Jul 2023 15:05:03 +0800 Subject: [PATCH 03/11] fix the return type in "validate_jsonb_schema" --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 93b52f6..c867d66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,7 +57,7 @@ fn validate_json_schema(schema: Json, instance: Json) -> bool { } #[pg_extern(immutable, strict)] -fn validate_jsonb_schema(schema: Json, instance: JsonB) -> Result<(), Vec> { +fn validate_jsonb_schema(schema: Json, instance: JsonB) -> bool { let compiled = match jsonschema::JSONSchema::compile(&schema.0) { Ok(c) => c, Err(e) => { From e82a6f57256c9fc0761ec95549ead59c3349faae Mon Sep 17 00:00:00 2001 From: duguorong Date: Mon, 10 Jul 2023 15:22:18 +0800 Subject: [PATCH 04/11] fix the compile errors --- src/lib.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c867d66..2a75f29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,19 +41,21 @@ fn validate_json_schema(schema: Json, instance: Json) -> bool { e.instance_path.to_string() ); } - false + return false; } }; - match compiled.validate(&instance.0) { + + let is_valid = match compiled.validate(&instance.0) { Ok(_) => true, Err(e) => { let _ = e .into_iter() - .map(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)) - .collect(); + .for_each(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)); false } - } + }; + + is_valid } #[pg_extern(immutable, strict)] @@ -68,19 +70,21 @@ fn validate_jsonb_schema(schema: Json, instance: JsonB) -> bool { e.instance_path.to_string() ); } - false + return false; } }; - match compiled.validate(&instance.0) { + + let is_valid = match compiled.validate(&instance.0) { Ok(_) => true, Err(e) => { let _ = e .into_iter() - .map(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)) - .collect(); + .for_each(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)); false } - } + }; + + is_valid } #[pg_schema] From 7ab20194168c6f9f64c05b9a7668c7dbc41fd4a4 Mon Sep 17 00:00:00 2001 From: duguorong Date: Mon, 10 Jul 2023 15:28:31 +0800 Subject: [PATCH 05/11] add the unit tests --- src/lib.rs | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2a75f29..d9a08c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,6 +190,90 @@ mod tests { "type": "obj" })))); } + + #[pg_test] + fn test_json_validates_schema_rs() { + let max_length: i32 = 5; + assert!(crate::validate_json_schema( + Json(json!({ "maxLength": max_length })), + Json(json!("foo")), + )); + } + + #[pg_test] + fn test_json_not_validates_schema_rs() { + let max_length: i32 = 5; + assert!(!crate::validate_json_schema( + Json(json!({ "maxLength": max_length })), + Json(json!("foobar")), + )); + } + + #[pg_test] + fn test_jsonb_validates_schema_rs() { + let max_length: i32 = 5; + assert!(crate::validate_jsonb_schema( + Json(json!({ "maxLength": max_length })), + JsonB(json!("foo")), + )); + } + + #[pg_test] + fn test_jsonb_not_validates_schema_rs() { + let max_length: i32 = 5; + assert!(!crate::validate_jsonb_schema( + Json(json!({ "maxLength": max_length })), + JsonB(json!("foobar")), + )); + } + + #[pg_test] + fn test_json_validates_schema_spi() { + let result = Spi::get_one::( + r#" + select validate_json_schema('{"type": "object"}', '{}') + "#, + ) + .unwrap() + .unwrap(); + assert!(result); + } + + #[pg_test] + fn test_json_not_validates_schema_spi() { + let result = Spi::get_one::( + r#" + select validate_json_schema('{"type": "object"}', '1') + "#, + ) + .unwrap() + .unwrap(); + assert!(!result); + } + + #[pg_test] + fn test_jsonb_validates_schema_spi() { + let result = Spi::get_one::( + r#" + select validate_jsonb_schema('{"type": "object"}', '{}') + "#, + ) + .unwrap() + .unwrap(); + assert!(result); + } + + #[pg_test] + fn test_jsonb_not_validates_schema_spi() { + let result = Spi::get_one::( + r#" + select validate_jsonb_schema('{"type": "object"}', '1') + "#, + ) + .unwrap() + .unwrap(); + assert!(!result); + } } #[cfg(test)] From 910a1191325a72cbc857e5718d7a16a35f0c3acb Mon Sep 17 00:00:00 2001 From: duguorong Date: Thu, 13 Jul 2023 13:16:15 +0800 Subject: [PATCH 06/11] remove the new functions & update existing ones --- src/lib.rs | 190 +++++++++++++---------------------------------------- 1 file changed, 47 insertions(+), 143 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d9a08c8..464b813 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,64 +4,64 @@ pg_module_magic!(); #[pg_extern(immutable, strict)] fn json_matches_schema(schema: Json, instance: Json) -> bool { - jsonschema::is_valid(&schema.0, &instance.0) -} + if jsonschema::is_valid(&schema.0, &instance.0) { + true + } else { + let compiled = match jsonschema::JSONSchema::compile(&schema.0) { + Ok(c) => c, + Err(e) => { + // Only call notice! for a non empty instance_path + if e.instance_path.last().is_some() { + notice!( + "Invalid JSON schema at path: {}", + e.instance_path.to_string() + ); + } + return false; + } + }; -#[pg_extern(immutable, strict)] -fn jsonb_matches_schema(schema: Json, instance: JsonB) -> bool { - jsonschema::is_valid(&schema.0, &instance.0) -} + let err = compiled.validate(&instance.0).unwrap_err(); + let _ = err + .into_iter() + .for_each(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)); -#[pg_extern(immutable, strict)] -fn jsonschema_is_valid(schema: Json) -> bool { - match jsonschema::JSONSchema::compile(&schema.0) { - Ok(_) => true, - Err(e) => { - // Only call notice! for a non empty instance_path - if e.instance_path.last().is_some() { - notice!( - "Invalid JSON schema at path: {}", - e.instance_path.to_string() - ); - } - false - } + false } } #[pg_extern(immutable, strict)] -fn validate_json_schema(schema: Json, instance: Json) -> bool { - let compiled = match jsonschema::JSONSchema::compile(&schema.0) { - Ok(c) => c, - Err(e) => { - // Only call notice! for a non empty instance_path - if e.instance_path.last().is_some() { - notice!( - "Invalid JSON schema at path: {}", - e.instance_path.to_string() - ); +fn jsonb_matches_schema(schema: Json, instance: JsonB) -> bool { + if jsonschema::is_valid(&schema.0, &instance.0) { + true + } else { + let compiled = match jsonschema::JSONSchema::compile(&schema.0) { + Ok(c) => c, + Err(e) => { + // Only call notice! for a non empty instance_path + if e.instance_path.last().is_some() { + notice!( + "Invalid JSON schema at path: {}", + e.instance_path.to_string() + ); + } + return false; } - return false; - } - }; + }; - let is_valid = match compiled.validate(&instance.0) { - Ok(_) => true, - Err(e) => { - let _ = e - .into_iter() - .for_each(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)); - false - } - }; + let err = compiled.validate(&instance.0).unwrap_err(); + let _ = err + .into_iter() + .for_each(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)); - is_valid + false + } } #[pg_extern(immutable, strict)] -fn validate_jsonb_schema(schema: Json, instance: JsonB) -> bool { - let compiled = match jsonschema::JSONSchema::compile(&schema.0) { - Ok(c) => c, +fn jsonschema_is_valid(schema: Json) -> bool { + match jsonschema::JSONSchema::compile(&schema.0) { + Ok(_) => true, Err(e) => { // Only call notice! for a non empty instance_path if e.instance_path.last().is_some() { @@ -70,21 +70,9 @@ fn validate_jsonb_schema(schema: Json, instance: JsonB) -> bool { e.instance_path.to_string() ); } - return false; - } - }; - - let is_valid = match compiled.validate(&instance.0) { - Ok(_) => true, - Err(e) => { - let _ = e - .into_iter() - .for_each(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)); false } - }; - - is_valid + } } #[pg_schema] @@ -190,90 +178,6 @@ mod tests { "type": "obj" })))); } - - #[pg_test] - fn test_json_validates_schema_rs() { - let max_length: i32 = 5; - assert!(crate::validate_json_schema( - Json(json!({ "maxLength": max_length })), - Json(json!("foo")), - )); - } - - #[pg_test] - fn test_json_not_validates_schema_rs() { - let max_length: i32 = 5; - assert!(!crate::validate_json_schema( - Json(json!({ "maxLength": max_length })), - Json(json!("foobar")), - )); - } - - #[pg_test] - fn test_jsonb_validates_schema_rs() { - let max_length: i32 = 5; - assert!(crate::validate_jsonb_schema( - Json(json!({ "maxLength": max_length })), - JsonB(json!("foo")), - )); - } - - #[pg_test] - fn test_jsonb_not_validates_schema_rs() { - let max_length: i32 = 5; - assert!(!crate::validate_jsonb_schema( - Json(json!({ "maxLength": max_length })), - JsonB(json!("foobar")), - )); - } - - #[pg_test] - fn test_json_validates_schema_spi() { - let result = Spi::get_one::( - r#" - select validate_json_schema('{"type": "object"}', '{}') - "#, - ) - .unwrap() - .unwrap(); - assert!(result); - } - - #[pg_test] - fn test_json_not_validates_schema_spi() { - let result = Spi::get_one::( - r#" - select validate_json_schema('{"type": "object"}', '1') - "#, - ) - .unwrap() - .unwrap(); - assert!(!result); - } - - #[pg_test] - fn test_jsonb_validates_schema_spi() { - let result = Spi::get_one::( - r#" - select validate_jsonb_schema('{"type": "object"}', '{}') - "#, - ) - .unwrap() - .unwrap(); - assert!(result); - } - - #[pg_test] - fn test_jsonb_not_validates_schema_spi() { - let result = Spi::get_one::( - r#" - select validate_jsonb_schema('{"type": "object"}', '1') - "#, - ) - .unwrap() - .unwrap(); - assert!(!result); - } } #[cfg(test)] From 82c1117a9303cb0bb20fce9188c29ce6ae9f1e31 Mon Sep 17 00:00:00 2001 From: duguorong Date: Thu, 13 Jul 2023 13:25:12 +0800 Subject: [PATCH 07/11] update the logic of handling error notice --- src/lib.rs | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 464b813..c463d37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,20 +7,9 @@ fn json_matches_schema(schema: Json, instance: Json) -> bool { if jsonschema::is_valid(&schema.0, &instance.0) { true } else { - let compiled = match jsonschema::JSONSchema::compile(&schema.0) { - Ok(c) => c, - Err(e) => { - // Only call notice! for a non empty instance_path - if e.instance_path.last().is_some() { - notice!( - "Invalid JSON schema at path: {}", - e.instance_path.to_string() - ); - } - return false; - } - }; - + // "jsonschema::is_valid(...)" already checks the validaity of JSON schema, and panics if it is invalid. + // Hence, we just unwrap the schema here. + let compiled = jsonschema::JSONSchema::compile(&schema.0).unwrap(); let err = compiled.validate(&instance.0).unwrap_err(); let _ = err .into_iter() @@ -35,20 +24,9 @@ fn jsonb_matches_schema(schema: Json, instance: JsonB) -> bool { if jsonschema::is_valid(&schema.0, &instance.0) { true } else { - let compiled = match jsonschema::JSONSchema::compile(&schema.0) { - Ok(c) => c, - Err(e) => { - // Only call notice! for a non empty instance_path - if e.instance_path.last().is_some() { - notice!( - "Invalid JSON schema at path: {}", - e.instance_path.to_string() - ); - } - return false; - } - }; - + // "jsonschema::is_valid(...)" already checks the validaity of JSON schema, and panics if it is invalid. + // Hence, we just unwrap the schema here. + let compiled = jsonschema::JSONSchema::compile(&schema.0).unwrap(); let err = compiled.validate(&instance.0).unwrap_err(); let _ = err .into_iter() From eb5dddae3926508751cab5d7803f0786d652f05b Mon Sep 17 00:00:00 2001 From: duguorong Date: Thu, 13 Jul 2023 13:29:55 +0800 Subject: [PATCH 08/11] fix the typo --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c463d37..2b170c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ fn json_matches_schema(schema: Json, instance: Json) -> bool { if jsonschema::is_valid(&schema.0, &instance.0) { true } else { - // "jsonschema::is_valid(...)" already checks the validaity of JSON schema, and panics if it is invalid. + // "jsonschema::is_valid(...)" already checks the validity of JSON schema, and panics if it is invalid. // Hence, we just unwrap the schema here. let compiled = jsonschema::JSONSchema::compile(&schema.0).unwrap(); let err = compiled.validate(&instance.0).unwrap_err(); @@ -24,7 +24,7 @@ fn jsonb_matches_schema(schema: Json, instance: JsonB) -> bool { if jsonschema::is_valid(&schema.0, &instance.0) { true } else { - // "jsonschema::is_valid(...)" already checks the validaity of JSON schema, and panics if it is invalid. + // "jsonschema::is_valid(...)" already checks the validity of JSON schema, and panics if it is invalid. // Hence, we just unwrap the schema here. let compiled = jsonschema::JSONSchema::compile(&schema.0).unwrap(); let err = compiled.validate(&instance.0).unwrap_err(); From 054e25ba8dda3d2ae65d9b41c0446266771f7247 Mon Sep 17 00:00:00 2001 From: duguorong Date: Wed, 19 Jul 2023 01:03:34 +0800 Subject: [PATCH 09/11] add the "jsonschema_validation_errors" function --- src/lib.rs | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2b170c3..d459e85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,36 +4,12 @@ pg_module_magic!(); #[pg_extern(immutable, strict)] fn json_matches_schema(schema: Json, instance: Json) -> bool { - if jsonschema::is_valid(&schema.0, &instance.0) { - true - } else { - // "jsonschema::is_valid(...)" already checks the validity of JSON schema, and panics if it is invalid. - // Hence, we just unwrap the schema here. - let compiled = jsonschema::JSONSchema::compile(&schema.0).unwrap(); - let err = compiled.validate(&instance.0).unwrap_err(); - let _ = err - .into_iter() - .for_each(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)); - - false - } + jsonschema::is_valid(&schema.0, &instance.0) } #[pg_extern(immutable, strict)] fn jsonb_matches_schema(schema: Json, instance: JsonB) -> bool { - if jsonschema::is_valid(&schema.0, &instance.0) { - true - } else { - // "jsonschema::is_valid(...)" already checks the validity of JSON schema, and panics if it is invalid. - // Hence, we just unwrap the schema here. - let compiled = jsonschema::JSONSchema::compile(&schema.0).unwrap(); - let err = compiled.validate(&instance.0).unwrap_err(); - let _ = err - .into_iter() - .for_each(|e| notice!("Invalid instance {} at {}", e.instance, e.instance_path)); - - false - } + jsonschema::is_valid(&schema.0, &instance.0) } #[pg_extern(immutable, strict)] @@ -53,6 +29,19 @@ fn jsonschema_is_valid(schema: Json) -> bool { } } +#[pg_extern(immutable, strict)] +fn jsonschema_validation_errors(schema: Json, instance: Json) -> Vec { + let schema = match jsonschema::JSONSchema::compile(&schema.0) { + Ok(s) => s, + Err(e) => return vec![e.to_string()], + }; + let errors = match schema.validate(&instance.0) { + Ok(_) => vec![], + Err(e) => e.into_iter().map(|e| e.to_string()).collect(), + }; + errors +} + #[pg_schema] #[cfg(any(test, feature = "pg_test"))] mod tests { From cca2542426cb07ac22aa12ec5a0cd69e33bf671b Mon Sep 17 00:00:00 2001 From: duguorong Date: Thu, 20 Jul 2023 14:16:17 +0800 Subject: [PATCH 10/11] add the unit tests --- src/lib.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index d459e85..627509c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,6 +145,54 @@ mod tests { "type": "obj" })))); } + + #[pg_test] + fn test_jsonschema_validation_errors_none() { + let errors = crate::jsonschema_validation_errors( + Json(json!({ "maxLength": 4 })), + Json(json!("foo")), + ); + assert!(errors.len() == 0); + } + + #[pg_test] + fn test_jsonschema_validation_erros_one() { + let errors = crate::jsonschema_validation_errors( + Json(json!({ "maxLength": 4 })), + Json(json!("123456789")), + ); + assert!(errors.len() == 1); + assert!(errors[0] == "\"123456789\" is longer than 4 characters".to_string()); + } + + #[pg_test] + fn test_jsonschema_validation_errors_multiple() { + let errors = crate::jsonschema_validation_errors( + Json(json!( + { + "type": "object", + "properties": { + "foo": { + "type": "string" + }, + "bar": { + "type": "number" + }, + "baz": { + "type": "boolean" + }, + "additionalProperties": false, + "required": ["foo", "bar", "baz"] + } + })), + Json(json!({"foo": 1, "bar": [], "bat": true})), + ); + assert!(errors.len() == 1); + assert!( + errors[0] + == "[\"foo\",\"bar\",\"baz\"] is not of types \"boolean\", \"object\"".to_string() + ); + } } #[cfg(test)] From ceecba39d8c3f34be49491cb8633627bb966f5df Mon Sep 17 00:00:00 2001 From: duguorong Date: Thu, 20 Jul 2023 22:18:17 +0800 Subject: [PATCH 11/11] update the unit tests --- src/lib.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 627509c..952dc8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -182,16 +182,15 @@ mod tests { "type": "boolean" }, "additionalProperties": false, - "required": ["foo", "bar", "baz"] } })), - Json(json!({"foo": 1, "bar": [], "bat": true})), - ); - assert!(errors.len() == 1); - assert!( - errors[0] - == "[\"foo\",\"bar\",\"baz\"] is not of types \"boolean\", \"object\"".to_string() + Json(json!({"foo": 1, "bar": [], "baz": "1"})), ); + + assert!(errors.len() == 3); + assert!(errors[0] == "[] is not of type \"number\"".to_string()); + assert!(errors[1] == "\"1\" is not of type \"boolean\"".to_string()); + assert!(errors[2] == "1 is not of type \"string\"".to_string()); } }