Error if multiple indexes include default = true

This commit is contained in:
Charlie Marsh 2025-12-05 21:04:22 -05:00
parent 9635258867
commit deeaf3cfba
2 changed files with 56 additions and 3 deletions

View File

@ -275,10 +275,11 @@ pub struct Tool {
pub uv: Option<ToolUv>, pub uv: Option<ToolUv>,
} }
/// Validates that index names in the `tool.uv.index` field are unique. /// Validates the `tool.uv.index` field.
/// ///
/// This custom deserializer function checks for duplicate index names /// This custom deserializer function checks for:
/// and returns an error if any duplicates are found. /// - Duplicate index names
/// - Multiple indexes marked as default
fn deserialize_index_vec<'de, D>(deserializer: D) -> Result<Option<Vec<Index>>, D::Error> fn deserialize_index_vec<'de, D>(deserializer: D) -> Result<Option<Vec<Index>>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
@ -286,6 +287,7 @@ where
let indexes = Option::<Vec<Index>>::deserialize(deserializer)?; let indexes = Option::<Vec<Index>>::deserialize(deserializer)?;
if let Some(indexes) = indexes.as_ref() { if let Some(indexes) = indexes.as_ref() {
let mut seen_names = FxHashSet::with_capacity_and_hasher(indexes.len(), FxBuildHasher); let mut seen_names = FxHashSet::with_capacity_and_hasher(indexes.len(), FxBuildHasher);
let mut seen_default = false;
for index in indexes { for index in indexes {
if let Some(name) = index.name.as_ref() { if let Some(name) = index.name.as_ref() {
if !seen_names.insert(name) { if !seen_names.insert(name) {
@ -294,6 +296,14 @@ where
))); )));
} }
} }
if index.default {
if seen_default {
return Err(serde::de::Error::custom(
"found multiple indexes with `default = true`; only one index may be marked as default",
));
}
seen_default = true;
}
} }
} }
Ok(indexes) Ok(indexes)

View File

@ -18384,6 +18384,49 @@ fn lock_repeat_named_index() -> Result<()> {
Ok(()) Ok(())
} }
/// If multiple indexes are marked as default within a single file, we should raise an error.
#[test]
fn lock_multiple_default_indexes() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[[tool.uv.index]]
name = "first"
url = "https://pypi.org/simple"
default = true
[[tool.uv.index]]
name = "second"
url = "https://example.com"
default = true
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse: `pyproject.toml`
Caused by: TOML parse error at line 8, column 9
|
8 | [[tool.uv.index]]
| ^^^^^^^^^^^^^^^^^
found multiple indexes with `default = true`; only one index may be marked as default
"###);
Ok(())
}
/// If a name is defined in both the workspace root and the member, prefer the index in the member. /// If a name is defined in both the workspace root and the member, prefer the index in the member.
#[test] #[test]
fn lock_repeat_named_index_member() -> Result<()> { fn lock_repeat_named_index_member() -> Result<()> {