use crate::ast::AstPattern;
use pattern_core::{Pattern, RangeValue, Subject, Symbol, Value};
use std::collections::{HashMap, HashSet};
pub fn gram_parse_to_json(input: &str) -> Result<String, String> {
if input.trim().is_empty() {
return Ok("[]".to_string());
}
let patterns = crate::parse_gram(input).map_err(|e| e.to_string())?;
let asts: Vec<AstPattern> = patterns.iter().map(AstPattern::from_pattern).collect();
serde_json::to_string(&asts).map_err(|e| e.to_string())
}
pub fn gram_stringify_from_json(input: &str) -> Result<String, String> {
let asts: Vec<AstPattern> = serde_json::from_str(input).map_err(|e| e.to_string())?;
let patterns: Vec<Pattern<Subject>> = asts
.iter()
.map(ast_to_pattern)
.collect::<Result<Vec<_>, _>>()?;
let gram_parts: Result<Vec<String>, String> = patterns
.iter()
.map(|p| crate::to_gram_pattern(p).map_err(|e| e.to_string()))
.collect();
Ok(gram_parts?.join(" "))
}
pub fn gram_validate_to_json(input: &str) -> String {
match crate::validate_gram(input) {
Ok(()) => "[]".to_string(),
Err(e) => {
let msg = e.to_string();
serde_json::to_string(&[msg]).unwrap_or_else(|_| "[]".to_string())
}
}
}
pub fn ast_to_pattern(ast: &AstPattern) -> Result<Pattern<Subject>, String> {
let subject = Subject {
identity: Symbol(ast.subject.identity.clone()),
labels: ast.subject.labels.iter().cloned().collect::<HashSet<_>>(),
properties: ast
.subject
.properties
.iter()
.map(|(k, v)| json_to_value(v).map(|val| (k.clone(), val)))
.collect::<Result<HashMap<_, _>, _>>()?,
};
let elements: Vec<Pattern<Subject>> = ast
.elements
.iter()
.map(ast_to_pattern)
.collect::<Result<Vec<_>, _>>()?;
if elements.is_empty() {
Ok(Pattern::point(subject))
} else {
Ok(Pattern::pattern(subject, elements))
}
}
fn json_to_value(v: &serde_json::Value) -> Result<Value, String> {
match v {
serde_json::Value::String(s) => Ok(Value::VString(s.clone())),
serde_json::Value::Bool(b) => Ok(Value::VBoolean(*b)),
serde_json::Value::Null => {
Err("JSON null is not representable as a gram value".to_string())
}
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Ok(Value::VInteger(i))
} else if let Some(f) = n.as_f64() {
Ok(Value::VDecimal(f))
} else {
Err(format!(
"JSON number is not representable as a gram decimal value: {}",
n
))
}
}
serde_json::Value::Array(arr) => {
let items: Vec<Value> = arr
.iter()
.map(json_to_value)
.collect::<Result<Vec<_>, _>>()?;
Ok(Value::VArray(items))
}
serde_json::Value::Object(obj) => {
if let Some(type_tag) = obj.get("type").and_then(|t| t.as_str()) {
match type_tag {
"symbol" => {
let val = obj
.get("value")
.and_then(|v| v.as_str())
.ok_or_else(|| "symbol value must be a string".to_string())?
.to_string();
Ok(Value::VSymbol(val))
}
"range" => {
let lower = obj.get("lower").and_then(|v| v.as_f64());
let upper = obj.get("upper").and_then(|v| v.as_f64());
Ok(Value::VRange(RangeValue { lower, upper }))
}
"measurement" => {
let unit = obj
.get("unit")
.and_then(|v| v.as_str())
.ok_or_else(|| "measurement unit must be a string".to_string())?
.to_string();
let value = obj
.get("value")
.and_then(|v| v.as_f64())
.ok_or_else(|| "measurement value must be a number".to_string())?;
Ok(Value::VMeasurement { unit, value })
}
"tagged" => {
let tag = obj
.get("tag")
.and_then(|v| v.as_str())
.ok_or_else(|| "tagged value tag must be a string".to_string())?
.to_string();
let content = obj
.get("content")
.and_then(|v| v.as_str())
.ok_or_else(|| "tagged value content must be a string".to_string())?
.to_string();
Ok(Value::VTaggedString { tag, content })
}
_ => Err(format!("unknown tagged value type: {}", type_tag)),
}
} else {
let map: HashMap<String, Value> = obj
.iter()
.map(|(k, v)| json_to_value(v).map(|val| (k.clone(), val)))
.collect::<Result<HashMap<_, _>, _>>()?;
Ok(Value::VMap(map))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_empty_input() {
assert_eq!(gram_parse_to_json("").unwrap(), "[]");
assert_eq!(gram_parse_to_json(" ").unwrap(), "[]");
}
#[test]
fn test_parse_simple_node() {
let json = gram_parse_to_json("(alice:Person)").unwrap();
let parsed: Vec<serde_json::Value> = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.len(), 1);
assert_eq!(parsed[0]["subject"]["identity"], "alice");
assert_eq!(parsed[0]["subject"]["labels"][0], "Person");
}
#[test]
fn test_parse_node_with_properties() {
let json = gram_parse_to_json(r#"(a {name: "Alice", age: 30})"#).unwrap();
let parsed: Vec<serde_json::Value> = serde_json::from_str(&json).unwrap();
assert_eq!(parsed[0]["subject"]["properties"]["name"], "Alice");
assert_eq!(parsed[0]["subject"]["properties"]["age"], 30);
}
#[test]
fn test_parse_relationship() {
let json = gram_parse_to_json("(a)-->(b)").unwrap();
let parsed: Vec<serde_json::Value> = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.len(), 1);
assert_eq!(parsed[0]["elements"].as_array().unwrap().len(), 2);
}
#[test]
fn test_stringify_round_trip() {
let original = "(alice:Person)";
let json = gram_parse_to_json(original).unwrap();
let gram = gram_stringify_from_json(&json).unwrap();
let json2 = gram_parse_to_json(&gram).unwrap();
let p1: Vec<serde_json::Value> = serde_json::from_str(&json).unwrap();
let p2: Vec<serde_json::Value> = serde_json::from_str(&json2).unwrap();
assert_eq!(p1[0]["subject"]["identity"], p2[0]["subject"]["identity"]);
assert_eq!(p1[0]["subject"]["labels"], p2[0]["subject"]["labels"]);
}
#[test]
fn test_validate_valid_input() {
let result = gram_validate_to_json("(alice:Person)");
let errors: Vec<String> = serde_json::from_str(&result).unwrap();
assert!(errors.is_empty());
}
#[test]
fn test_validate_invalid_input() {
let result = gram_validate_to_json("(((invalid");
let errors: Vec<String> = serde_json::from_str(&result).unwrap();
assert!(!errors.is_empty());
}
#[test]
fn test_json_interchange_format_subject_key() {
let json = gram_parse_to_json("(x)").unwrap();
assert!(json.contains("\"subject\""));
assert!(!json.contains("\"value\"") || json.contains("\"value\":"));
}
#[test]
fn test_value_types_in_json() {
let json = gram_parse_to_json(r#"(a {s: "hello", i: 42, f: 3.14, b: true})"#).unwrap();
let parsed: Vec<serde_json::Value> = serde_json::from_str(&json).unwrap();
let props = &parsed[0]["subject"]["properties"];
assert!(props["s"].is_string());
assert!(props["i"].is_number());
assert!(props["f"].is_number());
assert!(props["b"].is_boolean());
}
#[test]
fn test_json_to_value_tagged_types() {
let v = json_to_value(&serde_json::json!({"type": "symbol", "value": "foo"})).unwrap();
assert!(matches!(v, Value::VSymbol(_)));
let v =
json_to_value(&serde_json::json!({"type": "measurement", "unit": "kg", "value": 5.0}))
.unwrap();
assert!(matches!(v, Value::VMeasurement { .. }));
let v = json_to_value(
&serde_json::json!({"type": "tagged", "tag": "date", "content": "2024-01-01"}),
)
.unwrap();
assert!(matches!(v, Value::VTaggedString { .. }));
let v = json_to_value(&serde_json::json!({"type": "range", "lower": 1.0, "upper": 10.0}))
.unwrap();
assert!(matches!(v, Value::VRange(_)));
}
#[test]
fn test_json_to_value_rejects_null() {
let err = json_to_value(&serde_json::Value::Null).unwrap_err();
assert!(err.contains("not representable"));
}
#[test]
fn test_json_to_value_rejects_unknown_tagged_type() {
let err = json_to_value(&serde_json::json!({"type": "unknown", "value": 1})).unwrap_err();
assert!(err.contains("unknown tagged value type"));
}
#[test]
fn test_stringify_rejects_null_property_in_json() {
let err = gram_stringify_from_json(
r#"[{"subject":{"identity":"alice","labels":["Person"],"properties":{"nickname":null}},"elements":[]}]"#,
)
.unwrap_err();
assert!(err.contains("not representable"));
}
#[test]
fn test_stringify_rejects_malformed_tagged_value() {
let err = gram_stringify_from_json(
r#"[{"subject":{"identity":"alice","labels":["Person"],"properties":{"code":{"type":"symbol"}}},"elements":[]}]"#,
)
.unwrap_err();
assert!(err.contains("symbol value must be a string"));
}
}