use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct ProblemJson {
#[serde(rename = "type")]
pub type_url: String,
pub title: String,
pub status: u16,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub instance: Option<String>,
}
impl ProblemJson {
fn new(type_suffix: &str, title: &str, status: u16) -> Self {
Self {
type_url: format!("https://nido.local/errors/{type_suffix}"),
title: title.to_owned(),
status,
detail: None,
instance: None,
}
}
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
self.detail = Some(detail.into());
self
}
pub fn with_instance(mut self, instance: impl Into<String>) -> Self {
self.instance = Some(instance.into());
self
}
}
#[derive(Debug, thiserror::Error)]
pub enum NidoSvcError {
#[error("not found: {0}")]
NotFound(String),
#[error("invalid argument: {0}")]
InvalidArgument(String),
#[error("unauthorized")]
Unauthorized,
#[error("internal error: {0}")]
Internal(#[from] anyhow::Error),
}
impl NidoSvcError {
fn to_problem(&self) -> (StatusCode, ProblemJson) {
match self {
NidoSvcError::NotFound(msg) => (
StatusCode::NOT_FOUND,
ProblemJson::new("not-found", "Not Found", 404).with_detail(msg.clone()),
),
NidoSvcError::InvalidArgument(msg) => (
StatusCode::BAD_REQUEST,
ProblemJson::new("invalid-argument", "Bad Request", 400).with_detail(msg.clone()),
),
NidoSvcError::Unauthorized => (
StatusCode::UNAUTHORIZED,
ProblemJson::new("unauthorized", "Unauthorized", 401),
),
NidoSvcError::Internal(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
ProblemJson::new("internal-error", "Internal Server Error", 500)
.with_detail(e.to_string()),
),
}
}
}
impl IntoResponse for NidoSvcError {
fn into_response(self) -> Response {
let (status, body) = self.to_problem();
(status, Json(body)).into_response()
}
}