Improve API error handling

This commit is contained in:
Timo Ley 2022-01-02 17:25:23 +01:00
parent c90d0b70b9
commit 0977a5ffcb
4 changed files with 41 additions and 32 deletions

View file

@ -22,4 +22,5 @@ new_mime_guess = "3.0.2"
headers = "0.3.5"
url = {version = "2.2.2", features = ["serde"]}
askama = "0.10"
urlencoding = "2.1.0"
urlencoding = "2.1.0"
thiserror = "1.0.30"

View file

@ -2,43 +2,50 @@ use std::convert::Infallible;
use axum::{
body::{Bytes, Full},
extract::multipart::MultipartError,
response::IntoResponse,
Json,
};
use reqwest::StatusCode;
use thiserror::Error;
use super::models::ErrorResponse;
#[derive(Error, Debug)]
pub enum APIError {
#[error("SQL error: {0}")]
Sql(#[from] sqlx::Error),
#[error("Multipart form error: {0}")]
Multipart(#[from] MultipartError),
#[error("Bad request: {0}")]
BadRequest(String),
}
impl ErrorResponse {
fn new(status: StatusCode, message: Option<String>) -> Self {
ErrorResponse {
let reason = status.canonical_reason().unwrap_or_default();
Self {
status,
error: message,
error: message.unwrap_or(reason.to_string()),
}
}
}
impl IntoResponse for ErrorResponse {
impl IntoResponse for APIError {
type Body = Full<Bytes>;
type BodyError = Infallible;
fn into_response(self) -> axum::http::Response<Self::Body> {
let status = self.status.clone();
(status, Json(self)).into_response()
}
}
impl From<sqlx::Error> for ErrorResponse {
fn from(err: sqlx::Error) -> Self {
match err {
sqlx::Error::RowNotFound => {
Self::new(StatusCode::NOT_FOUND, Some("Not found".to_string()))
}
_ => Self::new(
StatusCode::INTERNAL_SERVER_ERROR,
Some("Internal Server Error".to_string()),
),
}
let res = match self {
APIError::Sql(err) => match err {
sqlx::Error::RowNotFound => ErrorResponse::new(StatusCode::NOT_FOUND, None),
_ => ErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR, None),
},
APIError::Multipart(_) => ErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR, None),
APIError::BadRequest(err) => ErrorResponse::new(StatusCode::BAD_REQUEST, Some(err)),
};
let status = res.status.clone();
(status, Json(res)).into_response()
}
}

View file

@ -88,7 +88,7 @@ pub struct UploadResponse {
pub struct ErrorResponse {
#[serde(serialize_with = "serialize_status")]
pub status: StatusCode,
pub error: Option<String>,
pub error: String,
}
//Query

View file

@ -1,4 +1,5 @@
use crate::config::ConfVars;
use crate::ipfs::IPFSFile;
use crate::v1::models::*;
use axum::extract::{ContentLengthLimit, Extension, Multipart, Query};
use axum::handler::{get, post};
@ -7,11 +8,13 @@ use axum::routing::BoxRoute;
use axum::{Json, Router};
use sqlx::MySqlPool;
use super::error::APIError;
async fn meme(
params: Query<MemeIDQuery>,
Extension(db_pool): Extension<MySqlPool>,
Extension(vars): Extension<ConfVars>,
) -> Result<impl IntoResponse, ErrorResponse> {
) -> Result<impl IntoResponse, APIError> {
let meme = Meme::get(params.id, &db_pool, vars.cdn).await?;
Ok(Json(MemeResponse {
status: 200,
@ -24,7 +27,7 @@ async fn memes(
params: Query<MemeFilterQuery>,
Extension(db_pool): Extension<MySqlPool>,
Extension(vars): Extension<ConfVars>,
) -> Result<impl IntoResponse, ErrorResponse> {
) -> Result<impl IntoResponse, APIError> {
let memes = Meme::get_all(params.0, &db_pool, vars.cdn).await?;
Ok(Json(MemesResponse {
status: 200,
@ -36,7 +39,7 @@ async fn memes(
async fn category(
params: Query<IDQuery>,
Extension(db_pool): Extension<MySqlPool>,
) -> Result<impl IntoResponse, ErrorResponse> {
) -> Result<impl IntoResponse, APIError> {
let category = Category::get(&params.id, &db_pool).await?;
Ok(Json(CategoryResponse {
status: 200,
@ -47,7 +50,7 @@ async fn category(
async fn categories(
Extension(db_pool): Extension<MySqlPool>,
) -> Result<impl IntoResponse, ErrorResponse> {
) -> Result<impl IntoResponse, APIError> {
let categories = Category::get_all(&db_pool).await?;
Ok(Json(CategoriesResponse {
status: 200,
@ -59,7 +62,7 @@ async fn categories(
async fn user(
params: Query<UserIDQuery>,
Extension(db_pool): Extension<MySqlPool>,
) -> Result<impl IntoResponse, ErrorResponse> {
) -> Result<impl IntoResponse, APIError> {
let user = User::get(params.0, &db_pool).await?;
Ok(Json(UserResponse {
status: 200,
@ -68,9 +71,7 @@ async fn user(
}))
}
async fn users(
Extension(db_pool): Extension<MySqlPool>,
) -> Result<impl IntoResponse, ErrorResponse> {
async fn users(Extension(db_pool): Extension<MySqlPool>) -> Result<impl IntoResponse, APIError> {
let users = User::get_all(&db_pool).await?;
Ok(Json(UsersResponse {
status: 200,
@ -83,7 +84,7 @@ async fn random(
params: Query<MemeFilterQuery>,
Extension(db_pool): Extension<MySqlPool>,
Extension(vars): Extension<ConfVars>,
) -> Result<impl IntoResponse, ErrorResponse> {
) -> Result<impl IntoResponse, APIError> {
let random = Meme::get_random(params.0, &db_pool, vars.cdn).await?;
Ok(Json(MemeResponse {
status: 200,
@ -96,8 +97,8 @@ async fn upload(
ContentLengthLimit(mut form): ContentLengthLimit<Multipart, { 1024 * 1024 * 1024 }>,
Extension(db_pool): Extension<MySqlPool>,
Extension(vars): Extension<ConfVars>,
) -> impl IntoResponse {
todo!();
) -> Result<impl IntoResponse, APIError> {
Ok(())
}
//TODO: Implement upload endpoint