How to implement uniform custom error? #280
-
I already opened issue #276, but decided to move here 😅. I want to implement uniform error. I implemented use poem::{
error::{MethodNotAllowedError, NotFoundError},
Endpoint, IntoResponse, Middleware, Request, Response, Result,
};
use poem_openapi::error::{ContentTypeError, ParseRequestPayloadError};
use crate::common::AppError;
pub struct ErrorMiddleware;
impl<E: Endpoint> Middleware<E> for ErrorMiddleware {
type Output = ErrorMiddlewareImpl<E>;
fn transform(&self, ep: E) -> Self::Output {
ErrorMiddlewareImpl { ep }
}
}
pub struct ErrorMiddlewareImpl<E> {
ep: E,
}
#[poem::async_trait]
impl<E: Endpoint> Endpoint for ErrorMiddlewareImpl<E> {
type Output = Response;
async fn call(&self, req: Request) -> Result<Self::Output> {
let uri = req.uri().to_string();
let resp = self.ep.call(req).await;
match resp {
Ok(resp) => Ok(resp.into_response()),
Err(e) => {
if e.is::<NotFoundError>() {
return Ok(AppError::ResourceNotFound(uri).into_response());
}
if e.is::<ParseRequestPayloadError>() {
return Ok(AppError::MalformedRequestPayload.into_response());
}
if e.is::<MethodNotAllowedError>() {
return Ok(AppError::MethodNotAllowed.into_response());
}
if e.is::<ContentTypeError>() {
match e.downcast::<ContentTypeError>().unwrap() {
ContentTypeError::ExpectContentType => {
return Ok(AppError::MissingContentType.into_response());
}
ContentTypeError::NotSupported { content_type } => {
return Ok(
AppError::UnsupportedContentType(content_type).into_response()
);
}
}
}
Ok(AppError::BadRequest(e.to_string()).into_response())
}
}
}
} use bcrypt::BcryptError;
use jsonwebtoken::errors::Error as JwtError;
use poem::{
error::ResponseError, http::header::CONTENT_TYPE, http::StatusCode, Body, IntoResponse,
Response,
};
use serde_json::json;
use sqlx::Error as SqlxError;
use thiserror::Error as ThisError;
#[derive(Debug, ThisError)]
pub enum AppError {
// 400
#[error("{0}")]
BadRequest(String),
#[error("Method not allowed.")]
MethodNotAllowed,
#[error("Missing request content type.")]
MissingContentType,
#[error("Request content type `{0}` unsupported.")]
UnsupportedContentType(String),
#[error("Malformed request payload.")]
MalformedRequestPayload,
// 401
#[error("Invalid access token.")]
InvalidAccessToken,
#[error("Missing access token.")]
MissingAccessToken,
#[error("Access token expired.")]
AccessTokenExpired,
#[error("Invalid credentials.")]
InvalidCredentials,
#[error("Superuser scope required.")]
SuperuserScopeRequired,
// 404
#[error("Object not found.")]
ObjectNotFound,
#[error("Resource at `{0}` not found.")]
ResourceNotFound(String),
// 500
#[error("Object already exists.")]
ObjectAlreadyExists,
#[error("Database returned error code: {0}.")]
DatabaseError(String),
#[error("Internal server error.")]
InternalError,
#[error("Database connection pool timed out.")]
PoolTimedOut,
}
impl From<SqlxError> for AppError {
fn from(e: SqlxError) -> Self {
match e {
SqlxError::PoolTimedOut => AppError::PoolTimedOut,
SqlxError::RowNotFound => AppError::ObjectNotFound,
SqlxError::Database(e) => match e.code() {
Some(code) => {
if code == "23505" {
return AppError::ObjectAlreadyExists;
}
AppError::DatabaseError(code.to_string())
}
None => AppError::InternalError,
},
_ => AppError::InternalError,
}
}
}
impl From<BcryptError> for AppError {
fn from(_: BcryptError) -> Self {
AppError::InternalError
}
}
impl From<JwtError> for AppError {
fn from(_: JwtError) -> Self {
AppError::InvalidAccessToken
}
}
impl ResponseError for AppError {
fn status(&self) -> StatusCode {
match self {
// 400
AppError::BadRequest(_)
| AppError::MethodNotAllowed
| AppError::MissingContentType
| AppError::UnsupportedContentType(_)
| AppError::MalformedRequestPayload => StatusCode::BAD_REQUEST,
// 401
AppError::InvalidAccessToken
| AppError::MissingAccessToken
| AppError::AccessTokenExpired
| AppError::InvalidCredentials
| AppError::SuperuserScopeRequired => StatusCode::UNAUTHORIZED,
// 404
AppError::ObjectNotFound | AppError::ResourceNotFound(_) => StatusCode::NOT_FOUND,
// 500
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn as_response(&self) -> Response {
build_response_error(self.status(), self.to_string())
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
build_response_error(self.status(), self.to_string())
}
}
fn build_response_error(status: StatusCode, reason: String) -> Response {
let body = Body::from_json(json!({
"status": "error".to_string(),
"reason": reason,
}))
.unwrap();
Response::builder()
.header(CONTENT_TYPE, "application/json; charset=utf-8")
.status(status)
.body(body)
} |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 7 replies
-
Hey :) Not sure if it's exactly what you are lookinng for, but take a look at |
Beta Was this translation helpful? Give feedback.
-
I found my errors :
let app = Route::new()
.nest("/api", api)
.nest("/", ui)
.with(middleware::ErrorMiddleware)
.with(Cors::new())
.data(pool); #[poem::async_trait]
impl<E: Endpoint> Endpoint for ErrorMiddlewareImpl<E> {
type Output = Response;
async fn call(&self, req: Request) -> Result<Self::Output> {
let method = req.method().clone();
let uri = req.uri().clone();
let start = Instant::now();
let resp = self.ep.call(req).await;
let elapsed = start.elapsed();
match resp {
Ok(resp) => {
let resp = resp.into_response();
println!(
"{} -> {} {} - {} ms",
method,
uri,
resp.status().as_u16(),
elapsed.as_millis()
);
Ok(resp)
}
Err(e) => {
if e.is::<poem::error::NotFoundError>() {
return Ok(AppError::ResourceNotFound(uri.to_string()).into_response());
}
if e.is::<poem::error::MethodNotAllowedError>() {
return Ok(AppError::MethodNotAllowed.into_response());
}
if e.is::<poem_openapi::error::ParseRequestPayloadError>() {
return Ok(AppError::MalformedRequestPayload.into_response());
}
if e.is::<poem_openapi::error::ContentTypeError>() {
match e
.downcast::<poem_openapi::error::ContentTypeError>()
.unwrap()
{
poem_openapi::error::ContentTypeError::ExpectContentType => {
return Ok(AppError::MissingContentType.into_response());
}
poem_openapi::error::ContentTypeError::NotSupported { content_type } => {
return Ok(
AppError::UnsupportedContentType(content_type).into_response()
);
}
}
}
// Ok(AppError::BadRequest(e.to_string()).into_response())
Ok(e.into_response())
}
}
}
} |
Beta Was this translation helpful? Give feedback.
I found my errors :
ErrorMiddleware
should beforeCors
: