Skip to main content

rsonschema/
lib.rs

1#![deny(missing_docs)]
2#![deny(warnings)]
3
4//! # **rsonschema**
5//!
6//! A fast, simple, user-friendly JSON Schema validator for Rust
7//!
8//! ## Usage
9//!
10//! To use this crate, add it to your `Cargo.toml` running:
11//!
12//! ```bash
13//! cargo add rsonschema
14//! ```
15//!
16//! Then start using it in your code:
17//!
18//! ```rust
19//! let schema = serde_json::json!({
20//!     "$schema": "https://json-schema.org/draft/2020-12/schema",
21//!     "minLength": 3
22//! });
23//!
24//! let instance = serde_json::json!("foo");
25//! let report = rsonschema::validate(
26//!     &instance,
27//!     schema.clone(),
28//! );
29//! assert!(report.is_valid());
30//!
31//! let instance = serde_json::json!("a");
32//! let report = rsonschema::validate(
33//!     &instance,
34//!     schema,
35//! );
36//! assert_eq!(
37//!     report,
38//!     rsonschema::ValidationReport {
39//!         errors: Some(
40//!             rsonschema::error::ValidationErrors::from([
41//!                 rsonschema::error::ValidationError {
42//!                     instance: serde_json::json!("a"),
43//!                     type_: rsonschema::error::type_::ValidationErrorType::MinLength {
44//!                         limit: 3.into(),
45//!                     },
46//!                     ..Default::default()
47//!                 }
48//!             ])
49//!         ),
50//!         ..Default::default()
51//!     }
52//! );
53//! ```
54//!
55//! ## Contribute
56//!
57//! ### FAQ
58//!
59//! #### Too many arguments?
60//!
61//! We doubted whether having such a large number of arguments on `validate` method was a correct choice.
62//! Currently we suppose this is the best option because every argument has its own differnt lifetime:
63//!
64//! - `instance` is the reference to the JSON instance to be validated and it is borrowed from the caller.
65//!   It changes for every validation subschema
66//! - `state` is a mutable reference to the state of the validation process.
67//!   It contains attributes that dont't change during the validation process,
68//!   such as the absolute schemas already encountered and the reference resolver
69//! - `relative_schemas` is a immutable reference to the relative schemas.
70//!   It is fundamental for the `$ref` keyword.
71//!   It is immutable because it is evaluated at the beginning the validation process for every new subschema
72//! - `parent_id` is a reference to the parent schema id
73//!   It is fundamental for the `$ref` keyword.
74//!   It is immutable because it changes at every validation subschema
75
76/// The module containing the error that may occurs while validating a JSON instance against a JSON schema
77pub mod error;
78/// The module containing the report of the validation
79mod report;
80/// The module containing the schema definitions
81pub mod schema;
82
83use either::Either;
84use serde_json::Value;
85use std::{collections, fmt};
86
87pub use report::ValidationReport;
88
89/// The custom reference resolver
90type RefResolver<'a> = &'a dyn Fn(&str) -> Option<Value>;
91
92/// The schemas involved in the validation
93type Schemas = collections::HashMap<schema::common::id::Id, Value>;
94
95/// Validate the given JSON instance against the schema
96pub fn validate(instance: &Value, schema: Value) -> ValidationReport {
97    validate_with_resolver(instance, schema, None, None)
98}
99
100/// Validate the given JSON instance against the schema using a custom reference resolver
101pub fn validate_with_resolver(
102    instance: &Value,
103    schema: Value,
104    pointer: Option<&str>,
105    ref_resolver: Option<RefResolver>,
106) -> ValidationReport {
107    let validable_schema = schema::common::ref_::get_schema(&schema, None, pointer);
108    match validable_schema {
109        Either::Left((validable_schema, parent_id)) => {
110            let parent_id = parent_id.as_ref();
111            let absolute_schemas = validable_schema.get_schemas(parent_id, true);
112            let mut relative_schemas = validable_schema.get_schemas(parent_id, false);
113            relative_schemas.insert(schema::common::id::Id::Relative(String::new()), schema);
114            let ref_resolver = ref_resolver.unwrap_or(&schema::common::ref_::resolve_ref);
115            let mut state = schema::common::state::State {
116                absolute_schemas,
117                ref_resolver,
118            };
119            validable_schema.validate(instance, &mut state, &relative_schemas, parent_id)
120        }
121        Either::Right(err) => ValidationReport {
122            errors: Some(vec![err]),
123            ..Default::default()
124        },
125    }
126}
127
128trait Validable: fmt::Debug {
129    fn get_schemas(&self, parent_id: Option<&schema::common::id::Id>, is_absolute: bool)
130    -> Schemas;
131
132    fn validate(
133        &self,
134        instance: &Value,
135        state: &mut schema::common::state::State,
136        relative_schemas: &Schemas,
137        parent_id: Option<&schema::common::id::Id>,
138    ) -> ValidationReport;
139}
140
141#[cfg(test)]
142mod tests {
143
144    use super::*;
145
146    pub(crate) fn assert_validate(
147        instance: Value,
148        schema: Value,
149        errors: Option<error::ValidationErrors>,
150    ) {
151        let actual = validate(&instance, schema);
152        let expected = ValidationReport {
153            errors,
154            ids: actual.ids.clone(),
155            evaluated: actual.evaluated.clone(),
156        };
157        assert_eq!(actual, expected);
158    }
159}