jsonschema_valid/
lib.rs

1//! # jsonschema-valid
2//!
3//! A simple crate to perform [JSON Schema](https://json-schema.org/) validation.
4//!
5//! Supports JSON Schema drafts 4, 6, and 7.
6//!
7//! ## Example:
8//!
9//! The following example validates some JSON data against a draft 6 JSON schema.
10//!
11//! ```rust
12//! # fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
13//! # use serde_json::Value;
14//! # use jsonschema_valid::schemas;
15//! # let schema_json = "{}";
16//! # let your_json_data = "{}";
17//! let schema: Value = serde_json::from_str(schema_json)?;
18//! let data: Value = serde_json::from_str(your_json_data)?;
19//! let cfg = jsonschema_valid::Config::from_schema(&schema, Some(schemas::Draft::Draft6))?;
20//! // Validate the schema itself
21//! assert!(cfg.validate_schema().is_ok());
22//! // Validate a JSON instance against the schema
23//! assert!(cfg.validate(&data).is_ok());
24//!
25//! # Ok(()) }
26//! ````
27
28#![warn(missing_docs)]
29
30use serde_json::Value;
31
32mod config;
33mod context;
34mod error;
35mod format;
36mod resolver;
37pub mod schemas;
38mod unique;
39mod util;
40mod validators;
41
42pub use crate::config::Config;
43use crate::context::Context;
44pub use crate::error::{ErrorIterator, ValidationError};
45
46/// Validates a given JSON instance against a given JSON schema, returning the
47/// errors, if any. draft may provide the schema draft to use. If not provided,
48/// it will be determined automatically from the schema.
49///
50/// # Arguments
51///
52/// * `cfg`: The configuration object to use
53/// * `instance`: The JSON document to validate
54///
55/// # Returns
56///
57/// * `errors`: A `Result` indicating whether there were any validation errors.
58///   If `Ok(())`, the `instance` is valid against `schema`. If `Err(e)`, `e` is
59///   an iterator over all of the validation errors.
60///
61/// ## Example:
62///
63/// The following example validates some JSON data against a draft 6 JSON schema.
64///
65/// ```rust
66/// # fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
67/// # use serde_json::Value;
68/// # use jsonschema_valid::{schemas, Config};
69/// # let schema_json = "{\"type\": \"integer\"}";
70/// # let your_json_data = "\"string\"";
71/// let schema: Value = serde_json::from_str(schema_json)?;
72/// let data: Value = serde_json::from_str(your_json_data)?;
73/// let cfg = jsonschema_valid::Config::from_schema(&schema, Some(schemas::Draft::Draft6))?;
74///
75/// let mut validation = jsonschema_valid::validate(&cfg, &data);
76/// if let Err(errors) = validation {
77///     for error in errors {
78///         println!("Error: {}", error);
79///     }
80/// }
81///
82/// # Ok(()) }
83/// ````
84pub fn validate<'a>(
85    cfg: &'a config::Config<'a>,
86    instance: &'a Value,
87) -> Result<(), ErrorIterator<'a>> {
88    let mut errors = validators::descend(
89        cfg,
90        instance,
91        cfg.get_schema(),
92        None,
93        Context::new_from(cfg.get_schema()),
94    )
95    .peekable();
96
97    if errors.peek().is_none() {
98        Ok(())
99    } else {
100        Err(Box::new(errors))
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    use std::fs;
109    use std::path::PathBuf;
110
111    // Test files we know will fail.
112    const KNOWN_FAILURES: &[&str] = &["refRemote.json"];
113
114    fn test_draft(dirname: &str, draft: schemas::Draft) {
115        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
116        path.push("JSON-Schema-Test-Suite/tests");
117        path.push(dirname);
118
119        let paths = fs::read_dir(path).unwrap();
120
121        for entry in paths {
122            let dir_entry = &entry.unwrap();
123            if KNOWN_FAILURES.contains(&dir_entry.file_name().to_str().unwrap()) {
124                continue;
125            }
126
127            let path = dir_entry.path();
128            if path.extension().map_or_else(|| "", |x| x.to_str().unwrap()) == "json" {
129                println!("Testing {:?}", path.display());
130                let file = fs::File::open(path).unwrap();
131                let json: Value = serde_json::from_reader(file).unwrap();
132                for testset in json.as_array().unwrap().iter() {
133                    println!(
134                        "  Test set {}",
135                        testset.get("description").unwrap().as_str().unwrap()
136                    );
137                    let schema = testset.get("schema").unwrap();
138                    let tests = testset.get("tests").unwrap();
139                    for test in tests.as_array().unwrap().iter() {
140                        println!(
141                            "    Test {}",
142                            test.get("description").unwrap().as_str().unwrap()
143                        );
144                        let data = test.get("data").unwrap();
145                        let valid = test.get("valid").unwrap();
146                        if let Value::Bool(expected_valid) = valid {
147                            let cfg = config::Config::from_schema(schema, Some(draft)).unwrap();
148                            assert!(cfg.validate_schema().is_ok());
149                            let result = validate(&cfg, data);
150                            assert_eq!(result.is_ok(), *expected_valid);
151                            let cfg2 = config::Config::from_schema(schema, Some(draft)).unwrap();
152                            let result2 = cfg2.validate(data);
153                            assert!(cfg2.validate_schema().is_ok());
154                            assert_eq!(result2.is_ok(), *expected_valid);
155                        }
156                    }
157                }
158            }
159        }
160    }
161
162    #[test]
163    fn test_draft7() {
164        test_draft("draft7", schemas::Draft::Draft7);
165    }
166
167    #[test]
168    fn test_draft6() {
169        test_draft("draft6", schemas::Draft::Draft6);
170    }
171
172    #[test]
173    fn test_draft4() {
174        test_draft("draft4", schemas::Draft::Draft4);
175    }
176}