Skip to main content

tokn_headers/
value.rs

1//! Header value storage. Values are kept as `Cow<'static, str>` so static
2//! defaults (e.g. `"application/json"`) cost zero allocations while dynamic
3//! values (auth tokens, session IDs) heap-allocate on demand.
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use smol_str::SmolStr;
7use std::borrow::Cow;
8use std::fmt;
9
10/// A header value. Use [`HeaderValue::from_static`] for `'static str` literals
11/// to avoid allocation.
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub struct HeaderValue(Cow<'static, str>);
14
15impl Serialize for HeaderValue {
16  fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
17    serializer.serialize_str(&self.0)
18  }
19}
20
21impl<'de> Deserialize<'de> for HeaderValue {
22  fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
23    let s = String::deserialize(deserializer)?;
24    Ok(HeaderValue::from_string(s))
25  }
26}
27
28impl HeaderValue {
29  /// Construct from a `'static` string literal at compile time.
30  pub const fn from_static(s: &'static str) -> Self {
31    Self(Cow::Borrowed(s))
32  }
33
34  /// Construct from an owned [`String`]. Allocates only if the input is non-empty.
35  pub fn from_string(s: String) -> Self {
36    Self(Cow::Owned(s))
37  }
38
39  /// Borrow the value as a `&str`.
40  pub fn as_str(&self) -> &str {
41    &self.0
42  }
43
44  /// Length of the value in bytes.
45  pub fn len(&self) -> usize {
46    self.0.len()
47  }
48
49  /// Whether the value is empty.
50  pub fn is_empty(&self) -> bool {
51    self.0.is_empty()
52  }
53}
54
55impl fmt::Display for HeaderValue {
56  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57    f.write_str(&self.0)
58  }
59}
60
61impl From<&'static str> for HeaderValue {
62  fn from(value: &'static str) -> Self {
63    Self::from_static(value)
64  }
65}
66
67impl From<String> for HeaderValue {
68  fn from(value: String) -> Self {
69    Self::from_string(value)
70  }
71}
72
73impl From<SmolStr> for HeaderValue {
74  fn from(value: SmolStr) -> Self {
75    Self(Cow::Owned(value.to_string()))
76  }
77}
78
79impl From<Cow<'static, str>> for HeaderValue {
80  fn from(value: Cow<'static, str>) -> Self {
81    Self(value)
82  }
83}
84
85impl From<&HeaderValue> for HeaderValue {
86  fn from(value: &HeaderValue) -> Self {
87    value.clone()
88  }
89}
90
91#[cfg(test)]
92mod tests {
93  use super::*;
94
95  #[test]
96  fn from_static_is_borrowed() {
97    let v = HeaderValue::from_static("application/json");
98    assert!(matches!(v.0, Cow::Borrowed(_)));
99    assert_eq!(v.as_str(), "application/json");
100  }
101
102  #[test]
103  fn from_string_is_owned() {
104    let v: HeaderValue = String::from("Bearer abc").into();
105    assert!(matches!(v.0, Cow::Owned(_)));
106    assert_eq!(v.as_str(), "Bearer abc");
107  }
108
109  #[test]
110  fn from_smol_str() {
111    let v: HeaderValue = SmolStr::new("ses_42").into();
112    assert_eq!(v.as_str(), "ses_42");
113  }
114
115  #[test]
116  fn equality_ignores_storage_kind() {
117    let a = HeaderValue::from_static("xyz");
118    let b: HeaderValue = String::from("xyz").into();
119    assert_eq!(a, b);
120  }
121}