blaze_common/
cache.rs

1use std::{
2    collections::{BTreeMap, BTreeSet},
3    convert::Infallible,
4    num::NonZeroUsize,
5    path::{Path, PathBuf},
6    str::FromStr,
7};
8
9use serde::{de::Error, Deserialize, Deserializer, Serialize};
10use strum_macros::{Display, EnumIter};
11
12use crate::{
13    enums::{unit_enum_deserialize, unit_enum_from_str},
14    error::Result,
15    util::normalize_path,
16};
17
18#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct TargetCache {
21    #[serde(default)]
22    invalidate_when: InvalidationStrategy,
23}
24
25impl TargetCache {
26    pub fn invalidate_when(&self) -> &InvalidationStrategy {
27        &self.invalidate_when
28    }
29}
30
31#[derive(Debug, Default, Clone, Hash, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct InvalidationStrategy {
34    #[serde(skip_serializing_if = "Option::is_none")]
35    input_changes: Option<BTreeSet<FileChangesMatcher>>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    output_changes: Option<BTreeSet<FileChangesMatcher>>,
38    #[serde(
39        default,
40        skip_serializing_if = "Option::is_none",
41        deserialize_with = "deserialize_files_missing"
42    )]
43    files_missing: Option<BTreeSet<PathBuf>>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    expired: Option<TtlOptions>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    command_fails: Option<CommandFailsOptions>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    env_changes: Option<EnvChangesOptions>,
50}
51
52impl InvalidationStrategy {
53    pub fn output_changes(&self) -> Option<&BTreeSet<FileChangesMatcher>> {
54        self.output_changes.as_ref()
55    }
56
57    pub fn input_changes(&self) -> Option<&BTreeSet<FileChangesMatcher>> {
58        self.input_changes.as_ref()
59    }
60
61    pub fn files_missing(&self) -> Option<&BTreeSet<PathBuf>> {
62        self.files_missing.as_ref()
63    }
64
65    pub fn expired(&self) -> Option<&TtlOptions> {
66        self.expired.as_ref()
67    }
68
69    pub fn command_fails(&self) -> Option<&CommandFailsOptions> {
70        self.command_fails.as_ref()
71    }
72
73    pub fn env_changes(&self) -> Option<&EnvChangesOptions> {
74        self.env_changes.as_ref()
75    }
76}
77
78fn deserialize_files_missing<'de, D>(
79    deserializer: D,
80) -> std::result::Result<Option<BTreeSet<PathBuf>>, D::Error>
81where
82    D: Deserializer<'de>,
83{
84    Ok(Some(
85        BTreeSet::<PathBuf>::deserialize(deserializer)?
86            .into_iter()
87            .map(normalize_path)
88            .collect::<Result<_>>()
89            .map_err(D::Error::custom)?,
90    ))
91}
92
93#[derive(
94    Default, Clone, Copy, Debug, Hash, EnumIter, PartialEq, Eq, PartialOrd, Ord, Display, Serialize,
95)]
96pub enum MatchingBehavior {
97    Timestamps,
98    #[default]
99    Mixed,
100    Hash,
101}
102
103unit_enum_from_str!(MatchingBehavior);
104unit_enum_deserialize!(MatchingBehavior);
105
106#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, PartialOrd, Ord)]
107pub struct FileChangesMatcher {
108    pattern: String,
109    exclude: BTreeSet<String>,
110    #[serde(skip_serializing_if = "Option::is_none", default)]
111    root: Option<PathBuf>,
112    behavior: MatchingBehavior,
113}
114
115impl FileChangesMatcher {
116    pub fn new(pattern: &str) -> Self {
117        Self {
118            pattern: pattern.to_owned(),
119            exclude: BTreeSet::new(),
120            root: None,
121            behavior: MatchingBehavior::default(),
122        }
123    }
124
125    pub fn with_exclude<I: IntoIterator<Item = S>, S: AsRef<str>>(mut self, patterns: I) -> Self {
126        self.exclude = patterns
127            .into_iter()
128            .map(|s| s.as_ref().to_owned())
129            .collect();
130        self
131    }
132
133    pub fn with_root(mut self, root: &Path) -> Self {
134        self.root = Some(root.to_owned());
135        self
136    }
137
138    pub fn with_behavior(mut self, behavior: MatchingBehavior) -> Self {
139        self.behavior = behavior;
140        self
141    }
142
143    pub fn pattern(&self) -> &str {
144        &self.pattern
145    }
146
147    pub fn exclude(&self) -> &BTreeSet<String> {
148        &self.exclude
149    }
150
151    pub fn root(&self) -> Option<&Path> {
152        self.root.as_deref()
153    }
154
155    pub fn behavior(&self) -> MatchingBehavior {
156        self.behavior
157    }
158}
159
160impl FromStr for FileChangesMatcher {
161    type Err = Infallible;
162
163    fn from_str(s: &str) -> std::result::Result<Self, Infallible> {
164        Ok(Self {
165            pattern: s.to_string(),
166            behavior: MatchingBehavior::default(),
167            exclude: BTreeSet::new(),
168            root: None,
169        })
170    }
171}
172
173impl<'de> Deserialize<'de> for FileChangesMatcher {
174    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
175    where
176        D: Deserializer<'de>,
177    {
178        #[derive(Deserialize)]
179        #[serde(remote = "FileChangesMatcher")]
180        struct FileChangesMatcherObject {
181            pattern: String,
182            #[serde(default)]
183            exclude: BTreeSet<String>,
184            root: Option<PathBuf>,
185            #[serde(default)]
186            behavior: MatchingBehavior,
187        }
188
189        #[derive(Deserialize)]
190        #[serde(untagged)]
191        enum FileChangesMatcherDeserializationMode {
192            SinglePattern(String),
193            #[serde(with = "FileChangesMatcherObject")]
194            Full(FileChangesMatcher),
195        }
196
197        Ok(
198            match FileChangesMatcherDeserializationMode::deserialize(deserializer)? {
199                FileChangesMatcherDeserializationMode::SinglePattern(pattern) => {
200                    FileChangesMatcher::from_str(pattern.as_str()).unwrap()
201                }
202                FileChangesMatcherDeserializationMode::Full(mut matcher) => {
203                    if let Some(root) = &mut matcher.root {
204                        *root = normalize_path(&*root).map_err(D::Error::custom)?;
205                    }
206                    matcher
207                }
208            },
209        )
210    }
211}
212
213#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
214pub struct CommandFailsOptions {
215    program: String,
216    #[serde(default)]
217    arguments: Vec<String>,
218    #[serde(default)]
219    environment: BTreeMap<String, String>,
220    #[serde(default)]
221    verbose: bool,
222    #[serde(deserialize_with = "deserialize_cwd", default)]
223    cwd: Option<PathBuf>,
224}
225
226impl CommandFailsOptions {
227    pub fn program(&self) -> &str {
228        &self.program
229    }
230
231    pub fn arguments(&self) -> &[String] {
232        &self.arguments
233    }
234
235    pub fn environment(&self) -> &BTreeMap<String, String> {
236        &self.environment
237    }
238
239    pub fn verbose(&self) -> bool {
240        self.verbose
241    }
242
243    pub fn cwd(&self) -> Option<&Path> {
244        self.cwd.as_deref()
245    }
246}
247
248fn deserialize_cwd<'de, D>(deserializer: D) -> std::result::Result<Option<PathBuf>, D::Error>
249where
250    D: Deserializer<'de>,
251{
252    Option::<PathBuf>::deserialize(deserializer)?
253        .map(normalize_path)
254        .transpose()
255        .map_err(D::Error::custom)
256}
257
258#[derive(Debug, Clone, Copy, Hash, Display, Serialize, EnumIter)]
259pub enum TimeUnit {
260    Milliseconds,
261    Seconds,
262    Minutes,
263    Hours,
264    Days,
265}
266
267unit_enum_from_str!(TimeUnit);
268unit_enum_deserialize!(TimeUnit);
269
270#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
271pub struct TtlOptions {
272    unit: TimeUnit,
273    amount: NonZeroUsize,
274}
275
276impl TtlOptions {
277    pub fn unit(&self) -> TimeUnit {
278        self.unit
279    }
280
281    pub fn amount(&self) -> usize {
282        self.amount.get()
283    }
284}
285
286#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
287pub struct EnvChangesOptions {
288    variables: BTreeSet<String>,
289}
290
291impl EnvChangesOptions {
292    pub fn variables(&self) -> &BTreeSet<String> {
293        &self.variables
294    }
295}