1use std::borrow::Cow;
2use std::fmt::{Display, Formatter};
3use std::path::PathBuf;
4use std::str::FromStr;
5use std::sync::LazyLock;
6
7use serde::ser::SerializeSeq;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use uv_small_str::SmallString;
11
12use crate::{
13 InvalidNameError, InvalidPipGroupError, InvalidPipGroupPathError, validate_and_normalize_ref,
14};
15
16#[derive(
22 Debug,
23 Clone,
24 PartialEq,
25 Eq,
26 PartialOrd,
27 Ord,
28 Hash,
29 rkyv::Archive,
30 rkyv::Deserialize,
31 rkyv::Serialize,
32)]
33#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
34#[rkyv(derive(Debug))]
35pub struct GroupName(SmallString);
36
37impl GroupName {
38 #[expect(clippy::needless_pass_by_value)]
42 fn from_owned(name: String) -> Result<Self, InvalidNameError> {
43 validate_and_normalize_ref(&name).map(Self)
44 }
45
46 pub fn as_str(&self) -> &str {
48 &self.0
49 }
50}
51
52impl FromStr for GroupName {
53 type Err = InvalidNameError;
54
55 fn from_str(name: &str) -> Result<Self, Self::Err> {
56 validate_and_normalize_ref(name).map(Self)
57 }
58}
59
60impl<'de> Deserialize<'de> for GroupName {
61 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
62 where
63 D: Deserializer<'de>,
64 {
65 struct Visitor;
66
67 impl serde::de::Visitor<'_> for Visitor {
68 type Value = GroupName;
69
70 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
71 f.write_str("a string")
72 }
73
74 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
75 GroupName::from_str(v).map_err(serde::de::Error::custom)
76 }
77
78 fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
79 GroupName::from_owned(v).map_err(serde::de::Error::custom)
80 }
81 }
82
83 deserializer.deserialize_str(Visitor)
84 }
85}
86
87impl Serialize for GroupName {
88 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
89 where
90 S: Serializer,
91 {
92 self.0.serialize(serializer)
93 }
94}
95
96impl std::fmt::Display for GroupName {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 self.0.fmt(f)
99 }
100}
101
102impl AsRef<str> for GroupName {
103 fn as_ref(&self) -> &str {
104 &self.0
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
113#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
114pub struct PipGroupName {
115 pub path: Option<PathBuf>,
116 pub name: GroupName,
117}
118
119impl FromStr for PipGroupName {
120 type Err = InvalidPipGroupError;
121
122 fn from_str(path_and_name: &str) -> Result<Self, Self::Err> {
123 if let Some((path, name)) = path_and_name.rsplit_once(':') {
129 if !path.ends_with("pyproject.toml") {
131 Err(InvalidPipGroupPathError(path.to_owned()))?;
132 }
133
134 let name = GroupName::from_str(name)?;
135 let path = Some(PathBuf::from(path));
136 Ok(Self { path, name })
137 } else {
138 let name = GroupName::from_str(path_and_name)?;
139 let path = None;
140 Ok(Self { path, name })
141 }
142 }
143}
144
145impl<'de> Deserialize<'de> for PipGroupName {
146 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
147 where
148 D: Deserializer<'de>,
149 {
150 let s = <Cow<'_, str>>::deserialize(deserializer)?;
151 Self::from_str(&s).map_err(serde::de::Error::custom)
152 }
153}
154
155impl Serialize for PipGroupName {
156 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
157 where
158 S: Serializer,
159 {
160 let string = self.to_string();
161 string.serialize(serializer)
162 }
163}
164
165impl Display for PipGroupName {
166 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
167 if let Some(path) = &self.path {
168 write!(f, "{}:{}", path.display(), self.name)
169 } else {
170 self.name.fmt(f)
171 }
172 }
173}
174
175#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
177pub enum DefaultGroups {
178 All,
180 List(Vec<GroupName>),
182}
183
184impl DefaultGroups {
185 pub fn from_groups(mut groups: Vec<GroupName>) -> Self {
187 groups.sort_unstable();
188 groups.dedup();
189 Self::List(groups)
190 }
191}
192
193#[cfg(feature = "schemars")]
194impl schemars::JsonSchema for DefaultGroups {
195 fn schema_name() -> Cow<'static, str> {
196 Cow::Borrowed("DefaultGroups")
197 }
198
199 fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
200 schemars::json_schema!({
201 "description": "Either the literal \"all\" or a list of groups",
202 "oneOf": [
203 {
204 "description": "All groups are defaulted",
205 "type": "string",
206 "const": "all"
207 },
208 {
209 "description": "A list of groups",
210 "type": "array",
211 "items": generator.subschema_for::<GroupName>()
212 }
213 ]
214 })
215 }
216}
217
218impl serde::Serialize for DefaultGroups {
220 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
221 where
222 S: serde::Serializer,
223 {
224 match self {
225 Self::All => serializer.serialize_str("all"),
226 Self::List(groups) => {
227 let mut seq = serializer.serialize_seq(Some(groups.len()))?;
228 for group in groups {
229 seq.serialize_element(&group)?;
230 }
231 seq.end()
232 }
233 }
234 }
235}
236
237impl<'de> serde::Deserialize<'de> for DefaultGroups {
239 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
240 where
241 D: serde::Deserializer<'de>,
242 {
243 struct StringOrVecVisitor;
244
245 impl<'de> serde::de::Visitor<'de> for StringOrVecVisitor {
246 type Value = DefaultGroups;
247
248 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
249 formatter.write_str(r#"the string "all" or a list of strings"#)
250 }
251
252 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
253 where
254 E: serde::de::Error,
255 {
256 if value != "all" {
257 return Err(serde::de::Error::custom(
258 r#"default-groups must be "all" or a ["list", "of", "groups"]"#,
259 ));
260 }
261 Ok(DefaultGroups::All)
262 }
263
264 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
265 where
266 A: serde::de::SeqAccess<'de>,
267 {
268 let mut groups = Vec::new();
269
270 while let Some(elem) = seq.next_element::<GroupName>()? {
271 groups.push(elem);
272 }
273
274 Ok(DefaultGroups::from_groups(groups))
275 }
276 }
277
278 deserializer.deserialize_any(StringOrVecVisitor)
279 }
280}
281
282impl Default for DefaultGroups {
283 fn default() -> Self {
285 Self::List(Vec::new())
286 }
287}
288
289pub static DEV_DEPENDENCIES: LazyLock<GroupName> =
294 LazyLock::new(|| GroupName::from_str("dev").unwrap());