serde_json_schema/
property.rs

1//! Represents the [Instance Data Model](https://json-schema.org/latest/json-schema-core.html#rfc.section.4.2.1)
2
3use serde::{Deserialize, Serialize};
4
5use std::{collections::HashMap, str::Split};
6
7use crate::{validation::NumberCriteria, Schema};
8
9/// Either a `PropertyInstance` or a reference
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11#[serde(untagged)]
12pub enum Property {
13    Value(PropertyInstance),
14    Ref(RefProperty),
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
18pub struct RefProperty {
19    #[serde(rename = "$ref")]
20    pub reference: String,
21}
22
23#[derive(Debug)]
24enum Data<'a> {
25    Map(&'a HashMap<String, Property>),
26    Prop(&'a Property),
27    Instance(&'a PropertyInstance),
28    Schema(&'a Schema),
29}
30
31fn get_items(p: &Property) -> Option<&PropertyInstance> {
32    match p {
33        Property::Value(PropertyInstance::Array { items }) => Some(&**items),
34        _ => None,
35    }
36}
37
38fn get_properties_instance(p: &PropertyInstance) -> Option<&HashMap<String, Property>> {
39    match p {
40        PropertyInstance::Object { properties, .. } => Some(properties),
41        _ => None,
42    }
43}
44
45fn get_properties(p: &Property) -> Option<&HashMap<String, Property>> {
46    match p {
47        Property::Value(v) => get_properties_instance(v),
48        _ => None,
49    }
50}
51
52fn find_ref<'a>(mut path: Split<'a, char>, mut data: Data<'a>) -> Option<Data<'a>> {
53    loop {
54        let Some(branch) = path.next() else {
55            return Some(data);
56        };
57        data = match (branch, data) {
58            ("properties", Data::Instance(v)) => Data::Map(get_properties_instance(v)?),
59            ("properties", Data::Map(v)) => Data::Map(get_properties(v.get(branch)?)?),
60            ("properties", Data::Prop(v)) => Data::Map(get_properties(v)?),
61            ("properties", Data::Schema(v)) => Data::Map(v.properties()?),
62            ("items", Data::Prop(v)) => Data::Instance(get_items(v)?),
63            (_, Data::Map(v)) => Data::Prop(v.get(branch)?),
64            _ => return None,
65        };
66    }
67}
68
69impl RefProperty {
70    pub fn deref<'a>(&'a self, schema: &'a Schema) -> Option<&PropertyInstance> {
71        let reference = self.reference.strip_prefix("#/")?;
72        let path = reference.split('/');
73        match find_ref(path, Data::Schema(schema))? {
74            Data::Prop(v) => match v {
75                Property::Ref(v) => Some(v.deref(schema)?),
76                Property::Value(v) => Some(v),
77            },
78            Data::Instance(v) => Some(v),
79            _ => None,
80        }
81    }
82}
83
84/// Represents the [Instance Data Model](https://json-schema.org/latest/json-schema-core.html#rfc.section.4.2.1)
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
86#[serde(tag = "type", rename_all = "camelCase")]
87pub enum PropertyInstance {
88    Null,
89
90    Boolean,
91
92    Integer {
93        #[serde(flatten)]
94        criteria: NumberCriteria,
95    },
96    Object {
97        properties: HashMap<String, Property>,
98        required: Option<Vec<String>>,
99    },
100
101    Array {
102        items: Box<PropertyInstance>,
103    },
104
105    Number {
106        #[serde(flatten)]
107        criteria: NumberCriteria,
108    },
109
110    String,
111}
112
113impl PropertyInstance {
114    /// TODO: implement [validation](https://json-schema.org/latest/json-schema-validation.html)
115    pub fn validate(&self, json: &serde_json::Value) -> Result<(), Vec<String>> {
116        use serde_json::Value;
117        use PropertyInstance::*;
118
119        match (&self, json) {
120            (Null, Value::Null) => Ok(()),
121            (Null, unexpected_value) => {
122                Err(vec![format!("expected null found {:?}", unexpected_value)])
123            }
124
125            (Boolean, Value::Bool(_)) => Ok(()),
126            (Boolean, unexpected_value) => Err(vec![format!(
127                "expected boolean found {:?}",
128                unexpected_value
129            )]),
130
131            (String, Value::String(_)) => Ok(()),
132            (String, unexpected_value) => Err(vec![format!(
133                "expected string found {:?}",
134                unexpected_value
135            )]),
136
137            (Number { .. }, Value::Number(_)) => Ok(()),
138            (Number { .. }, unexpected_value) => Err(vec![format!(
139                "expected number found {:?}",
140                unexpected_value
141            )]),
142
143            (Integer { .. }, Value::Number(i)) if i.is_i64() => Ok(()),
144            (Integer { .. }, unexpected_value) => Err(vec![format!(
145                "expected integer found {:?}",
146                unexpected_value
147            )]),
148
149            (Array { items }, Value::Array(elems)) => {
150                let errors: Vec<std::string::String> = elems
151                    .iter()
152                    .map(|value| items.validate(value))
153                    .filter_map(Result::err)
154                    .flat_map(|errors| errors.into_iter())
155                    .collect();
156                if errors.is_empty() {
157                    Ok(())
158                } else {
159                    Err(errors)
160                }
161            }
162            (Array { .. }, unexpected_value) => {
163                Err(vec![format!("expected array found {:?}", unexpected_value)])
164            }
165
166            (
167                Object {
168                    properties,
169                    required,
170                },
171                Value::Object(object),
172            ) => {
173                let errors: Vec<std::string::String> = properties
174                    .iter()
175                    .filter_map(|(k, schema)| {
176                        object
177                            .get(k)
178                            .map(|v| match schema {
179                                Property::Value(schema) => schema.validate(v).err(),
180                                Property::Ref(_schema) => unimplemented!(),
181                            })
182                            .unwrap_or_else(|| {
183                                if required.iter().flat_map(|v| v.iter()).any(|x| x == k) {
184                                    Some(vec![format!(
185                                        "object doesn't contain the required property {:?}",
186                                        k
187                                    )])
188                                } else {
189                                    None
190                                }
191                            })
192                    })
193                    .flat_map(|errors| errors.into_iter())
194                    .collect();
195                if errors.is_empty() {
196                    Ok(())
197                } else {
198                    Err(errors)
199                }
200            }
201
202            (Object { .. }, _) => Err(vec![format!("invalid object")]),
203        }
204    }
205}