1use std::{
2 collections::{BTreeMap, btree_map::Entry},
3 mem,
4 str::FromStr,
5};
6use uv_cache_key::CacheKeyHasher;
7use uv_normalize::PackageName;
8
9#[derive(Debug, Clone)]
10pub struct ConfigSettingEntry {
11 key: String,
13 value: String,
15}
16
17impl FromStr for ConfigSettingEntry {
18 type Err = String;
19
20 fn from_str(s: &str) -> Result<Self, Self::Err> {
21 let Some((key, value)) = s.split_once('=') else {
22 return Err(format!(
23 "Invalid config setting: {s} (expected `KEY=VALUE`)"
24 ));
25 };
26 Ok(Self {
27 key: key.trim().to_string(),
28 value: value.trim().to_string(),
29 })
30 }
31}
32
33#[derive(Debug, Clone)]
34pub struct ConfigSettingPackageEntry {
35 package: PackageName,
37 setting: ConfigSettingEntry,
39}
40
41impl FromStr for ConfigSettingPackageEntry {
42 type Err = String;
43
44 fn from_str(s: &str) -> Result<Self, Self::Err> {
45 let Some((package_str, config_str)) = s.split_once(':') else {
46 return Err(format!(
47 "Invalid config setting: {s} (expected `PACKAGE:KEY=VALUE`)"
48 ));
49 };
50
51 let package = PackageName::from_str(package_str.trim())
52 .map_err(|e| format!("Invalid package name: {e}"))?;
53 let setting = ConfigSettingEntry::from_str(config_str)?;
54
55 Ok(Self { package, setting })
56 }
57}
58
59#[derive(Debug, Clone, Hash, PartialEq, Eq)]
60#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema), schemars(untagged))]
61enum ConfigSettingValue {
62 String(String),
64 List(Vec<String>),
66}
67
68impl serde::Serialize for ConfigSettingValue {
69 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
70 match self {
71 Self::String(value) => serializer.serialize_str(value),
72 Self::List(values) => serializer.collect_seq(values.iter()),
73 }
74 }
75}
76
77impl<'de> serde::Deserialize<'de> for ConfigSettingValue {
78 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
79 struct Visitor;
80
81 impl<'de> serde::de::Visitor<'de> for Visitor {
82 type Value = ConfigSettingValue;
83
84 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
85 formatter.write_str("a string or list of strings")
86 }
87
88 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
89 Ok(ConfigSettingValue::String(value.to_string()))
90 }
91
92 fn visit_string<E: serde::de::Error>(self, value: String) -> Result<Self::Value, E> {
93 Ok(ConfigSettingValue::String(value))
94 }
95
96 fn visit_seq<A: serde::de::SeqAccess<'de>>(
97 self,
98 mut seq: A,
99 ) -> Result<Self::Value, A::Error> {
100 let mut values = Vec::new();
101 while let Some(value) = seq.next_element()? {
102 values.push(value);
103 }
104 Ok(ConfigSettingValue::List(values))
105 }
106 }
107
108 deserializer.deserialize_any(Visitor)
109 }
110}
111
112#[derive(Debug, Default, Hash, Clone, PartialEq, Eq)]
117#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
118pub struct ConfigSettings(BTreeMap<String, ConfigSettingValue>);
119
120impl FromIterator<ConfigSettingEntry> for ConfigSettings {
121 fn from_iter<T: IntoIterator<Item = ConfigSettingEntry>>(iter: T) -> Self {
122 let mut config = BTreeMap::default();
123 for entry in iter {
124 match config.entry(entry.key) {
125 Entry::Vacant(vacant) => {
126 vacant.insert(ConfigSettingValue::String(entry.value));
127 }
128 Entry::Occupied(mut occupied) => match occupied.get_mut() {
129 ConfigSettingValue::String(existing) => {
130 let existing = mem::take(existing);
131 occupied.insert(ConfigSettingValue::List(vec![existing, entry.value]));
132 }
133 ConfigSettingValue::List(existing) => {
134 existing.push(entry.value);
135 }
136 },
137 }
138 }
139 Self(config)
140 }
141}
142
143impl ConfigSettings {
144 pub(crate) fn is_empty(&self) -> bool {
146 self.0.is_empty()
147 }
148
149 pub fn escape_for_python(&self) -> String {
151 serde_json::to_string(self).expect("Failed to serialize config settings")
152 }
153
154 #[must_use]
156 pub fn merge(self, other: Self) -> Self {
157 let mut config = self.0;
158 for (key, value) in other.0 {
159 match config.entry(key) {
160 Entry::Vacant(vacant) => {
161 vacant.insert(value);
162 }
163 Entry::Occupied(mut occupied) => match occupied.get_mut() {
164 ConfigSettingValue::String(existing) => {
165 let existing = mem::take(existing);
166 match value {
167 ConfigSettingValue::String(value) => {
168 occupied.insert(ConfigSettingValue::List(vec![existing, value]));
169 }
170 ConfigSettingValue::List(mut values) => {
171 values.insert(0, existing);
172 occupied.insert(ConfigSettingValue::List(values));
173 }
174 }
175 }
176 ConfigSettingValue::List(existing) => match value {
177 ConfigSettingValue::String(value) => {
178 existing.push(value);
179 }
180 ConfigSettingValue::List(values) => {
181 existing.extend(values);
182 }
183 },
184 },
185 }
186 }
187 Self(config)
188 }
189}
190
191impl uv_cache_key::CacheKey for ConfigSettings {
192 fn cache_key(&self, state: &mut CacheKeyHasher) {
193 for (key, value) in &self.0 {
194 key.cache_key(state);
195 match value {
196 ConfigSettingValue::String(value) => value.cache_key(state),
197 ConfigSettingValue::List(values) => values.cache_key(state),
198 }
199 }
200 }
201}
202
203impl serde::Serialize for ConfigSettings {
204 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
205 use serde::ser::SerializeMap;
206
207 let mut map = serializer.serialize_map(Some(self.0.len()))?;
208 for (key, value) in &self.0 {
209 map.serialize_entry(key, value)?;
210 }
211 map.end()
212 }
213}
214
215impl<'de> serde::Deserialize<'de> for ConfigSettings {
216 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
217 struct Visitor;
218
219 impl<'de> serde::de::Visitor<'de> for Visitor {
220 type Value = ConfigSettings;
221
222 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
223 formatter.write_str("a map from string to string or list of strings")
224 }
225
226 fn visit_map<A: serde::de::MapAccess<'de>>(
227 self,
228 mut map: A,
229 ) -> Result<Self::Value, A::Error> {
230 let mut config = BTreeMap::default();
231 while let Some((key, value)) = map.next_entry()? {
232 config.insert(key, value);
233 }
234 Ok(ConfigSettings(config))
235 }
236 }
237
238 deserializer.deserialize_map(Visitor)
239 }
240}
241
242#[derive(Debug, Default, Clone, PartialEq, Eq)]
244#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
245pub struct PackageConfigSettings(BTreeMap<PackageName, ConfigSettings>);
246
247impl FromIterator<ConfigSettingPackageEntry> for PackageConfigSettings {
248 fn from_iter<T: IntoIterator<Item = ConfigSettingPackageEntry>>(iter: T) -> Self {
249 let mut package_configs: BTreeMap<PackageName, Vec<ConfigSettingEntry>> = BTreeMap::new();
250
251 for entry in iter {
252 package_configs
253 .entry(entry.package)
254 .or_default()
255 .push(entry.setting);
256 }
257
258 let configs = package_configs
259 .into_iter()
260 .map(|(package, entries)| (package, entries.into_iter().collect()))
261 .collect();
262
263 Self(configs)
264 }
265}
266
267impl PackageConfigSettings {
268 pub fn get(&self, package: &PackageName) -> Option<&ConfigSettings> {
270 self.0.get(package)
271 }
272
273 #[must_use]
275 pub fn merge(mut self, other: Self) -> Self {
276 for (package, settings) in other.0 {
277 match self.0.entry(package) {
278 Entry::Vacant(vacant) => {
279 vacant.insert(settings);
280 }
281 Entry::Occupied(mut occupied) => {
282 let merged = mem::take(occupied.get_mut()).merge(settings);
283 occupied.insert(merged);
284 }
285 }
286 }
287 self
288 }
289}
290
291impl uv_cache_key::CacheKey for PackageConfigSettings {
292 fn cache_key(&self, state: &mut CacheKeyHasher) {
293 for (package, settings) in &self.0 {
294 package.to_string().cache_key(state);
295 settings.cache_key(state);
296 }
297 }
298}
299
300impl serde::Serialize for PackageConfigSettings {
301 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
302 use serde::ser::SerializeMap;
303
304 let mut map = serializer.serialize_map(Some(self.0.len()))?;
305 for (key, value) in &self.0 {
306 map.serialize_entry(&key.to_string(), value)?;
307 }
308 map.end()
309 }
310}
311
312impl<'de> serde::Deserialize<'de> for PackageConfigSettings {
313 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
314 struct Visitor;
315
316 impl<'de> serde::de::Visitor<'de> for Visitor {
317 type Value = PackageConfigSettings;
318
319 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
320 formatter.write_str("a map from package name to config settings")
321 }
322
323 fn visit_map<A: serde::de::MapAccess<'de>>(
324 self,
325 mut map: A,
326 ) -> Result<Self::Value, A::Error> {
327 let mut config = BTreeMap::default();
328 while let Some((key, value)) = map.next_entry::<String, ConfigSettings>()? {
329 let package = PackageName::from_str(&key).map_err(|e| {
330 serde::de::Error::custom(format!("Invalid package name: {e}"))
331 })?;
332 config.insert(package, value);
333 }
334 Ok(PackageConfigSettings(config))
335 }
336 }
337
338 deserializer.deserialize_map(Visitor)
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn collect_config_settings() {
348 let settings: ConfigSettings = vec![
349 ConfigSettingEntry {
350 key: "key".to_string(),
351 value: "value".to_string(),
352 },
353 ConfigSettingEntry {
354 key: "key".to_string(),
355 value: "value2".to_string(),
356 },
357 ConfigSettingEntry {
358 key: "list".to_string(),
359 value: "value3".to_string(),
360 },
361 ConfigSettingEntry {
362 key: "list".to_string(),
363 value: "value4".to_string(),
364 },
365 ]
366 .into_iter()
367 .collect();
368 assert_eq!(
369 settings.0.get("key"),
370 Some(&ConfigSettingValue::List(vec![
371 "value".to_string(),
372 "value2".to_string()
373 ]))
374 );
375 assert_eq!(
376 settings.0.get("list"),
377 Some(&ConfigSettingValue::List(vec![
378 "value3".to_string(),
379 "value4".to_string()
380 ]))
381 );
382 }
383
384 #[test]
385 fn escape_for_python() {
386 let mut settings = ConfigSettings::default();
387 settings.0.insert(
388 "key".to_string(),
389 ConfigSettingValue::String("value".to_string()),
390 );
391 settings.0.insert(
392 "list".to_string(),
393 ConfigSettingValue::List(vec!["value1".to_string(), "value2".to_string()]),
394 );
395 assert_eq!(
396 settings.escape_for_python(),
397 r#"{"key":"value","list":["value1","value2"]}"#
398 );
399
400 let mut settings = ConfigSettings::default();
401 settings.0.insert(
402 "key".to_string(),
403 ConfigSettingValue::String("Hello, \"world!\"".to_string()),
404 );
405 settings.0.insert(
406 "list".to_string(),
407 ConfigSettingValue::List(vec!["'value1'".to_string()]),
408 );
409 assert_eq!(
410 settings.escape_for_python(),
411 r#"{"key":"Hello, \"world!\"","list":["'value1'"]}"#
412 );
413
414 let mut settings = ConfigSettings::default();
415 settings.0.insert(
416 "key".to_string(),
417 ConfigSettingValue::String("val\\1 {}value".to_string()),
418 );
419 assert_eq!(settings.escape_for_python(), r#"{"key":"val\\1 {}value"}"#);
420 }
421
422 #[test]
423 fn parse_config_setting_package_entry() {
424 let entry = ConfigSettingPackageEntry::from_str("numpy:editable_mode=compat").unwrap();
426 assert_eq!(entry.package.as_ref(), "numpy");
427 assert_eq!(entry.setting.key, "editable_mode");
428 assert_eq!(entry.setting.value, "compat");
429
430 let entry = ConfigSettingPackageEntry::from_str("my-package:some_key=value").unwrap();
432 assert_eq!(entry.package.as_ref(), "my-package");
433 assert_eq!(entry.setting.key, "some_key");
434 assert_eq!(entry.setting.value, "value");
435
436 let entry = ConfigSettingPackageEntry::from_str(" numpy : key = value ").unwrap();
438 assert_eq!(entry.package.as_ref(), "numpy");
439 assert_eq!(entry.setting.key, "key");
440 assert_eq!(entry.setting.value, "value");
441 }
442
443 #[test]
444 fn collect_config_settings_package() {
445 let settings: PackageConfigSettings = vec![
446 ConfigSettingPackageEntry::from_str("numpy:editable_mode=compat").unwrap(),
447 ConfigSettingPackageEntry::from_str("numpy:another_key=value").unwrap(),
448 ConfigSettingPackageEntry::from_str("scipy:build_option=fast").unwrap(),
449 ]
450 .into_iter()
451 .collect();
452
453 let numpy_settings = settings
454 .get(&PackageName::from_str("numpy").unwrap())
455 .unwrap();
456 assert_eq!(
457 numpy_settings.0.get("editable_mode"),
458 Some(&ConfigSettingValue::String("compat".to_string()))
459 );
460 assert_eq!(
461 numpy_settings.0.get("another_key"),
462 Some(&ConfigSettingValue::String("value".to_string()))
463 );
464
465 let scipy_settings = settings
466 .get(&PackageName::from_str("scipy").unwrap())
467 .unwrap();
468 assert_eq!(
469 scipy_settings.0.get("build_option"),
470 Some(&ConfigSettingValue::String("fast".to_string()))
471 );
472 }
473}