use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use systemprompt_config::SecretsBootstrapError;
use systemprompt_loader::ConfigLoadError;
use systemprompt_models::api::ApiError;
use super::super::bridge_error::BridgeError;
use crate::error::ApiHttpError;
#[derive(Debug, thiserror::Error)]
pub enum ReleaseError {
#[error(transparent)]
Auth(Box<BridgeError>),
#[error("services config not ready")]
ServicesNotReady(#[source] ConfigLoadError),
#[error("bridge releases are not configured on this gateway")]
NotConfigured,
#[error("no published build for platform {platform}")]
UnknownPlatform { platform: String },
#[error("no {prefix}* release found in {repo}")]
NoRelease { prefix: String, repo: String },
#[error("release {tag} has no asset {asset}")]
MissingAsset { tag: String, asset: String },
#[error("release {version} publishes no SHA256SUMS")]
NoChecksums { version: String },
#[error("SHA256SUMS has no entry for {asset}")]
ChecksumMissing { asset: String },
#[error("{stage} failed")]
Request {
stage: &'static str,
#[source]
source: reqwest::Error,
},
#[error("{stage} returned {status}")]
UpstreamStatus {
stage: &'static str,
status: StatusCode,
},
#[error("bridge release token secret unavailable")]
SecretsUnavailable(#[source] SecretsBootstrapError),
#[error("bridge release token secret {key} is not configured")]
TokenNotConfigured { key: String },
}
impl ReleaseError {
#[must_use]
pub const fn status(&self) -> StatusCode {
match self {
Self::Auth(_) => StatusCode::UNAUTHORIZED,
Self::NotConfigured
| Self::UnknownPlatform { .. }
| Self::NoRelease { .. }
| Self::MissingAsset { .. } => StatusCode::NOT_FOUND,
Self::NoChecksums { .. }
| Self::ChecksumMissing { .. }
| Self::Request { .. }
| Self::UpstreamStatus { .. } => StatusCode::BAD_GATEWAY,
Self::ServicesNotReady(_)
| Self::SecretsUnavailable(_)
| Self::TokenNotConfigured { .. } => StatusCode::SERVICE_UNAVAILABLE,
}
}
#[must_use]
pub const fn error_key(&self) -> &'static str {
match self {
Self::Auth(_) => "invalid_credential",
Self::NotConfigured => "bridge_releases_not_configured",
Self::UnknownPlatform { .. } => "platform_not_published",
Self::NoRelease { .. } => "release_not_found",
Self::MissingAsset { .. } => "release_asset_not_found",
Self::NoChecksums { .. } | Self::ChecksumMissing { .. } => {
"release_checksum_unavailable"
},
Self::Request { .. } | Self::UpstreamStatus { .. } => "release_upstream_failed",
Self::ServicesNotReady(_)
| Self::SecretsUnavailable(_)
| Self::TokenNotConfigured { .. } => "release_feed_not_ready",
}
}
}
impl From<BridgeError> for ReleaseError {
fn from(err: BridgeError) -> Self {
Self::Auth(Box::new(err))
}
}
impl IntoResponse for ReleaseError {
fn into_response(self) -> Response {
let status = self.status();
let key = self.error_key();
let api = match self {
Self::Auth(inner) => return ApiHttpError::from(*inner).into_response(),
err if status == StatusCode::NOT_FOUND => {
ApiError::not_found(err.to_string()).with_error_key(key)
},
err => ApiError::service_unavailable("Bridge release feed unavailable")
.with_error_key(key)
.with_source(err),
};
let mut response = api.into_response();
*response.status_mut() = status;
response
}
}