uv_pypi_types/metadata/
mod.rs1mod build_requires;
2mod metadata10;
3mod metadata23;
4mod metadata_resolver;
5mod pyproject_toml;
6mod requires_dist;
7mod requires_txt;
8
9use std::str::Utf8Error;
10
11use mailparse::{MailHeaderMap, MailParseError};
12use thiserror::Error;
13
14use uv_normalize::InvalidNameError;
15use uv_pep440::{VersionParseError, VersionSpecifiersParseError};
16use uv_pep508::Pep508Error;
17
18use crate::VerbatimParsedUrl;
19
20pub use build_requires::BuildRequires;
21pub use metadata_resolver::ResolutionMetadata;
22pub use metadata10::Metadata10;
23pub use metadata23::Metadata23;
24pub use pyproject_toml::PyProjectToml;
25pub use requires_dist::RequiresDist;
26pub use requires_txt::RequiresTxt;
27
28#[derive(Error, Debug)]
32pub enum MetadataError {
33 #[error(transparent)]
34 MailParse(#[from] MailParseError),
35 #[error("Invalid `pyproject.toml`")]
36 InvalidPyprojectTomlSyntax(#[source] toml_edit::TomlError),
37 #[error(transparent)]
38 InvalidPyprojectTomlSchema(toml_edit::de::Error),
39 #[error(
40 "`pyproject.toml` is using the `[project]` table, but the required `project.name` field is not set"
41 )]
42 MissingName,
43 #[error("Metadata field {0} not found")]
44 FieldNotFound(&'static str),
45 #[error("Invalid version: {0}")]
46 Pep440VersionError(VersionParseError),
47 #[error(transparent)]
48 Pep440Error(#[from] VersionSpecifiersParseError),
49 #[error(transparent)]
50 Pep508Error(#[from] Box<Pep508Error<VerbatimParsedUrl>>),
51 #[error(transparent)]
52 InvalidName(#[from] InvalidNameError),
53 #[error("Invalid `Metadata-Version` field: {0}")]
54 InvalidMetadataVersion(String),
55 #[error("Reading metadata from `PKG-INFO` requires Metadata 2.2 or later (found: {0})")]
56 UnsupportedMetadataVersion(String),
57 #[error("The following field was marked as dynamic: {0}")]
58 DynamicField(&'static str),
59 #[error(
60 "The project uses Poetry's syntax to declare its dependencies, despite including a `project` table in `pyproject.toml`"
61 )]
62 PoetrySyntax,
63 #[error("Failed to read `requires.txt` contents")]
64 RequiresTxtContents(#[from] std::io::Error),
65 #[error("The description is not valid utf-8")]
66 DescriptionEncoding(#[source] Utf8Error),
67}
68
69impl From<Pep508Error<VerbatimParsedUrl>> for MetadataError {
70 fn from(error: Pep508Error<VerbatimParsedUrl>) -> Self {
71 Self::Pep508Error(Box::new(error))
72 }
73}
74
75#[derive(Debug)]
77struct Headers<'a> {
78 headers: Vec<mailparse::MailHeader<'a>>,
79 body_start: usize,
80}
81
82impl<'a> Headers<'a> {
83 fn parse(content: &'a [u8]) -> Result<Self, MailParseError> {
85 let (headers, body_start) = mailparse::parse_headers(content)?;
86 Ok(Self {
87 headers,
88 body_start,
89 })
90 }
91
92 fn get_first_value(&self, name: &str) -> Option<String> {
94 self.headers.get_first_header(name).and_then(|header| {
95 let value = header.get_value();
96 if value == "UNKNOWN" {
97 None
98 } else {
99 Some(value)
100 }
101 })
102 }
103
104 fn get_all_values(&self, name: &str) -> impl Iterator<Item = String> {
106 self.headers
107 .get_all_values(name)
108 .into_iter()
109 .filter(|value| value != "UNKNOWN")
110 }
111}
112
113fn parse_version(metadata_version: &str) -> Result<(u8, u8), MetadataError> {
115 let (major, minor) =
116 metadata_version
117 .split_once('.')
118 .ok_or(MetadataError::InvalidMetadataVersion(
119 metadata_version.to_string(),
120 ))?;
121 let major = major
122 .parse::<u8>()
123 .map_err(|_| MetadataError::InvalidMetadataVersion(metadata_version.to_string()))?;
124 let minor = minor
125 .parse::<u8>()
126 .map_err(|_| MetadataError::InvalidMetadataVersion(metadata_version.to_string()))?;
127 Ok((major, minor))
128}