1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
//! Validate input data against schemas.
//!
//! This module contains logic related to *validation*, the process of taking a
//! piece of input data (called an "instance") and checking if it's valid
//! according to a schema.
//!
//! See the docs for [`Validator`](struct.Validator.html) for more.

use crate::registry::Registry;
use crate::vm::validate;
use failure::Error;
use json_pointer::JsonPointer;
use std::borrow::Cow;
use url::Url;

/// Validates instances against a registry of schemas.
pub struct Validator<'a> {
    config: Config,
    registry: &'a Registry,
}

impl<'a> Validator<'a> {
    /// Constructs a new validator using a registry and the default
    /// configuration.
    pub fn new(registry: &'a Registry) -> Self {
        Self::new_with_config(Config::default(), registry)
    }

    /// Constructs a new validator using a registry and configuration.
    pub fn new_with_config(config: Config, registry: &'a Registry) -> Self {
        Self { config, registry }
    }

    /// Validate an instance against the default schema the registry.
    ///
    /// See [`validate_by_uri`](#method.validate_by_uri) for possible error
    /// conditions.
    pub fn validate(
        &'a self,
        instance: &'a serde_json::Value,
    ) -> Result<Vec<ValidationError<'a>>, Error> {
        self.validate_by_id(&None, instance)
    }

    /// Validate an instance against the schema with the given URI.
    ///
    /// Returns an error if the registry is currently unsealed (see
    /// [`Registry::is_sealed`](../registry/struct.Registry.html#method.is_sealed)), or if
    /// the maximum reference depth is exceeded (see
    /// [`ValidatorConfig::max_depth`](struct.ValidatorConfig.html#method.max_depth)).
    ///
    /// The generated errors have the same lifetime as the inputted instance;
    /// this crate avoids copying data out of your inputted data.
    pub fn validate_by_id(
        &'a self,
        id: &'a Option<Url>,
        instance: &'a serde_json::Value,
    ) -> Result<Vec<ValidationError<'a>>, Error> {
        validate(
            self.config.max_errors,
            self.config.max_depth,
            self.config.strict_instance_semantics,
            self.registry,
            id,
            instance,
        )
    }
}

/// Configuration for how validation should proceed.
pub struct Config {
    max_errors: usize,
    max_depth: usize,
    strict_instance_semantics: bool,
}

impl Config {
    /// Create a new, default `Config`.
    pub fn new() -> Self {
        Self::default()
    }

    /// Sets the maximum number of errors to produce before stopping validation.
    /// 0, the default value, indicates that all errors should be produced.
    ///
    /// If your use-case doesn't care about errors, and you just want to abort
    /// on the first error, you should set this value to 1.
    pub fn max_errors(&mut self, max_errors: usize) -> &mut Self {
        self.max_errors = max_errors;
        self
    }

    /// Sets the maximum call depth before aborting evaluation. The default
    /// value is to follow 32 cross-references before aborting.
    ///
    /// When evaluation is aborted because of this maximum depth, validation
    /// *fails*. No validation errors are returned.
    ///
    /// This functionality exists to support detecting infinite loops in
    /// schemas, for example in circularly-defined schemas.
    pub fn max_depth(&mut self, max_depth: usize) -> &mut Self {
        self.max_depth = max_depth;
        self
    }

    /// Sets whether to use strict instance semantics. The default is to not use
    /// strict instance semantics.
    ///
    /// Essentially, strict instance semantics determines whether it's ok for an
    /// instance (input) to have properties not mentioned in a schema. When
    /// using strict instance semantics, these undeclared properties will be
    /// considered erroneuous. In non-strict instance semantics, these
    /// properties are simply ignored.
    pub fn strict_instance_semantics(&mut self, strict_instance_semantics: bool) -> &mut Self {
        self.strict_instance_semantics = strict_instance_semantics;
        self
    }
}

impl Default for Config {
    fn default() -> Self {
        Self {
            max_errors: 0,
            max_depth: 32,
            strict_instance_semantics: false,
        }
    }
}

/// Contains a single problem with an instance when evaluated against a schema.
///
/// Note that, despite its name, `ValidationError` is not an error in the usual
/// Rust sense. It is an ordinary struct, which happens to contain information
/// about why some data was unsatisfactory against a given schema.
///
/// `ValidationError` uses `Cow` instead of `String` to store its components.
/// That's because this crate makes every effort to never copy data out of your
/// instances. However, some parts of error paths require allocation (such as
/// when the `usize` indices of an array are converted into `String`), and so
/// `Cow` is used.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ValidationError<'a> {
    instance_path: JsonPointer<Cow<'a, str>, Vec<Cow<'a, str>>>,
    schema_path: JsonPointer<Cow<'a, str>, Vec<Cow<'a, str>>>,
    schema_id: &'a Option<Url>,
}

impl<'a> ValidationError<'a> {
    pub fn new(
        instance_path: JsonPointer<Cow<'a, str>, Vec<Cow<'a, str>>>,
        schema_path: JsonPointer<Cow<'a, str>, Vec<Cow<'a, str>>>,
        schema_id: &'a Option<Url>,
    ) -> ValidationError<'a> {
        ValidationError {
            instance_path,
            schema_path,
            schema_id,
        }
    }

    /// A pointer into the part of the instance (input) which was rejected.
    pub fn instance_path(&self) -> &JsonPointer<Cow<'a, str>, Vec<Cow<'a, str>>> {
        &self.instance_path
    }

    /// A pointer into the part of the schema which rejected the instance.
    pub fn schema_path(&self) -> &JsonPointer<Cow<'a, str>, Vec<Cow<'a, str>>> {
        &self.schema_path
    }

    /// The ID of the schema which rejected the instance. If the schema
    /// doesn't have an ID, then this is None.
    pub fn schema_id(&self) -> &Option<Url> {
        &self.schema_id
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::schema::Schema;
    use serde_json::json;

    #[test]
    fn infinite_loop() -> Result<(), Error> {
        let mut registry = Registry::new();
        registry.register(Schema::from_serde(serde_json::from_value(json!({
            "ref": "#",
        }))?)?)?;

        let validator = Validator::new(&registry);
        assert!(validator.validate(&json!({})).is_err());

        Ok(())
    }

    #[test]
    fn max_errors() -> Result<(), Error> {
        let mut registry = Registry::new();
        registry.register(Schema::from_serde(serde_json::from_value(json!({
            "elements": { "type": "string" },
        }))?)?)?;

        let mut config = Config::new();
        config.max_errors(3);

        let validator = Validator::new_with_config(config, &registry);
        assert_eq!(
            validator
                .validate(&json!([null, null, null, null, null,]))
                .unwrap()
                .len(),
            3
        );

        Ok(())
    }
}