ic_http_certification/tree/
certification_tree_path.rs

1use crate::utils::{
2    EXACT_PATH_TERMINATOR, EXACT_PATH_TERMINATOR_BYTES, PATH_PREFIX, WILDCARD_PATH_TERMINATOR,
3    WILDCARD_PATH_TERMINATOR_BYTES,
4};
5use std::borrow::Cow;
6
7pub(super) type CertificationTreePathSegment = Vec<u8>;
8pub(super) type InnerTreePath = Vec<CertificationTreePathSegment>;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub(super) enum HttpCertificationPathType<'a> {
12    Exact(Cow<'a, str>),
13    Wildcard(Cow<'a, str>),
14}
15
16/// A path to an [HttpCertification](crate::HttpCertification) in an
17/// [HttpCertificationTree](crate::HttpCertificationTree).
18///
19/// Two variants are supported:
20///
21/// - The [Exact](HttpCertificationPath::exact()) variant is used for paths that match a full URL path.
22/// For example, `HttpCertificationPath::exact('/foo')` will match the URL path `/foo` but not `/foo/bar`
23/// or `/foo/baz`.
24///
25/// - The [Wildcard](HttpCertificationPath::wildcard()) variant is used for paths that match a URL path prefix.
26/// For example, `HttpCertificationPath::wildcard('/foo')` will match the URL paths `/foo/bar` and `/foo/baz`.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct HttpCertificationPath<'a>(HttpCertificationPathType<'a>);
29
30impl<'a> HttpCertificationPath<'a> {
31    /// An exact path to an [HttpCertification](crate::HttpCertification) in an
32    /// [HttpCertificationTree](crate::HttpCertificationTree). This path will match only
33    /// [HttpRequest](crate::HttpRequest) URL paths that are exactly the same as the given path.
34    pub fn exact(path: impl Into<Cow<'a, str>>) -> Self {
35        Self(HttpCertificationPathType::Exact(path.into()))
36    }
37
38    /// A wildcard path to an [HttpCertification](crate::HttpCertification) in an
39    /// [HttpCertificationTree](crate::HttpCertificationTree). This path will match all
40    /// [HttpRequest](crate::HttpRequest) URL paths that start with the given prefix.
41    pub fn wildcard(path: impl Into<Cow<'a, str>>) -> Self {
42        Self(HttpCertificationPathType::Wildcard(path.into()))
43    }
44
45    pub(super) fn to_tree_path(&self) -> InnerTreePath {
46        match &self.0 {
47            HttpCertificationPathType::Exact(path) => {
48                Self::path_to_segments(path.as_ref(), EXACT_PATH_TERMINATOR_BYTES)
49            }
50            HttpCertificationPathType::Wildcard(path) => {
51                Self::path_to_segments(path.as_ref(), WILDCARD_PATH_TERMINATOR_BYTES)
52            }
53        }
54    }
55
56    pub(super) fn get_type(&self) -> &HttpCertificationPathType<'a> {
57        &self.0
58    }
59
60    /// Converts this path into a format suitable for use in the `expr_path` field of the `IC-Certificate` header.
61    pub fn to_expr_path(&self) -> Vec<String> {
62        match &self.0 {
63            HttpCertificationPathType::Exact(path) => {
64                Self::path_to_string_segments(path.as_ref(), EXACT_PATH_TERMINATOR)
65            }
66            HttpCertificationPathType::Wildcard(path) => {
67                Self::path_to_string_segments(path.as_ref(), WILDCARD_PATH_TERMINATOR)
68            }
69        }
70    }
71
72    fn path_to_segments(path: &str, terminator: &[u8]) -> InnerTreePath {
73        let mut path_segments = path
74            .split('/')
75            .filter(|e| !e.is_empty())
76            .map(str::as_bytes)
77            .map(Vec::from)
78            .collect::<InnerTreePath>();
79        if path.ends_with('/') {
80            path_segments.push("".as_bytes().to_vec());
81        }
82
83        path_segments.push(terminator.to_vec());
84
85        path_segments
86    }
87
88    fn path_to_string_segments(path: &str, terminator: &str) -> Vec<String> {
89        let mut path_segments = vec![PATH_PREFIX.to_string()];
90        path_segments.append(
91            &mut path
92                .split('/')
93                .filter(|e| !e.is_empty())
94                .map(String::from)
95                .collect(),
96        );
97        if path.ends_with('/') {
98            path_segments.push("".to_string());
99        }
100
101        path_segments.push(terminator.to_string());
102
103        path_segments
104    }
105}
106
107impl<'a> From<HttpCertificationPath<'a>> for Cow<'a, HttpCertificationPath<'a>> {
108    fn from(path: HttpCertificationPath<'a>) -> Cow<'a, HttpCertificationPath<'a>> {
109        Cow::Owned(path)
110    }
111}
112
113impl<'a> From<&'a HttpCertificationPath<'a>> for Cow<'a, HttpCertificationPath<'a>> {
114    fn from(path: &'a HttpCertificationPath<'a>) -> Cow<'a, HttpCertificationPath<'a>> {
115        Cow::Borrowed(path)
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use rstest::*;
123    use rstest_reuse::*;
124
125    #[template]
126    #[rstest]
127    #[case("", vec!["<$>"])]
128    #[case("/", vec!["", "<$>"])]
129    #[case("/foo", vec!["foo", "<$>"])]
130    #[case("foo", vec!["foo", "<$>"])]
131    #[case("/foo/", vec!["foo", "", "<$>"])]
132    #[case("foo/", vec!["foo", "", "<$>"])]
133    #[case("/foo/bar", vec!["foo", "bar", "<$>"])]
134    #[case("foo/bar", vec!["foo", "bar", "<$>"])]
135    #[case("/foo/bar/", vec!["foo", "bar", "", "<$>"])]
136    #[case("foo/bar/", vec!["foo", "bar", "", "<$>"])]
137    fn exact_paths(#[case] path: &str, #[case] expected: Vec<&str>) {}
138
139    #[template]
140    #[rstest]
141    #[case("", vec!["<*>"])]
142    #[case("/", vec!["", "<*>"])]
143    #[case("/foo", vec!["foo", "<*>"])]
144    #[case("foo", vec!["foo", "<*>"])]
145    #[case("/foo/", vec!["foo", "", "<*>"])]
146    #[case("foo/", vec!["foo", "", "<*>"])]
147    #[case("/foo/bar", vec!["foo", "bar", "<*>"])]
148    #[case("foo/bar", vec!["foo", "bar", "<*>"])]
149    #[case("/foo/bar/", vec!["foo", "bar", "", "<*>"])]
150    #[case("foo/bar/", vec!["foo", "bar", "", "<*>"])]
151    fn wildcard_paths(#[case] path: &str, #[case] expected: Vec<&str>) {}
152
153    #[apply(exact_paths)]
154    fn exact_path_to_tree_path(#[case] path: &str, #[case] expected: Vec<&str>) {
155        let path = HttpCertificationPath::exact(path);
156
157        let result = path.to_tree_path();
158        let expected = expected
159            .iter()
160            .map(|segment| segment.as_bytes().to_vec())
161            .collect::<InnerTreePath>();
162
163        assert_eq!(result, expected);
164    }
165
166    #[apply(wildcard_paths)]
167    fn wildcard_path_to_tree_path(#[case] path: &str, #[case] expected: Vec<&str>) {
168        let path = HttpCertificationPath::wildcard(path);
169
170        let result = path.to_tree_path();
171        let expected = expected
172            .iter()
173            .map(|segment| segment.as_bytes().to_vec())
174            .collect::<InnerTreePath>();
175
176        assert_eq!(result, expected);
177    }
178
179    #[apply(exact_paths)]
180    fn exact_path_to_expr_path(#[case] path: &str, #[case] expected: Vec<&str>) {
181        let path = HttpCertificationPath::exact(path);
182
183        let result = path.to_expr_path();
184        let expected = [PATH_PREFIX]
185            .iter()
186            .chain(expected.iter())
187            .map(|segment| segment.to_string())
188            .collect::<Vec<_>>();
189
190        assert_eq!(result, expected);
191    }
192
193    #[apply(wildcard_paths)]
194    fn wildcard_path_to_expr_path(#[case] path: &str, #[case] expected: Vec<&str>) {
195        let path = HttpCertificationPath::wildcard(path);
196
197        let result = path.to_expr_path();
198        let expected = [PATH_PREFIX]
199            .iter()
200            .chain(expected.iter())
201            .map(|segment| segment.to_string())
202            .collect::<Vec<_>>();
203
204        assert_eq!(result, expected);
205    }
206}