#[cfg(feature = "http")]
use axum::http::StatusCode;
#[cfg(feature = "http")]
use axum::response::{IntoResponse, Response};
use ferro_blob_store::BlobStoreError;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum MavenError {
#[error("invalid Maven layout path: {0}")]
InvalidPath(String),
#[error("POM coordinate mismatch: {0}")]
CoordinateMismatch(String),
#[error("invalid POM: {0}")]
InvalidPom(String),
#[error("invalid maven-metadata.xml: {0}")]
InvalidMetadata(String),
#[error("not found: {0}")]
NotFound(String),
#[error("checksum mismatch: {0}")]
ChecksumMismatch(String),
#[error(transparent)]
Storage(#[from] BlobStoreError),
}
#[cfg(feature = "http")]
impl MavenError {
#[must_use]
pub const fn status(&self) -> StatusCode {
match self {
Self::InvalidPath(_)
| Self::InvalidPom(_)
| Self::InvalidMetadata(_)
| Self::CoordinateMismatch(_)
| Self::ChecksumMismatch(_) => StatusCode::BAD_REQUEST,
Self::NotFound(_) => StatusCode::NOT_FOUND,
Self::Storage(err) => storage_status(err),
}
}
}
#[cfg(feature = "http")]
const fn storage_status(err: &BlobStoreError) -> StatusCode {
match err {
BlobStoreError::NotFound(_) => StatusCode::NOT_FOUND,
BlobStoreError::DigestMismatch { .. } | BlobStoreError::InvalidDigest(_) => {
StatusCode::BAD_REQUEST
}
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
#[cfg(feature = "http")]
impl IntoResponse for MavenError {
fn into_response(self) -> Response {
let status = self.status();
let body = self.to_string();
(status, body).into_response()
}
}
#[cfg(all(test, feature = "http"))]
mod tests {
use super::MavenError;
use axum::http::StatusCode;
use ferro_blob_store::{BlobStoreError, Digest};
#[test]
fn invalid_path_maps_to_400() {
let err = MavenError::InvalidPath("no artifactId segment".into());
assert_eq!(err.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn not_found_maps_to_404() {
let err = MavenError::NotFound("foo.jar".into());
assert_eq!(err.status(), StatusCode::NOT_FOUND);
}
#[test]
fn coordinate_and_pom_and_metadata_and_checksum_map_to_400() {
for err in [
MavenError::CoordinateMismatch("gav".into()),
MavenError::InvalidPom("bad pom".into()),
MavenError::InvalidMetadata("bad metadata".into()),
MavenError::ChecksumMismatch("nope".into()),
] {
assert_eq!(err.status(), StatusCode::BAD_REQUEST);
}
}
#[test]
fn storage_not_found_maps_to_404() {
let err = MavenError::Storage(BlobStoreError::NotFound("sha256:deadbeef".into()));
assert_eq!(err.status(), StatusCode::NOT_FOUND);
}
#[test]
fn storage_digest_mismatch_maps_to_400() {
let err = MavenError::Storage(BlobStoreError::DigestMismatch {
expected: "sha256:00".into(),
computed: "sha256:11".into(),
});
assert_eq!(err.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn storage_invalid_digest_maps_to_400() {
let parse_err = "not-a-digest"
.parse::<Digest>()
.expect_err("must reject malformed digest");
let err = MavenError::Storage(BlobStoreError::InvalidDigest(parse_err));
assert_eq!(err.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn storage_io_maps_to_500() {
let io = std::io::Error::other("disk gone");
let err = MavenError::Storage(BlobStoreError::Io(io));
assert_eq!(err.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}