Skip to main content

cimschema/shacl/
model.rs

1use std::collections::HashMap;
2
3/// A single value in a SHACL constraint payload.
4#[derive(Debug, Clone)]
5#[allow(dead_code)]
6pub enum ShaclValue {
7    Str(String),
8    Int(i64),
9    Float(f64),
10    Bool(bool),
11    List(Vec<String>),
12    /// Nested sub-shape branches for sh:or / sh:and / sh:xone.
13    /// Each inner Vec is one branch; each branch is a list of constraints.
14    Shapes(Vec<Vec<ConstraintInfo>>),
15}
16
17impl ShaclValue {
18    pub fn as_str(&self) -> Option<&str> {
19        if let ShaclValue::Str(s) = self { Some(s) } else { None }
20    }
21    pub fn as_int(&self) -> Option<i64> {
22        if let ShaclValue::Int(n) = self { Some(*n) } else { None }
23    }
24    pub fn as_float(&self) -> Option<f64> {
25        match self {
26            ShaclValue::Float(f) => Some(*f),
27            ShaclValue::Int(n) => Some(*n as f64),
28            _ => None,
29        }
30    }
31    pub fn as_list(&self) -> Option<&Vec<String>> {
32        if let ShaclValue::List(v) = self { Some(v) } else { None }
33    }
34    pub fn as_shapes(&self) -> Option<&Vec<Vec<ConstraintInfo>>> {
35        if let ShaclValue::Shapes(v) = self { Some(v) } else { None }
36    }
37}
38
39/// One constraint on a property shape (e.g. sh:minCount, sh:in, sh:pattern).
40#[derive(Debug, Clone)]
41#[allow(dead_code)]
42pub struct ConstraintInfo {
43    /// SHACL property path as simplified IRI segments (e.g. ["cim:ACLineSegment.r"]).
44    pub path: Vec<String>,
45    /// sh:severity simplified (e.g. "sh:Violation", "sh:Warning").
46    pub severity: String,
47    pub message: String,
48    /// sh:name value — structured human-readable rule name (e.g. "C:301:EQ:ACLineSegment.r:valueRange").
49    pub name: String,
50    pub description: String,
51    /// SHACL constraint component (e.g. "sh:RequiredConstraintComponent").
52    pub component: String,
53    /// Component-specific payload keyed by simplified predicate name.
54    pub payload: HashMap<String, ShaclValue>,
55    /// Shape IRI used as the machine-readable rule identifier (e.g. "equ:ACLineSegment.r-valueRange").
56    pub rule_id: String,
57}
58
59/// Payload key on `sh:in` / `sh:hasValue` constraints: the values that were
60/// string literals in the TTL rather than IRIs.
61///
62/// Both flatten to `String` in the `in` / `hasValue` payload, and the
63/// difference decides how a value compares against decoded data: the decoder
64/// keeps element text verbatim but stores an `rdf:resource` as its fragment.
65/// Without it, `"urn:ogc:def:crs:EPSG::4326"` was cut to `4326` like an IRI.
66pub const LITERALS: &str = "literals";
67
68/// Payload key on node-level `sh:and` / `sh:or` / `sh:xone` constraints: the
69/// predicates a branch used that [`ConstraintInfo`] branches cannot carry
70/// (`sh:class`, `sh:not`, `sh:datatype`, ...). Non-empty means the branches as
71/// imported are weaker than the schema's, so the combination must not run.
72pub const UNSUPPORTED: &str = "unsupported";
73
74/// Payload key on node-level `sh:and` / `sh:or` / `sh:xone` constraints: the
75/// indices (as strings) of the branches written `[ sh:not X ]`, which conform
76/// when X does not.
77pub const NEGATED: &str = "negated";
78
79/// The target of a NodeShape (what objects it applies to).
80#[derive(Debug, Clone)]
81#[allow(dead_code)]
82pub struct TargetInfo {
83    /// "targetClass", "targetNode", "targetSubjectsOf", "targetObjectsOf", or
84    /// "sparqlTarget". Only "targetClass"/"targetNode" resolve to a concrete class for
85    /// codegen (see codegen.rs's kind filter in render_file); the others carry no
86    /// resolvable class and are recorded so the shape survives parsing instead of being
87    /// dropped, with codegen recording an explicit skip entry for them instead.
88    pub kind: String,
89    /// For "targetClass"/"targetNode": simplified IRI of the target (e.g.
90    /// "cim:ACLineSegment"). For "targetSubjectsOf"/"targetObjectsOf": the predicate
91    /// IRI. For "sparqlTarget": the blank node id of the `sh:target [a sh:SPARQLTarget;
92    /// ..]` object (not a resolvable value on its own).
93    pub value: String,
94}
95
96/// A parsed and simplified SHACL shape (NodeShape or PropertyShape).
97#[derive(Debug, Clone)]
98#[allow(dead_code)]
99pub struct ShapeInfo {
100    pub id: String,
101    pub targets: Vec<TargetInfo>,
102    /// Non-empty for PropertyShapes; the property path.
103    pub path: Vec<String>,
104    pub name: String,
105    pub description: String,
106    pub constraints: Vec<ConstraintInfo>,
107    /// Nested sh:property shapes.
108    pub properties: Vec<ShapeInfo>,
109    /// `sh:closed true` — the shape's `sh:property` paths, plus
110    /// `sh:ignoredProperties`, are the *only* properties the profile allows on
111    /// the target class. Simplified IRIs, as [`Self::path`] uses.
112    ///
113    /// Read straight off the NodeShape rather than from [`Self::properties`]:
114    /// the allowed set is written as constraint-less `[ sh:path X ]` blank
115    /// nodes, which `build_property_shape` drops for having nothing to check.
116    pub closed: Option<Vec<String>>,
117    /// `sh:deactivated true` — the schema switched this shape off. Carried
118    /// rather than dropped at parse time so the simplification stage can
119    /// account for it like any other skip.
120    pub deactivated: bool,
121}
122
123/// All shapes extracted from one TTL file.
124#[derive(Debug)]
125pub struct FileResults {
126    /// TTL base file name without extension (e.g. "61970-600-2_Equipment-AP-Con-Simple-SHACL").
127    pub file_name: String,
128    pub shapes: Vec<ShapeInfo>,
129    /// The file's own `@prefix` declarations, prefix → IRI.
130    ///
131    /// Shapes carry simplified IRIs (`"cim:Equipment"`), and the prefix alone
132    /// does not identify a class: NCP binds `cim` to `https://cim.ucaiug.io/ns#`
133    /// while CGMES binds it to `http://iec.ch/TC57/CIM100#`, and the two
134    /// families overlap on 164 local names. Resolving a target or a path
135    /// therefore needs the map that was in scope where it was written.
136    pub prefixes: HashMap<String, String>,
137}