1use std::{
2 ops::{Deref, DerefMut},
3 str::FromStr,
4};
5
6use jiff::Timestamp;
7use rustc_hash::FxHashMap;
8use serde::ser::SerializeMap;
9use uv_distribution_types::{ExcludeNewerOverride, ExcludeNewerSpan, ExcludeNewerValue};
10use uv_normalize::PackageName;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum ExcludeNewerValueChange {
14 SpanChanged(ExcludeNewerSpan, ExcludeNewerSpan),
16 SpanAdded(ExcludeNewerSpan),
18 SpanRemoved,
20 RelativeTimestampChanged(Timestamp, Timestamp, ExcludeNewerSpan),
22 AbsoluteTimestampChanged(Timestamp, Timestamp),
24}
25
26impl ExcludeNewerValueChange {
27 pub fn is_relative_timestamp_change(&self) -> bool {
28 matches!(self, Self::RelativeTimestampChanged(_, _, _))
29 }
30}
31
32impl std::fmt::Display for ExcludeNewerValueChange {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 Self::SpanChanged(old, new) => {
36 write!(f, "change of exclude newer span from `{old}` to `{new}`")
37 }
38 Self::SpanAdded(span) => {
39 write!(f, "addition of exclude newer span `{span}`")
40 }
41 Self::SpanRemoved => {
42 write!(f, "removal of exclude newer span")
43 }
44 Self::RelativeTimestampChanged(old, new, span) => {
45 write!(
46 f,
47 "change of calculated ({span}) exclude newer timestamp from `{old}` to `{new}`"
48 )
49 }
50 Self::AbsoluteTimestampChanged(old, new) => {
51 write!(
52 f,
53 "change of exclude newer timestamp from `{old}` to `{new}`"
54 )
55 }
56 }
57 }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub enum ExcludeNewerChange {
62 GlobalChanged(ExcludeNewerValueChange),
63 GlobalAdded(ExcludeNewerValue),
64 GlobalRemoved,
65 Package(ExcludeNewerPackageChange),
66}
67
68impl ExcludeNewerChange {
69 pub fn is_relative_timestamp_change(&self) -> bool {
71 match self {
72 Self::GlobalChanged(change) => change.is_relative_timestamp_change(),
73 Self::GlobalAdded(_) | Self::GlobalRemoved => false,
74 Self::Package(change) => change.is_relative_timestamp_change(),
75 }
76 }
77}
78
79impl std::fmt::Display for ExcludeNewerChange {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 match self {
82 Self::GlobalChanged(change) => {
83 write!(f, "{change}")
84 }
85 Self::GlobalAdded(value) => {
86 write!(f, "addition of global exclude newer {value}")
87 }
88 Self::GlobalRemoved => write!(f, "removal of global exclude newer"),
89 Self::Package(change) => {
90 write!(f, "{change}")
91 }
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub enum ExcludeNewerPackageChange {
98 PackageAdded(PackageName, ExcludeNewerOverride),
99 PackageRemoved(PackageName),
100 PackageChanged(PackageName, Box<ExcludeNewerOverrideChange>),
101}
102
103impl ExcludeNewerPackageChange {
104 pub fn is_relative_timestamp_change(&self) -> bool {
105 match self {
106 Self::PackageAdded(_, _) | Self::PackageRemoved(_) => false,
107 Self::PackageChanged(_, change) => change.is_relative_timestamp_change(),
108 }
109 }
110}
111
112impl std::fmt::Display for ExcludeNewerPackageChange {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 match self {
115 Self::PackageAdded(name, ExcludeNewerOverride::Enabled(value)) => {
116 write!(
117 f,
118 "addition of exclude newer `{}` for package `{name}`",
119 value.as_ref()
120 )
121 }
122 Self::PackageAdded(name, ExcludeNewerOverride::Disabled) => {
123 write!(
124 f,
125 "addition of exclude newer exclusion for package `{name}`"
126 )
127 }
128 Self::PackageRemoved(name) => {
129 write!(f, "removal of exclude newer for package `{name}`")
130 }
131 Self::PackageChanged(name, change) => write!(f, "{change} for package `{name}`"),
132 }
133 }
134}
135
136fn compare_exclude_newer_value(
137 this: &ExcludeNewerValue,
138 other: &ExcludeNewerValue,
139) -> Option<ExcludeNewerValueChange> {
140 match (this.span(), other.span()) {
141 (None, Some(span)) => Some(ExcludeNewerValueChange::SpanAdded(*span)),
142 (Some(_), None) => Some(ExcludeNewerValueChange::SpanRemoved),
143 (Some(self_span), Some(other_span)) if self_span != other_span => Some(
144 ExcludeNewerValueChange::SpanChanged(*self_span, *other_span),
145 ),
146 (Some(_), Some(span)) if this.timestamp() != other.timestamp() => {
147 Some(ExcludeNewerValueChange::RelativeTimestampChanged(
148 this.timestamp(),
149 other.timestamp(),
150 *span,
151 ))
152 }
153 (None, None) if this.timestamp() != other.timestamp() => Some(
154 ExcludeNewerValueChange::AbsoluteTimestampChanged(this.timestamp(), other.timestamp()),
155 ),
156 (Some(_), Some(_)) | (None, None) => None,
157 }
158}
159
160pub struct ExcludeNewerValueWithSpanRef<'a>(pub &'a ExcludeNewerValue);
161
162impl serde::Serialize for ExcludeNewerValueWithSpanRef<'_> {
163 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
164 where
165 S: serde::Serializer,
166 {
167 if let Some(span) = self.0.span() {
168 let mut map = serializer.serialize_map(Some(2))?;
169 map.serialize_entry("timestamp", &self.0.timestamp())?;
170 map.serialize_entry("span", span)?;
171 map.end()
172 } else {
173 self.0.timestamp().serialize(serializer)
174 }
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
180#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
181pub struct ExcludeNewerPackageEntry {
182 pub package: PackageName,
183 pub setting: ExcludeNewerOverride,
184}
185
186impl FromStr for ExcludeNewerPackageEntry {
187 type Err = String;
188
189 fn from_str(s: &str) -> Result<Self, Self::Err> {
191 let Some((package, value)) = s.split_once('=') else {
192 return Err(format!(
193 "Invalid `exclude-newer-package` value `{s}`: expected format `PACKAGE=DATE` or `PACKAGE=false`"
194 ));
195 };
196
197 let package = PackageName::from_str(package).map_err(|err| {
198 format!("Invalid `exclude-newer-package` package name `{package}`: {err}")
199 })?;
200
201 let setting = if value == "false" {
202 ExcludeNewerOverride::Disabled
203 } else {
204 ExcludeNewerOverride::Enabled(Box::new(ExcludeNewerValue::from_str(value).map_err(
205 |err| format!("Invalid `exclude-newer-package` value `{value}`: {err}"),
206 )?))
207 };
208
209 Ok(Self { package, setting })
210 }
211}
212
213impl From<(PackageName, ExcludeNewerOverride)> for ExcludeNewerPackageEntry {
214 fn from((package, setting): (PackageName, ExcludeNewerOverride)) -> Self {
215 Self { package, setting }
216 }
217}
218
219impl From<(PackageName, ExcludeNewerValue)> for ExcludeNewerPackageEntry {
220 fn from((package, timestamp): (PackageName, ExcludeNewerValue)) -> Self {
221 Self {
222 package,
223 setting: ExcludeNewerOverride::Enabled(Box::new(timestamp)),
224 }
225 }
226}
227
228pub fn serialize_exclude_newer_package_with_spans<S>(
229 value: &Option<ExcludeNewerPackage>,
230 serializer: S,
231) -> Result<S::Ok, S::Error>
232where
233 S: serde::Serializer,
234{
235 let Some(value) = value else {
236 return serializer.serialize_none();
237 };
238
239 let mut map = serializer.serialize_map(Some(value.len()))?;
240 for (name, setting) in value {
241 match setting {
242 ExcludeNewerOverride::Disabled => map.serialize_entry(name, &false)?,
243 ExcludeNewerOverride::Enabled(value) => {
244 map.serialize_entry(name, &ExcludeNewerValueWithSpanRef(value.as_ref()))?;
245 }
246 }
247 }
248 map.end()
249}
250
251#[derive(Debug, Clone, PartialEq, Eq)]
252pub enum ExcludeNewerOverrideChange {
253 Disabled { was: ExcludeNewerValue },
254 Enabled { now: ExcludeNewerValue },
255 TimestampChanged(ExcludeNewerValueChange),
256}
257
258impl ExcludeNewerOverrideChange {
259 pub fn is_relative_timestamp_change(&self) -> bool {
260 match self {
261 Self::Disabled { .. } | Self::Enabled { .. } => false,
262 Self::TimestampChanged(change) => change.is_relative_timestamp_change(),
263 }
264 }
265}
266
267impl std::fmt::Display for ExcludeNewerOverrideChange {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 match self {
270 Self::Disabled { was } => {
271 write!(f, "add exclude newer exclusion (was `{was}`)")
272 }
273 Self::Enabled { now } => {
274 write!(f, "remove exclude newer exclusion (now `{now}`)")
275 }
276 Self::TimestampChanged(change) => write!(f, "{change}"),
277 }
278 }
279}
280
281#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
282#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
283pub struct ExcludeNewerPackage(FxHashMap<PackageName, ExcludeNewerOverride>);
284
285impl Deref for ExcludeNewerPackage {
286 type Target = FxHashMap<PackageName, ExcludeNewerOverride>;
287
288 fn deref(&self) -> &Self::Target {
289 &self.0
290 }
291}
292
293impl DerefMut for ExcludeNewerPackage {
294 fn deref_mut(&mut self) -> &mut Self::Target {
295 &mut self.0
296 }
297}
298
299impl FromIterator<ExcludeNewerPackageEntry> for ExcludeNewerPackage {
300 fn from_iter<T: IntoIterator<Item = ExcludeNewerPackageEntry>>(iter: T) -> Self {
301 Self(
302 iter.into_iter()
303 .map(|entry| (entry.package, entry.setting))
304 .collect(),
305 )
306 }
307}
308
309impl IntoIterator for ExcludeNewerPackage {
310 type Item = (PackageName, ExcludeNewerOverride);
311 type IntoIter = std::collections::hash_map::IntoIter<PackageName, ExcludeNewerOverride>;
312
313 fn into_iter(self) -> Self::IntoIter {
314 self.0.into_iter()
315 }
316}
317
318impl<'a> IntoIterator for &'a ExcludeNewerPackage {
319 type Item = (&'a PackageName, &'a ExcludeNewerOverride);
320 type IntoIter = std::collections::hash_map::Iter<'a, PackageName, ExcludeNewerOverride>;
321
322 fn into_iter(self) -> Self::IntoIter {
323 self.0.iter()
324 }
325}
326
327impl ExcludeNewerPackage {
328 pub fn into_inner(self) -> FxHashMap<PackageName, ExcludeNewerOverride> {
330 self.0
331 }
332
333 pub fn is_empty(&self) -> bool {
335 self.0.is_empty()
336 }
337
338 #[must_use]
340 pub fn recompute(self) -> Self {
341 Self(
342 self.0
343 .into_iter()
344 .map(|(name, setting)| {
345 let setting = match setting {
346 ExcludeNewerOverride::Disabled => ExcludeNewerOverride::Disabled,
347 ExcludeNewerOverride::Enabled(value) => {
348 ExcludeNewerOverride::Enabled(Box::new((*value).recompute()))
349 }
350 };
351 (name, setting)
352 })
353 .collect(),
354 )
355 }
356
357 pub fn compare(&self, other: &Self) -> Option<ExcludeNewerPackageChange> {
358 for (package, setting) in self {
359 match (setting, other.get(package)) {
360 (
361 ExcludeNewerOverride::Enabled(self_timestamp),
362 Some(ExcludeNewerOverride::Enabled(other_timestamp)),
363 ) => {
364 if let Some(change) =
365 compare_exclude_newer_value(self_timestamp, other_timestamp)
366 {
367 return Some(ExcludeNewerPackageChange::PackageChanged(
368 package.clone(),
369 Box::new(ExcludeNewerOverrideChange::TimestampChanged(change)),
370 ));
371 }
372 }
373 (
374 ExcludeNewerOverride::Enabled(self_timestamp),
375 Some(ExcludeNewerOverride::Disabled),
376 ) => {
377 return Some(ExcludeNewerPackageChange::PackageChanged(
378 package.clone(),
379 Box::new(ExcludeNewerOverrideChange::Disabled {
380 was: self_timestamp.as_ref().clone(),
381 }),
382 ));
383 }
384 (
385 ExcludeNewerOverride::Disabled,
386 Some(ExcludeNewerOverride::Enabled(other_timestamp)),
387 ) => {
388 return Some(ExcludeNewerPackageChange::PackageChanged(
389 package.clone(),
390 Box::new(ExcludeNewerOverrideChange::Enabled {
391 now: other_timestamp.as_ref().clone(),
392 }),
393 ));
394 }
395 (ExcludeNewerOverride::Disabled, Some(ExcludeNewerOverride::Disabled)) => {}
396 (_, None) => {
397 return Some(ExcludeNewerPackageChange::PackageRemoved(package.clone()));
398 }
399 }
400 }
401
402 for (package, value) in other {
403 if !self.contains_key(package) {
404 return Some(ExcludeNewerPackageChange::PackageAdded(
405 package.clone(),
406 value.clone(),
407 ));
408 }
409 }
410
411 None
412 }
413}
414
415#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
417#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
418pub struct ExcludeNewer {
419 #[serde(default, skip_serializing_if = "Option::is_none")]
421 pub global: Option<ExcludeNewerValue>,
422 #[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
424 pub package: ExcludeNewerPackage,
425}
426
427impl ExcludeNewer {
428 pub fn global(global: ExcludeNewerValue) -> Self {
430 Self {
431 global: Some(global),
432 package: ExcludeNewerPackage::default(),
433 }
434 }
435
436 pub fn new(global: Option<ExcludeNewerValue>, package: ExcludeNewerPackage) -> Self {
438 Self { global, package }
439 }
440
441 pub fn from_args(
443 global: Option<ExcludeNewerValue>,
444 package: Vec<ExcludeNewerPackageEntry>,
445 ) -> Self {
446 let package: ExcludeNewerPackage = package.into_iter().collect();
447
448 Self { global, package }
449 }
450
451 pub fn exclude_newer_package(&self, package_name: &PackageName) -> Option<ExcludeNewerValue> {
456 match self.package.get(package_name) {
457 Some(ExcludeNewerOverride::Enabled(timestamp)) => Some(timestamp.as_ref().clone()),
458 Some(ExcludeNewerOverride::Disabled) => None,
459 None => self.global.clone(),
460 }
461 }
462
463 pub fn is_empty(&self) -> bool {
465 self.global.is_none() && self.package.is_empty()
466 }
467
468 #[must_use]
472 pub fn recompute(self) -> Self {
473 Self {
474 global: self.global.map(ExcludeNewerValue::recompute),
475 package: self.package.recompute(),
476 }
477 }
478
479 pub fn compare(&self, other: &Self) -> Option<ExcludeNewerChange> {
480 match (&self.global, &other.global) {
481 (Some(self_global), Some(other_global)) => {
482 if let Some(change) = compare_exclude_newer_value(self_global, other_global) {
483 return Some(ExcludeNewerChange::GlobalChanged(change));
484 }
485 }
486 (None, Some(global)) => {
487 return Some(ExcludeNewerChange::GlobalAdded(global.clone()));
488 }
489 (Some(_), None) => return Some(ExcludeNewerChange::GlobalRemoved),
490 (None, None) => (),
491 }
492 self.package
493 .compare(&other.package)
494 .map(ExcludeNewerChange::Package)
495 }
496}
497
498impl std::fmt::Display for ExcludeNewer {
499 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
500 if let Some(global) = &self.global {
501 write!(f, "global: {global}")?;
502 if !self.package.is_empty() {
503 write!(f, ", ")?;
504 }
505 }
506 let mut first = true;
507 for (name, setting) in &self.package {
508 if !first {
509 write!(f, ", ")?;
510 }
511 match setting {
512 ExcludeNewerOverride::Enabled(timestamp) => {
513 write!(f, "{name}: {}", timestamp.as_ref())?;
514 }
515 ExcludeNewerOverride::Disabled => {
516 write!(f, "{name}: disabled")?;
517 }
518 }
519 first = false;
520 }
521 Ok(())
522 }
523}