manas_http/header/slug/
mod.rs1use std::{borrow::Cow, fmt::Display, ops::Deref};
5
6use headers::{Header, HeaderName, HeaderValue};
7use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Slug {
25 pct_decoded_slugtext: String,
27}
28
29pub static SLUG: HeaderName = HeaderName::from_static("slug");
31
32pub static SLUG_ENCODE_ASCII_SET: AsciiSet = CONTROLS.add(b'%');
34
35impl Header for Slug {
36 fn name() -> &'static HeaderName {
37 &SLUG
38 }
39
40 fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
41 where
42 Self: Sized,
43 I: Iterator<Item = &'i HeaderValue>,
44 {
45 let mut slugtext = String::new();
46 for value in values {
47 let pct_decoded_value = percent_decode(value.as_bytes()).decode_utf8_lossy();
49
50 if !slugtext.is_empty() {
51 slugtext.push(',');
53 }
54 slugtext.push_str(pct_decoded_value.as_ref());
55 }
56 Ok(Self {
57 pct_decoded_slugtext: slugtext,
58 })
59 }
60
61 fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
62 values.extend(std::iter::once(
63 HeaderValue::from_str(
64 Into::<Cow<str>>::into(utf8_percent_encode(
65 &self.pct_decoded_slugtext,
66 &SLUG_ENCODE_ASCII_SET,
67 ))
68 .as_ref(),
69 )
70 .expect("Must be valid header"),
71 ))
72 }
73}
74
75impl Display for Slug {
76 #[inline]
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 self.pct_decoded_slugtext.fmt(f)
79 }
80}
81
82impl Deref for Slug {
83 type Target = str;
84
85 #[inline]
86 fn deref(&self) -> &Self::Target {
87 &self.pct_decoded_slugtext
88 }
89}
90
91impl AsRef<str> for Slug {
92 #[inline]
93 fn as_ref(&self) -> &str {
94 &self.pct_decoded_slugtext
95 }
96}
97
98impl From<String> for Slug {
99 #[inline]
100 fn from(s: String) -> Self {
101 Self {
102 pct_decoded_slugtext: s,
103 }
104 }
105}
106
107impl From<&str> for Slug {
108 #[inline]
109 fn from(s: &str) -> Self {
110 Self {
111 pct_decoded_slugtext: s.to_owned(),
112 }
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use claims::*;
119 use rstest::*;
120
121 use super::*;
122
123 #[rstest]
124 #[case(&[""], "")]
125 #[case(&["a b"], "a b")]
126 #[case(&["abc", "def"], "abc,def")]
127 #[case(&["%E0%A4%B0%E0%A4%BE%E0%A4%AE %E0%A4%B2%E0%A4%95%E0%A5%8D%E0%A4%B7%E0%A5%8D%E0%A4%AE%E0%A4%A3"], "राम लक्ष्मण")]
128 fn decode_works_correctly(#[case] header_value_strs: &[&str], #[case] expected_slug_str: &str) {
129 let header_values: Vec<HeaderValue> = header_value_strs
130 .iter()
131 .map(|v| assert_ok!(HeaderValue::from_str(v)))
132 .collect();
133 let slug = assert_ok!(Slug::decode(&mut header_values.iter()));
134 assert_eq!(slug.as_ref(), expected_slug_str);
135 }
136
137 #[rstest]
138 #[case("a b", "a b")]
139 #[case("a/b", "a/b")]
140 #[case("a%b", "a%25b")]
141 #[case("राम लक्ष्मण", "%E0%A4%B0%E0%A4%BE%E0%A4%AE %E0%A4%B2%E0%A4%95%E0%A5%8D%E0%A4%B7%E0%A5%8D%E0%A4%AE%E0%A4%A3")]
142 fn encode_works_correctly(#[case] slug_str: &str, #[case] expected_header_str: &str) {
143 let slug: Slug = slug_str.to_string().into();
144 let mut headers = Vec::<HeaderValue>::new();
145 slug.encode(&mut headers);
146
147 let encoded_header = headers.first().expect("Slug value not encoded");
148 let encoded_header_str = assert_ok!(encoded_header.to_str(), "Encoding corruption");
149 assert_eq!(encoded_header_str, expected_header_str, "Invalid encoding");
150 }
151}