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 = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.2.0",
|
||||
@ -361,6 +362,17 @@ dependencies = [
|
||||
"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]]
|
||||
name = "axum-test"
|
||||
version = "16.4.1"
|
||||
|
@ -9,7 +9,7 @@ edition = "2021"
|
||||
async-std = { version = "1.13.0" }
|
||||
async-trait = { version = "0.1.83" }
|
||||
authdb = { path = "../../authdb/" }
|
||||
axum = { version = "0.7.9" }
|
||||
axum = { version = "0.7.9", features = [ "macros" ] }
|
||||
futures = { version = "0.3.31" }
|
||||
include_dir = { version = "0.7.4" }
|
||||
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 serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{database::GameId, core::Core};
|
||||
use crate::{
|
||||
core::Core,
|
||||
database::GameId,
|
||||
types::{AppError, FatalError},
|
||||
};
|
||||
|
||||
use super::auth_required;
|
||||
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct CreateGameRequest {
|
||||
pub game_type: String,
|
||||
@ -18,16 +23,10 @@ pub async fn create_game(
|
||||
core: Core,
|
||||
headers: HeaderMap,
|
||||
req: CreateGameRequest,
|
||||
) -> (StatusCode, Json<Option<GameId>>) {
|
||||
println!("create game handler");
|
||||
) -> ResultExt<GameId, AppError, FatalError> {
|
||||
auth_required(core.clone(), headers, |user| async move {
|
||||
let game = core.create_game(&user.id, &req.game_type, &req.game_name).await;
|
||||
println!("create_game completed: {:?}", game);
|
||||
match game {
|
||||
ResultExt::Ok(game_id) => (StatusCode::OK, Json(Some(game_id))),
|
||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||
ResultExt::Fatal(fatal) => panic!("{}", fatal),
|
||||
}
|
||||
}).await
|
||||
core.create_game(&user.id, &req.game_type, &req.game_name)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,48 @@
|
||||
mod game_management;
|
||||
mod user_management;
|
||||
use axum::{http::StatusCode, Json};
|
||||
use futures::Future;
|
||||
pub use game_management::*;
|
||||
use typeshare::typeshare;
|
||||
pub use user_management::*;
|
||||
|
||||
use result_extended::ResultExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::core::Core;
|
||||
use crate::{
|
||||
core::Core,
|
||||
types::{AppError, FatalError},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct HealthCheck {
|
||||
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> {
|
||||
match core.status().await {
|
||||
ResultExt::Ok(s) => serde_json::to_vec(&HealthCheck {
|
||||
|
@ -3,7 +3,7 @@ use axum::{
|
||||
Json,
|
||||
};
|
||||
use futures::Future;
|
||||
use result_extended::{error, ok, ResultExt};
|
||||
use result_extended::{error, ok, return_error, ResultExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
@ -58,16 +58,14 @@ pub async fn auth_required<F, A, Fut>(
|
||||
core: Core,
|
||||
headers: HeaderMap,
|
||||
f: F,
|
||||
) -> (StatusCode, Json<Option<A>>)
|
||||
) -> ResultExt<A, AppError, FatalError>
|
||||
where
|
||||
F: FnOnce(User) -> Fut,
|
||||
Fut: Future<Output = (StatusCode, Json<Option<A>>)>,
|
||||
Fut: Future<Output = ResultExt<A, AppError, FatalError>>,
|
||||
{
|
||||
match check_session(&core, headers).await {
|
||||
ResultExt::Ok(Some(user)) => f(user).await,
|
||||
ResultExt::Ok(None) => (StatusCode::UNAUTHORIZED, Json(None)),
|
||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||
ResultExt::Fatal(err) => panic!("{}", err),
|
||||
match return_error!(check_session(&core, headers).await) {
|
||||
Some(user) => f(user).await,
|
||||
None => error(AppError::AuthFailed),
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,94 +73,64 @@ pub async fn admin_required<F, A, Fut>(
|
||||
core: Core,
|
||||
headers: HeaderMap,
|
||||
f: F,
|
||||
) -> (StatusCode, Json<Option<A>>)
|
||||
) -> ResultExt<A, AppError, FatalError>
|
||||
where
|
||||
F: FnOnce(User) -> Fut,
|
||||
Fut: Future<Output = (StatusCode, Json<Option<A>>)>,
|
||||
Fut: Future<Output = ResultExt<A, AppError, FatalError>>,
|
||||
{
|
||||
match check_session(&core, headers).await {
|
||||
ResultExt::Ok(Some(user)) => {
|
||||
match return_error!(check_session(&core, headers).await) {
|
||||
Some(user) => {
|
||||
if user.admin {
|
||||
f(user).await
|
||||
} else {
|
||||
(StatusCode::FORBIDDEN, Json(None))
|
||||
error(AppError::PermissionDenied)
|
||||
}
|
||||
}
|
||||
ResultExt::Ok(None) => (StatusCode::UNAUTHORIZED, Json(None)),
|
||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||
ResultExt::Fatal(err) => panic!("{}", err),
|
||||
None => error(AppError::AuthFailed),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn check_password(
|
||||
core: Core,
|
||||
req: Json<AuthRequest>,
|
||||
) -> (StatusCode, Json<Option<SessionId>>) {
|
||||
) -> ResultExt<SessionId, AppError, FatalError> {
|
||||
let Json(AuthRequest { username, password }) = req;
|
||||
match 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),
|
||||
}
|
||||
core.auth(&username, &password).await
|
||||
}
|
||||
|
||||
pub async fn get_user(
|
||||
core: Core,
|
||||
headers: HeaderMap,
|
||||
user_id: Option<UserId>,
|
||||
) -> (StatusCode, Json<Option<UserProfile>>) {
|
||||
) -> ResultExt<Option<UserProfile>, AppError, FatalError> {
|
||||
auth_required(core.clone(), headers, |user| async move {
|
||||
match user_id {
|
||||
Some(user_id) => match core.user(user_id).await {
|
||||
ResultExt::Ok(Some(user)) => (StatusCode::OK, Json(Some(user))),
|
||||
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![],
|
||||
})),
|
||||
),
|
||||
Some(user_id) => core.user(user_id).await,
|
||||
None => core.user(user.id).await,
|
||||
}
|
||||
})
|
||||
.await
|
||||
}).await
|
||||
}
|
||||
|
||||
pub async fn create_user(
|
||||
core: Core,
|
||||
headers: HeaderMap,
|
||||
req: CreateUserRequest,
|
||||
) -> (StatusCode, Json<Option<()>>) {
|
||||
) -> ResultExt<UserId, AppError, FatalError> {
|
||||
admin_required(core.clone(), headers, |_admin| async {
|
||||
match core.create_user(&req.username).await {
|
||||
ResultExt::Ok(_) => (StatusCode::OK, Json(None)),
|
||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||
ResultExt::Fatal(fatal) => panic!("{}", fatal),
|
||||
}
|
||||
})
|
||||
.await
|
||||
core.create_user(&req.username).await
|
||||
}).await
|
||||
}
|
||||
|
||||
pub async fn set_password(
|
||||
core: Core,
|
||||
headers: HeaderMap,
|
||||
req: SetPasswordRequest,
|
||||
) -> (StatusCode, Json<Option<()>>) {
|
||||
) -> ResultExt<(), AppError, FatalError> {
|
||||
auth_required(core.clone(), headers, |user| async {
|
||||
if req.password_1 == req.password_2 {
|
||||
match 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),
|
||||
}
|
||||
core.set_password(user.id, req.password_1).await
|
||||
} else {
|
||||
(StatusCode::BAD_REQUEST, Json(None))
|
||||
error(AppError::BadRequest)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}).await
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
use axum::{
|
||||
extract::Path,
|
||||
http::HeaderMap,
|
||||
http::{HeaderMap, StatusCode},
|
||||
routing::{get, post, put},
|
||||
Json, Router,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
core::Core,
|
||||
database::UserId,
|
||||
database::{SessionId, UserId},
|
||||
handlers::{
|
||||
check_password, create_game, create_user, get_user, healthcheck, set_password, AuthRequest,
|
||||
CreateGameRequest, CreateUserRequest, SetPasswordRequest,
|
||||
check_password, create_game, create_user, get_user, healthcheck, set_password,
|
||||
wrap_handler, AuthRequest, CreateGameRequest, CreateUserRequest, SetPasswordRequest,
|
||||
},
|
||||
};
|
||||
|
||||
@ -27,54 +27,54 @@ pub fn routes(core: Core) -> Router {
|
||||
"/api/v1/auth",
|
||||
post({
|
||||
let core = core.clone();
|
||||
move |req: Json<AuthRequest>| 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)
|
||||
}
|
||||
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| 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)]
|
||||
|
Loading…
Reference in New Issue
Block a user