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 fn is_type_of_object(&self) -> bool;
11 fn is_type_of_array(&self) -> bool;
13 fn is_type_of_byte_array(&self) -> bool;
15 fn is_type_of_string(&self) -> bool;
17 fn get_schema_properties(&self) -> Result<&JsonValue, anyhow::Error>;
19 fn get_schema_required_fields(&self) -> Result<Vec<&str>, anyhow::Error>;
21 fn is_type_of_identifier(&self) -> bool;
27}
28
29pub 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}