JSONSchema validation implementation in Rust

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?

Code

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> {
        f.write_str(&self.name())
    }
}

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):

#[derive(Debug)]
pub struct ValidationError {
    kind: ValidationErrorKind,
}

#[derive(Debug)]
pub enum ValidationErrorKind {
    FalseSchema(Value),
    // ...
}

Validation node example ([actual code][3]):

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)?
                    }
                }
            }
        }
        Ok(())
    }
    // ...
}

Playground for a smaller version;

Dependencies

Rust version: 1.42.0

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.