chore: use type aliases to avoid dependency injection bugs

This commit is contained in:
Aravinth Manivannan 2024-05-06 22:27:58 +05:30
parent 96ccb47aa8
commit c7f5619e7a
Signed by: realaravinth
GPG Key ID: F8F50389936984FF
12 changed files with 171 additions and 129 deletions

View File

@ -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

View File

@ -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<HttpResponse> {
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());
}
}

View File

@ -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);
}

View File

@ -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<Arc<dyn ForgeRepositoryInterface>>,
routes: web::Data<Arc<RoutesRepository>>,
template: web::Data<Arc<dyn LoginPageInterface>>,
) -> InUIResult<HttpResponse> {
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<Arc<dyn ForgeRepositoryInterface>>,
save_oauth_state_adapter: web::Data<Arc<dyn SaveOAuthState>>,
generate_random_string: web::Data<Arc<dyn GenerateRandomStringInterface>>,
routes: web::Data<Arc<RoutesRepository>>,
settings: web::Data<crate::settings::Settings>,
#[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<HttpResponse> {
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<dyn ForgeRepositoryInterface> = 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<dyn GenerateRandomStringInterface> = {
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<dyn OAuthAuthReqUri> = {
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<dyn SaveOAuthState> = {
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<dyn ForgeRepositoryInterface> = {
let mock_forges = {
let mock_oauth_req_uri: Arc<dyn OAuthAuthReqUri> = {
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;

View File

@ -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<Arc<dyn LoginPageInterface>>;

View File

@ -20,8 +20,8 @@ pub fn register_templates(t: &mut tera::Tera) {
}
pub fn load_templates(cfg: &mut web::ServiceConfig) {
let t: Arc<dyn LoginPageInterface> = 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)]

View File

@ -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);

View File

@ -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<Arc<dyn ForgeRepositoryInterface>>;
pub type WebSaveOauthState = web::Data<Arc<dyn SaveOAuthState>>;
pub type WebRouteRepository = web::Data<Arc<RoutesRepository>>;
pub type WebSettings = web::Data<crate::settings::Settings>;

View File

@ -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<dyn ForgeRepositoryInterface> = 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());
};

View File

@ -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<dyn SaveOAuthState>,
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<dyn RunMigrations> {
self.pg_adapter.migratable()
}
}

View File

@ -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()

View File

@ -4,6 +4,8 @@ use actix_web::web;
use mockall::predicate::*;
use mockall::*;
pub type WebGenerateRandomStringInterface = web::Data<Arc<dyn GenerateRandomStringInterface>>;
#[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<dyn GenerateRandomStringInterface> = 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)