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}