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//! - 
16//! - 
17//! - 
18//! - 
19//! - 
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(®istry)
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(®istry)
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(®istry);
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(¤t_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/// [](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/// [](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/// [](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/// [](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/// [](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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
3690 .is_valid(&schema));
3691
3692 assert!(crate::meta::options()
3693 .with_registry(®istry)
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(®istry)
3724 .is_valid(&schema));
3725
3726 assert!(crate::meta::options()
3727 .with_registry(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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(®istry)
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}