Files
crank/apps/admin-api/src/error.rs
T
2026-03-25 21:31:54 +03:00

168 lines
5.1 KiB
Rust

use axum::{
Json,
http::StatusCode,
response::{IntoResponse, Response},
};
use mcpaas_mapping::MappingError;
use mcpaas_registry::RegistryError;
use mcpaas_runtime::RuntimeError;
use mcpaas_schema::SchemaError;
use serde_json::{Value, json};
use thiserror::Error;
use tracing::{error, warn};
use crate::storage::StorageError;
#[derive(Debug, Error)]
pub enum ApiError {
#[error("{message}")]
Validation { message: String },
#[error("{message}")]
NotFound { message: String },
#[error("{message}")]
Conflict { message: String },
#[error("{message}")]
Internal { message: String },
}
impl ApiError {
pub fn validation(message: impl Into<String>) -> Self {
Self::Validation {
message: message.into(),
}
}
pub fn not_found(message: impl Into<String>) -> Self {
Self::NotFound {
message: message.into(),
}
}
pub fn conflict(message: impl Into<String>) -> Self {
Self::Conflict {
message: message.into(),
}
}
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal {
message: message.into(),
}
}
fn status_code(&self) -> StatusCode {
match self {
Self::Validation { .. } => StatusCode::BAD_REQUEST,
Self::NotFound { .. } => StatusCode::NOT_FOUND,
Self::Conflict { .. } => StatusCode::CONFLICT,
Self::Internal { .. } => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn code(&self) -> &'static str {
match self {
Self::Validation { .. } => "validation_error",
Self::NotFound { .. } => "not_found",
Self::Conflict { .. } => "conflict",
Self::Internal { .. } => "internal_error",
}
}
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
match &self {
Self::Internal { message } => {
error!(error_code = self.code(), error_message = %message)
}
Self::Validation { message }
| Self::NotFound { message }
| Self::Conflict { message } => {
warn!(error_code = self.code(), error_message = %message)
}
}
let body = Json(json!({
"error": {
"code": self.code(),
"message": self.to_string(),
}
}));
(self.status_code(), body).into_response()
}
}
impl From<RegistryError> for ApiError {
fn from(value: RegistryError) -> Self {
match value {
RegistryError::OperationNotFound { operation_id } => {
Self::not_found(format!("operation {operation_id} was not found"))
}
RegistryError::OperationVersionNotFound {
operation_id,
version,
} => Self::not_found(format!(
"operation version {version} for {operation_id} was not found"
)),
RegistryError::AuthProfileNotFound { auth_profile_id } => {
Self::not_found(format!("auth profile {auth_profile_id} was not found"))
}
RegistryError::OperationAlreadyExists { operation_id } => {
Self::conflict(format!("operation {operation_id} already exists"))
}
RegistryError::InvalidInitialVersion { .. }
| RegistryError::InvalidVersionSequence { .. }
| RegistryError::ImmutableOperationFieldChanged { .. }
| RegistryError::InvalidEnumRepresentation { .. }
| RegistryError::InvalidNumericValue { .. }
| RegistryError::YamlImportJobNotFound { .. } => Self::validation(value.to_string()),
RegistryError::Storage(_) | RegistryError::Serialization(_) => {
Self::internal(value.to_string())
}
}
}
}
impl From<MappingError> for ApiError {
fn from(value: MappingError) -> Self {
Self::validation(value.to_string())
}
}
impl From<SchemaError> for ApiError {
fn from(value: SchemaError) -> Self {
Self::validation(value.to_string())
}
}
impl From<StorageError> for ApiError {
fn from(value: StorageError) -> Self {
match value {
StorageError::InvalidStorageRef { details } => Self::validation(details),
StorageError::Io(_) | StorageError::Serialization(_) => {
Self::internal(value.to_string())
}
}
}
}
pub fn runtime_test_failure(error: &RuntimeError) -> Value {
json!({
"code": runtime_test_failure_code(error),
"message": error.to_string()
})
}
fn runtime_test_failure_code(error: &RuntimeError) -> &'static str {
match error {
RuntimeError::Schema(_) => "runtime_schema_error",
RuntimeError::Mapping(_) => "runtime_mapping_error",
RuntimeError::GraphqlAdapter(_) => "runtime_graphql_error",
RuntimeError::GrpcAdapter(_) => "runtime_grpc_error",
RuntimeError::RestAdapter(_) => "runtime_rest_error",
RuntimeError::UnsupportedProtocol { .. } => "runtime_protocol_error",
RuntimeError::InvalidPreparedRequest { .. } => "runtime_request_error",
}
}