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}