uv_resolver/
exclude_newer.rs1#[cfg(feature = "schemars")]
2use std::borrow::Cow;
3use std::{
4 ops::{Deref, DerefMut},
5 str::FromStr,
6};
7
8use jiff::{Timestamp, ToSpan, tz::TimeZone};
9use rustc_hash::FxHashMap;
10use uv_normalize::PackageName;
11
12#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
14pub struct ExcludeNewerTimestamp(Timestamp);
15
16impl ExcludeNewerTimestamp {
17 pub fn timestamp_millis(&self) -> i64 {
19 self.0.as_millisecond()
20 }
21}
22
23impl From<Timestamp> for ExcludeNewerTimestamp {
24 fn from(timestamp: Timestamp) -> Self {
25 Self(timestamp)
26 }
27}
28
29impl FromStr for ExcludeNewerTimestamp {
30 type Err = String;
31
32 fn from_str(input: &str) -> Result<Self, Self::Err> {
37 if let Ok(timestamp) = input.parse::<Timestamp>() {
50 return Ok(Self(timestamp));
51 }
52 let date = input
53 .parse::<jiff::civil::Date>()
54 .map_err(|err| format!("`{input}` could not be parsed as a valid date: {err}"))?;
55 let timestamp = date
56 .checked_add(1.day())
57 .and_then(|date| date.to_zoned(TimeZone::system()))
58 .map(|zdt| zdt.timestamp())
59 .map_err(|err| {
60 format!(
61 "`{input}` parsed to date `{date}`, but could not \
62 be converted to a timestamp: {err}",
63 )
64 })?;
65 Ok(Self(timestamp))
66 }
67}
68
69impl std::fmt::Display for ExcludeNewerTimestamp {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 self.0.fmt(f)
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
77#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
78pub struct ExcludeNewerPackageEntry {
79 pub package: PackageName,
80 pub timestamp: ExcludeNewerTimestamp,
81}
82
83impl FromStr for ExcludeNewerPackageEntry {
84 type Err = String;
85
86 fn from_str(s: &str) -> Result<Self, Self::Err> {
88 let Some((package, date)) = s.split_once('=') else {
89 return Err(format!(
90 "Invalid `exclude-newer-package` value `{s}`: expected format `PACKAGE=DATE`"
91 ));
92 };
93
94 let package = PackageName::from_str(package).map_err(|err| {
95 format!("Invalid `exclude-newer-package` package name `{package}`: {err}")
96 })?;
97 let timestamp = ExcludeNewerTimestamp::from_str(date)
98 .map_err(|err| format!("Invalid `exclude-newer-package` timestamp `{date}`: {err}"))?;
99
100 Ok(Self { package, timestamp })
101 }
102}
103
104impl From<(PackageName, ExcludeNewerTimestamp)> for ExcludeNewerPackageEntry {
105 fn from((package, timestamp): (PackageName, ExcludeNewerTimestamp)) -> Self {
106 Self { package, timestamp }
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
111#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
112pub struct ExcludeNewerPackage(FxHashMap<PackageName, ExcludeNewerTimestamp>);
113
114impl Deref for ExcludeNewerPackage {
115 type Target = FxHashMap<PackageName, ExcludeNewerTimestamp>;
116
117 fn deref(&self) -> &Self::Target {
118 &self.0
119 }
120}
121
122impl DerefMut for ExcludeNewerPackage {
123 fn deref_mut(&mut self) -> &mut Self::Target {
124 &mut self.0
125 }
126}
127
128impl FromIterator<ExcludeNewerPackageEntry> for ExcludeNewerPackage {
129 fn from_iter<T: IntoIterator<Item = ExcludeNewerPackageEntry>>(iter: T) -> Self {
130 Self(
131 iter.into_iter()
132 .map(|entry| (entry.package, entry.timestamp))
133 .collect(),
134 )
135 }
136}
137
138impl IntoIterator for ExcludeNewerPackage {
139 type Item = (PackageName, ExcludeNewerTimestamp);
140 type IntoIter = std::collections::hash_map::IntoIter<PackageName, ExcludeNewerTimestamp>;
141
142 fn into_iter(self) -> Self::IntoIter {
143 self.0.into_iter()
144 }
145}
146
147impl<'a> IntoIterator for &'a ExcludeNewerPackage {
148 type Item = (&'a PackageName, &'a ExcludeNewerTimestamp);
149 type IntoIter = std::collections::hash_map::Iter<'a, PackageName, ExcludeNewerTimestamp>;
150
151 fn into_iter(self) -> Self::IntoIter {
152 self.0.iter()
153 }
154}
155
156impl ExcludeNewerPackage {
157 pub fn into_inner(self) -> FxHashMap<PackageName, ExcludeNewerTimestamp> {
159 self.0
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
165#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
166pub struct ExcludeNewer {
167 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub global: Option<ExcludeNewerTimestamp>,
170 #[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
172 pub package: ExcludeNewerPackage,
173}
174
175impl ExcludeNewer {
176 pub fn global(global: ExcludeNewerTimestamp) -> Self {
178 Self {
179 global: Some(global),
180 package: ExcludeNewerPackage::default(),
181 }
182 }
183
184 pub fn new(global: Option<ExcludeNewerTimestamp>, package: ExcludeNewerPackage) -> Self {
186 Self { global, package }
187 }
188
189 pub fn from_args(
191 global: Option<ExcludeNewerTimestamp>,
192 package: Vec<ExcludeNewerPackageEntry>,
193 ) -> Self {
194 let package: ExcludeNewerPackage = package.into_iter().collect();
195
196 Self { global, package }
197 }
198
199 pub fn exclude_newer_package(
201 &self,
202 package_name: &PackageName,
203 ) -> Option<ExcludeNewerTimestamp> {
204 self.package.get(package_name).copied().or(self.global)
205 }
206
207 pub fn is_empty(&self) -> bool {
209 self.global.is_none() && self.package.is_empty()
210 }
211}
212
213impl std::fmt::Display for ExcludeNewer {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 if let Some(global) = self.global {
216 write!(f, "global: {global}")?;
217 if !self.package.is_empty() {
218 write!(f, ", ")?;
219 }
220 }
221 let mut first = true;
222 for (name, timestamp) in &self.package {
223 if !first {
224 write!(f, ", ")?;
225 }
226 write!(f, "{name}: {timestamp}")?;
227 first = false;
228 }
229 Ok(())
230 }
231}
232
233#[cfg(feature = "schemars")]
234impl schemars::JsonSchema for ExcludeNewerTimestamp {
235 fn schema_name() -> Cow<'static, str> {
236 Cow::Borrowed("ExcludeNewerTimestamp")
237 }
238
239 fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
240 schemars::json_schema!({
241 "type": "string",
242 "pattern": r"^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2}))?$",
243 "description": "Exclude distributions uploaded after the given timestamp.\n\nAccepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same format (e.g., `2006-12-02`).",
244 })
245 }
246}