use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AstPattern {
pub subject: AstSubject,
pub elements: Vec<AstPattern>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AstSubject {
pub identity: String,
pub labels: Vec<String>,
pub properties: HashMap<String, serde_json::Value>,
}
impl AstPattern {
pub fn empty() -> Self {
AstPattern {
subject: AstSubject {
identity: String::new(),
labels: Vec::new(),
properties: HashMap::new(),
},
elements: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParseWithHeaderResult {
pub header: Option<HashMap<String, serde_json::Value>>,
pub patterns: Vec<AstPattern>,
}
impl ParseWithHeaderResult {
pub fn from_parts(
header: Option<pattern_core::PropertyRecord>,
patterns: Vec<pattern_core::Pattern<pattern_core::Subject>>,
) -> Self {
ParseWithHeaderResult {
header: header.map(|h| {
h.iter()
.map(|(k, v)| (k.clone(), value_to_json(v)))
.collect()
}),
patterns: patterns.iter().map(AstPattern::from_pattern).collect(),
}
}
pub fn header_to_record(&self) -> Result<Option<pattern_core::PropertyRecord>, String> {
match &self.header {
None => Ok(None),
Some(map) => {
let record: pattern_core::PropertyRecord = map
.iter()
.map(|(k, v)| json_to_value(v).map(|val| (k.clone(), val)))
.collect::<Result<_, _>>()?;
Ok(Some(record))
}
}
}
}
use pattern_core::{Pattern, Subject, Value};
impl AstPattern {
pub fn to_pattern(&self) -> Result<Pattern<Subject>, String> {
crate::json::ast_to_pattern(self)
}
pub fn from_pattern(pattern: &Pattern<Subject>) -> Self {
let subject = pattern.value();
AstPattern {
subject: AstSubject {
identity: subject.identity.0.clone(),
labels: subject.labels.iter().cloned().collect(),
properties: subject
.properties
.iter()
.map(|(k, v)| (k.clone(), value_to_json(v)))
.collect(),
},
elements: pattern
.elements()
.iter()
.map(AstPattern::from_pattern)
.collect(),
}
}
}
fn value_to_json(value: &Value) -> serde_json::Value {
match value {
Value::VInteger(i) => serde_json::Value::Number((*i).into()),
Value::VDecimal(d) => {
serde_json::Number::from_f64(*d)
.map(serde_json::Value::Number)
.unwrap_or_else(|| serde_json::Value::Null)
}
Value::VBoolean(b) => serde_json::Value::Bool(*b),
Value::VString(s) => serde_json::Value::String(s.clone()),
Value::VArray(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
Value::VMap(map) => serde_json::Value::Object(
map.iter()
.map(|(k, v)| (k.clone(), value_to_json(v)))
.collect(),
),
Value::VSymbol(sym) => serde_json::json!({
"type": "symbol",
"value": sym.clone()
}),
Value::VRange(range) => serde_json::json!({
"type": "range",
"lower": range.lower,
"upper": range.upper
}),
Value::VMeasurement { unit, value } => serde_json::json!({
"type": "measurement",
"unit": unit,
"value": value
}),
Value::VTaggedString { tag, content } => serde_json::json!({
"type": "tagged",
"tag": tag,
"content": content
}),
}
}
pub(crate) fn json_to_value(v: &serde_json::Value) -> Result<Value, String> {
use pattern_core::RangeValue;
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: std::collections::HashMap<String, Value> = obj
.iter()
.map(|(k, v)| json_to_value(v).map(|val| (k.clone(), val)))
.collect::<Result<std::collections::HashMap<_, _>, _>>()?;
Ok(Value::VMap(map))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pattern_core::{Pattern, Subject, Symbol};
use std::collections::{HashMap, HashSet};
#[test]
fn test_empty_pattern() {
let pattern = AstPattern::empty();
assert_eq!(pattern.subject.identity, "");
assert_eq!(pattern.subject.labels.len(), 0);
assert_eq!(pattern.subject.properties.len(), 0);
assert_eq!(pattern.elements.len(), 0);
}
#[test]
fn test_json_serialization() {
let pattern = AstPattern {
subject: AstSubject {
identity: "alice".to_string(),
labels: vec!["Person".to_string()],
properties: {
let mut props = HashMap::new();
props.insert("name".to_string(), serde_json::json!("Alice"));
props
},
},
elements: vec![],
};
let json = serde_json::to_string(&pattern).unwrap();
assert!(json.contains("alice"));
assert!(json.contains("Person"));
let deserialized: AstPattern = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, pattern);
}
#[test]
fn test_nested_patterns() {
let child1 = AstPattern {
subject: AstSubject {
identity: "child1".to_string(),
labels: vec![],
properties: HashMap::new(),
},
elements: vec![],
};
let child2 = AstPattern {
subject: AstSubject {
identity: "child2".to_string(),
labels: vec![],
properties: HashMap::new(),
},
elements: vec![],
};
let parent = AstPattern {
subject: AstSubject {
identity: "parent".to_string(),
labels: vec![],
properties: HashMap::new(),
},
elements: vec![child1, child2],
};
assert_eq!(parent.elements.len(), 2);
assert_eq!(parent.elements[0].subject.identity, "child1");
assert_eq!(parent.elements[1].subject.identity, "child2");
let json = serde_json::to_string(&parent).unwrap();
let deserialized: AstPattern = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.elements.len(), 2);
}
#[test]
fn test_from_pattern_simple() {
let subject = Subject {
identity: Symbol("alice".to_string()),
labels: {
let mut labels = HashSet::new();
labels.insert("Person".to_string());
labels
},
properties: HashMap::new(),
};
let pattern = Pattern::point(subject);
let ast = AstPattern::from_pattern(&pattern);
assert_eq!(ast.subject.identity, "alice");
assert_eq!(ast.subject.labels, vec!["Person"]);
assert_eq!(ast.elements.len(), 0);
}
#[test]
fn test_from_pattern_with_properties() {
let subject = Subject {
identity: Symbol("alice".to_string()),
labels: HashSet::new(),
properties: {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::VString("Alice".to_string()));
props.insert("age".to_string(), Value::VInteger(30));
props
},
};
let pattern = Pattern::point(subject);
let ast = AstPattern::from_pattern(&pattern);
assert_eq!(ast.subject.identity, "alice");
assert_eq!(ast.subject.properties.len(), 2);
assert_eq!(ast.subject.properties.get("name").unwrap(), "Alice");
let age_value = ast.subject.properties.get("age").unwrap();
assert_eq!(age_value, 30); }
#[test]
fn test_value_serialization_simple_types() {
let v = value_to_json(&Value::VInteger(42));
assert_eq!(v, serde_json::json!(42));
assert!(v.is_number());
let v = value_to_json(&Value::VDecimal(3.14));
assert_eq!(v, serde_json::json!(3.14));
assert!(v.is_number());
let v = value_to_json(&Value::VBoolean(true));
assert_eq!(v, serde_json::Value::Bool(true));
let v = value_to_json(&Value::VString("hello".to_string()));
assert_eq!(v, serde_json::Value::String("hello".to_string()));
let v = value_to_json(&Value::VArray(vec![Value::VInteger(1), Value::VInteger(2)]));
assert!(v.is_array());
assert_eq!(v.as_array().unwrap().len(), 2);
assert_eq!(v.as_array().unwrap()[0], serde_json::json!(1));
assert_eq!(v.as_array().unwrap()[1], serde_json::json!(2));
}
#[test]
fn test_value_serialization_tagged_types() {
let v = value_to_json(&Value::VSymbol("user123".to_string()));
assert_eq!(v["type"], "symbol");
assert_eq!(v["value"], "user123");
let v = value_to_json(&Value::VRange(pattern_core::RangeValue {
lower: Some(1.0),
upper: Some(10.0),
}));
assert_eq!(v["type"], "range");
assert_eq!(v["lower"], 1.0);
assert_eq!(v["upper"], 10.0);
let v = value_to_json(&Value::VMeasurement {
unit: "cm".to_string(),
value: 168.0,
});
assert_eq!(v["type"], "measurement");
assert_eq!(v["value"], 168.0);
assert_eq!(v["unit"], "cm");
let v = value_to_json(&Value::VTaggedString {
tag: "date".to_string(),
content: "2024-01-09".to_string(),
});
assert_eq!(v["type"], "tagged");
assert_eq!(v["tag"], "date");
assert_eq!(v["content"], "2024-01-09");
}
#[test]
fn test_value_serialization_map() {
let mut map = HashMap::new();
map.insert("key1".to_string(), Value::VString("value1".to_string()));
map.insert("key2".to_string(), Value::VInteger(42));
let v = value_to_json(&Value::VMap(map));
assert!(v.is_object());
assert_eq!(v["key1"], "value1");
assert_eq!(v["key2"], 42);
}
}