Skip to main content

asimov_patterns/options/
caching.rs

1// This is free and unencumbered software released into the public domain.
2
3//! Shared cache freshness options for source commands.
4
5#[cfg(feature = "clap")]
6use alloc::string::ToString;
7#[cfg(any(feature = "std", feature = "clap"))]
8use alloc::{format, string::String};
9use bon::Builder;
10use core::time::Duration;
11
12#[cfg(feature = "clap")]
13const HELP_MAX_AGE: &str = r#"Maximum acceptable cache age.
14
15Use a positive duration such as 1h, 7d, or "1m 30s".
16If omitted, the default is module-specific."#;
17
18/// Cache freshness requests shared by fetcher and lister commands.
19///
20/// Flatten this alongside [`crate::FetcherOptions`] or [`crate::ListerOptions`]
21/// in a host's Clap arguments. The maximum age defaults to `None`, leaving
22/// policy to the selected module. Clap accepts durations such as `1h`,
23/// `7d`, or `1m 30s`; it rejects a zero maximum age.
24/// Builders and direct field assignments do not validate durations.
25///
26/// These options describe requests; they do not implement caching. Hosts can
27/// forward explicit values using `max_age_option()` (available with `std` or
28/// `clap`). Execution deadlines are configured separately by
29/// [`crate::TimingOptions`].
30#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Builder)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[builder(derive(Debug))]
33#[cfg_attr(feature = "clap", derive(clap::Args))]
34#[cfg_attr(feature = "clap", command(about = None, long_about = None))]
35pub struct CachingOptions {
36    /// Maximum acceptable cache age (for example, 1h or 7d).
37    /// Default is module-specific.
38    #[cfg_attr(
39        feature = "clap",
40        clap(
41            long,
42            value_name = "DURATION",
43            value_parser = parse_max_age,
44            help = "Maximum acceptable cache age",
45            long_help = HELP_MAX_AGE
46        )
47    )]
48    #[cfg_attr(
49        feature = "serde",
50        serde(
51            default,
52            skip_serializing_if = "Option::is_none",
53            with = "crate::serialization::optional_duration"
54        )
55    )]
56    pub max_age: Option<Duration>,
57}
58
59#[cfg(any(feature = "std", feature = "clap"))]
60impl CachingOptions {
61    /// Formats an explicit maximum age as one `--max-age=DURATION` argument.
62    ///
63    /// Returns `None` when unset, preserving the module's default policy.
64    pub fn max_age_option(&self) -> Option<String> {
65        self.max_age
66            .map(|value| format!("--max-age={}", humantime::format_duration(value)))
67    }
68}
69
70#[cfg(feature = "clap")]
71fn parse_max_age(value: &str) -> Result<Duration, String> {
72    let duration = humantime::parse_duration(value).map_err(|error| error.to_string())?;
73    if duration.is_zero() {
74        return Err("max age must be positive".into());
75    }
76    Ok(duration)
77}