From c7f5619e7ad09e60defe0ebeb626c0ae6dda9344 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Mon, 6 May 2024 22:27:58 +0530 Subject: [PATCH] chore: use type aliases to avoid dependency injection bugs --- .woodpecker.yml | 1 + .../input/web/login/handlers/login_page.rs | 66 +++++++++ .../adapter/input/web/login/handlers/mod.rs | 14 ++ .../request_oauth_authorization_forgejo.rs} | 135 ++++++------------ src/auth/adapter/input/web/login/mod.rs | 10 +- src/auth/adapter/input/web/login/template.rs | 4 +- src/auth/adapter/input/web/mod.rs | 3 +- src/auth/adapter/input/web/types.rs | 14 ++ src/auth/adapter/mod.rs | 18 ++- src/auth/adapter/out/db/mod.rs | 27 ---- src/main.rs | 2 +- src/utils/random_string.rs | 6 +- 12 files changed, 171 insertions(+), 129 deletions(-) create mode 100644 src/auth/adapter/input/web/login/handlers/login_page.rs create mode 100644 src/auth/adapter/input/web/login/handlers/mod.rs rename src/auth/adapter/input/web/login/{handler.rs => handlers/request_oauth_authorization_forgejo.rs} (53%) create mode 100644 src/auth/adapter/input/web/types.rs diff --git a/.woodpecker.yml b/.woodpecker.yml index 9bf989b..3405447 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -8,6 +8,7 @@ steps: commands: # - curl -fsSL https://deb.nodesource.com/setup_16.x | bash - &&\ # - apt update && apt-get -y --no-install-recommends install nodejs tar gpg curl wget + - rustup toolchain install nightly - rustup component add rustfmt - rustup component add clippy - make db.migrate diff --git a/src/auth/adapter/input/web/login/handlers/login_page.rs b/src/auth/adapter/input/web/login/handlers/login_page.rs new file mode 100644 index 0000000..40ded58 --- /dev/null +++ b/src/auth/adapter/input/web/login/handlers/login_page.rs @@ -0,0 +1,66 @@ +use actix_web::http::header::ContentType; +use actix_web::{get, web, HttpResponse}; + +use crate::auth::application::port::input::ui::errors::*; + +use super::template::LoginCtxFactory; +use super::types; + +#[get("/login")] +#[tracing::instrument(name = "login page handler", skip(forges, routes, template))] +async fn handler( + forges: types::WebForgeRepositoryInterface, + routes: types::WebRouteRepository, + template: super::WebLoginPageInterface, +) -> InUIResult { + let template_ctx = LoginCtxFactory::get_ctx(forges.get_supported_forges(), &routes); + let page = template.get_login_page(template_ctx).unwrap(); + Ok(HttpResponse::Ok() + .insert_header(ContentType::html()) + .body(page)) +} + +pub fn services(cfg: &mut web::ServiceConfig) { + cfg.service(handler); +} + +#[cfg(test)] +mod tests { + use super::*; + use actix_web::{http::header::ContentType, test, App}; + use std::sync::Arc; + + use crate::auth::adapter::input::web::login::template::load_templates; + use crate::auth::adapter::input::web::routes::RoutesRepository; + use crate::auth::adapter::out::forge::forge_repository::MockForgeRepositoryInterface; + use crate::auth::adapter::out::forge::SupportedForges; + + #[actix_web::test] + async fn test_login_page_handler() { + let mut mock_forges = MockForgeRepositoryInterface::default(); + mock_forges + .expect_get_supported_forges() + .return_const(vec![SupportedForges::Forgejo]) + .times(1); + let mock_forges = types::WebForgeRepositoryInterface::new(Arc::new(mock_forges)); + + let routes = types::WebRouteRepository::new(Arc::new(RoutesRepository::default())); + + let app = test::init_service( + App::new() + .app_data(mock_forges) + .app_data(routes) + .configure(load_templates) + .service(handler), + ) + .await; + + let req = test::TestRequest::get() + .uri("/login") + .insert_header(ContentType::html()) + .to_request(); + let resp = test::call_service(&app, req).await; + println!("{}", resp.status()); + assert!(resp.status().is_success()); + } +} diff --git a/src/auth/adapter/input/web/login/handlers/mod.rs b/src/auth/adapter/input/web/login/handlers/mod.rs new file mode 100644 index 0000000..36715fd --- /dev/null +++ b/src/auth/adapter/input/web/login/handlers/mod.rs @@ -0,0 +1,14 @@ +mod login_page; +mod request_oauth_authorization_forgejo; + +use actix_web::web; + +use super::adapter; +use super::template; +use super::types; +use super::WebLoginPageInterface; + +pub fn services(cfg: &mut web::ServiceConfig) { + login_page::services(cfg); + request_oauth_authorization_forgejo::services(cfg); +} diff --git a/src/auth/adapter/input/web/login/handler.rs b/src/auth/adapter/input/web/login/handlers/request_oauth_authorization_forgejo.rs similarity index 53% rename from src/auth/adapter/input/web/login/handler.rs rename to src/auth/adapter/input/web/login/handlers/request_oauth_authorization_forgejo.rs index e9c4084..9f2dfd0 100644 --- a/src/auth/adapter/input/web/login/handler.rs +++ b/src/auth/adapter/input/web/login/handlers/request_oauth_authorization_forgejo.rs @@ -1,40 +1,31 @@ -use std::sync::Arc; - -use actix_web::http::header::ContentType; -use actix_web::{get, post, web, HttpResponse}; +use actix_web::{post, web, HttpResponse}; use url::Url; -use crate::auth::adapter::input::web::routes::RoutesRepository; -use crate::auth::adapter::out::forge::{ - forge_repository::ForgeRepositoryInterface, SupportedForges, -}; +use super::types; +use crate::auth::adapter::out::forge::SupportedForges; use crate::auth::application::port::input::ui::{errors::*, login::RequestAuthorizationInterface}; -use crate::auth::application::port::out::db::save_oauth_state::SaveOAuthState; -use crate::utils::random_string::GenerateRandomStringInterface; -use super::template::{LoginCtxFactory, LoginPageInterface}; - -#[get("/login")] -#[tracing::instrument(name = "login page handler", skip(forges, routes, template))] -async fn login_page( - forges: web::Data>, - routes: web::Data>, - template: web::Data>, -) -> InUIResult { - let template_ctx = LoginCtxFactory::get_ctx(forges.get_supported_forges(), &routes); - let page = template.get_login_page(template_ctx).unwrap(); - Ok(HttpResponse::Ok() - .insert_header(ContentType::html()) - .body(page)) +pub fn services(cfg: &mut web::ServiceConfig) { + cfg.service(handler); } #[post("/oauth/forgejo/login")] -async fn request_oauth_authorization_forgejo( - forges: web::Data>, - save_oauth_state_adapter: web::Data>, - generate_random_string: web::Data>, - routes: web::Data>, - settings: web::Data, +#[tracing::instrument( + name = "login page handler", + skip( + forges, + save_oauth_state_adapter, + generate_random_string, + routes, + settings + ) +)] +async fn handler( + forges: types::WebForgeRepositoryInterface, + save_oauth_state_adapter: types::WebSaveOauthState, + generate_random_string: types::WebGenerateRandomStringInterface, + routes: types::WebRouteRepository, + settings: types::WebSettings, ) -> InUIResult { let oauth_auth_req_uri_adapter = &forges .get_forge_factory(&SupportedForges::Forgejo) @@ -47,10 +38,7 @@ async fn request_oauth_authorization_forgejo( &settings.server.domain, &routes.process_oauth_authorization_response(&SupportedForges::Forgejo) )) - .map_err(|_| { - println!("mistake"); - InUIError::InternalServerError - })?; + .map_err(|_| InUIError::InternalServerError)?; let web_adapter = super::adapter::RequestAuthorizationHandler::new( save_oauth_state_adapter.as_ref().to_owned(), @@ -64,57 +52,26 @@ async fn request_oauth_authorization_forgejo( .await } -pub fn services(cfg: &mut web::ServiceConfig) { - cfg.service(login_page); - cfg.service(request_oauth_authorization_forgejo); -} - #[cfg(test)] mod tests { + use std::sync::Arc; + use super::*; use actix_web::http::{header, StatusCode}; use actix_web::{http::header::ContentType, test, App}; - use crate::auth::adapter::input::web::login::template::load_templates; + use crate::auth::adapter::input::web::routes::RoutesRepository; use crate::auth::adapter::out::forge::forge_factory::{ ForgeAdapterFactoryInterface, MockForgeAdapterFactoryInterface, }; use crate::auth::adapter::out::forge::forge_repository::MockForgeRepositoryInterface; + use crate::auth::application::port::out::forge::oauth_auth_req_uri::OAuthAuthReqUri; use crate::auth::application::port::out::{ db::save_oauth_state::MockSaveOAuthState, forge::oauth_auth_req_uri::MockOAuthAuthReqUri, }; use crate::utils::random_string::*; - #[actix_web::test] - async fn test_login_page_handler() { - let mut mock_forges = MockForgeRepositoryInterface::default(); - mock_forges - .expect_get_supported_forges() - .return_const(vec![SupportedForges::Forgejo]) - .times(1); - let mock_forges: Arc = Arc::new(mock_forges); - - let routes = RoutesRepository::default(); - - let app = test::init_service( - App::new() - .app_data(web::Data::new(mock_forges)) - .app_data(web::Data::new(Arc::new(routes.clone()))) - .configure(load_templates) - .service(login_page), - ) - .await; - - let req = test::TestRequest::get() - .uri("/login") - .insert_header(ContentType::html()) - .to_request(); - let resp = test::call_service(&app, req).await; - println!("{}", resp.status()); - assert!(resp.status().is_success()); - } - #[actix_web::test] async fn test_ui_handler_request_oauth_authorization_forgejo() { let random_string = "foorand"; @@ -124,35 +81,35 @@ mod tests { let mut redirect_uri = url.clone(); redirect_uri.set_query(Some(&format!("state={random_string}"))); - let mock_random_generate_string: Arc = { + let mock_random_generate_string = { let mut mock_random_generate_string = MockGenerateRandomStringInterface::new(); mock_random_generate_string .expect_get_random() .times(1) .return_const(random_string.to_string()); - Arc::new(mock_random_generate_string) + WebGenerateRandomStringInterface::new(Arc::new(mock_random_generate_string)) }; - let mock_oauth_req_uri: Arc = { - let r = redirect_uri.clone(); - let mut mock_oauth_req_uri = MockOAuthAuthReqUri::new(); - mock_oauth_req_uri - .expect_oauth_auth_req_uri() - .times(1) - .returning(move |_, _| Ok(r.clone())); - Arc::new(mock_oauth_req_uri) - }; - - let mock_save_oauth_state: Arc = { + let mock_save_oauth_state = { let mut mock_save_oauth_state = MockSaveOAuthState::new(); mock_save_oauth_state .expect_save_oauth_state() .times(1) .returning(|_, _, _| Ok(())); - Arc::new(mock_save_oauth_state) + types::WebSaveOauthState::new(Arc::new(mock_save_oauth_state)) }; - let mock_forges: Arc = { + let mock_forges = { + let mock_oauth_req_uri: Arc = { + let r = redirect_uri.clone(); + let mut mock_oauth_req_uri = MockOAuthAuthReqUri::new(); + mock_oauth_req_uri + .expect_oauth_auth_req_uri() + .times(1) + .returning(move |_, _| Ok(r.clone())); + Arc::new(mock_oauth_req_uri) + }; + let mut mock_forge_factory = MockForgeAdapterFactoryInterface::default(); let a = mock_oauth_req_uri.clone(); mock_forge_factory @@ -168,20 +125,20 @@ mod tests { .times(1) .returning(move |_| Some(mock_forge_factory.clone())); - Arc::new(mock_forges) + types::WebForgeRepositoryInterface::new(Arc::new(mock_forges)) }; let routes = RoutesRepository::default(); let app = test::init_service( App::new() - .app_data(web::Data::new(mock_save_oauth_state.clone())) + .app_data(mock_save_oauth_state) .wrap(tracing_actix_web::TracingLogger::default()) - .app_data(web::Data::new(mock_random_generate_string)) - .app_data(web::Data::new(mock_forges)) + .app_data(mock_random_generate_string) + .app_data(mock_forges) .app_data(web::Data::new(Arc::new(routes.clone()))) .app_data(web::Data::new(settings.clone())) - .service(request_oauth_authorization_forgejo), + .service(handler), ) .await; diff --git a/src/auth/adapter/input/web/login/mod.rs b/src/auth/adapter/input/web/login/mod.rs index 609ed14..b8ef5ba 100644 --- a/src/auth/adapter/input/web/login/mod.rs +++ b/src/auth/adapter/input/web/login/mod.rs @@ -1,12 +1,18 @@ use actix_web::web; +use std::sync::Arc; mod adapter; -mod handler; +mod handlers; mod template; +use super::types; +use template::LoginPageInterface; + pub use template::register_templates; pub fn services(cfg: &mut web::ServiceConfig) { template::load_templates(cfg); - handler::services(cfg); + handlers::services(cfg); } + +pub type WebLoginPageInterface = web::Data>; diff --git a/src/auth/adapter/input/web/login/template.rs b/src/auth/adapter/input/web/login/template.rs index d94db15..9024c62 100644 --- a/src/auth/adapter/input/web/login/template.rs +++ b/src/auth/adapter/input/web/login/template.rs @@ -20,8 +20,8 @@ pub fn register_templates(t: &mut tera::Tera) { } pub fn load_templates(cfg: &mut web::ServiceConfig) { - let t: Arc = Arc::new(LoginPageTemplate); - cfg.app_data(web::Data::new(t)); + let t = super::WebLoginPageInterface::new(Arc::new(LoginPageTemplate)); + cfg.app_data(t); } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] diff --git a/src/auth/adapter/input/web/mod.rs b/src/auth/adapter/input/web/mod.rs index 88f16c7..95d50a4 100644 --- a/src/auth/adapter/input/web/mod.rs +++ b/src/auth/adapter/input/web/mod.rs @@ -5,11 +5,12 @@ use actix_web::web; pub mod login; mod routes; mod template; +pub mod types; use routes::RoutesRepository; pub fn load_ctx() -> impl FnOnce(&mut web::ServiceConfig) { - let routes = web::Data::new(Arc::new(RoutesRepository::default())); + let routes = types::WebRouteRepository::new(Arc::new(RoutesRepository::default())); let f = move |cfg: &mut web::ServiceConfig| { cfg.app_data(routes); diff --git a/src/auth/adapter/input/web/types.rs b/src/auth/adapter/input/web/types.rs new file mode 100644 index 0000000..e364e08 --- /dev/null +++ b/src/auth/adapter/input/web/types.rs @@ -0,0 +1,14 @@ +use std::sync::Arc; + +use actix_web::web; + +use crate::auth::adapter::out::forge::forge_repository::ForgeRepositoryInterface; +use crate::auth::application::port::out::db::save_oauth_state::SaveOAuthState; +pub(super) use crate::utils::random_string::WebGenerateRandomStringInterface; + +use super::RoutesRepository; + +pub type WebForgeRepositoryInterface = web::Data>; +pub type WebSaveOauthState = web::Data>; +pub type WebRouteRepository = web::Data>; +pub type WebSettings = web::Data; diff --git a/src/auth/adapter/mod.rs b/src/auth/adapter/mod.rs index 29f7c6d..44f811e 100644 --- a/src/auth/adapter/mod.rs +++ b/src/auth/adapter/mod.rs @@ -7,10 +7,10 @@ pub mod input; pub mod out; use crate::settings; -use out::db::DBAdapter; +use out::db::postgres::DBOutPostgresAdapter; use out::forge::{forge_repository::ForgeRepository, forgejo::Forgejo}; -use self::out::forge::forge_repository::ForgeRepositoryInterface; +use input::web::types; pub fn load_adapters( pool: PgPool, @@ -21,11 +21,19 @@ pub fn load_adapters( settings.forges.forgejo.client_id.clone(), settings.forges.forgejo.client_secret.clone(), ); - let forges: Arc = Arc::new(ForgeRepository::new(forgejo)); + + let forge_repository_interface = + types::WebForgeRepositoryInterface::new(Arc::new(ForgeRepository::new(forgejo))); + let db = DBOutPostgresAdapter::new(pool); + let save_oauth_state_adapter: types::WebSaveOauthState = + types::WebSaveOauthState::new(Arc::new(db.clone())); + + let s = types::WebSettings::new(settings.clone()); let f = move |cfg: &mut web::ServiceConfig| { - cfg.app_data(web::Data::new(Arc::new(DBAdapter::new(pool)))); - cfg.app_data(web::Data::new(forges.clone())); + cfg.app_data(save_oauth_state_adapter); + cfg.app_data(forge_repository_interface); + cfg.app_data(s); cfg.configure(input::web::load_ctx()); }; diff --git a/src/auth/adapter/out/db/mod.rs b/src/auth/adapter/out/db/mod.rs index 0f50f3a..26e9103 100644 --- a/src/auth/adapter/out/db/mod.rs +++ b/src/auth/adapter/out/db/mod.rs @@ -1,28 +1 @@ -use std::sync::Arc; - -use sqlx::PgPool; - -use crate::auth::application::port::out::db::save_oauth_state::SaveOAuthState; -use crate::db::migrate::RunMigrations; - pub mod postgres; - -#[derive(Clone)] -pub struct DBAdapter { - pub save_oauth_state_adapter: Arc, - pg_adapter: postgres::DBOutPostgresAdapter, -} - -impl DBAdapter { - pub fn new(pool: PgPool) -> Self { - let pg_adapter = postgres::DBOutPostgresAdapter::new(pool); - Self { - save_oauth_state_adapter: Arc::new(pg_adapter.clone()), - pg_adapter, - } - } - - pub fn migratable(&self) -> Arc { - self.pg_adapter.migratable() - } -} diff --git a/src/main.rs b/src/main.rs index 3c5b926..dca96d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,11 +31,11 @@ async fn main() { .wrap(tracing_actix_web::TracingLogger::default()) .wrap(middleware::Compress::default()) .app_data(actix_web::web::Data::new(settings.clone())) - .app_data(utils::random_string::GenerateRandomString::inject()) .wrap( middleware::DefaultHeaders::new().add(("Permissions-Policy", "interest-cohort=()")), ) .configure(auth::adapter::load_adapters(db.pool.clone(), &settings)) + .configure(utils::random_string::GenerateRandomString::inject()) }) .bind(&socket_addr) .unwrap() diff --git a/src/utils/random_string.rs b/src/utils/random_string.rs index cc5bf93..dfabdda 100644 --- a/src/utils/random_string.rs +++ b/src/utils/random_string.rs @@ -4,6 +4,8 @@ use actix_web::web; use mockall::predicate::*; use mockall::*; +pub type WebGenerateRandomStringInterface = web::Data>; + #[automock] pub trait GenerateRandomStringInterface: Send + Sync { fn get_random(&self, len: usize) -> String; @@ -28,9 +30,9 @@ impl GenerateRandomStringInterface for GenerateRandomString { impl GenerateRandomString { pub fn inject() -> impl FnOnce(&mut web::ServiceConfig) { - let g: Arc = Arc::new(GenerateRandomString); + let g = WebGenerateRandomStringInterface::new(Arc::new(GenerateRandomString)); let f = move |cfg: &mut web::ServiceConfig| { - cfg.app_data(web::Data::new(g)); + cfg.app_data(g); }; Box::new(f)