toad_msg/
cache_key.rs

1use core::fmt::Debug;
2use core::hash::{Hash, Hasher};
3
4use toad_array::{AppendCopy, Array};
5use toad_hash::Blake2Hasher;
6
7use crate::repeat::{PATH, QUERY};
8use crate::{Message, MessageOptions, OptionMap};
9
10/// Default hasher used for [`CacheKey`]
11///
12/// Hashes:
13///  - [Message Code](toad_msg::Message.code)
14///  - [Uri-Path](toad_msg::opt::known::no_repeat::HOST)
15///  - [Uri-Query](toad_msg::opt::known::no_repeat::HOST)
16///  - [Accept](toad_msg::opt::known::no_repeat::ACCEPT)
17#[derive(Debug, Clone, Default)]
18pub struct DefaultCacheKey(Blake2Hasher);
19
20impl DefaultCacheKey {
21  /// Create a new `DefaultCacheKey`
22  pub fn new() -> Self {
23    Self::default()
24  }
25}
26
27impl CacheKey for DefaultCacheKey {
28  type Hasher = Blake2Hasher;
29
30  fn hasher(&mut self) -> &mut Self::Hasher {
31    &mut self.0
32  }
33
34  fn add_cache_key<P, O>(&mut self, msg: &Message<P, O>)
35    where P: Array<Item = u8> + AppendCopy<u8>,
36          O: OptionMap
37  {
38    msg.code.hash(&mut self.0);
39    msg.opts.iter().for_each(|(num, vals)| {
40                     if num.include_in_cache_key() {
41                       vals.iter().for_each(|v| v.hash(&mut self.0))
42                     }
43                   });
44  }
45}
46
47/// The cache key can be used to compare messages for representing
48/// the same action against the same resource; for example requests
49/// with different IDs but the same method and cache-key affecting options
50/// (ex. path, query parameters) will yield the same cache-key.
51///
52/// Extends [`core::hash::Hash`] with the ability to build a cache-key of a message
53/// in the hasher's state.
54///
55/// [`DefaultCacheKey`] Provides a default implementation.
56pub trait CacheKey
57  where Self: Sized + Debug
58{
59  /// Type used to generate hashes
60  type Hasher: Hasher;
61
62  #[allow(missing_docs)]
63  fn hasher(&mut self) -> &mut Self::Hasher;
64
65  /// Add this message's cache key to the hasher's internal state.
66  ///
67  /// After invoking this, to get the [`u64`] hash use [`Hasher::finish`].
68  ///
69  /// Alternately, use [`CacheKey::cache_key`] to go directly to the [`u64`] hash.
70  fn add_cache_key<P, O>(&mut self, msg: &Message<P, O>)
71    where P: Array<Item = u8> + AppendCopy<u8>,
72          O: OptionMap;
73
74  /// Add this message's cache key to the hasher's internal state and yield the [`u64`] hash.
75  ///
76  /// ```
77  /// use core::hash::Hasher;
78  ///
79  /// use toad_msg::alloc::Message;
80  /// use toad_msg::Type::Con;
81  /// use toad_msg::{CacheKey, Code, ContentFormat, DefaultCacheKey, Id, MessageOptions, Token};
82  ///
83  /// let mut msg_a = Message::new(Con, Code::GET, Id(1), Token(Default::default()));
84  /// msg_a.set_path("foo/bar");
85  /// msg_a.set_accept(ContentFormat::Text);
86  /// let mut ha = DefaultCacheKey::new();
87  /// ha.cache_key(&msg_a);
88  ///
89  /// let mut msg_b = Message::new(Con, Code::GET, Id(2), Token(Default::default()));
90  /// msg_b.set_accept(ContentFormat::Text);
91  /// msg_b.set_path("foo/bar");
92  /// let mut hb = DefaultCacheKey::new();
93  /// hb.cache_key(&msg_b);
94  ///
95  /// assert_eq!(ha.hasher().finish(), hb.hasher().finish());
96  /// ```
97  fn cache_key<P, O>(&mut self, msg: &Message<P, O>) -> u64
98    where P: Array<Item = u8> + AppendCopy<u8>,
99          O: OptionMap
100  {
101    self.add_cache_key(msg);
102    self.hasher().finish()
103  }
104}
105
106impl<T> CacheKey for &mut T where T: CacheKey
107{
108  type Hasher = T::Hasher;
109
110  fn hasher(&mut self) -> &mut Self::Hasher {
111    <T as CacheKey>::hasher(self)
112  }
113
114  fn add_cache_key<P, O>(&mut self, msg: &Message<P, O>)
115    where P: Array<Item = u8> + AppendCopy<u8>,
116          O: OptionMap
117  {
118    <T as CacheKey>::add_cache_key(self, msg)
119  }
120}
121
122#[cfg(test)]
123mod test {
124  use core::hash::Hasher;
125
126  use crate::alloc::Message;
127  use crate::{CacheKey, Code, ContentFormat, DefaultCacheKey, Id, MessageOptions, Token, Type};
128
129  #[test]
130  pub fn cache_key() {
131    fn req<F>(stuff: F) -> u64
132      where F: FnOnce(&mut Message)
133    {
134      let mut req = Message::new(Type::Con, Code::GET, Id(1), Token(Default::default()));
135      stuff(&mut req);
136
137      let mut h = DefaultCacheKey::new();
138      h.cache_key(&req);
139      h.hasher().finish()
140    }
141
142    assert_ne!(req(|r| {
143                 r.set_path("a/b/c").ok();
144               }),
145               req(|_| {}));
146    assert_eq!(req(|r| {
147                 r.set_path("a/b/c").ok();
148               }),
149               req(|r| {
150                 r.set_path("a/b/c").ok();
151               }));
152    assert_ne!(req(|r| {
153                 r.set_path("a/b/c").ok();
154                 r.add_query("filter[temp](less_than)=123").ok();
155               }),
156               req(|r| {
157                 r.set_path("a/b/c").ok();
158               }));
159    assert_ne!(req(|r| {
160                 r.set_path("a/b/c").ok();
161                 r.add_query("filter[temp](less_than)=123").ok();
162                 r.set_accept(ContentFormat::Json).ok();
163               }),
164               req(|r| {
165                 r.set_path("a/b/c").ok();
166                 r.add_query("filter[temp](less_than)=123").ok();
167                 r.set_accept(ContentFormat::Text).ok();
168               }));
169    assert_eq!(req(|r| {
170                 r.set_path("a/b/c").ok();
171                 r.add_query("filter[temp](less_than)=123").ok();
172                 r.set_accept(ContentFormat::Json).ok();
173               }),
174               req(|r| {
175                 r.set_path("a/b/c").ok();
176                 r.add_query("filter[temp](less_than)=123").ok();
177                 r.set_accept(ContentFormat::Json).ok();
178               }));
179  }
180}