diff --git a/src/api/schema.rs b/src/api/schema.rs index 009e8efb..6f4baaf3 100644 --- a/src/api/schema.rs +++ b/src/api/schema.rs @@ -92,6 +92,23 @@ impl IntegerSchema { self.maximum = Some(maximium); self } + + fn check_constraints(&self, value: isize) -> Result<(), Error> { + + if let Some(minimum) = self.minimum { + if value < minimum { + bail!("value must have a minimum value of {}", minimum); + } + } + + if let Some(maximum) = self.maximum { + if value > maximum { + bail!("value must have a maximum value of {}", maximum); + } + } + + Ok(()) + } } @@ -153,6 +170,34 @@ impl StringSchema { Ok(()) } + pub fn check_constraints(&self, value: &str) -> Result<(), Error> { + + self.check_length(value.chars().count())?; + + if let Some(ref format) = self.format { + match format.as_ref() { + ApiStringFormat::Pattern(ref regex) => { + if !regex.is_match(value) { + bail!("value does not match the regex pattern"); + } + } + ApiStringFormat::Enum(ref stringvec) => { + if stringvec.iter().find(|&e| *e == value) == None { + bail!("value is not defined in the enumeration."); + } + } + ApiStringFormat::Complex(ref subschema) => { + parse_property_string(value, subschema)?; + } + ApiStringFormat::VerifyFn(verify_fn) => { + verify_fn(value)?; + } + } + } + + Ok(()) + } + } #[derive(Debug)] @@ -403,48 +448,12 @@ pub fn parse_simple_value(value_str: &str, schema: &Schema) -> Result { let res: isize = value_str.parse()?; - - if let Some(minimum) = integer_schema.minimum { - if res < minimum { - bail!("value must have a minimum value of {}", minimum); - } - } - - if let Some(maximum) = integer_schema.maximum { - if res > maximum { - bail!("value must have a maximum value of {}", maximum); - } - } - + integer_schema.check_constraints(res)?; Value::Number(res.into()) } Schema::String(string_schema) => { - let res: String = value_str.into(); - - string_schema.check_length(res.chars().count())?; - - if let Some(ref format) = string_schema.format { - match format.as_ref() { - ApiStringFormat::Pattern(ref regex) => { - if !regex.is_match(&res) { - bail!("value does not match the regex pattern"); - } - } - ApiStringFormat::Enum(ref stringvec) => { - if stringvec.iter().find(|&e| *e == res) == None { - bail!("value is not defined in the enumeration."); - } - } - ApiStringFormat::Complex(ref subschema) => { - parse_property_string(&res, subschema)?; - } - ApiStringFormat::VerifyFn(verify_fn) => { - verify_fn(&res)?; - } - } - } - - Value::String(res) + string_schema.check_constraints(value_str)?; + Value::String(value_str.into()) } _ => bail!("unable to parse complex (sub) objects."), }; @@ -536,6 +545,106 @@ pub fn parse_query_string(query: &str, schema: &ObjectSchema, test_required: boo parse_parameter_strings(¶m_list, schema, test_required) } +pub fn verify_json(data: &Value, schema: &Schema) -> Result<(), Error> { + + match schema { + Schema::Object(object_schema) => { + verify_json_object(data, &object_schema)?; + } + Schema::Array(array_schema) => { + verify_json_array(data, &array_schema)?; + } + Schema::Null => { + if !data.is_null() { + bail!("Expected Null, but value is not Null."); + } + } + Schema::Boolean(boolean_schema) => verify_json_boolean(data, &boolean_schema)?, + Schema::Integer(integer_schema) => verify_json_integer(data, &integer_schema)?, + Schema::String(string_schema) => verify_json_string(data, &string_schema)?, + } + Ok(()) +} + +pub fn verify_json_string(data: &Value, schema: &StringSchema) -> Result<(), Error> { + if let Some(value) = data.as_str() { + schema.check_constraints(value) + } else { + bail!("Expected string value."); + } +} + +pub fn verify_json_boolean(data: &Value, schema: &BooleanSchema) -> Result<(), Error> { + if !data.is_boolean() { + bail!("Expected boolean value."); + } + Ok(()) +} + +pub fn verify_json_integer(data: &Value, schema: &IntegerSchema) -> Result<(), Error> { + + if let Some(value) = data.as_i64() { + schema.check_constraints(value as isize) + } else { + bail!("Expected integer value."); + } +} + +pub fn verify_json_array(data: &Value, schema: &ArraySchema) -> Result<(), Error> { + + let list = match data { + Value::Array(ref list) => list, + Value::Object(_) => bail!("Expected array - got object."), + _ => bail!("Expected array - got scalar value."), + }; + + schema.check_length(list.len())?; + + for item in list { + verify_json(item, &schema.items)?; + } + + Ok(()) +} + +pub fn verify_json_object(data: &Value, schema: &ObjectSchema) -> Result<(), Error> { + + let map = match data { + Value::Object(ref map) => map, + Value::Array(_) => bail!("Expected object - got array."), + _ => bail!("Expected object - got scalar value."), + }; + + let properties = &schema.properties; + let additional_properties = schema.additional_properties; + + for (key, value) in map { + if let Some((_optional, prop_schema)) = properties.get::(key) { + match prop_schema.as_ref() { + Schema::Object(object_schema) => { + verify_json_object(value, object_schema)?; + } + Schema::Array(array_schema) => { + verify_json_array(value, array_schema)?; + } + _ => verify_json(value, prop_schema)?, + } + } else { + if !additional_properties { + bail!("property '{}': schema does not allow additional properties.", key); + } + } + } + + for (name, (optional, _prop_schema)) in properties { + if *optional == false && data[name] == Value::Null { + bail!("property '{}': property is missing and it is not optional.", name); + } + } + + Ok(()) +} + #[test] fn test_schema1() { let schema = Schema::Object(ObjectSchema {