Skip to main content

pattern_core/
subject.rs

1//! Subject type definition
2//!
3//! This module provides the Subject type and related types (Symbol, Value, RangeValue, PropertyRecord)
4//! for use as pattern values in `Pattern<Subject>`.
5
6use std::fmt;
7
8/// Symbol identifier that uniquely identifies the subject.
9///
10/// A `Symbol` is a wrapper around a `String` that represents an identifier.
11/// In gram notation, symbols appear before labels and properties.
12///
13/// # Examples
14///
15/// ```rust
16/// use pattern_core::Symbol;
17///
18/// let symbol = Symbol("n".to_string());
19/// assert_eq!(symbol.0, "n");
20/// ```
21#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
22pub struct Symbol(pub String);
23
24impl fmt::Debug for Symbol {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        f.debug_tuple("Symbol").field(&self.0).finish()
27    }
28}
29
30impl fmt::Display for Symbol {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "{}", self.0)
33    }
34}
35
36impl From<&str> for Symbol {
37    fn from(s: &str) -> Self {
38        Symbol(s.to_string())
39    }
40}
41
42impl From<String> for Symbol {
43    fn from(s: String) -> Self {
44        Symbol(s)
45    }
46}
47
48/// Range value for numeric ranges (lower and upper bounds, both optional).
49///
50/// Used in `Value::VRange` to represent numeric ranges with optional bounds.
51///
52/// Note: This type only implements `PartialEq`, not `Eq`, because `f64` doesn't implement `Eq`
53/// (due to NaN != NaN).
54///
55/// # Examples
56///
57/// ```rust
58/// use pattern_core::RangeValue;
59///
60/// // Closed range: 1.0 to 10.0
61/// let range1 = RangeValue {
62///     lower: Some(1.0),
63///     upper: Some(10.0),
64/// };
65///
66/// // Lower bound only: 1.0 to infinity
67/// let range2 = RangeValue {
68///     lower: Some(1.0),
69///     upper: None,
70/// };
71///
72/// // Upper bound only: negative infinity to 10.0
73/// let range3 = RangeValue {
74///     lower: None,
75///     upper: Some(10.0),
76/// };
77/// ```
78#[derive(Clone, PartialEq)]
79pub struct RangeValue {
80    /// Lower bound of the range (inclusive), `None` means unbounded below
81    pub lower: Option<f64>,
82    /// Upper bound of the range (inclusive), `None` means unbounded above
83    pub upper: Option<f64>,
84}
85
86impl fmt::Debug for RangeValue {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        f.debug_struct("RangeValue")
89            .field("lower", &self.lower)
90            .field("upper", &self.upper)
91            .finish()
92    }
93}
94
95impl fmt::Display for RangeValue {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match (self.lower, self.upper) {
98            (Some(lower), Some(upper)) => write!(f, "{}..{}", lower, upper),
99            (Some(lower), None) => write!(f, "{}..", lower),
100            (None, Some(upper)) => write!(f, "..{}", upper),
101            (None, None) => write!(f, ".."),
102        }
103    }
104}
105
106/// Property value types for Subject properties.
107///
108/// `Value` is an enum that represents rich value types that can be stored in Subject properties.
109/// It supports standard types (integers, decimals, booleans, strings, symbols) and extended types
110/// (tagged strings, arrays, maps, ranges, measurements).
111///
112/// Note: This type only implements `PartialEq`, not `Eq`, because it contains `RangeValue`
113/// which uses `f64` (`f64` doesn't implement `Eq` due to NaN != NaN).
114///
115/// # Examples
116///
117/// ```rust
118/// use pattern_core::Value;
119///
120/// // Standard types
121/// let int_val = Value::VInteger(42);
122/// let decimal_val = Value::VDecimal(3.14);
123/// let bool_val = Value::VBoolean(true);
124/// let string_val = Value::VString("hello".to_string());
125/// let symbol_val = Value::VSymbol("sym".to_string());
126///
127/// // Extended types
128/// let tagged = Value::VTaggedString {
129///     tag: "type".to_string(),
130///     content: "value".to_string(),
131/// };
132/// let array = Value::VArray(vec![
133///     Value::VInteger(1),
134///     Value::VInteger(2),
135/// ]);
136/// ```
137#[derive(Clone, PartialEq)]
138pub enum Value {
139    /// Integer value (i64)
140    VInteger(i64),
141    /// Decimal value (f64)
142    VDecimal(f64),
143    /// Boolean value
144    VBoolean(bool),
145    /// String value
146    VString(String),
147    /// Symbol value (string identifier)
148    VSymbol(String),
149    /// Tagged string with a type tag and content
150    VTaggedString {
151        /// The type tag
152        tag: String,
153        /// The string content
154        content: String,
155    },
156    /// Array of values
157    VArray(Vec<Value>),
158    /// Map from string keys to values
159    VMap(std::collections::HashMap<String, Value>),
160    /// Numeric range value
161    VRange(RangeValue),
162    /// Measurement with unit and numeric value (e.g., "5kg" -> unit="kg", value=5.0)
163    VMeasurement {
164        /// The unit string (e.g., "kg", "m", "s")
165        unit: String,
166        /// The numeric value
167        value: f64,
168    },
169}
170
171impl fmt::Debug for Value {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        match self {
174            Value::VInteger(i) => f.debug_tuple("VInteger").field(i).finish(),
175            Value::VDecimal(d) => f.debug_tuple("VDecimal").field(d).finish(),
176            Value::VBoolean(b) => f.debug_tuple("VBoolean").field(b).finish(),
177            Value::VString(s) => f.debug_tuple("VString").field(s).finish(),
178            Value::VSymbol(s) => f.debug_tuple("VSymbol").field(s).finish(),
179            Value::VTaggedString { tag, content } => f
180                .debug_struct("VTaggedString")
181                .field("tag", tag)
182                .field("content", content)
183                .finish(),
184            Value::VArray(arr) => f.debug_list().entries(arr).finish(),
185            Value::VMap(map) => f.debug_map().entries(map.iter()).finish(),
186            Value::VRange(r) => f.debug_tuple("VRange").field(r).finish(),
187            Value::VMeasurement { unit, value } => f
188                .debug_struct("VMeasurement")
189                .field("unit", unit)
190                .field("value", value)
191                .finish(),
192        }
193    }
194}
195
196impl fmt::Display for Value {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        match self {
199            Value::VInteger(i) => write!(f, "{}", i),
200            Value::VDecimal(d) => write!(f, "{}", d),
201            Value::VBoolean(b) => write!(f, "{}", b),
202            Value::VString(s) => write!(f, "\"{}\"", s),
203            Value::VSymbol(s) => write!(f, "{}", s),
204            Value::VTaggedString { tag, content } => write!(f, "{}:{}", tag, content),
205            Value::VArray(arr) => {
206                write!(f, "[")?;
207                for (i, item) in arr.iter().enumerate() {
208                    if i > 0 {
209                        write!(f, ", ")?;
210                    }
211                    write!(f, "{}", item)?;
212                }
213                write!(f, "]")
214            }
215            Value::VMap(map) => {
216                write!(f, "{{")?;
217                let mut first = true;
218                for (k, v) in map.iter() {
219                    if !first {
220                        write!(f, ", ")?;
221                    }
222                    first = false;
223                    write!(f, "{}: {}", k, v)?;
224                }
225                write!(f, "}}")
226            }
227            Value::VRange(r) => write!(f, "{}", r),
228            Value::VMeasurement { unit, value } => write!(f, "{}{}", value, unit),
229        }
230    }
231}
232
233/// Property record type alias.
234///
235/// A `PropertyRecord` is a map from string keys to `Value` types, storing
236/// structured data about a Subject.
237///
238/// # Examples
239///
240/// ```rust
241/// use pattern_core::{PropertyRecord, Value};
242/// use std::collections::HashMap;
243///
244/// let mut props: PropertyRecord = HashMap::new();
245/// props.insert("name".to_string(), Value::VString("Alice".to_string()));
246/// props.insert("age".to_string(), Value::VInteger(30));
247/// ```
248pub type PropertyRecord = std::collections::HashMap<String, Value>;
249
250/// Self-descriptive object with identity, labels, and properties.
251///
252/// `Subject` is designed to be the primary content type for patterns
253/// (i.e., `Pattern<Subject>` will be the common use case).
254///
255/// A Subject contains:
256/// - **Identity**: A required symbol identifier that uniquely identifies the subject
257/// - **Labels**: A set of label strings that categorize or classify the subject
258/// - **Properties**: A key-value map storing properties with rich value types
259///
260/// Note: This type only implements `PartialEq`, not `Eq`, because it contains `Value`
261/// which uses `f64` (`f64` doesn't implement `Eq` due to NaN != NaN).
262///
263/// # Examples
264///
265/// ```rust
266/// use pattern_core::{Subject, Symbol, Value};
267/// use std::collections::{HashSet, HashMap};
268///
269/// let subject = Subject {
270///     identity: Symbol("n".to_string()),
271///     labels: {
272///         let mut s = HashSet::new();
273///         s.insert("Person".to_string());
274///         s
275///     },
276///     properties: {
277///         let mut m = HashMap::new();
278///         m.insert("name".to_string(), Value::VString("Alice".to_string()));
279///         m.insert("age".to_string(), Value::VInteger(30));
280///         m
281///     },
282/// };
283/// ```
284///
285/// # Usage with Pattern
286///
287/// ```rust
288/// use pattern_core::{Pattern, Subject, Symbol};
289/// use std::collections::HashSet;
290///
291/// let subject = Subject {
292///     identity: Symbol("n".to_string()),
293///     labels: HashSet::new(),
294///     properties: std::collections::HashMap::new(),
295/// };
296///
297/// let pattern: Pattern<Subject> = Pattern {
298///     value: subject,
299///     elements: vec![],
300/// };
301/// ```
302#[derive(Clone, PartialEq)]
303pub struct Subject {
304    /// Symbol identifier that uniquely identifies the subject.
305    ///
306    /// The identity is always required. In gram notation, identities appear
307    /// before labels and properties.
308    pub identity: Symbol,
309
310    /// Set of label strings that categorize or classify the subject.
311    ///
312    /// Labels provide classification information. The set can be empty (no labels)
313    /// or contain one or more unique labels. In gram notation, labels are prefixed
314    /// with `:` or `::` and appear after the identity and before properties.
315    pub labels: std::collections::HashSet<String>,
316
317    /// Key-value property map storing structured data about the subject.
318    ///
319    /// Properties store attributes and metadata. The property record can be empty
320    /// (no properties) or contain any number of key-value pairs. In gram notation,
321    /// properties appear in curly braces: `{name:"Alice", age:30}`.
322    pub properties: PropertyRecord,
323}
324
325impl fmt::Debug for Subject {
326    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327        f.debug_struct("Subject")
328            .field("identity", &self.identity)
329            .field("labels", &self.labels)
330            .field("properties", &self.properties)
331            .finish()
332    }
333}
334
335impl fmt::Display for Subject {
336    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337        write!(f, "{}", self.identity)?;
338
339        if !self.labels.is_empty() {
340            write!(f, ":")?;
341            let mut labels_vec: Vec<_> = self.labels.iter().collect();
342            labels_vec.sort();
343            for (i, label) in labels_vec.iter().enumerate() {
344                if i > 0 {
345                    write!(f, "::")?;
346                }
347                write!(f, "{}", label)?;
348            }
349        }
350
351        if !self.properties.is_empty() {
352            write!(f, " {{")?;
353            let mut props_vec: Vec<_> = self.properties.iter().collect();
354            props_vec.sort_by_key(|(k, _)| *k);
355            for (i, (key, value)) in props_vec.iter().enumerate() {
356                if i > 0 {
357                    write!(f, ", ")?;
358                }
359                write!(f, "{}: {}", key, value)?;
360            }
361            write!(f, "}}")?;
362        }
363
364        Ok(())
365    }
366}
367
368impl Subject {
369    /// Creates an identity-only Subject with no labels or properties.
370    ///
371    /// Useful as a reference handle when passing to methods that accept `&Subject`
372    /// and only need the identity (e.g., `add_relationship` source/target args).
373    ///
374    /// # Examples
375    ///
376    /// ```rust
377    /// use pattern_core::Subject;
378    ///
379    /// let mut g = pattern_core::graph::StandardGraph::new();
380    /// let alice = Subject::build("alice").label("Person").done();
381    /// let bob   = Subject::build("bob").label("Person").done();
382    /// g.add_node(alice.clone());
383    /// g.add_node(bob.clone());
384    /// g.add_relationship(Subject::build("r1").label("KNOWS").done(), &alice, &bob);
385    ///
386    /// // When you only have an ID string, use from_id as a lightweight reference:
387    /// g.add_relationship(
388    ///     Subject::build("r2").label("KNOWS").done(),
389    ///     &Subject::from_id("alice"),
390    ///     &Subject::from_id("bob"),
391    /// );
392    /// ```
393    pub fn from_id(identity: impl Into<String>) -> Subject {
394        Subject {
395            identity: Symbol(identity.into()),
396            labels: std::collections::HashSet::new(),
397            properties: std::collections::HashMap::new(),
398        }
399    }
400
401    /// Creates a SubjectBuilder with the given identity.
402    ///
403    /// # Examples
404    ///
405    /// ```rust
406    /// use pattern_core::Subject;
407    ///
408    /// let subject = Subject::build("alice")
409    ///     .label("Person")
410    ///     .property("name", "Alice")
411    ///     .done();
412    /// assert_eq!(subject.identity.0, "alice");
413    /// assert!(subject.labels.contains("Person"));
414    /// ```
415    pub fn build(identity: impl Into<String>) -> SubjectBuilder {
416        SubjectBuilder {
417            identity: Symbol(identity.into()),
418            labels: std::collections::HashSet::new(),
419            properties: PropertyRecord::new(),
420        }
421    }
422}
423
424/// Fluent builder for constructing Subject values.
425///
426/// Created via `Subject::build(identity)`. Chain `.label()` and `.property()`
427/// calls, then finalize with `.done()` or use `Into<Subject>`.
428pub struct SubjectBuilder {
429    identity: Symbol,
430    labels: std::collections::HashSet<String>,
431    properties: PropertyRecord,
432}
433
434impl SubjectBuilder {
435    /// Adds a label to the subject being built.
436    pub fn label(mut self, label: impl Into<String>) -> Self {
437        self.labels.insert(label.into());
438        self
439    }
440
441    /// Adds a property to the subject being built.
442    pub fn property(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
443        self.properties.insert(key.into(), value.into());
444        self
445    }
446
447    /// Finalizes the builder and returns the constructed Subject.
448    pub fn done(self) -> Subject {
449        Subject {
450            identity: self.identity,
451            labels: self.labels,
452            properties: self.properties,
453        }
454    }
455}
456
457impl From<SubjectBuilder> for Subject {
458    fn from(builder: SubjectBuilder) -> Self {
459        builder.done()
460    }
461}
462
463// ============================================================================
464// Value conversion implementations
465// ============================================================================
466
467impl From<i64> for Value {
468    fn from(v: i64) -> Self {
469        Value::VInteger(v)
470    }
471}
472
473impl From<i32> for Value {
474    fn from(v: i32) -> Self {
475        Value::VInteger(v as i64)
476    }
477}
478
479impl From<f64> for Value {
480    fn from(v: f64) -> Self {
481        Value::VDecimal(v)
482    }
483}
484
485impl From<bool> for Value {
486    fn from(v: bool) -> Self {
487        Value::VBoolean(v)
488    }
489}
490
491impl From<String> for Value {
492    fn from(v: String) -> Self {
493        Value::VString(v)
494    }
495}
496
497impl From<&str> for Value {
498    fn from(v: &str) -> Self {
499        Value::VString(v.to_string())
500    }
501}