Skip to main content

jsonschema/
lib.rs

1#![allow(clippy::unnecessary_wraps)]
2//! A high-performance JSON Schema validator for Rust.
3//!
4//! - 📚 Support for popular JSON Schema drafts
5//! - 🔧 Custom keywords and format validators
6//! - 🌐 Blocking & non-blocking remote reference fetching (network/file)
7//! - 🎨 Structured Output v1 reports (flag/list/hierarchical)
8//! - ✨ Meta-schema validation for schema documents
9//! - 🚀 WebAssembly support
10//!
11//! ## Supported drafts
12//!
13//! Compliance levels vary across drafts, with newer versions having some unimplemented keywords.
14//!
15//! - ![Draft 2020-12](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft2020-12.json)
16//! - ![Draft 2019-09](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft2019-09.json)
17//! - ![Draft 7](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft7.json)
18//! - ![Draft 6](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft6.json)
19//! - ![Draft 4](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft4.json)
20//!
21//! # Validation
22//!
23//! The `jsonschema` crate offers two main approaches to validation: one-off validation and reusable validators.
24//! When external references are involved, the validator can be constructed using either blocking or non-blocking I/O.
25//!
26//!
27//! For simple use cases where you need to validate an instance against a schema once, use [`is_valid`] or [`validate`] functions:
28//!
29//! ```rust
30//! use serde_json::json;
31//!
32//! let schema = json!({"type": "string"});
33//! let instance = json!("Hello, world!");
34//!
35//! assert!(jsonschema::is_valid(&schema, &instance));
36//! assert!(jsonschema::validate(&schema, &instance).is_ok());
37//! ```
38//!
39//! For better performance, especially when validating multiple instances against the same schema, build a validator once and reuse it:
40//! If your schema contains external references, you can choose between blocking and non-blocking construction:
41//!
42//! ```rust
43//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
44//! use serde_json::json;
45//!
46//! let schema = json!({"type": "string"});
47//! // Blocking construction - will fetch external references synchronously
48//! let validator = jsonschema::validator_for(&schema)?;
49//! // Non-blocking construction - will fetch external references asynchronously
50//! # #[cfg(feature = "resolve-async")]
51//! # async fn async_example() -> Result<(), Box<dyn std::error::Error>> {
52//! # let schema = json!({"type": "string"});
53//! let validator = jsonschema::async_validator_for(&schema).await?;
54//! # Ok(())
55//! # }
56//!
57//!  // Once constructed, validation is always synchronous as it works with in-memory data
58//! assert!(validator.is_valid(&json!("Hello, world!")));
59//! assert!(!validator.is_valid(&json!(42)));
60//! assert!(validator.validate(&json!(42)).is_err());
61//!
62//! // Iterate over all errors
63//! let instance = json!(42);
64//! for error in validator.iter_errors(&instance) {
65//!     eprintln!("Error: {}", error);
66//!     eprintln!("Location: {}", error.instance_path());
67//! }
68//! # Ok(())
69//! # }
70//! ```
71//!
72//! ### Note on `format` keyword
73//!
74//! By default, format validation is draft‑dependent. To opt in for format checks, you can configure your validator like this:
75//!
76//! ```rust
77//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
78//! # use serde_json::json;
79//! #
80//! # let schema = json!({"type": "string"});
81//! let validator = jsonschema::draft202012::options()
82//!     .should_validate_formats(true)
83//!     .build(&schema)?;
84//! # Ok(())
85//! # }
86//! ```
87//!
88//! Once built, any `format` keywords in your schema will be actively validated according to the chosen draft.
89//!
90//! # Structured Output
91//!
92//! The `evaluate()` method provides access to structured validation output formats defined by
93//! [JSON Schema Output v1](https://github.com/json-schema-org/json-schema-spec/blob/main/specs/output/jsonschema-validation-output-machines.md).
94//! This is useful when you need detailed information about the validation process beyond simple pass/fail results.
95//!
96//! ```rust
97//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
98//! use serde_json::json;
99//!
100//! let schema = json!({
101//!     "type": "object",
102//!     "properties": {
103//!         "name": {"type": "string"},
104//!         "age": {"type": "number", "minimum": 0}
105//!     },
106//!     "required": ["name"]
107//! });
108//!
109//! let validator = jsonschema::validator_for(&schema)?;
110//! let instance = json!({"name": "Alice", "age": 30});
111//!
112//! // Evaluate the instance
113//! let evaluation = validator.evaluate(&instance);
114//!
115//! // Flag format: Simple boolean validity
116//! let flag = evaluation.flag();
117//! assert!(flag.valid);
118//!
119//! // List format: Flat list of all evaluation steps
120//! let list_output = serde_json::to_value(evaluation.list())?;
121//! println!("List output: {}", serde_json::to_string_pretty(&list_output)?);
122//!
123//! // Hierarchical format: Nested tree structure
124//! let hierarchical_output = serde_json::to_value(evaluation.hierarchical())?;
125//! println!(
126//!     "Hierarchical output: {}",
127//!     serde_json::to_string_pretty(&hierarchical_output)?
128//! );
129//!
130//! // Iterate over annotations collected during validation
131//! for annotation in evaluation.iter_annotations() {
132//!     println!("Annotation at {}: {:?}",
133//!         annotation.instance_location,
134//!         annotation.annotations
135//!     );
136//! }
137//!
138//! // Iterate over errors (if any)
139//! for error in evaluation.iter_errors() {
140//!     println!("Error: {}", error.error);
141//! }
142//! # Ok(())
143//! # }
144//! ```
145//!
146//! The structured output formats are particularly useful for:
147//! - **Debugging**: Understanding exactly which schema keywords matched or failed
148//! - **User feedback**: Providing detailed, actionable error messages
149//! - **Annotations**: Collecting metadata produced by successful validation
150//! - **Tooling**: Building development tools that work with JSON Schema
151//!
152//! For example, validating `["hello", "oops"]` against a schema with both `prefixItems` and
153//! `items` produces list output similar to:
154//!
155//! ```json
156//! {
157//!   "valid": false,
158//!   "details": [
159//!     {"valid": false, "evaluationPath": "", "schemaLocation": "", "instanceLocation": ""},
160//!     {
161//!       "valid": false,
162//!       "evaluationPath": "/items",
163//!       "schemaLocation": "/items",
164//!       "instanceLocation": "",
165//!       "droppedAnnotations": true
166//!     },
167//!     {
168//!       "valid": false,
169//!       "evaluationPath": "/items",
170//!       "schemaLocation": "/items",
171//!       "instanceLocation": "/1"
172//!     },
173//!     {
174//!       "valid": false,
175//!       "evaluationPath": "/items/type",
176//!       "schemaLocation": "/items/type",
177//!       "instanceLocation": "/1",
178//!       "errors": {"type": "\"oops\" is not of type \"integer\""}
179//!     },
180//!     {"valid": true, "evaluationPath": "/prefixItems", "schemaLocation": "/prefixItems", "instanceLocation": "", "annotations": 0}
181//!   ]
182//! }
183//! ```
184//!
185//! ## Output Formats
186//!
187//! ### Flag Format
188//!
189//! The simplest format, containing only a boolean validity indicator:
190//!
191//! ```rust
192//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
193//! # use serde_json::json;
194//! # let schema = json!({"type": "string"});
195//! # let validator = jsonschema::validator_for(&schema)?;
196//! let evaluation = validator.evaluate(&json!("hello"));
197//! let flag = evaluation.flag();
198//!
199//! let output = serde_json::to_value(flag)?;
200//! // Output: {"valid": true}
201//! # Ok(())
202//! # }
203//! ```
204//!
205//! ### List Format
206//!
207//! A flat list of all evaluation units, where each unit describes a validation step:
208//!
209//! ```rust
210//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
211//! # use serde_json::json;
212//! let schema = json!({
213//!     "allOf": [
214//!         {"type": "number"},
215//!         {"minimum": 0}
216//!     ]
217//! });
218//! let validator = jsonschema::validator_for(&schema)?;
219//! let evaluation = validator.evaluate(&json!(42));
220//!
221//! let list = evaluation.list();
222//! let output = serde_json::to_value(list)?;
223//! // Output includes all evaluation steps in a flat array
224//! # Ok(())
225//! # }
226//! ```
227//!
228//! ### Hierarchical Format
229//!
230//! A nested tree structure that mirrors the schema's logical structure:
231//!
232//! ```rust
233//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
234//! # use serde_json::json;
235//! let schema = json!({
236//!     "allOf": [
237//!         {"type": "number"},
238//!         {"minimum": 0}
239//!     ]
240//! });
241//! let validator = jsonschema::validator_for(&schema)?;
242//! let evaluation = validator.evaluate(&json!(42));
243//!
244//! let hierarchical = evaluation.hierarchical();
245//! let output = serde_json::to_value(hierarchical)?;
246//! // Output has nested "details" arrays for sub-schema evaluations
247//! # Ok(())
248//! # }
249//! ```
250//!
251//! # Meta-Schema Validation
252//!
253//! The crate provides functionality to validate JSON Schema documents themselves against their meta-schemas.
254//! This ensures your schema documents are valid according to the JSON Schema specification.
255//!
256//! ```rust
257//! use serde_json::json;
258//!
259//! let schema = json!({
260//!     "type": "object",
261//!     "properties": {
262//!         "name": {"type": "string"},
263//!         "age": {"type": "integer", "minimum": 0}
264//!     }
265//! });
266//!
267//! // Validate schema with automatic draft detection
268//! assert!(jsonschema::meta::is_valid(&schema));
269//! assert!(jsonschema::meta::validate(&schema).is_ok());
270//!
271//! // Invalid schema example
272//! let invalid_schema = json!({
273//!     "type": "invalid_type",  // must be one of the valid JSON Schema types
274//!     "minimum": "not_a_number"
275//! });
276//! assert!(!jsonschema::meta::is_valid(&invalid_schema));
277//! assert!(jsonschema::meta::validate(&invalid_schema).is_err());
278//! ```
279//!
280//! # Configuration
281//!
282//! `jsonschema` provides several ways to configure and use JSON Schema validation.
283//!
284//! ## Draft-specific Modules
285//!
286//! The library offers modules for specific JSON Schema draft versions:
287//!
288//! - [`draft4`]
289//! - [`draft6`]
290//! - [`draft7`]
291//! - [`draft201909`]
292//! - [`draft202012`]
293//!
294//! Each module provides:
295//! - A `new` function to create a validator
296//! - An `is_valid` function for validation with a boolean result
297//! - An `validate` function for getting the first validation error
298//! - An `options` function to create a draft-specific configuration builder
299//! - A `meta` module for draft-specific meta-schema validation
300//!
301//! Here's how you can explicitly use a specific draft version:
302//!
303//! ```rust
304//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
305//! use serde_json::json;
306//!
307//! let schema = json!({"type": "string"});
308//!
309//! // Instance validation
310//! let validator = jsonschema::draft7::new(&schema)?;
311//! assert!(validator.is_valid(&json!("Hello")));
312//!
313//! // Meta-schema validation
314//! assert!(jsonschema::draft7::meta::is_valid(&schema));
315//! # Ok(())
316//! # }
317//! ```
318//!
319//! You can also use the convenience [`is_valid`] and [`validate`] functions:
320//!
321//! ```rust
322//! use serde_json::json;
323//!
324//! let schema = json!({"type": "number", "minimum": 0});
325//! let instance = json!(42);
326//!
327//! assert!(jsonschema::draft202012::is_valid(&schema, &instance));
328//! assert!(jsonschema::draft202012::validate(&schema, &instance).is_ok());
329//! ```
330//!
331//! For more advanced configuration, you can use the draft-specific `options` function:
332//!
333//! ```rust
334//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
335//! use serde_json::json;
336//!
337//! let schema = json!({"type": "string", "format": "ends-with-42"});
338//! let validator = jsonschema::draft202012::options()
339//!     .with_format("ends-with-42", |s| s.ends_with("42"))
340//!     .should_validate_formats(true)
341//!     .build(&schema)?;
342//!
343//! assert!(validator.is_valid(&json!("Hello 42")));
344//! assert!(!validator.is_valid(&json!("No!")));
345//! # Ok(())
346//! # }
347//! ```
348//!
349//! ## General Configuration
350//!
351//! For configuration options that are not draft-specific, `jsonschema` provides a builder via `jsonschema::options()`.
352//!
353//! Here's an example:
354//!
355//! ```rust
356//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
357//! use serde_json::json;
358//!
359//! let schema = json!({"type": "string"});
360//! let validator = jsonschema::options()
361//!     // Add configuration options here
362//!     .build(&schema)?;
363//!
364//! assert!(validator.is_valid(&json!("Hello")));
365//! # Ok(())
366//! # }
367//! ```
368//!
369//! For a complete list of configuration options and their usage, please refer to the [`ValidationOptions`] struct.
370//!
371//! ## Automatic Draft Detection
372//!
373//! If you don't need to specify a particular draft version, you can use `jsonschema::validator_for`
374//! which automatically detects the appropriate draft:
375//!
376//! ```rust
377//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
378//! use serde_json::json;
379//!
380//! let schema = json!({"$schema": "http://json-schema.org/draft-07/schema#", "type": "string"});
381//! let validator = jsonschema::validator_for(&schema)?;
382//!
383//! assert!(validator.is_valid(&json!("Hello")));
384//! # Ok(())
385//! # }
386//! ```
387//!
388//! # External References
389//!
390//! By default, `jsonschema` resolves HTTP references using `reqwest` and file references from the local file system.
391//! Both blocking and non-blocking retrieval is supported during validator construction. Note that the validation
392//! itself is always synchronous as it operates on in-memory data only.
393//!
394//! ```rust
395//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
396//! use serde_json::json;
397//!
398//! let schema = json!({"$schema": "http://json-schema.org/draft-07/schema#", "type": "string"});
399//!
400//! // Building a validator with blocking retrieval (default)
401//! let validator = jsonschema::validator_for(&schema)?;
402//!
403//! // Building a validator with non-blocking retrieval (requires `resolve-async` feature)
404//! # #[cfg(feature = "resolve-async")]
405//! let validator = jsonschema::async_validator_for(&schema).await?;
406//!
407//! // Validation is always synchronous
408//! assert!(validator.is_valid(&json!("Hello")));
409//! # Ok(())
410//! # }
411//! ```
412//!
413//! To enable HTTPS support, add the `rustls-tls` feature to `reqwest` in your `Cargo.toml`:
414//!
415//! ```toml
416//! reqwest = { version = "*", features = ["rustls-tls"] }
417//! ```
418//!
419//! You can disable the default behavior using crate features:
420//!
421//! - Disable HTTP resolving: `default-features = false, features = ["resolve-file"]`
422//! - Disable file resolving: `default-features = false, features = ["resolve-http", "tls-aws-lc-rs"]`
423//! - Enable async resolution: `features = ["resolve-async"]`
424//! - Disable all resolving: `default-features = false`
425//!
426//! ## Custom retrievers
427//!
428//! You can implement custom retrievers for both blocking and non-blocking retrieval:
429//!
430//! ```rust
431//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
432//! use std::{collections::HashMap, sync::Arc};
433//! use jsonschema::{Retrieve, Uri};
434//! use serde_json::{json, Value};
435//!
436//! struct InMemoryRetriever {
437//!     schemas: HashMap<String, Value>,
438//! }
439//!
440//! impl Retrieve for InMemoryRetriever {
441//!
442//!    fn retrieve(
443//!        &self,
444//!        uri: &Uri<String>,
445//!    ) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
446//!         self.schemas
447//!             .get(uri.as_str())
448//!             .cloned()
449//!             .ok_or_else(|| format!("Schema not found: {uri}").into())
450//!     }
451//! }
452//!
453//! let mut schemas = HashMap::new();
454//! schemas.insert(
455//!     "https://example.com/person.json".to_string(),
456//!     json!({
457//!         "type": "object",
458//!         "properties": {
459//!             "name": { "type": "string" },
460//!             "age": { "type": "integer" }
461//!         },
462//!         "required": ["name", "age"]
463//!     }),
464//! );
465//!
466//! let retriever = InMemoryRetriever { schemas };
467//!
468//! let schema = json!({
469//!     "$ref": "https://example.com/person.json"
470//! });
471//!
472//! let validator = jsonschema::options()
473//!     .with_retriever(retriever)
474//!     .build(&schema)?;
475//!
476//! assert!(validator.is_valid(&json!({
477//!     "name": "Alice",
478//!     "age": 30
479//! })));
480//!
481//! assert!(!validator.is_valid(&json!({
482//!     "name": "Bob"
483//! })));
484//! #    Ok(())
485//! # }
486//! ```
487//!
488//! And non-blocking version with the `resolve-async` feature enabled:
489//!
490//! ```rust
491//! # #[cfg(feature = "resolve-async")]
492//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
493//! use jsonschema::{AsyncRetrieve, Registry, Resource, Uri};
494//! use serde_json::{Value, json};
495//!
496//! struct HttpRetriever;
497//!
498//! #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
499//! #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
500//! impl AsyncRetrieve for HttpRetriever {
501//!     async fn retrieve(
502//!         &self,
503//!         uri: &Uri<String>,
504//!     ) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
505//!         reqwest::get(uri.as_str())
506//!             .await?
507//!             .json()
508//!             .await
509//!             .map_err(Into::into)
510//!     }
511//! }
512//!
513//! // Then use it to build a validator
514//! let validator = jsonschema::async_options()
515//!     .with_retriever(HttpRetriever)
516//!     .build(&json!({"$ref": "https://example.com/user.json"}))
517//!     .await?;
518//! # Ok(())
519//! # }
520//! ```
521//!
522//! On `wasm32` targets, use `async_trait::async_trait(?Send)` so your retriever can rely on `Rc`, `JsFuture`, or other non-thread-safe types.
523//!
524//! ## Validating against schema definitions
525//!
526//! When working with large schemas containing multiple definitions (e.g., Open API schemas, DAP schemas),
527//! you may want to validate data against a specific definition rather than the entire schema. This can be
528//! achieved by registering the root schema as a resource and creating a wrapper schema that references
529//! the target definition:
530//!
531//! ```rust
532//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
533//! use serde_json::json;
534//! use jsonschema::Resource;
535//!
536//! // Root schema with multiple definitions
537//! let root_schema = json!({
538//!     "$id": "https://example.com/root",
539//!     "definitions": {
540//!         "User": {
541//!             "type": "object",
542//!             "properties": {
543//!                 "name": {"type": "string"},
544//!                 "age": {"type": "integer", "minimum": 0}
545//!             },
546//!             "required": ["name"]
547//!         },
548//!         "Product": {
549//!             "type": "object",
550//!             "properties": {
551//!                 "id": {"type": "integer"},
552//!                 "title": {"type": "string"}
553//!             },
554//!             "required": ["id", "title"]
555//!         }
556//!     }
557//! });
558//!
559//! // Create a schema that references the specific definition you want to validate against
560//! let user_schema = json!({"$ref": "https://example.com/root#/definitions/User"});
561//!
562//! // Register the root schema and build validator for the specific definition
563//! let validator = jsonschema::options()
564//!     .with_resource("https://example.com/root", Resource::from_contents(root_schema))
565//!     .build(&user_schema)?;
566//!
567//! // Now validate data against just the User definition
568//! assert!(validator.is_valid(&json!({"name": "Alice", "age": 30})));
569//! assert!(!validator.is_valid(&json!({"age": 25})));  // Missing required "name"
570//! # Ok(())
571//! # }
572//! ```
573//!
574//! This pattern is particularly useful when:
575//! - Working with API schemas that define multiple request/response types
576//! - Validating configuration snippets against specific sections of a larger schema
577//! - Testing individual schema components in isolation
578//!
579//! # Regular Expression Configuration
580//!
581//! The `jsonschema` crate allows configuring the regular expression engine used for validating
582//! keywords like `pattern` or `patternProperties`.
583//!
584//! By default, the crate uses [`fancy-regex`](https://docs.rs/fancy-regex), which supports advanced
585//! regular expression features such as lookaround and backreferences.
586//!
587//! The primary motivation for switching to the `regex` engine is security and performance:
588//! it guarantees linear-time matching, preventing potential Denial of Service attacks from malicious patterns
589//! in user-provided schemas while offering better performance with a smaller feature set.
590//!
591//! You can configure the engine at **runtime** using the [`PatternOptions`] API:
592//!
593//! ### Example: Configure `fancy-regex` with Backtracking Limit
594//!
595//! ```rust
596//! use serde_json::json;
597//! use jsonschema::PatternOptions;
598//!
599//! let schema = json!({
600//!     "type": "string",
601//!     "pattern": "^(a+)+$"
602//! });
603//!
604//! let validator = jsonschema::options()
605//!     .with_pattern_options(
606//!         PatternOptions::fancy_regex()
607//!             .backtrack_limit(10_000)
608//!     )
609//!     .build(&schema)
610//!     .expect("A valid schema");
611//! ```
612//!
613//! ### Example: Use the `regex` Engine Instead
614//!
615//! ```rust
616//! use serde_json::json;
617//! use jsonschema::PatternOptions;
618//!
619//! let schema = json!({
620//!     "type": "string",
621//!     "pattern": "^a+$"
622//! });
623//!
624//! let validator = jsonschema::options()
625//!     .with_pattern_options(PatternOptions::regex())
626//!     .build(&schema)
627//!     .expect("A valid schema");
628//! ```
629//!
630//! ### Notes
631//!
632//! - If neither engine is explicitly set, `fancy-regex` is used by default.
633//! - Regular expressions that rely on advanced features like `(?<=...)` (lookbehind) or backreferences (`\1`) will fail with the `regex` engine.
634//!
635//! # Custom Keywords
636//!
637//! `jsonschema` allows you to extend its functionality by implementing custom validation logic through custom keywords.
638//! This feature is particularly useful when you need to validate against domain-specific rules that aren't covered by the standard JSON Schema keywords.
639//!
640//! To implement a custom keyword, you need to:
641//! 1. Create a struct that implements the [`Keyword`] trait
642//! 2. Create a factory function or closure that produces instances of your custom keyword
643//! 3. Register the custom keyword with the [`Validator`] instance using the [`ValidationOptions::with_keyword`] method
644//!
645//! Here's a complete example:
646//!
647//! ```rust
648//! use jsonschema::{paths::Location, Keyword, ValidationError};
649//! use serde_json::{json, Map, Value};
650//!
651//! struct EvenNumberValidator;
652//!
653//! impl Keyword for EvenNumberValidator {
654//!     fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
655//!         if let Some(n) = instance.as_u64() {
656//!             if n % 2 == 0 {
657//!                 return Ok(());
658//!             }
659//!         }
660//!         Err(ValidationError::custom("value must be an even integer"))
661//!     }
662//!
663//!     fn is_valid(&self, instance: &Value) -> bool {
664//!         instance.as_u64().map_or(false, |n| n % 2 == 0)
665//!     }
666//! }
667//!
668//! fn even_number_factory<'a>(
669//!     _parent: &'a Map<String, Value>,
670//!     value: &'a Value,
671//!     _path: Location,
672//! ) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
673//!     if value.as_bool() == Some(true) {
674//!         Ok(Box::new(EvenNumberValidator))
675//!     } else {
676//!         Err(ValidationError::schema("The 'even-number' keyword must be set to true"))
677//!     }
678//! }
679//!
680//! let schema = json!({"even-number": true, "type": "integer"});
681//! let validator = jsonschema::options()
682//!     .with_keyword("even-number", even_number_factory)
683//!     .build(&schema)
684//!     .expect("Invalid schema");
685//!
686//! assert!(validator.is_valid(&json!(2)));
687//! assert!(!validator.is_valid(&json!(3)));
688//! assert!(!validator.is_valid(&json!("not a number")));
689//! ```
690//!
691//! In this example, we've created a custom `even-number` keyword that validates whether a number is even.
692//! The `EvenNumberValidator` implements the actual validation logic, while the `even_number_factory`
693//! creates instances of the validator and allows for additional configuration based on the keyword's value in the schema.
694//!
695//! You can also use a closure instead of a factory function for simpler cases:
696//!
697//! ```rust
698//! # use jsonschema::{paths::Location, Keyword, ValidationError};
699//! # use serde_json::{json, Map, Value};
700//! #
701//! # struct EvenNumberValidator;
702//! #
703//! # impl Keyword for EvenNumberValidator {
704//! #     fn validate<'i>(&self, instance: &'i Value) -> Result<(), ValidationError<'i>> {
705//! #         Ok(())
706//! #     }
707//! #
708//! #     fn is_valid(&self, instance: &Value) -> bool {
709//! #         true
710//! #     }
711//! # }
712//! let schema = json!({"even-number": true, "type": "integer"});
713//! let validator = jsonschema::options()
714//!     .with_keyword("even-number", |_, _, _| {
715//!         Ok(Box::new(EvenNumberValidator))
716//!     })
717//!     .build(&schema)
718//!     .expect("Invalid schema");
719//! ```
720//!
721//! # Custom Formats
722//!
723//! JSON Schema allows for format validation through the `format` keyword. While `jsonschema`
724//! provides built-in validators for standard formats, you can also define custom format validators
725//! for domain-specific string formats.
726//!
727//! To implement a custom format validator:
728//!
729//! 1. Define a function or a closure that takes a `&str` and returns a `bool`.
730//! 2. Register the function with `jsonschema::options().with_format()`.
731//!
732//! ```rust
733//! use serde_json::json;
734//!
735//! // Step 1: Define the custom format validator function
736//! fn ends_with_42(s: &str) -> bool {
737//!     s.ends_with("42!")
738//! }
739//!
740//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
741//! // Step 2: Create a schema using the custom format
742//! let schema = json!({
743//!     "type": "string",
744//!     "format": "ends-with-42"
745//! });
746//!
747//! // Step 3: Build the validator with the custom format
748//! let validator = jsonschema::options()
749//!     .with_format("ends-with-42", ends_with_42)
750//!     .with_format("ends-with-43", |s| s.ends_with("43!"))
751//!     .should_validate_formats(true)
752//!     .build(&schema)?;
753//!
754//! // Step 4: Validate instances
755//! assert!(validator.is_valid(&json!("Hello42!")));
756//! assert!(!validator.is_valid(&json!("Hello43!")));
757//! assert!(!validator.is_valid(&json!(42))); // Not a string
758//! #    Ok(())
759//! # }
760//! ```
761//!
762//! ### Notes on Custom Format Validators
763//!
764//! - Custom format validators are only called for string instances.
765//! - In newer drafts, `format` is purely an annotation and won’t do any checking unless you
766//!   opt in by calling `.should_validate_formats(true)` on your options builder. If you omit
767//!   it, all `format` keywords are ignored at validation time.
768//!
769//! # Arbitrary Precision Numbers
770//!
771//! Enable the `arbitrary-precision` feature for exact validation of numbers beyond standard numeric ranges:
772//!
773//! ```toml
774//! jsonschema = { version = "x.y.z", features = ["arbitrary-precision"] }
775//! ```
776//!
777//! This provides:
778//! - Arbitrarily large integers (e.g., `18446744073709551616`)
779//! - Exact decimal precision without `f64` rounding (e.g., `0.1`, `0.3`)
780//!
781//! **Important**: Precision is only preserved when parsing JSON from strings. Using Rust literals
782//! or the `json!()` macro converts numbers to `f64`, losing precision.
783//!
784//! ```rust
785//! # use jsonschema::Validator;
786//! // Precision preserved - parsed from JSON string
787//! let schema = serde_json::from_str(r#"{"minimum": 0.1}"#)?;
788//! let instance = serde_json::from_str("0.3")?;
789//! let validator = Validator::new(&schema)?;
790//! assert!(validator.is_valid(&instance));
791//! # Ok::<(), Box<dyn std::error::Error>>(())
792//! ```
793//!
794//! # WebAssembly support
795//!
796//! `jsonschema` supports WebAssembly with different capabilities based on the target platform:
797//!
798//! ## Browser/JavaScript (`wasm32-unknown-unknown`)
799//!
800//! When targeting browser or JavaScript environments, external reference resolution is not
801//! supported by default due to platform limitations:
802//!    - No filesystem access (`resolve-file` feature is not available)
803//!    - No synchronous HTTP requests (`resolve-http` feature is not available)
804//!
805//! To use `jsonschema` in these environments, disable default features:
806//!
807//! ```toml
808//! jsonschema = { version = "x.y.z", default-features = false }
809//! ```
810//!
811//! Note: Attempting to compile with `resolve-http` or `resolve-file` features on
812//! `wasm32-unknown-unknown` will result in a compile error.
813//!
814//! For external references in browser environments, implement a custom retriever that uses
815//! browser APIs (like `fetch`). See the [External References](#external-references) section.
816//!
817//! ## WASI (`wasm32-wasip1` / `wasm32-wasip2`)
818//!
819//! WASI environments (preview 1 and preview 2) can compile schemas and run validators, but the bundled
820//! HTTP retriever depends on `reqwest`’s blocking client, which isn't available on these targets. Use
821//! file access and custom retrievers instead.
822//!
823//! **Supported:**
824//! - Blocking file resolution (`resolve-file` feature)
825//! - Custom blocking retrievers (including wrapping async operations)
826//! - Custom async retrievers via the `resolve-async` feature (for example, `jsonschema::async_options`
827//!   together with your own async runtime)
828//!
829//! **Not Supported:**
830//! - The bundled HTTP retriever (depends on `reqwest`’s blocking client)
831//!
832//! ```toml
833//! jsonschema = { version = "x.y.z", default-features = false, features = ["resolve-file"] }
834//! ```
835//!
836//! **Workaround for HTTP:** Implement a custom blocking or async [`Retrieve`] that uses your preferred
837//! HTTP client, and enable `resolve-async` if you want to build validators through `async_options()`
838//! on WASI.
839
840#[cfg(all(
841    target_arch = "wasm32",
842    target_os = "unknown",
843    any(feature = "resolve-file", feature = "resolve-http")
844))]
845compile_error!(
846    "Features 'resolve-http' and 'resolve-file' are not supported on wasm32-unknown-unknown"
847);
848#[cfg(all(
849    not(target_arch = "wasm32"),
850    feature = "resolve-http",
851    not(any(feature = "tls-aws-lc-rs", feature = "tls-ring"))
852))]
853compile_error!(
854    "Feature `resolve-http` requires a TLS provider: enable `tls-aws-lc-rs` \
855(default) or `tls-ring`."
856);
857
858pub mod canonical;
859pub(crate) mod compiler;
860mod content_encoding;
861mod content_media_type;
862mod ecma;
863pub mod error;
864mod evaluation;
865#[doc(hidden)]
866pub mod ext;
867mod http;
868mod keywords;
869mod node;
870mod options;
871pub mod output;
872pub mod paths;
873pub(crate) mod properties;
874pub(crate) mod regex;
875mod retriever;
876pub mod types;
877mod validator;
878
879pub use error::{ErrorIterator, MaskedValidationError, ValidationError, ValidationErrors};
880pub use evaluation::{
881    AnnotationEntry, ErrorEntry, Evaluation, FlagOutput, HierarchicalOutput, ListOutput,
882};
883pub use http::HttpOptions;
884pub use keywords::custom::Keyword;
885pub use options::{EmailOptions, FancyRegex, PatternOptions, Regex, ValidationOptions};
886pub use referencing::{
887    Draft, Error as ReferencingError, Registry, RegistryOptions, Resource, Retrieve, Uri,
888};
889#[cfg(all(feature = "resolve-http", not(target_arch = "wasm32")))]
890pub use retriever::{HttpRetriever, HttpRetrieverError};
891pub use types::{JsonType, JsonTypeSet, JsonTypeSetIterator};
892pub use validator::{ValidationContext, Validator};
893
894#[cfg(feature = "resolve-async")]
895pub use referencing::AsyncRetrieve;
896#[cfg(all(
897    feature = "resolve-http",
898    feature = "resolve-async",
899    not(target_arch = "wasm32")
900))]
901pub use retriever::AsyncHttpRetriever;
902
903use serde_json::Value;
904
905/// Validate `instance` against `schema` and get a `true` if the instance is valid and `false`
906/// otherwise. Draft is detected automatically.
907///
908/// # Examples
909///
910/// ```rust
911/// use serde_json::json;
912///
913/// let schema = json!({"maxLength": 5});
914/// let instance = json!("foo");
915/// assert!(jsonschema::is_valid(&schema, &instance));
916/// ```
917///
918/// # Panics
919///
920/// This function panics if an invalid schema is passed.
921///
922/// This function **must not** be called from within an async runtime if the schema contains
923/// external references that require network requests, or it will panic when attempting to block.
924/// Use `async_validator_for` for async contexts, or run this in a separate blocking thread
925/// via `tokio::task::spawn_blocking`.
926#[must_use]
927#[inline]
928pub fn is_valid(schema: &Value, instance: &Value) -> bool {
929    validator_for(schema)
930        .expect("Invalid schema")
931        .is_valid(instance)
932}
933
934/// Validate `instance` against `schema` and return the first error if any. Draft is detected automatically.
935///
936/// # Examples
937///
938/// ```rust
939/// use serde_json::json;
940///
941/// let schema = json!({"maxLength": 5});
942/// let instance = json!("foo");
943/// assert!(jsonschema::validate(&schema, &instance).is_ok());
944/// ```
945///
946/// # Errors
947///
948/// Returns the first [`ValidationError`] encountered when `instance` violates `schema`.
949///
950/// # Panics
951///
952/// This function panics if an invalid schema is passed.
953///
954/// This function **must not** be called from within an async runtime if the schema contains
955/// external references that require network requests, or it will panic when attempting to block.
956/// Use `async_validator_for` for async contexts, or run this in a separate blocking thread
957/// via `tokio::task::spawn_blocking`.
958#[inline]
959pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
960    validator_for(schema)
961        .expect("Invalid schema")
962        .validate(instance)
963}
964
965/// Evaluate `instance` against `schema` and return structured validation output. Draft is detected automatically.
966///
967/// Returns an [`Evaluation`] containing detailed validation results in JSON Schema Output v1 format,
968/// including annotations and errors across the entire validation tree.
969///
970/// # Examples
971///
972/// ```rust
973/// use serde_json::json;
974///
975/// let schema = json!({"type": "string", "minLength": 3});
976/// let instance = json!("foo");
977/// let evaluation = jsonschema::evaluate(&schema, &instance);
978/// assert!(evaluation.flag().valid);
979/// ```
980///
981/// # Panics
982///
983/// This function panics if an invalid schema is passed.
984///
985/// This function **must not** be called from within an async runtime if the schema contains
986/// external references that require network requests, or it will panic when attempting to block.
987/// Use `async_validator_for` for async contexts, or run this in a separate blocking thread
988/// via `tokio::task::spawn_blocking`.
989#[must_use]
990#[inline]
991pub fn evaluate(schema: &Value, instance: &Value) -> Evaluation {
992    validator_for(schema)
993        .expect("Invalid schema")
994        .evaluate(instance)
995}
996
997/// Create a validator for the input schema with automatic draft detection and default options.
998///
999/// # Examples
1000///
1001/// ```rust
1002/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1003/// use serde_json::json;
1004///
1005/// let schema = json!({"minimum": 5});
1006/// let instance = json!(42);
1007///
1008/// let validator = jsonschema::validator_for(&schema)?;
1009/// assert!(validator.is_valid(&instance));
1010/// # Ok(())
1011/// # }
1012/// ```
1013///
1014/// # Errors
1015///
1016/// Returns an error if the schema is invalid or external references cannot be resolved.
1017///
1018/// # Panics
1019///
1020/// This function **must not** be called from within an async runtime if the schema contains
1021/// external references that require network requests, or it will panic when attempting to block.
1022/// Use `async_validator_for` for async contexts, or run this in a separate blocking thread
1023/// via `tokio::task::spawn_blocking`.
1024pub fn validator_for(schema: &Value) -> Result<Validator, ValidationError<'static>> {
1025    Validator::new(schema)
1026}
1027
1028/// Create a validator for the input schema with automatic draft detection and default options,
1029/// using non-blocking retrieval for external references.
1030///
1031/// This is the async counterpart to [`validator_for`]. Note that only the construction is
1032/// asynchronous - validation itself is always synchronous.
1033///
1034/// # Examples
1035///
1036/// ```rust
1037/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1038/// use serde_json::json;
1039///
1040/// let schema = json!({
1041///     "type": "object",
1042///     "properties": {
1043///         "user": { "$ref": "https://example.com/user.json" }
1044///     }
1045/// });
1046///
1047/// let validator = jsonschema::async_validator_for(&schema).await?;
1048/// assert!(validator.is_valid(&json!({"user": {"name": "Alice"}})));
1049/// # Ok(())
1050/// # }
1051/// ```
1052///
1053/// # Errors
1054///
1055/// Returns an error if the schema is invalid or external references cannot be resolved.
1056#[cfg(feature = "resolve-async")]
1057pub async fn async_validator_for(schema: &Value) -> Result<Validator, ValidationError<'static>> {
1058    Validator::async_new(schema).await
1059}
1060
1061/// Create a builder for configuring JSON Schema validation options.
1062///
1063/// This function returns a [`ValidationOptions`] struct, which allows you to set various
1064/// options for JSON Schema validation. You can use this builder to specify
1065/// the draft version, set custom formats, and more.
1066///
1067/// If [`with_draft`](ValidationOptions::with_draft) is not called, the draft is
1068/// auto-detected from the schema's `$schema` field — the same behaviour as [`validator_for`].
1069///
1070/// **Note:** When calling [`ValidationOptions::build`], it **must not** be called from within
1071/// an async runtime if the schema contains external references that require network requests,
1072/// or it will panic. Use `async_options` for async contexts.
1073///
1074/// # Examples
1075///
1076/// Basic usage with draft specification:
1077///
1078/// ```
1079/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1080/// use serde_json::json;
1081/// use jsonschema::Draft;
1082///
1083/// let schema = json!({"type": "string"});
1084/// let validator = jsonschema::options()
1085///     .with_draft(Draft::Draft7)
1086///     .build(&schema)?;
1087///
1088/// assert!(validator.is_valid(&json!("Hello")));
1089/// # Ok(())
1090/// # }
1091/// ```
1092///
1093/// Advanced configuration:
1094///
1095/// ```
1096/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1097/// use serde_json::json;
1098///
1099/// let schema = json!({"type": "string", "format": "custom"});
1100/// let validator = jsonschema::options()
1101///     .with_format("custom", |value| value.len() == 3)
1102///     .should_validate_formats(true)
1103///     .build(&schema)?;
1104///
1105/// assert!(validator.is_valid(&json!("abc")));
1106/// assert!(!validator.is_valid(&json!("abcd")));
1107/// # Ok(())
1108/// # }
1109/// ```
1110///
1111/// See [`ValidationOptions`] for all available configuration options.
1112#[must_use]
1113pub fn options() -> ValidationOptions {
1114    Validator::options()
1115}
1116
1117/// Create a builder for configuring JSON Schema validation options.
1118///
1119/// This function returns a [`ValidationOptions`] struct which allows you to set various options for JSON Schema validation.
1120/// External references will be retrieved using non-blocking I/O.
1121///
1122/// # Examples
1123///
1124/// Basic usage with external references:
1125///
1126/// ```rust
1127/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1128/// use serde_json::json;
1129///
1130/// let schema = json!({
1131///     "$ref": "https://example.com/user.json"
1132/// });
1133///
1134/// let validator = jsonschema::async_options()
1135///     .build(&schema)
1136///     .await?;
1137///
1138/// assert!(validator.is_valid(&json!({"name": "Alice"})));
1139/// # Ok(())
1140/// # }
1141/// ```
1142///
1143/// Advanced configuration:
1144///
1145/// ```rust
1146/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1147/// use serde_json::{Value, json};
1148/// use jsonschema::{Draft, AsyncRetrieve, Uri};
1149///
1150/// // Custom async retriever
1151/// struct MyRetriever;
1152///
1153/// #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
1154/// #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
1155/// impl AsyncRetrieve for MyRetriever {
1156///     async fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
1157///         // Custom retrieval logic
1158///         Ok(json!({}))
1159///     }
1160/// }
1161///
1162/// let schema = json!({
1163///     "$ref": "https://example.com/user.json"
1164/// });
1165/// let validator = jsonschema::async_options()
1166///     .with_draft(Draft::Draft202012)
1167///     .with_retriever(MyRetriever)
1168///     .build(&schema)
1169///     .await?;
1170/// # Ok(())
1171/// # }
1172/// ```
1173///
1174/// On `wasm32` targets, annotate your implementation with `async_trait::async_trait(?Send)` to drop the `Send + Sync` requirement.
1175///
1176/// See [`ValidationOptions`] for all available configuration options.
1177#[cfg(feature = "resolve-async")]
1178#[must_use]
1179pub fn async_options() -> ValidationOptions<std::sync::Arc<dyn AsyncRetrieve>> {
1180    Validator::async_options()
1181}
1182
1183/// Functionality for validating JSON Schema documents against their meta-schemas.
1184pub mod meta {
1185    use crate::{error::ValidationError, Draft};
1186    use ahash::AHashSet;
1187    use referencing::{Registry, Retrieve};
1188    use serde_json::Value;
1189
1190    pub use validator_handle::MetaValidator;
1191
1192    /// Create a meta-validation options builder.
1193    ///
1194    /// # Examples
1195    ///
1196    /// ```rust
1197    /// use serde_json::json;
1198    /// use jsonschema::{Registry, Resource};
1199    ///
1200    /// let custom_meta = Resource::from_contents(json!({
1201    ///     "$schema": "https://json-schema.org/draft/2020-12/schema",
1202    ///     "type": "object"
1203    /// }));
1204    ///
1205    /// let registry = Registry::try_new(
1206    ///     "http://example.com/meta",
1207    ///     custom_meta
1208    /// ).unwrap();
1209    ///
1210    /// let schema = json!({
1211    ///     "$schema": "http://example.com/meta",
1212    ///     "type": "string"
1213    /// });
1214    ///
1215    /// assert!(jsonschema::meta::options()
1216    ///     .with_registry(registry)
1217    ///     .is_valid(&schema));
1218    /// ```
1219    #[must_use]
1220    pub fn options() -> MetaSchemaOptions {
1221        MetaSchemaOptions::default()
1222    }
1223
1224    /// Options for meta-schema validation.
1225    #[derive(Clone, Default)]
1226    pub struct MetaSchemaOptions {
1227        registry: Option<Registry>,
1228    }
1229
1230    impl MetaSchemaOptions {
1231        /// Use a registry for resolving custom meta-schemas.
1232        ///
1233        /// # Examples
1234        ///
1235        /// ```rust
1236        /// use serde_json::json;
1237        /// use jsonschema::{Registry, Resource};
1238        ///
1239        /// let custom_meta = Resource::from_contents(json!({
1240        ///     "$schema": "https://json-schema.org/draft/2020-12/schema",
1241        ///     "type": "object"
1242        /// }));
1243        ///
1244        /// let registry = Registry::try_new(
1245        ///     "http://example.com/meta",
1246        ///     custom_meta
1247        /// ).unwrap();
1248        ///
1249        /// let options = jsonschema::meta::options()
1250        ///     .with_registry(registry);
1251        /// ```
1252        #[must_use]
1253        pub fn with_registry(mut self, registry: Registry) -> Self {
1254            self.registry = Some(registry);
1255            self
1256        }
1257
1258        /// Check if a schema is valid according to its meta-schema.
1259        ///
1260        /// # Panics
1261        ///
1262        /// Panics if the meta-schema cannot be resolved.
1263        #[must_use]
1264        pub fn is_valid(&self, schema: &Value) -> bool {
1265            match try_meta_validator_for(schema, self.registry.as_ref()) {
1266                Ok(validator) => validator.as_ref().is_valid(schema),
1267                Err(e) => panic!("Failed to resolve meta-schema: {e}"),
1268            }
1269        }
1270
1271        /// Validate a schema according to its meta-schema.
1272        ///
1273        /// # Errors
1274        ///
1275        /// Returns [`ValidationError`] if the schema is invalid or if the meta-schema cannot be resolved.
1276        pub fn validate<'a>(&self, schema: &'a Value) -> Result<(), ValidationError<'a>> {
1277            let validator = try_meta_validator_for(schema, self.registry.as_ref())?;
1278            validator.as_ref().validate(schema)
1279        }
1280    }
1281
1282    mod validator_handle {
1283        use crate::Validator;
1284        use std::{marker::PhantomData, ops::Deref};
1285
1286        /// Handle to a draft-specific meta-schema [`Validator`]. Borrows cached validators on native
1287        /// targets and owns validators on `wasm32`.
1288        pub struct MetaValidator<'a>(MetaValidatorInner<'a>);
1289
1290        // Native builds can hand out references to cached validators or own dynamic ones,
1291        // while wasm targets need owned instances because the validator type does not implement `Sync` there.
1292        enum MetaValidatorInner<'a> {
1293            #[cfg(not(target_family = "wasm"))]
1294            Borrowed(&'a Validator),
1295            Owned(Box<Validator>, PhantomData<&'a Validator>),
1296        }
1297
1298        #[cfg_attr(target_family = "wasm", allow(clippy::elidable_lifetime_names))]
1299        impl<'a> MetaValidator<'a> {
1300            #[cfg(not(target_family = "wasm"))]
1301            pub(crate) fn borrowed(validator: &'a Validator) -> Self {
1302                Self(MetaValidatorInner::Borrowed(validator))
1303            }
1304
1305            pub(crate) fn owned(validator: Validator) -> Self {
1306                Self(MetaValidatorInner::Owned(Box::new(validator), PhantomData))
1307            }
1308        }
1309
1310        impl AsRef<Validator> for MetaValidator<'_> {
1311            fn as_ref(&self) -> &Validator {
1312                match &self.0 {
1313                    #[cfg(not(target_family = "wasm"))]
1314                    MetaValidatorInner::Borrowed(validator) => validator,
1315                    MetaValidatorInner::Owned(validator, _) => validator,
1316                }
1317            }
1318        }
1319
1320        impl Deref for MetaValidator<'_> {
1321            type Target = Validator;
1322
1323            fn deref(&self) -> &Self::Target {
1324                self.as_ref()
1325            }
1326        }
1327    }
1328
1329    pub(crate) mod validators {
1330        use crate::Validator;
1331        #[cfg(not(target_family = "wasm"))]
1332        use std::sync::LazyLock;
1333
1334        fn build_validator(schema: &serde_json::Value) -> Validator {
1335            crate::options()
1336                .without_schema_validation()
1337                .build(schema)
1338                .expect("Meta-schema should be valid")
1339        }
1340
1341        #[cfg(not(target_family = "wasm"))]
1342        pub(crate) static DRAFT4_META_VALIDATOR: LazyLock<Validator> =
1343            LazyLock::new(|| build_validator(&referencing::meta::DRAFT4));
1344        #[cfg(target_family = "wasm")]
1345        pub(crate) fn draft4_meta_validator() -> Validator {
1346            build_validator(&referencing::meta::DRAFT4)
1347        }
1348
1349        #[cfg(not(target_family = "wasm"))]
1350        pub(crate) static DRAFT6_META_VALIDATOR: LazyLock<Validator> =
1351            LazyLock::new(|| build_validator(&referencing::meta::DRAFT6));
1352        #[cfg(target_family = "wasm")]
1353        pub(crate) fn draft6_meta_validator() -> Validator {
1354            build_validator(&referencing::meta::DRAFT6)
1355        }
1356
1357        #[cfg(not(target_family = "wasm"))]
1358        pub(crate) static DRAFT7_META_VALIDATOR: LazyLock<Validator> =
1359            LazyLock::new(|| build_validator(&referencing::meta::DRAFT7));
1360        #[cfg(target_family = "wasm")]
1361        pub(crate) fn draft7_meta_validator() -> Validator {
1362            build_validator(&referencing::meta::DRAFT7)
1363        }
1364
1365        #[cfg(not(target_family = "wasm"))]
1366        pub(crate) static DRAFT201909_META_VALIDATOR: LazyLock<Validator> =
1367            LazyLock::new(|| build_validator(&referencing::meta::DRAFT201909));
1368        #[cfg(target_family = "wasm")]
1369        pub(crate) fn draft201909_meta_validator() -> Validator {
1370            build_validator(&referencing::meta::DRAFT201909)
1371        }
1372
1373        #[cfg(not(target_family = "wasm"))]
1374        pub(crate) static DRAFT202012_META_VALIDATOR: LazyLock<Validator> =
1375            LazyLock::new(|| build_validator(&referencing::meta::DRAFT202012));
1376        #[cfg(target_family = "wasm")]
1377        pub(crate) fn draft202012_meta_validator() -> Validator {
1378            build_validator(&referencing::meta::DRAFT202012)
1379        }
1380    }
1381
1382    pub(crate) fn validator_for_draft(draft: Draft) -> MetaValidator<'static> {
1383        #[cfg(not(target_family = "wasm"))]
1384        {
1385            match draft {
1386                Draft::Draft4 => MetaValidator::borrowed(&validators::DRAFT4_META_VALIDATOR),
1387                Draft::Draft6 => MetaValidator::borrowed(&validators::DRAFT6_META_VALIDATOR),
1388                Draft::Draft7 => MetaValidator::borrowed(&validators::DRAFT7_META_VALIDATOR),
1389                Draft::Draft201909 => {
1390                    MetaValidator::borrowed(&validators::DRAFT201909_META_VALIDATOR)
1391                }
1392                // Draft202012, Unknown, or any future draft variants
1393                _ => MetaValidator::borrowed(&validators::DRAFT202012_META_VALIDATOR),
1394            }
1395        }
1396        #[cfg(target_family = "wasm")]
1397        {
1398            let validator = match draft {
1399                Draft::Draft4 => validators::draft4_meta_validator(),
1400                Draft::Draft6 => validators::draft6_meta_validator(),
1401                Draft::Draft7 => validators::draft7_meta_validator(),
1402                Draft::Draft201909 => validators::draft201909_meta_validator(),
1403                // Draft202012, Unknown, or any future draft variants
1404                _ => validators::draft202012_meta_validator(),
1405            };
1406            MetaValidator::owned(validator)
1407        }
1408    }
1409
1410    /// Validate a JSON Schema document against its meta-schema and get a `true` if the schema is valid
1411    /// and `false` otherwise. Draft version is detected automatically.
1412    ///
1413    /// # Examples
1414    ///
1415    /// ```rust
1416    /// use serde_json::json;
1417    ///
1418    /// let schema = json!({
1419    ///     "type": "string",
1420    ///     "maxLength": 5
1421    /// });
1422    /// assert!(jsonschema::meta::is_valid(&schema));
1423    /// ```
1424    ///
1425    /// # Panics
1426    ///
1427    /// This function panics if the meta-schema can't be detected.
1428    ///
1429    /// # Note
1430    ///
1431    /// This helper only works with the built-in JSON Schema drafts. For schemas that declare a
1432    /// custom `$schema`, construct a registry that contains your meta-schema and use
1433    /// [`meta::options().with_registry(...)`](crate::meta::options) to validate it.
1434    #[must_use]
1435    pub fn is_valid(schema: &Value) -> bool {
1436        match try_meta_validator_for(schema, None) {
1437            Ok(validator) => validator.as_ref().is_valid(schema),
1438            Err(error) => panic!("Failed to resolve meta-schema: {error}"),
1439        }
1440    }
1441    /// Validate a JSON Schema document against its meta-schema and return the first error if any.
1442    /// Draft version is detected automatically.
1443    ///
1444    /// # Examples
1445    ///
1446    /// ```rust
1447    /// use serde_json::json;
1448    ///
1449    /// let schema = json!({
1450    ///     "type": "string",
1451    ///     "maxLength": 5
1452    /// });
1453    /// assert!(jsonschema::meta::validate(&schema).is_ok());
1454    ///
1455    /// // Invalid schema
1456    /// let invalid_schema = json!({
1457    ///     "type": "invalid_type"
1458    /// });
1459    /// assert!(jsonschema::meta::validate(&invalid_schema).is_err());
1460    /// ```
1461    ///
1462    /// # Errors
1463    ///
1464    /// Returns the first [`ValidationError`] describing why the schema violates the detected meta-schema.
1465    ///
1466    /// # Panics
1467    ///
1468    /// This function panics if the meta-schema can't be detected.
1469    ///
1470    /// # Note
1471    ///
1472    /// Like [`is_valid`], this helper only handles the bundled JSON
1473    /// Schema drafts. For custom meta-schemas, use [`meta::options().with_registry(...)`](crate::meta::options)
1474    /// so the registry can supply the meta-schema.
1475    pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
1476        let validator = try_meta_validator_for(schema, None)?;
1477        validator.as_ref().validate(schema)
1478    }
1479
1480    /// Build a validator for a JSON Schema's meta-schema.
1481    /// Draft version is detected automatically.
1482    ///
1483    /// Returns a [`MetaValidator`] that can be used to validate the schema or access
1484    /// structured validation output via the evaluate API.
1485    ///
1486    /// # Examples
1487    ///
1488    /// ```rust
1489    /// use serde_json::json;
1490    ///
1491    /// let schema = json!({
1492    ///     "type": "string",
1493    ///     "maxLength": 5
1494    /// });
1495    ///
1496    /// let validator = jsonschema::meta::validator_for(&schema)
1497    ///     .expect("Valid meta-schema");
1498    ///
1499    /// // Use evaluate API for structured output
1500    /// let evaluation = validator.evaluate(&schema);
1501    /// assert!(evaluation.flag().valid);
1502    /// ```
1503    ///
1504    /// # Errors
1505    ///
1506    /// Returns [`ValidationError`] if the meta-schema cannot be resolved or built.
1507    ///
1508    /// # Panics
1509    ///
1510    /// This function panics if the meta-schema can't be detected.
1511    ///
1512    /// # Note
1513    ///
1514    /// This helper only handles the bundled JSON Schema drafts. For custom meta-schemas,
1515    /// use [`meta::options().with_registry(...)`](crate::meta::options).
1516    pub fn validator_for(
1517        schema: &Value,
1518    ) -> Result<MetaValidator<'static>, ValidationError<'static>> {
1519        try_meta_validator_for(schema, None)
1520    }
1521
1522    fn try_meta_validator_for<'a>(
1523        schema: &Value,
1524        registry: Option<&Registry>,
1525    ) -> Result<MetaValidator<'a>, ValidationError<'static>> {
1526        let draft = Draft::default().detect(schema);
1527
1528        // For custom meta-schemas (Draft::Unknown), attempt to resolve the meta-schema
1529        if draft == Draft::Unknown {
1530            if let Some(meta_schema_uri) = schema
1531                .as_object()
1532                .and_then(|obj| obj.get("$schema"))
1533                .and_then(|s| s.as_str())
1534            {
1535                // Try registry first if available
1536                if let Some(registry) = registry {
1537                    let (custom_meta_schema, resolved_draft) =
1538                        resolve_meta_schema_with_registry(meta_schema_uri, registry)?;
1539                    let validator = crate::options()
1540                        .with_draft(resolved_draft)
1541                        .with_registry(registry.clone())
1542                        .without_schema_validation()
1543                        .build(&custom_meta_schema)?;
1544                    return Ok(MetaValidator::owned(validator));
1545                }
1546
1547                // Use default retriever
1548                let (custom_meta_schema, resolved_draft) =
1549                    resolve_meta_schema_chain(meta_schema_uri)?;
1550                let validator = crate::options()
1551                    .with_draft(resolved_draft)
1552                    .without_schema_validation()
1553                    .build(&custom_meta_schema)?;
1554                return Ok(MetaValidator::owned(validator));
1555            }
1556        }
1557
1558        Ok(validator_for_draft(draft))
1559    }
1560
1561    fn resolve_meta_schema_with_registry(
1562        uri: &str,
1563        registry: &Registry,
1564    ) -> Result<(Value, Draft), ValidationError<'static>> {
1565        let resolver = registry.try_resolver(uri)?;
1566        let first_resolved = resolver.lookup("")?;
1567        let first_meta_schema = first_resolved.contents().clone();
1568
1569        let draft = walk_meta_schema_chain(uri, |current_uri| {
1570            let resolver = registry.try_resolver(current_uri)?;
1571            let resolved = resolver.lookup("")?;
1572            Ok(resolved.contents().clone())
1573        })?;
1574
1575        Ok((first_meta_schema, draft))
1576    }
1577
1578    fn resolve_meta_schema_chain(uri: &str) -> Result<(Value, Draft), ValidationError<'static>> {
1579        let retriever = crate::retriever::DefaultRetriever;
1580        let first_meta_uri = referencing::uri::from_str(uri)?;
1581        let first_meta_schema = retriever
1582            .retrieve(&first_meta_uri)
1583            .map_err(|e| referencing::Error::unretrievable(uri, e))?;
1584
1585        let draft = walk_meta_schema_chain(uri, |current_uri| {
1586            let meta_uri = referencing::uri::from_str(current_uri)?;
1587            Ok(retriever
1588                .retrieve(&meta_uri)
1589                .map_err(|e| referencing::Error::unretrievable(current_uri, e))?)
1590        })?;
1591
1592        Ok((first_meta_schema, draft))
1593    }
1594
1595    pub(crate) fn walk_meta_schema_chain(
1596        start_uri: &str,
1597        mut fetch: impl FnMut(&str) -> Result<Value, ValidationError<'static>>,
1598    ) -> Result<Draft, ValidationError<'static>> {
1599        let mut visited = AHashSet::new();
1600        let mut current_uri = start_uri.to_string();
1601
1602        loop {
1603            if !visited.insert(current_uri.clone()) {
1604                return Err(referencing::Error::circular_metaschema(current_uri).into());
1605            }
1606
1607            let meta_schema = fetch(&current_uri)?;
1608            let draft = Draft::default().detect(&meta_schema);
1609
1610            if draft != Draft::Unknown {
1611                return Ok(draft);
1612            }
1613
1614            current_uri = meta_schema
1615                .get("$schema")
1616                .and_then(|s| s.as_str())
1617                .expect("`$schema` must exist when draft is Unknown")
1618                .to_string();
1619        }
1620    }
1621}
1622
1623/// Functionality specific to JSON Schema Draft 4.
1624///
1625/// [![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)
1626///
1627/// This module provides functions for creating validators and performing validation
1628/// according to the JSON Schema Draft 4 specification.
1629///
1630/// # Examples
1631///
1632/// ```rust
1633/// use serde_json::json;
1634///
1635/// let schema = json!({"type": "number", "multipleOf": 2});
1636/// let instance = json!(4);
1637///
1638/// assert!(jsonschema::draft4::is_valid(&schema, &instance));
1639/// ```
1640pub mod draft4 {
1641    use super::{Draft, ValidationError, ValidationOptions, Validator, Value};
1642
1643    /// Create a new JSON Schema validator using Draft 4 specifications.
1644    ///
1645    /// # Examples
1646    ///
1647    /// ```rust
1648    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1649    /// use serde_json::json;
1650    ///
1651    /// let schema = json!({"minimum": 5});
1652    /// let instance = json!(42);
1653    ///
1654    /// let validator = jsonschema::draft4::new(&schema)?;
1655    /// assert!(validator.is_valid(&instance));
1656    /// # Ok(())
1657    /// # }
1658    /// ```
1659    ///
1660    /// # Errors
1661    ///
1662    /// Returns an error if the schema is not a valid Draft 4 document or if referenced resources
1663    /// cannot be resolved.
1664    pub fn new(schema: &Value) -> Result<Validator, ValidationError<'static>> {
1665        options().build(schema)
1666    }
1667    /// Validate an instance against a schema using Draft 4 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::draft4::is_valid(&schema, &valid));
1679    /// assert!(!jsonschema::draft4::is_valid(&schema, &invalid));
1680    /// ```
1681    ///
1682    /// # Panics
1683    ///
1684    /// Panics if `schema` cannot be compiled into a Draft 4 validator.
1685    #[must_use]
1686    pub fn is_valid(schema: &Value, instance: &Value) -> bool {
1687        new(schema).expect("Invalid schema").is_valid(instance)
1688    }
1689    /// Validate an instance against a schema using Draft 4 specifications without creating a validator.
1690    ///
1691    /// # Examples
1692    ///
1693    /// ```rust
1694    /// use serde_json::json;
1695    ///
1696    /// let schema = json!({"minimum": 5});
1697    /// let valid = json!(42);
1698    /// let invalid = json!(3);
1699    ///
1700    /// assert!(jsonschema::draft4::validate(&schema, &valid).is_ok());
1701    /// assert!(jsonschema::draft4::validate(&schema, &invalid).is_err());
1702    /// ```
1703    ///
1704    /// # Errors
1705    ///
1706    /// Returns the first [`ValidationError`] when `instance` violates the schema.
1707    ///
1708    /// # Panics
1709    ///
1710    /// Panics if `schema` cannot be compiled into a Draft 4 validator.
1711    pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
1712        new(schema).expect("Invalid schema").validate(instance)
1713    }
1714    /// Creates a [`ValidationOptions`] builder pre-configured for JSON Schema Draft 4.
1715    ///
1716    /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft4)`.
1717    ///
1718    /// # Examples
1719    ///
1720    /// ```
1721    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1722    /// use serde_json::json;
1723    ///
1724    /// let schema = json!({"type": "string", "format": "ends-with-42"});
1725    /// let validator = jsonschema::draft4::options()
1726    ///     .with_format("ends-with-42", |s| s.ends_with("42"))
1727    ///     .should_validate_formats(true)
1728    ///     .build(&schema)?;
1729    ///
1730    /// assert!(validator.is_valid(&json!("Hello 42")));
1731    /// assert!(!validator.is_valid(&json!("No!")));
1732    /// # Ok(())
1733    /// # }
1734    /// ```
1735    ///
1736    /// See [`ValidationOptions`] for all available configuration options.
1737    #[must_use]
1738    pub fn options() -> ValidationOptions {
1739        crate::options().with_draft(Draft::Draft4)
1740    }
1741
1742    /// Functionality for validating JSON Schema Draft 4 documents.
1743    pub mod meta {
1744        use crate::{meta::MetaValidator, ValidationError};
1745        use serde_json::Value;
1746
1747        /// Returns a handle to the Draft 4 meta-schema validator. Native targets borrow cached
1748        /// statics while `wasm32` builds an owned validator.
1749        #[must_use]
1750        pub fn validator() -> MetaValidator<'static> {
1751            crate::meta::validator_for_draft(super::Draft::Draft4)
1752        }
1753
1754        /// Validate a JSON Schema document against Draft 4 meta-schema and get a `true` if the schema is valid
1755        /// and `false` otherwise.
1756        ///
1757        /// # Examples
1758        ///
1759        /// ```rust
1760        /// use serde_json::json;
1761        ///
1762        /// let schema = json!({
1763        ///     "type": "string",
1764        ///     "maxLength": 5
1765        /// });
1766        /// assert!(jsonschema::draft4::meta::is_valid(&schema));
1767        /// ```
1768        #[must_use]
1769        #[inline]
1770        pub fn is_valid(schema: &Value) -> bool {
1771            validator().as_ref().is_valid(schema)
1772        }
1773
1774        /// Validate a JSON Schema document against Draft 4 meta-schema and return the first error if any.
1775        ///
1776        /// # Examples
1777        ///
1778        /// ```rust
1779        /// use serde_json::json;
1780        ///
1781        /// let schema = json!({
1782        ///     "type": "string",
1783        ///     "maxLength": 5
1784        /// });
1785        /// assert!(jsonschema::draft4::meta::validate(&schema).is_ok());
1786        ///
1787        /// // Invalid schema
1788        /// let invalid_schema = json!({
1789        ///     "type": "invalid_type"
1790        /// });
1791        /// assert!(jsonschema::draft4::meta::validate(&invalid_schema).is_err());
1792        /// ```
1793        ///
1794        /// # Errors
1795        ///
1796        /// Returns the first [`ValidationError`] describing why the schema violates the Draft 4 meta-schema.
1797        #[inline]
1798        pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
1799            validator().as_ref().validate(schema)
1800        }
1801    }
1802}
1803
1804/// Functionality specific to JSON Schema Draft 6.
1805///
1806/// [![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)
1807///
1808/// This module provides functions for creating validators and performing validation
1809/// according to the JSON Schema Draft 6 specification.
1810///
1811/// # Examples
1812///
1813/// ```rust
1814/// use serde_json::json;
1815///
1816/// let schema = json!({"type": "string", "format": "uri"});
1817/// let instance = json!("https://www.example.com");
1818///
1819/// assert!(jsonschema::draft6::is_valid(&schema, &instance));
1820/// ```
1821pub mod draft6 {
1822    use super::{Draft, ValidationError, ValidationOptions, Validator, Value};
1823
1824    /// Create a new JSON Schema validator using Draft 6 specifications.
1825    ///
1826    /// # Examples
1827    ///
1828    /// ```rust
1829    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1830    /// use serde_json::json;
1831    ///
1832    /// let schema = json!({"minimum": 5});
1833    /// let instance = json!(42);
1834    ///
1835    /// let validator = jsonschema::draft6::new(&schema)?;
1836    /// assert!(validator.is_valid(&instance));
1837    /// # Ok(())
1838    /// # }
1839    /// ```
1840    ///
1841    /// # Errors
1842    ///
1843    /// Returns an error if the schema is not a valid Draft 6 document or if referenced resources
1844    /// cannot be resolved.
1845    pub fn new(schema: &Value) -> Result<Validator, ValidationError<'static>> {
1846        options().build(schema)
1847    }
1848    /// Validate an instance against a schema using Draft 6 specifications without creating a validator.
1849    ///
1850    /// # Examples
1851    ///
1852    /// ```rust
1853    /// use serde_json::json;
1854    ///
1855    /// let schema = json!({"minimum": 5});
1856    /// let valid = json!(42);
1857    /// let invalid = json!(3);
1858    ///
1859    /// assert!(jsonschema::draft6::is_valid(&schema, &valid));
1860    /// assert!(!jsonschema::draft6::is_valid(&schema, &invalid));
1861    /// ```
1862    ///
1863    /// # Panics
1864    ///
1865    /// Panics if `schema` cannot be compiled into a Draft 6 validator.
1866    #[must_use]
1867    pub fn is_valid(schema: &Value, instance: &Value) -> bool {
1868        new(schema).expect("Invalid schema").is_valid(instance)
1869    }
1870    /// Validate an instance against a schema using Draft 6 specifications without creating a validator.
1871    ///
1872    /// # Examples
1873    ///
1874    /// ```rust
1875    /// use serde_json::json;
1876    ///
1877    /// let schema = json!({"minimum": 5});
1878    /// let valid = json!(42);
1879    /// let invalid = json!(3);
1880    ///
1881    /// assert!(jsonschema::draft6::validate(&schema, &valid).is_ok());
1882    /// assert!(jsonschema::draft6::validate(&schema, &invalid).is_err());
1883    /// ```
1884    ///
1885    /// # Errors
1886    ///
1887    /// Returns the first [`ValidationError`] when `instance` violates the schema.
1888    ///
1889    /// # Panics
1890    ///
1891    /// Panics if `schema` cannot be compiled into a Draft 6 validator.
1892    pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
1893        new(schema).expect("Invalid schema").validate(instance)
1894    }
1895    /// Creates a [`ValidationOptions`] builder pre-configured for JSON Schema Draft 6.
1896    ///
1897    /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft6)`.
1898    ///
1899    /// # Examples
1900    ///
1901    /// ```
1902    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1903    /// use serde_json::json;
1904    ///
1905    /// let schema = json!({"type": "string", "format": "ends-with-42"});
1906    /// let validator = jsonschema::draft6::options()
1907    ///     .with_format("ends-with-42", |s| s.ends_with("42"))
1908    ///     .should_validate_formats(true)
1909    ///     .build(&schema)?;
1910    ///
1911    /// assert!(validator.is_valid(&json!("Hello 42")));
1912    /// assert!(!validator.is_valid(&json!("No!")));
1913    /// # Ok(())
1914    /// # }
1915    /// ```
1916    ///
1917    /// See [`ValidationOptions`] for all available configuration options.
1918    #[must_use]
1919    pub fn options() -> ValidationOptions {
1920        crate::options().with_draft(Draft::Draft6)
1921    }
1922
1923    /// Functionality for validating JSON Schema Draft 6 documents.
1924    pub mod meta {
1925        use crate::{meta::MetaValidator, ValidationError};
1926        use serde_json::Value;
1927
1928        /// Returns a handle to the Draft 6 meta-schema validator. Native targets borrow cached
1929        /// statics while `wasm32` builds an owned validator.
1930        #[must_use]
1931        pub fn validator() -> MetaValidator<'static> {
1932            crate::meta::validator_for_draft(super::Draft::Draft6)
1933        }
1934
1935        /// Validate a JSON Schema document against Draft 6 meta-schema and get a `true` if the schema is valid
1936        /// and `false` otherwise.
1937        ///
1938        /// # Examples
1939        ///
1940        /// ```rust
1941        /// use serde_json::json;
1942        ///
1943        /// let schema = json!({
1944        ///     "type": "string",
1945        ///     "maxLength": 5
1946        /// });
1947        /// assert!(jsonschema::draft6::meta::is_valid(&schema));
1948        /// ```
1949        #[must_use]
1950        #[inline]
1951        pub fn is_valid(schema: &Value) -> bool {
1952            validator().as_ref().is_valid(schema)
1953        }
1954
1955        /// Validate a JSON Schema document against Draft 6 meta-schema and return the first error if any.
1956        ///
1957        /// # Examples
1958        ///
1959        /// ```rust
1960        /// use serde_json::json;
1961        ///
1962        /// let schema = json!({
1963        ///     "type": "string",
1964        ///     "maxLength": 5
1965        /// });
1966        /// assert!(jsonschema::draft6::meta::validate(&schema).is_ok());
1967        ///
1968        /// // Invalid schema
1969        /// let invalid_schema = json!({
1970        ///     "type": "invalid_type"
1971        /// });
1972        /// assert!(jsonschema::draft6::meta::validate(&invalid_schema).is_err());
1973        /// ```
1974        ///
1975        /// # Errors
1976        ///
1977        /// Returns the first [`ValidationError`] describing why the schema violates the Draft 6 meta-schema.
1978        #[inline]
1979        pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
1980            validator().as_ref().validate(schema)
1981        }
1982    }
1983}
1984
1985/// Functionality specific to JSON Schema Draft 7.
1986///
1987/// [![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)
1988///
1989/// This module provides functions for creating validators and performing validation
1990/// according to the JSON Schema Draft 7 specification.
1991///
1992/// # Examples
1993///
1994/// ```rust
1995/// use serde_json::json;
1996///
1997/// let schema = json!({"type": "string", "pattern": "^[a-zA-Z0-9]+$"});
1998/// let instance = json!("abc123");
1999///
2000/// assert!(jsonschema::draft7::is_valid(&schema, &instance));
2001/// ```
2002pub mod draft7 {
2003    use super::{Draft, ValidationError, ValidationOptions, Validator, Value};
2004
2005    /// Create a new JSON Schema validator using Draft 7 specifications.
2006    ///
2007    /// # Examples
2008    ///
2009    /// ```rust
2010    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2011    /// use serde_json::json;
2012    ///
2013    /// let schema = json!({"minimum": 5});
2014    /// let instance = json!(42);
2015    ///
2016    /// let validator = jsonschema::draft7::new(&schema)?;
2017    /// assert!(validator.is_valid(&instance));
2018    /// # Ok(())
2019    /// # }
2020    /// ```
2021    ///
2022    /// # Errors
2023    ///
2024    /// Returns an error if the schema is not a valid Draft 7 document or if referenced resources
2025    /// cannot be resolved.
2026    pub fn new(schema: &Value) -> Result<Validator, ValidationError<'static>> {
2027        options().build(schema)
2028    }
2029    /// Validate an instance against a schema using Draft 7 specifications without creating a validator.
2030    ///
2031    /// # Examples
2032    ///
2033    /// ```rust
2034    /// use serde_json::json;
2035    ///
2036    /// let schema = json!({"minimum": 5});
2037    /// let valid = json!(42);
2038    /// let invalid = json!(3);
2039    ///
2040    /// assert!(jsonschema::draft7::is_valid(&schema, &valid));
2041    /// assert!(!jsonschema::draft7::is_valid(&schema, &invalid));
2042    /// ```
2043    ///
2044    /// # Panics
2045    ///
2046    /// Panics if `schema` cannot be compiled into a Draft 7 validator.
2047    #[must_use]
2048    pub fn is_valid(schema: &Value, instance: &Value) -> bool {
2049        new(schema).expect("Invalid schema").is_valid(instance)
2050    }
2051    /// Validate an instance against a schema using Draft 7 specifications without creating a validator.
2052    ///
2053    /// # Examples
2054    ///
2055    /// ```rust
2056    /// use serde_json::json;
2057    ///
2058    /// let schema = json!({"minimum": 5});
2059    /// let valid = json!(42);
2060    /// let invalid = json!(3);
2061    ///
2062    /// assert!(jsonschema::draft7::validate(&schema, &valid).is_ok());
2063    /// assert!(jsonschema::draft7::validate(&schema, &invalid).is_err());
2064    /// ```
2065    ///
2066    /// # Errors
2067    ///
2068    /// Returns the first [`ValidationError`] when `instance` violates the schema.
2069    ///
2070    /// # Panics
2071    ///
2072    /// Panics if `schema` cannot be compiled into a Draft 7 validator.
2073    pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
2074        new(schema).expect("Invalid schema").validate(instance)
2075    }
2076    /// Creates a [`ValidationOptions`] builder pre-configured for JSON Schema Draft 7.
2077    ///
2078    /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft7)`.
2079    ///
2080    /// # Examples
2081    ///
2082    /// ```
2083    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2084    /// use serde_json::json;
2085    ///
2086    /// let schema = json!({"type": "string", "format": "ends-with-42"});
2087    /// let validator = jsonschema::draft7::options()
2088    ///     .with_format("ends-with-42", |s| s.ends_with("42"))
2089    ///     .should_validate_formats(true)
2090    ///     .build(&schema)?;
2091    ///
2092    /// assert!(validator.is_valid(&json!("Hello 42")));
2093    /// assert!(!validator.is_valid(&json!("No!")));
2094    /// # Ok(())
2095    /// # }
2096    /// ```
2097    ///
2098    /// See [`ValidationOptions`] for all available configuration options.
2099    #[must_use]
2100    pub fn options() -> ValidationOptions {
2101        crate::options().with_draft(Draft::Draft7)
2102    }
2103
2104    /// Functionality for validating JSON Schema Draft 7 documents.
2105    pub mod meta {
2106        use crate::{meta::MetaValidator, ValidationError};
2107        use serde_json::Value;
2108
2109        /// Returns a handle to the Draft 7 meta-schema validator. Native targets borrow cached
2110        /// statics while `wasm32` builds an owned validator.
2111        #[must_use]
2112        pub fn validator() -> MetaValidator<'static> {
2113            crate::meta::validator_for_draft(super::Draft::Draft7)
2114        }
2115
2116        /// Validate a JSON Schema document against Draft 7 meta-schema and get a `true` if the schema is valid
2117        /// and `false` otherwise.
2118        ///
2119        /// # Examples
2120        ///
2121        /// ```rust
2122        /// use serde_json::json;
2123        ///
2124        /// let schema = json!({
2125        ///     "type": "string",
2126        ///     "maxLength": 5
2127        /// });
2128        /// assert!(jsonschema::draft7::meta::is_valid(&schema));
2129        /// ```
2130        #[must_use]
2131        #[inline]
2132        pub fn is_valid(schema: &Value) -> bool {
2133            validator().as_ref().is_valid(schema)
2134        }
2135
2136        /// Validate a JSON Schema document against Draft 7 meta-schema and return the first error if any.
2137        ///
2138        /// # Examples
2139        ///
2140        /// ```rust
2141        /// use serde_json::json;
2142        ///
2143        /// let schema = json!({
2144        ///     "type": "string",
2145        ///     "maxLength": 5
2146        /// });
2147        /// assert!(jsonschema::draft7::meta::validate(&schema).is_ok());
2148        ///
2149        /// // Invalid schema
2150        /// let invalid_schema = json!({
2151        ///     "type": "invalid_type"
2152        /// });
2153        /// assert!(jsonschema::draft7::meta::validate(&invalid_schema).is_err());
2154        /// ```
2155        ///
2156        /// # Errors
2157        ///
2158        /// Returns the first [`ValidationError`] describing why the schema violates the Draft 7 meta-schema.
2159        #[inline]
2160        pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
2161            validator().as_ref().validate(schema)
2162        }
2163    }
2164}
2165
2166/// Functionality specific to JSON Schema Draft 2019-09.
2167///
2168/// [![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)
2169///
2170/// This module provides functions for creating validators and performing validation
2171/// according to the JSON Schema Draft 2019-09 specification.
2172///
2173/// # Examples
2174///
2175/// ```rust
2176/// use serde_json::json;
2177///
2178/// let schema = json!({"type": "array", "minItems": 2, "uniqueItems": true});
2179/// let instance = json!([1, 2]);
2180///
2181/// assert!(jsonschema::draft201909::is_valid(&schema, &instance));
2182/// ```
2183pub mod draft201909 {
2184    use super::{Draft, ValidationError, ValidationOptions, Validator, Value};
2185
2186    /// Create a new JSON Schema validator using Draft 2019-09 specifications.
2187    ///
2188    /// # Examples
2189    ///
2190    /// ```rust
2191    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2192    /// use serde_json::json;
2193    ///
2194    /// let schema = json!({"minimum": 5});
2195    /// let instance = json!(42);
2196    ///
2197    /// let validator = jsonschema::draft201909::new(&schema)?;
2198    /// assert!(validator.is_valid(&instance));
2199    /// # Ok(())
2200    /// # }
2201    /// ```
2202    ///
2203    /// # Errors
2204    ///
2205    /// Returns an error if the schema is not a valid Draft 2019-09 document or if referenced resources
2206    /// cannot be resolved.
2207    pub fn new(schema: &Value) -> Result<Validator, ValidationError<'static>> {
2208        options().build(schema)
2209    }
2210    /// Validate an instance against a schema using Draft 2019-09 specifications without creating a validator.
2211    ///
2212    /// # Examples
2213    ///
2214    /// ```rust
2215    /// use serde_json::json;
2216    ///
2217    /// let schema = json!({"minimum": 5});
2218    /// let valid = json!(42);
2219    /// let invalid = json!(3);
2220    ///
2221    /// assert!(jsonschema::draft201909::is_valid(&schema, &valid));
2222    /// assert!(!jsonschema::draft201909::is_valid(&schema, &invalid));
2223    /// ```
2224    ///
2225    /// # Panics
2226    ///
2227    /// Panics if `schema` cannot be compiled into a Draft 2019-09 validator.
2228    #[must_use]
2229    pub fn is_valid(schema: &Value, instance: &Value) -> bool {
2230        new(schema).expect("Invalid schema").is_valid(instance)
2231    }
2232    /// Validate an instance against a schema using Draft 2019-09 specifications without creating a validator.
2233    ///
2234    /// # Examples
2235    ///
2236    /// ```rust
2237    /// use serde_json::json;
2238    ///
2239    /// let schema = json!({"minimum": 5});
2240    /// let valid = json!(42);
2241    /// let invalid = json!(3);
2242    ///
2243    /// assert!(jsonschema::draft201909::validate(&schema, &valid).is_ok());
2244    /// assert!(jsonschema::draft201909::validate(&schema, &invalid).is_err());
2245    /// ```
2246    ///
2247    /// # Errors
2248    ///
2249    /// Returns the first [`ValidationError`] when `instance` violates the schema.
2250    ///
2251    /// # Panics
2252    ///
2253    /// Panics if `schema` cannot be compiled into a Draft 2019-09 validator.
2254    pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
2255        new(schema).expect("Invalid schema").validate(instance)
2256    }
2257    /// Creates a [`ValidationOptions`] builder pre-configured for JSON Schema Draft 2019-09.
2258    ///
2259    /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft201909)`.
2260    ///
2261    /// # Examples
2262    ///
2263    /// ```
2264    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2265    /// use serde_json::json;
2266    ///
2267    /// let schema = json!({"type": "string", "format": "ends-with-42"});
2268    /// let validator = jsonschema::draft201909::options()
2269    ///     .with_format("ends-with-42", |s| s.ends_with("42"))
2270    ///     .should_validate_formats(true)
2271    ///     .build(&schema)?;
2272    ///
2273    /// assert!(validator.is_valid(&json!("Hello 42")));
2274    /// assert!(!validator.is_valid(&json!("No!")));
2275    /// # Ok(())
2276    /// # }
2277    /// ```
2278    ///
2279    /// See [`ValidationOptions`] for all available configuration options.
2280    #[must_use]
2281    pub fn options() -> ValidationOptions {
2282        crate::options().with_draft(Draft::Draft201909)
2283    }
2284
2285    /// Functionality for validating JSON Schema Draft 2019-09 documents.
2286    pub mod meta {
2287        use crate::{meta::MetaValidator, ValidationError};
2288        use serde_json::Value;
2289
2290        /// Returns a handle to the Draft 2019-09 meta-schema validator. Native targets borrow cached
2291        /// statics while `wasm32` builds an owned validator.
2292        #[must_use]
2293        pub fn validator() -> MetaValidator<'static> {
2294            crate::meta::validator_for_draft(super::Draft::Draft201909)
2295        }
2296        /// Validate a JSON Schema document against Draft 2019-09 meta-schema and get a `true` if the schema is valid
2297        /// and `false` otherwise.
2298        ///
2299        /// # Examples
2300        ///
2301        /// ```rust
2302        /// use serde_json::json;
2303        ///
2304        /// let schema = json!({
2305        ///     "type": "string",
2306        ///     "maxLength": 5
2307        /// });
2308        /// assert!(jsonschema::draft201909::meta::is_valid(&schema));
2309        /// ```
2310        #[must_use]
2311        #[inline]
2312        pub fn is_valid(schema: &Value) -> bool {
2313            validator().as_ref().is_valid(schema)
2314        }
2315
2316        /// Validate a JSON Schema document against Draft 2019-09 meta-schema and return the first error if any.
2317        ///
2318        /// # Examples
2319        ///
2320        /// ```rust
2321        /// use serde_json::json;
2322        ///
2323        /// let schema = json!({
2324        ///     "type": "string",
2325        ///     "maxLength": 5
2326        /// });
2327        /// assert!(jsonschema::draft201909::meta::validate(&schema).is_ok());
2328        ///
2329        /// // Invalid schema
2330        /// let invalid_schema = json!({
2331        ///     "type": "invalid_type"
2332        /// });
2333        /// assert!(jsonschema::draft201909::meta::validate(&invalid_schema).is_err());
2334        /// ```
2335        ///
2336        /// # Errors
2337        ///
2338        /// Returns the first [`ValidationError`] describing why the schema violates the Draft 2019-09 meta-schema.
2339        #[inline]
2340        pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
2341            validator().as_ref().validate(schema)
2342        }
2343    }
2344}
2345
2346/// Functionality specific to JSON Schema Draft 2020-12.
2347///
2348/// [![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)
2349///
2350/// This module provides functions for creating validators and performing validation
2351/// according to the JSON Schema Draft 2020-12 specification.
2352///
2353/// # Examples
2354///
2355/// ```rust
2356/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2357/// use serde_json::json;
2358///
2359/// let schema = json!({"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]});
2360/// let instance = json!({"name": "John Doe"});
2361///
2362/// assert!(jsonschema::draft202012::is_valid(&schema, &instance));
2363/// # Ok(())
2364/// # }
2365/// ```
2366pub mod draft202012 {
2367    use super::{Draft, ValidationError, ValidationOptions, Validator, Value};
2368
2369    /// Create a new JSON Schema validator using Draft 2020-12 specifications.
2370    ///
2371    /// # Examples
2372    ///
2373    /// ```rust
2374    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2375    /// use serde_json::json;
2376    ///
2377    /// let schema = json!({"minimum": 5});
2378    /// let instance = json!(42);
2379    ///
2380    /// let validator = jsonschema::draft202012::new(&schema)?;
2381    /// assert!(validator.is_valid(&instance));
2382    /// # Ok(())
2383    /// # }
2384    /// ```
2385    ///
2386    /// # Errors
2387    ///
2388    /// Returns an error if the schema is not a valid Draft 2020-12 document or if referenced resources
2389    /// cannot be resolved.
2390    pub fn new(schema: &Value) -> Result<Validator, ValidationError<'static>> {
2391        options().build(schema)
2392    }
2393    /// Validate an instance against a schema using Draft 2020-12 specifications without creating a validator.
2394    ///
2395    /// # Examples
2396    ///
2397    /// ```rust
2398    /// use serde_json::json;
2399    ///
2400    /// let schema = json!({"minimum": 5});
2401    /// let valid = json!(42);
2402    /// let invalid = json!(3);
2403    ///
2404    /// assert!(jsonschema::draft202012::is_valid(&schema, &valid));
2405    /// assert!(!jsonschema::draft202012::is_valid(&schema, &invalid));
2406    /// ```
2407    ///
2408    /// # Panics
2409    ///
2410    /// Panics if `schema` cannot be compiled into a Draft 2020-12 validator.
2411    #[must_use]
2412    pub fn is_valid(schema: &Value, instance: &Value) -> bool {
2413        new(schema).expect("Invalid schema").is_valid(instance)
2414    }
2415    /// Validate an instance against a schema using Draft 2020-12 specifications without creating a validator.
2416    ///
2417    /// # Examples
2418    ///
2419    /// ```rust
2420    /// use serde_json::json;
2421    ///
2422    /// let schema = json!({"minimum": 5});
2423    /// let valid = json!(42);
2424    /// let invalid = json!(3);
2425    ///
2426    /// assert!(jsonschema::draft202012::validate(&schema, &valid).is_ok());
2427    /// assert!(jsonschema::draft202012::validate(&schema, &invalid).is_err());
2428    /// ```
2429    ///
2430    /// # Errors
2431    ///
2432    /// Returns the first [`ValidationError`] when `instance` violates the schema.
2433    ///
2434    /// # Panics
2435    ///
2436    /// Panics if `schema` cannot be compiled into a Draft 2020-12 validator.
2437    pub fn validate<'i>(schema: &Value, instance: &'i Value) -> Result<(), ValidationError<'i>> {
2438        new(schema).expect("Invalid schema").validate(instance)
2439    }
2440    /// Creates a [`ValidationOptions`] builder pre-configured for JSON Schema Draft 2020-12.
2441    ///
2442    /// This function provides a shorthand for `jsonschema::options().with_draft(Draft::Draft202012)`.
2443    ///
2444    /// # Examples
2445    ///
2446    /// ```
2447    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2448    /// use serde_json::json;
2449    ///
2450    /// let schema = json!({"type": "string", "format": "ends-with-42"});
2451    /// let validator = jsonschema::draft202012::options()
2452    ///     .with_format("ends-with-42", |s| s.ends_with("42"))
2453    ///     .should_validate_formats(true)
2454    ///     .build(&schema)?;
2455    ///
2456    /// assert!(validator.is_valid(&json!("Hello 42")));
2457    /// assert!(!validator.is_valid(&json!("No!")));
2458    /// # Ok(())
2459    /// # }
2460    /// ```
2461    ///
2462    /// See [`ValidationOptions`] for all available configuration options.
2463    #[must_use]
2464    pub fn options() -> ValidationOptions {
2465        crate::options().with_draft(Draft::Draft202012)
2466    }
2467
2468    /// Functionality for validating JSON Schema Draft 2020-12 documents.
2469    pub mod meta {
2470        use crate::{meta::MetaValidator, ValidationError};
2471        use serde_json::Value;
2472
2473        /// Returns a handle to the Draft 2020-12 meta-schema validator. Native targets borrow
2474        /// cached statics while `wasm32` builds an owned validator.
2475        #[must_use]
2476        pub fn validator() -> MetaValidator<'static> {
2477            crate::meta::validator_for_draft(super::Draft::Draft202012)
2478        }
2479
2480        /// Validate a JSON Schema document against Draft 2020-12 meta-schema and get a `true` if the schema is valid
2481        /// and `false` otherwise.
2482        ///
2483        /// # Examples
2484        ///
2485        /// ```rust
2486        /// use serde_json::json;
2487        ///
2488        /// let schema = json!({
2489        ///     "type": "string",
2490        ///     "maxLength": 5
2491        /// });
2492        /// assert!(jsonschema::draft202012::meta::is_valid(&schema));
2493        /// ```
2494        #[must_use]
2495        #[inline]
2496        pub fn is_valid(schema: &Value) -> bool {
2497            validator().as_ref().is_valid(schema)
2498        }
2499
2500        /// Validate a JSON Schema document against Draft 2020-12 meta-schema and return the first error if any.
2501        ///
2502        /// # Examples
2503        ///
2504        /// ```rust
2505        /// use serde_json::json;
2506        ///
2507        /// let schema = json!({
2508        ///     "type": "string",
2509        ///     "maxLength": 5
2510        /// });
2511        /// assert!(jsonschema::draft202012::meta::validate(&schema).is_ok());
2512        ///
2513        /// // Invalid schema
2514        /// let invalid_schema = json!({
2515        ///     "type": "invalid_type"
2516        /// });
2517        /// assert!(jsonschema::draft202012::meta::validate(&invalid_schema).is_err());
2518        /// ```
2519        ///
2520        /// # Errors
2521        ///
2522        /// Returns the first [`ValidationError`] describing why the schema violates the Draft 2020-12 meta-schema.
2523        #[inline]
2524        pub fn validate(schema: &Value) -> Result<(), ValidationError<'_>> {
2525            validator().as_ref().validate(schema)
2526        }
2527    }
2528}
2529
2530#[cfg(test)]
2531pub(crate) mod tests_util {
2532    use super::Validator;
2533    use crate::ValidationError;
2534    use serde_json::Value;
2535
2536    #[track_caller]
2537    pub(crate) fn is_not_valid_with(validator: &Validator, instance: &Value) {
2538        assert!(
2539            !validator.is_valid(instance),
2540            "{instance} should not be valid (via is_valid)",
2541        );
2542        assert!(
2543            validator.validate(instance).is_err(),
2544            "{instance} should not be valid (via validate)",
2545        );
2546        assert!(
2547            validator.iter_errors(instance).next().is_some(),
2548            "{instance} should not be valid (via validate)",
2549        );
2550        let evaluation = validator.evaluate(instance);
2551        assert!(
2552            !evaluation.flag().valid,
2553            "{instance} should not be valid (via evaluate)",
2554        );
2555    }
2556
2557    #[track_caller]
2558    pub(crate) fn is_not_valid(schema: &Value, instance: &Value) {
2559        let validator = crate::options()
2560            .should_validate_formats(true)
2561            .build(schema)
2562            .expect("Invalid schema");
2563        is_not_valid_with(&validator, instance);
2564    }
2565
2566    pub(crate) fn expect_errors(schema: &Value, instance: &Value, errors: &[&str]) {
2567        let mut actual = crate::validator_for(schema)
2568            .expect("Should be a valid schema")
2569            .iter_errors(instance)
2570            .map(|e| e.to_string())
2571            .collect::<Vec<String>>();
2572        actual.sort();
2573        assert_eq!(actual, errors);
2574    }
2575
2576    #[track_caller]
2577    pub(crate) fn is_valid_with(validator: &Validator, instance: &Value) {
2578        if let Some(first) = validator.iter_errors(instance).next() {
2579            panic!(
2580                "{} should be valid (via validate). Error: {} at {}",
2581                instance,
2582                first,
2583                first.instance_path()
2584            );
2585        }
2586        assert!(
2587            validator.is_valid(instance),
2588            "{instance} should be valid (via is_valid)",
2589        );
2590        assert!(
2591            validator.validate(instance).is_ok(),
2592            "{instance} should be valid (via is_valid)",
2593        );
2594        let evaluation = validator.evaluate(instance);
2595        assert!(
2596            evaluation.flag().valid,
2597            "{instance} should be valid (via evaluate)",
2598        );
2599    }
2600
2601    #[track_caller]
2602    pub(crate) fn is_valid(schema: &Value, instance: &Value) {
2603        let validator = crate::options()
2604            .should_validate_formats(true)
2605            .build(schema)
2606            .expect("Invalid schema");
2607        is_valid_with(&validator, instance);
2608    }
2609
2610    #[track_caller]
2611    pub(crate) fn validate(schema: &Value, instance: &Value) -> ValidationError<'static> {
2612        let validator = crate::options()
2613            .should_validate_formats(true)
2614            .build(schema)
2615            .expect("Invalid schema");
2616        let err = validator
2617            .validate(instance)
2618            .expect_err("Should be an error")
2619            .to_owned();
2620        err
2621    }
2622
2623    #[track_caller]
2624    pub(crate) fn assert_schema_location(schema: &Value, instance: &Value, expected: &str) {
2625        let error = validate(schema, instance);
2626        assert_eq!(error.schema_path().as_str(), expected);
2627    }
2628
2629    #[track_caller]
2630    pub(crate) fn assert_evaluation_path(schema: &Value, instance: &Value, expected: &str) {
2631        let error = validate(schema, instance);
2632        assert_eq!(error.evaluation_path().as_str(), expected);
2633    }
2634
2635    #[track_caller]
2636    pub(crate) fn assert_locations(schema: &Value, instance: &Value, expected: &[&str]) {
2637        let validator = crate::validator_for(schema).unwrap();
2638        let mut errors: Vec<_> = validator
2639            .iter_errors(instance)
2640            .map(|error| error.schema_path().as_str().to_string())
2641            .collect();
2642        errors.sort();
2643        for (error, location) in errors.into_iter().zip(expected) {
2644            assert_eq!(error, *location);
2645        }
2646    }
2647
2648    #[track_caller]
2649    pub(crate) fn assert_keyword_location(
2650        validator: &Validator,
2651        instance: &Value,
2652        instance_pointer: &str,
2653        keyword_pointer: &str,
2654    ) {
2655        fn pointer_from_schema_location(location: &str) -> &str {
2656            location
2657                .split_once('#')
2658                .map_or(location, |(_, fragment)| fragment)
2659        }
2660
2661        let evaluation = validator.evaluate(instance);
2662        let serialized =
2663            serde_json::to_value(evaluation.list()).expect("List output should be serializable");
2664        let details = serialized
2665            .get("details")
2666            .and_then(|value| value.as_array())
2667            .expect("List output must contain details");
2668        let mut available = Vec::new();
2669        for entry in details {
2670            let Some(instance_location) = entry
2671                .get("instanceLocation")
2672                .and_then(|value| value.as_str())
2673            else {
2674                continue;
2675            };
2676            if instance_location != instance_pointer {
2677                continue;
2678            }
2679            let schema_location = entry
2680                .get("schemaLocation")
2681                .and_then(|value| value.as_str())
2682                .unwrap_or("");
2683            let pointer = pointer_from_schema_location(schema_location);
2684            if pointer == keyword_pointer {
2685                return;
2686            }
2687            available.push(pointer.to_string());
2688        }
2689
2690        panic!(
2691            "No annotation for instance pointer `{instance_pointer}` with keyword location `{keyword_pointer}`. Available keyword locations for pointer: {available:?}"
2692        );
2693    }
2694
2695    #[track_caller]
2696    pub(crate) fn is_valid_with_draft4(schema: &Value, instance: &Value) {
2697        let validator = crate::options()
2698            .with_draft(crate::Draft::Draft4)
2699            .should_validate_formats(true)
2700            .build(schema)
2701            .expect("Invalid schema");
2702        is_valid_with(&validator, instance);
2703    }
2704
2705    #[track_caller]
2706    pub(crate) fn is_not_valid_with_draft4(schema: &Value, instance: &Value) {
2707        let validator = crate::options()
2708            .with_draft(crate::Draft::Draft4)
2709            .should_validate_formats(true)
2710            .build(schema)
2711            .expect("Invalid schema");
2712        is_not_valid_with(&validator, instance);
2713    }
2714}
2715
2716#[cfg(test)]
2717mod tests {
2718    use crate::{validator_for, ValidationError};
2719    use referencing::{Registry, Resource};
2720
2721    use super::Draft;
2722    use serde_json::json;
2723    use test_case::test_case;
2724
2725    #[test_case(crate::is_valid ; "autodetect")]
2726    #[test_case(crate::draft4::is_valid ; "draft4")]
2727    #[test_case(crate::draft6::is_valid ; "draft6")]
2728    #[test_case(crate::draft7::is_valid ; "draft7")]
2729    #[test_case(crate::draft201909::is_valid ; "draft201909")]
2730    #[test_case(crate::draft202012::is_valid ; "draft202012")]
2731    fn test_is_valid(is_valid_fn: fn(&serde_json::Value, &serde_json::Value) -> bool) {
2732        let schema = json!({
2733            "type": "object",
2734            "properties": {
2735                "name": {"type": "string"},
2736                "age": {"type": "integer", "minimum": 0}
2737            },
2738            "required": ["name"]
2739        });
2740
2741        let valid_instance = json!({
2742            "name": "John Doe",
2743            "age": 30
2744        });
2745
2746        let invalid_instance = json!({
2747            "age": -5
2748        });
2749
2750        assert!(is_valid_fn(&schema, &valid_instance));
2751        assert!(!is_valid_fn(&schema, &invalid_instance));
2752    }
2753
2754    #[test_case(crate::validate ; "autodetect")]
2755    #[test_case(crate::draft4::validate ; "draft4")]
2756    #[test_case(crate::draft6::validate ; "draft6")]
2757    #[test_case(crate::draft7::validate ; "draft7")]
2758    #[test_case(crate::draft201909::validate ; "draft201909")]
2759    #[test_case(crate::draft202012::validate ; "draft202012")]
2760    fn test_validate(
2761        validate_fn: for<'i> fn(
2762            &serde_json::Value,
2763            &'i serde_json::Value,
2764        ) -> Result<(), ValidationError<'i>>,
2765    ) {
2766        let schema = json!({
2767            "type": "object",
2768            "properties": {
2769                "name": {"type": "string"},
2770                "age": {"type": "integer", "minimum": 0}
2771            },
2772            "required": ["name"]
2773        });
2774
2775        let valid_instance = json!({
2776            "name": "John Doe",
2777            "age": 30
2778        });
2779
2780        let invalid_instance = json!({
2781            "age": -5
2782        });
2783
2784        assert!(validate_fn(&schema, &valid_instance).is_ok());
2785        assert!(validate_fn(&schema, &invalid_instance).is_err());
2786    }
2787
2788    #[test]
2789    fn test_evaluate() {
2790        let schema = json!({
2791            "type": "object",
2792            "properties": {
2793                "name": {"type": "string"},
2794                "age": {"type": "integer", "minimum": 0}
2795            },
2796            "required": ["name"]
2797        });
2798
2799        let valid_instance = json!({
2800            "name": "John Doe",
2801            "age": 30
2802        });
2803
2804        let invalid_instance = json!({
2805            "age": -5
2806        });
2807
2808        let valid_eval = crate::evaluate(&schema, &valid_instance);
2809        assert!(valid_eval.flag().valid);
2810
2811        let invalid_eval = crate::evaluate(&schema, &invalid_instance);
2812        assert!(!invalid_eval.flag().valid);
2813        let errors: Vec<_> = invalid_eval.iter_errors().collect();
2814        assert!(!errors.is_empty());
2815    }
2816
2817    #[test_case(crate::meta::validate, crate::meta::is_valid ; "autodetect")]
2818    #[test_case(crate::draft4::meta::validate, crate::draft4::meta::is_valid ; "draft4")]
2819    #[test_case(crate::draft6::meta::validate, crate::draft6::meta::is_valid ; "draft6")]
2820    #[test_case(crate::draft7::meta::validate, crate::draft7::meta::is_valid ; "draft7")]
2821    #[test_case(crate::draft201909::meta::validate, crate::draft201909::meta::is_valid ; "draft201909")]
2822    #[test_case(crate::draft202012::meta::validate, crate::draft202012::meta::is_valid ; "draft202012")]
2823    fn test_meta_validation(
2824        validate_fn: fn(&serde_json::Value) -> Result<(), ValidationError>,
2825        is_valid_fn: fn(&serde_json::Value) -> bool,
2826    ) {
2827        let valid = json!({
2828            "type": "object",
2829            "properties": {
2830                "name": {"type": "string"},
2831                "age": {"type": "integer", "minimum": 0}
2832            },
2833            "required": ["name"]
2834        });
2835
2836        let invalid = json!({
2837            "type": "invalid_type",
2838            "minimum": "not_a_number",
2839            "required": true  // should be an array
2840        });
2841
2842        assert!(validate_fn(&valid).is_ok());
2843        assert!(validate_fn(&invalid).is_err());
2844        assert!(is_valid_fn(&valid));
2845        assert!(!is_valid_fn(&invalid));
2846    }
2847
2848    #[test]
2849    fn test_exclusive_minimum_across_drafts() {
2850        // In Draft 4, exclusiveMinimum is a boolean modifier for minimum
2851        let draft4_schema = json!({
2852            "$schema": "http://json-schema.org/draft-04/schema#",
2853            "minimum": 5,
2854            "exclusiveMinimum": true
2855        });
2856        assert!(crate::meta::is_valid(&draft4_schema));
2857        assert!(crate::meta::validate(&draft4_schema).is_ok());
2858
2859        // This is invalid in Draft 4 (exclusiveMinimum must be boolean)
2860        let invalid_draft4 = json!({
2861            "$schema": "http://json-schema.org/draft-04/schema#",
2862            "exclusiveMinimum": 5
2863        });
2864        assert!(!crate::meta::is_valid(&invalid_draft4));
2865        assert!(crate::meta::validate(&invalid_draft4).is_err());
2866
2867        // In Draft 6 and later, exclusiveMinimum is a numeric value
2868        let drafts = [
2869            "http://json-schema.org/draft-06/schema#",
2870            "http://json-schema.org/draft-07/schema#",
2871            "https://json-schema.org/draft/2019-09/schema",
2872            "https://json-schema.org/draft/2020-12/schema",
2873        ];
2874
2875        for uri in drafts {
2876            // Valid in Draft 6+ (numeric exclusiveMinimum)
2877            let valid_schema = json!({
2878                "$schema": uri,
2879                "exclusiveMinimum": 5
2880            });
2881            assert!(
2882                crate::meta::is_valid(&valid_schema),
2883                "Schema should be valid for {uri}"
2884            );
2885            assert!(
2886                crate::meta::validate(&valid_schema).is_ok(),
2887                "Schema validation should succeed for {uri}",
2888            );
2889
2890            // Invalid in Draft 6+ (can't use boolean with minimum)
2891            let invalid_schema = json!({
2892                "$schema": uri,
2893                "minimum": 5,
2894                "exclusiveMinimum": true
2895            });
2896            assert!(
2897                !crate::meta::is_valid(&invalid_schema),
2898                "Schema should be invalid for {uri}",
2899            );
2900            assert!(
2901                crate::meta::validate(&invalid_schema).is_err(),
2902                "Schema validation should fail for {uri}",
2903            );
2904        }
2905    }
2906
2907    #[test_case(
2908        "http://json-schema.org/draft-04/schema#",
2909        true,
2910        5,
2911        true ; "draft4 valid"
2912    )]
2913    #[test_case(
2914        "http://json-schema.org/draft-04/schema#",
2915        5,
2916        true,
2917        false ; "draft4 invalid"
2918    )]
2919    #[test_case(
2920        "http://json-schema.org/draft-06/schema#",
2921        5,
2922        true,
2923        false ; "draft6 invalid"
2924    )]
2925    #[test_case(
2926        "http://json-schema.org/draft-07/schema#",
2927        5,
2928        true,
2929        false ; "draft7 invalid"
2930    )]
2931    #[test_case(
2932        "https://json-schema.org/draft/2019-09/schema",
2933        5,
2934        true,
2935        false ; "draft2019-09 invalid"
2936    )]
2937    #[test_case(
2938        "https://json-schema.org/draft/2020-12/schema",
2939        5,
2940        true,
2941        false ; "draft2020-12 invalid"
2942    )]
2943    fn test_exclusive_minimum_detection(
2944        schema_uri: &str,
2945        exclusive_minimum: impl Into<serde_json::Value>,
2946        minimum: impl Into<serde_json::Value>,
2947        expected: bool,
2948    ) {
2949        let schema = json!({
2950            "$schema": schema_uri,
2951            "minimum": minimum.into(),
2952            "exclusiveMinimum": exclusive_minimum.into()
2953        });
2954
2955        let is_valid_result = crate::meta::is_valid(&schema);
2956        assert_eq!(is_valid_result, expected);
2957
2958        let validate_result = crate::meta::validate(&schema);
2959        assert_eq!(validate_result.is_ok(), expected);
2960    }
2961
2962    #[test]
2963    fn test_invalid_schema_uri() {
2964        let schema = json!({
2965            "$schema": "invalid-uri",
2966            "type": "string"
2967        });
2968
2969        let result = crate::options().without_schema_validation().build(&schema);
2970
2971        assert!(result.is_err());
2972        let error = result.unwrap_err();
2973        assert!(error.to_string().contains("Unknown meta-schema"));
2974        assert!(error.to_string().contains("invalid-uri"));
2975    }
2976
2977    #[test]
2978    fn test_invalid_schema_keyword() {
2979        let schema = json!({
2980            // Note `htt`, not `http`
2981            "$schema": "htt://json-schema.org/draft-07/schema",
2982            "type": "string"
2983        });
2984
2985        // Without registering the meta-schema, this should fail
2986        let result = crate::options().without_schema_validation().build(&schema);
2987
2988        assert!(result.is_err());
2989        let error = result.unwrap_err();
2990        assert!(error.to_string().contains("Unknown meta-schema"));
2991        assert!(error
2992            .to_string()
2993            .contains("htt://json-schema.org/draft-07/schema"));
2994    }
2995
2996    #[test_case(Draft::Draft4)]
2997    #[test_case(Draft::Draft6)]
2998    #[test_case(Draft::Draft7)]
2999    fn meta_schemas(draft: Draft) {
3000        // See GH-258
3001        for schema in [json!({"enum": [0, 0.0]}), json!({"enum": []})] {
3002            assert!(crate::options().with_draft(draft).build(&schema).is_ok());
3003        }
3004    }
3005
3006    #[test]
3007    fn incomplete_escape_in_pattern() {
3008        // See GH-253
3009        let schema = json!({"pattern": "\\u"});
3010        assert!(crate::validator_for(&schema).is_err());
3011    }
3012
3013    #[test]
3014    fn validation_error_propagation() {
3015        fn foo() -> Result<(), Box<dyn std::error::Error>> {
3016            let schema = json!({});
3017            let validator = validator_for(&schema)?;
3018            let _ = validator.is_valid(&json!({}));
3019            Ok(())
3020        }
3021        let _ = foo();
3022    }
3023
3024    #[test]
3025    fn test_meta_validation_with_unknown_schema() {
3026        let schema = json!({
3027            "$schema": "json-schema:///custom",
3028            "type": "string"
3029        });
3030
3031        // Meta-validation now errors when the meta-schema is unknown/unregistered
3032        assert!(crate::meta::validate(&schema).is_err());
3033
3034        // Building a validator also fails without registration
3035        let result = crate::validator_for(&schema);
3036        assert!(result.is_err());
3037    }
3038
3039    #[test]
3040    #[cfg(all(not(target_arch = "wasm32"), feature = "resolve-file"))]
3041    fn test_meta_validation_respects_metaschema_draft() {
3042        use std::io::Write;
3043
3044        let mut temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
3045        let meta_schema_draft7 = json!({
3046            "$id": "http://example.com/meta/draft7",
3047            "$schema": "http://json-schema.org/draft-07/schema",
3048            "type": ["object", "boolean"],
3049            "properties": {
3050                "$schema": { "type": "string" },
3051                "type": {},
3052                "properties": { "type": "object" }
3053            },
3054            "additionalProperties": false
3055        });
3056        write!(temp_file, "{meta_schema_draft7}").expect("Failed to write to temp file");
3057
3058        let uri = crate::retriever::path_to_uri(temp_file.path());
3059
3060        let schema_using_draft7_meta = json!({
3061            "$schema": uri,
3062            "type": "object",
3063            "properties": {
3064                "name": { "type": "string" }
3065            },
3066            "unevaluatedProperties": false
3067        });
3068
3069        let schema_valid_for_draft7_meta = json!({
3070            "$schema": uri,
3071            "type": "object",
3072            "properties": {
3073                "name": { "type": "string" }
3074            }
3075        });
3076
3077        assert!(crate::meta::is_valid(&meta_schema_draft7));
3078        assert!(!crate::meta::is_valid(&schema_using_draft7_meta));
3079        assert!(crate::meta::is_valid(&schema_valid_for_draft7_meta));
3080    }
3081
3082    #[test]
3083    #[cfg(all(not(target_arch = "wasm32"), feature = "resolve-file"))]
3084    fn test_meta_schema_chain_resolution() {
3085        use std::io::Write;
3086
3087        // Create intermediate meta-schema pointing to Draft 2020-12
3088        let mut intermediate_file =
3089            tempfile::NamedTempFile::new().expect("Failed to create temp file");
3090        let intermediate_meta = json!({
3091            "$id": "http://example.com/meta/intermediate",
3092            "$schema": "https://json-schema.org/draft/2020-12/schema",
3093            "type": "object"
3094        });
3095        write!(intermediate_file, "{intermediate_meta}").expect("Failed to write to temp file");
3096        let intermediate_uri = crate::retriever::path_to_uri(intermediate_file.path());
3097
3098        // Create custom meta-schema with unknown draft that points to intermediate
3099        // This triggers the chain resolution code path in resolve_meta_schema_chain
3100        let mut custom_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
3101        let custom_meta = json!({
3102            "$id": "http://example.com/meta/custom",
3103            "$schema": intermediate_uri,
3104            "type": "object"
3105        });
3106        write!(custom_file, "{custom_meta}").expect("Failed to write to temp file");
3107        let custom_uri = crate::retriever::path_to_uri(custom_file.path());
3108
3109        let schema = json!({
3110            "$schema": custom_uri,
3111            "type": "string"
3112        });
3113
3114        // Should successfully resolve through the chain and detect Draft 2020-12
3115        assert!(crate::meta::is_valid(&schema));
3116    }
3117
3118    #[test]
3119    #[cfg(all(not(target_arch = "wasm32"), feature = "resolve-file"))]
3120    fn test_circular_meta_schema_reference() {
3121        use std::io::Write;
3122
3123        // Create meta-schema A pointing to meta-schema B
3124        let mut meta_a_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
3125        let meta_a_uri = crate::retriever::path_to_uri(meta_a_file.path());
3126
3127        // Create meta-schema B pointing back to meta-schema A
3128        let mut meta_b_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
3129        let meta_b_uri = crate::retriever::path_to_uri(meta_b_file.path());
3130
3131        let meta_a = json!({
3132            "$id": "http://example.com/meta/a",
3133            "$schema": &meta_b_uri,
3134            "type": "object"
3135        });
3136        write!(meta_a_file, "{meta_a}").expect("Failed to write to temp file");
3137
3138        let meta_b = json!({
3139            "$id": "http://example.com/meta/b",
3140            "$schema": &meta_a_uri,
3141            "type": "object"
3142        });
3143        write!(meta_b_file, "{meta_b}").expect("Failed to write to temp file");
3144
3145        let schema = json!({
3146            "$schema": meta_a_uri.clone(),
3147            "type": "string"
3148        });
3149
3150        // Should return a circular meta-schema error
3151        let result = crate::meta::options().validate(&schema);
3152        assert!(result.is_err());
3153        assert!(result
3154            .unwrap_err()
3155            .to_string()
3156            .contains("Circular meta-schema reference"));
3157    }
3158
3159    #[test]
3160    fn simple_schema_with_unknown_draft() {
3161        // Define a custom meta-schema
3162        let meta_schema = json!({
3163            "$schema": "https://json-schema.org/draft/2020-12/schema",
3164            "$id": "http://custom.example.com/schema",
3165            "$vocabulary": {
3166                "https://json-schema.org/draft/2020-12/vocab/core": true,
3167                "https://json-schema.org/draft/2020-12/vocab/applicator": true,
3168                "https://json-schema.org/draft/2020-12/vocab/validation": true,
3169            }
3170        });
3171
3172        // Schema using the custom meta-schema
3173        let schema = json!({
3174            "$schema": "http://custom.example.com/schema",
3175            "type": "object",
3176            "properties": {
3177                "name": { "type": "string" }
3178            }
3179        });
3180
3181        // Register the custom meta-schema as a resource
3182        let resources = vec![(
3183            "http://custom.example.com/schema".to_string(),
3184            Resource::from_contents(meta_schema),
3185        )];
3186
3187        let validator = crate::options()
3188            .without_schema_validation()
3189            .with_resources(resources.into_iter())
3190            .build(&schema)
3191            .expect("Should build validator");
3192
3193        // Valid instance
3194        assert!(validator.is_valid(&json!({"name": "test"})));
3195
3196        // Invalid instance - name should be string, not number
3197        assert!(!validator.is_valid(&json!({"name": 123})));
3198
3199        // Also verify type validation works
3200        assert!(!validator.is_valid(&json!("not an object")));
3201    }
3202
3203    #[test]
3204    fn custom_meta_schema_support() {
3205        // Define a custom meta-schema that extends Draft 2020-12
3206        let meta_schema = json!({
3207            "$id": "http://example.com/meta/schema",
3208            "$schema": "https://json-schema.org/draft/2020-12/schema",
3209            "title": "Core schema definition",
3210            "type": "object",
3211            "allOf": [
3212                {
3213                    "$ref": "#/$defs/editable"
3214                },
3215                {
3216                    "$ref": "#/$defs/core"
3217                }
3218            ],
3219            "properties": {
3220                "properties": {
3221                    "type": "object",
3222                    "patternProperties": {
3223                        ".*": {
3224                            "type": "object",
3225                            "properties": {
3226                                "type": {
3227                                    "type": "string",
3228                                    "enum": [
3229                                        "array",
3230                                        "boolean",
3231                                        "integer",
3232                                        "number",
3233                                        "object",
3234                                        "string",
3235                                        "null"
3236                                    ]
3237                                }
3238                            }
3239                        }
3240                    },
3241                    "propertyNames": {
3242                        "type": "string",
3243                        "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
3244                    }
3245                }
3246            },
3247            "unevaluatedProperties": false,
3248            "required": [
3249                "properties"
3250            ],
3251            "$defs": {
3252                "core": {
3253                    "type": "object",
3254                    "properties": {
3255                        "$id": {
3256                            "type": "string"
3257                        },
3258                        "$schema": {
3259                            "type": "string"
3260                        },
3261                        "type": {
3262                            "const": "object"
3263                        },
3264                        "title": {
3265                            "type": "string"
3266                        },
3267                        "description": {
3268                            "type": "string"
3269                        },
3270                        "additionalProperties": {
3271                            "type": "boolean",
3272                            "const": false
3273                        }
3274                    },
3275                    "required": [
3276                        "$id",
3277                        "$schema",
3278                        "type"
3279                    ]
3280                },
3281                "editable": {
3282                    "type": "object",
3283                    "properties": {
3284                        "creationDate": {
3285                            "type": "string",
3286                            "format": "date-time"
3287                        },
3288                        "updateDate": {
3289                            "type": "string",
3290                            "format": "date-time"
3291                        }
3292                    },
3293                    "required": [
3294                        "creationDate"
3295                    ]
3296                }
3297            }
3298        });
3299
3300        // A schema that uses the custom meta-schema
3301        let element_schema = json!({
3302            "$schema": "http://example.com/meta/schema",
3303            "$id": "http://example.com/schemas/element",
3304            "title": "Element",
3305            "description": "An element",
3306            "creationDate": "2024-12-31T12:31:53+01:00",
3307            "properties": {
3308                "value": {
3309                    "type": "string"
3310                }
3311            },
3312            "type": "object"
3313        });
3314
3315        // Build the validator with both the meta-schema and the element schema as resources
3316        let resources = vec![
3317            (
3318                "http://example.com/meta/schema".to_string(),
3319                referencing::Resource::from_contents(meta_schema),
3320            ),
3321            (
3322                "http://example.com/schemas/element".to_string(),
3323                referencing::Resource::from_contents(element_schema.clone()),
3324            ),
3325        ];
3326
3327        let validator = crate::options()
3328            .without_schema_validation()
3329            .with_resources(resources.into_iter())
3330            .build(&element_schema)
3331            .expect("Should successfully build validator with custom meta-schema");
3332
3333        let valid_instance = json!({
3334            "value": "test string"
3335        });
3336        assert!(validator.is_valid(&valid_instance));
3337
3338        let invalid_instance = json!({
3339            "value": 123
3340        });
3341        assert!(!validator.is_valid(&invalid_instance));
3342    }
3343
3344    #[test]
3345    fn custom_meta_schema_with_fragment_finds_vocabularies() {
3346        // Custom meta-schema URIs with trailing # should be found in registry
3347        let custom_meta = json!({
3348            "$id": "http://example.com/custom-with-unevaluated",
3349            "$schema": "https://json-schema.org/draft/2020-12/schema",
3350            "$vocabulary": {
3351                "https://json-schema.org/draft/2020-12/vocab/core": true,
3352                "https://json-schema.org/draft/2020-12/vocab/applicator": true,
3353                "https://json-schema.org/draft/2020-12/vocab/validation": true,
3354                "https://json-schema.org/draft/2020-12/vocab/unevaluated": true
3355            }
3356        });
3357
3358        let registry = Registry::try_new(
3359            "http://example.com/custom-with-unevaluated",
3360            Resource::from_contents(custom_meta),
3361        )
3362        .expect("Should create registry");
3363
3364        let schema = json!({
3365            "$schema": "http://example.com/custom-with-unevaluated#",
3366            "type": "object",
3367            "properties": {
3368                "foo": { "type": "string" }
3369            },
3370            "unevaluatedProperties": false
3371        });
3372
3373        let validator = crate::options()
3374            .without_schema_validation()
3375            .with_registry(registry)
3376            .build(&schema)
3377            .expect("Should build validator");
3378
3379        assert!(validator.is_valid(&json!({"foo": "bar"})));
3380        assert!(!validator.is_valid(&json!({"foo": "bar", "extra": "value"})));
3381    }
3382
3383    #[test]
3384    fn strict_meta_schema_catches_typos() {
3385        // Issue #764: Use strict meta-schema with unevaluatedProperties: false
3386        // to catch typos in schema keywords
3387
3388        let strict_meta = json!({
3389            "$schema": "https://json-schema.org/draft/2020-12/schema",
3390            "$id": "https://json-schema.org/draft/2020-12/strict",
3391            "$dynamicAnchor": "meta",
3392            "$ref": "https://json-schema.org/draft/2020-12/schema",
3393            "unevaluatedProperties": false
3394        });
3395
3396        let registry = Registry::try_new(
3397            "https://json-schema.org/draft/2020-12/strict",
3398            Resource::from_contents(strict_meta),
3399        )
3400        .expect("Should create registry");
3401
3402        // Valid schema - all keywords are recognized
3403        let valid_schema = json!({
3404            "$schema": "https://json-schema.org/draft/2020-12/strict",
3405            "type": "object",
3406            "properties": {
3407                "name": {"type": "string", "minLength": 1}
3408            }
3409        });
3410
3411        assert!(crate::meta::options()
3412            .with_registry(registry.clone())
3413            .is_valid(&valid_schema));
3414
3415        // Invalid schema - top-level typo "typ" instead of "type"
3416        let invalid_schema_top_level = json!({
3417            "$schema": "https://json-schema.org/draft/2020-12/strict",
3418            "typ": "string"  // Typo
3419        });
3420
3421        assert!(!crate::meta::options()
3422            .with_registry(registry.clone())
3423            .is_valid(&invalid_schema_top_level));
3424
3425        // Invalid schema - nested invalid keyword "minSize" (not a real JSON Schema keyword)
3426        let invalid_schema_nested = json!({
3427            "$schema": "https://json-schema.org/draft/2020-12/strict",
3428            "type": "object",
3429            "properties": {
3430                "name": {"type": "string", "minSize": 1}  // Invalid keyword in nested schema
3431            }
3432        });
3433
3434        assert!(!crate::meta::options()
3435            .with_registry(registry)
3436            .is_valid(&invalid_schema_nested));
3437    }
3438
3439    #[test]
3440    fn custom_meta_schema_preserves_underlying_draft_behavior() {
3441        // Regression test: Custom meta-schemas should preserve the draft-specific
3442        // behavior of their underlying draft, not default to Draft 2020-12
3443        // Draft 7 specific behavior: $ref siblings are ignored
3444
3445        let custom_meta_draft7 = json!({
3446            "$id": "http://example.com/meta/draft7-custom",
3447            "$schema": "http://json-schema.org/draft-07/schema#",
3448            "type": "object",
3449            "properties": {
3450                "customKeyword": {"type": "string"}
3451            }
3452        });
3453
3454        let registry = Registry::try_new(
3455            "http://example.com/meta/draft7-custom",
3456            Resource::from_contents(custom_meta_draft7),
3457        )
3458        .expect("Should create registry");
3459
3460        let schema = json!({
3461            "$schema": "http://example.com/meta/draft7-custom",
3462            "$ref": "#/$defs/positiveNumber",
3463            "maximum": 5,
3464            "$defs": {
3465                "positiveNumber": {
3466                    "type": "number",
3467                    "minimum": 0
3468                }
3469            }
3470        });
3471
3472        let validator = crate::options()
3473            .without_schema_validation()
3474            .with_registry(registry)
3475            .build(&schema)
3476            .expect("Should build validator");
3477
3478        // In Draft 7: siblings of $ref are ignored, so maximum: 5 has no effect
3479        // In Draft 2020-12: siblings are evaluated, so maximum: 5 would apply
3480        assert!(validator.is_valid(&json!(10)));
3481    }
3482
3483    mod meta_options_tests {
3484        use super::*;
3485        use crate::{Registry, Resource};
3486
3487        #[test]
3488        fn test_meta_options_with_registry_valid_schema() {
3489            let custom_meta = Resource::from_contents(json!({
3490                "$schema": "https://json-schema.org/draft/2020-12/schema",
3491                "type": "object",
3492                "properties": {
3493                    "$schema": { "type": "string" },
3494                    "type": { "type": "string" },
3495                    "maxLength": { "type": "integer" }
3496                },
3497                "additionalProperties": false
3498            }));
3499
3500            let registry = Registry::try_new("http://example.com/meta", custom_meta).unwrap();
3501
3502            let schema = json!({
3503                "$schema": "http://example.com/meta",
3504                "type": "string",
3505                "maxLength": 10
3506            });
3507
3508            assert!(crate::meta::options()
3509                .with_registry(registry.clone())
3510                .is_valid(&schema));
3511
3512            assert!(crate::meta::options()
3513                .with_registry(registry)
3514                .validate(&schema)
3515                .is_ok());
3516        }
3517
3518        #[test]
3519        fn test_meta_options_with_registry_invalid_schema() {
3520            let custom_meta = Resource::from_contents(json!({
3521                "$schema": "https://json-schema.org/draft/2020-12/schema",
3522                "type": "object",
3523                "properties": {
3524                    "type": { "type": "string" }
3525                },
3526                "additionalProperties": false
3527            }));
3528
3529            let registry = Registry::try_new("http://example.com/meta", custom_meta).unwrap();
3530
3531            // Schema has disallowed property
3532            let schema = json!({
3533                "$schema": "http://example.com/meta",
3534                "type": "string",
3535                "maxLength": 10  // Not allowed by custom meta-schema
3536            });
3537
3538            assert!(!crate::meta::options()
3539                .with_registry(registry.clone())
3540                .is_valid(&schema));
3541
3542            assert!(crate::meta::options()
3543                .with_registry(registry)
3544                .validate(&schema)
3545                .is_err());
3546        }
3547
3548        #[test]
3549        fn test_meta_options_with_registry_chain() {
3550            // Create a chain: custom-meta -> draft2020-12
3551            let custom_meta = Resource::from_contents(json!({
3552                "$schema": "https://json-schema.org/draft/2020-12/schema",
3553                "type": "object"
3554            }));
3555
3556            let registry = Registry::try_new("http://example.com/custom", custom_meta).unwrap();
3557
3558            let schema = json!({
3559                "$schema": "http://example.com/custom",
3560                "type": "string"
3561            });
3562
3563            assert!(crate::meta::options()
3564                .with_registry(registry)
3565                .is_valid(&schema));
3566        }
3567
3568        #[test]
3569        fn test_meta_options_with_registry_multi_level_chain() {
3570            // Create chain: schema -> meta-level-2 -> meta-level-1 -> draft2020-12
3571            let meta_level_1 = Resource::from_contents(json!({
3572                "$id": "http://example.com/meta/level1",
3573                "$schema": "https://json-schema.org/draft/2020-12/schema",
3574                "type": "object",
3575                "properties": {
3576                    "customProp": { "type": "boolean" }
3577                }
3578            }));
3579
3580            let meta_level_2 = Resource::from_contents(json!({
3581                "$id": "http://example.com/meta/level2",
3582                "$schema": "http://example.com/meta/level1",
3583                "type": "object",
3584                "customProp": true
3585            }));
3586
3587            let registry = Registry::try_from_resources([
3588                ("http://example.com/meta/level1", meta_level_1),
3589                ("http://example.com/meta/level2", meta_level_2),
3590            ])
3591            .unwrap();
3592
3593            let schema = json!({
3594                "$schema": "http://example.com/meta/level2",
3595                "type": "string",
3596                "customProp": true
3597            });
3598
3599            assert!(crate::meta::options()
3600                .with_registry(registry)
3601                .is_valid(&schema));
3602        }
3603
3604        #[test]
3605        fn test_meta_options_with_registry_multi_document_meta_schema() {
3606            let shared_constraints = Resource::from_contents(json!({
3607                "$id": "http://example.com/meta/shared",
3608                "$schema": "https://json-schema.org/draft/2020-12/schema",
3609                "type": "object",
3610                "properties": {
3611                    "maxLength": { "type": "integer", "minimum": 0 }
3612                }
3613            }));
3614
3615            let root_meta = Resource::from_contents(json!({
3616                "$id": "http://example.com/meta/root",
3617                "$schema": "https://json-schema.org/draft/2020-12/schema",
3618                "type": "object",
3619                "properties": {
3620                    "$schema": { "type": "string" },
3621                    "type": { "type": "string" }
3622                },
3623                "allOf": [
3624                    { "$ref": "http://example.com/meta/shared" }
3625                ]
3626            }));
3627
3628            let registry = Registry::try_from_resources([
3629                ("http://example.com/meta/root", root_meta),
3630                ("http://example.com/meta/shared", shared_constraints),
3631            ])
3632            .unwrap();
3633
3634            let schema = json!({
3635                "$schema": "http://example.com/meta/root",
3636                "type": "string",
3637                "maxLength": 5
3638            });
3639
3640            let result = crate::meta::options()
3641                .with_registry(registry.clone())
3642                .validate(&schema);
3643
3644            assert!(
3645                result.is_ok(),
3646                "meta validation failed even though registry contains all meta-schemas: {}",
3647                result.unwrap_err()
3648            );
3649
3650            assert!(crate::meta::options()
3651                .with_registry(registry)
3652                .is_valid(&schema));
3653        }
3654
3655        #[test]
3656        fn test_meta_options_without_registry_unknown_metaschema() {
3657            let schema = json!({
3658                "$schema": "http://0.0.0.0/nonexistent",
3659                "type": "string"
3660            });
3661
3662            // Without registry, should fail to resolve
3663            let result = crate::meta::options().validate(&schema);
3664            assert!(result.is_err());
3665        }
3666
3667        #[test]
3668        #[should_panic(expected = "Failed to resolve meta-schema")]
3669        fn test_meta_options_is_valid_panics_on_missing_metaschema() {
3670            let schema = json!({
3671                "$schema": "http://0.0.0.0/nonexistent",
3672                "type": "string"
3673            });
3674
3675            // is_valid() should panic if meta-schema cannot be resolved
3676            let _ = crate::meta::options().is_valid(&schema);
3677        }
3678
3679        #[test]
3680        fn test_meta_options_with_registry_missing_metaschema() {
3681            let custom_meta = Resource::from_contents(json!({
3682                "$schema": "https://json-schema.org/draft/2020-12/schema",
3683                "type": "object"
3684            }));
3685
3686            let registry = Registry::try_new("http://example.com/meta1", custom_meta).unwrap();
3687
3688            // Schema references a different meta-schema not in registry
3689            let schema = json!({
3690                "$schema": "http://example.com/meta2",
3691                "type": "string"
3692            });
3693
3694            let result = crate::meta::options()
3695                .with_registry(registry)
3696                .validate(&schema);
3697
3698            assert!(result.is_err());
3699        }
3700
3701        #[test]
3702        fn test_meta_options_circular_reference_detection() {
3703            // Create a circular reference: meta1 -> meta2 -> meta1
3704            let meta1 = Resource::from_contents(json!({
3705                "$id": "http://example.com/meta1",
3706                "$schema": "http://example.com/meta2",
3707                "type": "object"
3708            }));
3709
3710            let meta2 = Resource::from_contents(json!({
3711                "$id": "http://example.com/meta2",
3712                "$schema": "http://example.com/meta1",
3713                "type": "object"
3714            }));
3715
3716            let registry = Registry::try_from_resources([
3717                ("http://example.com/meta1", meta1),
3718                ("http://example.com/meta2", meta2),
3719            ])
3720            .unwrap();
3721
3722            let schema = json!({
3723                "$schema": "http://example.com/meta1",
3724                "type": "string"
3725            });
3726
3727            let result = crate::meta::options()
3728                .with_registry(registry)
3729                .validate(&schema);
3730
3731            assert!(result.is_err());
3732            // Check it's specifically a circular error
3733            let err = result.unwrap_err();
3734            assert!(err.to_string().contains("Circular"));
3735        }
3736
3737        #[test]
3738        fn test_meta_options_standard_drafts_without_registry() {
3739            // Standard drafts should work without registry
3740            let schemas = vec![
3741                json!({ "$schema": "http://json-schema.org/draft-04/schema#", "type": "string" }),
3742                json!({ "$schema": "http://json-schema.org/draft-06/schema#", "type": "string" }),
3743                json!({ "$schema": "http://json-schema.org/draft-07/schema#", "type": "string" }),
3744                json!({ "$schema": "https://json-schema.org/draft/2019-09/schema", "type": "string" }),
3745                json!({ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "string" }),
3746            ];
3747
3748            for schema in schemas {
3749                assert!(
3750                    crate::meta::options().is_valid(&schema),
3751                    "Failed for schema: {schema}"
3752                );
3753            }
3754        }
3755
3756        #[test]
3757        fn test_meta_options_validate_returns_specific_errors() {
3758            let custom_meta = Resource::from_contents(json!({
3759                "$schema": "https://json-schema.org/draft/2020-12/schema",
3760                "type": "object",
3761                "required": ["type"]
3762            }));
3763
3764            let registry = Registry::try_new("http://example.com/meta", custom_meta).unwrap();
3765
3766            // Schema missing required property
3767            let schema = json!({
3768                "$schema": "http://example.com/meta",
3769                "properties": {
3770                    "name": { "type": "string" }
3771                }
3772            });
3773
3774            let result = crate::meta::options()
3775                .with_registry(registry)
3776                .validate(&schema);
3777
3778            assert!(result.is_err());
3779            let err = result.unwrap_err();
3780            assert!(err.to_string().contains("required") || err.to_string().contains("type"));
3781        }
3782
3783        #[test]
3784        fn test_meta_options_builds_validator_with_resolved_draft() {
3785            let custom_meta = Resource::from_contents(json!({
3786                "$id": "http://example.com/meta/draft7-based",
3787                "$schema": "http://json-schema.org/draft-07/schema#",
3788                "type": "object",
3789                "properties": {
3790                    "$schema": { "type": "string" },
3791                    "type": { "type": "string" },
3792                    "minLength": { "type": "integer" }
3793                },
3794                "additionalProperties": false
3795            }));
3796
3797            let registry =
3798                Registry::try_new("http://example.com/meta/draft7-based", custom_meta).unwrap();
3799
3800            let schema = json!({
3801                "$schema": "http://example.com/meta/draft7-based",
3802                "type": "string",
3803                "minLength": 5
3804            });
3805
3806            let result = crate::meta::options()
3807                .with_registry(registry)
3808                .validate(&schema);
3809
3810            assert!(result.is_ok());
3811        }
3812
3813        #[test]
3814        fn test_meta_options_validator_uses_correct_draft() {
3815            let custom_meta_draft6 = Resource::from_contents(json!({
3816                "$id": "http://example.com/meta/draft6-based",
3817                "$schema": "http://json-schema.org/draft-06/schema#",
3818                "type": "object",
3819                "properties": {
3820                    "$schema": { "type": "string" },
3821                    "type": { "type": "string" },
3822                    "exclusiveMinimum": { "type": "number" }
3823                },
3824                "additionalProperties": false
3825            }));
3826
3827            let registry =
3828                Registry::try_new("http://example.com/meta/draft6-based", custom_meta_draft6)
3829                    .unwrap();
3830
3831            let schema_valid_for_draft6 = json!({
3832                "$schema": "http://example.com/meta/draft6-based",
3833                "type": "number",
3834                "exclusiveMinimum": 0
3835            });
3836
3837            let result = crate::meta::options()
3838                .with_registry(registry)
3839                .validate(&schema_valid_for_draft6);
3840
3841            assert!(result.is_ok());
3842        }
3843
3844        #[test]
3845        fn test_meta_options_without_schema_validation_in_built_validator() {
3846            let custom_meta = Resource::from_contents(json!({
3847                "$id": "http://example.com/meta/custom",
3848                "$schema": "https://json-schema.org/draft/2020-12/schema",
3849                "type": "object",
3850                "properties": {
3851                    "$schema": { "type": "string" },
3852                    "type": { "type": "string" }
3853                },
3854                "additionalProperties": false
3855            }));
3856
3857            let registry =
3858                Registry::try_new("http://example.com/meta/custom", custom_meta).unwrap();
3859
3860            let schema = json!({
3861                "$schema": "http://example.com/meta/custom",
3862                "type": "string"
3863            });
3864
3865            let result = crate::meta::options()
3866                .with_registry(registry)
3867                .validate(&schema);
3868
3869            assert!(result.is_ok());
3870        }
3871
3872        #[test]
3873        fn test_meta_validation_uses_resolved_draft_from_chain() {
3874            // Chain: user-schema -> custom-meta -> Draft 4
3875            // Validator should use Draft 4 rules to validate the schema
3876            let custom_meta = Resource::from_contents(json!({
3877                "$id": "http://example.com/meta/draft4-based",
3878                "$schema": "http://json-schema.org/draft-04/schema#",
3879                "type": "object",
3880                "properties": {
3881                    "$schema": { "type": "string" },
3882                    "type": { "type": "string" },
3883                    "enum": { "type": "array" },
3884                    "const": { "type": "string" }
3885                },
3886                "additionalProperties": false
3887            }));
3888
3889            let registry =
3890                Registry::try_new("http://example.com/meta/draft4-based", custom_meta).unwrap();
3891
3892            let schema = json!({
3893                "$schema": "http://example.com/meta/draft4-based",
3894                "type": "string",
3895                "const": "foo"
3896            });
3897
3898            let result = crate::meta::options()
3899                .with_registry(registry)
3900                .validate(&schema);
3901
3902            assert!(result.is_ok());
3903        }
3904
3905        #[test]
3906        fn test_meta_validation_multi_level_chain_uses_resolved_draft() {
3907            // Multi-level chain: user-schema -> meta-2 -> meta-1 -> Draft 4
3908            let meta_level_1 = Resource::from_contents(json!({
3909                "$id": "http://example.com/meta/level1",
3910                "$schema": "http://json-schema.org/draft-04/schema#",
3911                "type": "object",
3912                "properties": {
3913                    "customKeyword": { "type": "boolean" }
3914                }
3915            }));
3916
3917            let meta_level_2 = Resource::from_contents(json!({
3918                "$id": "http://example.com/meta/level2",
3919                "$schema": "http://example.com/meta/level1",
3920                "type": "object",
3921                "properties": {
3922                    "$schema": { "type": "string" },
3923                    "type": { "type": "string" },
3924                    "minimum": { "type": "number" },
3925                    "exclusiveMinimum": { "type": "boolean" }
3926                },
3927                "customKeyword": true,
3928                "additionalProperties": false
3929            }));
3930
3931            let registry = Registry::try_from_resources([
3932                ("http://example.com/meta/level1", meta_level_1),
3933                ("http://example.com/meta/level2", meta_level_2),
3934            ])
3935            .unwrap();
3936
3937            let schema = json!({
3938                "$schema": "http://example.com/meta/level2",
3939                "type": "number",
3940                "minimum": 5,
3941                "exclusiveMinimum": true
3942            });
3943
3944            let result = crate::meta::options()
3945                .with_registry(registry)
3946                .validate(&schema);
3947
3948            assert!(result.is_ok());
3949        }
3950    }
3951
3952    #[test]
3953    fn test_meta_validator_for_valid_schema() {
3954        let schema = json!({
3955            "type": "string",
3956            "maxLength": 5
3957        });
3958
3959        let validator = crate::meta::validator_for(&schema).expect("Valid meta-schema");
3960        assert!(validator.is_valid(&schema));
3961    }
3962
3963    #[test]
3964    fn test_meta_validator_for_invalid_schema() {
3965        let schema = json!({
3966            "type": "invalid_type"
3967        });
3968
3969        let validator = crate::meta::validator_for(&schema).expect("Valid meta-schema");
3970        assert!(!validator.is_valid(&schema));
3971    }
3972
3973    #[test]
3974    fn test_meta_validator_for_evaluate_api() {
3975        let schema = json!({
3976            "type": "string",
3977            "maxLength": 5
3978        });
3979
3980        let validator = crate::meta::validator_for(&schema).expect("Valid meta-schema");
3981        let evaluation = validator.evaluate(&schema);
3982
3983        let flag = evaluation.flag();
3984        assert!(flag.valid);
3985    }
3986
3987    #[test]
3988    fn test_meta_validator_for_evaluate_api_invalid() {
3989        let schema = json!({
3990            "type": "invalid_type",
3991            "minimum": "not a number"
3992        });
3993
3994        let validator = crate::meta::validator_for(&schema).expect("Valid meta-schema");
3995        let evaluation = validator.evaluate(&schema);
3996
3997        let flag = evaluation.flag();
3998        assert!(!flag.valid);
3999    }
4000
4001    #[test]
4002    fn test_meta_validator_for_all_drafts() {
4003        let schemas = vec![
4004            json!({ "$schema": "http://json-schema.org/draft-04/schema#", "type": "string" }),
4005            json!({ "$schema": "http://json-schema.org/draft-06/schema#", "type": "string" }),
4006            json!({ "$schema": "http://json-schema.org/draft-07/schema#", "type": "string" }),
4007            json!({ "$schema": "https://json-schema.org/draft/2019-09/schema", "type": "string" }),
4008            json!({ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "string" }),
4009        ];
4010
4011        for schema in schemas {
4012            let validator = crate::meta::validator_for(&schema).unwrap();
4013            assert!(validator.is_valid(&schema));
4014        }
4015    }
4016
4017    #[test]
4018    fn test_meta_validator_for_iter_errors() {
4019        let schema = json!({
4020            "type": "invalid_type",
4021            "minimum": "not a number"
4022        });
4023
4024        let validator = crate::meta::validator_for(&schema).expect("Valid meta-schema");
4025        let errors: Vec<_> = validator.iter_errors(&schema).collect();
4026        assert!(!errors.is_empty());
4027    }
4028}
4029
4030#[cfg(all(test, feature = "resolve-async", not(target_family = "wasm")))]
4031mod async_tests {
4032    use referencing::Resource;
4033    use std::{collections::HashMap, sync::Arc};
4034
4035    use serde_json::json;
4036
4037    use crate::{AsyncRetrieve, Draft, Uri};
4038
4039    /// Mock async retriever for testing
4040    #[derive(Clone)]
4041    struct TestRetriever {
4042        schemas: HashMap<String, serde_json::Value>,
4043    }
4044
4045    impl TestRetriever {
4046        fn new() -> Self {
4047            let mut schemas = HashMap::new();
4048            schemas.insert(
4049                "https://example.com/user.json".to_string(),
4050                json!({
4051                    "type": "object",
4052                    "properties": {
4053                        "name": {"type": "string"},
4054                        "age": {"type": "integer", "minimum": 0}
4055                    },
4056                    "required": ["name"]
4057                }),
4058            );
4059            Self { schemas }
4060        }
4061    }
4062
4063    #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
4064    #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
4065    impl AsyncRetrieve for TestRetriever {
4066        async fn retrieve(
4067            &self,
4068            uri: &Uri<String>,
4069        ) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
4070            self.schemas
4071                .get(uri.as_str())
4072                .cloned()
4073                .ok_or_else(|| "Schema not found".into())
4074        }
4075    }
4076
4077    #[tokio::test]
4078    async fn test_async_validator_for() {
4079        let schema = json!({
4080            "$ref": "https://example.com/user.json"
4081        });
4082
4083        let validator = crate::async_options()
4084            .with_retriever(TestRetriever::new())
4085            .build(&schema)
4086            .await
4087            .unwrap();
4088
4089        // Valid instance
4090        assert!(validator.is_valid(&json!({
4091            "name": "John Doe",
4092            "age": 30
4093        })));
4094
4095        // Invalid instances
4096        assert!(!validator.is_valid(&json!({
4097            "age": -5
4098        })));
4099        assert!(!validator.is_valid(&json!({
4100            "name": 123,
4101            "age": 30
4102        })));
4103    }
4104
4105    #[tokio::test]
4106    async fn test_async_options_with_draft() {
4107        let schema = json!({
4108            "$ref": "https://example.com/user.json"
4109        });
4110
4111        let validator = crate::async_options()
4112            .with_draft(Draft::Draft202012)
4113            .with_retriever(TestRetriever::new())
4114            .build(&schema)
4115            .await
4116            .unwrap();
4117
4118        assert!(validator.is_valid(&json!({
4119            "name": "John Doe",
4120            "age": 30
4121        })));
4122    }
4123
4124    #[tokio::test]
4125    async fn test_async_retrieval_failure() {
4126        let schema = json!({
4127            "$ref": "https://example.com/nonexistent.json"
4128        });
4129
4130        let result = crate::async_options()
4131            .with_retriever(TestRetriever::new())
4132            .build(&schema)
4133            .await;
4134
4135        assert!(result.is_err());
4136        assert!(result.unwrap_err().to_string().contains("Schema not found"));
4137    }
4138
4139    #[tokio::test]
4140    async fn test_async_nested_references() {
4141        let mut retriever = TestRetriever::new();
4142        retriever.schemas.insert(
4143            "https://example.com/nested.json".to_string(),
4144            json!({
4145                "type": "object",
4146                "properties": {
4147                    "user": { "$ref": "https://example.com/user.json" }
4148                }
4149            }),
4150        );
4151
4152        let schema = json!({
4153            "$ref": "https://example.com/nested.json"
4154        });
4155
4156        let validator = crate::async_options()
4157            .with_retriever(retriever)
4158            .build(&schema)
4159            .await
4160            .unwrap();
4161
4162        // Valid nested structure
4163        assert!(validator.is_valid(&json!({
4164            "user": {
4165                "name": "John Doe",
4166                "age": 30
4167            }
4168        })));
4169
4170        // Invalid nested structure
4171        assert!(!validator.is_valid(&json!({
4172            "user": {
4173                "age": -5
4174            }
4175        })));
4176    }
4177
4178    #[tokio::test]
4179    async fn test_async_with_registry() {
4180        use crate::Registry;
4181
4182        // Create a registry with initial schemas
4183        let registry = Registry::options()
4184            .async_retriever(TestRetriever::new())
4185            .build([(
4186                "https://example.com/user.json",
4187                Resource::from_contents(json!({
4188                    "type": "object",
4189                    "properties": {
4190                        "name": {"type": "string"},
4191                        "age": {"type": "integer", "minimum": 0}
4192                    },
4193                    "required": ["name"]
4194                })),
4195            )])
4196            .await
4197            .unwrap();
4198
4199        // Create a validator using the pre-populated registry
4200        let validator = crate::async_options()
4201            .with_registry(registry)
4202            .build(&json!({
4203                "$ref": "https://example.com/user.json"
4204            }))
4205            .await
4206            .unwrap();
4207
4208        // Verify that validation works with the registry
4209        assert!(validator.is_valid(&json!({
4210            "name": "John Doe",
4211            "age": 30
4212        })));
4213        assert!(!validator.is_valid(&json!({
4214            "age": -5
4215        })));
4216    }
4217
4218    #[tokio::test]
4219    async fn test_async_validator_for_basic() {
4220        let schema = json!({"type": "integer"});
4221
4222        let validator = crate::async_validator_for(&schema).await.unwrap();
4223
4224        assert!(validator.is_valid(&json!(42)));
4225        assert!(!validator.is_valid(&json!("abc")));
4226    }
4227
4228    #[tokio::test]
4229    async fn test_async_build_future_is_send() {
4230        let schema = Arc::new(json!({
4231            "$ref": "https://example.com/user.json"
4232        }));
4233        let retriever = TestRetriever::new();
4234
4235        let handle = tokio::spawn({
4236            let schema = Arc::clone(&schema);
4237            let retriever = retriever.clone();
4238            async move {
4239                crate::async_options()
4240                    .with_retriever(retriever)
4241                    .build(&schema)
4242                    .await
4243            }
4244        });
4245
4246        let validator = handle.await.unwrap().unwrap();
4247        assert!(validator.is_valid(&json!({
4248            "name": "John Doe",
4249            "age": 30
4250        })));
4251    }
4252}