use axum::{
Json,
response::{IntoResponse as _, Response},
};
use axum_error_sets::{AideResponseFor, ErrorSet, IntoResponseWith, StatusResultExt as _};
use axum_error_sets::{
ResultSetExt as _,
code::{Conflict, InternalServerError, NotFound, Unauthorized},
};
use axum_typed_routing::api_route;
use http::StatusCode;
use rootcause::Report;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use type_sets::SupersetOf;
#[derive(Debug)]
struct AppError {
report: Option<Report>,
message: Option<String>,
}
impl AppError {
fn new(message: impl Into<String>) -> Self {
Self {
report: None,
message: Some(message.into()),
}
}
}
impl<T: Into<Report>> From<T> for AppError {
fn from(error: T) -> Self {
Self {
report: Some(error.into()),
message: None,
}
}
}
impl IntoResponseWith for AppError {
fn into_response_with(self, status: StatusCode) -> Response {
let message = self.message.unwrap_or_default();
(status, message).into_response()
}
}
impl AideResponseFor for AppError {
type Inner = String;
fn inferred_response_for(
_ctx: &mut aide::generate::GenContext,
_operation: &mut aide::openapi::Operation,
status: StatusCode,
) -> aide::openapi::Response {
aide::openapi::Response {
description: format!("Request failed with status {status}"),
..Default::default()
}
}
}
type ApiResult<T, E> = Result<T, ErrorSet<AppError, E>>;
fn find_user() -> ApiResult<User, (NotFound,)> {
Err(ErrorSet::new_with::<NotFound>(AppError::new(
"user not found",
)))
}
fn is_superset<T, R>()
where
T: SupersetOf<R>,
{
}
fn is_subset<T, R>()
where
T: type_sets::SubsetOf<R>,
{
}
fn update_user() -> ApiResult<User, (NotFound, Conflict)> {
find_user().into_superset()?;
check_conflict().into_conflict()?;
Ok(User)
}
#[api_route(PUT "/users/{id}")]
#[axum::debug_handler]
async fn update(
id: String,
) -> ApiResult<Json<User>, (Unauthorized, NotFound, Conflict, InternalServerError)> {
authenticate().into_unauthorized()?;
update_user().into_superset()?;
persist().into_internal()?;
Ok(Json(User))
}
fn repository_example() -> ApiResult<User, (NotFound,)> {
database_lookup().into_not_found().map_err(Into::into)
}
fn service_example() -> ApiResult<User, (NotFound, Conflict)> {
repository_example().into_superset()
}
fn subset_to_superset() -> ApiResult<User, (NotFound, Conflict)> {
let user: ApiResult<User, (NotFound,)> = find_user();
user.into_superset()
}
fn valid_status() -> ApiResult<(), (NotFound, Conflict)> {
check_conflict().into_conflict()?;
Ok(())
}
fn explicit_status() -> ApiResult<(), (NotFound,)> {
database_lookup().into_not_found()?;
Ok(())
}
fn propagated_status() -> ApiResult<(), (NotFound,)> {
find_user()?;
Ok(())
}
fn database_lookup() -> std::result::Result<User, AppError> {
Err(AppError::new("user not found"))
}
fn check_conflict() -> std::result::Result<(), AppError> {
Ok(())
}
fn authenticate() -> std::result::Result<(), AppError> {
Ok(())
}
fn persist() -> std::result::Result<(), AppError> {
Ok(())
}
#[derive(JsonSchema, Serialize, Deserialize)]
struct User;
fn main() {}