dpp/util/
json_schema.rs

1use anyhow::{anyhow, bail};
2use platform_value::Value;
3use serde_json::Value as JsonValue;
4
5use crate::data_contract::errors::DataContractError;
6use crate::identifier;
7
8pub trait JsonSchemaExt {
9    /// returns true if json value contains property 'type`, and it equals 'object'
10    fn is_type_of_object(&self) -> bool;
11    /// returns true if json value contains property 'type`, and it equals 'array'
12    fn is_type_of_array(&self) -> bool;
13    /// returns true if json value contains property `byteArray` and it equals true
14    fn is_type_of_byte_array(&self) -> bool;
15    /// returns true if json value contains property 'type`, and it equals 'string'
16    fn is_type_of_string(&self) -> bool;
17    /// returns the properties of Json Schema object
18    fn get_schema_properties(&self) -> Result<&JsonValue, anyhow::Error>;
19    /// returns the required fields of Json Schema object
20    fn get_schema_required_fields(&self) -> Result<Vec<&str>, anyhow::Error>;
21    /// returns the indexes from Json Schema
22    // fn get_indices<I: FromIterator<Index>>(&self) -> Result<I, anyhow::Error>;
23    /// returns the indexes from Json Schema
24    // fn get_indices_map<I: FromIterator<(String, Index)>>(&self) -> Result<I, anyhow::Error>;
25    /// returns true if json value contains property `contentMediaType` and it equals to Identifier
26    fn is_type_of_identifier(&self) -> bool;
27}
28
29// TODO: This is big (due to using regex inside?)
30pub fn resolve_uri<'a>(value: &'a Value, uri: &str) -> Result<&'a Value, DataContractError> {
31    if !uri.starts_with("#/") {
32        return Err(DataContractError::InvalidURI(
33            "only local uri references are allowed".to_string(),
34        ));
35    }
36
37    let string_path = uri.strip_prefix("#/").unwrap().replace('/', ".");
38    value.get_value_at_path(&string_path).map_err(|e| e.into())
39}
40
41impl JsonSchemaExt for JsonValue {
42    fn get_schema_required_fields(&self) -> Result<Vec<&str>, anyhow::Error> {
43        if let JsonValue::Object(ref map) = self {
44            let required = map.get("required");
45            if required.is_none() {
46                return Ok(vec![]);
47            }
48            if let JsonValue::Array(required_list) = required.unwrap() {
49                return required_list
50                    .iter()
51                    .map(|v| v.as_str())
52                    .collect::<Option<Vec<&str>>>()
53                    .ok_or_else(|| anyhow!("unable to convert list of required fields to string"));
54            }
55            bail!("the 'required' property is not array");
56        }
57        bail!("the json value is not a map");
58    }
59
60    fn is_type_of_string(&self) -> bool {
61        if let JsonValue::Object(ref map) = self {
62            if let Some(JsonValue::String(schema_type)) = map.get("type") {
63                return schema_type == "string";
64            }
65        }
66        false
67    }
68
69    fn is_type_of_object(&self) -> bool {
70        if let JsonValue::Object(ref map) = self {
71            if let Some(JsonValue::String(schema_type)) = map.get("type") {
72                return schema_type == "object";
73            }
74        }
75        false
76    }
77
78    fn is_type_of_array(&self) -> bool {
79        if let JsonValue::Object(ref map) = self {
80            if let Some(JsonValue::String(schema_type)) = map.get("type") {
81                return schema_type == "array";
82            }
83        }
84        false
85    }
86
87    fn is_type_of_byte_array(&self) -> bool {
88        if let JsonValue::Object(ref map) = self {
89            if let Some(JsonValue::Bool(is_byte_array)) = map.get("byteArray") {
90                return *is_byte_array;
91            }
92        }
93        false
94    }
95
96    fn get_schema_properties(&self) -> Result<&JsonValue, anyhow::Error> {
97        if let JsonValue::Object(ref map) = self {
98            return map
99                .get("properties")
100                .ok_or_else(|| anyhow!("Couldn't find 'properties' in '{:?}'", map));
101        }
102        bail!("the {:?} isn't an map", self);
103    }
104
105    fn is_type_of_identifier(&self) -> bool {
106        if let JsonValue::Object(ref map) = self {
107            if let Some(JsonValue::String(media_type)) = map.get("contentMediaType") {
108                return media_type == identifier::MEDIA_TYPE;
109            }
110        }
111        false
112    }
113}
114
115#[cfg(test)]
116mod test {
117    use crate::data_contract::config::DataContractConfig;
118    use crate::data_contract::document_type::accessors::DocumentTypeV0Getters;
119    use crate::data_contract::document_type::DocumentType;
120    use std::collections::BTreeMap;
121
122    use platform_value::Identifier;
123    use platform_version::version::PlatformVersion;
124    use serde_json::json;
125
126    #[test]
127    fn test_extract_indices() {
128        let platform_version = PlatformVersion::latest();
129        let input = json!({
130            "type": "object",
131            "indices": [
132                {
133                    "properties": [
134                        {
135                            "$ownerId": "asc"
136                        }
137                    ],
138                    "name": "&ownerId",
139                    "unique": true
140                },
141                {
142                    "properties": [
143                        {
144                            "$ownerId": "asc"
145                        },
146                        {
147                            "$updatedAt": "asc"
148                        }
149                    ],
150                    "name": "&ownerId&updatedAt"
151                }
152            ],
153            "properties": {
154                "avatarUrl": {
155                    "type": "string",
156                    "format": "uri",
157                    "maxLength": 2048,
158                    "position": 0
159                },
160                "publicMessage": {
161                    "type": "string",
162                    "maxLength": 140,
163                    "position": 1
164                },
165                "displayName": {
166                    "type": "string",
167                    "maxLength": 25,
168                    "position": 2
169                }
170            },
171            "required": [
172                "$createdAt",
173                "$updatedAt"
174            ],
175            "additionalProperties": false
176        });
177
178        let platform_value = platform_value::to_value(input).unwrap();
179
180        let config = DataContractConfig::default_for_version(platform_version)
181            .expect("should create a default config");
182
183        let document_type = DocumentType::try_from_schema(
184            Identifier::random(),
185            1,
186            config.version(),
187            "doc",
188            platform_value,
189            None,
190            &BTreeMap::new(),
191            &config,
192            false,
193            &mut vec![],
194            platform_version,
195        )
196        .unwrap();
197
198        let indices = document_type.indexes();
199
200        assert_eq!(indices.len(), 2);
201
202        assert_eq!(indices["&ownerId"].name, "&ownerId");
203        assert_eq!(indices["&ownerId"].properties.len(), 1);
204        assert_eq!(indices["&ownerId"].properties[0].name, "$ownerId");
205        assert!(indices["&ownerId"].properties[0].ascending);
206        assert!(indices["&ownerId"].unique);
207
208        assert_eq!(indices["&ownerId&updatedAt"].name, "&ownerId&updatedAt");
209        assert_eq!(indices["&ownerId&updatedAt"].properties.len(), 2);
210        assert_eq!(indices["&ownerId&updatedAt"].properties[0].name, "$ownerId");
211        assert_eq!(
212            indices["&ownerId&updatedAt"].properties[1].name,
213            "$updatedAt"
214        );
215        assert!(!indices["&ownerId&updatedAt"].unique);
216    }
217}