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