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 #[allow(clippy::needless_pass_by_value)]
42 pub 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
184#[cfg(feature = "schemars")]
185impl schemars::JsonSchema for DefaultGroups {
186 fn schema_name() -> Cow<'static, str> {
187 Cow::Borrowed("DefaultGroups")
188 }
189
190 fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
191 schemars::json_schema!({
192 "description": "Either the literal \"all\" or a list of groups",
193 "oneOf": [
194 {
195 "description": "All groups are defaulted",
196 "type": "string",
197 "const": "all"
198 },
199 {
200 "description": "A list of groups",
201 "type": "array",
202 "items": generator.subschema_for::<GroupName>()
203 }
204 ]
205 })
206 }
207}
208
209impl serde::Serialize for DefaultGroups {
211 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
212 where
213 S: serde::Serializer,
214 {
215 match self {
216 Self::All => serializer.serialize_str("all"),
217 Self::List(groups) => {
218 let mut seq = serializer.serialize_seq(Some(groups.len()))?;
219 for group in groups {
220 seq.serialize_element(&group)?;
221 }
222 seq.end()
223 }
224 }
225 }
226}
227
228impl<'de> serde::Deserialize<'de> for DefaultGroups {
230 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
231 where
232 D: serde::Deserializer<'de>,
233 {
234 struct StringOrVecVisitor;
235
236 impl<'de> serde::de::Visitor<'de> for StringOrVecVisitor {
237 type Value = DefaultGroups;
238
239 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
240 formatter.write_str(r#"the string "all" or a list of strings"#)
241 }
242
243 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
244 where
245 E: serde::de::Error,
246 {
247 if value != "all" {
248 return Err(serde::de::Error::custom(
249 r#"default-groups must be "all" or a ["list", "of", "groups"]"#,
250 ));
251 }
252 Ok(DefaultGroups::All)
253 }
254
255 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
256 where
257 A: serde::de::SeqAccess<'de>,
258 {
259 let mut groups = Vec::new();
260
261 while let Some(elem) = seq.next_element::<GroupName>()? {
262 groups.push(elem);
263 }
264
265 Ok(DefaultGroups::List(groups))
266 }
267 }
268
269 deserializer.deserialize_any(StringOrVecVisitor)
270 }
271}
272
273impl Default for DefaultGroups {
274 fn default() -> Self {
276 Self::List(Vec::new())
277 }
278}
279
280pub static DEV_DEPENDENCIES: LazyLock<GroupName> =
285 LazyLock::new(|| GroupName::from_str("dev").unwrap());