From 68d63bba07ba8a01ca2bd3f810c99608fda24040 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Wed, 27 Apr 2022 10:52:24 +0530 Subject: [PATCH] chore: load app settings via app ctx and do away with global static loader --- Cargo.toml | 2 +- src/ctx.rs | 30 ++++++++++++++++++++++++++++++ src/deploy.rs | 29 ++++++++++++----------------- src/errors.rs | 7 ++++--- src/main.rs | 25 +++++++++++++++---------- src/meta.rs | 20 ++++++++++---------- src/settings.rs | 15 +++++---------- 7 files changed, 77 insertions(+), 51 deletions(-) create mode 100644 src/ctx.rs diff --git a/Cargo.toml b/Cargo.toml index b7a8c54..6cc0fff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ my-codegen = {package = "actix-web-codegen", git ="https://github.com/realaravin config = "0.13" git2 = "0.14.2" -serde = { version = "1", features = ["derive"]} +serde = { version = "1", features = ["derive", "rc"]} serde_json = "1" pretty_env_logger = "0.4" diff --git a/src/ctx.rs b/src/ctx.rs new file mode 100644 index 0000000..27ddb9f --- /dev/null +++ b/src/ctx.rs @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +use std::sync::Arc; + +use crate::settings::Settings; + +#[derive(Clone)] +pub struct Ctx { + pub settings: Settings, +} + +impl Ctx { + pub fn new(settings: Settings) -> Arc { + Arc::new(Self { settings }) + } +} diff --git a/src/deploy.rs b/src/deploy.rs index cafbe00..11af586 100644 --- a/src/deploy.rs +++ b/src/deploy.rs @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; use tokio::sync::oneshot; use crate::errors::*; -use crate::SETTINGS; +use crate::AppCtx; pub mod routes { pub struct Deploy { @@ -42,11 +42,12 @@ pub struct DeployEvent { } #[my_codegen::post(path = "crate::V1_API_ROUTES.deploy.update")] -async fn update(payload: web::Json) -> ServiceResult { - for page in SETTINGS.pages.iter() { +async fn update(payload: web::Json, ctx: AppCtx) -> ServiceResult { + for page in ctx.settings.pages.iter() { if page.secret == payload.secret { let (tx, rx) = oneshot::channel(); - web::block(|| { + let page = page.clone(); + web::block(move || { tx.send(page.update()).unwrap(); }) .await @@ -65,18 +66,18 @@ pub fn services(cfg: &mut web::ServiceConfig) { #[cfg(test)] mod tests { - use actix_web::{http::StatusCode, test, App}; + use actix_web::{http::StatusCode, test}; - use crate::services; + use crate::tests; use crate::*; use super::*; #[actix_rt::test] async fn deploy_update_works() { - let app = test::init_service(App::new().configure(services)).await; - - let page = SETTINGS.pages.get(0); + let ctx = tests::get_data().await; + let app = get_app!(ctx).await; + let page = ctx.settings.pages.get(0); let page = page.unwrap(); let mut payload = DeployEvent { @@ -86,10 +87,7 @@ mod tests { let resp = test::call_service( &app, - test::TestRequest::post() - .uri(V1_API_ROUTES.deploy.update) - .set_json(&payload) - .to_request(), + post_request!(&payload, V1_API_ROUTES.deploy.update).to_request(), ) .await; assert_eq!(resp.status(), StatusCode::OK); @@ -98,10 +96,7 @@ mod tests { let resp = test::call_service( &app, - test::TestRequest::post() - .uri(V1_API_ROUTES.deploy.update) - .set_json(&payload) - .to_request(), + post_request!(&payload, V1_API_ROUTES.deploy.update).to_request(), ) .await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); diff --git a/src/errors.rs b/src/errors.rs index e819c0c..0d23bc8 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,6 +17,7 @@ //! represents all the ways a trait can fail using this crate use std::convert::From; use std::io::Error as FSErrorInner; +use std::sync::Arc; use actix_web::{ error::ResponseError, @@ -90,7 +91,7 @@ pub enum ServiceError { _0, _1 )] - PathTaken(Page, Page), + PathTaken(Arc, Arc), /// when the a Secret configured for a page is already taken #[display( @@ -98,7 +99,7 @@ pub enum ServiceError { _0, _1 )] - SecretTaken(Page, Page), + SecretTaken(Arc, Arc), /// when the a Repository URL configured for a page is already taken #[display( @@ -106,7 +107,7 @@ pub enum ServiceError { _0, _1 )] - DuplicateRepositoryURL(Page, Page), + DuplicateRepositoryURL(Arc, Arc), #[display(fmt = "File System Error {}", _0)] FSError(FSError), diff --git a/src/main.rs b/src/main.rs index b7144ab..404f893 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,28 +15,27 @@ * along with this program. If not, see . */ use std::env; +use std::sync::Arc; use actix_web::{ - error::InternalError, http::StatusCode, middleware as actix_middleware, web::JsonConfig, App, - HttpServer, + error::InternalError, http::StatusCode, middleware as actix_middleware, web::Data as WebData, + web::JsonConfig, App, HttpServer, }; -use lazy_static::lazy_static; use log::info; +mod ctx; mod deploy; mod errors; mod meta; mod page; mod routes; mod settings; +#[cfg(test)] +mod tests; pub use routes::ROUTES as V1_API_ROUTES; pub use settings::Settings; -lazy_static! { - pub static ref SETTINGS: Settings = Settings::new().unwrap(); -} - pub const CACHE_AGE: u32 = 604800; pub const GIT_COMMIT_HASH: &str = env!("GIT_HASH"); @@ -45,6 +44,8 @@ pub const PKG_NAME: &str = env!("CARGO_PKG_NAME"); pub const PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); pub const PKG_HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE"); +pub type AppCtx = WebData>; + #[cfg(not(tarpaulin_include))] #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -55,6 +56,9 @@ async fn main() -> std::io::Result<()> { } } + let settings = Settings::new().unwrap(); + let ctx = WebData::new(ctx::Ctx::new(settings.clone())); + pretty_env_logger::init(); info!( @@ -62,12 +66,13 @@ async fn main() -> std::io::Result<()> { PKG_NAME, PKG_DESCRIPTION, PKG_HOMEPAGE, VERSION, GIT_COMMIT_HASH ); - println!("Starting server on: http://{}", SETTINGS.server.get_ip()); + info!("Starting server on: http://{}", settings.server.get_ip()); HttpServer::new(move || { App::new() .wrap(actix_middleware::Logger::default()) .wrap(actix_middleware::Compress::default()) + .app_data(ctx.clone()) .app_data(get_json_err()) .wrap( actix_middleware::DefaultHeaders::new() @@ -78,8 +83,8 @@ async fn main() -> std::io::Result<()> { )) .configure(services) }) - .workers(SETTINGS.server.workers.unwrap_or_else(num_cpus::get)) - .bind(SETTINGS.server.get_ip()) + .workers(settings.server.workers.unwrap_or_else(num_cpus::get)) + .bind(settings.server.get_ip()) .unwrap() .run() .await diff --git a/src/meta.rs b/src/meta.rs index 6c218f2..7ab75e3 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -17,13 +17,13 @@ use actix_web::{web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; -use crate::{GIT_COMMIT_HASH, VERSION}; +use crate::{AppCtx, GIT_COMMIT_HASH, VERSION}; #[derive(Clone, Debug, Deserialize, Serialize)] -pub struct BuildDetails { - pub version: &'static str, - pub git_commit_hash: &'static str, - pub source_code: &'static str, +pub struct BuildDetails<'a> { + pub version: &'a str, + pub git_commit_hash: &'a str, + pub source_code: &'a str, } pub mod routes { @@ -44,11 +44,11 @@ pub mod routes { /// emmits build details of the bninary #[my_codegen::get(path = "crate::V1_API_ROUTES.meta.build_details")] -async fn build_details() -> impl Responder { +async fn build_details(ctx: AppCtx) -> impl Responder { let build = BuildDetails { version: VERSION, git_commit_hash: GIT_COMMIT_HASH, - source_code: &crate::SETTINGS.source_code, + source_code: &ctx.settings.source_code, }; HttpResponse::Ok().json(build) } @@ -59,14 +59,14 @@ pub fn services(cfg: &mut web::ServiceConfig) { #[cfg(test)] mod tests { - use actix_web::{http::StatusCode, test, App}; + use actix_web::{http::StatusCode, test}; - use crate::services; use crate::*; #[actix_rt::test] async fn build_details_works() { - let app = test::init_service(App::new().configure(services)).await; + let ctx = tests::get_data().await; + let app = get_app!(ctx).await; let resp = test::call_service( &app, diff --git a/src/settings.rs b/src/settings.rs index d93157d..65aea91 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -16,6 +16,7 @@ */ use std::env; use std::path::Path; +use std::sync::Arc; use config::{Config, Environment, File}; use log::warn; @@ -43,7 +44,7 @@ impl Server { pub struct Settings { pub server: Server, pub source_code: String, - pub pages: Vec, + pub pages: Vec>, } #[cfg(not(tarpaulin_include))] @@ -101,20 +102,14 @@ impl Settings { continue; } if page.secret == page2.secret { - log::error!( - "{}", - ServiceError::SecretTaken(page.to_owned(), page2.to_owned()) - ); + log::error!("{}", ServiceError::SecretTaken(page.clone(), page2.clone())); } else if page.repo == page2.repo { log::error!( "{}", - ServiceError::DuplicateRepositoryURL(page.to_owned(), page2.to_owned(),) + ServiceError::DuplicateRepositoryURL(page.clone(), page2.clone(),) ); } else if page.path == page2.path { - log::error!( - "{}", - ServiceError::PathTaken(page.to_owned(), page2.to_owned()) - ); + log::error!("{}", ServiceError::PathTaken(page.clone(), page2.clone())); } } if let Err(e) = page.update() {