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}