spire_core/request/
meta.rs1use std::borrow::Cow;
4use std::fmt;
5
6use http::Uri;
7
8use crate::request::{Request, Response};
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
14pub struct Tag(Cow<'static, str>);
15
16impl Tag {
17 #[must_use]
19 pub const fn from_static(value: &'static str) -> Self {
20 Self(Cow::Borrowed(value))
21 }
22
23 #[must_use]
25 pub fn as_str(&self) -> &str {
26 &self.0
27 }
28}
29
30impl From<&str> for Tag {
31 fn from(value: &str) -> Self {
32 Self(Cow::Owned(value.to_owned()))
33 }
34}
35
36impl From<String> for Tag {
37 fn from(value: String) -> Self {
38 Self(Cow::Owned(value))
39 }
40}
41
42impl AsRef<str> for Tag {
43 fn as_ref(&self) -> &str {
44 self.as_str()
45 }
46}
47
48impl fmt::Display for Tag {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 f.write_str(self.as_str())
51 }
52}
53
54pub trait Tags: Copy + Into<Tag> + 'static {
104 const ALL: &'static [Self];
106
107 #[must_use]
109 fn from_tag(tag: &Tag) -> Option<Self> {
110 Self::ALL
111 .iter()
112 .copied()
113 .find(|member| (*member).into() == *tag)
114 }
115}
116
117#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
121pub struct Depth(u32);
122
123impl Depth {
124 pub const ROOT: Self = Self(0);
126
127 #[must_use]
129 pub const fn new(depth: u32) -> Self {
130 Self(depth)
131 }
132
133 #[must_use]
135 pub const fn get(self) -> u32 {
136 self.0
137 }
138
139 #[must_use]
141 pub const fn child(self) -> Self {
142 Self(self.0.saturating_add(1))
143 }
144}
145
146impl fmt::Display for Depth {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 self.0.fmt(f)
149 }
150}
151
152#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
156pub struct Attempt(u32);
157
158impl Attempt {
159 pub const FIRST: Self = Self(0);
161
162 #[must_use]
164 pub const fn new(attempt: u32) -> Self {
165 Self(attempt)
166 }
167
168 #[must_use]
170 pub const fn get(self) -> u32 {
171 self.0
172 }
173
174 #[must_use]
176 pub const fn next(self) -> Self {
177 Self(self.0.saturating_add(1))
178 }
179}
180
181impl fmt::Display for Attempt {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 self.0.fmt(f)
184 }
185}
186
187pub trait RequestExt {
192 fn tag(&self) -> Option<&Tag>;
194
195 fn depth(&self) -> Depth;
197
198 fn attempt(&self) -> Attempt;
200}
201
202impl<B> RequestExt for Request<B> {
203 fn tag(&self) -> Option<&Tag> {
204 self.extensions().get()
205 }
206
207 fn depth(&self) -> Depth {
208 self.extensions().get().copied().unwrap_or_default()
209 }
210
211 fn attempt(&self) -> Attempt {
212 self.extensions().get().copied().unwrap_or_default()
213 }
214}
215
216#[derive(Debug, Clone)]
218struct FinalUri(Uri);
219
220pub trait ResponseExt {
222 fn final_uri(&self) -> Option<&Uri>;
227
228 fn set_final_uri(&mut self, uri: Uri);
230}
231
232impl<B> ResponseExt for Response<B> {
233 fn final_uri(&self) -> Option<&Uri> {
234 self.extensions().get::<FinalUri>().map(|FinalUri(uri)| uri)
235 }
236
237 fn set_final_uri(&mut self, uri: Uri) {
238 self.extensions_mut().insert(FinalUri(uri));
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn request_metadata_defaults() {
248 let request = Request::new(());
249 assert_eq!(request.tag(), None);
250 assert_eq!(request.depth(), Depth::ROOT);
251 assert_eq!(request.attempt(), Attempt::FIRST);
252 }
253
254 #[test]
255 fn response_final_uri() {
256 let mut response = Response::new(());
257 assert_eq!(response.final_uri(), None);
258 response.set_final_uri(Uri::from_static("https://example.com/moved"));
259 assert_eq!(response.final_uri().unwrap(), "https://example.com/moved");
260 }
261
262 #[test]
263 fn request_metadata_from_extensions() {
264 let mut request = Request::new(());
265 request.extensions_mut().insert(Tag::from_static("page"));
266 request.extensions_mut().insert(Depth::new(2));
267 assert_eq!(request.tag(), Some(&Tag::from("page")));
268 assert_eq!(request.depth().child(), Depth::new(3));
269 }
270}