1use std::collections::{HashMap, HashSet, VecDeque};
7use std::fmt;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum PropertyPath {
12 Iri(String),
14 Sequence(Box<PropertyPath>, Box<PropertyPath>),
16 Alternative(Box<PropertyPath>, Box<PropertyPath>),
18 ZeroOrMore(Box<PropertyPath>),
20 OneOrMore(Box<PropertyPath>),
22 ZeroOrOne(Box<PropertyPath>),
24 Inverse(Box<PropertyPath>),
26 NegatedSet(Vec<String>),
28}
29
30#[derive(Debug, Clone, PartialEq)]
32pub enum PathError {
33 UnknownSyntax(String),
35 EmptyPath,
37}
38
39impl fmt::Display for PathError {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 PathError::UnknownSyntax(s) => write!(f, "Unknown path syntax: {s}"),
43 PathError::EmptyPath => write!(f, "Empty path expression"),
44 }
45 }
46}
47
48impl std::error::Error for PathError {}
49
50#[derive(Debug, Default, Clone)]
54pub struct PathGraph {
55 forward: HashMap<String, Vec<(String, String)>>,
57 reverse: HashMap<String, Vec<(String, String)>>,
59}
60
61impl PathGraph {
62 pub fn new() -> Self {
64 Self::default()
65 }
66
67 pub fn add_triple(&mut self, subject: &str, predicate: &str, object: &str) {
69 self.forward
70 .entry(subject.to_string())
71 .or_default()
72 .push((predicate.to_string(), object.to_string()));
73 self.reverse
74 .entry(object.to_string())
75 .or_default()
76 .push((predicate.to_string(), subject.to_string()));
77 }
78
79 pub fn subjects(&self) -> Vec<String> {
81 self.forward.keys().cloned().collect()
82 }
83
84 pub fn objects_of(&self, subject: &str, predicate: &str) -> Vec<String> {
86 self.forward
87 .get(subject)
88 .map(|pairs| {
89 pairs
90 .iter()
91 .filter(|(p, _)| p == predicate)
92 .map(|(_, o)| o.clone())
93 .collect()
94 })
95 .unwrap_or_default()
96 }
97
98 pub fn subjects_of(&self, object: &str, predicate: &str) -> Vec<String> {
100 self.reverse
101 .get(object)
102 .map(|pairs| {
103 pairs
104 .iter()
105 .filter(|(p, _)| p == predicate)
106 .map(|(_, s)| s.clone())
107 .collect()
108 })
109 .unwrap_or_default()
110 }
111
112 pub fn all_predicates(&self) -> Vec<String> {
114 let mut preds: HashSet<String> = HashSet::new();
115 for pairs in self.forward.values() {
116 for (p, _) in pairs {
117 preds.insert(p.clone());
118 }
119 }
120 preds.into_iter().collect()
121 }
122
123 pub fn objects_via_any_except(&self, subject: &str, excluded: &[String]) -> Vec<String> {
125 let excluded_set: HashSet<&String> = excluded.iter().collect();
126 self.forward
127 .get(subject)
128 .map(|pairs| {
129 pairs
130 .iter()
131 .filter(|(p, _)| !excluded_set.contains(p))
132 .map(|(_, o)| o.clone())
133 .collect()
134 })
135 .unwrap_or_default()
136 }
137}
138
139pub struct PropertyPathEvaluator;
141
142impl PropertyPathEvaluator {
143 pub fn new() -> Self {
145 Self
146 }
147
148 pub fn evaluate(
152 &self,
153 graph: &PathGraph,
154 path: &PropertyPath,
155 start_node: &str,
156 ) -> Vec<String> {
157 let mut results = self.eval_inner(graph, path, start_node);
158 results.sort();
159 results.dedup();
160 results
161 }
162
163 fn eval_inner(&self, graph: &PathGraph, path: &PropertyPath, start: &str) -> Vec<String> {
164 match path {
165 PropertyPath::Iri(iri) => graph.objects_of(start, iri),
166
167 PropertyPath::Sequence(a, b) => {
168 let mid_nodes = self.eval_inner(graph, a, start);
169 let mut results = Vec::new();
170 for mid in &mid_nodes {
171 let mut tail = self.eval_inner(graph, b, mid);
172 results.append(&mut tail);
173 }
174 results
175 }
176
177 PropertyPath::Alternative(a, b) => {
178 let mut ra = self.eval_inner(graph, a, start);
179 let mut rb = self.eval_inner(graph, b, start);
180 ra.append(&mut rb);
181 ra
182 }
183
184 PropertyPath::ZeroOrMore(inner) => {
185 self.bfs_closure(graph, inner, start, true)
187 }
188
189 PropertyPath::OneOrMore(inner) => {
190 self.bfs_closure(graph, inner, start, false)
192 }
193
194 PropertyPath::ZeroOrOne(inner) => {
195 let mut results = self.eval_inner(graph, inner, start);
197 results.push(start.to_string());
198 results
199 }
200
201 PropertyPath::Inverse(inner) => {
202 let reversed = Self::build_reverse_graph(graph);
204 self.eval_inner(&reversed, inner, start)
205 }
206
207 PropertyPath::NegatedSet(iris) => graph.objects_via_any_except(start, iris),
208 }
209 }
210
211 fn bfs_closure(
216 &self,
217 graph: &PathGraph,
218 path: &PropertyPath,
219 start: &str,
220 include_start: bool,
221 ) -> Vec<String> {
222 let mut visited: HashSet<String> = HashSet::new();
223 let mut queue: VecDeque<String> = VecDeque::new();
224 let mut results: Vec<String> = Vec::new();
225
226 if include_start {
227 visited.insert(start.to_string());
228 results.push(start.to_string());
229 }
230
231 let direct = self.eval_inner(graph, path, start);
233 for node in direct {
234 if visited.insert(node.clone()) {
235 results.push(node.clone());
236 queue.push_back(node);
237 }
238 }
239
240 while let Some(current) = queue.pop_front() {
242 let next_nodes = self.eval_inner(graph, path, ¤t);
243 for node in next_nodes {
244 if visited.insert(node.clone()) {
245 results.push(node.clone());
246 queue.push_back(node);
247 }
248 }
249 }
250
251 results
252 }
253
254 fn build_reverse_graph(graph: &PathGraph) -> PathGraph {
256 let mut reversed = PathGraph::new();
257 for (subject, pairs) in &graph.forward {
258 for (predicate, object) in pairs {
259 reversed.add_triple(object, predicate, subject);
260 }
261 }
262 reversed
263 }
264}
265
266impl Default for PropertyPathEvaluator {
267 fn default() -> Self {
268 Self::new()
269 }
270}
271
272pub fn parse_path(s: &str) -> Result<PropertyPath, PathError> {
285 let s = s.trim();
286 if s.is_empty() {
287 return Err(PathError::EmptyPath);
288 }
289 parse_alternative(s)
290}
291
292fn parse_alternative(s: &str) -> Result<PropertyPath, PathError> {
294 let parts = split_top_level(s, '|');
296 if parts.len() == 1 {
297 return parse_sequence(s);
298 }
299 let mut iter = parts.iter().rev();
301 let last = parse_sequence(iter.next().ok_or(PathError::EmptyPath)?.trim())?;
302 let mut result = last;
303 for part in iter {
304 let left = parse_sequence(part.trim())?;
305 result = PropertyPath::Alternative(Box::new(left), Box::new(result));
306 }
307 Ok(result)
308}
309
310fn parse_sequence(s: &str) -> Result<PropertyPath, PathError> {
312 let parts = split_top_level(s, '/');
313 if parts.len() == 1 {
314 return parse_postfix(s);
315 }
316 let mut iter = parts.iter();
317 let first_str = iter.next().ok_or(PathError::EmptyPath)?.trim();
318 let mut result = parse_postfix(first_str)?;
319 for part in iter {
320 let right = parse_postfix(part.trim())?;
321 result = PropertyPath::Sequence(Box::new(result), Box::new(right));
322 }
323 Ok(result)
324}
325
326fn parse_postfix(s: &str) -> Result<PropertyPath, PathError> {
328 if s.is_empty() {
329 return Err(PathError::EmptyPath);
330 }
331
332 if let Some(rest) = s.strip_prefix('^') {
334 let inner = parse_postfix(rest.trim())?;
335 return Ok(PropertyPath::Inverse(Box::new(inner)));
336 }
337
338 if let Some(stripped) = s.strip_prefix('!') {
340 let inner_str = stripped.trim();
341 if inner_str.starts_with('(') && inner_str.ends_with(')') {
343 let content = &inner_str[1..inner_str.len() - 1];
344 let iri_parts: Vec<&str> = content.split('|').collect();
346 let mut iris = Vec::new();
347 for part in iri_parts {
348 let p = part.trim();
349 if p.starts_with('<') && p.ends_with('>') {
350 iris.push(p[1..p.len() - 1].to_string());
351 } else if !p.is_empty() {
352 iris.push(p.to_string());
353 }
354 }
355 if iris.is_empty() {
356 return Err(PathError::UnknownSyntax(s.to_string()));
357 }
358 return Ok(PropertyPath::NegatedSet(iris));
359 }
360 return Err(PathError::UnknownSyntax(s.to_string()));
361 }
362
363 let (base_str, suffix) = extract_postfix_suffix(s);
366
367 let base = parse_atom(base_str)?;
368 match suffix {
369 Some('*') => Ok(PropertyPath::ZeroOrMore(Box::new(base))),
370 Some('+') => Ok(PropertyPath::OneOrMore(Box::new(base))),
371 Some('?') => Ok(PropertyPath::ZeroOrOne(Box::new(base))),
372 Some(c) => Err(PathError::UnknownSyntax(format!("Unknown postfix: {c}"))),
373 None => Ok(base),
374 }
375}
376
377fn extract_postfix_suffix(s: &str) -> (&str, Option<char>) {
379 if s.is_empty() {
380 return (s, None);
381 }
382 let last = s.chars().next_back();
383 match last {
384 Some('*') | Some('+') | Some('?') => {
385 let suffix_char = last;
386 let base = s[..s.len() - 1].trim_end();
387 if !base.is_empty() {
389 (base, suffix_char)
390 } else {
391 (s, None)
392 }
393 }
394 _ => (s, None),
395 }
396}
397
398fn parse_atom(s: &str) -> Result<PropertyPath, PathError> {
400 let s = s.trim();
401 if s.is_empty() {
402 return Err(PathError::EmptyPath);
403 }
404
405 if s.starts_with('(') && s.ends_with(')') {
407 let inner = &s[1..s.len() - 1];
408 return parse_alternative(inner);
409 }
410
411 if s.starts_with('<') && s.ends_with('>') {
413 let iri = &s[1..s.len() - 1];
414 return Ok(PropertyPath::Iri(iri.to_string()));
415 }
416
417 if s.chars()
419 .all(|c| c.is_alphanumeric() || c == ':' || c == '_' || c == '-' || c == '.')
420 {
421 return Ok(PropertyPath::Iri(s.to_string()));
422 }
423
424 Err(PathError::UnknownSyntax(s.to_string()))
425}
426
427fn split_top_level(s: &str, delimiter: char) -> Vec<&str> {
429 let mut parts = Vec::new();
430 let mut depth_angle = 0usize;
431 let mut depth_paren = 0usize;
432 let mut start = 0;
433
434 for (i, c) in s.char_indices() {
435 match c {
436 '<' => depth_angle += 1,
437 '>' => depth_angle = depth_angle.saturating_sub(1),
438 '(' => depth_paren += 1,
439 ')' => depth_paren = depth_paren.saturating_sub(1),
440 _ if c == delimiter && depth_angle == 0 && depth_paren == 0 => {
441 parts.push(&s[start..i]);
442 start = i + delimiter.len_utf8();
443 }
444 _ => {}
445 }
446 }
447 parts.push(&s[start..]);
448 parts
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 fn make_graph() -> PathGraph {
456 let mut g = PathGraph::new();
457 g.add_triple("a", "p", "b");
459 g.add_triple("b", "p", "c");
460 g.add_triple("c", "p", "d");
461 g.add_triple("a", "q", "e");
463 g.add_triple("e", "r", "a");
465 g
466 }
467
468 fn evaluator() -> PropertyPathEvaluator {
469 PropertyPathEvaluator::new()
470 }
471
472 #[test]
475 fn test_iri_path_simple() {
476 let g = make_graph();
477 let ev = evaluator();
478 let path = PropertyPath::Iri("p".to_string());
479 let mut r = ev.evaluate(&g, &path, "a");
480 r.sort();
481 assert_eq!(r, vec!["b"]);
482 }
483
484 #[test]
485 fn test_iri_path_no_match() {
486 let g = make_graph();
487 let ev = evaluator();
488 let path = PropertyPath::Iri("p".to_string());
489 let r = ev.evaluate(&g, &path, "d"); assert!(r.is_empty());
491 }
492
493 #[test]
494 fn test_iri_path_multiple_objects() {
495 let mut g = PathGraph::new();
496 g.add_triple("a", "p", "b");
497 g.add_triple("a", "p", "c");
498 let ev = evaluator();
499 let path = PropertyPath::Iri("p".to_string());
500 let mut r = ev.evaluate(&g, &path, "a");
501 r.sort();
502 assert_eq!(r, vec!["b", "c"]);
503 }
504
505 #[test]
508 fn test_sequence_two_hops() {
509 let g = make_graph();
510 let ev = evaluator();
511 let path = PropertyPath::Sequence(
512 Box::new(PropertyPath::Iri("p".to_string())),
513 Box::new(PropertyPath::Iri("p".to_string())),
514 );
515 let r = ev.evaluate(&g, &path, "a");
516 assert_eq!(r, vec!["c"]);
517 }
518
519 #[test]
520 fn test_sequence_three_hops() {
521 let g = make_graph();
522 let ev = evaluator();
523 let path = PropertyPath::Sequence(
524 Box::new(PropertyPath::Sequence(
525 Box::new(PropertyPath::Iri("p".to_string())),
526 Box::new(PropertyPath::Iri("p".to_string())),
527 )),
528 Box::new(PropertyPath::Iri("p".to_string())),
529 );
530 let r = ev.evaluate(&g, &path, "a");
531 assert_eq!(r, vec!["d"]);
532 }
533
534 #[test]
535 fn test_sequence_no_match() {
536 let g = make_graph();
537 let ev = evaluator();
538 let path = PropertyPath::Sequence(
539 Box::new(PropertyPath::Iri("p".to_string())),
540 Box::new(PropertyPath::Iri("q".to_string())), );
542 let r = ev.evaluate(&g, &path, "a");
543 assert!(r.is_empty());
544 }
545
546 #[test]
549 fn test_alternative_both_branches() {
550 let g = make_graph();
551 let ev = evaluator();
552 let path = PropertyPath::Alternative(
553 Box::new(PropertyPath::Iri("p".to_string())),
554 Box::new(PropertyPath::Iri("q".to_string())),
555 );
556 let mut r = ev.evaluate(&g, &path, "a");
557 r.sort();
558 assert_eq!(r, vec!["b", "e"]);
559 }
560
561 #[test]
562 fn test_alternative_dedup() {
563 let mut g = PathGraph::new();
564 g.add_triple("a", "p", "b");
565 g.add_triple("a", "q", "b"); let ev = evaluator();
567 let path = PropertyPath::Alternative(
568 Box::new(PropertyPath::Iri("p".to_string())),
569 Box::new(PropertyPath::Iri("q".to_string())),
570 );
571 let r = ev.evaluate(&g, &path, "a");
572 assert_eq!(r, vec!["b"]); }
574
575 #[test]
576 fn test_alternative_one_empty() {
577 let g = make_graph();
578 let ev = evaluator();
579 let path = PropertyPath::Alternative(
580 Box::new(PropertyPath::Iri("p".to_string())),
581 Box::new(PropertyPath::Iri("z".to_string())), );
583 let r = ev.evaluate(&g, &path, "a");
584 assert_eq!(r, vec!["b"]);
585 }
586
587 #[test]
590 fn test_zero_or_more_includes_start() {
591 let g = make_graph();
592 let ev = evaluator();
593 let path = PropertyPath::ZeroOrMore(Box::new(PropertyPath::Iri("p".to_string())));
594 let mut r = ev.evaluate(&g, &path, "a");
595 r.sort();
596 assert_eq!(r, vec!["a", "b", "c", "d"]);
597 }
598
599 #[test]
600 fn test_zero_or_more_no_outgoing() {
601 let g = make_graph();
602 let ev = evaluator();
603 let path = PropertyPath::ZeroOrMore(Box::new(PropertyPath::Iri("p".to_string())));
604 let r = ev.evaluate(&g, &path, "d"); assert_eq!(r, vec!["d"]); }
607
608 #[test]
609 fn test_zero_or_more_cycle_safety() {
610 let mut g = PathGraph::new();
611 g.add_triple("a", "p", "b");
612 g.add_triple("b", "p", "a"); let ev = evaluator();
614 let path = PropertyPath::ZeroOrMore(Box::new(PropertyPath::Iri("p".to_string())));
615 let mut r = ev.evaluate(&g, &path, "a");
616 r.sort();
617 assert_eq!(r, vec!["a", "b"]); }
619
620 #[test]
623 fn test_one_or_more_excludes_start() {
624 let g = make_graph();
625 let ev = evaluator();
626 let path = PropertyPath::OneOrMore(Box::new(PropertyPath::Iri("p".to_string())));
627 let mut r = ev.evaluate(&g, &path, "a");
628 r.sort();
629 assert_eq!(r, vec!["b", "c", "d"]);
630 }
631
632 #[test]
633 fn test_one_or_more_no_outgoing() {
634 let g = make_graph();
635 let ev = evaluator();
636 let path = PropertyPath::OneOrMore(Box::new(PropertyPath::Iri("p".to_string())));
637 let r = ev.evaluate(&g, &path, "d");
638 assert!(r.is_empty());
639 }
640
641 #[test]
642 fn test_one_or_more_cycle_safety() {
643 let mut g = PathGraph::new();
644 g.add_triple("a", "p", "b");
645 g.add_triple("b", "p", "a");
646 let ev = evaluator();
647 let path = PropertyPath::OneOrMore(Box::new(PropertyPath::Iri("p".to_string())));
648 let mut r = ev.evaluate(&g, &path, "a");
649 r.sort();
650 assert!(r.contains(&"b".to_string()));
653 }
654
655 #[test]
658 fn test_zero_or_one_includes_start() {
659 let g = make_graph();
660 let ev = evaluator();
661 let path = PropertyPath::ZeroOrOne(Box::new(PropertyPath::Iri("p".to_string())));
662 let mut r = ev.evaluate(&g, &path, "a");
663 r.sort();
664 assert_eq!(r, vec!["a", "b"]);
665 }
666
667 #[test]
668 fn test_zero_or_one_no_outgoing() {
669 let g = make_graph();
670 let ev = evaluator();
671 let path = PropertyPath::ZeroOrOne(Box::new(PropertyPath::Iri("p".to_string())));
672 let mut r = ev.evaluate(&g, &path, "d");
673 r.sort();
674 assert_eq!(r, vec!["d"]); }
676
677 #[test]
680 fn test_inverse_path() {
681 let g = make_graph();
682 let ev = evaluator();
683 let path = PropertyPath::Inverse(Box::new(PropertyPath::Iri("p".to_string())));
684 let r = ev.evaluate(&g, &path, "b"); assert_eq!(r, vec!["a"]);
686 }
687
688 #[test]
689 fn test_inverse_chain() {
690 let g = make_graph();
691 let ev = evaluator();
692 let path = PropertyPath::Sequence(
694 Box::new(PropertyPath::Inverse(Box::new(PropertyPath::Iri(
695 "p".to_string(),
696 )))),
697 Box::new(PropertyPath::Inverse(Box::new(PropertyPath::Iri(
698 "p".to_string(),
699 )))),
700 );
701 let r = ev.evaluate(&g, &path, "d");
702 assert_eq!(r, vec!["b"]);
703 }
704
705 #[test]
706 fn test_inverse_no_match() {
707 let g = make_graph();
708 let ev = evaluator();
709 let path = PropertyPath::Inverse(Box::new(PropertyPath::Iri("p".to_string())));
710 let r = ev.evaluate(&g, &path, "a"); assert!(r.is_empty());
712 }
713
714 #[test]
717 fn test_negated_set_excludes_predicate() {
718 let g = make_graph();
719 let ev = evaluator();
720 let path = PropertyPath::NegatedSet(vec!["q".to_string()]);
721 let mut r = ev.evaluate(&g, &path, "a"); r.sort();
723 assert_eq!(r, vec!["b"]);
724 }
725
726 #[test]
727 fn test_negated_set_all_excluded() {
728 let g = make_graph();
729 let ev = evaluator();
730 let path = PropertyPath::NegatedSet(vec!["p".to_string(), "q".to_string()]);
731 let r = ev.evaluate(&g, &path, "a"); assert!(r.is_empty());
733 }
734
735 #[test]
736 fn test_negated_set_empty_exclusion() {
737 let g = make_graph();
738 let ev = evaluator();
739 let path = PropertyPath::NegatedSet(vec![]); let mut r = ev.evaluate(&g, &path, "a");
741 r.sort();
742 assert_eq!(r, vec!["b", "e"]);
743 }
744
745 #[test]
748 fn test_path_graph_subjects() {
749 let g = make_graph();
750 let mut s = g.subjects();
751 s.sort();
752 assert!(s.contains(&"a".to_string()));
753 assert!(s.contains(&"e".to_string()));
754 }
755
756 #[test]
757 fn test_path_graph_objects_of() {
758 let g = make_graph();
759 let r = g.objects_of("a", "p");
760 assert_eq!(r, vec!["b"]);
761 }
762
763 #[test]
764 fn test_path_graph_add_multiple() {
765 let mut g = PathGraph::new();
766 g.add_triple("x", "pred", "y1");
767 g.add_triple("x", "pred", "y2");
768 g.add_triple("x", "pred", "y3");
769 let mut r = g.objects_of("x", "pred");
770 r.sort();
771 assert_eq!(r, vec!["y1", "y2", "y3"]);
772 }
773
774 #[test]
777 fn test_parse_iri() {
778 let p = parse_path("<http://example.org/p>").expect("path parse should succeed");
779 assert_eq!(p, PropertyPath::Iri("http://example.org/p".to_string()));
780 }
781
782 #[test]
783 fn test_parse_empty_error() {
784 let r = parse_path("");
785 assert_eq!(r, Err(PathError::EmptyPath));
786 }
787
788 #[test]
789 fn test_parse_sequence() {
790 let p = parse_path("<a>/<b>").expect("path parse should succeed");
791 assert_eq!(
792 p,
793 PropertyPath::Sequence(
794 Box::new(PropertyPath::Iri("a".to_string())),
795 Box::new(PropertyPath::Iri("b".to_string())),
796 )
797 );
798 }
799
800 #[test]
801 fn test_parse_alternative() {
802 let p = parse_path("<a>|<b>").expect("path parse should succeed");
803 assert_eq!(
804 p,
805 PropertyPath::Alternative(
806 Box::new(PropertyPath::Iri("a".to_string())),
807 Box::new(PropertyPath::Iri("b".to_string())),
808 )
809 );
810 }
811
812 #[test]
813 fn test_parse_zero_or_more() {
814 let p = parse_path("<a>*").expect("path parse should succeed");
815 assert_eq!(
816 p,
817 PropertyPath::ZeroOrMore(Box::new(PropertyPath::Iri("a".to_string())))
818 );
819 }
820
821 #[test]
822 fn test_parse_one_or_more() {
823 let p = parse_path("<a>+").expect("path parse should succeed");
824 assert_eq!(
825 p,
826 PropertyPath::OneOrMore(Box::new(PropertyPath::Iri("a".to_string())))
827 );
828 }
829
830 #[test]
831 fn test_parse_zero_or_one() {
832 let p = parse_path("<a>?").expect("path parse should succeed");
833 assert_eq!(
834 p,
835 PropertyPath::ZeroOrOne(Box::new(PropertyPath::Iri("a".to_string())))
836 );
837 }
838
839 #[test]
840 fn test_parse_inverse() {
841 let p = parse_path("^<a>").expect("path parse should succeed");
842 assert_eq!(
843 p,
844 PropertyPath::Inverse(Box::new(PropertyPath::Iri("a".to_string())))
845 );
846 }
847
848 #[test]
849 fn test_parse_negated_set_single() {
850 let p = parse_path("!(<http://example.org/p>)").expect("path parse should succeed");
851 assert_eq!(
852 p,
853 PropertyPath::NegatedSet(vec!["http://example.org/p".to_string()])
854 );
855 }
856
857 #[test]
858 fn test_parse_negated_set_multiple() {
859 let p = parse_path("!(<a>|<b>)").expect("path parse should succeed");
860 assert_eq!(
861 p,
862 PropertyPath::NegatedSet(vec!["a".to_string(), "b".to_string()])
863 );
864 }
865
866 #[test]
867 fn test_parse_bare_name() {
868 let p = parse_path("rdf:type").expect("path parse should succeed");
869 assert_eq!(p, PropertyPath::Iri("rdf:type".to_string()));
870 }
871
872 #[test]
875 fn test_zero_or_more_complex_cycle() {
876 let mut g = PathGraph::new();
877 g.add_triple("a", "p", "b");
878 g.add_triple("b", "p", "c");
879 g.add_triple("c", "p", "a"); let ev = evaluator();
881 let path = PropertyPath::ZeroOrMore(Box::new(PropertyPath::Iri("p".to_string())));
882 let mut r = ev.evaluate(&g, &path, "a");
883 r.sort();
884 assert_eq!(r, vec!["a", "b", "c"]);
885 }
886
887 #[test]
888 fn test_one_or_more_self_loop() {
889 let mut g = PathGraph::new();
890 g.add_triple("a", "p", "a"); let ev = evaluator();
892 let path = PropertyPath::OneOrMore(Box::new(PropertyPath::Iri("p".to_string())));
893 let mut r = ev.evaluate(&g, &path, "a");
894 r.sort();
895 assert_eq!(r, vec!["a"]);
896 }
897
898 #[test]
901 fn test_path_error_display() {
902 let e = PathError::UnknownSyntax("???".to_string());
903 assert!(e.to_string().contains("Unknown path syntax"));
904
905 let e2 = PathError::EmptyPath;
906 assert!(e2.to_string().contains("Empty path"));
907 }
908
909 #[test]
912 fn test_evaluator_default() {
913 let ev = PropertyPathEvaluator;
914 let g = PathGraph::new();
915 let path = PropertyPath::Iri("p".to_string());
916 let r = ev.evaluate(&g, &path, "x");
917 assert!(r.is_empty());
918 }
919
920 #[test]
921 fn test_graph_default() {
922 let g = PathGraph::default();
923 assert!(g.subjects().is_empty());
924 }
925}