trellis_core/
resource_key.rs1use core::fmt;
2
3const MULTI_SEGMENT_PREFIX: &str = "segments:";
4const ESCAPED_SINGLE_SEGMENT_PREFIX: &str = "segment:";
5
6#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
8pub struct ResourceKey {
9 inner: Box<ResourceKeyInner>,
10}
11
12#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
13struct ResourceKeyInner {
14 segments: Box<[Box<str>]>,
15 encoded: Box<str>,
16}
17
18impl ResourceKey {
19 pub fn new(key: impl Into<Box<str>>) -> Self {
21 Self::from_boxed_segments(vec![key.into()].into_boxed_slice())
22 }
23
24 pub fn from_segments<I, S>(segments: I) -> Self
34 where
35 I: IntoIterator<Item = S>,
36 S: Into<Box<str>>,
37 {
38 Self::try_from_segments(segments).expect("resource keys require at least one segment")
39 }
40
41 pub fn try_from_segments<I, S>(segments: I) -> Option<Self>
44 where
45 I: IntoIterator<Item = S>,
46 S: Into<Box<str>>,
47 {
48 let segments = segments
49 .into_iter()
50 .map(Into::into)
51 .collect::<Vec<_>>()
52 .into_boxed_slice();
53 (!segments.is_empty()).then(|| Self::from_boxed_segments(segments))
54 }
55
56 pub fn wildcard(key: impl AsRef<str>) -> Self {
61 Self::from_segments(["wildcard", key.as_ref()])
62 }
63
64 pub fn as_str(&self) -> &str {
70 &self.inner.encoded
71 }
72
73 pub fn segments(&self) -> impl ExactSizeIterator<Item = &str> + '_ {
75 self.inner.segments.iter().map(|segment| segment.as_ref())
76 }
77
78 pub fn segment(&self, index: usize) -> Option<&str> {
80 self.inner
81 .segments
82 .get(index)
83 .map(|segment| segment.as_ref())
84 }
85
86 pub fn segment_count(&self) -> usize {
88 self.inner.segments.len()
89 }
90
91 fn from_boxed_segments(segments: Box<[Box<str>]>) -> Self {
92 let encoded = encode_segments(&segments);
93 Self {
94 inner: Box::new(ResourceKeyInner { segments, encoded }),
95 }
96 }
97}
98
99impl fmt::Debug for ResourceKey {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 let segments = self.segments().collect::<Vec<_>>();
102 f.debug_tuple("ResourceKey").field(&segments).finish()
103 }
104}
105
106fn encode_segments(segments: &[Box<str>]) -> Box<str> {
107 if let [segment] = segments {
108 if segment.starts_with(MULTI_SEGMENT_PREFIX)
109 || segment.starts_with(ESCAPED_SINGLE_SEGMENT_PREFIX)
110 {
111 return encode_single_segment(segment).into_boxed_str();
112 }
113 return segment.clone();
114 }
115
116 let mut encoded = String::from(MULTI_SEGMENT_PREFIX);
117 encoded.push_str(&segments.len().to_string());
118 encoded.push(':');
119 for segment in segments {
120 encoded.push_str(&segment.len().to_string());
121 encoded.push(':');
122 encoded.push_str(segment);
123 }
124 encoded.into_boxed_str()
125}
126
127fn encode_single_segment(segment: &str) -> String {
128 let mut encoded = String::from(ESCAPED_SINGLE_SEGMENT_PREFIX);
129 encoded.push_str(&segment.len().to_string());
130 encoded.push(':');
131 encoded.push_str(segment);
132 encoded
133}
134
135#[cfg(feature = "serde")]
136impl serde::Serialize for ResourceKey {
137 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
138 where
139 S: serde::Serializer,
140 {
141 if let [segment] = self.inner.segments.as_ref() {
142 serializer.serialize_str(segment)
143 } else {
144 serializer.collect_seq(self.segments())
145 }
146 }
147}
148
149#[cfg(feature = "serde")]
150impl<'de> serde::Deserialize<'de> for ResourceKey {
151 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152 where
153 D: serde::Deserializer<'de>,
154 {
155 #[derive(serde::Deserialize)]
156 #[serde(untagged)]
157 enum EncodedResourceKey {
158 Single(String),
159 Segments(Vec<String>),
160 }
161
162 match EncodedResourceKey::deserialize(deserializer)? {
163 EncodedResourceKey::Single(segment) => Ok(Self::new(segment)),
164 EncodedResourceKey::Segments(segments) => {
165 Self::try_from_segments(segments).ok_or_else(|| {
166 serde::de::Error::custom("resource key must contain at least one segment")
167 })
168 }
169 }
170 }
171}