1use crate::ast::AstPattern;
28use pattern_core::{Pattern, RangeValue, Subject, Symbol, Value};
29use std::collections::{HashMap, HashSet};
30
31pub fn gram_parse_to_json(input: &str) -> Result<String, String> {
52 if input.trim().is_empty() {
53 return Ok("[]".to_string());
54 }
55 let patterns = crate::parse_gram(input).map_err(|e| e.to_string())?;
56 let asts: Vec<AstPattern> = patterns.iter().map(AstPattern::from_pattern).collect();
57 serde_json::to_string(&asts).map_err(|e| e.to_string())
58}
59
60pub fn gram_stringify_from_json(input: &str) -> Result<String, String> {
80 let asts: Vec<AstPattern> = serde_json::from_str(input).map_err(|e| e.to_string())?;
81 let patterns: Vec<Pattern<Subject>> = asts
82 .iter()
83 .map(ast_to_pattern)
84 .collect::<Result<Vec<_>, _>>()?;
85 let gram_parts: Result<Vec<String>, String> = patterns
86 .iter()
87 .map(|p| crate::to_gram_pattern(p).map_err(|e| e.to_string()))
88 .collect();
89 Ok(gram_parts?.join(" "))
90}
91
92pub fn gram_validate_to_json(input: &str) -> String {
102 match crate::validate_gram(input) {
103 Ok(()) => "[]".to_string(),
104 Err(e) => {
105 let msg = e.to_string();
106 serde_json::to_string(&[msg]).unwrap_or_else(|_| "[]".to_string())
107 }
108 }
109}
110
111fn ast_to_pattern(ast: &AstPattern) -> Result<Pattern<Subject>, String> {
113 let subject = Subject {
114 identity: Symbol(ast.subject.identity.clone()),
115 labels: ast.subject.labels.iter().cloned().collect::<HashSet<_>>(),
116 properties: ast
117 .subject
118 .properties
119 .iter()
120 .map(|(k, v)| json_to_value(v).map(|val| (k.clone(), val)))
121 .collect::<Result<HashMap<_, _>, _>>()?,
122 };
123 let elements: Vec<Pattern<Subject>> = ast
124 .elements
125 .iter()
126 .map(ast_to_pattern)
127 .collect::<Result<Vec<_>, _>>()?;
128 if elements.is_empty() {
129 Ok(Pattern::point(subject))
130 } else {
131 Ok(Pattern::pattern(subject, elements))
132 }
133}
134
135fn json_to_value(v: &serde_json::Value) -> Result<Value, String> {
137 match v {
138 serde_json::Value::String(s) => Ok(Value::VString(s.clone())),
139 serde_json::Value::Bool(b) => Ok(Value::VBoolean(*b)),
140 serde_json::Value::Null => {
141 Err("JSON null is not representable as a gram value".to_string())
142 }
143 serde_json::Value::Number(n) => {
144 if let Some(i) = n.as_i64() {
145 Ok(Value::VInteger(i))
146 } else {
147 Ok(Value::VDecimal(n.as_f64().unwrap_or(0.0)))
148 }
149 }
150 serde_json::Value::Array(arr) => {
151 let items: Vec<Value> = arr
152 .iter()
153 .map(json_to_value)
154 .collect::<Result<Vec<_>, _>>()?;
155 Ok(Value::VArray(items))
156 }
157 serde_json::Value::Object(obj) => {
158 if let Some(type_tag) = obj.get("type").and_then(|t| t.as_str()) {
160 match type_tag {
161 "symbol" => {
162 let val = obj
163 .get("value")
164 .and_then(|v| v.as_str())
165 .ok_or_else(|| "symbol value must be a string".to_string())?
166 .to_string();
167 Ok(Value::VSymbol(val))
168 }
169 "range" => {
170 let lower = obj.get("lower").and_then(|v| v.as_f64());
171 let upper = obj.get("upper").and_then(|v| v.as_f64());
172 Ok(Value::VRange(RangeValue { lower, upper }))
173 }
174 "measurement" => {
175 let unit = obj
176 .get("unit")
177 .and_then(|v| v.as_str())
178 .ok_or_else(|| "measurement unit must be a string".to_string())?
179 .to_string();
180 let value = obj
181 .get("value")
182 .and_then(|v| v.as_f64())
183 .ok_or_else(|| "measurement value must be a number".to_string())?;
184 Ok(Value::VMeasurement { unit, value })
185 }
186 "tagged" => {
187 let tag = obj
188 .get("tag")
189 .and_then(|v| v.as_str())
190 .ok_or_else(|| "tagged value tag must be a string".to_string())?
191 .to_string();
192 let content = obj
193 .get("content")
194 .and_then(|v| v.as_str())
195 .ok_or_else(|| "tagged value content must be a string".to_string())?
196 .to_string();
197 Ok(Value::VTaggedString { tag, content })
198 }
199 _ => Err(format!("unknown tagged value type: {}", type_tag)),
200 }
201 } else {
202 let map: HashMap<String, Value> = obj
204 .iter()
205 .map(|(k, v)| json_to_value(v).map(|val| (k.clone(), val)))
206 .collect::<Result<HashMap<_, _>, _>>()?;
207 Ok(Value::VMap(map))
208 }
209 }
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_parse_empty_input() {
219 assert_eq!(gram_parse_to_json("").unwrap(), "[]");
220 assert_eq!(gram_parse_to_json(" ").unwrap(), "[]");
221 }
222
223 #[test]
224 fn test_parse_simple_node() {
225 let json = gram_parse_to_json("(alice:Person)").unwrap();
226 let parsed: Vec<serde_json::Value> = serde_json::from_str(&json).unwrap();
227 assert_eq!(parsed.len(), 1);
228 assert_eq!(parsed[0]["subject"]["identity"], "alice");
229 assert_eq!(parsed[0]["subject"]["labels"][0], "Person");
230 }
231
232 #[test]
233 fn test_parse_node_with_properties() {
234 let json = gram_parse_to_json(r#"(a {name: "Alice", age: 30})"#).unwrap();
235 let parsed: Vec<serde_json::Value> = serde_json::from_str(&json).unwrap();
236 assert_eq!(parsed[0]["subject"]["properties"]["name"], "Alice");
237 assert_eq!(parsed[0]["subject"]["properties"]["age"], 30);
238 }
239
240 #[test]
241 fn test_parse_relationship() {
242 let json = gram_parse_to_json("(a)-->(b)").unwrap();
243 let parsed: Vec<serde_json::Value> = serde_json::from_str(&json).unwrap();
244 assert_eq!(parsed.len(), 1);
245 assert_eq!(parsed[0]["elements"].as_array().unwrap().len(), 2);
246 }
247
248 #[test]
249 fn test_stringify_round_trip() {
250 let original = "(alice:Person)";
251 let json = gram_parse_to_json(original).unwrap();
252 let gram = gram_stringify_from_json(&json).unwrap();
253 let json2 = gram_parse_to_json(&gram).unwrap();
255 let p1: Vec<serde_json::Value> = serde_json::from_str(&json).unwrap();
256 let p2: Vec<serde_json::Value> = serde_json::from_str(&json2).unwrap();
257 assert_eq!(p1[0]["subject"]["identity"], p2[0]["subject"]["identity"]);
258 assert_eq!(p1[0]["subject"]["labels"], p2[0]["subject"]["labels"]);
259 }
260
261 #[test]
262 fn test_validate_valid_input() {
263 let result = gram_validate_to_json("(alice:Person)");
264 let errors: Vec<String> = serde_json::from_str(&result).unwrap();
265 assert!(errors.is_empty());
266 }
267
268 #[test]
269 fn test_validate_invalid_input() {
270 let result = gram_validate_to_json("(((invalid");
271 let errors: Vec<String> = serde_json::from_str(&result).unwrap();
272 assert!(!errors.is_empty());
273 }
274
275 #[test]
276 fn test_json_interchange_format_subject_key() {
277 let json = gram_parse_to_json("(x)").unwrap();
279 assert!(json.contains("\"subject\""));
280 assert!(!json.contains("\"value\"") || json.contains("\"value\":"));
281 }
282
283 #[test]
284 fn test_value_types_in_json() {
285 let json = gram_parse_to_json(r#"(a {s: "hello", i: 42, f: 3.14, b: true})"#).unwrap();
286 let parsed: Vec<serde_json::Value> = serde_json::from_str(&json).unwrap();
287 let props = &parsed[0]["subject"]["properties"];
288 assert!(props["s"].is_string());
289 assert!(props["i"].is_number());
290 assert!(props["f"].is_number());
291 assert!(props["b"].is_boolean());
292 }
293
294 #[test]
295 fn test_json_to_value_tagged_types() {
296 let v = json_to_value(&serde_json::json!({"type": "symbol", "value": "foo"})).unwrap();
298 assert!(matches!(v, Value::VSymbol(_)));
299
300 let v =
302 json_to_value(&serde_json::json!({"type": "measurement", "unit": "kg", "value": 5.0}))
303 .unwrap();
304 assert!(matches!(v, Value::VMeasurement { .. }));
305
306 let v = json_to_value(
308 &serde_json::json!({"type": "tagged", "tag": "date", "content": "2024-01-01"}),
309 )
310 .unwrap();
311 assert!(matches!(v, Value::VTaggedString { .. }));
312
313 let v = json_to_value(&serde_json::json!({"type": "range", "lower": 1.0, "upper": 10.0}))
315 .unwrap();
316 assert!(matches!(v, Value::VRange(_)));
317 }
318
319 #[test]
320 fn test_json_to_value_rejects_null() {
321 let err = json_to_value(&serde_json::Value::Null).unwrap_err();
322 assert!(err.contains("not representable"));
323 }
324
325 #[test]
326 fn test_json_to_value_rejects_unknown_tagged_type() {
327 let err = json_to_value(&serde_json::json!({"type": "unknown", "value": 1})).unwrap_err();
328 assert!(err.contains("unknown tagged value type"));
329 }
330
331 #[test]
332 fn test_stringify_rejects_null_property_in_json() {
333 let err = gram_stringify_from_json(
334 r#"[{"subject":{"identity":"alice","labels":["Person"],"properties":{"nickname":null}},"elements":[]}]"#,
335 )
336 .unwrap_err();
337 assert!(err.contains("not representable"));
338 }
339
340 #[test]
341 fn test_stringify_rejects_malformed_tagged_value() {
342 let err = gram_stringify_from_json(
343 r#"[{"subject":{"identity":"alice","labels":["Person"],"properties":{"code":{"type":"symbol"}}},"elements":[]}]"#,
344 )
345 .unwrap_err();
346 assert!(err.contains("symbol value must be a string"));
347 }
348}