pub async fn cache_fallible_res<D, E, F, Ft>(name: &str, f: F, force_run: bool) -> Result<D, E>
where
    // By making this `Any`, we can downcast it to manage errors intuitively
    D: Serialize + for<'de> Deserialize<'de>,
    E: std::error::Error,
    F: Fn() -> Ft,
    Ft: Future<Output = Result<D, E>>,
{
    // Replace any slashes with dashes to keep a flat directory structure
    let name = name.replace('/', "-");
    // In production, we'll just run the function directly
    if cfg!(debug_assertions) {
        // Check if the cache file exists
        let filename = format!("dist/cache/{}.json", &name);
        match File::open(&filename).await {
            Ok(mut file) => {
                if force_run {
                    let res = f().await?;
                    // Now cache the result
                    let str_res = serde_json::to_string(&res).unwrap_or_else(|err| {
                        panic!(
                            "couldn't serialize result of entry '{}' for caching: {}",
                            &filename, err
                        )
                    });
                    let mut file = File::create(&filename).await.unwrap_or_else(|err| {
                        panic!(
                            "couldn't create cache file for entry '{}': {}",
                            &filename, err
                        )
                    });
                    file.write_all(str_res.as_bytes())
                        .await
                        .unwrap_or_else(|err| {
                            panic!(
                                "couldn't write cache to file for entry '{}': {}",
                                &filename, err
                            )
                        });

                    Ok(res)
                } else {
                    let mut contents = String::new();
                    file.read_to_string(&mut contents)
                        .await
                        .unwrap_or_else(|err| {
                            panic!(
                                "couldn't read cache from file for entry '{}': {}",
                                &filename, err
                            )
                        });
                    let res = match serde_json::from_str(&contents) {
                        Ok(cached_res) => cached_res,
                        // If the stuff in the cache can't be deserialized, we'll force a recreation
                        // (we don't recurse because that requires boxing the future)
                        Err(_) => {
                            let res = f().await?;
                            // Now cache the result
                            let str_res = serde_json::to_string(&res).unwrap_or_else(|err| {
                                panic!(
                                    "couldn't serialize result of entry '{}' for caching: {}",
                                    &filename, err
                                )
                            });
                            let mut file = File::create(&filename).await.unwrap_or_else(|err| {
                                panic!(
                                    "couldn't create cache file for entry '{}': {}",
                                    &filename, err
                                )
                            });
                            file.write_all(str_res.as_bytes())
                                .await
                                .unwrap_or_else(|err| {
                                    panic!(
                                        "couldn't write cache to file for entry '{}': {}",
                                        &filename, err
                                    )
                                });

                            res
                        }
                    };

                    Ok(res)
                }
            }
            Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
                // The file doesn't exist yet, create the parent cache directory
                create_dir_all("dist/cache")
                    .await
                    .unwrap_or_else(|err| panic!("couldn't create cache directory: {}", err));
                // We have no cache, so we'll have to run the function
                let res = f().await?;
                // Now cache the result
                let str_res = serde_json::to_string(&res).unwrap_or_else(|err| {
                    panic!(
                        "couldn't serialize result of entry '{}' for caching: {}",
                        &filename, err
                    )
                });
                let mut file = File::create(&filename).await.unwrap_or_else(|err| {
                    panic!(
                        "couldn't create cache file for entry '{}': {}",
                        &filename, err
                    )
                });
                file.write_all(str_res.as_bytes())
                    .await
                    .unwrap_or_else(|err| {
                        panic!(
                            "couldn't write cache to file for entry '{}': {}",
                            &filename, err
                        )
                    });

                Ok(res)
            }
            // Any other filesystem errors are unacceptable
            Err(err) => panic!(
                "filesystem error occurred while trying to read cache file for entry '{}': {}",
                &filename, err
            ),
        }
    } else {
        f().await
    }
}
Same as `cache_res`, but takes a function that returns a `Result`, allowing
you to use `?` and the like inside your logic.
packages/perseus/src/utils/cache_res.rs