tor_keymgr/arti_path.rs
1//! [`ArtiPath`] and its associated helpers.
2
3use std::str::FromStr;
4
5use derive_deftly::{define_derive_deftly, Deftly};
6use derive_more::{Deref, Display, Into};
7use serde::{Deserialize, Serialize};
8use tor_persist::slug::{self, BadSlug};
9
10use crate::{ArtiPathRange, ArtiPathSyntaxError, KeySpecifierComponent};
11
12// TODO: this is only used for ArtiPaths (we should consider turning this
13// intro a regular impl ArtiPath {} and removing the macro).
14define_derive_deftly! {
15 /// Implement `new()`, `TryFrom<String>` in terms of `validate_str`, and `as_ref<str>`
16 //
17 // TODO maybe this is generally useful? Or maybe we should find a crate?
18 ValidatedString for struct, expect items:
19
20 impl $ttype {
21 #[doc = concat!("Create a new [`", stringify!($tname), "`].")]
22 ///
23 /// This function returns an error if `inner` is not in the right syntax.
24 pub fn new(inner: String) -> Result<Self, ArtiPathSyntaxError> {
25 Self::validate_str(&inner)?;
26 Ok(Self(inner))
27 }
28 }
29
30 impl TryFrom<String> for $ttype {
31 type Error = ArtiPathSyntaxError;
32
33 fn try_from(s: String) -> Result<Self, ArtiPathSyntaxError> {
34 Self::new(s)
35 }
36 }
37
38 impl FromStr for $ttype {
39 type Err = ArtiPathSyntaxError;
40
41 fn from_str(s: &str) -> Result<Self, ArtiPathSyntaxError> {
42 Self::validate_str(s)?;
43 Ok(Self(s.to_owned()))
44 }
45 }
46
47 impl AsRef<str> for $ttype {
48 fn as_ref(&self) -> &str {
49 &self.0.as_str()
50 }
51 }
52}
53
54/// A unique identifier for a particular instance of a key.
55///
56/// In an [`ArtiNativeKeystore`](crate::ArtiNativeKeystore), this also represents the path of the
57/// key relative to the root of the keystore, minus the file extension.
58///
59/// An `ArtiPath` is a nonempty sequence of [`Slug`](tor_persist::slug::Slug)s, separated by `/`. Path
60/// components may contain lowercase ASCII alphanumerics, and `-` or `_`.
61/// See [slug] for the full syntactic requirements.
62/// Consequently, leading or trailing or duplicated / are forbidden.
63///
64/// The last component of the path may optionally contain the encoded (string) representation
65/// of one or more
66/// [`KeySpecifierComponent`]
67/// s representing the denotators of the key.
68/// They are separated from the rest of the component, and from each other,
69/// by [`DENOTATOR_SEP`] characters.
70/// Denotators are encoded using their
71/// [`KeySpecifierComponent::to_slug`]
72/// implementation.
73/// The denotators **must** come after all the other fields.
74/// Denotator strings are validated in the same way as [`Slug`](tor-persist::slug::Slug)s.
75///
76/// For example, the last component of the path `"foo/bar/bax+denotator_example+1"`
77/// is `"bax+denotator_example+1"`.
78/// Its denotators are `"denotator_example"` and `"1"` (encoded as strings).
79///
80/// NOTE: There is a 1:1 mapping between a value that implements `KeySpecifier` and its
81/// corresponding `ArtiPath`. A `KeySpecifier` can be converted to an `ArtiPath`, but the reverse
82/// conversion is not supported.
83#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Deref, Into, Display)] //
84#[derive(Serialize, Deserialize)]
85#[serde(try_from = "String", into = "String")]
86#[derive(Deftly)]
87#[derive_deftly(ValidatedString)]
88pub struct ArtiPath(String);
89
90/// A separator for `ArtiPath`s.
91pub(crate) const PATH_SEP: char = '/';
92
93/// A separator for that marks the beginning of the keys denotators
94/// within an [`ArtiPath`].
95///
96/// This separator can only appear within the last component of an [`ArtiPath`],
97/// and the substring that follows it is assumed to be the string representation
98/// of the denotators of the path.
99pub const DENOTATOR_SEP: char = '+';
100
101impl ArtiPath {
102 /// Validate the underlying representation of an `ArtiPath`
103 fn validate_str(inner: &str) -> Result<(), ArtiPathSyntaxError> {
104 // Validate the denotators, if there are any.
105 let path = if let Some((main_part, denotators)) = inner.split_once(DENOTATOR_SEP) {
106 for d in denotators.split(DENOTATOR_SEP) {
107 let () = slug::check_syntax(d)?;
108 }
109
110 main_part
111 } else {
112 inner
113 };
114
115 if let Some(e) = path
116 .split(PATH_SEP)
117 .map(|s| {
118 if s.is_empty() {
119 Err(BadSlug::EmptySlugNotAllowed.into())
120 } else {
121 Ok(slug::check_syntax(s)?)
122 }
123 })
124 .find(|e| e.is_err())
125 {
126 return e;
127 }
128
129 Ok(())
130 }
131
132 /// Return the substring corresponding to the specified `range`.
133 ///
134 /// Returns `None` if `range` is not within the bounds of this `ArtiPath`.
135 ///
136 /// ### Example
137 /// ```
138 /// # use tor_keymgr::{ArtiPath, ArtiPathRange, ArtiPathSyntaxError};
139 /// # fn demo() -> Result<(), ArtiPathSyntaxError> {
140 /// let path = ArtiPath::new("foo_bar_bax_1".into())?;
141 ///
142 /// let range = ArtiPathRange::from(2..5);
143 /// assert_eq!(path.substring(&range), Some("o_b"));
144 ///
145 /// let range = ArtiPathRange::from(22..50);
146 /// assert_eq!(path.substring(&range), None);
147 /// # Ok(())
148 /// # }
149 /// #
150 /// # demo().unwrap();
151 /// ```
152 pub fn substring(&self, range: &ArtiPathRange) -> Option<&str> {
153 self.0.get(range.0.clone())
154 }
155
156 /// Create an `ArtiPath` from an `ArtiPath` and a list of denotators.
157 ///
158 /// If `cert_denotators` is empty, returns the specified `path` as-is.
159 /// Otherwise, returns an `ArtiPath` that consists of the specified `path`
160 /// followed by a [`DENOTATOR_SEP`] character and the specified denotators
161 /// (the denotators are encoded as described in the [`ArtiPath`] docs).
162 ///
163 /// Returns an error if any of the specified denotators are not valid `Slug`s.
164 //
165 /// ### Example
166 /// ```nocompile
167 /// # // `nocompile` because this function is not pub
168 /// # use tor_keymgr::{
169 /// # ArtiPath, ArtiPathRange, ArtiPathSyntaxError, KeySpecifierComponent,
170 /// # KeySpecifierComponentViaDisplayFromStr,
171 /// # };
172 /// # use derive_more::{Display, FromStr};
173 /// # #[derive(Display, FromStr)]
174 /// # struct Denotator(String);
175 /// # impl KeySpecifierComponentViaDisplayFromStr for Denotator {}
176 /// # fn demo() -> Result<(), ArtiPathSyntaxError> {
177 /// let path = ArtiPath::new("my_key_path".into())?;
178 /// let denotators = [
179 /// &Denotator("foo".to_string()) as &dyn KeySpecifierComponent,
180 /// &Denotator("bar".to_string()) as &dyn KeySpecifierComponent,
181 /// ];
182 ///
183 /// let expected_path = ArtiPath::new("my_key_path+foo+bar".into())?;
184 ///
185 /// assert_eq!(
186 /// ArtiPath::from_path_and_denotators(path.clone(), &denotators[..])?,
187 /// expected_path
188 /// );
189 ///
190 /// assert_eq!(
191 /// ArtiPath::from_path_and_denotators(path.clone(), &[])?,
192 /// path
193 /// );
194 /// # Ok(())
195 /// # }
196 /// #
197 /// # demo().unwrap();
198 /// ```
199 pub(crate) fn from_path_and_denotators(
200 path: ArtiPath,
201 cert_denotators: &[&dyn KeySpecifierComponent],
202 ) -> Result<ArtiPath, ArtiPathSyntaxError> {
203 if cert_denotators.is_empty() {
204 return Ok(path);
205 }
206
207 let path: String = [Ok(path.0)]
208 .into_iter()
209 .chain(
210 cert_denotators
211 .iter()
212 .map(|s| s.to_slug().map(|s| s.to_string())),
213 )
214 .collect::<Result<Vec<_>, _>>()?
215 .join(&DENOTATOR_SEP.to_string());
216
217 ArtiPath::new(path)
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 // @@ begin test lint list maintained by maint/add_warning @@
224 #![allow(clippy::bool_assert_comparison)]
225 #![allow(clippy::clone_on_copy)]
226 #![allow(clippy::dbg_macro)]
227 #![allow(clippy::mixed_attributes_style)]
228 #![allow(clippy::print_stderr)]
229 #![allow(clippy::print_stdout)]
230 #![allow(clippy::single_char_pattern)]
231 #![allow(clippy::unwrap_used)]
232 #![allow(clippy::unchecked_duration_subtraction)]
233 #![allow(clippy::useless_vec)]
234 #![allow(clippy::needless_pass_by_value)]
235 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
236 use super::*;
237
238 use derive_more::{Display, FromStr};
239
240 use crate::KeySpecifierComponentViaDisplayFromStr;
241
242 #[derive(Display, FromStr)]
243 struct Denotator(String);
244
245 impl KeySpecifierComponentViaDisplayFromStr for Denotator {}
246
247 #[test]
248 fn arti_path_from_path_and_denotators() {
249 let path = ArtiPath::new("my_key_path".into()).unwrap();
250 let denotators = [
251 &Denotator("foo".to_string()) as &dyn KeySpecifierComponent,
252 &Denotator("bar".to_string()) as &dyn KeySpecifierComponent,
253 &Denotator("baz".to_string()) as &dyn KeySpecifierComponent,
254 ];
255
256 let expected_path = ArtiPath::new("my_key_path+foo+bar+baz".into()).unwrap();
257
258 assert_eq!(
259 ArtiPath::from_path_and_denotators(path.clone(), &denotators[..]).unwrap(),
260 expected_path
261 );
262
263 assert_eq!(
264 ArtiPath::from_path_and_denotators(path.clone(), &[]).unwrap(),
265 path
266 );
267 }
268}