use std::borrow::Cow;
use std::fmt;
use http::Uri;
use crate::request::{Request, Response};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Tag(Cow<'static, str>);
impl Tag {
#[must_use]
pub const fn from_static(value: &'static str) -> Self {
Self(Cow::Borrowed(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for Tag {
fn from(value: &str) -> Self {
Self(Cow::Owned(value.to_owned()))
}
}
impl From<String> for Tag {
fn from(value: String) -> Self {
Self(Cow::Owned(value))
}
}
impl AsRef<str> for Tag {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
pub trait Tags: Copy + Into<Tag> + 'static {
const ALL: &'static [Self];
#[must_use]
fn from_tag(tag: &Tag) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|member| (*member).into() == *tag)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Depth(u32);
impl Depth {
pub const ROOT: Self = Self(0);
#[must_use]
pub const fn new(depth: u32) -> Self {
Self(depth)
}
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
#[must_use]
pub const fn child(self) -> Self {
Self(self.0.saturating_add(1))
}
}
impl fmt::Display for Depth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Attempt(u32);
impl Attempt {
pub const FIRST: Self = Self(0);
#[must_use]
pub const fn new(attempt: u32) -> Self {
Self(attempt)
}
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
#[must_use]
pub const fn next(self) -> Self {
Self(self.0.saturating_add(1))
}
}
impl fmt::Display for Attempt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
pub trait RequestExt {
fn tag(&self) -> Option<&Tag>;
fn depth(&self) -> Depth;
fn attempt(&self) -> Attempt;
}
impl<B> RequestExt for Request<B> {
fn tag(&self) -> Option<&Tag> {
self.extensions().get()
}
fn depth(&self) -> Depth {
self.extensions().get().copied().unwrap_or_default()
}
fn attempt(&self) -> Attempt {
self.extensions().get().copied().unwrap_or_default()
}
}
#[derive(Debug, Clone)]
struct FinalUri(Uri);
pub trait ResponseExt {
fn final_uri(&self) -> Option<&Uri>;
fn set_final_uri(&mut self, uri: Uri);
}
impl<B> ResponseExt for Response<B> {
fn final_uri(&self) -> Option<&Uri> {
self.extensions().get::<FinalUri>().map(|FinalUri(uri)| uri)
}
fn set_final_uri(&mut self, uri: Uri) {
self.extensions_mut().insert(FinalUri(uri));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_metadata_defaults() {
let request = Request::new(());
assert_eq!(request.tag(), None);
assert_eq!(request.depth(), Depth::ROOT);
assert_eq!(request.attempt(), Attempt::FIRST);
}
#[test]
fn response_final_uri() {
let mut response = Response::new(());
assert_eq!(response.final_uri(), None);
response.set_final_uri(Uri::from_static("https://example.com/moved"));
assert_eq!(response.final_uri().unwrap(), "https://example.com/moved");
}
#[test]
fn request_metadata_from_extensions() {
let mut request = Request::new(());
request.extensions_mut().insert(Tag::from_static("page"));
request.extensions_mut().insert(Depth::new(2));
assert_eq!(request.tag(), Some(&Tag::from("page")));
assert_eq!(request.depth().child(), Depth::new(3));
}
}