tinylfu_cached/cache/
put_or_update.rs

1use std::hash::Hash;
2use std::time::Duration;
3
4use crate::cache::config::WeightCalculationFn;
5use crate::cache::errors::Errors;
6use crate::cache::types::Weight;
7
8/// `PutOrUpdateRequest` encapsulates the fields that are required for a `put` or an `update` operation.
9///
10/// It is a parameter to `put_or_update` method of [`crate::cache::cached::CacheD`].
11///
12/// It allows put operation with `value`, `weight` and `time_to_live` and also provides flexibility in updating just the `value` or the `weight` or the `time_to_live` or all of these for an existing key.
13///
14/// It also allows removing the `time_to_live` against an existing key. Either of `time_to_live` or `remove_time_to_live` can be provided.
15///
16/// If `PutOrUpdateRequest` results in a `put` operation, the flag `remove_time_to_live` will have no significance.
17pub struct PutOrUpdateRequest<Key, Value>
18    where Key: Hash + Eq + Send + Sync + Clone,
19          Value: Send + Sync {
20    pub(crate) key: Key,
21    pub(crate) value: Option<Value>,
22    pub(crate) weight: Option<Weight>,
23    pub(crate) time_to_live: Option<Duration>,
24    pub(crate) remove_time_to_live: bool,
25}
26
27impl<Key, Value> PutOrUpdateRequest<Key, Value>
28    where Key: Hash + Eq + Send + Sync + Clone,
29          Value: Send + Sync {
30
31    /// Returns the weight in a `PutOrUpdateRequest`.
32    ///
33    /// Weight is either the client provided weight or calculated from the value and presence/absence of `time_to_live`
34    pub(crate) fn updated_weight(&self, weight_calculation_fn: &WeightCalculationFn<Key, Value>) -> Option<Weight> {
35        self.weight.or_else(|| self.value.as_ref().map(|value| {
36            if self.time_to_live.is_some() {
37                (weight_calculation_fn)(&self.key, value, true)
38            } else {
39                (weight_calculation_fn)(&self.key, value, false)
40            }
41        }))
42    }
43}
44
45/// Convenient builder that allows creating an instance of PutOrUpdateRequest.
46pub struct PutOrUpdateRequestBuilder<Key, Value>
47    where Key: Hash + Eq + Send + Sync + Clone,
48          Value: Send + Sync {
49    key: Key,
50    value: Option<Value>,
51    weight: Option<Weight>,
52    time_to_live: Option<Duration>,
53    remove_time_to_live: bool,
54}
55
56impl<Key, Value> PutOrUpdateRequestBuilder<Key, Value>
57    where Key: Hash + Eq + Send + Sync + Clone,
58          Value: Send + Sync {
59
60    /// Creates a new instance of `PutOrUpdateRequestBuilder` with the specified Key.
61    pub fn new(key: Key) -> PutOrUpdateRequestBuilder<Key, Value> {
62        PutOrUpdateRequestBuilder {
63            key,
64            value: None,
65            weight: None,
66            time_to_live: None,
67            remove_time_to_live: false,
68        }
69    }
70
71    /// Sets the value in `PutOrUpdateRequestBuilder`.
72    pub fn value(mut self, value: Value) -> PutOrUpdateRequestBuilder<Key, Value> {
73        self.value = Some(value);
74        self
75    }
76
77    /// Sets the weight, weight must be greater than zero.
78    pub fn weight(mut self, weight: Weight) -> PutOrUpdateRequestBuilder<Key, Value> {
79        assert!(weight > 0, "{}", Errors::KeyWeightGtZero("PutOrUpdate request builder"));
80        self.weight = Some(weight);
81        self
82    }
83
84    /// Sets the time_to_live.
85    pub fn time_to_live(mut self, time_to_live: Duration) -> PutOrUpdateRequestBuilder<Key, Value> {
86        self.time_to_live = Some(time_to_live);
87        self
88    }
89
90    /// Marks a flag to remove time_to_live.
91    ///
92    /// Either of `time_to_live` or `remove_time_to_live` can be provided.
93    pub fn remove_time_to_live(mut self) -> PutOrUpdateRequestBuilder<Key, Value> {
94        self.remove_time_to_live = true;
95        self
96    }
97
98    /// Builds an instance of PutOrUpdateRequest.
99    pub fn build(self) -> PutOrUpdateRequest<Key, Value> {
100        let valid_put_or_update = self.value.is_some() || self.weight.is_some() || self.time_to_live.is_some() || self.remove_time_to_live;
101        assert!(valid_put_or_update, "{}", Errors::InvalidPutOrUpdate);
102
103        let both_time_to_live_and_remove_time_to_live = self.time_to_live.is_some() && self.remove_time_to_live;
104        assert!(!both_time_to_live_and_remove_time_to_live, "{}", Errors::InvalidPutOrUpdateEitherTimeToLiveOrRemoveTimeToLive);
105
106        PutOrUpdateRequest {
107            key: self.key,
108            value: self.value,
109            weight: self.weight,
110            time_to_live: self.time_to_live,
111            remove_time_to_live: self.remove_time_to_live,
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use std::time::Duration;
119    use crate::cache::config::weight_calculation::Calculation;
120
121    use crate::cache::types::IsTimeToLiveSpecified;
122    use crate::cache::put_or_update::{PutOrUpdateRequest, PutOrUpdateRequestBuilder};
123
124    #[test]
125    #[should_panic]
126    fn invalid_put_or_update_with_weight_as_zero() {
127        let _: PutOrUpdateRequest<&str, &str> = PutOrUpdateRequestBuilder::new("topic").weight(0).build();
128    }
129
130    #[test]
131    #[should_panic]
132    fn invalid_put_or_update_with_only_key_specified() {
133        let _: PutOrUpdateRequest<&str, &str> = PutOrUpdateRequestBuilder::new("topic").build();
134    }
135
136    #[test]
137    #[should_panic]
138    fn invalid_put_or_update_with_both_time_to_live_and_remove_time_to_live_specified() {
139        let _: PutOrUpdateRequest<&str, &str> = PutOrUpdateRequestBuilder::new("topic").weight(10).remove_time_to_live().time_to_live(Duration::from_secs(9)).build();
140    }
141
142    #[test]
143    fn put_or_update_request_with_key_value() {
144        let put_or_update_request = PutOrUpdateRequestBuilder::new("topic").value("microservices").build();
145
146        assert_eq!("topic", put_or_update_request.key);
147        assert_eq!(Some("microservices"), put_or_update_request.value);
148    }
149
150    #[test]
151    fn put_or_update_request_with_weight() {
152        let put_or_update_request = PutOrUpdateRequestBuilder::new("topic").value("microservices").weight(10).build();
153
154        assert_eq!(Some(10), put_or_update_request.weight);
155    }
156
157    #[test]
158    fn put_or_update_request_with_time_to_live() {
159        let put_or_update_request = PutOrUpdateRequestBuilder::new("topic").value("microservices").time_to_live(Duration::from_secs(10)).build();
160
161        assert_eq!(Some(Duration::from_secs(10)), put_or_update_request.time_to_live);
162    }
163
164    #[test]
165    fn put_or_update_request_remove_time_to_live() {
166        let put_or_update_request = PutOrUpdateRequestBuilder::new("topic").value("microservices").remove_time_to_live().build();
167
168        assert!(put_or_update_request.remove_time_to_live);
169    }
170
171    #[test]
172    fn updated_weight_if_weight_is_provided() {
173        let put_or_update_request = PutOrUpdateRequestBuilder::new("topic").weight(10).build();
174        let weight_calculation_fn = Box::new(|_key: &&str, _value: &&str, _is_time_to_live_specified: IsTimeToLiveSpecified| 100);
175
176        assert_eq!(Some(10), put_or_update_request.updated_weight(&weight_calculation_fn));
177    }
178
179    #[test]
180    fn updated_weight_if_value_is_provided() {
181        let put_or_update_request = PutOrUpdateRequestBuilder::new("topic").value("cached").build();
182        let weight_calculation_fn = Box::new(|_key: &&str, value: &&str, _is_time_to_live_specified: IsTimeToLiveSpecified| value.len() as i64);
183
184        assert_eq!(Some(6), put_or_update_request.updated_weight(&weight_calculation_fn));
185    }
186
187    #[test]
188    fn updated_weight_if_weight_and_value_is_provided() {
189        let put_or_update_request = PutOrUpdateRequestBuilder::new("topic").value("cached").weight(22).build();
190        let weight_calculation_fn = Box::new(|_key: &&str, value: &&str, _is_time_to_live_specified: IsTimeToLiveSpecified| value.len() as i64);
191
192        assert_eq!(Some(22), put_or_update_request.updated_weight(&weight_calculation_fn));
193    }
194
195    #[test]
196    fn updated_weight_if_neither_weight_nor_value_is_provided() {
197        let put_or_update_request = PutOrUpdateRequestBuilder::new("topic").remove_time_to_live().build();
198        let weight_calculation_fn = Box::new(|_key: &&str, value: &&str, _is_time_to_live_specified: IsTimeToLiveSpecified| value.len() as i64);
199
200        assert_eq!(None, put_or_update_request.updated_weight(&weight_calculation_fn));
201    }
202
203    #[test]
204    fn updated_weight_if_only_time_to_live_is_provided() {
205        let put_or_update_request: PutOrUpdateRequest<&str, &str> = PutOrUpdateRequestBuilder::new("topic").time_to_live(Duration::from_secs(500)).build();
206        let weight_calculation_fn = Box::new(Calculation::perform);
207
208        assert_eq!(None, put_or_update_request.updated_weight(&weight_calculation_fn));
209    }
210
211    #[test]
212    fn updated_weight_if_value_is_provided_without_time_to_live() {
213        let key: u64 = 100;
214        let value: u64 = 1000;
215
216        let put_or_update_request = PutOrUpdateRequestBuilder::new(key).value(value).build();
217        let weight_calculation_fn = Box::new(Calculation::perform);
218
219        assert_eq!(Some(40), put_or_update_request.updated_weight(&weight_calculation_fn));
220    }
221
222    #[test]
223    fn updated_weight_if_value_is_provided_with_time_to_live() {
224        let key: u64 = 100;
225        let value: u64 = 1000;
226
227        let put_or_update_request = PutOrUpdateRequestBuilder::new(key).value(value).time_to_live(Duration::from_secs(100)).build();
228        let weight_calculation_fn = Box::new(Calculation::perform);
229
230        assert_eq!(Some(64), put_or_update_request.updated_weight(&weight_calculation_fn));
231    }
232}