Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optionally_try_get_with in moka::future::Cache #254

Open
dcullen12 opened this issue Apr 12, 2023 · 1 comment
Open

optionally_try_get_with in moka::future::Cache #254

dcullen12 opened this issue Apr 12, 2023 · 1 comment
Labels
enhancement New feature or request
Milestone

Comments

@dcullen12
Copy link

I have a use case where the value being fetched through the cache may or may not exist. If it exists, the fetch may fail. Therefor I would like to be able to use some combination of optionally_get_with and try_get_with that would return Option<Result<V>>, and only caches type V if the future returns Some(Ok(V)).

@tatsuya6502 tatsuya6502 added the enhancement New feature or request label Apr 18, 2023
@tatsuya6502
Copy link
Member

Hi. Sorry for a late reply.

I reviewed the current code of moka, but unfortunately, it will take some time to implement optionally_try_get_with because the current code is not flexible enough to handle such a case.

For now, you could workaround by using try_get_with method with a custom error type like this:

use thiserror::Error;

/// An error type that can be converted from `Option<Result<V, std::io::Error>>`.
#[derive(Error, Debug)]
pub enum MyError {
    /// No such key. Used when the key does not exist (`None`).
    #[error("no such key")]
    NoSuchKey,
    /// I/O error. Used when the key exists but the value cannot be retrieved
    /// (`Some(Err(_))`).
    #[error(transparent)]
    Io(#[from] std::io::Error),
}

and then, define a function to convert Option<Result<V, std::io::Error>> into Result<V, MyError>, which can be passed to try_get_with method:

fn convert(v: Option<Result<V, std::io::Error>>) -> Result<V, MyError> {
    match v {
        Some(Ok(v)) => Ok(v),
        Some(Err(e)) => Err(MyError::Io(e)),
        None => Err(MyError::NoSuchKey),
    }
}

Here is a working example:

// Cargo.toml
//
// [dependencies]
// moka = { version = "0.10.2", features = ["future"] }
// thiserror = "1.0.40"
// tokio = { version = "1.27.0", features = ["rt-multi-thread", "macros"] }

use moka::future::Cache;
use thiserror::Error;

type K = i32;
type V = char;

/// Try to retrieve a value from somewhere for the given key, and returns one of
/// the following values:
///
/// - `None` if the key does not exist.
/// - `Some(Ok(value))` if the key exists and the value can be retrieved.
/// - `Some(Err(error))` if the key exists but the value cannot be retrieved.
async fn retrieve_value(key: &K) -> Option<Result<V, std::io::Error>> {
    match key {
        // For a demonstration purpose, return a hard-coded value for the given key.
        0 => Some(Ok('0')),
        1 => Some(Err(std::io::Error::new(
            std::io::ErrorKind::Other,
            "some io error",
        ))),
        2 => None,
        _ => unreachable!(),
    }
}

/// An error type that can be converted from `Option<Result<V, std::io::Error>>`.
#[derive(Error, Debug)]
pub enum MyError {
    /// No such key. Used when the key does not exist (`None`).
    #[error("no such key")]
    NoSuchKey,
    /// I/O error. Used when the key exists but the value cannot be retrieved
    /// (`Some(Err(_))`).
    #[error(transparent)]
    Io(#[from] std::io::Error),
}

/// Converts a value that returned from `retrieve_value()` into a value that can
/// be passed to `try_get_with()`.
fn convert(v: Option<Result<V, std::io::Error>>) -> Result<V, MyError> {
    match v {
        Some(Ok(v)) => Ok(v),
        Some(Err(e)) => Err(MyError::Io(e)),
        None => Err(MyError::NoSuchKey),
    }
}

#[tokio::main]
async fn main() {
    let cache = Cache::builder().build();

    // A case that retrieve_value() returns Some(Ok('0')).
    let key0 = 0;
    let result = cache
        .try_get_with(key0, async { convert(retrieve_value(&key0).await) })
        .await;
    // Expects `Ok('0')`
    assert!(matches!(result, Ok('0')));
    assert!(matches!(cache.get(&key0), Some('0')));

    // A case that retrieve_value() returns Some(Err(std::io::Error)).
    let key1 = 1;
    let result = cache
        .try_get_with(key1, async { convert(retrieve_value(&key1).await) })
        .await;
    // Expects `Err<Arc<MyError>>` when `MyError` is `Io(std::io::Error)`.
    assert!(matches!(result, Err(e) if matches!(*e, MyError::Io(_))));
    assert!(!cache.contains_key(&key1));

    // A case that retrieve_value() returns None.
    let key2 = 2;
    let result = cache
        .try_get_with(key2, async { convert(retrieve_value(&key2).await) })
        .await;
    // Expects `Err<Arc<MyError>>` when `MyError` is `NoSuchKey`.
    assert!(matches!(result, Err(e) if matches!(*e, MyError::NoSuchKey)));
    assert!(!cache.contains_key(&key2));
}

Hope this helps.

@tatsuya6502 tatsuya6502 added this to the Backlog milestone Apr 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants