From 6c26605764e747d0d6921ec1e99a62bbc0aadbd0 Mon Sep 17 00:00:00 2001 From: Dmitry Dygalo Date: Wed, 18 Sep 2024 22:33:09 +0200 Subject: [PATCH] chore: Make return types more flexible Signed-off-by: Dmitry Dygalo --- CHANGELOG.md | 2 + README.md | 3 +- crates/jsonschema/src/compilation/mod.rs | 4 +- crates/jsonschema/src/compilation/options.rs | 12 +- crates/jsonschema/src/lib.rs | 150 +++++++++++++------ 5 files changed, 114 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93a69d49..7d6fd703 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ ### Changed - Make `Debug` implementation for `SchemaNode` opaque. +- Make `jsonschema::validator_for` and related functions return `ValidationError<'static>` in their `Err` variant. + This change makes possible to use the `?` operator to return errors from functions where the input schema is defined. ### Deprecated diff --git a/README.md b/README.md index 1f9fad64..823a88dc 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,7 @@ fn main() -> Result<(), Box> { assert!(jsonschema::is_valid(&schema, &instance)); // Build & reuse (faster) - let validator = jsonschema::validator_for(&schema) - .expect("Invalid schema"); + let validator = jsonschema::validator_for(&schema)?; // Iterate over errors if let Err(errors) = validator.validate(&instance) { diff --git a/crates/jsonschema/src/compilation/mod.rs b/crates/jsonschema/src/compilation/mod.rs index dc39a6e5..e8581933 100644 --- a/crates/jsonschema/src/compilation/mod.rs +++ b/crates/jsonschema/src/compilation/mod.rs @@ -65,14 +65,14 @@ impl Validator { ValidationOptions::default() } /// Create a validator using the default options. - pub fn new(schema: &Value) -> Result { + pub fn new(schema: &Value) -> Result> { Self::options().build(schema) } /// Create a validator using the default options. /// /// **DEPRECATED**: Use [`Validator::new`] instead. #[deprecated(since = "0.20.0", note = "Use `Validator::new` instead")] - pub fn compile(schema: &Value) -> Result { + pub fn compile(schema: &Value) -> Result> { Self::new(schema) } /// Run validation against `instance` and return an iterator over [`ValidationError`] in the error case. diff --git a/crates/jsonschema/src/compilation/options.rs b/crates/jsonschema/src/compilation/options.rs index a685c51e..81d2ec07 100644 --- a/crates/jsonschema/src/compilation/options.rs +++ b/crates/jsonschema/src/compilation/options.rs @@ -309,10 +309,7 @@ impl ValidationOptions { /// assert!(validator.is_valid(&json!("Hello"))); /// assert!(!validator.is_valid(&json!(42))); /// ``` - pub fn build<'a>( - &self, - schema: &'a serde_json::Value, - ) -> Result> { + pub fn build(&self, schema: &serde_json::Value) -> Result> { // Draft is detected in the following precedence order: // - Explicitly specified; // - $schema field in the document; @@ -348,11 +345,14 @@ impl ValidationOptions { .validate(schema) .err() { - return Err(errors.next().expect("Should have at least one element")); + return Err(errors + .next() + .expect("Should have at least one element") + .into_owned()); } } - let node = compile_validators(schema, &context)?; + let node = compile_validators(schema, &context).map_err(|err| err.into_owned())?; Ok(Validator { node, config }) } diff --git a/crates/jsonschema/src/lib.rs b/crates/jsonschema/src/lib.rs index 5dc2f7c8..c68efa32 100644 --- a/crates/jsonschema/src/lib.rs +++ b/crates/jsonschema/src/lib.rs @@ -33,11 +33,11 @@ //! For better performance, especially when validating multiple instances against the same schema, build a validator once and reuse it: //! //! ```rust +//! # fn main() -> Result<(), Box> { //! use serde_json::json; //! //! let schema = json!({"type": "string"}); -//! let validator = jsonschema::validator_for(&schema) -//! .expect("Invalid schema"); +//! let validator = jsonschema::validator_for(&schema)?; //! //! assert!(validator.is_valid(&json!("Hello, world!"))); //! assert!(!validator.is_valid(&json!(42))); @@ -51,6 +51,8 @@ //! eprintln!("Location: {}", error.instance_path); //! } //! } +//! # Ok(()) +//! # } //! ``` //! //! # Configuration @@ -75,13 +77,15 @@ //! Here's how you can explicitly use a specific draft version: //! //! ```rust +//! # fn main() -> Result<(), Box> { //! use serde_json::json; //! //! let schema = json!({"type": "string"}); -//! let validator = jsonschema::draft7::new(&schema) -//! .expect("Invalid schema"); +//! let validator = jsonschema::draft7::new(&schema)?; //! //! assert!(validator.is_valid(&json!("Hello"))); +//! # Ok(()) +//! # } //! ``` //! //! You can also use the convenience `is_valid` function for quick validation: @@ -98,17 +102,19 @@ //! For more advanced configuration, you can use the draft-specific `options` function: //! //! ```rust +//! # fn main() -> Result<(), Box> { //! 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) -//! .build(&schema) -//! .expect("Invalid schema"); +//! .build(&schema)?; //! //! assert!(validator.is_valid(&json!("Hello 42"))); //! assert!(!validator.is_valid(&json!("No!"))); +//! # Ok(()) +//! # } //! ``` //! //! ## General Configuration @@ -118,15 +124,17 @@ //! Here's an example of using the general options builder: //! //! ```rust +//! # fn main() -> Result<(), Box> { //! use serde_json::json; //! //! let schema = json!({"type": "string"}); //! let validator = jsonschema::options() //! // Add configuration options here -//! .build(&schema) -//! .expect("Invalid schema"); +//! .build(&schema)?; //! //! assert!(validator.is_valid(&json!("Hello"))); +//! # Ok(()) +//! # } //! ``` //! //! For a complete list of configuration options and their usage, please refer to the [`ValidationOptions`] struct. @@ -137,13 +145,15 @@ //! which automatically detects the appropriate draft: //! //! ```rust +//! # fn main() -> Result<(), Box> { //! 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"); +//! let validator = jsonschema::validator_for(&schema)?; //! //! assert!(validator.is_valid(&json!("Hello"))); +//! # Ok(()) +//! # } //! ``` //! //! # Reference Resolving @@ -211,8 +221,7 @@ //! //! let validator = jsonschema::options() //! .with_resolver(resolver) -//! .build(&schema) -//! .expect("Invalid schema"); +//! .build(&schema)?; //! //! assert!(validator.is_valid(&json!({ //! "name": "Alice", @@ -239,8 +248,7 @@ //! "type": "string" //! }); //! let instance = json!("some string"); -//! let validator = jsonschema::validator_for(&schema_json) -//! .expect("Invalid schema"); +//! let validator = jsonschema::validator_for(&schema_json)?; //! //! let output = validator.apply(&instance).basic(); //! @@ -344,8 +352,7 @@ //! let schema = json!({"even-number": true, "type": "integer"}); //! let validator = jsonschema::options() //! .with_keyword("even-number", even_number_validator_factory) -//! .build(&schema) -//! .expect("Invalid schema"); +//! .build(&schema)?; //! //! assert!(validator.is_valid(&json!(2))); //! assert!(!validator.is_valid(&json!(3))); @@ -384,13 +391,15 @@ //! # true //! # } //! # } +//! # fn main() -> Result<(), Box> { //! let schema = json!({"even-number": true, "type": "integer"}); //! let validator = jsonschema::options() //! .with_keyword("even-number", |_, _, _| { //! Ok(Box::new(EvenNumberValidator)) //! }) -//! .build(&schema) -//! .expect("Invalid schema"); +//! .build(&schema)?; +//! # Ok(()) +//! # } //! ``` //! //! # Custom Formats @@ -423,8 +432,7 @@ //! let validator = jsonschema::options() //! .with_format("ends-with-42", ends_with_42) //! .with_format("ends-with-43", |s| s.ends_with("43!")) -//! .build(&schema) -//! .expect("Invalid schema"); +//! .build(&schema)?; //! //! // Step 4: Validate instances //! assert!(validator.is_valid(&json!("Hello42!"))); @@ -505,7 +513,7 @@ pub fn is_valid(schema: &Value, instance: &Value) -> bool { /// /// This function is deprecated since version 0.20.0. Use [`validator_for`] instead. #[deprecated(since = "0.20.0", note = "Use `validator_for` instead")] -pub fn compile(schema: &Value) -> Result { +pub fn compile(schema: &Value) -> Result> { Validator::new(schema) } @@ -514,15 +522,18 @@ pub fn compile(schema: &Value) -> Result { /// # Examples /// /// ```rust +/// # fn main() -> Result<(), Box> { /// use serde_json::json; /// /// let schema = json!({"minimum": 5}); /// let instance = json!(42); /// -/// let validator = jsonschema::validator_for(&schema).expect("Invalid schema"); +/// let validator = jsonschema::validator_for(&schema)?; /// assert!(validator.is_valid(&instance)); +/// # Ok(()) +/// # } /// ``` -pub fn validator_for(schema: &Value) -> Result { +pub fn validator_for(schema: &Value) -> Result> { Validator::new(schema) } @@ -537,31 +548,35 @@ pub fn validator_for(schema: &Value) -> Result { /// Basic usage with draft specification: /// /// ``` +/// # fn main() -> Result<(), Box> { /// use serde_json::json; /// use jsonschema::Draft; /// /// let schema = json!({"type": "string"}); /// let validator = jsonschema::options() /// .with_draft(Draft::Draft7) -/// .build(&schema) -/// .expect("Invalid schema"); +/// .build(&schema)?; /// /// assert!(validator.is_valid(&json!("Hello"))); +/// # Ok(()) +/// # } /// ``` /// /// Advanced configuration: /// /// ``` +/// # fn main() -> Result<(), Box> { /// use serde_json::json; /// /// let schema = json!({"type": "string", "format": "custom"}); /// let validator = jsonschema::options() /// .with_format("custom", |value| value.len() == 3) -/// .build(&schema) -/// .expect("Invalid schema"); +/// .build(&schema)?; /// /// assert!(validator.is_valid(&json!("abc"))); /// assert!(!validator.is_valid(&json!("abcd"))); +/// # Ok(()) +/// # } /// ``` /// /// See [`ValidationOptions`] for all available configuration options. @@ -594,15 +609,18 @@ pub mod draft4 { /// # Examples /// /// ```rust + /// # fn main() -> Result<(), Box> { /// use serde_json::json; /// /// let schema = json!({"minimum": 5}); /// let instance = json!(42); /// - /// let validator = jsonschema::draft4::new(&schema).expect("Invalid schema"); + /// let validator = jsonschema::draft4::new(&schema)?; /// assert!(validator.is_valid(&instance)); + /// # Ok(()) + /// # } /// ``` - pub fn new(schema: &Value) -> Result { + pub fn new(schema: &Value) -> Result> { options().build(schema) } /// Validate an instance against a schema using Draft 4 specifications without creating a validator. @@ -630,17 +648,19 @@ pub mod draft4 { /// # Examples /// /// ``` + /// # fn main() -> Result<(), Box> { /// 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) - /// .build(&schema) - /// .expect("Invalid schema"); + /// .build(&schema)?; /// /// assert!(validator.is_valid(&json!("Hello 42"))); /// assert!(!validator.is_valid(&json!("No!"))); + /// # Ok(()) + /// # } /// ``` /// /// See [`ValidationOptions`] for all available configuration options. @@ -677,15 +697,18 @@ pub mod draft6 { /// # Examples /// /// ```rust + /// # fn main() -> Result<(), Box> { /// use serde_json::json; /// /// let schema = json!({"minimum": 5}); /// let instance = json!(42); /// - /// let validator = jsonschema::draft6::new(&schema).expect("Invalid schema"); + /// let validator = jsonschema::draft6::new(&schema)?; /// assert!(validator.is_valid(&instance)); + /// # Ok(()) + /// # } /// ``` - pub fn new(schema: &Value) -> Result { + pub fn new(schema: &Value) -> Result> { options().build(schema) } /// Validate an instance against a schema using Draft 6 specifications without creating a validator. @@ -713,17 +736,19 @@ pub mod draft6 { /// # Examples /// /// ``` + /// # fn main() -> Result<(), Box> { /// 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) - /// .build(&schema) - /// .expect("Invalid schema"); + /// .build(&schema)?; /// /// assert!(validator.is_valid(&json!("Hello 42"))); /// assert!(!validator.is_valid(&json!("No!"))); + /// # Ok(()) + /// # } /// ``` /// /// See [`ValidationOptions`] for all available configuration options. @@ -760,15 +785,18 @@ pub mod draft7 { /// # Examples /// /// ```rust + /// # fn main() -> Result<(), Box> { /// use serde_json::json; /// /// let schema = json!({"minimum": 5}); /// let instance = json!(42); /// - /// let validator = jsonschema::draft7::new(&schema).expect("Invalid schema"); + /// let validator = jsonschema::draft7::new(&schema)?; /// assert!(validator.is_valid(&instance)); + /// # Ok(()) + /// # } /// ``` - pub fn new(schema: &Value) -> Result { + pub fn new(schema: &Value) -> Result> { options().build(schema) } /// Validate an instance against a schema using Draft 7 specifications without creating a validator. @@ -796,17 +824,19 @@ pub mod draft7 { /// # Examples /// /// ``` + /// # fn main() -> Result<(), Box> { /// 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) - /// .build(&schema) - /// .expect("Invalid schema"); + /// .build(&schema)?; /// /// assert!(validator.is_valid(&json!("Hello 42"))); /// assert!(!validator.is_valid(&json!("No!"))); + /// # Ok(()) + /// # } /// ``` /// /// See [`ValidationOptions`] for all available configuration options. @@ -843,15 +873,18 @@ pub mod draft201909 { /// # Examples /// /// ```rust + /// # fn main() -> Result<(), Box> { /// use serde_json::json; /// /// let schema = json!({"minimum": 5}); /// let instance = json!(42); /// - /// let validator = jsonschema::draft201909::new(&schema).expect("Invalid schema"); + /// let validator = jsonschema::draft201909::new(&schema)?; /// assert!(validator.is_valid(&instance)); + /// # Ok(()) + /// # } /// ``` - pub fn new(schema: &Value) -> Result { + pub fn new(schema: &Value) -> Result> { options().build(schema) } /// Validate an instance against a schema using Draft 2019-09 specifications without creating a validator. @@ -879,17 +912,19 @@ pub mod draft201909 { /// # Examples /// /// ``` + /// # fn main() -> Result<(), Box> { /// 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) - /// .build(&schema) - /// .expect("Invalid schema"); + /// .build(&schema)?; /// /// assert!(validator.is_valid(&json!("Hello 42"))); /// assert!(!validator.is_valid(&json!("No!"))); + /// # Ok(()) + /// # } /// ``` /// /// See [`ValidationOptions`] for all available configuration options. @@ -911,12 +946,15 @@ pub mod draft201909 { /// # Examples /// /// ```rust +/// # fn main() -> Result<(), Box> { /// 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)); +/// # Ok(()) +/// # } /// ``` pub mod draft202012 { use super::*; @@ -926,15 +964,18 @@ pub mod draft202012 { /// # Examples /// /// ```rust + /// # fn main() -> Result<(), Box> { /// use serde_json::json; /// /// let schema = json!({"minimum": 5}); /// let instance = json!(42); /// - /// let validator = jsonschema::draft202012::new(&schema).expect("Invalid schema"); + /// let validator = jsonschema::draft202012::new(&schema)?; /// assert!(validator.is_valid(&instance)); + /// # Ok(()) + /// # } /// ``` - pub fn new(schema: &Value) -> Result { + pub fn new(schema: &Value) -> Result> { options().build(schema) } /// Validate an instance against a schema using Draft 2020-12 specifications without creating a validator. @@ -962,17 +1003,19 @@ pub mod draft202012 { /// # Examples /// /// ``` + /// # fn main() -> Result<(), Box> { /// 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) - /// .build(&schema) - /// .expect("Invalid schema"); + /// .build(&schema)?; /// /// assert!(validator.is_valid(&json!("Hello 42"))); /// assert!(!validator.is_valid(&json!("No!"))); + /// # Ok(()) + /// # } /// ``` /// /// See [`ValidationOptions`] for all available configuration options. @@ -1089,6 +1132,8 @@ pub(crate) mod tests_util { #[cfg(test)] mod tests { + use crate::validator_for; + use super::Draft; use serde_json::json; use test_case::test_case; @@ -1138,4 +1183,15 @@ mod tests { let schema = json!({"pattern": "\\u"}); assert!(crate::validator_for(&schema).is_err()) } + + #[test] + fn validation_error_propagation() { + fn foo() -> Result<(), Box> { + let schema = json!({}); + let validator = validator_for(&schema)?; + let _ = validator.is_valid(&json!({})); + Ok(()) + } + let _ = foo(); + } }