From 55cbe85d74ecb9fee5f656936a8ba8cf76820db1 Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 3 Mar 2026 19:13:01 +0100 Subject: [PATCH] Unify poetry check types (#18260) Found this duplication when looking at the toml parsing code. --- Cargo.lock | 1 - .../src/metadata/pyproject_toml.rs | 11 ++--- crates/uv-requirements/Cargo.toml | 1 - crates/uv-requirements/src/specification.rs | 2 +- crates/uv-requirements/src/unnamed.rs | 32 +-------------- crates/uv-resolver/src/lock/mod.rs | 41 ++++++++++--------- crates/uv/tests/it/pip_install.rs | 1 + 7 files changed, 31 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 325e23726..e115e7595 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6945,7 +6945,6 @@ dependencies = [ "fs-err", "futures", "rustc-hash", - "serde", "thiserror 2.0.18", "toml", "tracing", diff --git a/crates/uv-pypi-types/src/metadata/pyproject_toml.rs b/crates/uv-pypi-types/src/metadata/pyproject_toml.rs index 0d3b0ec77..9783adc6b 100644 --- a/crates/uv-pypi-types/src/metadata/pyproject_toml.rs +++ b/crates/uv-pypi-types/src/metadata/pyproject_toml.rs @@ -14,7 +14,7 @@ use crate::MetadataError; #[serde(rename_all = "kebab-case")] pub struct PyProjectToml { pub project: Option, - pub(super) tool: Option, + pub tool: Option, } impl PyProjectToml { @@ -80,11 +80,12 @@ impl TryFrom for Project { #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "kebab-case")] -pub(super) struct Tool { - pub(super) poetry: Option, +pub struct Tool { + pub poetry: Option, } #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "kebab-case")] -#[expect(clippy::empty_structs_with_brackets)] -pub(super) struct ToolPoetry {} +pub struct ToolPoetry { + pub name: Option, +} diff --git a/crates/uv-requirements/Cargo.toml b/crates/uv-requirements/Cargo.toml index 38bb61f0f..abbdec15a 100644 --- a/crates/uv-requirements/Cargo.toml +++ b/crates/uv-requirements/Cargo.toml @@ -41,7 +41,6 @@ console = { workspace = true } fs-err = { workspace = true, features = ["tokio"] } futures = { workspace = true } rustc-hash = { workspace = true } -serde = { workspace = true } thiserror = { workspace = true } toml = { workspace = true } tracing = { workspace = true } diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index 9934209fd..b304d8701 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -284,7 +284,7 @@ impl RequirementsSpecification { )); } }; - let pyproject_toml = toml::from_str::(&content) + let pyproject_toml = PyProjectToml::from_toml(&content) .with_context(|| format!("Failed to parse: `{}`", path.user_display()))?; Self { diff --git a/crates/uv-requirements/src/unnamed.rs b/crates/uv-requirements/src/unnamed.rs index 9e103ea75..dbfecba92 100644 --- a/crates/uv-requirements/src/unnamed.rs +++ b/crates/uv-requirements/src/unnamed.rs @@ -5,7 +5,6 @@ use std::sync::Arc; use configparser::ini::Ini; use futures::{TryStreamExt, stream::FuturesOrdered}; -use serde::Deserialize; use tracing::debug; use url::Host; @@ -17,8 +16,7 @@ use uv_distribution_types::{ }; use uv_normalize::PackageName; use uv_pep508::{UnnamedRequirement, VersionOrUrl}; -use uv_pypi_types::Metadata10; -use uv_pypi_types::{ParsedUrl, VerbatimParsedUrl}; +use uv_pypi_types::{Metadata10, ParsedUrl, PyProjectToml, VerbatimParsedUrl}; use uv_resolver::{InMemoryIndex, MetadataResponse}; use uv_types::{BuildContext, HashStrategy}; @@ -168,7 +166,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> { let project_path = parsed_directory_url.install_path.join("pyproject.toml"); if let Some(pyproject) = fs_err::read_to_string(project_path) .ok() - .and_then(|contents| toml::from_str::(&contents).ok()) + .and_then(|contents| PyProjectToml::from_toml(&contents).ok()) { // Read PEP 621 metadata from the `pyproject.toml`. if let Some(project) = pyproject.project { @@ -315,29 +313,3 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> { }) } } - -/// A pyproject.toml as specified in PEP 517. -#[derive(Deserialize, Debug)] -#[serde(rename_all = "kebab-case")] -struct PyProjectToml { - project: Option, - tool: Option, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "kebab-case")] -struct Project { - name: PackageName, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "kebab-case")] -struct Tool { - poetry: Option, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "kebab-case")] -struct ToolPoetry { - name: Option, -} diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 36d656975..1413ba9f6 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1793,27 +1793,28 @@ impl Lock { // metadata object. let parent = root.join(source_tree); let path = parent.join("pyproject.toml"); - let metadata = - match fs_err::tokio::read_to_string(&path).await { - Ok(contents) => { - let pyproject_toml = toml::from_str::(&contents) - .map_err(|err| LockErrorKind::InvalidPyprojectToml { + let metadata = match fs_err::tokio::read_to_string(&path).await { + Ok(contents) => { + let pyproject_toml = + PyProjectToml::from_toml(&contents).map_err(|err| { + LockErrorKind::InvalidPyprojectToml { path: path.clone(), err, - })?; - database - .requires_dist(&parent, &pyproject_toml) - .await - .map_err(|err| LockErrorKind::Resolution { - id: package.id.clone(), - err, - })? - } - Err(err) if err.kind() == io::ErrorKind::NotFound => None, - Err(err) => { - return Err(LockErrorKind::UnreadablePyprojectToml { path, err }.into()); - } - }; + } + })?; + database + .requires_dist(&parent, &pyproject_toml) + .await + .map_err(|err| LockErrorKind::Resolution { + id: package.id.clone(), + err, + })? + } + Err(err) if err.kind() == io::ErrorKind::NotFound => None, + Err(err) => { + return Err(LockErrorKind::UnreadablePyprojectToml { path, err }.into()); + } + }; let satisfied = metadata.is_some_and(|metadata| { // Validate that the package is still dynamic. @@ -5986,7 +5987,7 @@ enum LockErrorKind { InvalidPyprojectToml { path: PathBuf, #[source] - err: toml::de::Error, + err: uv_pypi_types::MetadataError, }, /// An error that occurs when a workspace member has a non-local source. #[error("Workspace member `{id}` has non-local source", id = id.cyan())] diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 635e7e745..a170f78a1 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -132,6 +132,7 @@ fn invalid_pyproject_toml_syntax() -> Result<()> { key with no value, expected `=` error: Failed to parse: `pyproject.toml` + Caused by: Invalid `pyproject.toml` Caused by: TOML parse error at line 1, column 5 | 1 | 123 - 456