jsonschema/lib.rs
1//! A high-performance JSON Schema validator for Rust.
2//!
3//! - 📚 Support for popular JSON Schema drafts
4//! - 🔧 Custom keywords and format validators
5//! - 🌐 Blocking & non-blocking remote reference fetching (network/file)
6//! - 🎨 `Basic` output style as per JSON Schema spec
7//! - ✨ Meta-schema validation for schema documents
8//! - 🚀 WebAssembly support
9//!
10//! ## Supported drafts
11//!
12//! Compliance levels vary across drafts, with newer versions having some unimplemented keywords.
13//!
14//! - 
15//! - 
16//! - 
17//! - 
18//! - 
19//!
20//! # Validation
21//!
22//! The `jsonschema` crate offers two main approaches to validation: one-off validation and reusable validators.
23//! When external references are involved, the validator can be constructed using either blocking or non-blocking I/O.
24//!
25//!
26//! For simple use cases where you need to validate an instance against a schema once, use [`is_valid`] or [`validate`] functions:
27//!
28//! ```rust
29//! use serde_json::json;
30//!
31//! let schema = json!({"type": "string"});
32//! let instance = json!("Hello, world!");
33//!
34//! assert!(jsonschema::is_valid(&schema, &instance));
35//! assert!(jsonschema::validate(&schema, &instance).is_ok());
36//! ```
37//!
38//! For better performance, especially when validating multiple instances against the same schema, build a validator once and reuse it:
39//! If your schema contains external references, you can choose between blocking and non-blocking construction:
40//!
41//! ```rust
42//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
43//! use serde_json::json;
44//!
45//! let schema = json!({"type": "string"});
46//! // Blocking construction - will fetch external references synchronously
47//! let validator = jsonschema::validator_for(&schema)?;
48//! // Non-blocking construction - will fetch external references asynchronously
49//! # #[cfg(feature = "resolve-async")]
50//! # async fn async_example() -> Result<(), Box<dyn std::error::Error>> {
51//! # let schema = json!({"type": "string"});
52//! let validator = jsonschema::async_validator_for(&schema).await?;
53//! # Ok(())
54//! # }
55//!
56//! // Once constructed, validation is always synchronous as it works with in-memory data
57//! assert!(validator.is_valid(&json!("Hello, world!")));
58//! assert!(!validator.is_valid(&json!(42)));
59//! assert!(validator.validate(&json!(42)).is_err());
60//!
61//! // Iterate over all errors
62//! let instance = json!(42);
63//! for error in validator.iter_errors(&instance) {
64//! eprintln!("Error: {}", error);
65//! eprintln!("Location: {}", error.instance_path);
66//! }
67//! # Ok(())
68//! # }
69//! ```
70//!
71//! ### Note on `format` keyword
72//!
73//! By default, format validation is draft‑dependent. To opt in for format checks, you can configure your validator like this:
74//!
75//! ```rust
76//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
77//! # use serde_json::json;
78//! #
79//! # let schema = json!({"type": "string"});
80//! let validator = jsonschema::draft202012::options()
81//! .should_validate_formats(true)
82//! .build(&schema)?;
83//! # Ok(())
84//! # }
85//! ```
86//!
87//! Once built, any `format` keywords in your schema will be actively validated according to the chosen draft.
88//!
89//! # Meta-Schema Validation
90//!
91//! The crate provides functionality to validate JSON Schema documents themselves against their meta-schemas.
92//! This ensures your schema documents are valid according to the JSON Schema specification.
93//!
94//! ```rust
95//! use serde_json::json;
96//!
97//! let schema = json!({
98//! "type": "object",
99//! "properties": {
100//! "name": {"type": "string"},
101//! "age": {"type": "integer", "minimum": 0}
102//! }
103//! });
104//!
105//! // Validate schema with automatic draft detection
106//! assert!(jsonschema::meta::is_valid(&schema));
107//! assert!(jsonschema::meta::validate(&schema).is_ok());
108//!
109//! // Invalid schema example
110//! let invalid_schema = json!({
111//! "type": "invalid_type", // must be one of the valid JSON Schema types
112//! "minimum": "not_a_number"
113//! });
114//! assert!(!jsonschema::meta::is_valid(&invalid_schema));
115//! assert!(jsonschema::meta::validate(&invalid_schema).is_err());
116//! ```
117//!
118//! # Configuration
119//!
120//! `jsonschema` provides several ways to configure and use JSON Schema validation.
121//!
122//! ## Draft-specific Modules
123//!
124//! The library offers modules for specific JSON Schema draft versions:
125//!
126//! - [`draft4`]
127//! - [`draft6`]
128//! - [`draft7`]
129//! - [`draft201909`]
130//! - [`draft202012`]
131//!
132//! Each module provides:
133//! - A `new` function to create a validator
134//! - An `is_valid` function for validation with a boolean result
135//! - An `validate` function for getting the first validation error
136//! - An `options` function to create a draft-specific configuration builder
137//! - A `meta` module for draft-specific meta-schema validation
138//!
139//! Here's how you can explicitly use a specific draft version:
140//!
141//! ```rust
142//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
143//! use serde_json::json;
144//!
145//! let schema = json!({"type": "string"});
146//!
147//! // Instance validation
148//! let validator = jsonschema::draft7::new(&schema)?;
149//! assert!(validator.is_valid(&json!("Hello")));
150//!
151//! // Meta-schema validation
152//! assert!(jsonschema::draft7::meta::is_valid(&schema));
153//! # Ok(())
154//! # }
155//! ```
156//!
157//! You can also use the convenience [`is_valid`] and [`validate`] functions:
158//!
159//! ```rust
160//! use serde_json::json;
161//!
162//! let schema = json!({"type": "number", "minimum": 0});
163//! let instance = json!(42);
164//!
165//! assert!(jsonschema::draft202012::is_valid(&schema, &instance));
166//! assert!(jsonschema::draft202012::validate(&schema, &instance).is_ok());
167//! ```
168//!
169//! For more advanced configuration, you can use the draft-specific `options` function:
170//!
171//! ```rust
172//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
173//! use serde_json::json;
174//!
175//! let schema = json!({"type": "string", "format": "ends-with-42"});
176//! let validator = jsonschema::draft202012::options()
177//! .with_format("ends-with-42", |s| s.ends_with("42"))
178//! .should_validate_formats(true)
179//! .build(&schema)?;
180//!
181//! assert!(validator.is_valid(&json!("Hello 42")));
182//! assert!(!validator.is_valid(&json!("No!")));
183//! # Ok(())
184//! # }
185//! ```
186//!
187//! ## General Configuration
188//!
189//! For configuration options that are not draft-specific, `jsonschema` provides a builder via `jsonschema::options()`.
190//!
191//! Here's an example:
192//!
193//! ```rust
194//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
195//! use serde_json::json;
196//!
197//! let schema = json!({"type": "string"});
198//! let validator = jsonschema::options()
199//! // Add configuration options here
200//! .build(&schema)?;
201//!
202//! assert!(validator.is_valid(&json!("Hello")));
203//! # Ok(())
204//! # }
205//! ```
206//!
207//! For a complete list of configuration options and their usage, please refer to the [`ValidationOptions`] struct.
208//!
209//! ## Automatic Draft Detection
210//!
211//! If you don't need to specify a particular draft version, you can use `jsonschema::validator_for`
212//! which automatically detects the appropriate draft:
213//!
214//! ```rust
215//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
216//! use serde_json::json;
217//!
218//! let schema = json!({"$schema": "http://json-schema.org/draft-07/schema#", "type": "string"});
219//! let validator = jsonschema::validator_for(&schema)?;
220//!
221//! assert!(validator.is_valid(&json!("Hello")));
222//! # Ok(())
223//! # }
224//! ```
225//!
226//! # External References
227//!
228//! By default, `jsonschema` resolves HTTP references using `reqwest` and file references from the local file system.
229//! Both blocking and non-blocking retrieval is supported during validator construction. Note that the validation
230//! itself is always synchronous as it operates on in-memory data only.
231//!
232//! ```rust
233//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
234//! use serde_json::json;
235//!
236//! let schema = json!({"$schema": "http://json-schema.org/draft-07/schema#", "type": "string"});
237//!
238//! // Building a validator with blocking retrieval (default)
239//! let validator = jsonschema::validator_for(&schema)?;
240//!
241//! // Building a validator with non-blocking retrieval (requires `resolve-async` feature)
242//! # #[cfg(feature = "resolve-async")]
243//! let validator = jsonschema::async_validator_for(&schema).await?;
244//!
245//! // Validation is always synchronous
246//! assert!(validator.is_valid(&json!("Hello")));
247//! # Ok(())
248//! # }
249//! ```
250//!
251//! To enable HTTPS support, add the `rustls-tls` feature to `reqwest` in your `Cargo.toml`:
252//!
253//! ```toml
254//! reqwest = { version = "*", features = ["rustls-tls"] }
255//! ```
256//!
257//! You can disable the default behavior using crate features:
258//!
259//! - Disable HTTP resolving: `default-features = false, features = ["resolve-file"]`
260//! - Disable file resolving: `default-features = false, features = ["resolve-http"]`
261//! - Enable async resolution: `features = ["resolve-async"]`
262//! - Disable all resolving: `default-features = false`
263//!
264//! ## Custom retrievers
265//!
266//! You can implement custom retrievers for both blocking and non-blocking retrieval:
267//!
268//! ```rust
269//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
270//! use std::{collections::HashMap, sync::Arc};
271//! use jsonschema::{Retrieve, Uri};
272//! use serde_json::{json, Value};
273//!
274//! struct InMemoryRetriever {
275//! schemas: HashMap<String, Value>,
276//! }
277//!
278//! impl Retrieve for InMemoryRetriever {
279//!
280//! fn retrieve(
281//! &self,
282//! uri: &Uri<String>,
283//! ) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
284//! self.schemas
285//! .get(uri.as_str())
286//! .cloned()
287//! .ok_or_else(|| format!("Schema not found: {uri}").into())
288//! }
289//! }
290//!
291//! let mut schemas = HashMap::new();
292//! schemas.insert(
293//! "https://example.com/person.json".to_string(),
294//! json!({
295//! "type": "object",
296//! "properties": {
297//! "name": { "type": "string" },
298//! "age": { "type": "integer" }
299//! },
300//! "required": ["name", "age"]
301//! }),
302//! );
303//!
304//! let retriever = InMemoryRetriever { schemas };
305//!
306//! let schema = json!({
307//! "$ref": "https://example.com/person.json"
308//! });
309//!
310//! let validator = jsonschema::options()
311//! .with_retriever(retriever)
312//! .build(&schema)?;
313//!
314//! assert!(validator.is_valid(&json!({
315//! "name": "Alice",
316//! "age": 30
317//! })));
318//!
319//! assert!(!validator.is_valid(&json!({
320//! "name": "Bob"
321//! })));
322//! # Ok(())
323//! # }
324//! ```
325//!
326//! And non-blocking version with the `resolve-async` feature enabled:
327//!
328//! ```rust
329//! # #[cfg(feature = "resolve-async")]
330//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
331//! use jsonschema::{AsyncRetrieve, Registry, Resource, Uri};
332//! use serde_json::{Value, json};
333//!
334//! struct HttpRetriever;
335//!
336//! #[async_trait::async_trait]
337//! impl AsyncRetrieve for HttpRetriever {
338//! async fn retrieve(
339//! &self,
340//! uri: &Uri<String>,
341//! ) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
342//! reqwest::get(uri.as_str())
343//! .await?
344//! .json()
345//! .await
346//! .map_err(Into::into)
347//! }
348//! }
349//!
350//! // Then use it to build a validator
351//! let validator = jsonschema::async_options()
352//! .with_retriever(HttpRetriever)
353//! .build(&json!({"$ref": "https://example.com/user.json"}))
354//! .await?;
355//! # Ok(())
356//! # }
357//! ```
358//!
359//! # Output Styles
360//!
361//! `jsonschema` supports the `basic` output style as defined in JSON Schema Draft 2019-09.
362//! This styles allow you to serialize validation results in a standardized format using `serde`.
363//!
364//! ```rust
365//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
366//! use serde_json::json;
367//!
368//! let schema_json = json!({
369//! "title": "string value",
370//! "type": "string"
371//! });
372//! let instance = json!("some string");
373//! let validator = jsonschema::validator_for(&schema_json)?;
374//!
375//! let output = validator.apply(&instance).basic();
376//!
377//! assert_eq!(
378//! serde_json::to_value(output)?,
379//! json!({
380//! "valid": true,
381//! "annotations": [
382//! {
383//! "keywordLocation": "",
384//! "instanceLocation": "",
385//! "annotations": {
386//! "title": "string value"
387//! }
388//! }
389//! ]
390//! })
391//! );
392//! # Ok(())
393//! # }
394//! ```
395//!
396//! # Regular Expression Configuration
397//!
398//! The `jsonschema` crate allows configuring the regular expression engine used for validating
399//! keywords like `pattern` or `patternProperties`.
400//!
401//! By default, the crate uses [`fancy-regex`](https://docs.rs/fancy-regex), which supports advanced
402//! regular expression features such as lookaround and backreferences.
403//!
404//! The primary motivation for switching to the `regex` engine is security and performance:
405//! it guarantees linear-time matching, preventing potential DoS attacks from malicious patterns
406//! in user-provided schemas while offering better performance with a smaller feature set.
407//!
408//! You can configure the engine at **runtime** using the [`PatternOptions`] API:
409//!
410//! ### Example: Configure `fancy-regex` with Backtracking Limit
411//!
412//! ```rust
413//! use serde_json::json;
414//! use jsonschema::PatternOptions;
415//!
416//! let schema = json!({
417//! "type": "string",
418//! "pattern": "^(a+)+$"
419//! });
420//!
421//! let validator = jsonschema::options()
422//! .with_pattern_options(
423//! PatternOptions::fancy_regex()
424//! .backtrack_limit(10_000)
425//! )
426//! .build(&schema)
427//! .expect("A valid schema");
428//! ```
429//!
430//! ### Example: Use the `regex` Engine Instead
431//!
432//! ```rust
433//! use serde_json::json;
434//! use jsonschema::PatternOptions;
435//!
436//! let schema = json!({
437//! "type": "string",
438//! "pattern": "^a+$"
439//! });
440//!
441//! let validator = jsonschema::options()
442//! .with_pattern_options(PatternOptions::regex())
443//! .build(&schema)
444//! .expect("A valid schema");
445//! ```
446//!
447//! ### Notes
448//!
449//! - If neither engine is explicitly set, `fancy-regex` is used by default.
450//! - Regular expressions that rely on advanced features like `(?<=...)` (lookbehind) or backreferences (`\1`) will fail with the `regex` engine.
451//!
452//! # Custom Keywords
453//!
454//! `jsonschema` allows you to extend its functionality by implementing custom validation logic through custom keywords.
455//! This feature is particularly useful when you need to validate against domain-specific rules that aren't covered by the standard JSON Schema keywords.
456//!
457//! To implement a custom keyword, you need to:
458//! 1. Create a struct that implements the [`Keyword`] trait
459//! 2. Create a factory function or closure that produces instances of your custom keyword
460//! 3. Register the custom keyword with the [`Validator`] instance using the [`ValidationOptions::with_keyword`] method
461//!
462//! Here's a complete example:
463//!
464//! ```rust
465//! use jsonschema::{
466//! paths::{LazyLocation, Location},
467//! Keyword, ValidationError,
468//! };
469//! use serde_json::{json, Map, Value};
470//! use std::iter::once;
471//!
472//! // Step 1: Implement the Keyword trait
473//! struct EvenNumberValidator;
474//!
475//! impl Keyword for EvenNumberValidator {
476//! fn validate<'i>(
477//! &self,
478//! instance: &'i Value,
479//! location: &LazyLocation,
480//! ) -> Result<(), ValidationError<'i>> {
481//! if let Value::Number(n) = instance {
482//! if n.as_u64().map_or(false, |n| n % 2 == 0) {
483//! Ok(())
484//! } else {
485//! return Err(ValidationError::custom(
486//! Location::new(),
487//! location.into(),
488//! instance,
489//! "Number must be even",
490//! ));
491//! }
492//! } else {
493//! Err(ValidationError::custom(
494//! Location::new(),
495//! location.into(),
496//! instance,
497//! "Value must be a number",
498//! ))
499//! }
500//! }
501//!
502//! fn is_valid(&self, instance: &Value) -> bool {
503//! instance.as_u64().map_or(false, |n| n % 2 == 0)
504//! }
505//! }
506//!
507//! // Step 2: Create a factory function
508//! fn even_number_validator_factory<'a>(
509//! _parent: &'a Map<String, Value>,
510//! value: &'a Value,
511//! path: Location,
512//! ) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
513//! // You can use the `value` parameter to configure your validator if needed
514//! if value.as_bool() == Some(true) {
515//! Ok(Box::new(EvenNumberValidator))
516//! } else {
517//! Err(ValidationError::custom(
518//! Location::new(),
519//! path,
520//! value,
521//! "The 'even-number' keyword must be set to true",
522//! ))
523//! }
524//! }
525//!
526//! // Step 3: Use the custom keyword
527//! fn main() -> Result<(), Box<dyn std::error::Error>> {
528//! let schema = json!({"even-number": true, "type": "integer"});
529//! let validator = jsonschema::options()
530//! .with_keyword("even-number", even_number_validator_factory)
531//! .build(&schema)?;
532//!
533//! assert!(validator.is_valid(&json!(2)));
534//! assert!(!validator.is_valid(&json!(3)));
535//! assert!(!validator.is_valid(&json!("not a number")));
536//!
537//! Ok(())
538//! }
539//! ```
540//!
541//! In this example, we've created a custom `even-number` keyword that validates whether a number is even.
542//! The `EvenNumberValidator` implements the actual validation logic, while the `even_number_validator_factory`
543//! creates instances of the validator and allows for additional configuration based on the keyword's value in the schema.
544//!
545//! You can also use a closure instead of a factory function for simpler cases:
546//!
547//! ```rust
548//! # use jsonschema::{
549//! # paths::LazyLocation,
550//! # Keyword, ValidationError,
551//! # };
552//! # use serde_json::{json, Map, Value};
553//! # use std::iter::once;
554//! #
555//! # struct EvenNumberValidator;
556//! #
557//! # impl Keyword for EvenNumberValidator {
558//! # fn validate<'i>(
559//! # &self,
560//! # instance: &'i Value,
561//! # location: &LazyLocation,
562//! # ) -> Result<(), ValidationError<'i>> {
563//! # Ok(())
564//! # }
565//! #
566//! # fn is_valid(&self, instance: &Value) -> bool {
567//! # true
568//! # }
569//! # }
570//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
571//! let schema = json!({"even-number": true, "type": "integer"});
572//! let validator = jsonschema::options()
573//! .with_keyword("even-number", |_, _, _| {
574//! Ok(Box::new(EvenNumberValidator))
575//! })
576//! .build(&schema)?;
577//! # Ok(())
578//! # }
579//! ```
580//!
581//! # Custom Formats
582//!
583//! JSON Schema allows for format validation through the `format` keyword. While `jsonschema`
584//! provides built-in validators for standard formats, you can also define custom format validators
585//! for domain-specific string formats.
586//!
587//! To implement a custom format validator:
588//!
589//! 1. Define a function or a closure that takes a `&str` and returns a `bool`.
590//! 2. Register the function with `jsonschema::options().with_format()`.
591//!
592//! ```rust
593//! use serde_json::json;
594//!
595//! // Step 1: Define the custom format validator function
596//! fn ends_with_42(s: &str) -> bool {
597//! s.ends_with("42!")
598//! }
599//!
600//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
601//! // Step 2: Create a schema using the custom format
602//! let schema = json!({
603//! "type": "string",
604//! "format": "ends-with-42"
605//! });
606//!
607//! // Step 3: Build the validator with the custom format
608//! let validator = jsonschema::options()
609//! .with_format("ends-with-42", ends_with_42)
610//! .with_format("ends-with-43", |s| s.ends_with("43!"))
611//! .should_validate_formats(true)
612//! .build(&schema)?;
613//!
614//! // Step 4: Validate instances
615//! assert!(validator.is_valid(&json!("Hello42!")));
616//! assert!(!validator.is_valid(&json!("Hello43!")));
617//! assert!(!validator.is_valid(&json!(42))); // Not a string
618//! # Ok(())
619//! # }
620//! ```
621//!
622//! ### Notes on Custom Format Validators
623//!
624//! - Custom format validators are only called for string instances.
625//! - In newer drafts, `format` is purely an annotation and won’t do any checking unless you
626//! opt in by calling `.should_validate_formats(true)` on your options builder. If you omit
627//! it, all `format` keywords are ignored at validation time.
628//!
629//! # WebAssembly support
630//!
631//! When using `jsonschema` in WASM environments, be aware that external references are
632//! not supported by default due to WASM limitations:
633//! - No filesystem access (`resolve-file` feature)
634//! - No direct HTTP requests, at least right now (`resolve-http` feature)
635//!
636//! To use `jsonschema` in WASM, disable default features:
637//!
638//! ```toml
639//! jsonschema = { version = "x.y.z", default-features = false }
640//! ```
641//!
642//! For external references in WASM you may want to implement a custom retriever.
643//! See the [External References](#external-references) section for implementation details.
644
645pub(crate) mod compiler;
646mod content_encoding;
647mod content_media_type;
648mod ecma;
649pub mod error;
650pub mod ext;
651mod keywords;
652mod node;
653mod options;
654pub mod output;
655pub mod paths;
656pub(crate) mod properties;
657pub(crate) mod regex;
658mod retriever;
659pub mod types;
660mod validator;
661
662#[deprecated(since = "0.30.0", note = "Use `jsonschema::types` instead.")]
663pub mod primitive_type {
664 pub use super::types::*;
665}
666
667pub use error::{ErrorIterator, MaskedValidationError, ValidationError};
668pub use keywords::custom::Keyword;
669pub use options::{FancyRegex, PatternOptions, Regex, ValidationOptions};
670pub use output::BasicOutput;
671pub use referencing::{
672 Draft, Error as ReferencingError, Registry, RegistryOptions, Resource, Retrieve, Uri,
673};
674pub use types::{JsonType, JsonTypeSet, JsonTypeSetIterator};
675pub use validator::Validator;
676
677#[cfg(feature = "resolve-async")]
678pub use referencing::AsyncRetrieve;
679
680use serde_json::Value;
681
682#[cfg(all(
683 target_arch = "wasm32",
684 any(feature = "resolve-http", feature = "resolve-file")
685))]
686compile_error!("Features 'resolve-http' and 'resolve-file' are not supported on WASM.");
687
688/// Validate `instance` against `schema` and get a `true` if the instance is valid and `false`
689/// otherwise. Draft is detected automatically.
690///
691/// # Examples
692///
693/// ```rust
694/// use serde_json::json;
695///
696/// let schema = json!({"maxLength": 5});
697/// let instance = json!("foo");
698/// assert!(jsonschema::is_valid(&schema, &instance));
699/// ```
700///
701/// # Panics
702///
703/// This function panics if an invalid schema is passed.
704#[must_use]
705#[inline]
706pub fn is_valid(schema: &Value, instance: &Value) -> bool {
707 validator_for(schema)
708 .expect("Invalid schema")
709 .is_valid(instance)
710}
711
712/// Validate `instance` against `schema` and return the first error if any. Draft is detected automatically.
713///
714/// # Examples
715///
716/// ```rust
717/// use serde_json::json;
718///
719/// let schema = json!({"maxLength": 5});
720/// let instance = json!("foo");
721/// assert!(jsonschema::validate(&schema, &instance).is_ok());
722/// ```
723///
724/// # Panics
725///
726/// This function panics if an invalid schema is passed.
727#[inline]
728pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
729 validator_for(schema)
730 .expect("Invalid schema")
731 .validate(instance)
732}
733
734/// Create a validator for the input schema with automatic draft detection and default options.
735///
736/// # Examples
737///
738/// ```rust
739/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
740/// use serde_json::json;
741///
742/// let schema = json!({"minimum": 5});
743/// let instance = json!(42);
744///
745/// let validator = jsonschema::validator_for(&schema)?;
746/// assert!(validator.is_valid(&instance));
747/// # Ok(())
748/// # }
749/// ```
750pub fn validator_for(schema: &Value) -> Result<Validator, ValidationError<'static>> {
751 Validator::new(schema)
752}
753
754/// Create a validator for the input schema with automatic draft detection and default options,
755/// using non-blocking retrieval for external references.
756///
757/// This is the async counterpart to [`validator_for`]. Note that only the construction is
758/// asynchronous - validation itself is always synchronous.
759///
760/// # Examples
761///
762/// ```rust
763/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
764/// use serde_json::json;
765///
766/// let schema = json!({
767/// "type": "object",
768/// "properties": {
769/// "user": { "$ref": "https://example.com/user.json" }
770/// }
771/// });
772///
773/// let validator = jsonschema::async_validator_for(&schema).await?;
774/// assert!(validator.is_valid(&json!({"user": {"name": "Alice"}})));
775/// # Ok(())
776/// # }
777/// ```
778#[cfg(feature = "resolve-async")]
779pub async fn async_validator_for(schema: &Value) -> Result<Validator, ValidationError<'static>> {
780 Validator::async_new(schema).await
781}
782
783/// Create a builder for configuring JSON Schema validation options.
784///
785/// This function returns a [`ValidationOptions`] struct, which allows you to set various
786/// options for JSON Schema validation. You can use this builder to specify
787/// the draft version, set custom formats, and more.
788///
789/// # Examples
790///
791/// Basic usage with draft specification:
792///
793/// ```
794/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
795/// use serde_json::json;
796/// use jsonschema::Draft;
797///
798/// let schema = json!({"type": "string"});
799/// let validator = jsonschema::options()
800/// .with_draft(Draft::Draft7)
801/// .build(&schema)?;
802///
803/// assert!(validator.is_valid(&json!("Hello")));
804/// # Ok(())
805/// # }
806/// ```
807///
808/// Advanced configuration:
809///
810/// ```
811/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
812/// use serde_json::json;
813///
814/// let schema = json!({"type": "string", "format": "custom"});
815/// let validator = jsonschema::options()
816/// .with_format("custom", |value| value.len() == 3)
817/// .should_validate_formats(true)
818/// .build(&schema)?;
819///
820/// assert!(validator.is_valid(&json!("abc")));
821/// assert!(!validator.is_valid(&json!("abcd")));
822/// # Ok(())
823/// # }
824/// ```
825///
826/// See [`ValidationOptions`] for all available configuration options.
827#[must_use]
828pub fn options() -> ValidationOptions {
829 Validator::options()
830}
831
832/// Create a builder for configuring JSON Schema validation options.
833///
834/// This function returns a [`ValidationOptions`] struct which allows you to set various options for JSON Schema validation.
835/// External references will be retrieved using non-blocking I/O.
836///
837/// # Examples
838///
839/// Basic usage with external references:
840///
841/// ```rust
842/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
843/// use serde_json::json;
844///
845/// let schema = json!({
846/// "$ref": "https://example.com/user.json"
847/// });
848///
849/// let validator = jsonschema::async_options()
850/// .build(&schema)
851/// .await?;
852///
853/// assert!(validator.is_valid(&json!({"name": "Alice"})));
854/// # Ok(())
855/// # }
856/// ```
857///
858/// Advanced configuration:
859///
860/// ```rust
861/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
862/// use serde_json::{Value, json};
863/// use jsonschema::{Draft, AsyncRetrieve, Uri};
864///
865/// // Custom async retriever
866/// struct MyRetriever;
867///
868/// #[async_trait::async_trait]
869/// impl AsyncRetrieve for MyRetriever {
870/// async fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
871/// // Custom retrieval logic
872/// Ok(json!({}))
873/// }
874/// }
875///
876/// let schema = json!({
877/// "$ref": "https://example.com/user.json"
878/// });
879/// let validator = jsonschema::async_options()
880/// .with_draft(Draft::Draft202012)
881/// .with_retriever(MyRetriever)
882/// .build(&schema)
883/// .await?;
884/// # Ok(())
885/// # }
886/// ```
887///
888/// See [`ValidationOptions`] for all available configuration options.
889#[cfg(feature = "resolve-async")]
890#[must_use]
891pub fn async_options() -> ValidationOptions<std::sync::Arc<dyn AsyncRetrieve>> {
892 Validator::async_options()
893}
894
895/// Functionality for validating JSON Schema documents against their meta-schemas.
896pub mod meta {
897 use crate::{error::ValidationError, Draft, ReferencingError};
898 use serde_json::Value;
899
900 use crate::Validator;
901
902 pub(crate) mod validators {
903 use crate::Validator;
904 use once_cell::sync::Lazy;
905
906 pub static DRAFT4_META_VALIDATOR: Lazy<Validator> = Lazy::new(|| {
907 crate::options()
908 .without_schema_validation()
909 .build(&referencing::meta::DRAFT4)
910 .expect("Draft 4 meta-schema should be valid")
911 });
912
913 pub static DRAFT6_META_VALIDATOR: Lazy<Validator> = Lazy::new(|| {
914 crate::options()
915 .without_schema_validation()
916 .build(&referencing::meta::DRAFT6)
917 .expect("Draft 6 meta-schema should be valid")
918 });
919
920 pub static DRAFT7_META_VALIDATOR: Lazy<Validator> = Lazy::new(|| {
921 crate::options()
922 .without_schema_validation()
923 .build(&referencing::meta::DRAFT7)
924 .expect("Draft 7 meta-schema should be valid")
925 });
926
927 pub static DRAFT201909_META_VALIDATOR: Lazy<Validator> = Lazy::new(|| {
928 crate::options()
929 .without_schema_validation()
930 .build(&referencing::meta::DRAFT201909)
931 .expect("Draft 2019-09 meta-schema should be valid")
932 });
933
934 pub static DRAFT202012_META_VALIDATOR: Lazy<Validator> = Lazy::new(|| {
935 crate::options()
936 .without_schema_validation()
937 .build(&referencing::meta::DRAFT202012)
938 .expect("Draft 2020-12 meta-schema should be valid")
939 });
940 }
941
942 /// Validate a JSON Schema document against its meta-schema and get a `true` if the schema is valid
943 /// and `false` otherwise. Draft version is detected automatically.
944 ///
945 /// # Examples
946 ///
947 /// ```rust
948 /// use serde_json::json;
949 ///
950 /// let schema = json!({
951 /// "type": "string",
952 /// "maxLength": 5
953 /// });
954 /// assert!(jsonschema::meta::is_valid(&schema));
955 /// ```
956 ///
957 /// # Panics
958 ///
959 /// This function panics if the meta-schema can't be detected.
960 #[must_use]
961 pub fn is_valid(schema: &Value) -> bool {
962 meta_validator_for(schema).is_valid(schema)
963 }
964 /// Validate a JSON Schema document against its meta-schema and return the first error if any.
965 /// Draft version is detected automatically.
966 ///
967 /// # Examples
968 ///
969 /// ```rust
970 /// use serde_json::json;
971 ///
972 /// let schema = json!({
973 /// "type": "string",
974 /// "maxLength": 5
975 /// });
976 /// assert!(jsonschema::meta::validate(&schema).is_ok());
977 ///
978 /// // Invalid schema
979 /// let invalid_schema = json!({
980 /// "type": "invalid_type"
981 /// });
982 /// assert!(jsonschema::meta::validate(&invalid_schema).is_err());
983 /// ```
984 ///
985 /// # Panics
986 ///
987 /// This function panics if the meta-schema can't be detected.
988 pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
989 meta_validator_for(schema).validate(schema)
990 }
991
992 fn meta_validator_for(schema: &Value) -> &'static Validator {
993 try_meta_validator_for(schema).expect("Failed to detect meta schema")
994 }
995
996 /// Try to validate a JSON Schema document against its meta-schema.
997 ///
998 /// # Returns
999 ///
1000 /// - `Ok(true)` if the schema is valid
1001 /// - `Ok(false)` if the schema is invalid
1002 /// - `Err(ReferencingError)` if the meta-schema can't be detected
1003 ///
1004 /// # Examples
1005 ///
1006 /// ```rust
1007 /// use serde_json::json;
1008 ///
1009 /// let schema = json!({
1010 /// "type": "string",
1011 /// "maxLength": 5
1012 /// });
1013 /// assert!(jsonschema::meta::try_is_valid(&schema).expect("Unknown draft"));
1014 ///
1015 /// // Invalid $schema URI
1016 /// let undetectable_schema = json!({
1017 /// "$schema": "invalid-uri",
1018 /// "type": "string"
1019 /// });
1020 /// assert!(jsonschema::meta::try_is_valid(&undetectable_schema).is_err());
1021 /// ```
1022 pub fn try_is_valid(schema: &Value) -> Result<bool, ReferencingError> {
1023 Ok(try_meta_validator_for(schema)?.is_valid(schema))
1024 }
1025
1026 /// Try to validate a JSON Schema document against its meta-schema.
1027 ///
1028 /// # Returns
1029 ///
1030 /// - `Ok(Ok(()))` if the schema is valid
1031 /// - `Ok(Err(ValidationError))` if the schema is invalid
1032 /// - `Err(ReferencingError)` if the meta-schema can't be detected
1033 ///
1034 /// # Examples
1035 ///
1036 /// ```rust
1037 /// use serde_json::json;
1038 ///
1039 /// let schema = json!({
1040 /// "type": "string",
1041 /// "maxLength": 5
1042 /// });
1043 /// assert!(jsonschema::meta::try_validate(&schema).expect("Invalid schema").is_ok());
1044 ///
1045 /// // Invalid schema
1046 /// let invalid_schema = json!({
1047 /// "type": "invalid_type"
1048 /// });
1049 /// assert!(jsonschema::meta::try_validate(&invalid_schema).expect("Invalid schema").is_err());
1050 ///
1051 /// // Invalid $schema URI
1052 /// let undetectable_schema = json!({
1053 /// "$schema": "invalid-uri",
1054 /// "type": "string"
1055 /// });
1056 /// assert!(jsonschema::meta::try_validate(&undetectable_schema).is_err());
1057 /// ```
1058 pub fn try_validate(
1059 schema: &Value,
1060 ) -> Result<Result<(), ValidationError<'_>>, ReferencingError> {
1061 Ok(try_meta_validator_for(schema)?.validate(schema))
1062 }
1063
1064 fn try_meta_validator_for(schema: &Value) -> Result<&'static Validator, ReferencingError> {
1065 Ok(match Draft::default().detect(schema)? {
1066 Draft::Draft4 => &validators::DRAFT4_META_VALIDATOR,
1067 Draft::Draft6 => &validators::DRAFT6_META_VALIDATOR,
1068 Draft::Draft7 => &validators::DRAFT7_META_VALIDATOR,
1069 Draft::Draft201909 => &validators::DRAFT201909_META_VALIDATOR,
1070 Draft::Draft202012 => &validators::DRAFT202012_META_VALIDATOR,
1071 _ => unreachable!("Unknown draft"),
1072 })
1073 }
1074}
1075
1076/// Functionality specific to JSON Schema Draft 4.
1077///
1078/// [](https://bowtie.report/#/implementations/rust-jsonschema)
1079///
1080/// This module provides functions for creating validators and performing validation
1081/// according to the JSON Schema Draft 4 specification.
1082///
1083/// # Examples
1084///
1085/// ```rust
1086/// use serde_json::json;
1087///
1088/// let schema = json!({"type": "number", "multipleOf": 2});
1089/// let instance = json!(4);
1090///
1091/// assert!(jsonschema::draft4::is_valid(&schema, &instance));
1092/// ```
1093pub mod draft4 {
1094 use super::{Draft, ValidationError, ValidationOptions, Validator, Value};
1095
1096 /// Create a new JSON Schema validator using Draft 4 specifications.
1097 ///
1098 /// # Examples
1099 ///
1100 /// ```rust
1101 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1102 /// use serde_json::json;
1103 ///
1104 /// let schema = json!({"minimum": 5});
1105 /// let instance = json!(42);
1106 ///
1107 /// let validator = jsonschema::draft4::new(&schema)?;
1108 /// assert!(validator.is_valid(&instance));
1109 /// # Ok(())
1110 /// # }
1111 /// ```
1112 pub fn new(schema: &Value) -> Result<Validator, ValidationError<'static>> {
1113 options().build(schema)
1114 }
1115 /// Validate an instance against a schema using Draft 4 specifications without creating a validator.
1116 ///
1117 /// # Examples
1118 ///
1119 /// ```rust
1120 /// use serde_json::json;
1121 ///
1122 /// let schema = json!({"minimum": 5});
1123 /// let valid = json!(42);
1124 /// let invalid = json!(3);
1125 ///
1126 /// assert!(jsonschema::draft4::is_valid(&schema, &valid));
1127 /// assert!(!jsonschema::draft4::is_valid(&schema, &invalid));
1128 /// ```
1129 #[must_use]
1130 pub fn is_valid(schema: &Value, instance: &Value) -> bool {
1131 new(schema).expect("Invalid schema").is_valid(instance)
1132 }
1133 /// Validate an instance against a schema using Draft 4 specifications without creating a validator.
1134 ///
1135 /// # Examples
1136 ///
1137 /// ```rust
1138 /// use serde_json::json;
1139 ///
1140 /// let schema = json!({"minimum": 5});
1141 /// let valid = json!(42);
1142 /// let invalid = json!(3);
1143 ///
1144 /// assert!(jsonschema::draft4::validate(&schema, &valid).is_ok());
1145 /// assert!(jsonschema::draft4::validate(&schema, &invalid).is_err());
1146 /// ```
1147 pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
1148 new(schema).expect("Invalid schema").validate(instance)
1149 }
1150 /// Creates a [`ValidationOptions`] builder pre-configured for JSON Schema Draft 4.
1151 ///
1152 /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft4)`.
1153 ///
1154 /// # Examples
1155 ///
1156 /// ```
1157 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1158 /// use serde_json::json;
1159 ///
1160 /// let schema = json!({"type": "string", "format": "ends-with-42"});
1161 /// let validator = jsonschema::draft4::options()
1162 /// .with_format("ends-with-42", |s| s.ends_with("42"))
1163 /// .should_validate_formats(true)
1164 /// .build(&schema)?;
1165 ///
1166 /// assert!(validator.is_valid(&json!("Hello 42")));
1167 /// assert!(!validator.is_valid(&json!("No!")));
1168 /// # Ok(())
1169 /// # }
1170 /// ```
1171 ///
1172 /// See [`ValidationOptions`] for all available configuration options.
1173 #[must_use]
1174 pub fn options() -> ValidationOptions {
1175 crate::options().with_draft(Draft::Draft4)
1176 }
1177
1178 /// Functionality for validating JSON Schema Draft 4 documents.
1179 pub mod meta {
1180 use crate::ValidationError;
1181 use serde_json::Value;
1182
1183 pub use crate::meta::validators::DRAFT4_META_VALIDATOR as VALIDATOR;
1184
1185 /// Validate a JSON Schema document against Draft 4 meta-schema and get a `true` if the schema is valid
1186 /// and `false` otherwise.
1187 ///
1188 /// # Examples
1189 ///
1190 /// ```rust
1191 /// use serde_json::json;
1192 ///
1193 /// let schema = json!({
1194 /// "type": "string",
1195 /// "maxLength": 5
1196 /// });
1197 /// assert!(jsonschema::draft4::meta::is_valid(&schema));
1198 /// ```
1199 #[must_use]
1200 #[inline]
1201 pub fn is_valid(schema: &Value) -> bool {
1202 VALIDATOR.is_valid(schema)
1203 }
1204
1205 /// Validate a JSON Schema document against Draft 4 meta-schema and return the first error if any.
1206 ///
1207 /// # Examples
1208 ///
1209 /// ```rust
1210 /// use serde_json::json;
1211 ///
1212 /// let schema = json!({
1213 /// "type": "string",
1214 /// "maxLength": 5
1215 /// });
1216 /// assert!(jsonschema::draft4::meta::validate(&schema).is_ok());
1217 ///
1218 /// // Invalid schema
1219 /// let invalid_schema = json!({
1220 /// "type": "invalid_type"
1221 /// });
1222 /// assert!(jsonschema::draft4::meta::validate(&invalid_schema).is_err());
1223 /// ```
1224 #[inline]
1225 pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
1226 VALIDATOR.validate(schema)
1227 }
1228 }
1229}
1230
1231/// Functionality specific to JSON Schema Draft 6.
1232///
1233/// [](https://bowtie.report/#/implementations/rust-jsonschema)
1234///
1235/// This module provides functions for creating validators and performing validation
1236/// according to the JSON Schema Draft 6 specification.
1237///
1238/// # Examples
1239///
1240/// ```rust
1241/// use serde_json::json;
1242///
1243/// let schema = json!({"type": "string", "format": "uri"});
1244/// let instance = json!("https://www.example.com");
1245///
1246/// assert!(jsonschema::draft6::is_valid(&schema, &instance));
1247/// ```
1248pub mod draft6 {
1249 use super::{Draft, ValidationError, ValidationOptions, Validator, Value};
1250
1251 /// Create a new JSON Schema validator using Draft 6 specifications.
1252 ///
1253 /// # Examples
1254 ///
1255 /// ```rust
1256 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1257 /// use serde_json::json;
1258 ///
1259 /// let schema = json!({"minimum": 5});
1260 /// let instance = json!(42);
1261 ///
1262 /// let validator = jsonschema::draft6::new(&schema)?;
1263 /// assert!(validator.is_valid(&instance));
1264 /// # Ok(())
1265 /// # }
1266 /// ```
1267 pub fn new(schema: &Value) -> Result<Validator, ValidationError<'static>> {
1268 options().build(schema)
1269 }
1270 /// Validate an instance against a schema using Draft 6 specifications without creating a validator.
1271 ///
1272 /// # Examples
1273 ///
1274 /// ```rust
1275 /// use serde_json::json;
1276 ///
1277 /// let schema = json!({"minimum": 5});
1278 /// let valid = json!(42);
1279 /// let invalid = json!(3);
1280 ///
1281 /// assert!(jsonschema::draft6::is_valid(&schema, &valid));
1282 /// assert!(!jsonschema::draft6::is_valid(&schema, &invalid));
1283 /// ```
1284 #[must_use]
1285 pub fn is_valid(schema: &Value, instance: &Value) -> bool {
1286 new(schema).expect("Invalid schema").is_valid(instance)
1287 }
1288 /// Validate an instance against a schema using Draft 6 specifications without creating a validator.
1289 ///
1290 /// # Examples
1291 ///
1292 /// ```rust
1293 /// use serde_json::json;
1294 ///
1295 /// let schema = json!({"minimum": 5});
1296 /// let valid = json!(42);
1297 /// let invalid = json!(3);
1298 ///
1299 /// assert!(jsonschema::draft6::validate(&schema, &valid).is_ok());
1300 /// assert!(jsonschema::draft6::validate(&schema, &invalid).is_err());
1301 /// ```
1302 pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
1303 new(schema).expect("Invalid schema").validate(instance)
1304 }
1305 /// Creates a [`ValidationOptions`] builder pre-configured for JSON Schema Draft 6.
1306 ///
1307 /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft6)`.
1308 ///
1309 /// # Examples
1310 ///
1311 /// ```
1312 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1313 /// use serde_json::json;
1314 ///
1315 /// let schema = json!({"type": "string", "format": "ends-with-42"});
1316 /// let validator = jsonschema::draft6::options()
1317 /// .with_format("ends-with-42", |s| s.ends_with("42"))
1318 /// .should_validate_formats(true)
1319 /// .build(&schema)?;
1320 ///
1321 /// assert!(validator.is_valid(&json!("Hello 42")));
1322 /// assert!(!validator.is_valid(&json!("No!")));
1323 /// # Ok(())
1324 /// # }
1325 /// ```
1326 ///
1327 /// See [`ValidationOptions`] for all available configuration options.
1328 #[must_use]
1329 pub fn options() -> ValidationOptions {
1330 crate::options().with_draft(Draft::Draft6)
1331 }
1332
1333 /// Functionality for validating JSON Schema Draft 6 documents.
1334 pub mod meta {
1335 use crate::ValidationError;
1336 use serde_json::Value;
1337
1338 pub use crate::meta::validators::DRAFT6_META_VALIDATOR as VALIDATOR;
1339
1340 /// Validate a JSON Schema document against Draft 6 meta-schema and get a `true` if the schema is valid
1341 /// and `false` otherwise.
1342 ///
1343 /// # Examples
1344 ///
1345 /// ```rust
1346 /// use serde_json::json;
1347 ///
1348 /// let schema = json!({
1349 /// "type": "string",
1350 /// "maxLength": 5
1351 /// });
1352 /// assert!(jsonschema::draft6::meta::is_valid(&schema));
1353 /// ```
1354 #[must_use]
1355 #[inline]
1356 pub fn is_valid(schema: &Value) -> bool {
1357 VALIDATOR.is_valid(schema)
1358 }
1359
1360 /// Validate a JSON Schema document against Draft 6 meta-schema and return the first error if any.
1361 ///
1362 /// # Examples
1363 ///
1364 /// ```rust
1365 /// use serde_json::json;
1366 ///
1367 /// let schema = json!({
1368 /// "type": "string",
1369 /// "maxLength": 5
1370 /// });
1371 /// assert!(jsonschema::draft6::meta::validate(&schema).is_ok());
1372 ///
1373 /// // Invalid schema
1374 /// let invalid_schema = json!({
1375 /// "type": "invalid_type"
1376 /// });
1377 /// assert!(jsonschema::draft6::meta::validate(&invalid_schema).is_err());
1378 /// ```
1379 #[inline]
1380 pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
1381 VALIDATOR.validate(schema)
1382 }
1383 }
1384}
1385
1386/// Functionality specific to JSON Schema Draft 7.
1387///
1388/// [](https://bowtie.report/#/implementations/rust-jsonschema)
1389///
1390/// This module provides functions for creating validators and performing validation
1391/// according to the JSON Schema Draft 7 specification.
1392///
1393/// # Examples
1394///
1395/// ```rust
1396/// use serde_json::json;
1397///
1398/// let schema = json!({"type": "string", "pattern": "^[a-zA-Z0-9]+$"});
1399/// let instance = json!("abc123");
1400///
1401/// assert!(jsonschema::draft7::is_valid(&schema, &instance));
1402/// ```
1403pub mod draft7 {
1404 use super::{Draft, ValidationError, ValidationOptions, Validator, Value};
1405
1406 /// Create a new JSON Schema validator using Draft 7 specifications.
1407 ///
1408 /// # Examples
1409 ///
1410 /// ```rust
1411 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1412 /// use serde_json::json;
1413 ///
1414 /// let schema = json!({"minimum": 5});
1415 /// let instance = json!(42);
1416 ///
1417 /// let validator = jsonschema::draft7::new(&schema)?;
1418 /// assert!(validator.is_valid(&instance));
1419 /// # Ok(())
1420 /// # }
1421 /// ```
1422 pub fn new(schema: &Value) -> Result<Validator, ValidationError<'static>> {
1423 options().build(schema)
1424 }
1425 /// Validate an instance against a schema using Draft 7 specifications without creating a validator.
1426 ///
1427 /// # Examples
1428 ///
1429 /// ```rust
1430 /// use serde_json::json;
1431 ///
1432 /// let schema = json!({"minimum": 5});
1433 /// let valid = json!(42);
1434 /// let invalid = json!(3);
1435 ///
1436 /// assert!(jsonschema::draft7::is_valid(&schema, &valid));
1437 /// assert!(!jsonschema::draft7::is_valid(&schema, &invalid));
1438 /// ```
1439 #[must_use]
1440 pub fn is_valid(schema: &Value, instance: &Value) -> bool {
1441 new(schema).expect("Invalid schema").is_valid(instance)
1442 }
1443 /// Validate an instance against a schema using Draft 7 specifications without creating a validator.
1444 ///
1445 /// # Examples
1446 ///
1447 /// ```rust
1448 /// use serde_json::json;
1449 ///
1450 /// let schema = json!({"minimum": 5});
1451 /// let valid = json!(42);
1452 /// let invalid = json!(3);
1453 ///
1454 /// assert!(jsonschema::draft7::validate(&schema, &valid).is_ok());
1455 /// assert!(jsonschema::draft7::validate(&schema, &invalid).is_err());
1456 /// ```
1457 pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
1458 new(schema).expect("Invalid schema").validate(instance)
1459 }
1460 /// Creates a [`ValidationOptions`] builder pre-configured for JSON Schema Draft 7.
1461 ///
1462 /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft7)`.
1463 ///
1464 /// # Examples
1465 ///
1466 /// ```
1467 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1468 /// use serde_json::json;
1469 ///
1470 /// let schema = json!({"type": "string", "format": "ends-with-42"});
1471 /// let validator = jsonschema::draft7::options()
1472 /// .with_format("ends-with-42", |s| s.ends_with("42"))
1473 /// .should_validate_formats(true)
1474 /// .build(&schema)?;
1475 ///
1476 /// assert!(validator.is_valid(&json!("Hello 42")));
1477 /// assert!(!validator.is_valid(&json!("No!")));
1478 /// # Ok(())
1479 /// # }
1480 /// ```
1481 ///
1482 /// See [`ValidationOptions`] for all available configuration options.
1483 #[must_use]
1484 pub fn options() -> ValidationOptions {
1485 crate::options().with_draft(Draft::Draft7)
1486 }
1487
1488 /// Functionality for validating JSON Schema Draft 7 documents.
1489 pub mod meta {
1490 use crate::ValidationError;
1491 use serde_json::Value;
1492
1493 pub use crate::meta::validators::DRAFT7_META_VALIDATOR as VALIDATOR;
1494
1495 /// Validate a JSON Schema document against Draft 7 meta-schema and get a `true` if the schema is valid
1496 /// and `false` otherwise.
1497 ///
1498 /// # Examples
1499 ///
1500 /// ```rust
1501 /// use serde_json::json;
1502 ///
1503 /// let schema = json!({
1504 /// "type": "string",
1505 /// "maxLength": 5
1506 /// });
1507 /// assert!(jsonschema::draft7::meta::is_valid(&schema));
1508 /// ```
1509 #[must_use]
1510 #[inline]
1511 pub fn is_valid(schema: &Value) -> bool {
1512 VALIDATOR.is_valid(schema)
1513 }
1514
1515 /// Validate a JSON Schema document against Draft 7 meta-schema and return the first error if any.
1516 ///
1517 /// # Examples
1518 ///
1519 /// ```rust
1520 /// use serde_json::json;
1521 ///
1522 /// let schema = json!({
1523 /// "type": "string",
1524 /// "maxLength": 5
1525 /// });
1526 /// assert!(jsonschema::draft7::meta::validate(&schema).is_ok());
1527 ///
1528 /// // Invalid schema
1529 /// let invalid_schema = json!({
1530 /// "type": "invalid_type"
1531 /// });
1532 /// assert!(jsonschema::draft7::meta::validate(&invalid_schema).is_err());
1533 /// ```
1534 #[inline]
1535 pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
1536 VALIDATOR.validate(schema)
1537 }
1538 }
1539}
1540
1541/// Functionality specific to JSON Schema Draft 2019-09.
1542///
1543/// [](https://bowtie.report/#/implementations/rust-jsonschema)
1544///
1545/// This module provides functions for creating validators and performing validation
1546/// according to the JSON Schema Draft 2019-09 specification.
1547///
1548/// # Examples
1549///
1550/// ```rust
1551/// use serde_json::json;
1552///
1553/// let schema = json!({"type": "array", "minItems": 2, "uniqueItems": true});
1554/// let instance = json!([1, 2]);
1555///
1556/// assert!(jsonschema::draft201909::is_valid(&schema, &instance));
1557/// ```
1558pub mod draft201909 {
1559 use super::{Draft, ValidationError, ValidationOptions, Validator, Value};
1560
1561 /// Create a new JSON Schema validator using Draft 2019-09 specifications.
1562 ///
1563 /// # Examples
1564 ///
1565 /// ```rust
1566 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1567 /// use serde_json::json;
1568 ///
1569 /// let schema = json!({"minimum": 5});
1570 /// let instance = json!(42);
1571 ///
1572 /// let validator = jsonschema::draft201909::new(&schema)?;
1573 /// assert!(validator.is_valid(&instance));
1574 /// # Ok(())
1575 /// # }
1576 /// ```
1577 pub fn new(schema: &Value) -> Result<Validator, ValidationError<'static>> {
1578 options().build(schema)
1579 }
1580 /// Validate an instance against a schema using Draft 2019-09 specifications without creating a validator.
1581 ///
1582 /// # Examples
1583 ///
1584 /// ```rust
1585 /// use serde_json::json;
1586 ///
1587 /// let schema = json!({"minimum": 5});
1588 /// let valid = json!(42);
1589 /// let invalid = json!(3);
1590 ///
1591 /// assert!(jsonschema::draft201909::is_valid(&schema, &valid));
1592 /// assert!(!jsonschema::draft201909::is_valid(&schema, &invalid));
1593 /// ```
1594 #[must_use]
1595 pub fn is_valid(schema: &Value, instance: &Value) -> bool {
1596 new(schema).expect("Invalid schema").is_valid(instance)
1597 }
1598 /// Validate an instance against a schema using Draft 2019-09 specifications without creating a validator.
1599 ///
1600 /// # Examples
1601 ///
1602 /// ```rust
1603 /// use serde_json::json;
1604 ///
1605 /// let schema = json!({"minimum": 5});
1606 /// let valid = json!(42);
1607 /// let invalid = json!(3);
1608 ///
1609 /// assert!(jsonschema::draft201909::validate(&schema, &valid).is_ok());
1610 /// assert!(jsonschema::draft201909::validate(&schema, &invalid).is_err());
1611 /// ```
1612 pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
1613 new(schema).expect("Invalid schema").validate(instance)
1614 }
1615 /// Creates a [`ValidationOptions`] builder pre-configured for JSON Schema Draft 2019-09.
1616 ///
1617 /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft201909)`.
1618 ///
1619 /// # Examples
1620 ///
1621 /// ```
1622 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1623 /// use serde_json::json;
1624 ///
1625 /// let schema = json!({"type": "string", "format": "ends-with-42"});
1626 /// let validator = jsonschema::draft201909::options()
1627 /// .with_format("ends-with-42", |s| s.ends_with("42"))
1628 /// .should_validate_formats(true)
1629 /// .build(&schema)?;
1630 ///
1631 /// assert!(validator.is_valid(&json!("Hello 42")));
1632 /// assert!(!validator.is_valid(&json!("No!")));
1633 /// # Ok(())
1634 /// # }
1635 /// ```
1636 ///
1637 /// See [`ValidationOptions`] for all available configuration options.
1638 #[must_use]
1639 pub fn options() -> ValidationOptions {
1640 crate::options().with_draft(Draft::Draft201909)
1641 }
1642
1643 /// Functionality for validating JSON Schema Draft 2019-09 documents.
1644 pub mod meta {
1645 use crate::ValidationError;
1646 use serde_json::Value;
1647
1648 pub use crate::meta::validators::DRAFT201909_META_VALIDATOR as VALIDATOR;
1649 /// Validate a JSON Schema document against Draft 2019-09 meta-schema and get a `true` if the schema is valid
1650 /// and `false` otherwise.
1651 ///
1652 /// # Examples
1653 ///
1654 /// ```rust
1655 /// use serde_json::json;
1656 ///
1657 /// let schema = json!({
1658 /// "type": "string",
1659 /// "maxLength": 5
1660 /// });
1661 /// assert!(jsonschema::draft201909::meta::is_valid(&schema));
1662 /// ```
1663 #[must_use]
1664 #[inline]
1665 pub fn is_valid(schema: &Value) -> bool {
1666 VALIDATOR.is_valid(schema)
1667 }
1668
1669 /// Validate a JSON Schema document against Draft 2019-09 meta-schema and return the first error if any.
1670 ///
1671 /// # Examples
1672 ///
1673 /// ```rust
1674 /// use serde_json::json;
1675 ///
1676 /// let schema = json!({
1677 /// "type": "string",
1678 /// "maxLength": 5
1679 /// });
1680 /// assert!(jsonschema::draft201909::meta::validate(&schema).is_ok());
1681 ///
1682 /// // Invalid schema
1683 /// let invalid_schema = json!({
1684 /// "type": "invalid_type"
1685 /// });
1686 /// assert!(jsonschema::draft201909::meta::validate(&invalid_schema).is_err());
1687 /// ```
1688 #[inline]
1689 pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
1690 VALIDATOR.validate(schema)
1691 }
1692 }
1693}
1694
1695/// Functionality specific to JSON Schema Draft 2020-12.
1696///
1697/// [](https://bowtie.report/#/implementations/rust-jsonschema)
1698///
1699/// This module provides functions for creating validators and performing validation
1700/// according to the JSON Schema Draft 2020-12 specification.
1701///
1702/// # Examples
1703///
1704/// ```rust
1705/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1706/// use serde_json::json;
1707///
1708/// let schema = json!({"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]});
1709/// let instance = json!({"name": "John Doe"});
1710///
1711/// assert!(jsonschema::draft202012::is_valid(&schema, &instance));
1712/// # Ok(())
1713/// # }
1714/// ```
1715pub mod draft202012 {
1716 use super::{Draft, ValidationError, ValidationOptions, Validator, Value};
1717
1718 /// Create a new JSON Schema validator using Draft 2020-12 specifications.
1719 ///
1720 /// # Examples
1721 ///
1722 /// ```rust
1723 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1724 /// use serde_json::json;
1725 ///
1726 /// let schema = json!({"minimum": 5});
1727 /// let instance = json!(42);
1728 ///
1729 /// let validator = jsonschema::draft202012::new(&schema)?;
1730 /// assert!(validator.is_valid(&instance));
1731 /// # Ok(())
1732 /// # }
1733 /// ```
1734 pub fn new(schema: &Value) -> Result<Validator, ValidationError<'static>> {
1735 options().build(schema)
1736 }
1737 /// Validate an instance against a schema using Draft 2020-12 specifications without creating a validator.
1738 ///
1739 /// # Examples
1740 ///
1741 /// ```rust
1742 /// use serde_json::json;
1743 ///
1744 /// let schema = json!({"minimum": 5});
1745 /// let valid = json!(42);
1746 /// let invalid = json!(3);
1747 ///
1748 /// assert!(jsonschema::draft202012::is_valid(&schema, &valid));
1749 /// assert!(!jsonschema::draft202012::is_valid(&schema, &invalid));
1750 /// ```
1751 #[must_use]
1752 pub fn is_valid(schema: &Value, instance: &Value) -> bool {
1753 new(schema).expect("Invalid schema").is_valid(instance)
1754 }
1755 /// Validate an instance against a schema using Draft 2020-12 specifications without creating a validator.
1756 ///
1757 /// # Examples
1758 ///
1759 /// ```rust
1760 /// use serde_json::json;
1761 ///
1762 /// let schema = json!({"minimum": 5});
1763 /// let valid = json!(42);
1764 /// let invalid = json!(3);
1765 ///
1766 /// assert!(jsonschema::draft202012::validate(&schema, &valid).is_ok());
1767 /// assert!(jsonschema::draft202012::validate(&schema, &invalid).is_err());
1768 /// ```
1769 pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
1770 new(schema).expect("Invalid schema").validate(instance)
1771 }
1772 /// Creates a [`ValidationOptions`] builder pre-configured for JSON Schema Draft 2020-12.
1773 ///
1774 /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft202012)`.
1775 ///
1776 /// # Examples
1777 ///
1778 /// ```
1779 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1780 /// use serde_json::json;
1781 ///
1782 /// let schema = json!({"type": "string", "format": "ends-with-42"});
1783 /// let validator = jsonschema::draft202012::options()
1784 /// .with_format("ends-with-42", |s| s.ends_with("42"))
1785 /// .should_validate_formats(true)
1786 /// .build(&schema)?;
1787 ///
1788 /// assert!(validator.is_valid(&json!("Hello 42")));
1789 /// assert!(!validator.is_valid(&json!("No!")));
1790 /// # Ok(())
1791 /// # }
1792 /// ```
1793 ///
1794 /// See [`ValidationOptions`] for all available configuration options.
1795 #[must_use]
1796 pub fn options() -> ValidationOptions {
1797 crate::options().with_draft(Draft::Draft202012)
1798 }
1799
1800 /// Functionality for validating JSON Schema Draft 2020-12 documents.
1801 pub mod meta {
1802 use crate::ValidationError;
1803 use serde_json::Value;
1804
1805 pub use crate::meta::validators::DRAFT202012_META_VALIDATOR as VALIDATOR;
1806
1807 /// Validate a JSON Schema document against Draft 2020-12 meta-schema and get a `true` if the schema is valid
1808 /// and `false` otherwise.
1809 ///
1810 /// # Examples
1811 ///
1812 /// ```rust
1813 /// use serde_json::json;
1814 ///
1815 /// let schema = json!({
1816 /// "type": "string",
1817 /// "maxLength": 5
1818 /// });
1819 /// assert!(jsonschema::draft202012::meta::is_valid(&schema));
1820 /// ```
1821 #[must_use]
1822 #[inline]
1823 pub fn is_valid(schema: &Value) -> bool {
1824 VALIDATOR.is_valid(schema)
1825 }
1826
1827 /// Validate a JSON Schema document against Draft 2020-12 meta-schema and return the first error if any.
1828 ///
1829 /// # Examples
1830 ///
1831 /// ```rust
1832 /// use serde_json::json;
1833 ///
1834 /// let schema = json!({
1835 /// "type": "string",
1836 /// "maxLength": 5
1837 /// });
1838 /// assert!(jsonschema::draft202012::meta::validate(&schema).is_ok());
1839 ///
1840 /// // Invalid schema
1841 /// let invalid_schema = json!({
1842 /// "type": "invalid_type"
1843 /// });
1844 /// assert!(jsonschema::draft202012::meta::validate(&invalid_schema).is_err());
1845 /// ```
1846 #[inline]
1847 pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
1848 VALIDATOR.validate(schema)
1849 }
1850 }
1851}
1852
1853#[cfg(test)]
1854pub(crate) mod tests_util {
1855 use super::Validator;
1856 use crate::ValidationError;
1857 use serde_json::Value;
1858
1859 #[track_caller]
1860 pub(crate) fn is_not_valid_with(validator: &Validator, instance: &Value) {
1861 assert!(
1862 !validator.is_valid(instance),
1863 "{instance} should not be valid (via is_valid)",
1864 );
1865 assert!(
1866 validator.validate(instance).is_err(),
1867 "{instance} should not be valid (via validate)",
1868 );
1869 assert!(
1870 validator.iter_errors(instance).next().is_some(),
1871 "{instance} should not be valid (via validate)",
1872 );
1873 assert!(
1874 !validator.apply(instance).basic().is_valid(),
1875 "{instance} should not be valid (via apply)",
1876 );
1877 }
1878
1879 #[track_caller]
1880 pub(crate) fn is_not_valid(schema: &Value, instance: &Value) {
1881 let validator = crate::options()
1882 .should_validate_formats(true)
1883 .build(schema)
1884 .expect("Invalid schema");
1885 is_not_valid_with(&validator, instance);
1886 }
1887
1888 #[track_caller]
1889 pub(crate) fn is_not_valid_with_draft(draft: crate::Draft, schema: &Value, instance: &Value) {
1890 let validator = crate::options()
1891 .should_validate_formats(true)
1892 .with_draft(draft)
1893 .build(schema)
1894 .expect("Invalid schema");
1895 is_not_valid_with(&validator, instance);
1896 }
1897
1898 pub(crate) fn expect_errors(schema: &Value, instance: &Value, errors: &[&str]) {
1899 assert_eq!(
1900 crate::validator_for(schema)
1901 .expect("Should be a valid schema")
1902 .iter_errors(instance)
1903 .map(|e| e.to_string())
1904 .collect::<Vec<String>>(),
1905 errors
1906 );
1907 }
1908
1909 #[track_caller]
1910 pub(crate) fn is_valid_with(validator: &Validator, instance: &Value) {
1911 if let Some(first) = validator.iter_errors(instance).next() {
1912 panic!(
1913 "{} should be valid (via validate). Error: {} at {}",
1914 instance, first, first.instance_path
1915 );
1916 }
1917 assert!(
1918 validator.is_valid(instance),
1919 "{instance} should be valid (via is_valid)",
1920 );
1921 assert!(
1922 validator.validate(instance).is_ok(),
1923 "{instance} should be valid (via is_valid)",
1924 );
1925 assert!(
1926 validator.apply(instance).basic().is_valid(),
1927 "{instance} should be valid (via apply)",
1928 );
1929 }
1930
1931 #[track_caller]
1932 pub(crate) fn is_valid(schema: &Value, instance: &Value) {
1933 let validator = crate::options()
1934 .should_validate_formats(true)
1935 .build(schema)
1936 .expect("Invalid schema");
1937 is_valid_with(&validator, instance);
1938 }
1939
1940 #[track_caller]
1941 pub(crate) fn is_valid_with_draft(draft: crate::Draft, schema: &Value, instance: &Value) {
1942 let validator = crate::options().with_draft(draft).build(schema).unwrap();
1943 is_valid_with(&validator, instance);
1944 }
1945
1946 #[track_caller]
1947 pub(crate) fn validate(schema: &Value, instance: &Value) -> ValidationError<'static> {
1948 let validator = crate::options()
1949 .should_validate_formats(true)
1950 .build(schema)
1951 .expect("Invalid schema");
1952 let err = validator
1953 .validate(instance)
1954 .expect_err("Should be an error")
1955 .to_owned();
1956 err
1957 }
1958
1959 #[track_caller]
1960 pub(crate) fn assert_schema_location(schema: &Value, instance: &Value, expected: &str) {
1961 let error = validate(schema, instance);
1962 assert_eq!(error.schema_path.as_str(), expected);
1963 }
1964
1965 #[track_caller]
1966 pub(crate) fn assert_locations(schema: &Value, instance: &Value, expected: &[&str]) {
1967 let validator = crate::validator_for(schema).unwrap();
1968 let errors = validator.iter_errors(instance);
1969 for (error, location) in errors.zip(expected) {
1970 assert_eq!(error.schema_path.as_str(), *location);
1971 }
1972 }
1973}
1974
1975#[cfg(test)]
1976mod tests {
1977 use crate::{validator_for, ValidationError};
1978
1979 use super::Draft;
1980 use serde_json::json;
1981 use test_case::test_case;
1982
1983 #[test_case(crate::is_valid ; "autodetect")]
1984 #[test_case(crate::draft4::is_valid ; "draft4")]
1985 #[test_case(crate::draft6::is_valid ; "draft6")]
1986 #[test_case(crate::draft7::is_valid ; "draft7")]
1987 #[test_case(crate::draft201909::is_valid ; "draft201909")]
1988 #[test_case(crate::draft202012::is_valid ; "draft202012")]
1989 fn test_is_valid(is_valid_fn: fn(&serde_json::Value, &serde_json::Value) -> bool) {
1990 let schema = json!({
1991 "type": "object",
1992 "properties": {
1993 "name": {"type": "string"},
1994 "age": {"type": "integer", "minimum": 0}
1995 },
1996 "required": ["name"]
1997 });
1998
1999 let valid_instance = json!({
2000 "name": "John Doe",
2001 "age": 30
2002 });
2003
2004 let invalid_instance = json!({
2005 "age": -5
2006 });
2007
2008 assert!(is_valid_fn(&schema, &valid_instance));
2009 assert!(!is_valid_fn(&schema, &invalid_instance));
2010 }
2011
2012 #[test_case(crate::validate ; "autodetect")]
2013 #[test_case(crate::draft4::validate ; "draft4")]
2014 #[test_case(crate::draft6::validate ; "draft6")]
2015 #[test_case(crate::draft7::validate ; "draft7")]
2016 #[test_case(crate::draft201909::validate ; "draft201909")]
2017 #[test_case(crate::draft202012::validate ; "draft202012")]
2018 fn test_validate(
2019 validate_fn: for<'i> fn(
2020 &serde_json::Value,
2021 &'i serde_json::Value,
2022 ) -> Result<(), ValidationError<'i>>,
2023 ) {
2024 let schema = json!({
2025 "type": "object",
2026 "properties": {
2027 "name": {"type": "string"},
2028 "age": {"type": "integer", "minimum": 0}
2029 },
2030 "required": ["name"]
2031 });
2032
2033 let valid_instance = json!({
2034 "name": "John Doe",
2035 "age": 30
2036 });
2037
2038 let invalid_instance = json!({
2039 "age": -5
2040 });
2041
2042 assert!(validate_fn(&schema, &valid_instance).is_ok());
2043 assert!(validate_fn(&schema, &invalid_instance).is_err());
2044 }
2045
2046 #[test_case(crate::meta::validate, crate::meta::is_valid ; "autodetect")]
2047 #[test_case(crate::draft4::meta::validate, crate::draft4::meta::is_valid ; "draft4")]
2048 #[test_case(crate::draft6::meta::validate, crate::draft6::meta::is_valid ; "draft6")]
2049 #[test_case(crate::draft7::meta::validate, crate::draft7::meta::is_valid ; "draft7")]
2050 #[test_case(crate::draft201909::meta::validate, crate::draft201909::meta::is_valid ; "draft201909")]
2051 #[test_case(crate::draft202012::meta::validate, crate::draft202012::meta::is_valid ; "draft202012")]
2052 fn test_meta_validation(
2053 validate_fn: fn(&serde_json::Value) -> Result<(), ValidationError>,
2054 is_valid_fn: fn(&serde_json::Value) -> bool,
2055 ) {
2056 let valid = json!({
2057 "type": "object",
2058 "properties": {
2059 "name": {"type": "string"},
2060 "age": {"type": "integer", "minimum": 0}
2061 },
2062 "required": ["name"]
2063 });
2064
2065 let invalid = json!({
2066 "type": "invalid_type",
2067 "minimum": "not_a_number",
2068 "required": true // should be an array
2069 });
2070
2071 assert!(validate_fn(&valid).is_ok());
2072 assert!(validate_fn(&invalid).is_err());
2073 assert!(is_valid_fn(&valid));
2074 assert!(!is_valid_fn(&invalid));
2075 }
2076
2077 #[test]
2078 fn test_exclusive_minimum_across_drafts() {
2079 // In Draft 4, exclusiveMinimum is a boolean modifier for minimum
2080 let draft4_schema = json!({
2081 "$schema": "http://json-schema.org/draft-04/schema#",
2082 "minimum": 5,
2083 "exclusiveMinimum": true
2084 });
2085 assert!(crate::meta::is_valid(&draft4_schema));
2086 assert!(crate::meta::validate(&draft4_schema).is_ok());
2087
2088 // This is invalid in Draft 4 (exclusiveMinimum must be boolean)
2089 let invalid_draft4 = json!({
2090 "$schema": "http://json-schema.org/draft-04/schema#",
2091 "exclusiveMinimum": 5
2092 });
2093 assert!(!crate::meta::is_valid(&invalid_draft4));
2094 assert!(crate::meta::validate(&invalid_draft4).is_err());
2095
2096 // In Draft 6 and later, exclusiveMinimum is a numeric value
2097 let drafts = [
2098 "http://json-schema.org/draft-06/schema#",
2099 "http://json-schema.org/draft-07/schema#",
2100 "https://json-schema.org/draft/2019-09/schema",
2101 "https://json-schema.org/draft/2020-12/schema",
2102 ];
2103
2104 for uri in drafts {
2105 // Valid in Draft 6+ (numeric exclusiveMinimum)
2106 let valid_schema = json!({
2107 "$schema": uri,
2108 "exclusiveMinimum": 5
2109 });
2110 assert!(
2111 crate::meta::is_valid(&valid_schema),
2112 "Schema should be valid for {uri}"
2113 );
2114 assert!(
2115 crate::meta::validate(&valid_schema).is_ok(),
2116 "Schema validation should succeed for {uri}",
2117 );
2118
2119 // Invalid in Draft 6+ (can't use boolean with minimum)
2120 let invalid_schema = json!({
2121 "$schema": uri,
2122 "minimum": 5,
2123 "exclusiveMinimum": true
2124 });
2125 assert!(
2126 !crate::meta::is_valid(&invalid_schema),
2127 "Schema should be invalid for {uri}",
2128 );
2129 assert!(
2130 crate::meta::validate(&invalid_schema).is_err(),
2131 "Schema validation should fail for {uri}",
2132 );
2133 }
2134 }
2135
2136 #[test_case(
2137 "http://json-schema.org/draft-04/schema#",
2138 true,
2139 5,
2140 true ; "draft4 valid"
2141 )]
2142 #[test_case(
2143 "http://json-schema.org/draft-04/schema#",
2144 5,
2145 true,
2146 false ; "draft4 invalid"
2147 )]
2148 #[test_case(
2149 "http://json-schema.org/draft-06/schema#",
2150 5,
2151 true,
2152 false ; "draft6 invalid"
2153 )]
2154 #[test_case(
2155 "http://json-schema.org/draft-07/schema#",
2156 5,
2157 true,
2158 false ; "draft7 invalid"
2159 )]
2160 #[test_case(
2161 "https://json-schema.org/draft/2019-09/schema",
2162 5,
2163 true,
2164 false ; "draft2019-09 invalid"
2165 )]
2166 #[test_case(
2167 "https://json-schema.org/draft/2020-12/schema",
2168 5,
2169 true,
2170 false ; "draft2020-12 invalid"
2171 )]
2172 fn test_exclusive_minimum_detection(
2173 schema_uri: &str,
2174 exclusive_minimum: impl Into<serde_json::Value>,
2175 minimum: impl Into<serde_json::Value>,
2176 expected: bool,
2177 ) {
2178 let schema = json!({
2179 "$schema": schema_uri,
2180 "minimum": minimum.into(),
2181 "exclusiveMinimum": exclusive_minimum.into()
2182 });
2183
2184 let is_valid_result = crate::meta::try_is_valid(&schema);
2185 assert!(is_valid_result.is_ok());
2186 assert_eq!(is_valid_result.expect("Unknown draft"), expected);
2187
2188 let validate_result = crate::meta::try_validate(&schema);
2189 assert!(validate_result.is_ok());
2190 assert_eq!(validate_result.expect("Unknown draft").is_ok(), expected);
2191 }
2192
2193 #[test]
2194 fn test_invalid_schema_uri() {
2195 let schema = json!({
2196 "$schema": "invalid-uri",
2197 "type": "string"
2198 });
2199
2200 assert!(crate::meta::try_is_valid(&schema).is_err());
2201 assert!(crate::meta::try_validate(&schema).is_err());
2202 }
2203
2204 #[test]
2205 fn test_invalid_schema_keyword() {
2206 let schema = json!({
2207 // Note `htt`, not `http`
2208 "$schema": "htt://json-schema.org/draft-07/schema",
2209 });
2210 let error = crate::validator_for(&schema).expect_err("Should fail");
2211 assert_eq!(
2212 error.to_string(),
2213 "Unknown specification: htt://json-schema.org/draft-07/schema"
2214 );
2215 }
2216
2217 #[test_case(Draft::Draft4)]
2218 #[test_case(Draft::Draft6)]
2219 #[test_case(Draft::Draft7)]
2220 fn meta_schemas(draft: Draft) {
2221 // See GH-258
2222 for schema in [json!({"enum": [0, 0.0]}), json!({"enum": []})] {
2223 assert!(crate::options().with_draft(draft).build(&schema).is_ok());
2224 }
2225 }
2226
2227 #[test]
2228 fn incomplete_escape_in_pattern() {
2229 // See GH-253
2230 let schema = json!({"pattern": "\\u"});
2231 assert!(crate::validator_for(&schema).is_err());
2232 }
2233
2234 #[test]
2235 fn validation_error_propagation() {
2236 fn foo() -> Result<(), Box<dyn std::error::Error>> {
2237 let schema = json!({});
2238 let validator = validator_for(&schema)?;
2239 let _ = validator.is_valid(&json!({}));
2240 Ok(())
2241 }
2242 let _ = foo();
2243 }
2244}
2245
2246#[cfg(all(test, feature = "resolve-async"))]
2247mod async_tests {
2248 use referencing::Resource;
2249 use std::collections::HashMap;
2250
2251 use serde_json::json;
2252
2253 use crate::{AsyncRetrieve, Draft, Uri};
2254
2255 /// Mock async retriever for testing
2256 struct TestRetriever {
2257 schemas: HashMap<String, serde_json::Value>,
2258 }
2259
2260 impl TestRetriever {
2261 fn new() -> Self {
2262 let mut schemas = HashMap::new();
2263 schemas.insert(
2264 "https://example.com/user.json".to_string(),
2265 json!({
2266 "type": "object",
2267 "properties": {
2268 "name": {"type": "string"},
2269 "age": {"type": "integer", "minimum": 0}
2270 },
2271 "required": ["name"]
2272 }),
2273 );
2274 Self { schemas }
2275 }
2276 }
2277
2278 #[async_trait::async_trait]
2279 impl AsyncRetrieve for TestRetriever {
2280 async fn retrieve(
2281 &self,
2282 uri: &Uri<String>,
2283 ) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
2284 self.schemas
2285 .get(uri.as_str())
2286 .cloned()
2287 .ok_or_else(|| "Schema not found".into())
2288 }
2289 }
2290
2291 #[tokio::test]
2292 async fn test_async_validator_for() {
2293 let schema = json!({
2294 "$ref": "https://example.com/user.json"
2295 });
2296
2297 let validator = crate::async_options()
2298 .with_retriever(TestRetriever::new())
2299 .build(&schema)
2300 .await
2301 .unwrap();
2302
2303 // Valid instance
2304 assert!(validator.is_valid(&json!({
2305 "name": "John Doe",
2306 "age": 30
2307 })));
2308
2309 // Invalid instances
2310 assert!(!validator.is_valid(&json!({
2311 "age": -5
2312 })));
2313 assert!(!validator.is_valid(&json!({
2314 "name": 123,
2315 "age": 30
2316 })));
2317 }
2318
2319 #[tokio::test]
2320 async fn test_async_options_with_draft() {
2321 let schema = json!({
2322 "$ref": "https://example.com/user.json"
2323 });
2324
2325 let validator = crate::async_options()
2326 .with_draft(Draft::Draft202012)
2327 .with_retriever(TestRetriever::new())
2328 .build(&schema)
2329 .await
2330 .unwrap();
2331
2332 assert!(validator.is_valid(&json!({
2333 "name": "John Doe",
2334 "age": 30
2335 })));
2336 }
2337
2338 #[tokio::test]
2339 async fn test_async_retrieval_failure() {
2340 let schema = json!({
2341 "$ref": "https://example.com/nonexistent.json"
2342 });
2343
2344 let result = crate::async_options()
2345 .with_retriever(TestRetriever::new())
2346 .build(&schema)
2347 .await;
2348
2349 assert!(result.is_err());
2350 assert!(result.unwrap_err().to_string().contains("Schema not found"));
2351 }
2352
2353 #[tokio::test]
2354 async fn test_async_nested_references() {
2355 let mut retriever = TestRetriever::new();
2356 retriever.schemas.insert(
2357 "https://example.com/nested.json".to_string(),
2358 json!({
2359 "type": "object",
2360 "properties": {
2361 "user": { "$ref": "https://example.com/user.json" }
2362 }
2363 }),
2364 );
2365
2366 let schema = json!({
2367 "$ref": "https://example.com/nested.json"
2368 });
2369
2370 let validator = crate::async_options()
2371 .with_retriever(retriever)
2372 .build(&schema)
2373 .await
2374 .unwrap();
2375
2376 // Valid nested structure
2377 assert!(validator.is_valid(&json!({
2378 "user": {
2379 "name": "John Doe",
2380 "age": 30
2381 }
2382 })));
2383
2384 // Invalid nested structure
2385 assert!(!validator.is_valid(&json!({
2386 "user": {
2387 "age": -5
2388 }
2389 })));
2390 }
2391
2392 #[tokio::test]
2393 async fn test_async_with_registry() {
2394 use crate::Registry;
2395
2396 // Create a registry with initial schemas
2397 let registry = Registry::options()
2398 .async_retriever(TestRetriever::new())
2399 .build([(
2400 "https://example.com/user.json",
2401 Resource::from_contents(json!({
2402 "type": "object",
2403 "properties": {
2404 "name": {"type": "string"},
2405 "age": {"type": "integer", "minimum": 0}
2406 },
2407 "required": ["name"]
2408 }))
2409 .unwrap(),
2410 )])
2411 .await
2412 .unwrap();
2413
2414 // Create a validator using the pre-populated registry
2415 let validator = crate::async_options()
2416 .with_registry(registry)
2417 .build(&json!({
2418 "$ref": "https://example.com/user.json"
2419 }))
2420 .await
2421 .unwrap();
2422
2423 // Verify that validation works with the registry
2424 assert!(validator.is_valid(&json!({
2425 "name": "John Doe",
2426 "age": 30
2427 })));
2428 assert!(!validator.is_valid(&json!({
2429 "age": -5
2430 })));
2431 }
2432
2433 #[tokio::test]
2434 async fn test_async_validator_for_basic() {
2435 let schema = json!({"type": "integer"});
2436
2437 let validator = crate::async_validator_for(&schema).await.unwrap();
2438
2439 assert!(validator.is_valid(&json!(42)));
2440 assert!(!validator.is_valid(&json!("abc")));
2441 }
2442}