scientific_workflow/configuration/
parameter_path.rs1use std::fmt;
4
5#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7pub struct ParameterPath {
8 segments: Box<[Box<str>]>,
9 identifier: Box<str>,
10}
11
12impl ParameterPath {
13 pub(crate) fn root(key: impl Into<Box<str>>) -> Self {
14 Self::from_segments(vec![key.into()]).expect("root parameter key is nonempty")
15 }
16
17 pub(crate) fn from_segments(segments: Vec<Box<str>>) -> Option<Self> {
18 if segments.is_empty() {
19 return None;
20 }
21 let identifier = encode_identifier(&segments).into_boxed_str();
22 Some(Self {
23 segments: segments.into_boxed_slice(),
24 identifier,
25 })
26 }
27
28 pub fn parse(key: &str) -> Option<Self> {
30 let pointer = key.strip_prefix('/')?;
31 let segments = pointer
32 .split('/')
33 .map(decode_pointer_segment)
34 .collect::<Option<Vec<_>>>()?;
35 Self::from_segments(segments.into_iter().map(String::into_boxed_str).collect())
36 }
37
38 pub(crate) fn appended(&self, segment: impl Into<Box<str>>) -> Self {
39 let mut segments = self.segments.to_vec();
40 segments.push(segment.into());
41 Self::from_segments(segments).expect("appending a nonempty path segment remains valid")
42 }
43
44 pub fn segments(&self) -> impl ExactSizeIterator<Item = &str> {
45 self.segments.iter().map(AsRef::as_ref)
46 }
47
48 pub fn is_ancestor_of(&self, other: &Self) -> bool {
49 self.segments.len() < other.segments.len()
50 && self
51 .segments
52 .iter()
53 .zip(other.segments.iter())
54 .all(|(left, right)| left == right)
55 }
56
57 pub fn identifier(&self) -> &str {
59 &self.identifier
60 }
61
62 pub fn to_json_pointer(&self) -> String {
63 let mut output = String::new();
64 for segment in &self.segments {
65 output.push('/');
66 for character in segment.chars() {
67 match character {
68 '~' => output.push_str("~0"),
69 '/' => output.push_str("~1"),
70 _ => output.push(character),
71 }
72 }
73 }
74 output
75 }
76}
77
78impl fmt::Display for ParameterPath {
79 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
80 formatter.write_str(self.identifier())
81 }
82}
83
84fn encode_identifier(segments: &[Box<str>]) -> String {
85 let mut output = String::new();
86 for segment in segments {
87 output.push('/');
88 for character in segment.chars() {
89 match character {
90 '~' => output.push_str("~0"),
91 '/' => output.push_str("~1"),
92 _ => output.push(character),
93 }
94 }
95 }
96 output
97}
98
99fn decode_pointer_segment(segment: &str) -> Option<String> {
100 let mut output = String::with_capacity(segment.len());
101 let mut characters = segment.chars();
102 while let Some(character) = characters.next() {
103 if character != '~' {
104 output.push(character);
105 continue;
106 }
107 match characters.next()? {
108 '0' => output.push('~'),
109 '1' => output.push('/'),
110 _ => return None,
111 }
112 }
113 Some(output)
114}