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