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