Skip to main content

momento_functions_host/
cache.rs

1//! Host interfaces for working with Momento Cache apis
2
3use std::time::Duration;
4
5use crate::encoding::{Encode, EncodeError, Extract, ExtractError};
6use momento_functions_wit::host::momento::functions::cache_list;
7use momento_functions_wit::host::momento::functions::cache_scalar;
8
9pub use cache_scalar::GetWithHashFound;
10pub use cache_scalar::GetWithHashResult;
11pub use cache_scalar::SetIfCondition;
12pub use cache_scalar::SetIfHashCondition;
13pub use cache_scalar::SetIfHashResult;
14pub use cache_scalar::SetIfResult;
15use momento_functions_wit::host::momento::functions::cache_list::FetchResponse;
16
17/// An error occurred when setting a value in the cache.
18#[derive(thiserror::Error, Debug)]
19pub enum CacheSetError<E: EncodeError> {
20    /// The provided value could not be encoded.
21    #[error("Failed to encode value.")]
22    EncodeFailed {
23        /// The underlying encoding error.
24        cause: E,
25    },
26    /// An error occurred when calling the host cache function.
27    #[error(transparent)]
28    CacheError(#[from] cache_scalar::Error),
29}
30
31/// An error occurred when getting a value from the cache.
32#[derive(thiserror::Error, Debug)]
33pub enum CacheGetError<E: ExtractError> {
34    /// The value could not be extracted with the provided implementation.
35    #[error("Failed to extract value.")]
36    ExtractFailed {
37        /// The underlying error.
38        cause: E,
39    },
40    /// An error occurred when calling the host cache function.
41    #[error(transparent)]
42    CacheError(#[from] cache_scalar::Error),
43}
44/// Get a value from the cache.
45///
46/// Examples:
47/// ________
48/// Bytes:
49/// ```rust,no_run
50/// # use momento_functions_host::cache;
51/// match cache::get::<Vec<u8>>("my_key") {
52///     Ok(Some(value)) => { /* use value */ }
53///     Ok(None) => { /* key not found */ }
54///     Err(e) => eprintln!("cache get failed: {e}"),
55/// }
56/// ```
57/// ________
58/// Json:
59/// ```rust,no_run
60/// # use momento_functions_host::cache;
61/// use momento_functions_host::encoding::Json;
62///
63/// #[derive(serde::Deserialize)]
64/// struct MyStruct {
65///   message: String
66/// }
67///
68/// match cache::get::<Json<MyStruct>>("my_key") {
69///     Ok(Some(Json(value))) => { /* use value */ }
70///     Ok(None) => { /* key not found */ }
71///     Err(e) => eprintln!("cache get failed: {e}"),
72/// }
73/// ```
74pub fn get<T: Extract>(key: impl AsRef<[u8]>) -> Result<Option<T>, CacheGetError<T::Error>> {
75    match cache_scalar::get(key.as_ref())? {
76        Some(v) => T::extract(v)
77            .map(Some)
78            .map_err(|e| CacheGetError::ExtractFailed { cause: e }),
79        None => Ok(None),
80    }
81}
82
83/// Set a value in the cache with a time-to-live.
84///
85/// Examples:
86/// ________
87/// Bytes:
88/// ```rust,no_run
89/// # use momento_functions_host::cache;
90/// # use std::time::Duration;
91/// match cache::set(
92///     "my_key",
93///     b"hello".to_vec(),
94///     Duration::from_secs(60),
95/// ) {
96///     Ok(()) => {}
97///     Err(e) => eprintln!("cache set failed: {e}"),
98/// }
99/// ```
100/// ________
101/// Json:
102/// ```rust,no_run
103/// # use momento_functions_host::cache;
104/// # use std::time::Duration;
105/// use momento_functions_host::encoding::Json;
106///
107/// #[derive(serde::Serialize)]
108/// struct MyStruct {
109///    hello: String
110/// }
111///
112/// match cache::set(
113///     "my_key",
114///     Json(MyStruct { hello: "hello".to_string() }),
115///     Duration::from_secs(60),
116/// ) {
117///     Ok(()) => {}
118///     Err(e) => eprintln!("cache set failed: {e}"),
119/// }
120/// ```
121pub fn set<E: Encode>(
122    key: impl AsRef<[u8]>,
123    value: E,
124    ttl: Duration,
125) -> Result<(), CacheSetError<E::Error>> {
126    cache_scalar::set(
127        key.as_ref(),
128        &value
129            .try_serialize()
130            .map_err(|e| CacheSetError::EncodeFailed { cause: e })?
131            .into(),
132        saturate_ttl(ttl),
133    )
134    .map_err(Into::into)
135}
136
137fn saturate_ttl(ttl: Duration) -> u64 {
138    ttl.as_millis().clamp(0, u64::MAX as u128) as u64
139}
140
141/// An error occurred when conditionally setting a value in the cache.
142#[derive(thiserror::Error, Debug)]
143pub enum CacheSetIfError<E: EncodeError> {
144    /// The provided value could not be encoded.
145    #[error("Failed to encode value.")]
146    EncodeFailed {
147        /// The underlying encoding error.
148        cause: E,
149    },
150    /// An error occurred when calling the host cache function.
151    #[error(transparent)]
152    CacheError(#[from] cache_scalar::Error),
153}
154
155/// An error occurred when deleting a value from the cache.
156#[derive(thiserror::Error, Debug)]
157pub enum CacheDeleteError {
158    /// An error occurred when calling the host cache function.
159    #[error(transparent)]
160    CacheError(#[from] cache_scalar::Error),
161}
162
163/// Conditionally set a value in the cache based on a condition.
164///
165/// Examples:
166/// ________
167/// Set only if absent:
168/// ```rust,no_run
169/// # use momento_functions_host::cache;
170/// # use momento_functions_host::cache::{SetIfCondition, SetIfResult};
171/// # use std::time::Duration;
172/// match cache::set_if(
173///     "my_key",
174///     b"hello".to_vec(),
175///     Duration::from_secs(60),
176///     SetIfCondition::Absent,
177/// ) {
178///     Ok(SetIfResult::Stored) => {
179///         // Do something
180///     }
181///     Ok(SetIfResult::NotStored) => {
182///         // Do something else
183///     }
184///     Err(e) => eprintln!("set_if failed: {e}"),
185/// }
186/// ```
187/// ________
188/// Set only if present:
189/// ```rust,no_run
190/// # use momento_functions_host::cache;
191/// # use momento_functions_host::cache::SetIfCondition;
192/// # use std::time::Duration;
193/// match cache::set_if(
194///     "my_key",
195///     b"updated".to_vec(),
196///     Duration::from_secs(60),
197///     SetIfCondition::Present,
198/// ) {
199///     Ok(result) => { /* inspect result */ }
200///     Err(e) => eprintln!("set_if failed: {e}"),
201/// }
202/// ```
203/// ________
204/// Set only if equal to a specific value:
205/// ```rust,no_run
206/// # use momento_functions_host::cache;
207/// # use momento_functions_host::cache::SetIfCondition;
208/// # use std::time::Duration;
209/// match cache::set_if(
210///     "my_key",
211///     b"new_value".to_vec(),
212///     Duration::from_secs(60),
213///     SetIfCondition::Equal(b"old_value".to_vec()),
214/// ) {
215///     Ok(result) => { /* inspect result */ }
216///     Err(e) => eprintln!("set_if failed: {e}"),
217/// }
218/// ```
219pub fn set_if<E: Encode>(
220    key: impl AsRef<[u8]>,
221    value: E,
222    ttl: Duration,
223    condition: SetIfCondition,
224) -> Result<SetIfResult, CacheSetIfError<E::Error>> {
225    cache_scalar::set_if(
226        key.as_ref(),
227        &value
228            .try_serialize()
229            .map_err(|e| CacheSetIfError::EncodeFailed { cause: e })?
230            .into(),
231        saturate_ttl(ttl),
232        &condition,
233    )
234    .map_err(Into::into)
235}
236
237/// Delete a value from the cache.
238///
239/// Note: This operation is idempotent. Deleting a key that does not exist
240/// will not return an error.
241///
242/// Examples:
243/// ________
244/// ```rust,no_run
245/// # use momento_functions_host::cache;
246/// match cache::delete("my_key") {
247///     Ok(()) => {}
248///     Err(e) => eprintln!("cache delete failed: {e}"),
249/// }
250/// ```
251pub fn delete(key: impl AsRef<[u8]>) -> Result<(), CacheDeleteError> {
252    cache_scalar::delete(key.as_ref()).map_err(Into::into)
253}
254
255/// An error occurred when getting a value with its hash from the cache.
256#[derive(thiserror::Error, Debug)]
257pub enum CacheGetWithHashError<E: ExtractError> {
258    /// The value could not be extracted with the provided implementation.
259    #[error("Failed to extract value.")]
260    ExtractFailed {
261        /// The underlying error.
262        cause: E,
263    },
264    /// An error occurred when calling the host cache function.
265    #[error(transparent)]
266    CacheError(#[from] cache_scalar::Error),
267}
268
269/// An error occurred when conditionally setting a value based on hash comparison.
270#[derive(thiserror::Error, Debug)]
271pub enum CacheSetIfHashError<E: EncodeError> {
272    /// The provided value could not be encoded.
273    #[error("Failed to encode value.")]
274    EncodeFailed {
275        /// The underlying encoding error.
276        cause: E,
277    },
278    /// An error occurred when calling the host cache function.
279    #[error(transparent)]
280    CacheError(#[from] cache_scalar::Error),
281}
282
283/// A value retrieved from the cache along with its hash.
284pub struct GetWithHashValue<T> {
285    /// The extracted value.
286    pub value: T,
287    /// The hash of the value.
288    pub hash: Vec<u8>,
289}
290
291/// Get a value from the cache along with its hash.
292///
293/// The hash can be used with [`set_if_hash`] to perform conditional updates
294/// based on whether the value has changed since it was read.
295///
296/// Examples:
297/// ________
298/// ```rust,no_run
299/// # use momento_functions_host::cache;
300/// match cache::get_with_hash::<Vec<u8>>("my_key") {
301///     Ok(Some(entry)) => {
302///         log::info!("Value: {:?}, Hash: {:?}", entry.value, entry.hash);
303///     }
304///     Ok(None) => { /* key not found */ }
305///     Err(e) => eprintln!("get_with_hash failed: {e}"),
306/// }
307/// ```
308pub fn get_with_hash<T: Extract>(
309    key: impl AsRef<[u8]>,
310) -> Result<Option<GetWithHashValue<T>>, CacheGetWithHashError<T::Error>> {
311    match cache_scalar::get_with_hash(key.as_ref())? {
312        GetWithHashResult::Found(found) => {
313            let value = T::extract(found.value)
314                .map_err(|e| CacheGetWithHashError::ExtractFailed { cause: e })?;
315            Ok(Some(GetWithHashValue {
316                value,
317                hash: found.hash,
318            }))
319        }
320        GetWithHashResult::Missing => Ok(None),
321    }
322}
323
324/// Conditionally set a value in the cache based on a hash comparison.
325///
326/// This is useful for optimistic concurrency control where you want to update
327/// a value only if it hasn't changed since you last read it, without needing
328/// to compare the full value.
329///
330/// Examples:
331/// ________
332/// Update only if the hash matches (value hasn't changed):
333/// ```rust,no_run
334/// # use momento_functions_host::cache;
335/// # use momento_functions_host::cache::{SetIfHashCondition, SetIfHashResult};
336/// # use std::time::Duration;
337/// // First, get the current value and its hash
338/// // let entry = cache::get_with_hash("my_key")?;
339/// let previous_hash = vec![1, 2, 3]; // Hash from a previous get_with_hash call
340///
341/// match cache::set_if_hash(
342///     "my_key",
343///     b"new_value".to_vec(),
344///     Duration::from_secs(60),
345///     SetIfHashCondition::PresentAndHashEqual(previous_hash),
346/// ) {
347///     Ok(SetIfHashResult::Stored(new_hash)) => {
348///         log::info!("Value updated, new hash: {:?}", new_hash);
349///     }
350///     Ok(SetIfHashResult::NotStored) => {
351///         log::info!("Value was modified by another process");
352///     }
353///     Err(e) => eprintln!("set_if_hash failed: {e}"),
354/// }
355/// ```
356pub fn set_if_hash<E: Encode>(
357    key: impl AsRef<[u8]>,
358    value: E,
359    ttl: Duration,
360    condition: SetIfHashCondition,
361) -> Result<SetIfHashResult, CacheSetIfHashError<E::Error>> {
362    cache_scalar::set_if_hash(
363        key.as_ref(),
364        &value
365            .try_serialize()
366            .map_err(|e| CacheSetIfHashError::EncodeFailed { cause: e })?
367            .into(),
368        saturate_ttl(ttl),
369        &condition,
370    )
371    .map_err(Into::into)
372}
373
374/// Represents the desired behavior for managing the TTL on collections.
375///
376/// The first time the collection is created, it needs to set a TTL. For subsequent operations
377/// that modify the collection, you may choose to update the TTL in order to prolong the life
378/// of the cached collection, or to leave the TTL unmodified to ensure the collection expires
379/// at the original TTL.
380#[derive(Copy, Clone, Debug, PartialEq, Eq)]
381pub struct CollectionTtl {
382    ttl: Duration,
383    refresh: bool,
384}
385
386impl CollectionTtl {
387    /// Create a collection TTL with the provided `ttl` and `refresh` settings.
388    pub const fn new(ttl: Duration, refresh: bool) -> Self {
389        Self { ttl, refresh }
390    }
391
392    /// Create a collection TTL that updates the TTL for the collection any time it is
393    /// modified.
394    pub fn refresh_on_update(ttl: impl Into<Duration>) -> Self {
395        Self::new(ttl.into(), true)
396    }
397
398    /// Create a collection TTL that will not refresh the TTL for the collection when
399    /// it is updated.
400    ///
401    /// Use this if you want to be sure that the collection expires at the originally
402    /// specified time, even if you make modifications to the value of the collection.
403    ///
404    /// The TTL will still be used when a new collection is created.
405    pub fn initialize_only(ttl: impl Into<Duration>) -> Self {
406        Self::new(ttl.into(), false)
407    }
408
409    /// Return a new collection TTL which uses the same TTL but refreshes on updates.
410    pub fn with_refresh_on_update(self) -> Self {
411        Self::new(self.ttl(), true)
412    }
413
414    /// Return a new collection TTL which uses the same TTL but does not refresh on
415    /// updates.
416    pub fn with_no_refresh_on_update(self) -> Self {
417        Self::new(self.ttl(), false)
418    }
419
420    /// Return a new collection TTL which has the same refresh behavior but uses the
421    /// provided TTL.
422    pub fn with_ttl(self, ttl: impl Into<Duration>) -> Self {
423        Self::new(ttl.into(), self.refresh())
424    }
425
426    /// Constructs a CollectionTtl with the specified TTL. The TTL for the collection will be
427    /// refreshed any time the collection is modified.
428    pub fn of(ttl: Duration) -> Self {
429        Self::new(ttl, true)
430    }
431
432    /// The [`Duration`] after which the cached collection should be expired from the
433    /// cache.
434    pub fn ttl(&self) -> Duration {
435        self.ttl
436    }
437
438    /// Whether the collection's TTL will be refreshed on every update.
439    ///
440    /// If true, this will extend the time at which the collection would expire when
441    /// an update operation happens. Otherwise, the collection's TTL will only be set
442    /// when it is initially created.
443    pub fn refresh(&self) -> bool {
444        self.refresh
445    }
446}
447
448/// An error occurred when pushing a value to the back of a list in the cache.
449#[derive(thiserror::Error, Debug)]
450pub enum CacheListPushBackError<E: EncodeError> {
451    /// The provided value could not be encoded.
452    #[error("Failed to encode value.")]
453    EncodeFailed {
454        /// The underlying encoding error.
455        cause: E,
456    },
457    /// An error occurred when calling the host cache function.
458    #[error(transparent)]
459    CacheError(#[from] cache_list::Error),
460}
461
462/// An error occurred when pushing a value to the front of a list in the cache.
463#[derive(thiserror::Error, Debug)]
464pub enum CacheListPushFrontError<E: EncodeError> {
465    /// The provided value could not be encoded.
466    #[error("Failed to encode value.")]
467    EncodeFailed {
468        /// The underlying encoding error.
469        cause: E,
470    },
471    /// An error occurred when calling the host cache function.
472    #[error(transparent)]
473    CacheError(#[from] cache_list::Error),
474}
475
476/// Adds an element to the back of the given list. Creates the list if it does not already exist.
477///
478/// # Arguments
479/// * `list_name` - The name of the list.
480/// * `value` - The value to append to the list.
481/// * `collection_ttl` - The time-to-live for the list.
482/// * `truncate_front_to_size` - If the list exceeds this length, remove excess from the front of the list.
483///
484/// # Examples:
485/// ________
486/// Append a value to the back of a list:
487/// ```rust,no_run
488/// # use momento_functions_host::cache;
489/// # use momento_functions_host::cache::CollectionTtl;
490/// # use std::time::Duration;
491/// match cache::list_push_back(
492///     "my_list",
493///     b"new_value".to_vec(),
494///     CollectionTtl::of(Duration::from_secs(60)),
495///     None,
496/// ) {
497///     Ok(list_length) => log::info!("New length of my_list: {}", list_length),
498///     Err(e) => eprintln!("list_push_back failed: {e}"),
499/// }
500/// ```
501pub fn list_push_back<E: Encode>(
502    list_name: impl AsRef<[u8]>,
503    value: E,
504    collection_ttl: CollectionTtl,
505    truncate_front_to_size: Option<u32>,
506) -> Result<u32, CacheListPushBackError<E::Error>> {
507    cache_list::list_push_back(
508        list_name.as_ref(),
509        &value
510            .try_serialize()
511            .map_err(|e| CacheListPushBackError::EncodeFailed { cause: e })?
512            .into(),
513        saturate_ttl(collection_ttl.ttl()),
514        collection_ttl.refresh(),
515        truncate_front_to_size.unwrap_or(0),
516    )
517    .map_err(Into::into)
518}
519
520/// Adds an element to the front of the given list. Creates the list if it does not already exist.
521///
522/// # Arguments
523/// * `list_name` - The name of the list.
524/// * `value` - The value to append to the list.
525/// * `collection_ttl` - The time-to-live for the list.
526/// * `truncate_back_to_size` - If the list exceeds this length, remove excess from the back of the list.
527///
528/// # Examples:
529/// ________
530/// Append a value to the back of a list:
531/// ```rust,no_run
532/// # use momento_functions_host::cache;
533/// # use momento_functions_host::cache::CollectionTtl;
534/// # use std::time::Duration;
535/// match cache::list_push_front(
536///     "my_list",
537///     b"new_value".to_vec(),
538///     CollectionTtl::of(Duration::from_secs(60)),
539///     None,
540/// ) {
541///     Ok(list_length) => log::info!("New length of my_list: {}", list_length),
542///     Err(e) => eprintln!("list_push_front failed: {e}"),
543/// }
544/// ```
545pub fn list_push_front<E: Encode>(
546    list_name: impl AsRef<[u8]>,
547    value: E,
548    collection_ttl: CollectionTtl,
549    truncate_back_to_size: Option<u32>,
550) -> Result<u32, CacheListPushFrontError<E::Error>> {
551    cache_list::list_push_front(
552        list_name.as_ref(),
553        &value
554            .try_serialize()
555            .map_err(|e| CacheListPushFrontError::EncodeFailed { cause: e })?
556            .into(),
557        saturate_ttl(collection_ttl.ttl()),
558        collection_ttl.refresh(),
559        truncate_back_to_size.unwrap_or(0),
560    )
561    .map_err(Into::into)
562}
563
564/// The start bound for a list fetch or retain operation.
565pub enum StartIndex {
566    /// Start from the beginning of the list.
567    Unbounded,
568    /// Start from the given index (inclusive). Negative values count from the end.
569    Inclusive(i32),
570}
571
572impl From<StartIndex> for cache_list::StartIndex {
573    fn from(value: StartIndex) -> Self {
574        match value {
575            StartIndex::Unbounded => Self::Unbounded,
576            StartIndex::Inclusive(i) => Self::Inclusive(i),
577        }
578    }
579}
580
581/// The end bound for a list fetch or retain operation.
582pub enum EndIndex {
583    /// Extend to the end of the list.
584    Unbounded,
585    /// End at the given index (exclusive). Negative values count from the end.
586    Exclusive(i32),
587}
588
589impl From<EndIndex> for cache_list::EndIndex {
590    fn from(value: EndIndex) -> Self {
591        match value {
592            EndIndex::Unbounded => Self::Unbounded,
593            EndIndex::Exclusive(i) => Self::Exclusive(i),
594        }
595    }
596}
597
598/// An error occurred when fetching a list from the cache.
599#[derive(thiserror::Error, Debug)]
600pub enum CacheListFetchError<E: ExtractError> {
601    /// The value could not be extracted with the provided implementation.
602    #[error("Failed to extract value.")]
603    ExtractFailed {
604        /// The underlying error.
605        cause: E,
606    },
607    /// An error occurred when calling the host cache function.
608    #[error(transparent)]
609    CacheError(#[from] cache_list::Error),
610}
611
612/// Gets a list from the cache with optional slices.
613///
614/// # Arguments
615/// * `list_name` - The name of the list.
616/// * `start_index` - The starting inclusive element of the list to fetch. Default is 0.
617/// * `end_index` - The ending exclusive element of the list to fetch. Default is up to and including end of list.
618///
619/// # Examples
620/// ________
621/// Bytes:
622/// ```rust,no_run
623/// # use momento_functions_host::cache;
624/// # use momento_functions_host::cache::{StartIndex, EndIndex};
625/// match cache::list_fetch::<Vec<u8>>("my_list", StartIndex::Unbounded, EndIndex::Unbounded) {
626///     Ok(Some(iter)) => {
627///         match iter.collect::<Result<Vec<Vec<u8>>, _>>() {
628///             Ok(values) => { /* use values */ }
629///             Err(e) => eprintln!("extract failed: {e}"),
630///         }
631///     }
632///     Ok(None) => { /* list not found */ }
633///     Err(e) => eprintln!("list_fetch failed: {e}"),
634/// }
635/// ```
636/// ________
637/// Json:
638/// ```rust,no_run
639/// # use momento_functions_host::cache;
640/// # use momento_functions_host::cache::{StartIndex, EndIndex};
641/// use momento_functions_host::encoding::Json;
642///
643/// #[derive(serde::Deserialize)]
644/// struct MyStruct {
645///     message: String,
646/// }
647///
648/// match cache::list_fetch::<Json<MyStruct>>("my_list", StartIndex::Unbounded, EndIndex::Unbounded) {
649///     Ok(Some(iter)) => {
650///         match iter.collect::<Result<Vec<Json<MyStruct>>, _>>() {
651///             Ok(values) => { /* use values */ }
652///             Err(e) => eprintln!("extract failed: {e}"),
653///         }
654///     }
655///     Ok(None) => { /* list not found */ }
656///     Err(e) => eprintln!("list_fetch failed: {e}"),
657/// }
658/// ```
659#[allow(clippy::type_complexity)]
660pub fn list_fetch<T: Extract>(
661    list_name: impl AsRef<[u8]>,
662    start_index: StartIndex,
663    end_index: EndIndex,
664) -> Result<
665    Option<impl Iterator<Item = Result<T, CacheListFetchError<T::Error>>>>,
666    CacheListFetchError<T::Error>,
667> {
668    Ok(
669        match cache_list::list_fetch(list_name.as_ref(), start_index.into(), end_index.into())? {
670            FetchResponse::Found(items) => Some(items.into_iter().map(|item| {
671                T::extract(item).map_err(|e| CacheListFetchError::ExtractFailed { cause: e })
672            })),
673            FetchResponse::Missing => None,
674        },
675    )
676}