Skip to content

Commit

Permalink
refactor: three-layer architecture (#38)
Browse files Browse the repository at this point in the history
* refactor: three-layer architecture

* skip uploading coverage to deepsource in main
  • Loading branch information
kentSarmiento committed Dec 27, 2023
1 parent 6103cb3 commit 2bc10c6
Show file tree
Hide file tree
Showing 28 changed files with 449 additions and 534 deletions.
1 change: 1 addition & 0 deletions .github/workflows/development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ jobs:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

- name: Upload coverage reports to DeepSource
if: github.event_name == 'pull_request'
run: |
curl https://deepsource.io/cli | sh
./bin/deepsource report --analyzer test-coverage --key rust --value-file ./lcov.info
Expand Down
85 changes: 80 additions & 5 deletions link-for-later/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use std::sync::Arc;
use std::{error, fmt, sync::Arc};

use axum::Router;

use crate::{
controller, repository,
repository::{DynLinks as DynLinksRepository, DynUsers as DynUsersRepository},
service::{self, DynLinks as DynLinksService, DynUsers as DynUsersService},
state::AppState,
types::Database,
};

Expand All @@ -26,9 +25,85 @@ pub fn new(db: Database) -> Router {
),
};

let state = AppState::new(links_service, users_service, links_repo, users_repo);
let state = State::new(links_service, users_service, links_repo, users_repo);
Router::new()
.merge(controller::links::routes(state.clone()))
.merge(controller::users::routes(state.clone()))
.merge(controller::routes::links::router(state.clone()))
.merge(controller::routes::users::router(state.clone()))
.with_state(state)
}

#[derive(Clone)]
pub struct State {
links_service: DynLinksService,
users_service: DynUsersService,
links_repo: DynLinksRepository,
users_repo: DynUsersRepository,
}

#[allow(clippy::must_use_candidate)]
impl State {
pub fn new(
links_service: DynLinksService,
users_service: DynUsersService,
links_repo: DynLinksRepository,
users_repo: DynUsersRepository,
) -> Self {
Self {
links_service,
users_service,
links_repo,
users_repo,
}
}

pub fn links_service(&self) -> &DynLinksService {
&self.links_service
}

pub fn users_service(&self) -> &DynUsersService {
&self.users_service
}

pub fn links_repo(&self) -> &DynLinksRepository {
&self.links_repo
}

pub fn users_repo(&self) -> &DynUsersRepository {
&self.users_repo
}
}

#[derive(Debug, PartialEq, Eq)]
pub enum Error {
LinkNotFound(String),
UserAlreadyExists(String),
UserNotFound(String),
IncorrectPassword(String),
Authorization(String),
Validation(String),
Database(String),
Server(String),

#[cfg(test)]
Test,
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::LinkNotFound(_) => write!(f, "link item not found"),
Self::UserAlreadyExists(_) => write!(f, "user already registered"),
Self::UserNotFound(_) => write!(f, "user not found"),
Self::IncorrectPassword(_) => write!(f, "incorrect password for user"),
Self::Authorization(_) => write!(f, "invalid authorization token"),
Self::Validation(_) => write!(f, "invalid request"),
Self::Database(_) => write!(f, "database error"),
Self::Server(_) => write!(f, "server error"),

#[cfg(test)]
Self::Test => write!(f, "test error"),
}
}
}

impl error::Error for Error {}
7 changes: 3 additions & 4 deletions link-for-later/src/controller.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pub mod auth;
pub mod error;
pub mod links;
pub mod users;
pub mod extractors;
pub mod responses;
pub mod routes;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use axum_extra::{
};
use jsonwebtoken::{decode, DecodingKey, Validation};

use crate::types::{auth::Claims, AppError};
use crate::{dto::Claims, types::AppError};

const JWT_SECRET_KEY: &str = "JWT_SECRET";

Expand All @@ -21,7 +21,7 @@ where
TypedHeader::<Authorization<Bearer>>::from_request_parts(parts, state)
.await
.map_err(|_| {
AppError::AuthorizationError(String::from("Authorization token not found"))
AppError::Authorization(String::from("Authorization token not found"))
})?;

let secret =
Expand All @@ -31,7 +31,7 @@ where
&DecodingKey::from_secret(secret.as_bytes()),
&Validation::default(),
)
.map_err(|e| AppError::AuthorizationError(format!("decode() {e:?}")))?;
.map_err(|e| AppError::Authorization(format!("decode() {e:?}")))?;

Ok(token_data.claims)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,6 @@ impl IntoResponse for AppError {
let error_message = self.to_string();
tracing::info!("{}", error_message);
let (status, error_message) = match self {
Self::ServerError(ref e) => {
tracing::debug!("{}: {}", error_message, e.to_string());
(StatusCode::INTERNAL_SERVER_ERROR, error_message)
}
Self::DatabaseError(ref e) => {
tracing::debug!("{}: {}", error_message, e.to_string());
(StatusCode::INTERNAL_SERVER_ERROR, error_message)
}
Self::LinkNotFound(ref e) => {
tracing::debug!("{}: {}", error_message, e.to_string());
(StatusCode::NOT_FOUND, error_message)
Expand All @@ -37,17 +29,25 @@ impl IntoResponse for AppError {
tracing::debug!("{}: {}", error_message, e.to_string());
(StatusCode::UNAUTHORIZED, error_message)
}
Self::AuthorizationError(ref e) => {
Self::Authorization(ref e) => {
tracing::debug!("{}: {}", error_message, e.to_string());
(StatusCode::UNAUTHORIZED, error_message)
}
Self::ValidationError(ref e) => {
Self::Validation(ref e) => {
tracing::debug!("{}: {}", error_message, e.to_string());
(StatusCode::BAD_REQUEST, error_message)
}
Self::Database(ref e) => {
tracing::debug!("{}: {}", error_message, e.to_string());
(StatusCode::INTERNAL_SERVER_ERROR, error_message)
}
Self::Server(ref e) => {
tracing::debug!("{}: {}", error_message, e.to_string());
(StatusCode::INTERNAL_SERVER_ERROR, error_message)
}

#[cfg(test)]
Self::TestError => (StatusCode::INTERNAL_SERVER_ERROR, error_message),
Self::Test => (StatusCode::INTERNAL_SERVER_ERROR, error_message),
};

let body = Json(json!({
Expand All @@ -66,13 +66,13 @@ mod tests {
#[test]
fn test_error_response() {
assert_eq!(
AppError::ServerError("a server operation failed".into())
AppError::Server("a server operation failed".into())
.into_response()
.status(),
StatusCode::INTERNAL_SERVER_ERROR
);
assert_eq!(
AppError::DatabaseError("a database operation failed".into())
AppError::Database("a database operation failed".into())
.into_response()
.status(),
StatusCode::INTERNAL_SERVER_ERROR
Expand Down Expand Up @@ -102,13 +102,13 @@ mod tests {
StatusCode::UNAUTHORIZED
);
assert_eq!(
AppError::AuthorizationError("an authorization error occurred".into())
AppError::Authorization("an authorization error occurred".into())
.into_response()
.status(),
StatusCode::UNAUTHORIZED
);
assert_eq!(
AppError::ValidationError("a validation error occurred".into())
AppError::Validation("a validation error occurred".into())
.into_response()
.status(),
StatusCode::BAD_REQUEST
Expand Down
2 changes: 2 additions & 0 deletions link-for-later/src/controller/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod links;
pub mod users;
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,12 @@ use axum::{
use validator::Validate;

use crate::{
state::AppState,
types::{
auth::Claims,
dto::{LinkItemRequest, LinkQueryBuilder},
entity::LinkItemBuilder,
AppError,
},
dto::{Claims, LinkItemRequest, LinkQueryBuilder},
entity::LinkItemBuilder,
types::{AppError, AppState},
};

pub fn routes(state: AppState) -> Router<AppState> {
pub fn router(state: AppState) -> Router<AppState> {
Router::new()
.nest(
"/v1",
Expand Down Expand Up @@ -50,7 +46,7 @@ async fn post(
match payload.validate() {
Ok(()) => {}
Err(e) => {
return AppError::ValidationError(format!("post_link() {e:?}")).into_response();
return AppError::Validation(format!("post_link() {e:?}")).into_response();
}
}

Expand Down Expand Up @@ -95,7 +91,7 @@ async fn put(
match payload.validate() {
Ok(()) => {}
Err(e) => {
return AppError::ValidationError(format!("put_link() {e:?}")).into_response();
return AppError::Validation(format!("put_link() {e:?}")).into_response();
}
}

Expand Down Expand Up @@ -141,13 +137,12 @@ mod tests {
use tracing_test::traced_test;

use crate::{
entity::LinkItem,
repository::{MockLinks as MockLinksRepo, MockUsers as MockUsersRepo},
service::{
DynLinks as DynLinksService, MockLinks as MockLinksService,
MockUsers as MockUsersService,
},
state::AppState,
types::{auth::Claims, entity::LinkItem},
};

use super::*;
Expand Down Expand Up @@ -214,7 +209,7 @@ mod tests {
.expect_search()
.withf(move |_, query| query == &repo_query)
.times(1)
.returning(|_, _| Err(AppError::TestError));
.returning(|_, _| Err(AppError::Test));

let app_state = AppStateBuilder::new(Arc::new(mock_links_service)).build();
let response = list(State(app_state), Claims::new("user-id", 0, 0)).await;
Expand Down Expand Up @@ -298,7 +293,7 @@ mod tests {
.expect_create()
.withf(move |_, item| item == &item_to_create)
.times(1)
.returning(|_, _| Err(AppError::TestError));
.returning(|_, _| Err(AppError::Test));

let app_state = AppStateBuilder::new(Arc::new(mock_links_service)).build();
let response = post(
Expand Down Expand Up @@ -361,7 +356,7 @@ mod tests {
.expect_get()
.withf(move |_, query| query == &repo_query)
.times(1)
.returning(|_, _| Err(AppError::TestError));
.returning(|_, _| Err(AppError::Test));

let app_state = AppStateBuilder::new(Arc::new(mock_links_service)).build();
let response = get(
Expand Down Expand Up @@ -458,7 +453,7 @@ mod tests {
.expect_update()
.withf(move |_, id, item| id == "1" && item == &item_to_update)
.times(1)
.returning(|_, _, _| Err(AppError::TestError));
.returning(|_, _, _| Err(AppError::Test));

let app_state = AppStateBuilder::new(Arc::new(mock_links_service)).build();
let response = put(
Expand Down Expand Up @@ -511,7 +506,7 @@ mod tests {
.expect_delete()
.withf(move |_, item| item == &item_to_delete)
.times(1)
.returning(|_, _| Err(AppError::TestError));
.returning(|_, _| Err(AppError::Test));

let app_state = AppStateBuilder::new(Arc::new(mock_links_service)).build();
let response = delete(
Expand Down
Loading

0 comments on commit 2bc10c6

Please sign in to comment.