1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use core::fmt::Debug;
use core::hash::{Hash, Hasher};

use toad_array::{AppendCopy, Array};
use toad_hash::Blake2Hasher;

use crate::repeat::{PATH, QUERY};
use crate::{Message, MessageOptions, OptionMap};

/// Default hasher used for [`CacheKey`]
///
/// Hashes:
///  - [Message Code](toad_msg::Message.code)
///  - [Uri-Path](toad_msg::opt::known::no_repeat::HOST)
///  - [Uri-Query](toad_msg::opt::known::no_repeat::HOST)
///  - [Accept](toad_msg::opt::known::no_repeat::ACCEPT)
#[derive(Debug, Clone, Default)]
pub struct DefaultCacheKey(Blake2Hasher);

impl DefaultCacheKey {
  /// Create a new `DefaultCacheKey`
  pub fn new() -> Self {
    Self::default()
  }
}

impl CacheKey for DefaultCacheKey {
  type Hasher = Blake2Hasher;

  fn hasher(&mut self) -> &mut Self::Hasher {
    &mut self.0
  }

  fn add_cache_key<P, O>(&mut self, msg: &Message<P, O>)
    where P: Array<Item = u8> + AppendCopy<u8>,
          O: OptionMap
  {
    msg.code.hash(&mut self.0);
    msg.opts.iter().for_each(|(num, vals)| {
                     if num.include_in_cache_key() {
                       vals.iter().for_each(|v| v.hash(&mut self.0))
                     }
                   });
  }
}

/// The cache key can be used to compare messages for representing
/// the same action against the same resource; for example requests
/// with different IDs but the same method and cache-key affecting options
/// (ex. path, query parameters) will yield the same cache-key.
///
/// Extends [`core::hash::Hash`] with the ability to build a cache-key of a message
/// in the hasher's state.
///
/// [`DefaultCacheKey`] Provides a default implementation.
pub trait CacheKey
  where Self: Sized + Debug
{
  /// Type used to generate hashes
  type Hasher: Hasher;

  #[allow(missing_docs)]
  fn hasher(&mut self) -> &mut Self::Hasher;

  /// Add this message's cache key to the hasher's internal state.
  ///
  /// After invoking this, to get the [`u64`] hash use [`Hasher::finish`].
  ///
  /// Alternately, use [`CacheKey::cache_key`] to go directly to the [`u64`] hash.
  fn add_cache_key<P, O>(&mut self, msg: &Message<P, O>)
    where P: Array<Item = u8> + AppendCopy<u8>,
          O: OptionMap;

  /// Add this message's cache key to the hasher's internal state and yield the [`u64`] hash.
  ///
  /// ```
  /// use core::hash::Hasher;
  ///
  /// use toad_msg::alloc::Message;
  /// use toad_msg::Type::Con;
  /// use toad_msg::{CacheKey, Code, DefaultCacheKey, Id, Token};
  ///
  /// let msg_a = Message::new(Con, Code::GET, Id(1), Token(Default::default()));
  /// let mut ha = DefaultCacheKey::new();
  /// ha.cache_key(&msg_a);
  ///
  /// let msg_b = Message::new(Con, Code::GET, Id(2), Token(Default::default()));
  /// let mut hb = DefaultCacheKey::new();
  /// hb.cache_key(&msg_a);
  ///
  /// assert_eq!(ha.hasher().finish(), hb.hasher().finish());
  /// ```
  fn cache_key<P, O>(&mut self, msg: &Message<P, O>) -> u64
    where P: Array<Item = u8> + AppendCopy<u8>,
          O: OptionMap
  {
    self.add_cache_key(msg);
    self.hasher().finish()
  }
}

impl<T> CacheKey for &mut T where T: CacheKey
{
  type Hasher = T::Hasher;

  fn hasher(&mut self) -> &mut Self::Hasher {
    <T as CacheKey>::hasher(self)
  }

  fn add_cache_key<P, O>(&mut self, msg: &Message<P, O>)
    where P: Array<Item = u8> + AppendCopy<u8>,
          O: OptionMap
  {
    <T as CacheKey>::add_cache_key(self, msg)
  }
}