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}