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
//! This module implements validation from [`serde_json::Value`].
//!
//! # Examples
//!
//! ```
//! use cddl_cat::validate_json_str;
//!
//! let cddl_input = "person = {name: tstr, age: int}";
//! let json_str = r#"{ "name": "Bob", "age": 43 }"#;
//!
//! validate_json_str("person", cddl_input, &json_str).unwrap();
//! ```
//!

#![cfg(feature = "serde_json")]

use crate::context::{BasicContext, LookupContext};
use crate::flatten::flatten_from_str;
use crate::ivt::RuleDef;
use crate::util::{ValidateError, ValidateResult};
use crate::validate::do_validate;
use crate::value::Value;
use serde_json::Value as JSON_Value;
use std::collections::BTreeMap;
use std::convert::TryFrom;

// Convert JSON `Value`s to the local `Value` type that the validate code
// uses.

impl TryFrom<&JSON_Value> for Value {
    type Error = ValidateError;

    fn try_from(value: &JSON_Value) -> Result<Self, Self::Error> {
        let result = match value {
            JSON_Value::Null => Value::Null,
            JSON_Value::Bool(b) => Value::Bool(*b),
            JSON_Value::Number(num) => {
                if let Some(u) = num.as_u64() {
                    Value::Integer(i128::from(u))
                } else if let Some(i) = num.as_i64() {
                    Value::Integer(i128::from(i))
                } else if let Some(f) = num.as_f64() {
                    Value::from_float(f)
                } else {
                    return Err(ValidateError::ValueError(
                        "JSON Value::Number conversion failure".into(),
                    ));
                }
            }
            JSON_Value::String(t) => Value::Text(t.clone()),
            JSON_Value::Array(a) => {
                let array: Result<_, _> = a.iter().map(Value::try_from).collect();
                Value::Array(array?)
            }
            JSON_Value::Object(m) => {
                type MapTree = BTreeMap<Value, Value>;
                let map: Result<MapTree, _> = m
                    .iter()
                    .map(|(k, v)| {
                        // An iterator returning a 2-tuple can be used as (key, value)
                        // when building a new map.
                        Ok((Value::Text(k.clone()), Value::try_from(v)?))
                    })
                    .collect();
                Value::Map(map?)
            }
        };
        Ok(result)
    }
}

// A variant that consumes the JSON Value.
impl TryFrom<JSON_Value> for Value {
    type Error = ValidateError;

    fn try_from(value: JSON_Value) -> Result<Self, Self::Error> {
        Value::try_from(&value)
    }
}

/// Validate already-parsed JSON data against an already-parsed CDDL schema.
pub fn validate_json(
    rule_def: &RuleDef,
    value: &JSON_Value,
    ctx: &dyn LookupContext,
) -> ValidateResult {
    let value = Value::try_from(value)?;
    do_validate(&value, rule_def, ctx)
}

/// Validate JSON-encoded data against a specified rule in a UTF-8 CDDL schema.
pub fn validate_json_str(name: &str, cddl: &str, json: &str) -> ValidateResult {
    // Parse the CDDL text and flatten it into IVT form.
    let flat_cddl = flatten_from_str(cddl)?;
    let ctx = BasicContext::new(flat_cddl);

    // Find the rule definition that was requested
    let rule_def: &RuleDef = ctx
        .rules
        .get(name)
        .ok_or_else(|| ValidateError::MissingRule(name.into()))?;

    // Deserialize the JSON bytes
    let json_value: JSON_Value =
        serde_json::from_str(json).map_err(|e| ValidateError::ValueError(format!("{}", e)))?;

    // Convert the JSON tree into a Value tree for validation
    let value = Value::try_from(json_value)?;
    do_validate(&value, rule_def, &ctx)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_json_number_behavior() {
        // Ensures that our JSON decoder tracks number types precisely, and
        // doesn't, say, allow floating-point values to become integers.
        // serde_json does sometimes permit as_f64 to work on integers, which is
        // why try_from has to test u64, then i64, then f64.

        let json_value: JSON_Value = serde_json::from_str("1").unwrap();
        assert!(json_value.as_u64().is_some());

        let json_value: JSON_Value = serde_json::from_str("-1").unwrap();
        assert!(json_value.as_u64().is_none());
        assert!(json_value.as_i64().is_some());

        let json_value: JSON_Value = serde_json::from_str("1.0").unwrap();
        assert!(json_value.as_u64().is_none());
        assert!(json_value.as_i64().is_none());
        assert!(json_value.as_f64().is_some());
    }
}