Skip to main content

rudof_rdf/rdf_core/
shacl_path.rs

1use iri_s::IriS;
2use serde::Serialize;
3use std::fmt::Display;
4
5/// Represents a SHACL property path for navigating RDF graphs.
6///
7/// SHACL paths follow the [SHACL property paths spec](https://www.w3.org/TR/shacl/#property-paths)
8/// which are a subset of SPARQL property paths.
9/// They enable complex navigation patterns through RDF graphs, extending simple predicate-based traversal with operations
10/// like sequences, alternatives, inverses, and quantifiers.
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
12pub enum SHACLPath {
13    /// A direct predicate path using a single IRI.
14    Predicate { pred: IriS },
15    /// An alternative path representing a union of multiple paths.
16    Alternative { paths: Vec<SHACLPath> },
17    /// A sequence path representing composed navigation.
18    Sequence { paths: Vec<SHACLPath> },
19    /// An inverse path representing reverse property navigation.
20    Inverse { path: Box<SHACLPath> },
21    /// A zero-or-more quantified path (transitive closure).
22    ZeroOrMore { path: Box<SHACLPath> },
23    /// A one-or-more quantified path (non-empty transitive closure).
24    OneOrMore { path: Box<SHACLPath> },
25    /// A zero-or-one quantified path (optional path).
26    ZeroOrOne { path: Box<SHACLPath> },
27}
28
29impl SHACLPath {
30    /// Creates a simple predicate path from an IRI.
31    ///
32    /// This is a convenience constructor for the most common path type,
33    /// equivalent to `SHACLPath::Predicate { pred }`.
34    ///
35    /// # Arguments
36    ///
37    /// * `pred` - The IRI representing the predicate to navigate
38    pub fn iri(pred: IriS) -> Self {
39        SHACLPath::Predicate { pred }
40    }
41
42    /// Extracts the predicate IRI from a simple predicate path.
43    pub fn pred(&self) -> Option<&IriS> {
44        match self {
45            SHACLPath::Predicate { pred } => Some(pred),
46            _ => None,
47        }
48    }
49
50    /// Creates a sequence path from multiple paths.
51    ///
52    /// # Arguments
53    ///
54    /// * `paths` - A vector of paths to compose in sequence
55    pub fn sequence(paths: Vec<SHACLPath>) -> Self {
56        SHACLPath::Sequence { paths }
57    }
58
59    /// Creates an alternative path from multiple paths.
60    ///
61    /// # Arguments
62    ///
63    /// * `paths` - A vector of alternative paths
64    pub fn alternative(paths: Vec<SHACLPath>) -> Self {
65        SHACLPath::Alternative { paths }
66    }
67
68    /// Creates an inverse path that navigates backwards.
69    ///
70    /// # Arguments
71    ///
72    /// * `path` - The path to invert
73    pub fn inverse(path: SHACLPath) -> Self {
74        SHACLPath::Inverse { path: Box::new(path) }
75    }
76
77    /// Creates a zero-or-more quantified path (transitive closure).
78    ///
79    /// # Arguments
80    ///
81    /// * `path` - The path to repeat
82    pub fn zero_or_more(path: SHACLPath) -> Self {
83        SHACLPath::ZeroOrMore { path: Box::new(path) }
84    }
85
86    /// Creates a one-or-more quantified path (non-empty transitive closure).
87    ///
88    /// # Arguments
89    ///
90    /// * `path` - The path to repeat
91    pub fn one_or_more(path: SHACLPath) -> Self {
92        SHACLPath::OneOrMore { path: Box::new(path) }
93    }
94
95    /// Creates a zero-or-one quantified path (optional path).
96    ///
97    /// # Arguments
98    ///
99    /// * `path` - The optional path
100    pub fn zero_or_one(path: SHACLPath) -> Self {
101        SHACLPath::ZeroOrOne { path: Box::new(path) }
102    }
103}
104
105impl Display for SHACLPath {
106    /// Formats the SHACL path as a SPARQL-like property path expression.
107    ///
108    /// The output follows SPARQL property path syntax conventions:
109    /// - Predicates are displayed as IRIs
110    /// - Alternatives use `|` separator with parentheses
111    /// - Sequences use `/` separator with parentheses
112    /// - Inverses use `^` prefix with parentheses
113    /// - Quantifiers use postfix operators: `*`, `+`, `?`
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        match self {
116            SHACLPath::Predicate { pred } => write!(f, "{pred}"),
117            SHACLPath::Alternative { paths } => {
118                write!(
119                    f,
120                    "({})",
121                    paths
122                        .iter()
123                        .map(|p| format!("{p}"))
124                        .collect::<Vec<String>>()
125                        .join(" | ")
126                )
127            },
128            SHACLPath::Sequence { paths } => write!(
129                f,
130                "({})",
131                paths
132                    .iter()
133                    .map(|p| format!("{p}"))
134                    .collect::<Vec<String>>()
135                    .join(" / ")
136            ),
137            SHACLPath::Inverse { path } => {
138                write!(f, "^({path})")
139            },
140            SHACLPath::ZeroOrMore { path } => {
141                write!(f, "({path})*")
142            },
143            SHACLPath::OneOrMore { path } => {
144                write!(f, "({path})+")
145            },
146            SHACLPath::ZeroOrOne { path } => {
147                write!(f, "({path})?")
148            },
149        }
150    }
151}