Skip to main content

morphir_core/metadata/
term.rs

1//! Expanded fact terms and graph identity.
2
3use crate::node_address::NodeUri;
4use serde_json::Value;
5
6/// The graph containing a fact. Only [`GraphName::Default`] executes today.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum GraphName {
9    /// The default graph used by the first executable increment.
10    Default,
11    /// A future named graph, retained as a distinct logical identity.
12    Named(NodeUri),
13}
14
15/// Data kept apart from a node reference.
16///
17/// This is a normalized storage term. Callers must validate the value against
18/// its predicate declaration before claiming it has validated semantics.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct TypedValue {
21    value: Value,
22    datatype: Option<NodeUri>,
23}
24
25impl TypedValue {
26    /// A data value without an explicit `@json` type.
27    pub fn new(value: Value) -> Self {
28        Self {
29            value,
30            datatype: None,
31        }
32    }
33
34    /// A single structured `@json` object with its expanded type declaration.
35    /// This constructor does not validate the declaration's closed data shape.
36    pub fn json(value: Value, datatype: NodeUri) -> Self {
37        Self {
38            value,
39            datatype: Some(datatype),
40        }
41    }
42
43    /// The closed data tree. A URI-looking string in this tree remains data.
44    pub fn value(&self) -> &Value {
45        &self.value
46    }
47
48    /// The expanded type declaration for an `@json` value, if present.
49    pub fn datatype(&self) -> Option<&NodeUri> {
50        self.datatype.as_ref()
51    }
52
53    pub(crate) fn identity(&self) -> String {
54        serde_json::to_string(&(
55            self.datatype.as_ref().map(ToString::to_string),
56            canonical_value(&self.value),
57        ))
58        .expect("JSON values and strings serialize")
59    }
60}
61
62/// One object of an expanded fact.
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum ObjectTerm {
65    /// Data, including literal strings that resemble node URIs.
66    Value(TypedValue),
67    /// A semantic link to another addressed IR node.
68    NodeRef(NodeUri),
69}
70
71impl ObjectTerm {
72    /// Construct a data object without `@json` coercion.
73    pub fn value(value: Value) -> Self {
74        Self::Value(TypedValue::new(value))
75    }
76
77    /// Construct one typed `@json` data object.
78    pub fn typed_json(value: Value, datatype: NodeUri) -> Self {
79        Self::Value(TypedValue::json(value, datatype))
80    }
81
82    pub(crate) fn identity(&self) -> String {
83        match self {
84            Self::Value(value) => format!("value:{}", value.identity()),
85            Self::NodeRef(uri) => format!("node:{}", uri),
86        }
87    }
88}
89
90/// One expanded subject-predicate-object-graph value.
91///
92/// A graph query returns a fact once even if several documents or carriers
93/// asserted it. Ownership belongs to [`crate::metadata::AssertionKey`].
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct Fact {
96    subject: NodeUri,
97    predicate: NodeUri,
98    object: ObjectTerm,
99    graph: GraphName,
100}
101
102impl Fact {
103    /// Construct a fact from expanded semantic identities.
104    pub fn new(subject: NodeUri, predicate: NodeUri, object: ObjectTerm, graph: GraphName) -> Self {
105        Self {
106            subject,
107            predicate,
108            object,
109            graph,
110        }
111    }
112
113    /// The addressed subject node.
114    pub fn subject(&self) -> &NodeUri {
115        &self.subject
116    }
117
118    /// The expanded predicate declaration address.
119    pub fn predicate(&self) -> &NodeUri {
120        &self.predicate
121    }
122
123    /// The typed data or node-reference object.
124    pub fn object(&self) -> &ObjectTerm {
125        &self.object
126    }
127
128    /// The logical graph identity.
129    pub fn graph(&self) -> &GraphName {
130        &self.graph
131    }
132
133    pub(crate) fn identity(&self) -> String {
134        serde_json::to_string(&(
135            self.subject.to_string(),
136            self.predicate.to_string(),
137            self.object.identity(),
138            match &self.graph {
139                GraphName::Default => "default".to_owned(),
140                GraphName::Named(uri) => format!("named:{uri}"),
141            },
142        ))
143        .expect("fact identities serialize")
144    }
145}
146
147fn canonical_value(value: &Value) -> Value {
148    match value {
149        Value::Array(items) => Value::Array(items.iter().map(canonical_value).collect()),
150        Value::Object(members) => {
151            let mut keys = members.keys().collect::<Vec<_>>();
152            keys.sort_unstable();
153            let mut sorted = serde_json::Map::new();
154            for key in keys {
155                sorted.insert(key.clone(), canonical_value(&members[key]));
156            }
157            Value::Object(sorted)
158        }
159        _ => value.clone(),
160    }
161}