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