Handle all applications errors in one location
This commit is contained in:
parent
4dc7a50000
commit
dc8cb834e0
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -314,6 +314,7 @@ checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum-core",
|
"axum-core",
|
||||||
|
"axum-macros",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.2.0",
|
"http 1.2.0",
|
||||||
@ -361,6 +362,17 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-macros"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.87",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-test"
|
name = "axum-test"
|
||||||
version = "16.4.1"
|
version = "16.4.1"
|
||||||
|
@ -9,7 +9,7 @@ edition = "2021"
|
|||||||
async-std = { version = "1.13.0" }
|
async-std = { version = "1.13.0" }
|
||||||
async-trait = { version = "0.1.83" }
|
async-trait = { version = "0.1.83" }
|
||||||
authdb = { path = "../../authdb/" }
|
authdb = { path = "../../authdb/" }
|
||||||
axum = { version = "0.7.9" }
|
axum = { version = "0.7.9", features = [ "macros" ] }
|
||||||
futures = { version = "0.3.31" }
|
futures = { version = "0.3.31" }
|
||||||
include_dir = { version = "0.7.4" }
|
include_dir = { version = "0.7.4" }
|
||||||
lazy_static = { version = "1.5.0" }
|
lazy_static = { version = "1.5.0" }
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
use axum::{http::{HeaderMap, StatusCode}, Json};
|
use axum::{
|
||||||
|
http::{HeaderMap, StatusCode},
|
||||||
|
Json,
|
||||||
|
};
|
||||||
use result_extended::ResultExt;
|
use result_extended::ResultExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{database::GameId, core::Core};
|
use crate::{
|
||||||
|
core::Core,
|
||||||
|
database::GameId,
|
||||||
|
types::{AppError, FatalError},
|
||||||
|
};
|
||||||
|
|
||||||
use super::auth_required;
|
use super::auth_required;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct CreateGameRequest {
|
pub struct CreateGameRequest {
|
||||||
pub game_type: String,
|
pub game_type: String,
|
||||||
@ -18,16 +23,10 @@ pub async fn create_game(
|
|||||||
core: Core,
|
core: Core,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
req: CreateGameRequest,
|
req: CreateGameRequest,
|
||||||
) -> (StatusCode, Json<Option<GameId>>) {
|
) -> ResultExt<GameId, AppError, FatalError> {
|
||||||
println!("create game handler");
|
|
||||||
auth_required(core.clone(), headers, |user| async move {
|
auth_required(core.clone(), headers, |user| async move {
|
||||||
let game = core.create_game(&user.id, &req.game_type, &req.game_name).await;
|
core.create_game(&user.id, &req.game_type, &req.game_name)
|
||||||
println!("create_game completed: {:?}", game);
|
.await
|
||||||
match game {
|
})
|
||||||
ResultExt::Ok(game_id) => (StatusCode::OK, Json(Some(game_id))),
|
.await
|
||||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
|
||||||
ResultExt::Fatal(fatal) => panic!("{}", fatal),
|
|
||||||
}
|
|
||||||
}).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,48 @@
|
|||||||
mod game_management;
|
mod game_management;
|
||||||
mod user_management;
|
mod user_management;
|
||||||
|
use axum::{http::StatusCode, Json};
|
||||||
|
use futures::Future;
|
||||||
pub use game_management::*;
|
pub use game_management::*;
|
||||||
|
use typeshare::typeshare;
|
||||||
pub use user_management::*;
|
pub use user_management::*;
|
||||||
|
|
||||||
use result_extended::ResultExt;
|
use result_extended::ResultExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::core::Core;
|
use crate::{
|
||||||
|
core::Core,
|
||||||
|
types::{AppError, FatalError},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct HealthCheck {
|
pub struct HealthCheck {
|
||||||
pub ok: bool,
|
pub ok: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn wrap_handler<F, A, Fut>(f: F) -> (StatusCode, Json<Option<A>>)
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Fut,
|
||||||
|
Fut: Future<Output = ResultExt<A, AppError, FatalError>>,
|
||||||
|
{
|
||||||
|
match f().await {
|
||||||
|
ResultExt::Ok(val) => (StatusCode::OK, Json(Some(val))),
|
||||||
|
ResultExt::Err(AppError::BadRequest) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||||
|
ResultExt::Err(AppError::CouldNotCreateObject) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||||
|
ResultExt::Err(AppError::NotFound(_)) => (StatusCode::NOT_FOUND, Json(None)),
|
||||||
|
ResultExt::Err(AppError::Inaccessible(_)) => (StatusCode::NOT_FOUND, Json(None)),
|
||||||
|
ResultExt::Err(AppError::PermissionDenied) => (StatusCode::FORBIDDEN, Json(None)),
|
||||||
|
ResultExt::Err(AppError::AuthFailed) => (StatusCode::UNAUTHORIZED, Json(None)),
|
||||||
|
ResultExt::Err(AppError::JsonError(_)) => (StatusCode::INTERNAL_SERVER_ERROR, Json(None)),
|
||||||
|
ResultExt::Err(AppError::UnexpectedError(_)) => {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, Json(None))
|
||||||
|
}
|
||||||
|
ResultExt::Err(AppError::UsernameUnavailable) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||||
|
ResultExt::Fatal(err) => {
|
||||||
|
panic!("The server encountered a fatal error: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn healthcheck(core: Core) -> Vec<u8> {
|
pub async fn healthcheck(core: Core) -> Vec<u8> {
|
||||||
match core.status().await {
|
match core.status().await {
|
||||||
ResultExt::Ok(s) => serde_json::to_vec(&HealthCheck {
|
ResultExt::Ok(s) => serde_json::to_vec(&HealthCheck {
|
||||||
|
@ -3,7 +3,7 @@ use axum::{
|
|||||||
Json,
|
Json,
|
||||||
};
|
};
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use result_extended::{error, ok, ResultExt};
|
use result_extended::{error, ok, return_error, ResultExt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use typeshare::typeshare;
|
use typeshare::typeshare;
|
||||||
|
|
||||||
@ -58,16 +58,14 @@ pub async fn auth_required<F, A, Fut>(
|
|||||||
core: Core,
|
core: Core,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
f: F,
|
f: F,
|
||||||
) -> (StatusCode, Json<Option<A>>)
|
) -> ResultExt<A, AppError, FatalError>
|
||||||
where
|
where
|
||||||
F: FnOnce(User) -> Fut,
|
F: FnOnce(User) -> Fut,
|
||||||
Fut: Future<Output = (StatusCode, Json<Option<A>>)>,
|
Fut: Future<Output = ResultExt<A, AppError, FatalError>>,
|
||||||
{
|
{
|
||||||
match check_session(&core, headers).await {
|
match return_error!(check_session(&core, headers).await) {
|
||||||
ResultExt::Ok(Some(user)) => f(user).await,
|
Some(user) => f(user).await,
|
||||||
ResultExt::Ok(None) => (StatusCode::UNAUTHORIZED, Json(None)),
|
None => error(AppError::AuthFailed),
|
||||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
|
||||||
ResultExt::Fatal(err) => panic!("{}", err),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,94 +73,64 @@ pub async fn admin_required<F, A, Fut>(
|
|||||||
core: Core,
|
core: Core,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
f: F,
|
f: F,
|
||||||
) -> (StatusCode, Json<Option<A>>)
|
) -> ResultExt<A, AppError, FatalError>
|
||||||
where
|
where
|
||||||
F: FnOnce(User) -> Fut,
|
F: FnOnce(User) -> Fut,
|
||||||
Fut: Future<Output = (StatusCode, Json<Option<A>>)>,
|
Fut: Future<Output = ResultExt<A, AppError, FatalError>>,
|
||||||
{
|
{
|
||||||
match check_session(&core, headers).await {
|
match return_error!(check_session(&core, headers).await) {
|
||||||
ResultExt::Ok(Some(user)) => {
|
Some(user) => {
|
||||||
if user.admin {
|
if user.admin {
|
||||||
f(user).await
|
f(user).await
|
||||||
} else {
|
} else {
|
||||||
(StatusCode::FORBIDDEN, Json(None))
|
error(AppError::PermissionDenied)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResultExt::Ok(None) => (StatusCode::UNAUTHORIZED, Json(None)),
|
None => error(AppError::AuthFailed),
|
||||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
|
||||||
ResultExt::Fatal(err) => panic!("{}", err),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn check_password(
|
pub async fn check_password(
|
||||||
core: Core,
|
core: Core,
|
||||||
req: Json<AuthRequest>,
|
req: Json<AuthRequest>,
|
||||||
) -> (StatusCode, Json<Option<SessionId>>) {
|
) -> ResultExt<SessionId, AppError, FatalError> {
|
||||||
let Json(AuthRequest { username, password }) = req;
|
let Json(AuthRequest { username, password }) = req;
|
||||||
match core.auth(&username, &password).await {
|
core.auth(&username, &password).await
|
||||||
ResultExt::Ok(session_id) => (StatusCode::OK, Json(Some(session_id))),
|
|
||||||
ResultExt::Err(_err) => (StatusCode::UNAUTHORIZED, Json(None)),
|
|
||||||
ResultExt::Fatal(err) => panic!("Fatal: {}", err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_user(
|
pub async fn get_user(
|
||||||
core: Core,
|
core: Core,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
user_id: Option<UserId>,
|
user_id: Option<UserId>,
|
||||||
) -> (StatusCode, Json<Option<UserProfile>>) {
|
) -> ResultExt<Option<UserProfile>, AppError, FatalError> {
|
||||||
auth_required(core.clone(), headers, |user| async move {
|
auth_required(core.clone(), headers, |user| async move {
|
||||||
match user_id {
|
match user_id {
|
||||||
Some(user_id) => match core.user(user_id).await {
|
Some(user_id) => core.user(user_id).await,
|
||||||
ResultExt::Ok(Some(user)) => (StatusCode::OK, Json(Some(user))),
|
None => core.user(user.id).await,
|
||||||
ResultExt::Ok(None) => (StatusCode::NOT_FOUND, Json(None)),
|
|
||||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
|
||||||
ResultExt::Fatal(err) => panic!("{}", err),
|
|
||||||
},
|
|
||||||
None => (
|
|
||||||
StatusCode::OK,
|
|
||||||
Json(Some(UserProfile {
|
|
||||||
id: UserId::from(user.id),
|
|
||||||
name: user.name,
|
|
||||||
is_admin: user.admin,
|
|
||||||
games: vec![],
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
})
|
}).await
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_user(
|
pub async fn create_user(
|
||||||
core: Core,
|
core: Core,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
req: CreateUserRequest,
|
req: CreateUserRequest,
|
||||||
) -> (StatusCode, Json<Option<()>>) {
|
) -> ResultExt<UserId, AppError, FatalError> {
|
||||||
admin_required(core.clone(), headers, |_admin| async {
|
admin_required(core.clone(), headers, |_admin| async {
|
||||||
match core.create_user(&req.username).await {
|
core.create_user(&req.username).await
|
||||||
ResultExt::Ok(_) => (StatusCode::OK, Json(None)),
|
}).await
|
||||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
|
||||||
ResultExt::Fatal(fatal) => panic!("{}", fatal),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_password(
|
pub async fn set_password(
|
||||||
core: Core,
|
core: Core,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
req: SetPasswordRequest,
|
req: SetPasswordRequest,
|
||||||
) -> (StatusCode, Json<Option<()>>) {
|
) -> ResultExt<(), AppError, FatalError> {
|
||||||
auth_required(core.clone(), headers, |user| async {
|
auth_required(core.clone(), headers, |user| async {
|
||||||
if req.password_1 == req.password_2 {
|
if req.password_1 == req.password_2 {
|
||||||
match core.set_password(user.id, req.password_1).await {
|
core.set_password(user.id, req.password_1).await
|
||||||
ResultExt::Ok(_) => (StatusCode::OK, Json(None)),
|
|
||||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
|
||||||
ResultExt::Fatal(fatal) => panic!("{}", fatal),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
(StatusCode::BAD_REQUEST, Json(None))
|
error(AppError::BadRequest)
|
||||||
}
|
}
|
||||||
})
|
}).await
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::Path,
|
extract::Path,
|
||||||
http::HeaderMap,
|
http::{HeaderMap, StatusCode},
|
||||||
routing::{get, post, put},
|
routing::{get, post, put},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::Core,
|
core::Core,
|
||||||
database::UserId,
|
database::{SessionId, UserId},
|
||||||
handlers::{
|
handlers::{
|
||||||
check_password, create_game, create_user, get_user, healthcheck, set_password, AuthRequest,
|
check_password, create_game, create_user, get_user, healthcheck, set_password,
|
||||||
CreateGameRequest, CreateUserRequest, SetPasswordRequest,
|
wrap_handler, AuthRequest, CreateGameRequest, CreateUserRequest, SetPasswordRequest,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,54 +27,54 @@ pub fn routes(core: Core) -> Router {
|
|||||||
"/api/v1/auth",
|
"/api/v1/auth",
|
||||||
post({
|
post({
|
||||||
let core = core.clone();
|
let core = core.clone();
|
||||||
move |req: Json<AuthRequest>| check_password(core, req)
|
move |req: Json<AuthRequest>| wrap_handler(|| check_password(core, req))
|
||||||
}),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
// By default, just get the self user.
|
|
||||||
"/api/v1/user",
|
|
||||||
get({
|
|
||||||
let core = core.clone();
|
|
||||||
move |headers: HeaderMap| get_user(core, headers, None)
|
|
||||||
})
|
|
||||||
.put({
|
|
||||||
let core = core.clone();
|
|
||||||
move |headers: HeaderMap, req: Json<CreateUserRequest>| {
|
|
||||||
let Json(req) = req;
|
|
||||||
create_user(core, headers, req)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/api/v1/user/password",
|
|
||||||
put({
|
|
||||||
let core = core.clone();
|
|
||||||
move |headers: HeaderMap, req: Json<SetPasswordRequest>| {
|
|
||||||
let Json(req) = req;
|
|
||||||
set_password(core, headers, req)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/api/v1/user/:user_id",
|
|
||||||
get({
|
|
||||||
let core = core.clone();
|
|
||||||
move |user_id: Path<UserId>, headers: HeaderMap| {
|
|
||||||
let Path(user_id) = user_id;
|
|
||||||
get_user(core, headers, Some(user_id))
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/api/v1/games",
|
|
||||||
put({
|
|
||||||
let core = core.clone();
|
|
||||||
move |headers: HeaderMap, req: Json<CreateGameRequest>| {
|
|
||||||
let Json(req) = req;
|
|
||||||
create_game(core, headers, req)
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
// By default, just get the self user.
|
||||||
|
"/api/v1/user",
|
||||||
|
get({
|
||||||
|
let core = core.clone();
|
||||||
|
move |headers: HeaderMap| wrap_handler(|| get_user(core, headers, None))
|
||||||
|
})
|
||||||
|
.put({
|
||||||
|
let core = core.clone();
|
||||||
|
move |headers: HeaderMap, req: Json<CreateUserRequest>| {
|
||||||
|
let Json(req) = req;
|
||||||
|
wrap_handler(|| create_user(core, headers, req))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/v1/user/password",
|
||||||
|
put({
|
||||||
|
let core = core.clone();
|
||||||
|
move |headers: HeaderMap, req: Json<SetPasswordRequest>| {
|
||||||
|
let Json(req) = req;
|
||||||
|
wrap_handler(|| set_password(core, headers, req))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/v1/user/:user_id",
|
||||||
|
get({
|
||||||
|
let core = core.clone();
|
||||||
|
move |user_id: Path<UserId>, headers: HeaderMap| {
|
||||||
|
let Path(user_id) = user_id;
|
||||||
|
wrap_handler(|| get_user(core, headers, Some(user_id)))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/v1/games",
|
||||||
|
put({
|
||||||
|
let core = core.clone();
|
||||||
|
move |headers: HeaderMap, req: Json<CreateGameRequest>| {
|
||||||
|
let Json(req) = req;
|
||||||
|
wrap_handler(|| create_game(core, headers, req))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
Loading…
Reference in New Issue
Block a user