Skip to content

Commit

Permalink
chore: finish all essential endpoints under the new schema (but much …
Browse files Browse the repository at this point in the history
…refactoring is still needed)
  • Loading branch information
simongoricar committed Sep 22, 2024
1 parent f8d47f6 commit ce2c834
Show file tree
Hide file tree
Showing 97 changed files with 6,612 additions and 2,413 deletions.
459 changes: 7 additions & 452 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ members = [
"kolomoni_migrations_core",
"kolomoni_migrations_macros",
"kolomoni_openapi",
"kolomoni_search",
# "kolomoni_search",
"kolomoni_test",
"kolomoni_test_util"
]
Expand Down Expand Up @@ -102,6 +102,7 @@ features = [
"postgres",
"uuid",
"chrono",
"json"
]


Expand Down Expand Up @@ -136,11 +137,12 @@ path = "./kolomoni/src/main.rs"


[dependencies]
kolomoni_core = { path = "./kolomoni_core" }
kolomoni_migrations = { path = "./kolomoni_migrations" }
kolomoni_configuration = { path = "./kolomoni_configuration" }
kolomoni_auth = { path = "./kolomoni_auth" }
kolomoni_database = { path = "./kolomoni_database" }
kolomoni_search = { path = "./kolomoni_search" }
# kolomoni_search = { path = "./kolomoni_search" }

clap = { workspace = true }

Expand All @@ -164,10 +166,13 @@ actix-cors = { workspace = true }

utoipa = { workspace = true }

sqlx = { workspace = true }

# sea-orm = { workspace = true }
# sea-orm-migration = { workspace = true }

dunce = { workspace = true }
uuid = { workspace = true }
chrono = { workspace = true }

itertools = { workspace = true }
Expand All @@ -178,6 +183,7 @@ paste = { workspace = true }



# TODO rename
[features]
with_test_facilities = []

Expand Down
2 changes: 2 additions & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[default.extend-words]
lik = "lik"
169 changes: 136 additions & 33 deletions kolomoni/src/api/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@
//! and ways to have those errors automatically turned into correct
//! HTTP error responses when returned as `Err(error)` from those functions.
use std::borrow::Cow;
use std::fmt::{Display, Formatter};

use actix_web::body::BoxBody;
use actix_web::http::StatusCode;
use actix_web::{HttpResponse, ResponseError};
use itertools::Itertools;
use kolomoni_auth::Permission;
use sea_orm::DbErr;
use kolomoni_auth::{JWTCreationError, Permission};
use kolomoni_database::entities::UserQueryError;
use kolomoni_database::QueryError;
use serde::Serialize;
use thiserror::Error;
use tracing::error;
use utoipa::ToSchema;

use super::macros::{KolomoniResponseBuilderJSONError, KolomoniResponseBuilderLMAError};
use crate::authentication::AuthenticatedUserError;


/// Simple JSON-encodable response containing a single field: a `reason`.
///
Expand Down Expand Up @@ -212,25 +217,33 @@ pub enum APIError {

/// Bad client request with a reason; will produce a `400 Bad Request`.
/// The `reason` will also be sent along in the response.
OtherClientError { reason: String },
OtherClientError { reason: Cow<'static, str> },

/// Internal error with a string reason.
/// Triggers a `500 Internal Server Error` (*doesn't leak the error through the API*).
InternalReason(String),

/// Internal error, constructed from an [`miette::Error`].
/// Triggers a `500 Internal Server Error` (*doesn't leak the error through the API*).
InternalError(miette::Error),
/// Triggers a `500 Internal Server Error` (**reason doesn't leak through the API**).
InternalErrorWithReason { reason: Cow<'static, str> },

/// Internal error, constructed from a boxed [`Error`].
/// Triggers a `500 Internal Server Error` (**error doesn't leak through the API**).
InternalGenericError {
#[from]
#[source]
error: Box<dyn std::error::Error>,
},

/// Internal error, constructed from an [`sea_orm::error::DbErr`].
/// Internal error, constructed from a [`sqlx::Error`].
/// Triggers a `500 Internal Server Error` (*doesn't leak the error through the API*).
InternalDatabaseError(DbErr),
InternalDatabaseError {
#[from]
#[source]
error: sqlx::Error,
},
}

impl APIError {
/// Initialize a new not found API error without a specific reason.
#[inline]
pub fn not_found() -> Self {
pub const fn not_found() -> Self {
Self::NotFound {
reason_response: None,
}
Expand All @@ -249,18 +262,18 @@ impl APIError {
/// a permission (or multiple permissions), but without clarification as to which those are.
#[allow(dead_code)]
#[inline]
pub fn missing_permission() -> Self {
pub const fn missing_permission() -> Self {
Self::NotEnoughPermissions {
missing_permission: None,
}
}

pub fn client_error<S>(reason: S) -> Self
where
S: Into<String>,
S: Into<Cow<'static, str>>,
{
Self::OtherClientError {
reason: reason.into(),
reason: Cow::from(reason.into()),
}
}

Expand All @@ -277,21 +290,36 @@ impl APIError {
/// some set of permissions.
#[inline]
#[allow(dead_code)]
pub fn missing_specific_permissions(permissions: Vec<Permission>) -> Self {
pub const fn missing_specific_permissions(permissions: Vec<Permission>) -> Self {
Self::NotEnoughPermissions {
missing_permission: Some(permissions),
}
}

pub fn internal_error<E>(error: E) -> Self
where
E: std::error::Error + 'static,
{
Self::InternalGenericError {
error: Box::new(error),
}
}

pub fn internal_database_error(error: sqlx::Error) -> Self {
Self::InternalDatabaseError { error }
}

/// Initialize a new internal API error using an internal reason string.
/// When constructing an HTTP response using this error variant, the **reason
/// is not leaked through the API.**
#[inline]
pub fn internal_reason<S>(reason: S) -> Self
pub fn internal_error_with_reason<S>(reason: S) -> Self
where
S: Into<String>,
S: Into<Cow<'static, str>>,
{
Self::InternalReason(reason.into())
Self::InternalErrorWithReason {
reason: reason.into(),
}
}
}

Expand Down Expand Up @@ -330,9 +358,19 @@ impl Display for APIError {
}
},
APIError::OtherClientError { reason } => write!(f, "Client error: {}", reason),
APIError::InternalReason(reason) => write!(f, "Internal error: {reason}."),
APIError::InternalError(error) => write!(f, "Internal error: {error}."),
APIError::InternalDatabaseError(error) => write!(f, "Internal database error: {error}."),
APIError::InternalErrorWithReason { reason } => write!(
f,
"Internal server error (with reason): {reason}."
),
APIError::InternalGenericError { error } => {
write!(f, "Internal server error (generic): {error:?}")
}
APIError::InternalDatabaseError { error } => {
write!(
f,
"Internal server error (database error): {error}."
)
}
}
}
}
Expand All @@ -344,9 +382,9 @@ impl ResponseError for APIError {
APIError::NotEnoughPermissions { .. } => StatusCode::FORBIDDEN,
APIError::NotFound { .. } => StatusCode::NOT_FOUND,
APIError::OtherClientError { .. } => StatusCode::BAD_REQUEST,
APIError::InternalReason(_) => StatusCode::INTERNAL_SERVER_ERROR,
APIError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR,
APIError::InternalDatabaseError(_) => StatusCode::INTERNAL_SERVER_ERROR,
APIError::InternalErrorWithReason { .. } => StatusCode::INTERNAL_SERVER_ERROR,
APIError::InternalGenericError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
APIError::InternalDatabaseError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
}
}

Expand All @@ -370,23 +408,23 @@ impl ResponseError for APIError {
reason: reason.to_string(),
})
}
APIError::InternalReason(error) => {
error!(error = error, "Internal error.");
APIError::InternalErrorWithReason { reason } => {
error!(error = %reason, "Internal database error (custom reason).");

HttpResponse::InternalServerError().finish()
}
APIError::InternalError(error) => {
APIError::InternalGenericError { error } => {
error!(
error = error.to_string(),
"Internal server error."
error = ?error,
"Internal server error (generic)."
);

HttpResponse::InternalServerError().finish()
}
APIError::InternalDatabaseError(error) => {
APIError::InternalDatabaseError { error } => {
error!(
error = error.to_string(),
"Internal database error.",
error = ?error,
"Internal server error (database error).",
);

HttpResponse::InternalServerError().finish()
Expand All @@ -396,6 +434,71 @@ impl ResponseError for APIError {
}


impl From<QueryError> for APIError {
fn from(value: QueryError) -> Self {
match value {
QueryError::SqlxError { error } => Self::InternalDatabaseError { error },
QueryError::ModelError { reason } => Self::InternalErrorWithReason { reason },
QueryError::DatabaseInconsistencyError { problem: reason } => {
Self::InternalErrorWithReason { reason }
}
}
}
}

impl From<UserQueryError> for APIError {
fn from(value: UserQueryError) -> Self {
match value {
UserQueryError::SqlxError { error } => Self::InternalDatabaseError { error },
UserQueryError::ModelError { reason } => Self::InternalErrorWithReason { reason },
UserQueryError::HasherError { error } => Self::InternalGenericError {
error: Box::new(error),
},
}
}
}

impl From<AuthenticatedUserError> for APIError {
fn from(value: AuthenticatedUserError) -> Self {
match value {
AuthenticatedUserError::QueryError { error } => Self::from(error),
}
}
}

impl From<JWTCreationError> for APIError {
fn from(value: JWTCreationError) -> Self {
match value {
JWTCreationError::JWTError { error } => Self::InternalGenericError {
error: Box::new(error),
},
}
}
}

impl From<KolomoniResponseBuilderJSONError> for APIError {
fn from(value: KolomoniResponseBuilderJSONError) -> Self {
match value {
KolomoniResponseBuilderJSONError::JsonError { error } => Self::InternalGenericError {
error: Box::new(error),
},
}
}
}

impl From<KolomoniResponseBuilderLMAError> for APIError {
fn from(value: KolomoniResponseBuilderLMAError) -> Self {
match value {
KolomoniResponseBuilderLMAError::JsonError { error } => Self::InternalGenericError {
error: Box::new(error),
},
}
}
}




/// Short for [`Result`]`<`[`HttpResponse`]`, `[`APIError`]`>`, intended to be used in most
/// places in handlers of the Stari Kolomoni API.
///
Expand Down
Loading

0 comments on commit ce2c834

Please sign in to comment.