Skip to main content

ferro_maven_layout/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Maven-specific error type.
3//!
4//! Maps onto HTTP status codes at the REST boundary. Wraps
5//! [`ferro_blob_store::BlobStoreError`] transparently so storage and
6//! digest failures surface without a second translation step.
7
8use axum::http::StatusCode;
9use axum::response::{IntoResponse, Response};
10use ferro_blob_store::BlobStoreError;
11
12/// Errors raised by the Maven protocol crate.
13#[derive(Debug, thiserror::Error)]
14#[non_exhaustive]
15pub enum MavenError {
16    /// The request path did not match the Maven 2/3 layout.
17    #[error("invalid Maven layout path: {0}")]
18    InvalidPath(String),
19
20    /// The request path's GAV did not match the POM contents on PUT.
21    #[error("POM coordinate mismatch: {0}")]
22    CoordinateMismatch(String),
23
24    /// The POM body failed to parse as XML.
25    #[error("invalid POM: {0}")]
26    InvalidPom(String),
27
28    /// A maven-metadata.xml body failed to parse.
29    #[error("invalid maven-metadata.xml: {0}")]
30    InvalidMetadata(String),
31
32    /// The requested artifact or metadata document does not exist.
33    #[error("not found: {0}")]
34    NotFound(String),
35
36    /// A checksum sidecar did not agree with the hash of the underlying
37    /// artifact.
38    #[error("checksum mismatch: {0}")]
39    ChecksumMismatch(String),
40
41    /// An underlying blob-store error (I/O, digest mismatch, missing blob).
42    #[error(transparent)]
43    Storage(#[from] BlobStoreError),
44}
45
46impl MavenError {
47    /// HTTP status code for this error category.
48    #[must_use]
49    pub fn status(&self) -> StatusCode {
50        match self {
51            Self::InvalidPath(_)
52            | Self::InvalidPom(_)
53            | Self::InvalidMetadata(_)
54            | Self::CoordinateMismatch(_)
55            | Self::ChecksumMismatch(_) => StatusCode::BAD_REQUEST,
56            Self::NotFound(_) => StatusCode::NOT_FOUND,
57            Self::Storage(err) => storage_status(err),
58        }
59    }
60}
61
62fn storage_status(err: &BlobStoreError) -> StatusCode {
63    match err {
64        BlobStoreError::NotFound(_) => StatusCode::NOT_FOUND,
65        BlobStoreError::DigestMismatch { .. } | BlobStoreError::InvalidDigest(_) => {
66            StatusCode::BAD_REQUEST
67        }
68        BlobStoreError::Io(_) => StatusCode::INTERNAL_SERVER_ERROR,
69        // BlobStoreError is `#[non_exhaustive]`; future variants land here.
70        _ => StatusCode::INTERNAL_SERVER_ERROR,
71    }
72}
73
74impl IntoResponse for MavenError {
75    fn into_response(self) -> Response {
76        let status = self.status();
77        let body = self.to_string();
78        (status, body).into_response()
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::MavenError;
85    use axum::http::StatusCode;
86
87    #[test]
88    fn invalid_path_maps_to_400() {
89        let err = MavenError::InvalidPath("no artifactId segment".into());
90        assert_eq!(err.status(), StatusCode::BAD_REQUEST);
91    }
92
93    #[test]
94    fn not_found_maps_to_404() {
95        let err = MavenError::NotFound("foo.jar".into());
96        assert_eq!(err.status(), StatusCode::NOT_FOUND);
97    }
98}