From d0e63c0c48ab0c6ee6936bcf300d8769d27e609a Mon Sep 17 00:00:00 2001 From: Dmitry Dygalo Date: Tue, 17 Sep 2024 00:07:18 +0200 Subject: [PATCH] feat: Rework public API Signed-off-by: Dmitry Dygalo --- README.md | 2 +- crates/benchmark-suite/benches/boon.rs | 2 +- crates/benchmark-suite/benches/jsonschema.rs | 11 +- crates/benchmark-suite/benches/valico.rs | 4 +- crates/jsonschema-cli/src/main.rs | 7 +- crates/jsonschema-py/src/lib.rs | 40 +- crates/jsonschema/benches/jsonschema.rs | 11 +- crates/jsonschema/benches/keywords.rs | 11 +- crates/jsonschema/src/compilation/mod.rs | 116 +-- crates/jsonschema/src/compilation/options.rs | 248 +++--- crates/jsonschema/src/error.rs | 23 +- crates/jsonschema/src/keywords/format.rs | 33 +- crates/jsonschema/src/keywords/items.rs | 4 +- crates/jsonschema/src/keywords/mod.rs | 21 +- crates/jsonschema/src/keywords/pattern.rs | 14 +- .../jsonschema/src/keywords/prefix_items.rs | 16 +- crates/jsonschema/src/keywords/ref_.rs | 6 +- .../src/keywords/unevaluated_properties.rs | 4 +- crates/jsonschema/src/lib.rs | 712 ++++++++++++++++-- crates/jsonschema/src/output.rs | 11 +- crates/jsonschema/tests/output.rs | 145 ++-- crates/jsonschema/tests/suite.rs | 34 +- fuzz/fuzz_targets/compile.rs | 2 +- fuzz/fuzz_targets/validation.rs | 8 +- 24 files changed, 1015 insertions(+), 470 deletions(-) diff --git a/README.md b/README.md index 8a4b0205..1550edcb 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ fn main() -> Result<(), Box> { assert!(jsonschema::is_valid(&schema, &instance)); // Build & reuse (faster) - let validator = jsonschema::compile(&schema) + let validator = jsonschema::validator_for(&schema) .expect("Invalid schema"); // Iterate over errors diff --git a/crates/benchmark-suite/benches/boon.rs b/crates/benchmark-suite/benches/boon.rs index 90ce3d5c..b57be6f9 100644 --- a/crates/benchmark-suite/benches/boon.rs +++ b/crates/benchmark-suite/benches/boon.rs @@ -12,7 +12,7 @@ fn bench_compile(c: &mut Criterion, name: &str, schema: &Value) { b.iter(|| { compiler .compile("schema.json", &mut Schemas::new()) - .expect("Failed to compiled"); + .expect("Failed to compile"); }) }); } diff --git a/crates/benchmark-suite/benches/jsonschema.rs b/crates/benchmark-suite/benches/jsonschema.rs index 6778efba..69d2d807 100644 --- a/crates/benchmark-suite/benches/jsonschema.rs +++ b/crates/benchmark-suite/benches/jsonschema.rs @@ -1,35 +1,34 @@ use benchmark::Benchmark; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use jsonschema::JSONSchema; use serde_json::Value; fn bench_compile(c: &mut Criterion, name: &str, schema: &Value) { c.bench_function(&format!("jsonschema/{}/compile", name), |b| { - b.iter(|| JSONSchema::compile(schema).expect("Valid schema")) + b.iter(|| jsonschema::validator_for(schema).expect("Valid schema")) }); } fn bench_is_valid(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) { - let compiled = JSONSchema::compile(schema).expect("Valid schema"); + let validator = jsonschema::validator_for(schema).expect("Valid schema"); c.bench_with_input( BenchmarkId::new(name, "is_valid"), instance, |b, instance| { b.iter(|| { - let _ = compiled.is_valid(instance); + let _ = validator.is_valid(instance); }) }, ); } fn bench_validate(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) { - let compiled = JSONSchema::compile(schema).expect("Valid schema"); + let validator = jsonschema::validator_for(schema).expect("Valid schema"); c.bench_with_input( BenchmarkId::new(name, "validate"), instance, |b, instance| { b.iter(|| { - let _ = compiled.validate(instance); + let _ = validator.validate(instance); }) }, ); diff --git a/crates/benchmark-suite/benches/valico.rs b/crates/benchmark-suite/benches/valico.rs index f191048e..c16f70ff 100644 --- a/crates/benchmark-suite/benches/valico.rs +++ b/crates/benchmark-suite/benches/valico.rs @@ -16,7 +16,7 @@ fn bench_compile(c: &mut Criterion, name: &str, schema: &Value) { fn bench_validate(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) { let mut scope = json_schema::Scope::new(); - let compiled = scope + let validator = scope .compile_and_return(schema.clone(), false) .expect("Valid schema"); c.bench_with_input( @@ -24,7 +24,7 @@ fn bench_validate(c: &mut Criterion, name: &str, schema: &Value, instance: &Valu instance, |b, instance| { b.iter(|| { - compiled.validate(instance).is_valid(); + validator.validate(instance).is_valid(); }) }, ); diff --git a/crates/jsonschema-cli/src/main.rs b/crates/jsonschema-cli/src/main.rs index ed5a9507..bf8d8dda 100644 --- a/crates/jsonschema-cli/src/main.rs +++ b/crates/jsonschema-cli/src/main.rs @@ -7,7 +7,6 @@ use std::{ }; use clap::Parser; -use jsonschema::JSONSchema; #[derive(Parser)] #[command(name = "jsonschema")] @@ -40,11 +39,11 @@ fn validate_instances( let mut success = true; let schema_json = read_json(schema_path)??; - match JSONSchema::compile(&schema_json) { - Ok(schema) => { + match jsonschema::validator_for(&schema_json) { + Ok(validator) => { for instance in instances { let instance_json = read_json(instance)??; - let validation = schema.validate(&instance_json); + let validation = validator.validate(&instance_json); let filename = instance.to_string_lossy(); match validation { Ok(()) => println!("{filename} - VALID"), diff --git a/crates/jsonschema-py/src/lib.rs b/crates/jsonschema-py/src/lib.rs index 89c0d606..cc21585e 100644 --- a/crates/jsonschema-py/src/lib.rs +++ b/crates/jsonschema-py/src/lib.rs @@ -126,7 +126,7 @@ fn make_options( draft: Option, formats: Option<&Bound<'_, PyDict>>, ) -> PyResult { - let mut options = jsonschema::JSONSchema::options(); + let mut options = jsonschema::options(); if let Some(raw_draft_version) = draft { options.with_draft(get_draft(raw_draft_version)?); } @@ -166,14 +166,14 @@ fn make_options( fn iter_on_error( py: Python<'_>, - compiled: &jsonschema::JSONSchema, + validator: &jsonschema::JSONSchema, instance: &Bound<'_, PyAny>, ) -> PyResult { let instance = ser::to_value(instance)?; let mut pyerrors = vec![]; panic::catch_unwind(AssertUnwindSafe(|| { - if let Err(errors) = compiled.validate(&instance) { + if let Err(errors) = validator.validate(&instance) { for error in errors { pyerrors.push(into_py_err(py, error)?); } @@ -188,11 +188,11 @@ fn iter_on_error( fn raise_on_error( py: Python<'_>, - compiled: &jsonschema::JSONSchema, + validator: &jsonschema::JSONSchema, instance: &Bound<'_, PyAny>, ) -> PyResult<()> { let instance = ser::to_value(instance)?; - let result = panic::catch_unwind(AssertUnwindSafe(|| compiled.validate(&instance))) + let result = panic::catch_unwind(AssertUnwindSafe(|| validator.validate(&instance))) .map_err(handle_format_checked_panic)?; let error = result .err() @@ -277,9 +277,9 @@ fn is_valid( let options = make_options(draft, formats)?; let schema = ser::to_value(schema)?; match options.compile(&schema) { - Ok(compiled) => { + Ok(validator) => { let instance = ser::to_value(instance)?; - panic::catch_unwind(AssertUnwindSafe(|| Ok(compiled.is_valid(&instance)))) + panic::catch_unwind(AssertUnwindSafe(|| Ok(validator.is_valid(&instance)))) .map_err(handle_format_checked_panic)? } Err(error) => Err(into_py_err(py, error)?), @@ -311,7 +311,7 @@ fn validate( let options = make_options(draft, formats)?; let schema = ser::to_value(schema)?; match options.compile(&schema) { - Ok(compiled) => raise_on_error(py, &compiled, instance), + Ok(validator) => raise_on_error(py, &validator, instance), Err(error) => Err(into_py_err(py, error)?), } } @@ -340,17 +340,17 @@ fn iter_errors( let options = make_options(draft, formats)?; let schema = ser::to_value(schema)?; match options.compile(&schema) { - Ok(compiled) => iter_on_error(py, &compiled, instance), + Ok(validator) => iter_on_error(py, &validator, instance), Err(error) => Err(into_py_err(py, error)?), } } /// JSONSchema(schema, draft=None, with_meta_schemas=False) /// -/// JSON Schema compiled into a validation tree. +/// A JSON Schema validator. /// -/// >>> compiled = JSONSchema({"minimum": 5}) -/// >>> compiled.is_valid(3) +/// >>> validator = JSONSchema({"minimum": 5}) +/// >>> validator.is_valid(3) /// False /// /// By default Draft 7 will be used for compilation. @@ -409,7 +409,7 @@ impl JSONSchema { /// /// Create `JSONSchema` from a serialized JSON string. /// - /// >>> compiled = JSONSchema.from_str('{"minimum": 5}') + /// >>> validator = JSONSchema.from_str('{"minimum": 5}') /// /// Use it if you have your schema as a string and want to utilize Rust JSON parsing. #[classmethod] @@ -451,10 +451,10 @@ impl JSONSchema { /// is_valid(instance) /// - /// Perform fast validation against the compiled schema. + /// Perform fast validation against the schema. /// - /// >>> compiled = JSONSchema({"minimum": 5}) - /// >>> compiled.is_valid(3) + /// >>> validator = JSONSchema({"minimum": 5}) + /// >>> validator.is_valid(3) /// False /// /// The output is a boolean value, that indicates whether the instance is valid or not. @@ -469,8 +469,8 @@ impl JSONSchema { /// /// Validate the input instance and raise `ValidationError` in the error case /// - /// >>> compiled = JSONSchema({"minimum": 5}) - /// >>> compiled.validate(3) + /// >>> validator = JSONSchema({"minimum": 5}) + /// >>> validator.validate(3) /// ... /// ValidationError: 3 is less than the minimum of 5 /// @@ -484,8 +484,8 @@ impl JSONSchema { /// /// Iterate the validation errors of the input instance /// - /// >>> compiled = JSONSchema({"minimum": 5}) - /// >>> next(compiled.iter_errors(3)) + /// >>> validator = JSONSchema({"minimum": 5}) + /// >>> next(validator.iter_errors(3)) /// ... /// ValidationError: 3 is less than the minimum of 5 #[pyo3(text_signature = "(instance)")] diff --git a/crates/jsonschema/benches/jsonschema.rs b/crates/jsonschema/benches/jsonschema.rs index e84f0acd..113aa96b 100644 --- a/crates/jsonschema/benches/jsonschema.rs +++ b/crates/jsonschema/benches/jsonschema.rs @@ -1,35 +1,34 @@ use benchmark::Benchmark; use codspeed_criterion_compat::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use jsonschema::JSONSchema; use serde_json::Value; fn bench_compile(c: &mut Criterion, name: &str, schema: &Value) { c.bench_function(&format!("{}/compile", name), |b| { - b.iter(|| JSONSchema::compile(schema).expect("Valid schema")) + b.iter(|| jsonschema::validator_for(schema).expect("Valid schema")) }); } fn bench_is_valid(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) { - let compiled = JSONSchema::compile(schema).expect("Valid schema"); + let validator = jsonschema::validator_for(schema).expect("Valid schema"); c.bench_with_input( BenchmarkId::new(name, "is_valid"), instance, |b, instance| { b.iter(|| { - let _ = compiled.is_valid(instance); + let _ = validator.is_valid(instance); }) }, ); } fn bench_validate(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) { - let compiled = JSONSchema::compile(schema).expect("Valid schema"); + let validator = jsonschema::validator_for(schema).expect("Valid schema"); c.bench_with_input( BenchmarkId::new(name, "validate"), instance, |b, instance| { b.iter(|| { - let _ = compiled.validate(instance); + let _ = validator.validate(instance); }) }, ); diff --git a/crates/jsonschema/benches/keywords.rs b/crates/jsonschema/benches/keywords.rs index f39ff7a3..a412f65f 100644 --- a/crates/jsonschema/benches/keywords.rs +++ b/crates/jsonschema/benches/keywords.rs @@ -1,35 +1,34 @@ use benchmark::run_keyword_benchmarks; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use jsonschema::JSONSchema; use serde_json::Value; fn bench_keyword_compile(c: &mut Criterion, name: &str, schema: &Value) { c.bench_function(&format!("keyword/{}/compile", name), |b| { - b.iter(|| JSONSchema::compile(schema).expect("Valid schema")) + b.iter(|| jsonschema::validator_for(schema).expect("Valid schema")) }); } fn bench_keyword_is_valid(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) { - let compiled = JSONSchema::compile(schema).expect("Valid schema"); + let validator = jsonschema::validator_for(schema).expect("Valid schema"); c.bench_with_input( BenchmarkId::new(format!("keyword/{}", name), "is_valid"), instance, |b, instance| { b.iter(|| { - let _ = compiled.is_valid(instance); + let _ = validator.is_valid(instance); }) }, ); } fn bench_keyword_validate(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) { - let compiled = JSONSchema::compile(schema).expect("Valid schema"); + let validator = jsonschema::validator_for(schema).expect("Valid schema"); c.bench_with_input( BenchmarkId::new(format!("keyword/{}", name), "validate"), instance, |b, instance| { b.iter(|| { - let _ = compiled.validate(instance); + let _ = validator.validate(instance); }) }, ); diff --git a/crates/jsonschema/src/compilation/mod.rs b/crates/jsonschema/src/compilation/mod.rs index eba99154..bf2797fb 100644 --- a/crates/jsonschema/src/compilation/mod.rs +++ b/crates/jsonschema/src/compilation/mod.rs @@ -55,7 +55,7 @@ impl JSONSchema { /// ```rust /// # use crate::jsonschema::{Draft, JSONSchema}; /// # let schema = serde_json::json!({}); - /// let maybe_jsonschema: Result = JSONSchema::options() + /// let validator = jsonschema::options() /// .with_draft(Draft::Draft7) /// .compile(&schema); /// ``` @@ -104,40 +104,49 @@ impl JSONSchema { /// "basic" output format /// /// ```rust - /// # use crate::jsonschema::{Draft, JSONSchema, output::{Output, BasicOutput}}; - /// let schema_json = serde_json::json!({ + /// # fn main() -> Result<(), Box> { + /// use serde_json::json; + /// + /// let schema = json!({ /// "title": "string value", /// "type": "string" /// }); - /// let instance = serde_json::json!{"some string"}; - /// let schema = JSONSchema::options().compile(&schema_json).unwrap(); - /// let output: BasicOutput = schema.apply(&instance).basic(); - /// let output_json = serde_json::to_value(output).unwrap(); - /// assert_eq!(output_json, serde_json::json!({ - /// "valid": true, - /// "annotations": [ - /// { - /// "keywordLocation": "", - /// "instanceLocation": "", - /// "annotations": { - /// "title": "string value" + /// let instance = json!("some string"); + /// + /// let validator = jsonschema::validator_for(&schema) + /// .expect("Invalid schema"); + /// + /// let output = validator.apply(&instance).basic(); + /// assert_eq!( + /// serde_json::to_value(output)?, + /// json!({ + /// "valid": true, + /// "annotations": [ + /// { + /// "keywordLocation": "", + /// "instanceLocation": "", + /// "annotations": { + /// "title": "string value" + /// } /// } - /// } - /// ] - /// })); + /// ] + /// }) + /// ); + /// # Ok(()) + /// # } /// ``` #[must_use] pub const fn apply<'a, 'b>(&'a self, instance: &'b Value) -> Output<'a, 'b> { Output::new(self, &self.node, instance) } - /// The [`Draft`] which this schema was compiled against + /// The [`Draft`] which was used to build this validator. #[must_use] pub fn draft(&self) -> Draft { self.config.draft() } - /// The [`CompilationOptions`] that were used to compile this schema + /// The [`CompilationOptions`] that were used to build this validator. #[must_use] pub fn config(&self) -> Arc { Arc::clone(&self.config) @@ -259,7 +268,6 @@ pub(crate) fn compile_validators<'a>( #[cfg(test)] mod tests { - use super::JSONSchema; use crate::{ error::{self, no_error, ValidationError}, keywords::custom::Keyword, @@ -287,38 +295,38 @@ mod tests { fn only_keyword() { // When only one keyword is specified let schema = json!({"type": "string"}); - let compiled = JSONSchema::compile(&schema).unwrap(); + let validator = crate::validator_for(&schema).unwrap(); let value1 = json!("AB"); let value2 = json!(1); // And only this validator - assert_eq!(compiled.node.validators().len(), 1); - assert!(compiled.validate(&value1).is_ok()); - assert!(compiled.validate(&value2).is_err()); + assert_eq!(validator.node.validators().len(), 1); + assert!(validator.validate(&value1).is_ok()); + assert!(validator.validate(&value2).is_err()); } #[test] fn validate_ref() { let schema = load("tests/suite/tests/draft7/ref.json", 1); let value = json!({"bar": 3}); - let compiled = JSONSchema::compile(&schema).unwrap(); - assert!(compiled.validate(&value).is_ok()); + let validator = crate::validator_for(&schema).unwrap(); + assert!(validator.validate(&value).is_ok()); let value = json!({"bar": true}); - assert!(compiled.validate(&value).is_err()); + assert!(validator.validate(&value).is_err()); } #[test] fn wrong_schema_type() { let schema = json!([1]); - let compiled = JSONSchema::compile(&schema); - assert!(compiled.is_err()); + let validator = crate::validator_for(&schema); + assert!(validator.is_err()); } #[test] fn multiple_errors() { let schema = json!({"minProperties": 2, "propertyNames": {"minLength": 3}}); let value = json!({"a": 3}); - let compiled = JSONSchema::compile(&schema).unwrap(); - let result = compiled.validate(&value); + let validator = crate::validator_for(&schema).unwrap(); + let result = validator.validate(&value); let errors: Vec = result.unwrap_err().collect(); assert_eq!(errors.len(), 2); assert_eq!( @@ -387,30 +395,30 @@ mod tests { // Define a JSON schema that enforces the top level object has ASCII keys and has at least 1 property let schema = json!({ "custom-object-type": "ascii-keys", "type": "object", "minProperties": 1 }); - let compiled = JSONSchema::options() + let validator = crate::options() .with_keyword("custom-object-type", custom_object_type_factory) .compile(&schema) .unwrap(); // Verify schema validation detects object with too few properties let instance = json!({}); - assert!(compiled.validate(&instance).is_err()); - assert!(!compiled.is_valid(&instance)); + assert!(validator.validate(&instance).is_err()); + assert!(!validator.is_valid(&instance)); // Verify validator succeeds on a valid custom-object-type let instance = json!({ "a" : 1 }); - assert!(compiled.validate(&instance).is_ok()); - assert!(compiled.is_valid(&instance)); + assert!(validator.validate(&instance).is_ok()); + assert!(validator.is_valid(&instance)); // Verify validator detects invalid custom-object-type let instance = json!({ "å" : 1 }); - let error = compiled + let error = validator .validate(&instance) .expect_err("Should fail") .next() .expect("Not empty"); assert_eq!(error.to_string(), "Key is not ASCII"); - assert!(!compiled.is_valid(&instance)); + assert!(!validator.is_valid(&instance)); } #[test] @@ -510,7 +518,7 @@ mod tests { // Schema includes both the custom format and the overridden keyword let schema = json!({ "minimum": 2, "type": "string", "format": "currency" }); - let compiled = JSONSchema::options() + let validator = crate::options() .with_format("currency", currency_format_checker) .with_keyword("minimum", custom_minimum_factory) .with_keyword("minimum-2", |_, _, _| todo!()) @@ -519,27 +527,27 @@ mod tests { // Control: verify schema validation rejects non-string types let instance = json!(15); - assert!(compiled.validate(&instance).is_err()); - assert!(!compiled.is_valid(&instance)); + assert!(validator.validate(&instance).is_err()); + assert!(!validator.is_valid(&instance)); // Control: verify validator rejects ill-formatted strings let instance = json!("not a currency"); - assert!(compiled.validate(&instance).is_err()); - assert!(!compiled.is_valid(&instance)); + assert!(validator.validate(&instance).is_err()); + assert!(!validator.is_valid(&instance)); // Verify validator allows properly formatted strings that conform to custom keyword let instance = json!("3.00"); - assert!(compiled.validate(&instance).is_ok()); - assert!(compiled.is_valid(&instance)); + assert!(validator.validate(&instance).is_ok()); + assert!(validator.is_valid(&instance)); // Verify validator rejects properly formatted strings that do not conform to custom keyword let instance = json!("1.99"); - assert!(compiled.validate(&instance).is_err()); - assert!(!compiled.is_valid(&instance)); + assert!(validator.validate(&instance).is_err()); + assert!(!validator.is_valid(&instance)); // Define another schema that applies "minimum" to an integer to ensure original behavior let schema = json!({ "minimum": 2, "type": "integer" }); - let compiled = JSONSchema::options() + let validator = crate::options() .with_format("currency", currency_format_checker) .with_keyword("minimum", custom_minimum_factory) .compile(&schema) @@ -547,17 +555,17 @@ mod tests { // Verify schema allows integers greater than 2 let instance = json!(3); - assert!(compiled.validate(&instance).is_ok()); - assert!(compiled.is_valid(&instance)); + assert!(validator.validate(&instance).is_ok()); + assert!(validator.is_valid(&instance)); // Verify schema rejects integers less than 2 let instance = json!(1); - assert!(compiled.validate(&instance).is_err()); - assert!(!compiled.is_valid(&instance)); + assert!(validator.validate(&instance).is_err()); + assert!(!validator.is_valid(&instance)); // Invalid `minimum` value let schema = json!({ "minimum": "foo" }); - let error = JSONSchema::options() + let error = crate::options() .with_keyword("minimum", custom_minimum_factory) .compile(&schema) .expect_err("Should fail"); diff --git a/crates/jsonschema/src/compilation/options.rs b/crates/jsonschema/src/compilation/options.rs index 30fb7064..2c817311 100644 --- a/crates/jsonschema/src/compilation/options.rs +++ b/crates/jsonschema/src/compilation/options.rs @@ -162,26 +162,26 @@ static META_SCHEMA_VALIDATORS: Lazy> = Lazy let mut store = AHashMap::with_capacity(3); store.insert( schemas::Draft::Draft4, - JSONSchema::options() + crate::options() .without_schema_validation() .compile(&DRAFT4) .expect(EXPECT_MESSAGE), ); store.insert( schemas::Draft::Draft6, - JSONSchema::options() + crate::options() .without_schema_validation() .compile(&DRAFT6) .expect(EXPECT_MESSAGE), ); store.insert( schemas::Draft::Draft7, - JSONSchema::options() + crate::options() .without_schema_validation() .compile(&DRAFT7) .expect(EXPECT_MESSAGE), ); - let mut options = JSONSchema::options(); + let mut options = crate::options(); options.store.insert( "https://json-schema.org/draft/2019-09/meta/applicator".into(), Arc::clone(&DRAFT201909_APPLICATOR), @@ -213,7 +213,7 @@ static META_SCHEMA_VALIDATORS: Lazy> = Lazy .compile(&DRAFT201909) .expect(EXPECT_MESSAGE), ); - let mut options = JSONSchema::options(); + let mut options = crate::options(); options.store.insert( "https://json-schema.org/draft/2020-12/meta/applicator".into(), Arc::clone(&DRAFT202012_APPLICATOR), @@ -256,10 +256,7 @@ static META_SCHEMA_VALIDATORS: Lazy> = Lazy store }); -/// Full configuration to guide the `JSONSchema` compilation. -/// -/// Using a `CompilationOptions` instance you can configure the supported draft, -/// content media types and more (check the exposed methods) +/// Configuration options for JSON Schema validation. #[derive(Clone)] pub struct CompilationOptions { external_resolver: Arc, @@ -293,11 +290,25 @@ impl Default for CompilationOptions { } impl CompilationOptions { + /// Return the draft version, or the default if not set. pub(crate) fn draft(&self) -> schemas::Draft { self.draft.unwrap_or_default() } - - /// Compile `schema` into `JSONSchema` using the currently defined options. + /// Build a JSON Schema validator using the current options. + /// + /// # Example + /// + /// ```rust + /// use serde_json::json; + /// + /// let schema = json!({"type": "string"}); + /// let validator = jsonschema::options() + /// .compile(&schema) + /// .expect("A valid schema"); + /// + /// assert!(validator.is_valid(&json!("Hello"))); + /// assert!(!validator.is_valid(&json!(42))); + /// ``` pub fn compile<'a>( &self, schema: &'a serde_json::Value, @@ -353,12 +364,13 @@ impl CompilationOptions { Ok(JSONSchema { node, config }) } - /// Ensure that the schema is going to be compiled using the defined Draft. + /// Sets the JSON Schema draft version. /// /// ```rust - /// # use jsonschema::{Draft, CompilationOptions}; - /// # let mut options = CompilationOptions::default(); - /// options.with_draft(Draft::Draft4); + /// use jsonschema::Draft; + /// + /// let options = jsonschema::options() + /// .with_draft(Draft::Draft4); /// ``` #[inline] pub fn with_draft(&mut self, draft: schemas::Draft) -> &mut Self { @@ -377,24 +389,17 @@ impl CompilationOptions { } } - /// Ensure that compiled schema is going to support the provided content media type. + /// Add support for a custom content media type validation. /// - /// Arguments: - /// * `media_type`: Name of the content media type to support (ie. "application/json") - /// * `media_type_check`: Method checking the validity of the input string according to - /// the media type. - /// The method should return `true` if the input is valid, `false` otherwise. + /// # Example /// - /// Example: /// ```rust - /// # use jsonschema::CompilationOptions; - /// # let mut options = CompilationOptions::default(); /// fn check_custom_media_type(instance_string: &str) -> bool { - /// // In reality the check might be a bit more different ;) - /// instance_string != "not good" + /// instance_string.starts_with("custom:") /// } - /// // Add support for application/jsonschema-test - /// options.with_content_media_type("application/jsonschema-test", check_custom_media_type); + /// + /// let options = jsonschema::options() + /// .with_content_media_type("application/custom", check_custom_media_type); /// ``` pub fn with_content_media_type( &mut self, @@ -406,20 +411,13 @@ impl CompilationOptions { self } - /// Use a custom resolver for resolving external schema references. + /// Set a custom resolver for external references. pub fn with_resolver(&mut self, resolver: impl SchemaResolver + 'static) -> &mut Self { self.external_resolver = Arc::new(resolver); self } - /// Ensure that compiled schema is not supporting the provided content media type. - /// - /// ```rust - /// # use jsonschema::CompilationOptions; - /// # let mut options = CompilationOptions::default(); - /// // Disable support for application/json (which is supported by jsonschema crate) - /// options.without_content_media_type_support("application/json"); - /// ``` + /// Remove support for a specific content media type validation. pub fn without_content_media_type_support(&mut self, media_type: &'static str) -> &mut Self { self.content_media_type_checks.insert(media_type, None); self @@ -463,71 +461,54 @@ impl CompilationOptions { None } } - - /// Ensure that compiled schema is going to support the provided content encoding. - /// - /// Arguments: - /// * `content_encoding`: Name of the content encoding to support (ie. "base64") - /// * `content_encoding_check`: Method checking the validity of the input string - /// according to content encoding. - /// The method should return `true` if the input is valid, `false` otherwise. - /// * `content_encoding_converter`: Method converting the input string into a string - /// representation (generally output of the decoding of the content encoding). - /// The method should return: - /// * `Err(ValidationError instance)`: in case of a `jsonschema` crate supported error (obtained via `?` or `From::from` APIs) - /// * `Ok(None)`: if the input string is not valid according to the content encoding - /// * `Ok(Some(content))`: if the input string is valid according to the content encoding, `content` will contain - /// the string representation of the decoded input - /// - /// Example: + /// Add support for a custom content encoding. + /// + /// # Arguments + /// + /// * `encoding`: Name of the content encoding (e.g., "base64") + /// * `check`: Validates the input string (return `true` if valid) + /// * `converter`: Converts the input string, returning: + /// - `Err(ValidationError)`: For supported errors + /// - `Ok(None)`: If input is invalid + /// - `Ok(Some(content))`: If valid, with decoded content + /// + /// # Example + /// /// ```rust - /// # use jsonschema::{CompilationOptions, ValidationError}; - /// # let mut options = CompilationOptions::default(); - /// // The instance_string contains a number (representing the length of the string) - /// // a space and then the string (whose length should match the expectation). - /// // Example: "3 The" or "4 123" - /// fn check_custom_encoding(instance_string: &str) -> bool { - /// if let Some(first_space_index) = instance_string.find(' ') { - /// if let Ok(value) = instance_string[..first_space_index].parse::() { - /// return instance_string[first_space_index + 1..].chars().count() == value as usize; - /// } - /// } - /// false + /// use jsonschema::ValidationError; + /// + /// fn check(s: &str) -> bool { + /// s.starts_with("valid:") /// } - /// fn converter_custom_encoding(instance_string: &str) -> Result, ValidationError<'static>> { - /// if let Some(first_space_index) = instance_string.find(' ') { - /// if let Ok(value) = instance_string[..first_space_index].parse::() { - /// if instance_string[first_space_index + 1..].chars().count() == value as usize { - /// return Ok(Some(instance_string[first_space_index + 1..].to_string())); - /// } - /// } + /// + /// fn convert(s: &str) -> Result, ValidationError<'static>> { + /// if s.starts_with("valid:") { + /// Ok(Some(s[6..].to_string())) + /// } else { + /// Ok(None) /// } - /// Ok(None) /// } - /// // Add support for prefix-length-string - /// options.with_content_encoding("prefix-length-string", check_custom_encoding, converter_custom_encoding); + /// + /// let options = jsonschema::options() + /// .with_content_encoding("custom", check, convert); /// ``` pub fn with_content_encoding( &mut self, - content_encoding: &'static str, - content_encoding_check: ContentEncodingCheckType, - content_encoding_converter: ContentEncodingConverterType, + encoding: &'static str, + check: ContentEncodingCheckType, + converter: ContentEncodingConverterType, ) -> &mut Self { - self.content_encoding_checks_and_converters.insert( - content_encoding, - Some((content_encoding_check, content_encoding_converter)), - ); + self.content_encoding_checks_and_converters + .insert(encoding, Some((check, converter))); self } - - /// Ensure that compiled schema is not supporting the provided content encoding. + /// Remove support for a specific content encoding. + /// + /// # Example /// /// ```rust - /// # use jsonschema::CompilationOptions; - /// # use serde_json::Value; - /// # let mut options = CompilationOptions::default(); - /// // Disable support for base64 (which is supported by jsonschema crate) - /// options.without_content_encoding_support("base64"); + /// let options = jsonschema::options() + /// .without_content_encoding_support("base64"); /// ``` pub fn without_content_encoding_support( &mut self, @@ -537,7 +518,6 @@ impl CompilationOptions { .insert(content_encoding, None); self } - /// Add meta schemas for supported JSON Schema drafts. /// It is helpful if your schema has references to JSON Schema meta-schemas: /// @@ -560,20 +540,19 @@ impl CompilationOptions { pub fn with_meta_schemas(&mut self) -> &mut Self { self } - - /// Add a new document to the store. It works as a cache to avoid making additional network - /// calls to remote schemas via the `$ref` keyword. + /// Add a document to the store. + /// + /// Acts as a cache to avoid network calls for remote schemas referenced by `$ref`. #[inline] pub fn with_document(&mut self, id: String, document: serde_json::Value) -> &mut Self { self.store.insert(id.into(), Arc::new(document)); self } - /// Register a custom "format" validator. + /// Register a custom format validator. /// - /// ## Example + /// # Example /// /// ```rust - /// # use jsonschema::JSONSchema; /// # use serde_json::json; /// fn my_format(s: &str) -> bool { /// // Your awesome format check! @@ -581,18 +560,15 @@ impl CompilationOptions { /// } /// # fn foo() { /// let schema = json!({"type": "string", "format": "custom"}); - /// let compiled = JSONSchema::options() + /// let validator = jsonschema::options() /// .with_format("custom", my_format) /// .compile(&schema) /// .expect("Valid schema"); - /// // Invalid string - /// assert!(!compiled.is_valid(&json!("foo"))); - /// // Valid string - /// assert!(compiled.is_valid(&json!("foo42!"))); + /// + /// assert!(!validator.is_valid(&json!("foo"))); + /// assert!(validator.is_valid(&json!("foo42!"))); /// # } /// ``` - /// - /// The format check function should receive `&str` and return `bool`. pub fn with_format(&mut self, name: N, format: F) -> &mut Self where N: Into, @@ -604,51 +580,48 @@ impl CompilationOptions { pub(crate) fn get_format(&self, format: &str) -> Option<(&String, &Arc)> { self.formats.get_key_value(format) } - /// Do not perform schema validation during compilation. - /// This method is only used to disable meta-schema validation for meta-schemas itself to avoid - /// infinite recursion. - /// The end-user will still receive `ValidationError` that are crafted manually during - /// compilation. + /// Disable schema validation during compilation. + /// + /// Used internally to prevent infinite recursion when validating meta-schemas. + /// **Note**: Manually-crafted `ValidationError`s may still occur during compilation. #[inline] pub(crate) fn without_schema_validation(&mut self) -> &mut Self { self.validate_schema = false; self } + /// Set whether to validate formats. + /// + /// Default behavior depends on the draft version. This method overrides + /// the default, enabling or disabling format validation regardless of draft. #[inline] - /// Force enable or disable format validation. - /// The default behavior is dependent on draft version, but the default behavior can be - /// overridden to validate or not regardless of draft. - pub fn should_validate_formats(&mut self, validate_formats: bool) -> &mut Self { - self.validate_formats = Some(validate_formats); + pub fn should_validate_formats(&mut self, yes: bool) -> &mut Self { + self.validate_formats = Some(yes); self } pub(crate) fn validate_formats(&self) -> bool { self.validate_formats .unwrap_or_else(|| self.draft().validate_formats_by_default()) } - - /// Set the `false` if unrecognized formats should be reported as a validation error. - /// By default unknown formats are silently ignored. - pub fn should_ignore_unknown_formats( - &mut self, - should_ignore_unknown_formats: bool, - ) -> &mut Self { - self.ignore_unknown_formats = should_ignore_unknown_formats; + /// Set whether to ignore unknown formats. + /// + /// By default, unknown formats are silently ignored. Set to `false` to report + /// unrecognized formats as validation errors. + pub fn should_ignore_unknown_formats(&mut self, yes: bool) -> &mut Self { + self.ignore_unknown_formats = yes; self } pub(crate) const fn are_unknown_formats_ignored(&self) -> bool { self.ignore_unknown_formats } - - /// Register a custom keyword definition. + /// Register a custom keyword validator. /// /// ## Example /// /// ```rust /// # use jsonschema::{ /// # paths::{JSONPointer, JsonPointerNode}, - /// # ErrorIterator, JSONSchema, Keyword, ValidationError, + /// # ErrorIterator, Keyword, ValidationError, /// # }; /// # use serde_json::{json, Map, Value}; /// # use std::iter::once; @@ -689,12 +662,13 @@ impl CompilationOptions { /// Ok(Box::new(MyCustomValidator)) /// } /// - /// assert!(JSONSchema::options() + /// let validator = jsonschema::options() /// .with_keyword("my-type", custom_validator_factory) /// .with_keyword("my-type-with-closure", |_, _, _| Ok(Box::new(MyCustomValidator))) /// .compile(&json!({ "my-type": "my-schema"})) - /// .expect("A valid schema") - /// .is_valid(&json!({ "a": "b"}))); + /// .expect("A valid schema"); + /// + /// assert!(validator.is_valid(&json!({ "a": "b"}))); /// ``` pub fn with_keyword(&mut self, name: N, factory: F) -> &mut Self where @@ -733,7 +707,7 @@ impl fmt::Debug for CompilationOptions { #[cfg(test)] mod tests { use super::CompilationOptions; - use crate::{schemas::Draft, JSONSchema}; + use crate::schemas::Draft; use serde_json::{json, Value}; use test_case::test_case; @@ -748,22 +722,22 @@ mod tests { if let Some(draft_version) = draft_version_in_options { options.with_draft(draft_version); } - let compiled = options.compile(schema).unwrap(); - compiled.draft() + let validator = options.compile(schema).unwrap(); + validator.draft() } #[test] fn test_with_document() { let schema = json!({"$ref": "http://example.json/schema.json#/rule"}); - let compiled = JSONSchema::options() + let validator = crate::options() .with_document( "http://example.json/schema.json".to_string(), json!({"rule": {"minLength": 5}}), ) .compile(&schema) .expect("Valid schema"); - assert!(!compiled.is_valid(&json!("foo"))); - assert!(compiled.is_valid(&json!("foobar"))); + assert!(!validator.is_valid(&json!("foo"))); + assert!(validator.is_valid(&json!("foobar"))); } fn custom(s: &str) -> bool { @@ -773,11 +747,11 @@ mod tests { #[test] fn custom_format() { let schema = json!({"type": "string", "format": "custom"}); - let compiled = JSONSchema::options() + let validator = crate::options() .with_format("custom", custom) .compile(&schema) .expect("Valid schema"); - assert!(!compiled.is_valid(&json!("foo"))); - assert!(compiled.is_valid(&json!("foo42!"))); + assert!(!validator.is_valid(&json!("foo"))); + assert!(validator.is_valid(&json!("foo42!"))); } } diff --git a/crates/jsonschema/src/error.rs b/crates/jsonschema/src/error.rs index a226bf57..90ee0fce 100644 --- a/crates/jsonschema/src/error.rs +++ b/crates/jsonschema/src/error.rs @@ -35,13 +35,12 @@ pub struct ValidationError<'a> { /// # Examples /// /// ```rust -/// use jsonschema::JSONSchema; /// use serde_json::json; /// /// let schema = json!({"maxLength": 5}); /// let instance = json!("foo"); -/// if let Ok(compiled) = JSONSchema::compile(&schema) { -/// let result = compiled.validate(&instance); +/// if let Ok(validator) = jsonschema::validator_for(&schema) { +/// let result = validator.validate(&instance); /// if let Err(errors) = result { /// for error in errors { /// println!("Validation error: {}", error) @@ -1022,7 +1021,7 @@ impl fmt::Display for ValidationError<'_> { #[cfg(test)] mod tests { use super::*; - use crate::{paths::PathChunk, JSONSchema}; + use crate::paths::PathChunk; use serde_json::json; use test_case::test_case; @@ -1071,8 +1070,8 @@ mod tests { } } ); - let compiled = JSONSchema::compile(&schema).unwrap(); - let mut result = compiled.validate(instance).expect_err("error iterator"); + let validator = crate::validator_for(&schema).unwrap(); + let mut result = validator.validate(instance).expect_err("error iterator"); let error = result.next().expect("validation error"); assert!(result.next().is_none()); @@ -1112,8 +1111,8 @@ mod tests { ] } ); - let compiled = JSONSchema::compile(&schema).unwrap(); - let mut result = compiled.validate(instance).expect_err("error iterator"); + let validator = crate::validator_for(&schema).unwrap(); + let mut result = validator.validate(instance).expect_err("error iterator"); let error = result.next().expect("validation error"); assert!(result.next().is_none()); @@ -1141,8 +1140,8 @@ mod tests { } } ); - let compiled = JSONSchema::compile(&schema).unwrap(); - let mut result = compiled.validate(instance).expect_err("error iterator"); + let validator = crate::validator_for(&schema).unwrap(); + let mut result = validator.validate(instance).expect_err("error iterator"); let error = result.next().expect("validation error"); assert!(result.next().is_none()); @@ -1163,8 +1162,8 @@ mod tests { } } ); - let compiled = JSONSchema::compile(&schema).unwrap(); - let mut result = compiled.validate(instance).expect_err("error iterator"); + let validator = crate::validator_for(&schema).unwrap(); + let mut result = validator.validate(instance).expect_err("error iterator"); let error = result.next().expect("validation error"); assert!(result.next().is_none()); diff --git a/crates/jsonschema/src/keywords/format.rs b/crates/jsonschema/src/keywords/format.rs index 29a063b1..b001c9fb 100644 --- a/crates/jsonschema/src/keywords/format.rs +++ b/crates/jsonschema/src/keywords/format.rs @@ -517,17 +517,14 @@ mod tests { use serde_json::json; use test_case::test_case; - use crate::{ - compilation::JSONSchema, error::ValidationErrorKind, schemas::Draft::Draft201909, - tests_util, - }; + use crate::{error::ValidationErrorKind, schemas::Draft::Draft201909, tests_util}; #[test] fn ignored_format() { let schema = json!({"format": "custom", "type": "string"}); let instance = json!("foo"); - let compiled = JSONSchema::compile(&schema).unwrap(); - assert!(compiled.is_valid(&instance)) + let validator = crate::validator_for(&schema).unwrap(); + assert!(validator.is_valid(&instance)) } #[test] @@ -536,11 +533,11 @@ mod tests { let email_instance = json!("email@example.com"); let not_email_instance = json!("foo"); - let with_validation = JSONSchema::options() + let with_validation = crate::options() .should_validate_formats(true) .compile(&schema) .unwrap(); - let without_validation = JSONSchema::options() + let without_validation = crate::options() .should_validate_formats(false) .compile(&schema) .unwrap(); @@ -556,8 +553,8 @@ mod tests { // See GH-230 let schema = json!({"format": "regex", "type": "string"}); let instance = json!("^\\cc$"); - let compiled = JSONSchema::compile(&schema).unwrap(); - assert!(compiled.is_valid(&instance)) + let validator = crate::validator_for(&schema).unwrap(); + assert!(validator.is_valid(&instance)) } #[test] @@ -572,14 +569,14 @@ mod tests { let passing_instance = json!("f308a72c-fa84-11eb-9a03-0242ac130003"); let failing_instance = json!("1"); - let compiled = JSONSchema::options() + let validator = crate::options() .with_draft(Draft201909) .should_validate_formats(true) .compile(&schema) .unwrap(); - assert!(compiled.is_valid(&passing_instance)); - assert!(!compiled.is_valid(&failing_instance)) + assert!(validator.is_valid(&passing_instance)); + assert!(!validator.is_valid(&failing_instance)) } #[test] @@ -600,24 +597,24 @@ mod tests { let passing_instances = vec![json!("P15DT1H22M1.5S"), json!("P30D"), json!("PT5M")]; let failing_instances = vec![json!("15DT1H22M1.5S"), json!("unknown")]; - let compiled = JSONSchema::options() + let validator = crate::options() .with_draft(Draft201909) .should_validate_formats(true) .compile(&schema) .unwrap(); for passing_instance in passing_instances { - assert!(compiled.is_valid(&passing_instance)); + assert!(validator.is_valid(&passing_instance)); } for failing_instance in failing_instances { - assert!(!compiled.is_valid(&failing_instance)); + assert!(!validator.is_valid(&failing_instance)); } } #[test] fn unknown_formats_should_not_be_ignored() { let schema = json!({ "format": "custom", "type": "string"}); - let validation_error = JSONSchema::options() + let validation_error = crate::options() .should_ignore_unknown_formats(false) .compile(&schema) .expect_err("the validation error should be returned"); @@ -643,7 +640,7 @@ mod tests { #[test_case("1.2.3", false; "too few octets")] #[test_case("1.2.3.4.5", false; "too many octets")] fn ip_v4(input: &str, expected: bool) { - let validator = JSONSchema::options() + let validator = crate::options() .should_validate_formats(true) .compile(&json!({"format": "ipv4", "type": "string"})) .expect("Invalid schema"); diff --git a/crates/jsonschema/src/keywords/items.rs b/crates/jsonschema/src/keywords/items.rs index 236e8968..ddeb35bc 100644 --- a/crates/jsonschema/src/keywords/items.rs +++ b/crates/jsonschema/src/keywords/items.rs @@ -119,7 +119,7 @@ impl Validate for ItemsObjectValidator { // `ItemsObjectValidator` is not used when prefixItems is defined, this is true if // there are any items in the instance. let schema_was_applied = !items.is_empty(); - output.annotate(serde_json::json! {schema_was_applied}.into()); + output.annotate(serde_json::json!(schema_was_applied).into()); output } else { PartialApplication::valid_empty() @@ -198,7 +198,7 @@ impl Validate for ItemsObjectSkipPrefixValidator { // we must produce an annotation with a boolean value indicating whether the subschema // was applied to any positions in the underlying array. let schema_was_applied = items.len() > self.skip_prefix; - output.annotate(serde_json::json! {schema_was_applied}.into()); + output.annotate(serde_json::json!(schema_was_applied).into()); output } else { PartialApplication::valid_empty() diff --git a/crates/jsonschema/src/keywords/mod.rs b/crates/jsonschema/src/keywords/mod.rs index 1d948342..0c038ed4 100644 --- a/crates/jsonschema/src/keywords/mod.rs +++ b/crates/jsonschema/src/keywords/mod.rs @@ -44,7 +44,6 @@ pub(crate) type BoxedValidator = Box; #[cfg(test)] mod tests { - use crate::compilation::JSONSchema; use serde_json::{json, Value}; use test_case::test_case; @@ -82,8 +81,8 @@ mod tests { #[test_case(&json!({"type": ["integer", "string"]}), &json!(null), r#"null is not of types "integer", "string""#)] #[test_case(&json!({"uniqueItems": true}), &json!([1, 1]), r#"[1,1] has non-unique elements"#)] fn error_message(schema: &Value, instance: &Value, expected: &str) { - let compiled = JSONSchema::compile(schema).unwrap(); - let errors: Vec<_> = compiled + let validator = crate::validator_for(schema).unwrap(); + let errors: Vec<_> = validator .validate(instance) .expect_err(&format!( "Validation error is expected. Schema=`{:?}` Instance=`{:?}`", @@ -124,22 +123,19 @@ mod tests { #[test_case(&json!({"propertyNames": {"maxLength": 3}}))] fn is_valid_another_type(schema: &Value) { let instance = json!(null); - let compiled = JSONSchema::compile(schema).unwrap(); - assert!(compiled.is_valid(&instance)) + assert!(crate::is_valid(schema, &instance)); } #[test_case(&json!({"additionalProperties": false}), &json!({}))] #[test_case(&json!({"additionalItems": false, "items": true}), &json!([]))] fn is_valid(schema: &Value, instance: &Value) { - let compiled = JSONSchema::compile(schema).unwrap(); - assert!(compiled.is_valid(instance)) + assert!(crate::is_valid(schema, instance)); } #[test_case(&json!({"type": "number"}), &json!(42))] #[test_case(&json!({"type": ["number", "null"]}), &json!(42))] fn integer_is_valid_number_multi_type(schema: &Value, instance: &Value) { // See: GH-147 - let compiled = JSONSchema::compile(schema).unwrap(); - assert!(compiled.is_valid(instance)) + assert!(crate::is_valid(schema, instance)); } // enum: Number #[test_case(&json!({"enum": [0.0]}), &json!(0))] @@ -163,8 +159,7 @@ mod tests { #[test_case(&json!({"const": {"c": [1.0]}}), &json!({"c": [1]}))] fn numeric_equivalence(schema: &Value, instance: &Value) { // See: GH-149 - let compiled = JSONSchema::compile(schema).unwrap(); - assert!(compiled.is_valid(instance)) + assert!(crate::is_valid(schema, instance)); } #[test] @@ -172,8 +167,8 @@ mod tests { // See: GH-190 let schema = json!({"required": ["foo", "bar"]}); let instance = json!({}); - let compiled = JSONSchema::compile(&schema).unwrap(); - let errors: Vec<_> = compiled + let validator = crate::validator_for(&schema).unwrap(); + let errors: Vec<_> = validator .validate(&instance) .expect_err("Validation errors") .collect(); diff --git a/crates/jsonschema/src/keywords/pattern.rs b/crates/jsonschema/src/keywords/pattern.rs index 6b2b4332..c5a964ad 100644 --- a/crates/jsonschema/src/keywords/pattern.rs +++ b/crates/jsonschema/src/keywords/pattern.rs @@ -169,7 +169,7 @@ mod tests { use crate::{ compilation::{context::BaseUri, DEFAULT_SCOPE}, resolver::{DefaultResolver, Resolver}, - tests_util, JSONSchema, + tests_util, }; use serde_json::{json, Value}; use std::sync::Arc; @@ -180,9 +180,9 @@ mod tests { #[test_case(r"^\W+$", "1_0", false)] #[test_case(r"\\w", r"\w", true)] fn regex_matches(pattern: &str, text: &str, is_matching: bool) { - let compiled = convert_regex(pattern).expect("A valid regex"); + let validator = convert_regex(pattern).expect("A valid regex"); assert_eq!( - compiled.is_match(text).expect("A valid pattern"), + validator.is_match(text).expect("A valid pattern"), is_matching ); } @@ -199,7 +199,7 @@ mod tests { let pattern = Value::String(pattern.into()); let text = Value::String(text.into()); let schema_json = Arc::new(json!({})); - let schema = JSONSchema::compile(&schema_json).unwrap(); + let validator = crate::validator_for(&schema_json).unwrap(); let resolver = Arc::new( Resolver::new( Arc::new(DefaultResolver), @@ -210,9 +210,9 @@ mod tests { ) .unwrap(), ); - let context = CompilationContext::new(BaseUri::Unknown, schema.config(), resolver); - let compiled = PatternValidator::compile(&pattern, &context).unwrap(); - assert_eq!(compiled.is_valid(&text), is_matching) + let context = CompilationContext::new(BaseUri::Unknown, validator.config(), resolver); + let validator = PatternValidator::compile(&pattern, &context).unwrap(); + assert_eq!(validator.is_valid(&text), is_matching) } #[test] diff --git a/crates/jsonschema/src/keywords/prefix_items.rs b/crates/jsonschema/src/keywords/prefix_items.rs index a5d4ea1d..b9f6b257 100644 --- a/crates/jsonschema/src/keywords/prefix_items.rs +++ b/crates/jsonschema/src/keywords/prefix_items.rs @@ -117,7 +117,7 @@ pub(crate) fn compile<'a>( #[cfg(test)] mod tests { - use crate::{compilation::JSONSchema, tests_util}; + use crate::tests_util; use serde_json::{json, Value}; use test_case::test_case; @@ -138,7 +138,7 @@ mod tests { } ] }), - &json!{[]}, + &json!([]), &json!({ "valid": true, "annotations": [] @@ -156,7 +156,7 @@ mod tests { } ] }), - &json!{["string", 1]}, + &json!(["string", 1]), &json!({ "valid": true, "annotations": [ @@ -177,7 +177,7 @@ mod tests { } ] }), - &json!{["string", 1]}, + &json!(["string", 1]), &json!({ "valid": true, "annotations": [ @@ -205,7 +205,7 @@ mod tests { } ] }), - &json!{["string", true, 2, 3]}, + &json!(["string", true, 2, 3]), &json!({ "valid": true, "annotations": [ @@ -233,8 +233,8 @@ mod tests { }); "valid prefixItems with mixed items" }] fn test_basic_output(schema_json: &Value, instance: &Value, expected_output: &Value) { - let schema = JSONSchema::options().compile(schema_json).unwrap(); - let output_json = serde_json::to_value(schema.apply(instance).basic()).unwrap(); - assert_eq!(&output_json, expected_output); + let schema = crate::options().compile(schema_json).unwrap(); + let output = serde_json::to_value(schema.apply(instance).basic()).unwrap(); + assert_eq!(&output, expected_output); } } diff --git a/crates/jsonschema/src/keywords/ref_.rs b/crates/jsonschema/src/keywords/ref_.rs index cb5c7533..633b886e 100644 --- a/crates/jsonschema/src/keywords/ref_.rs +++ b/crates/jsonschema/src/keywords/ref_.rs @@ -145,7 +145,7 @@ pub(crate) const fn supports_adjacent_validation(draft: Draft) -> bool { #[cfg(test)] mod tests { - use crate::{tests_util, JSONSchema}; + use crate::tests_util; use serde_json::{json, Value}; use test_case::test_case; @@ -193,8 +193,8 @@ mod tests { "required": ["things"], "$defs": { "codes": { "enum": ["AA", "BB"] } } }); - let compiled = JSONSchema::options().compile(&schema).unwrap(); - let mut iter = compiled.validate(&instance).expect_err("Should fail"); + let validator = crate::validator_for(&schema).unwrap(); + let mut iter = validator.validate(&instance).expect_err("Should fail"); let expected = "/properties/things/items/properties/code/enum"; assert_eq!( iter.next() diff --git a/crates/jsonschema/src/keywords/unevaluated_properties.rs b/crates/jsonschema/src/keywords/unevaluated_properties.rs index aa31b0d2..9e849cdb 100644 --- a/crates/jsonschema/src/keywords/unevaluated_properties.rs +++ b/crates/jsonschema/src/keywords/unevaluated_properties.rs @@ -1318,7 +1318,7 @@ pub(crate) fn compile<'a>( } match UnevaluatedPropertiesValidator::compile(parent, schema, context) { - Ok(compiled) => Some(Ok(Box::new(compiled))), + Ok(validator) => Some(Ok(Box::new(validator))), Err(e) => Some(Err(e)), } } @@ -1444,7 +1444,7 @@ mod tests { "blah": 1 }); - let validator = crate::compile(&schema).expect("Schema should compile"); + let validator = crate::validator_for(&schema).expect("Schema should compile"); assert!(validator.validate(&valid).is_ok(), "Validation should pass"); assert!(validator.is_valid(&valid), "Instance should be valid"); diff --git a/crates/jsonschema/src/lib.rs b/crates/jsonschema/src/lib.rs index 3f094e91..77ed12c1 100644 --- a/crates/jsonschema/src/lib.rs +++ b/crates/jsonschema/src/lib.rs @@ -19,8 +19,6 @@ //! //! The `jsonschema` crate offers two main approaches to validation: one-off validation and reusable validators. //! -//! ## One-off Validation -//! //! For simple use cases where you need to validate an instance against a schema once, use the `is_valid` function: //! //! ```rust @@ -32,15 +30,13 @@ //! assert!(jsonschema::is_valid(&schema, &instance)); //! ``` //! -//! ## Reusable Validators -//! //! For better performance, especially when validating multiple instances against the same schema, build a validator once and reuse it: //! //! ```rust //! use serde_json::json; //! //! let schema = json!({"type": "string"}); -//! let validator = jsonschema::compile(&schema) +//! let validator = jsonschema::validator_for(&schema) //! .expect("Invalid schema"); //! //! assert!(validator.is_valid(&json!("Hello, world!"))); @@ -59,23 +55,98 @@ //! //! # Configuration //! -//! `jsonschema` provides a builder for configuration options via `JSONSchema::options()`. +//! `jsonschema` provides several ways to configure and use JSON Schema validation. +//! +//! ## Draft-specific Modules +//! +//! The library offers modules for specific JSON Schema draft versions: +//! +//! - [`draft4`] +//! - [`draft6`] +//! - [`draft7`] +//! - [`draft201909`] +//! - [`draft202012`] +//! +//! Each module provides: +//! - A `new` function to create a validator +//! - An `is_valid` function for quick validation +//! - An `options` function to create a draft-specific configuration builder //! -//! Here is how you can explicitly set the JSON Schema draft version: +//! Here's how you can explicitly use a specific draft version: //! //! ```rust -//! use jsonschema::{JSONSchema, Draft}; //! use serde_json::json; //! //! let schema = json!({"type": "string"}); -//! let validator = JSONSchema::options() -//! .with_draft(Draft::Draft7) +//! let validator = jsonschema::draft7::new(&schema) +//! .expect("Invalid schema"); +//! +//! assert!(validator.is_valid(&json!("Hello"))); +//! ``` +//! +//! You can also use the convenience `is_valid` function for quick validation: +//! +//! ```rust +//! use serde_json::json; +//! +//! let schema = json!({"type": "number", "minimum": 0}); +//! let instance = json!(42); +//! +//! assert!(jsonschema::draft202012::is_valid(&schema, &instance)); +//! ``` +//! +//! For more advanced configuration, you can use the draft-specific `options` function: +//! +//! ```rust +//! use serde_json::json; +//! +//! let schema = json!({"type": "string", "format": "ends-with-42"}); +//! let validator = jsonschema::draft202012::options() +//! .with_format("ends-with-42", |s| s.ends_with("42")) +//! .should_validate_formats(true) +//! .compile(&schema) +//! .expect("Invalid schema"); +//! +//! assert!(validator.is_valid(&json!("Hello 42"))); +//! assert!(!validator.is_valid(&json!("No!"))); +//! ``` +//! +//! ## General Configuration +//! +//! For configuration options that are not draft-specific, `jsonschema` provides a general builder via `jsonschema::options()`. +//! +//! Here's an example of using the general options builder: +//! +//! ```rust +//! use serde_json::json; +//! use jsonschema::{Draft, JSONSchema}; +//! +//! let schema = json!({"type": "string"}); +//! let validator = jsonschema::options() +//! // Add configuration options here //! .compile(&schema) //! .expect("Invalid schema"); +//! +//! assert!(validator.is_valid(&json!("Hello"))); //! ``` //! //! For a complete list of configuration options and their usage, please refer to the [`CompilationOptions`] struct. //! +//! ## Automatic Draft Detection +//! +//! If you don't need to specify a particular draft version, you can use `jsonschema::validator_for` +//! which automatically detects the appropriate draft: +//! +//! ```rust +//! use serde_json::json; +//! +//! let schema = json!({"$schema": "http://json-schema.org/draft-07/schema#", "type": "string"}); +//! let validator = jsonschema::validator_for(&schema) +//! .expect("Invalid schema"); +//! +//! assert!(validator.is_valid(&json!("Hello"))); +//! ``` +//! //! # Reference Resolving //! //! By default, `jsonschema` resolves HTTP references using `reqwest` and file references from the local file system. @@ -98,7 +169,7 @@ //! # fn main() -> Result<(), Box> { //! use std::{collections::HashMap, sync::Arc}; //! use anyhow::anyhow; -//! use jsonschema::{JSONSchema, SchemaResolver, SchemaResolverError}; +//! use jsonschema::{SchemaResolver, SchemaResolverError}; //! use serde_json::{json, Value}; //! use url::Url; //! @@ -139,7 +210,7 @@ //! "$ref": "https://example.com/person.json" //! }); //! -//! let validator = JSONSchema::options() +//! let validator = jsonschema::options() //! .with_resolver(resolver) //! .compile(&schema) //! .expect("Invalid schema"); @@ -162,7 +233,6 @@ //! //! ```rust //! # fn main() -> Result<(), Box> { -//! use jsonschema::BasicOutput; //! use serde_json::json; //! //! let schema_json = json!({ @@ -170,14 +240,13 @@ //! "type": "string" //! }); //! let instance = json!("some string"); -//! let schema = jsonschema::compile(&schema_json) +//! let validator = jsonschema::validator_for(&schema_json) //! .expect("Invalid schema"); //! -//! let output: BasicOutput = schema.apply(&instance).basic(); -//! let output_json = serde_json::to_value(output)?; +//! let output = validator.apply(&instance).basic(); //! //! assert_eq!( -//! output_json, +//! serde_json::to_value(output)?, //! json!({ //! "valid": true, //! "annotations": [ @@ -210,7 +279,7 @@ //! ```rust //! use jsonschema::{ //! paths::{JSONPointer, JsonPointerNode}, -//! ErrorIterator, JSONSchema, Keyword, ValidationError, +//! ErrorIterator, Keyword, ValidationError, //! }; //! use serde_json::{json, Map, Value}; //! use std::iter::once; @@ -274,7 +343,7 @@ //! // Step 3: Use the custom keyword //! fn main() -> Result<(), Box> { //! let schema = json!({"even-number": true, "type": "integer"}); -//! let validator = JSONSchema::options() +//! let validator = jsonschema::options() //! .with_keyword("even-number", even_number_validator_factory) //! .compile(&schema) //! .expect("Invalid schema"); @@ -296,7 +365,7 @@ //! ```rust //! # use jsonschema::{ //! # paths::{JSONPointer, JsonPointerNode}, -//! # ErrorIterator, JSONSchema, Keyword, ValidationError, +//! # ErrorIterator, Keyword, ValidationError, //! # }; //! # use serde_json::{json, Map, Value}; //! # use std::iter::once; @@ -317,7 +386,7 @@ //! # } //! # } //! let schema = json!({"even-number": true, "type": "integer"}); -//! let validator = JSONSchema::options() +//! let validator = jsonschema::options() //! .with_keyword("even-number", |_, _, _| { //! Ok(Box::new(EvenNumberValidator)) //! }) @@ -334,10 +403,9 @@ //! To implement a custom format validator: //! //! 1. Define a function or a closure that takes a `&str` and returns a `bool`. -//! 2. Register the function with `JSONSchema::options().with_format()`. +//! 2. Register the function with `jsonschema::options().with_format()`. //! //! ```rust -//! use jsonschema::JSONSchema; //! use serde_json::json; //! //! // Step 1: Define the custom format validator function @@ -353,9 +421,9 @@ //! }); //! //! // Step 3: Compile the schema with the custom format -//! let validator = JSONSchema::options() +//! let validator = jsonschema::options() //! .with_format("ends-with-42", ends_with_42) -//! .with_format("ends-with-43", |s: &str| s.ends_with("43!")) +//! .with_format("ends-with-43", |s| s.ends_with("43!")) //! .compile(&schema) //! .expect("Invalid schema"); //! @@ -396,6 +464,9 @@ pub use schemas::Draft; use serde_json::Value; /// A shortcut for validating `instance` against `schema`. Draft version is detected automatically. +/// +/// # Examples +/// /// ```rust /// use jsonschema::is_valid; /// use serde_json::json; @@ -405,59 +476,550 @@ use serde_json::Value; /// assert!(is_valid(&schema, &instance)); /// ``` /// +/// # Panics +/// /// This function panics if an invalid schema is passed. #[must_use] #[inline] pub fn is_valid(schema: &Value, instance: &Value) -> bool { - let compiled = JSONSchema::compile(schema).expect("Invalid schema"); - compiled.is_valid(instance) + validator_for(schema) + .expect("Invalid schema") + .is_valid(instance) } /// Compile the input schema for faster validation. +/// +/// # Deprecated +/// This function is deprecated since version 0.20.0. Use `validator_for` instead. +/// +/// # Examples +/// +/// ```rust +/// use serde_json::json; +/// +/// let schema = json!({"minimum": 5}); +/// let instance = json!(42); +/// +/// let validator = jsonschema::validator_for(&schema).expect("Invalid schema"); +/// assert!(validator.is_valid(&instance)); +/// ``` +#[deprecated(since = "0.20.0", note = "Use `validator_for` instead")] pub fn compile(schema: &Value) -> Result { JSONSchema::compile(schema) } +/// Create a validator for the input schema with automatic draft detection. +/// +/// # Examples +/// +/// ```rust +/// use serde_json::json; +/// +/// let schema = json!({"minimum": 5}); +/// let instance = json!(42); +/// +/// let validator = jsonschema::validator_for(&schema).expect("Invalid schema"); +/// assert!(validator.is_valid(&instance)); +/// ``` +pub fn validator_for(schema: &Value) -> Result { + JSONSchema::compile(schema) +} + +/// Creates a builder for configuring JSON Schema validation options. +/// +/// This function returns a [`CompilationOptions`] struct, which allows you to set various +/// options for JSON Schema validation. You can use this builder to specify +/// the draft version, set custom formats, configure output, and more. +/// +/// # Examples +/// +/// Basic usage with draft specification: +/// +/// ``` +/// use serde_json::json; +/// use jsonschema::Draft; +/// +/// let schema = json!({"type": "string"}); +/// let validator = jsonschema::options() +/// .with_draft(Draft::Draft7) +/// .compile(&schema) +/// .expect("Invalid schema"); +/// +/// assert!(validator.is_valid(&json!("Hello"))); +/// ``` +/// +/// Advanced configuration: +/// +/// ``` +/// use serde_json::json; +/// +/// let schema = json!({"type": "string", "format": "custom"}); +/// let validator = jsonschema::options() +/// .with_format("custom", |value| value.len() == 3) +/// .compile(&schema) +/// .expect("Invalid schema"); +/// +/// assert!(validator.is_valid(&json!("abc"))); +/// assert!(!validator.is_valid(&json!("abcd"))); +/// ``` +/// +/// See [`CompilationOptions`] for all available configuration options. +pub fn options() -> CompilationOptions { + JSONSchema::options() +} + +/// Functionality specific to JSON Schema Draft 4. +/// +/// [![Draft 4](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft4.json)](https://bowtie.report/#/implementations/rust-jsonschema) +/// +/// This module provides functions for creating validators and performing validation +/// according to the JSON Schema Draft 4 specification. +/// +/// # Examples +/// +/// ```rust +/// use serde_json::json; +/// +/// let schema = json!({"type": "number", "multipleOf": 2}); +/// let instance = json!(4); +/// +/// assert!(jsonschema::draft4::is_valid(&schema, &instance)); +/// ``` +pub mod draft4 { + use super::*; + + /// Create a new JSON Schema validator using Draft 4 specifications. + /// + /// # Examples + /// + /// ```rust + /// use serde_json::json; + /// + /// let schema = json!({"minimum": 5}); + /// let instance = json!(42); + /// + /// let validator = jsonschema::draft4::new(&schema).expect("Invalid schema"); + /// assert!(validator.is_valid(&instance)); + /// ``` + pub fn new(schema: &Value) -> Result { + crate::options().with_draft(Draft::Draft4).compile(schema) + } + /// Validate an instance against a schema using Draft 4 specifications without creating a validator. + /// + /// # Examples + /// + /// ```rust + /// use serde_json::json; + /// + /// let schema = json!({"minimum": 5}); + /// let valid_instance = json!(42); + /// let invalid_instance = json!(3); + /// + /// assert!(jsonschema::draft4::is_valid(&schema, &valid_instance)); + /// assert!(!jsonschema::draft4::is_valid(&schema, &invalid_instance)); + /// ``` + #[must_use] + pub fn is_valid(schema: &Value, instance: &Value) -> bool { + new(schema).expect("Invalid schema").is_valid(instance) + } + /// Creates a [`CompilationOptions`] builder pre-configured for JSON Schema Draft 4. + /// + /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft4)`. + /// + /// # Examples + /// + /// ``` + /// use serde_json::json; + /// + /// let schema = json!({"type": "string", "format": "ends-with-42"}); + /// let validator = jsonschema::draft4::options() + /// .with_format("ends-with-42", |s| s.ends_with("42")) + /// .should_validate_formats(true) + /// .compile(&schema) + /// .expect("Invalid schema"); + /// + /// assert!(validator.is_valid(&json!("Hello 42"))); + /// assert!(!validator.is_valid(&json!("No!"))); + /// ``` + /// + /// See [`CompilationOptions`] for all available configuration options. + #[must_use] + pub fn options() -> CompilationOptions { + let mut options = crate::options(); + options.with_draft(Draft::Draft4); + options + } +} + +/// Functionality specific to JSON Schema Draft 6. +/// +/// [![Draft 6](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft6.json)](https://bowtie.report/#/implementations/rust-jsonschema) +/// +/// This module provides functions for creating validators and performing validation +/// according to the JSON Schema Draft 6 specification. +/// +/// # Examples +/// +/// ```rust +/// use serde_json::json; +/// +/// let schema = json!({"type": "string", "format": "uri"}); +/// let instance = json!("https://www.example.com"); +/// +/// assert!(jsonschema::draft6::is_valid(&schema, &instance)); +/// ``` +pub mod draft6 { + use super::*; + + /// Create a new JSON Schema validator using Draft 6 specifications. + /// + /// # Examples + /// + /// ```rust + /// use serde_json::json; + /// + /// let schema = json!({"minimum": 5}); + /// let instance = json!(42); + /// + /// let validator = jsonschema::draft6::new(&schema).expect("Invalid schema"); + /// assert!(validator.is_valid(&instance)); + /// ``` + pub fn new(schema: &Value) -> Result { + options().compile(schema) + } + /// Validate an instance against a schema using Draft 6 specifications without creating a validator. + /// + /// # Examples + /// + /// ```rust + /// use serde_json::json; + /// + /// let schema = json!({"minimum": 5}); + /// let valid_instance = json!(42); + /// let invalid_instance = json!(3); + /// + /// assert!(jsonschema::draft6::is_valid(&schema, &valid_instance)); + /// assert!(!jsonschema::draft6::is_valid(&schema, &invalid_instance)); + /// ``` + #[must_use] + pub fn is_valid(schema: &Value, instance: &Value) -> bool { + new(schema).expect("Invalid schema").is_valid(instance) + } + /// Creates a [`CompilationOptions`] builder pre-configured for JSON Schema Draft 6. + /// + /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft6)`. + /// + /// # Examples + /// + /// ``` + /// use serde_json::json; + /// + /// let schema = json!({"type": "string", "format": "ends-with-42"}); + /// let validator = jsonschema::draft6::options() + /// .with_format("ends-with-42", |s| s.ends_with("42")) + /// .should_validate_formats(true) + /// .compile(&schema) + /// .expect("Invalid schema"); + /// + /// assert!(validator.is_valid(&json!("Hello 42"))); + /// assert!(!validator.is_valid(&json!("No!"))); + /// ``` + /// + /// See [`CompilationOptions`] for all available configuration options. + #[must_use] + pub fn options() -> CompilationOptions { + let mut options = crate::options(); + options.with_draft(Draft::Draft6); + options + } +} + +/// Functionality specific to JSON Schema Draft 7. +/// +/// [![Draft 7](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft7.json)](https://bowtie.report/#/implementations/rust-jsonschema) +/// +/// This module provides functions for creating validators and performing validation +/// according to the JSON Schema Draft 7 specification. +/// +/// # Examples +/// +/// ```rust +/// use serde_json::json; +/// +/// let schema = json!({"type": "string", "pattern": "^[a-zA-Z0-9]+$"}); +/// let instance = json!("abc123"); +/// +/// assert!(jsonschema::draft7::is_valid(&schema, &instance)); +/// ``` +pub mod draft7 { + use super::*; + + /// Create a new JSON Schema validator using Draft 7 specifications. + /// + /// # Examples + /// + /// ```rust + /// use serde_json::json; + /// + /// let schema = json!({"minimum": 5}); + /// let instance = json!(42); + /// + /// let validator = jsonschema::draft7::new(&schema).expect("Invalid schema"); + /// assert!(validator.is_valid(&instance)); + /// ``` + pub fn new(schema: &Value) -> Result { + options().compile(schema) + } + /// Validate an instance against a schema using Draft 7 specifications without creating a validator. + /// + /// # Examples + /// + /// ```rust + /// use serde_json::json; + /// + /// let schema = json!({"minimum": 5}); + /// let valid_instance = json!(42); + /// let invalid_instance = json!(3); + /// + /// assert!(jsonschema::draft7::is_valid(&schema, &valid_instance)); + /// assert!(!jsonschema::draft7::is_valid(&schema, &invalid_instance)); + /// ``` + #[must_use] + pub fn is_valid(schema: &Value, instance: &Value) -> bool { + new(schema).expect("Invalid schema").is_valid(instance) + } + /// Creates a [`CompilationOptions`] builder pre-configured for JSON Schema Draft 7. + /// + /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft7)`. + /// + /// # Examples + /// + /// ``` + /// use serde_json::json; + /// + /// let schema = json!({"type": "string", "format": "ends-with-42"}); + /// let validator = jsonschema::draft7::options() + /// .with_format("ends-with-42", |s| s.ends_with("42")) + /// .should_validate_formats(true) + /// .compile(&schema) + /// .expect("Invalid schema"); + /// + /// assert!(validator.is_valid(&json!("Hello 42"))); + /// assert!(!validator.is_valid(&json!("No!"))); + /// ``` + /// + /// See [`CompilationOptions`] for all available configuration options. + #[must_use] + pub fn options() -> CompilationOptions { + let mut options = crate::options(); + options.with_draft(Draft::Draft7); + options + } +} + +/// Functionality specific to JSON Schema Draft 2019-09. +/// +/// [![Draft 2019-09](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft2019-09.json)](https://bowtie.report/#/implementations/rust-jsonschema) +/// +/// This module provides functions for creating validators and performing validation +/// according to the JSON Schema Draft 2019-09 specification. +/// +/// # Examples +/// +/// ```rust +/// use serde_json::json; +/// +/// let schema = json!({"type": "array", "minItems": 2, "uniqueItems": true}); +/// let instance = json!([1, 2]); +/// +/// assert!(jsonschema::draft201909::is_valid(&schema, &instance)); +/// ``` +pub mod draft201909 { + use super::*; + + /// Create a new JSON Schema validator using Draft 2019-09 specifications. + /// + /// # Examples + /// + /// ```rust + /// use serde_json::json; + /// + /// let schema = json!({"minimum": 5}); + /// let instance = json!(42); + /// + /// let validator = jsonschema::draft201909::new(&schema).expect("Invalid schema"); + /// assert!(validator.is_valid(&instance)); + /// ``` + pub fn new(schema: &Value) -> Result { + options().compile(schema) + } + /// Validate an instance against a schema using Draft 2019-09 specifications without creating a validator. + /// + /// # Examples + /// + /// ```rust + /// use serde_json::json; + /// + /// let schema = json!({"minimum": 5}); + /// let valid_instance = json!(42); + /// let invalid_instance = json!(3); + /// + /// assert!(jsonschema::draft201909::is_valid(&schema, &valid_instance)); + /// assert!(!jsonschema::draft201909::is_valid(&schema, &invalid_instance)); + /// ``` + #[must_use] + pub fn is_valid(schema: &Value, instance: &Value) -> bool { + new(schema).expect("Invalid schema").is_valid(instance) + } + /// Creates a [`CompilationOptions`] builder pre-configured for JSON Schema Draft 2019-09. + /// + /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft201909)`. + /// + /// # Examples + /// + /// ``` + /// use serde_json::json; + /// + /// let schema = json!({"type": "string", "format": "ends-with-42"}); + /// let validator = jsonschema::draft201909::options() + /// .with_format("ends-with-42", |s| s.ends_with("42")) + /// .should_validate_formats(true) + /// .compile(&schema) + /// .expect("Invalid schema"); + /// + /// assert!(validator.is_valid(&json!("Hello 42"))); + /// assert!(!validator.is_valid(&json!("No!"))); + /// ``` + /// + /// See [`CompilationOptions`] for all available configuration options. + #[must_use] + pub fn options() -> CompilationOptions { + let mut options = crate::options(); + options.with_draft(Draft::Draft201909); + options + } +} + +/// Functionality specific to JSON Schema Draft 2020-12. +/// +/// [![Draft 2020-12](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft2020-12.json)](https://bowtie.report/#/implementations/rust-jsonschema) +/// +/// This module provides functions for creating validators and performing validation +/// according to the JSON Schema Draft 2020-12 specification. +/// +/// # Examples +/// +/// ```rust +/// use serde_json::json; +/// +/// let schema = json!({"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}); +/// let instance = json!({"name": "John Doe"}); +/// +/// assert!(jsonschema::draft202012::is_valid(&schema, &instance)); +/// ``` +pub mod draft202012 { + use super::*; + + /// Create a new JSON Schema validator using Draft 2020-12 specifications. + /// + /// # Examples + /// + /// ```rust + /// use serde_json::json; + /// + /// let schema = json!({"minimum": 5}); + /// let instance = json!(42); + /// + /// let validator = jsonschema::draft202012::new(&schema).expect("Invalid schema"); + /// assert!(validator.is_valid(&instance)); + /// ``` + pub fn new(schema: &Value) -> Result { + options().compile(schema) + } + /// Validate an instance against a schema using Draft 2020-12 specifications without creating a validator. + /// + /// # Examples + /// + /// ```rust + /// use serde_json::json; + /// + /// let schema = json!({"minimum": 5}); + /// let valid_instance = json!(42); + /// let invalid_instance = json!(3); + /// + /// assert!(jsonschema::draft202012::is_valid(&schema, &valid_instance)); + /// assert!(!jsonschema::draft202012::is_valid(&schema, &invalid_instance)); + /// ``` + #[must_use] + pub fn is_valid(schema: &Value, instance: &Value) -> bool { + new(schema).expect("Invalid schema").is_valid(instance) + } + /// Creates a [`CompilationOptions`] builder pre-configured for JSON Schema Draft 2020-12. + /// + /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft202012)`. + /// + /// # Examples + /// + /// ``` + /// use serde_json::json; + /// + /// let schema = json!({"type": "string", "format": "ends-with-42"}); + /// let validator = jsonschema::draft202012::options() + /// .with_format("ends-with-42", |s| s.ends_with("42")) + /// .should_validate_formats(true) + /// .compile(&schema) + /// .expect("Invalid schema"); + /// + /// assert!(validator.is_valid(&json!("Hello 42"))); + /// assert!(!validator.is_valid(&json!("No!"))); + /// ``` + /// + /// See [`CompilationOptions`] for all available configuration options. + #[must_use] + pub fn options() -> CompilationOptions { + let mut options = crate::options(); + options.with_draft(Draft::Draft202012); + options + } +} + #[cfg(test)] pub(crate) mod tests_util { use super::JSONSchema; use crate::ValidationError; use serde_json::Value; - pub(crate) fn is_not_valid_with(compiled: &JSONSchema, instance: &Value) { + pub(crate) fn is_not_valid_with(validator: &JSONSchema, instance: &Value) { assert!( - !compiled.is_valid(instance), + !validator.is_valid(instance), "{} should not be valid (via is_valid)", instance ); assert!( - compiled.validate(instance).is_err(), + validator.validate(instance).is_err(), "{} should not be valid (via validate)", instance ); assert!( - !compiled.apply(instance).basic().is_valid(), + !validator.apply(instance).basic().is_valid(), "{} should not be valid (via apply)", instance ); } pub(crate) fn is_not_valid(schema: &Value, instance: &Value) { - let compiled = JSONSchema::compile(schema).unwrap(); - is_not_valid_with(&compiled, instance) + let validator = crate::validator_for(schema).unwrap(); + is_not_valid_with(&validator, instance) } pub(crate) fn is_not_valid_with_draft(draft: crate::Draft, schema: &Value, instance: &Value) { - let compiled = JSONSchema::options() - .with_draft(draft) - .compile(schema) - .unwrap(); - is_not_valid_with(&compiled, instance) + let validator = crate::options().with_draft(draft).compile(schema).unwrap(); + is_not_valid_with(&validator, instance) } pub(crate) fn expect_errors(schema: &Value, instance: &Value, errors: &[&str]) { assert_eq!( - JSONSchema::compile(schema) + crate::validator_for(schema) .expect("Should be a valid schema") .validate(instance) .expect_err(format!("{} should not be valid", instance).as_str()) @@ -467,8 +1029,8 @@ pub(crate) mod tests_util { ) } - pub(crate) fn is_valid_with(compiled: &JSONSchema, instance: &Value) { - if let Err(mut errors) = compiled.validate(instance) { + pub(crate) fn is_valid_with(validator: &JSONSchema, instance: &Value) { + if let Err(mut errors) = validator.validate(instance) { let first = errors.next().expect("Errors iterator is empty"); panic!( "{} should be valid (via validate). Error: {} at {}", @@ -476,33 +1038,30 @@ pub(crate) mod tests_util { ); } assert!( - compiled.is_valid(instance), + validator.is_valid(instance), "{} should be valid (via is_valid)", instance ); assert!( - compiled.apply(instance).basic().is_valid(), + validator.apply(instance).basic().is_valid(), "{} should be valid (via apply)", instance ); } pub(crate) fn is_valid(schema: &Value, instance: &Value) { - let compiled = JSONSchema::compile(schema).unwrap(); - is_valid_with(&compiled, instance); + let validator = crate::validator_for(schema).unwrap(); + is_valid_with(&validator, instance); } pub(crate) fn is_valid_with_draft(draft: crate::Draft, schema: &Value, instance: &Value) { - let compiled = JSONSchema::options() - .with_draft(draft) - .compile(schema) - .unwrap(); - is_valid_with(&compiled, instance) + let validator = crate::options().with_draft(draft).compile(schema).unwrap(); + is_valid_with(&validator, instance) } pub(crate) fn validate(schema: &Value, instance: &Value) -> ValidationError<'static> { - let compiled = JSONSchema::compile(schema).unwrap(); - let err = compiled + let validator = crate::validator_for(schema).unwrap(); + let err = validator .validate(instance) .expect_err("Should be an error") .next() @@ -517,8 +1076,10 @@ pub(crate) mod tests_util { } pub(crate) fn assert_schema_paths(schema: &Value, instance: &Value, expected: &[&str]) { - let compiled = JSONSchema::compile(schema).unwrap(); - let errors = compiled.validate(instance).expect_err("Should be an error"); + let validator = crate::validator_for(schema).unwrap(); + let errors = validator + .validate(instance) + .expect_err("Should be an error"); for (error, schema_path) in errors.zip(expected) { assert_eq!(error.schema_path.to_string(), *schema_path) } @@ -527,17 +1088,37 @@ pub(crate) mod tests_util { #[cfg(test)] mod tests { - use super::{is_valid, Draft, JSONSchema}; + use super::Draft; use serde_json::json; use test_case::test_case; - #[test] - fn test_is_valid() { - let schema = json!({"minLength": 5}); - let valid = json!("foobar"); - let invalid = json!("foo"); - assert!(is_valid(&schema, &valid)); - assert!(!is_valid(&schema, &invalid)); + #[test_case(crate::is_valid ; "autodetect")] + #[test_case(crate::draft4::is_valid ; "draft4")] + #[test_case(crate::draft6::is_valid ; "draft6")] + #[test_case(crate::draft7::is_valid ; "draft7")] + #[test_case(crate::draft201909::is_valid ; "draft201909")] + #[test_case(crate::draft202012::is_valid ; "draft202012")] + fn test_is_valid(is_valid_fn: fn(&serde_json::Value, &serde_json::Value) -> bool) { + let schema = json!({ + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer", "minimum": 0} + }, + "required": ["name"] + }); + + let valid_instance = json!({ + "name": "John Doe", + "age": 30 + }); + + let invalid_instance = json!({ + "age": -5 + }); + + assert!(is_valid_fn(&schema, &valid_instance)); + assert!(!is_valid_fn(&schema, &invalid_instance)); } #[test_case(Draft::Draft4)] @@ -546,10 +1127,7 @@ mod tests { fn meta_schemas(draft: Draft) { // See GH-258 for schema in [json!({"enum": [0, 0.0]}), json!({"enum": []})] { - assert!(JSONSchema::options() - .with_draft(draft) - .compile(&schema) - .is_ok()) + assert!(crate::options().with_draft(draft).compile(&schema).is_ok()) } } @@ -557,6 +1135,6 @@ mod tests { fn incomplete_escape_in_pattern() { // See GH-253 let schema = json!({"pattern": "\\u"}); - assert!(JSONSchema::compile(&schema).is_err()) + assert!(crate::validator_for(&schema).is_err()) } } diff --git a/crates/jsonschema/src/output.rs b/crates/jsonschema/src/output.rs index 01e78368..8d3be954 100644 --- a/crates/jsonschema/src/output.rs +++ b/crates/jsonschema/src/output.rs @@ -75,14 +75,15 @@ impl<'a, 'b> Output<'a, 'b> { /// # Examples /// /// ```rust - /// # use crate::jsonschema::{Draft, output::{Output, BasicOutput}, JSONSchema}; - /// # let schema_json = serde_json::json!({ + /// # use jsonschema::BasicOutput; + /// # use serde_json::json; + /// # let schema = json!({ /// # "title": "string value", /// # "type": "string" /// # }); - /// # let instance = serde_json::json!{"some string"}; - /// # let schema = JSONSchema::options().compile(&schema_json).unwrap(); - /// let output: BasicOutput = schema.apply(&instance).basic(); + /// # let instance = json!("some string"); + /// # let validator = jsonschema::validator_for(&schema).expect("Invalid schema"); + /// let output = validator.apply(&instance).basic(); /// match output { /// BasicOutput::Valid(annotations) => { /// for annotation in annotations { diff --git a/crates/jsonschema/tests/output.rs b/crates/jsonschema/tests/output.rs index a29c63b2..391979dd 100644 --- a/crates/jsonschema/tests/output.rs +++ b/crates/jsonschema/tests/output.rs @@ -1,10 +1,9 @@ -use jsonschema::JSONSchema; use serde_json::json; use test_case::test_case; #[test_case{ &json!({"allOf": [{"type": "string", "typeannotation": "value"}, {"maxLength": 20, "lengthannotation": "value"}]}), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": true, "annotations": [ @@ -24,7 +23,7 @@ use test_case::test_case; }] #[test_case{ &json!({"allOf": [{"type": "array"}, {"maxLength": 4}]}), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": false, "errors": [ @@ -43,7 +42,7 @@ use test_case::test_case; }] #[test_case{ &json!({"allOf": [{"type": "string", "typeannotation": "value"}]}), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": true, "annotations": [ @@ -59,7 +58,7 @@ use test_case::test_case; }] #[test_case{ &json!({"allOf": [{"type": "array"}]}), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": false, "errors": [ @@ -73,7 +72,7 @@ use test_case::test_case; }] #[test_case{ &json!({"anyOf": [{"type": "string", "someannotation": "value"}, {"maxLength": 4}, {"minLength": 1}]}), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": true, "annotations": [ @@ -89,7 +88,7 @@ use test_case::test_case; }] #[test_case{ &json!({"anyOf": [{"type": "object"}, {"maxLength": 4}]}), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": false, "errors": [ @@ -108,7 +107,7 @@ use test_case::test_case; }] #[test_case{ &json!({"oneOf": [{"type": "object", "someannotation": "somevalue"}, {"type": "string"}]}), - &json!{{"somekey": "some value"}}, + &json!({"somekey": "some value"}), &json!({ "valid": true, "annotations": [ @@ -124,7 +123,7 @@ use test_case::test_case; }] #[test_case{ &json!({"oneOf": [{"type": "object"}, {"maxLength": 4}]}), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": false, "errors": [ @@ -143,7 +142,7 @@ use test_case::test_case; }] #[test_case{ &json!({"oneOf": [{"type": "string"}, {"maxLength": 40}]}), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": false, "errors": [ @@ -160,7 +159,7 @@ use test_case::test_case; "if": {"type": "string", "ifannotation": "ifvalue"}, "then": {"maxLength": 20, "thenannotation": "thenvalue"} }), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": true, "annotations": [ @@ -186,7 +185,7 @@ use test_case::test_case; "if": {"type": "string", "ifannotation": "ifvalue"}, "then": {"maxLength": 4, "thenannotation": "thenvalue"} }), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": false, "errors": [ @@ -203,7 +202,7 @@ use test_case::test_case; "if": {"type": "object", "ifannotation": "ifvalue"}, "else": {"maxLength": 20, "elseannotation": "elsevalue"} }), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": true, "annotations": [ @@ -222,7 +221,7 @@ use test_case::test_case; "if": {"type": "string", "ifannotation": "ifvalue"}, "else": {"type": "array", "elseannotation": "elsevalue"} }), - &json!{{"some": "object"}}, + &json!({"some": "object"}), &json!({ "valid": false, "errors": [ @@ -240,7 +239,7 @@ use test_case::test_case; "then": {"maxLength": 20, "thenannotation": "thenvalue"}, "else": {"type": "number", "elseannotation": "elsevalue"} }), - &json!{"some string"}, + &json!("some string"), &json!({ "valid": true, "annotations": [ @@ -267,7 +266,7 @@ use test_case::test_case; "then": {"maxLength": 20, "thenannotation": "thenvalue"}, "else": {"type": "number", "elseannotation": "elsevalue"} }), - &json!{12}, + &json!(12), &json!({ "valid": true, "annotations": [ @@ -287,7 +286,7 @@ use test_case::test_case; "then": {"maxLength": 4, "thenannotation": "thenvalue"}, "else": {"type": "number", "elseannotation": "elsevalue"} }), - &json!{"12345"}, + &json!("12345"), &json!({ "valid": false, "errors": [ @@ -304,7 +303,7 @@ use test_case::test_case; "then": {"maxLength": 20, "thenannotation": "thenvalue"}, "else": {"type": "number", "elseannotation": "elsevalue"} }), - &json!{{"some": "object"}}, + &json!({"some": "object"}), &json!({ "valid": false, "errors": [ @@ -324,7 +323,7 @@ use test_case::test_case; "annotation": "value" } }), - &json!{[1,2]}, + &json!([1,2]), &json!({ "valid": true, "annotations": [ @@ -358,7 +357,7 @@ use test_case::test_case; "annotation": "value" } }), - &json!{[]}, + &json!([]), &json!({ "valid": true, "annotations": [ @@ -378,7 +377,7 @@ use test_case::test_case; "annotation": "value" } }), - &json!{[1,2,"3"]}, + &json!([1,2,"3"]), &json!({ "valid": false, "errors": [ @@ -403,7 +402,7 @@ use test_case::test_case; "maximum": 2 } }), - &json!{[1,3,2]}, + &json!([1,3,2]), &json!({ "valid": true, "annotations": [ @@ -437,7 +436,7 @@ use test_case::test_case; "maximum": 2 } }), - &json!{["one"]}, + &json!(["one"]), &json!({ "valid": false, "errors": [ @@ -456,10 +455,10 @@ use test_case::test_case; "age": {"type": "number"} } }), - &json!{{ + &json!({ "name": "some name", "age": 10 - }}, + }), &json!({ "valid": true, "annotations": [ @@ -489,11 +488,11 @@ use test_case::test_case; "unmatchedProp\\S": {"type": "object"}, } }), - &json!{{ + &json!({ "numProp1": 1, "numProp2": 2, "stringProp1": "1" - }}, + }), &json!({ "valid": true, "annotations": [ @@ -529,11 +528,11 @@ use test_case::test_case; "numProp(\\d+)": {"type": "number", "some": "subannotation"} } }), - &json!{{ + &json!({ "numProp1": 1, "numProp2": 2, "stringProp1": "1" - }}, + }), &json!({ "valid": true, "annotations": [ @@ -566,9 +565,9 @@ use test_case::test_case; &json!({ "propertyNames": {"maxLength": 10, "some": "annotation"} }), - &json!{{ + &json!({ "name": "some name", - }}, + }), &json!({ "valid": true, "annotations": [ @@ -581,13 +580,13 @@ use test_case::test_case; }); "valid propertyNames" }] fn test_basic_output( - schema_json: &serde_json::Value, + schema: &serde_json::Value, instance: &serde_json::Value, expected_output: &serde_json::Value, ) { - let schema = JSONSchema::options().compile(schema_json).unwrap(); - let output_json = serde_json::to_value(schema.apply(instance).basic()).unwrap(); - assert_eq!(&output_json, expected_output); + let validator = jsonschema::validator_for(schema).unwrap(); + let output = serde_json::to_value(validator.apply(instance).basic()).unwrap(); + assert_eq!(&output, expected_output); } /// These tests are separated from the rest of the basic output tests for convenience, there's @@ -610,10 +609,10 @@ fn test_basic_output( &json!({ "additionalProperties": {"type": "number" } }), - &json!{{ + &json!({ "name": "somename", "otherprop": "one" - }}, + }), &json!({ "valid": false, "errors": [ @@ -634,10 +633,10 @@ fn test_basic_output( &json!({ "additionalProperties": {"type": "number", "some": "annotation" } }), - &json!{{ + &json!({ "name": 1, "otherprop": 2 - }}, + }), &json!({ "valid": true, "annotations": [ @@ -667,9 +666,9 @@ fn test_basic_output( &json!({ "additionalProperties": false }), - &json!{{ + &json!({ "name": "somename", - }}, + }), &json!({ "valid": false, "errors": [ @@ -685,7 +684,7 @@ fn test_basic_output( &json!({ "additionalProperties": false }), - &json!{{}}, + &json!({}), &json!({ "valid": true, "annotations": [] @@ -698,9 +697,9 @@ fn test_basic_output( "name": {"type": "string", "prop": "annotation"} } }), - &json!{{ + &json!({ "name": "somename", - }}, + }), &json!({ "valid": true, "annotations": [ @@ -719,10 +718,10 @@ fn test_basic_output( "name": {"type": "string", "prop": "annotation"} } }), - &json!{{ + &json!({ "name": "somename", "other": "prop" - }}, + }), &json!({ "valid": false, "errors": [ @@ -741,10 +740,10 @@ fn test_basic_output( "name": {"type": "string", "prop": "annotation"} } }), - &json!{{ + &json!({ "name": "somename", "otherprop": 1 - }}, + }), &json!({ "valid": true, "annotations": [ @@ -773,10 +772,10 @@ fn test_basic_output( "name": {"type": "string", "prop": "annotation"} } }), - &json!{{ + &json!({ "name": "somename", "otherprop": "one" - }}, + }), &json!({ "valid": false, "errors": [ @@ -795,10 +794,10 @@ fn test_basic_output( "^x-": {"type": "integer", "minimum": 5, "patternio": "annotation"}, } }), - &json!{{ + &json!({ "otherprop": "one", "x-foo": 7 - }}, + }), &json!({ "valid": true, "annotations": [ @@ -832,10 +831,10 @@ fn test_basic_output( "^x-": {"type": "integer", "minimum": 5 }, } }), - &json!{{ + &json!({ "otherprop":1, "x-foo": 3 - }}, + }), &json!({ "valid": false, "errors": [ @@ -862,10 +861,10 @@ fn test_basic_output( }, "additionalProperties": {"type": "number" } }), - &json!{{ + &json!({ "name": "somename", "otherprop": "one" - }}, + }), &json!({ "valid": false, "errors": [ @@ -887,10 +886,10 @@ fn test_basic_output( }, "additionalProperties": {"type": "number" } }), - &json!{{ + &json!({ "name": "somename", "otherprop": 1 - }}, + }), &json!({ "valid": true, "annotations": [ @@ -912,10 +911,10 @@ fn test_basic_output( }, "additionalProperties": false }), - &json!{{ + &json!({ "name": "somename", "stringProp1": "one" - }}, + }), &json!({ "valid": true, "annotations": [ @@ -939,11 +938,11 @@ fn test_basic_output( }, "additionalProperties": false }), - &json!{{ + &json!({ "name": "somename", "stringProp1": "one", "otherprop": "something" - }}, + }), &json!({ "valid": false, "errors": [ @@ -962,9 +961,9 @@ fn test_basic_output( }, "additionalProperties": false }), - &json!{{ + &json!({ "stringProp1": "one", - }}, + }), &json!({ "valid": true, "annotations": [ @@ -990,10 +989,10 @@ fn test_basic_output( }, "additionalProperties": false }), - &json!{{ + &json!({ "stringProp1": "one", "otherprop": "something" - }}, + }), &json!({ "valid": false, "errors": [ @@ -1006,15 +1005,15 @@ fn test_basic_output( }); "invalid AdditionalPropertiesWithPatternsFalseValidator" }] fn test_additional_properties_basic_output( - schema_json: &serde_json::Value, + schema: &serde_json::Value, instance: &serde_json::Value, - expected_output: &serde_json::Value, + expected: &serde_json::Value, ) { - let schema = JSONSchema::options().compile(schema_json).unwrap(); - let output_json = serde_json::to_value(schema.apply(instance).basic()).unwrap(); - if &output_json != expected_output { - let expected_str = serde_json::to_string_pretty(expected_output).unwrap(); - let actual_str = serde_json::to_string_pretty(&output_json).unwrap(); + let validator = jsonschema::validator_for(schema).unwrap(); + let output = serde_json::to_value(validator.apply(instance).basic()).unwrap(); + if &output != expected { + let expected_str = serde_json::to_string_pretty(expected).unwrap(); + let actual_str = serde_json::to_string_pretty(&output).unwrap(); panic!("\nExpected:\n{}\n\nGot:\n{}\n", expected_str, actual_str); } } diff --git a/crates/jsonschema/tests/suite.rs b/crates/jsonschema/tests/suite.rs index 8e813c70..c99428f5 100644 --- a/crates/jsonschema/tests/suite.rs +++ b/crates/jsonschema/tests/suite.rs @@ -1,4 +1,4 @@ -use jsonschema::{Draft, JSONSchema}; +use jsonschema::Draft; use std::fs; use testsuite::{suite, Test}; @@ -81,7 +81,7 @@ use testsuite::{suite, Test}; ] )] fn test_suite(test: Test) { - let mut options = JSONSchema::options(); + let mut options = jsonschema::options(); match test.draft { "draft4" => { options.with_draft(Draft::Draft4); @@ -98,10 +98,10 @@ fn test_suite(test: Test) { if test.is_optional { options.should_validate_formats(true); } - let compiled = options + let validator = options .compile(&test.schema) .expect("should not fail to compile schema"); - let result = compiled.validate(&test.data); + let result = validator.validate(&test.data); if test.valid { if let Err(mut errors_iterator) = result { @@ -117,14 +117,14 @@ fn test_suite(test: Test) { ); } assert!( - compiled.is_valid(&test.data), + validator.is_valid(&test.data), "Test case should be valid:\nCase: {}\nTest: {}\nSchema: {}\nInstance: {}", test.case, test.description, test.schema, test.data, ); - let output = compiled.apply(&test.data).basic(); + let output = validator.apply(&test.data).basic(); assert!( output.is_valid(), "Test case should be valid via basic output:\nCase: {}\nTest: {}\nSchema: {}\nInstance: {}\nError: {:?}", @@ -158,14 +158,14 @@ fn test_suite(test: Test) { ); } assert!( - !compiled.is_valid(&test.data), + !validator.is_valid(&test.data), "Test case should be invalid:\nCase: {}\nTest: {}\nSchema: {}\nInstance: {}", test.case, test.description, test.schema, test.data, ); - let output = compiled.apply(&test.data).basic(); + let output = validator.apply(&test.data).basic(); assert!( !output.is_valid(), "Test case should be invalid via basic output:\nCase: {}\nTest: {}\nSchema: {}\nInstance: {}", @@ -187,15 +187,13 @@ fn test_instance_path() { let data: serde_json::Value = serde_json::from_str(&test_file).expect("Valid JSON"); for item in expected.as_array().expect("Is array") { let suite_id = item["suite_id"].as_u64().expect("Is integer") as usize; - let raw_schema = &data[suite_id]["schema"]; - let schema = JSONSchema::options() - .compile(raw_schema) - .unwrap_or_else(|_| { - panic!( - "Valid schema. File: {}; Suite ID: {}; Schema: {}", - filename, suite_id, raw_schema - ) - }); + let schema = &data[suite_id]["schema"]; + let validator = jsonschema::options().compile(schema).unwrap_or_else(|_| { + panic!( + "Valid schema. File: {}; Suite ID: {}; Schema: {}", + filename, suite_id, schema + ) + }); for test_data in item["tests"].as_array().expect("Valid array") { let test_id = test_data["id"].as_u64().expect("Is integer") as usize; let instance_path: Vec<&str> = test_data["instance_path"] @@ -205,7 +203,7 @@ fn test_instance_path() { .map(|value| value.as_str().expect("A string")) .collect(); let instance = &data[suite_id]["tests"][test_id]["data"]; - let error = schema + let error = validator .validate(instance) .expect_err(&format!( "\nFile: {}\nSuite: {}\nTest: {}", diff --git a/fuzz/fuzz_targets/compile.rs b/fuzz/fuzz_targets/compile.rs index bb7685a4..042fa61f 100644 --- a/fuzz/fuzz_targets/compile.rs +++ b/fuzz/fuzz_targets/compile.rs @@ -5,5 +5,5 @@ fuzz_target!(|data: &[u8]| { let Ok(schema) = serde_json::from_slice(data) else { return; }; - let _ = jsonschema::compile(&schema); + let _ = jsonschema::validator_for(&schema); }); diff --git a/fuzz/fuzz_targets/validation.rs b/fuzz/fuzz_targets/validation.rs index 8d6a4b7f..abeb54f6 100644 --- a/fuzz/fuzz_targets/validation.rs +++ b/fuzz/fuzz_targets/validation.rs @@ -5,15 +5,15 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: (&[u8], &[u8])| { let (schema, instance) = data; if let Ok(schema) = serde_json::from_slice(schema) { - if let Ok(compiled) = jsonschema::compile(&schema) { + if let Ok(validator) = jsonschema::validator_for(&schema) { if let Ok(instance) = serde_json::from_slice(instance) { - let _ = compiled.is_valid(&instance); - if let Err(errors) = compiled.validate(&instance) { + let _ = validator.is_valid(&instance); + if let Err(errors) = validator.validate(&instance) { for error in errors { let _ = error.to_string(); } } - let output: BasicOutput = compiled.apply(&instance).basic(); + let output = validator.apply(&instance).basic(); let _ = serde_json::to_value(output).expect("Failed to serialize"); } }