Skip to main content

deps_pypi/
error.rs

1use thiserror::Error;
2
3/// Errors specific to PyPI/Python dependency handling.
4///
5/// These errors cover parsing pyproject.toml files and validating PEP 508
6/// dependency specifications. Registry communication errors are reported as
7/// `deps_core::DepsError` directly (see `crate::registry`).
8///
9/// `#[non_exhaustive]` (matching [`crate::types::PypiDependencySection`]):
10/// this enum grows as new parse-failure modes are distinguished (e.g.
11/// [`PypiError::RequirementTooLong`], added without a matching
12/// `cargo-semver-checks` gate), so an external exhaustive `match` must not
13/// be able to break on a future addition.
14#[derive(Error, Debug)]
15#[non_exhaustive]
16pub enum PypiError {
17    /// Failed to parse pyproject.toml
18    #[error("Failed to parse pyproject.toml: {message}")]
19    TomlParseError { message: String },
20
21    /// Invalid PEP 508 dependency specification
22    #[error("Invalid PEP 508 dependency specification: {source}")]
23    InvalidDependencySpec {
24        #[source]
25        source: pep508_rs::Pep508Error,
26    },
27
28    /// Unsupported dependency format
29    #[error("Unsupported dependency format: {message}")]
30    UnsupportedFormat { message: String },
31
32    /// PEP 508 requirement string exceeded the length cap protecting against
33    /// `pep508_rs`'s O(n²) extras-list parser (see
34    /// `crate::parser::MAX_REQUIREMENT_LEN`). Kept as a distinct variant
35    /// (rather than folded into `UnsupportedFormat`) so callers can tell a
36    /// deliberate length rejection apart from a genuine syntax error — the
37    /// two must be counted differently by heuristics like the
38    /// `requirements.txt` "is this really a manifest" signal.
39    #[error("requirement string too long: {len} bytes (max {max} bytes)")]
40    RequirementTooLong { len: usize, max: usize },
41}
42
43/// Result type alias for PyPI operations.
44pub type Result<T> = std::result::Result<T, PypiError>;
45
46impl PypiError {
47    /// Create an unsupported format error.
48    pub fn unsupported_format(message: impl Into<String>) -> Self {
49        Self::UnsupportedFormat {
50            message: message.into(),
51        }
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn test_error_display() {
61        let err = PypiError::unsupported_format("invalid table format");
62        assert_eq!(
63            err.to_string(),
64            "Unsupported dependency format: invalid table format"
65        );
66    }
67
68    #[test]
69    fn test_toml_parse_error_display() {
70        let err = PypiError::TomlParseError {
71            message: "unexpected token".into(),
72        };
73        assert_eq!(
74            err.to_string(),
75            "Failed to parse pyproject.toml: unexpected token"
76        );
77    }
78}