I'm developing a Rust library that implements JSONSchema validation (Drafts 7 fully works at the moment). I have Rust-specific questions and concerns about my implementation.

It works by compiling the input schema first and validating the data after it:

use jsonschema::{JSONSchema, Draft};
use serde_json::json;

let schema = json!({"maxLength": 5});
let instance = json!("foo");
let compiled = JSONSchema::compile(&schema, Some(Draft::Draft7));
let result = compiled.validate(&instance);

Main points:

  • Input schema is parsed into a tree of structs that implement a common trait - Validate that provides validate method (code);
  • Each struct in this tree have different members that may refer to the original schema (e.g. holding a vector of strings for "required" keyword) or have some computed values, e.g. regex (example);
  • During validation, the input value is passed to this tree and it is validated by specific nodes (code);
  • validate returns Result<(), ValidationError>, where the error case contains data needed for displaying errors. This data is copied from schema or instance or constructed dynamically somewhere in the tree;
  • When resolving a reference, a Cow is returned. Borrowed for cases when the referenced document exists in instance and Owned when it is loaded from a remote location (e.g. {"$ref": "http://localhost/schema.json"});
  • A new validation tree is generated for resolved schemas and validation goes further with this new tree;

My concerns:

  1. Validate trait. Is it a good approach to build a validation tree? What are the alternatives for such a case?
  2. Is it possible to avoid copying to ValidationError and use references when possible? In some cases, objects are generated / loaded from a remote location, during validation and have different lifetime than schema or instance. In this case, probably the data should be owned - is it possible to have ValidationError working for both cases?


Trait (actual code):

pub trait Validate<'a>: Send + Sync + 'a {
    fn validate(&self, schema: &JSONSchema, instance: &Value) -> ValidationResult;
    fn name(&self) -> String;

impl<'a> Debug for dyn Validate<'a> + Send + Sync + 'a {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {

pub type ValidationResult = Result<(), error::ValidationError>;
pub type CompilationResult<'a> = Result<BoxedValidator<'a>, error::CompilationError>;
pub type BoxedValidator<'a> = Box<dyn Validate<'a> + Send + Sync + 'a>;
pub type Validators<'a> = Vec<BoxedValidator<'a>>;

ValidationError (actual code):

pub struct ValidationError {
    kind: ValidationErrorKind,

pub enum ValidationErrorKind {
    // ...

Validation node example (actual code):

pub struct PropertiesValidator<'a> {
    properties: Vec<(&'a String, Validators<'a>)>,

impl<'a> PropertiesValidator<'a> {
    pub(crate) fn compile(
        schema: &'a Value,
        context: &CompilationContext,
    ) -> CompilationResult<'a> {
        match schema {
            Value::Object(map) => {
                let mut properties = Vec::with_capacity(map.len());
                for (key, subschema) in map {
                    properties.push((key, compile_validators(subschema, context)?));
                Ok(Box::new(PropertiesValidator { properties }))
            _ => Err(CompilationError::SchemaError),

impl<'a> Validate<'a> for PropertiesValidator<'a> {
    fn validate(&self, schema: &JSONSchema, instance: &Value) -> ValidationResult {
        if let Value::Object(item) = instance {
            for (name, validators) in self.properties.iter() {
                if let Some(item) = item.get(*name) {
                    for validator in validators {
                        validator.validate(schema, item)?
    // ...

Playground for a smaller version;


Rust version: 1.42.0


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.