Page MenuHomeDevCentral

D1863.diff
No OneTemporary

D1863.diff

diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,9 +19,15 @@
repository = "https://devcentral.nasqueron.org/source/limiting-factor/"
[dependencies]
-diesel = { version = "^1.0.0", features = ["postgres", "r2d2", "chrono"] }
+diesel = { version = "^1.0.0", features = ["postgres", "r2d2", "chrono"], optional = true }
dotenv = "0.9.0"
log = "^0.4.4"
-r2d2 = "^0.8.2"
+r2d2 = { version = "^0.8.2", optional = true }
rocket = "^0.3.16"
rocket_contrib = { version = "^0.3.16", features = [ "json" ] }
+
+[features]
+default = ["pgsql"]
+minimal = []
+
+pgsql = ["diesel", "r2d2"]
diff --git a/src/api/replies.rs b/src/api/replies.rs
--- a/src/api/replies.rs
+++ b/src/api/replies.rs
@@ -2,16 +2,18 @@
//!
//! This module provides useful traits and methods to craft API replies from an existing type.
-use std::error::Error;
-
-use diesel::result::DatabaseErrorInformation;
-use diesel::result::DatabaseErrorKind;
+#[cfg(feature = "pgsql")]
+use diesel::result::{DatabaseErrorInformation, DatabaseErrorKind, QueryResult};
+#[cfg(feature = "pgsql")]
use diesel::result::Error as ResultError;
-use diesel::result::QueryResult;
+
use rocket::http::Status;
use rocket::response::Failure;
use rocket_contrib::Json;
+#[cfg(feature = "pgsql")]
+use std::error::Error;
+
/* -------------------------------------------------------------
Custom types
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
@@ -30,6 +32,7 @@
fn into_json_response(self) -> ApiJsonResponse<T>;
}
+#[cfg(feature = "pgsql")]
impl<T> ApiResponse<T> for QueryResult<T> {
/// Prepares an API response from a query result.
///
@@ -92,6 +95,7 @@
fn into_failure_response(self) -> Failure;
}
+#[cfg(feature = "pgsql")]
impl FailureResponse for ResultError {
/// Consumes the error and creates a Failure 500 Internal server error response.
fn into_failure_response(self) -> Failure {
@@ -109,6 +113,7 @@
Failure::from(Status::InternalServerError)
}
+#[cfg(feature = "pgsql")]
fn build_database_error_response(error_kind: DatabaseErrorKind, info: Box<dyn DatabaseErrorInformation>) -> Failure {
match error_kind {
// Case IIIa - The query tries to do an INSERT violating an unique constraint
diff --git a/src/config.rs b/src/config.rs
--- a/src/config.rs
+++ b/src/config.rs
@@ -8,6 +8,10 @@
//! extract variables from an .env file or environment.
use dotenv::dotenv;
+#[cfg(feature = "pgsql")]
+use kernel::DefaultService;
+use kernel::{MinimalService, Service};
+use rocket::Route;
use std::env;
use std::error::Error;
use ErrorResult;
@@ -21,6 +25,17 @@
fn get_database_url(&self) -> &str;
fn get_entry_point(&self) -> &str;
fn get_database_pool_size(&self) -> u32;
+ fn with_database(&self) -> bool;
+ fn into_service(self, routes: Vec<Route>) -> Box<dyn Service>;
+}
+
+/* -------------------------------------------------------------
+ EnvironmentConfigurable
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/// This trait allows to configure the object from the environment
+pub trait EnvironmentConfigurable {
+ fn parse_environment() -> ErrorResult<Self> where Self: Sized;
}
/* -------------------------------------------------------------
@@ -36,39 +51,57 @@
/// - `API_ENTRY_POINT` (facultative, by default `/`): the mouting point of the API methods
/// - `DATABASE_URL` (mandatory): the URL to connect to your database
/// - `DATABASE_POOL_SIZE` (facultative, by default 4): the number of connections to open
+#[cfg(feature = "pgsql")]
pub struct DefaultConfig {
database_url: String,
entry_point: String,
database_pool_size: u32,
+ with_database: bool,
+}
+
+#[cfg(feature = "pgsql")]
+impl DefaultConfig {
+ const DEFAULT_DATABASE_POOL_SIZE: u32 = 4;
}
+#[cfg(feature = "pgsql")]
impl Config for DefaultConfig {
- fn get_database_url(&self) -> &str {
- &self.database_url
- }
+ fn get_database_url(&self) -> &str { &self.database_url }
- fn get_entry_point(&self) -> &str {
- &self.entry_point
- }
+ fn get_entry_point(&self) -> &str { &self.entry_point }
- fn get_database_pool_size(&self) -> u32 {
- self.database_pool_size
+ fn get_database_pool_size(&self) -> u32 { self.database_pool_size }
+
+ fn with_database(&self) -> bool { self.with_database }
+
+ fn into_service(self, routes: Vec<Route>) -> Box<dyn Service> {
+ let service = DefaultService {
+ config: self,
+ routes: Box::new(routes),
+ };
+
+ Box::new(service)
}
}
-impl DefaultConfig {
- pub const DEFAULT_DATABASE_POOL_SIZE: u32 = 4;
-
- pub fn parse_environment() -> ErrorResult<Self> {
+#[cfg(feature = "pgsql")]
+impl EnvironmentConfigurable for DefaultConfig {
+ fn parse_environment() -> ErrorResult<Self> {
if let Err(error) = dotenv() {
warn!(target: "config", "Can't parse .env: {}", error.description());
};
+ let with_database = env::var("LF_DISABLE_DATABASE").is_err();
+
let database_url = match env::var("DATABASE_URL") {
Ok(url) => url,
Err(e) => {
- error!(target: "config", "You need to specify a DATABASE_URL variable in the environment (or .env file).");
- return Err(Box::new(e));
+ if with_database {
+ error!(target: "config", "You need to specify a DATABASE_URL variable in the environment (or .env file).");
+ return Err(Box::new(e));
+ }
+
+ String::new()
}
};
@@ -92,7 +125,63 @@
database_url,
entry_point,
database_pool_size,
+ with_database,
})
}
}
+/* -------------------------------------------------------------
+ MinimalConfig
+
+ :: Config
+ :: sui generis implementation
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/// This is a minimal implementation of the `Config` trait, which extracts the following variables
+/// from an .env file or environment:
+///
+/// - `API_ENTRY_POINT` (facultative, by default `/`): the mouting point of the API methods
+///
+/// It sets the server not to use a database.
+pub struct MinimalConfig {
+ entry_point: String,
+}
+
+impl Config for MinimalConfig {
+ fn get_database_url(&self) -> &str {
+ ""
+ }
+
+ fn get_entry_point(&self) -> &str {
+ &self.entry_point
+ }
+
+ fn get_database_pool_size(&self) -> u32 {
+ 0
+ }
+
+ fn with_database(&self) -> bool { false }
+
+ fn into_service(self, routes: Vec<Route>) -> Box<dyn Service> {
+ let service = MinimalService {
+ config: self,
+ routes: Box::new(routes),
+ };
+
+ Box::new(service)
+ }
+}
+
+impl EnvironmentConfigurable for MinimalConfig {
+ fn parse_environment() -> ErrorResult<Self> {
+ if let Err(error) = dotenv() {
+ warn!(target: "config", "Can't parse .env: {}", error.description());
+ };
+
+ let entry_point = env::var("API_ENTRY_POINT").unwrap_or(String::from("/"));
+
+ Ok(MinimalConfig {
+ entry_point,
+ })
+ }
+}
diff --git a/src/kernel.rs b/src/kernel.rs
--- a/src/kernel.rs
+++ b/src/kernel.rs
@@ -2,63 +2,193 @@
//!
//! Provides methods to start the server and handle the application
-use config::Config;
+use config::{Config, MinimalConfig};
+#[cfg(feature = "pgsql")]
use config::DefaultConfig;
-use database::initialize_database_pool;
-use database::test_database_connection;
+#[cfg(feature = "pgsql")]
+use database::{initialize_database_pool, test_database_connection};
use ErrorResult;
use rocket::Route;
use rocket::ignite;
use std::process;
+use std::marker::PhantomData;
+use config::EnvironmentConfigurable;
/* -------------------------------------------------------------
- Application
+ Service
Allow to define config and routes. Launch a server.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-pub trait Application {
+pub trait Service {
fn get_config(&self) -> &dyn Config;
fn get_routes(&self) -> &[Route];
+ fn launch_server(&mut self) -> ErrorResult<()>;
+
+ fn check_service_configuration(&self) -> ErrorResult<()>;
+
+ fn run (&mut self) -> ErrorResult<()> {
+ info!(target: "runner", "Server started.");
+
+ {
+ self.check_service_configuration()?
+ }
+
+ self.launch_server()?;
+
+ Ok(())
+ }
+}
+
+/* -------------------------------------------------------------
+ Default service
+
+ Allow to define config and routes. Launch a server.
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/// The default service offers a pgsql database connection with Diesel and r2d2.
+#[cfg(feature = "pgsql")]
+pub struct DefaultService {
+ pub config: DefaultConfig,
+ pub routes: Box<Vec<Route>>,
+}
+
+#[cfg(feature = "pgsql")]
+impl Service for DefaultService {
+ fn get_config(&self) -> &dyn Config { &self.config }
+
+ fn get_routes(&self) -> &[Route] { self.routes.as_slice() }
+
fn launch_server(&mut self) -> ErrorResult<()> {
let config = self.get_config();
let routes = self.get_routes();
- ignite()
- .manage(
+ let mut server = ignite();
+
+ if config.with_database() {
+ server = server.manage(
initialize_database_pool(config.get_database_url(), config.get_database_pool_size())?
- )
+ );
+ }
+
+ server
.mount(config.get_entry_point(), routes.to_vec())
.launch();
Ok(())
}
- fn run (&mut self) -> ErrorResult<()> {
- info!(target: "runner", "Server started.");
-
- // Initial connection to test if the database configuration works
- {
- let config = self.get_config();
+ fn check_service_configuration(&self) -> ErrorResult<()> {
+ let config = self.get_config();
+ if config.with_database() {
test_database_connection(config.get_database_url())?;
info!(target: "runner", "Connection to database established.");
}
- self.launch_server()?;
+ Ok(())
+ }
+}
+
+/* -------------------------------------------------------------
+ Minimal service
+
+ Allow to define config and routes. Launch a server.
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/// The minimal service allows to spawn a server without any extra feature.
+pub struct MinimalService {
+ pub config: MinimalConfig,
+ pub routes: Box<Vec<Route>>,
+}
+
+impl Service for MinimalService {
+ fn get_config(&self) -> &dyn Config { &self.config }
+
+ fn get_routes(&self) -> &[Route] { self.routes.as_slice() }
+
+ fn launch_server(&mut self) -> ErrorResult<()> {
+ let config = self.get_config();
+ let routes = self.get_routes();
+
+ ignite()
+ .mount(config.get_entry_point(), routes.to_vec())
+ .launch();
Ok(())
}
+
+ fn check_service_configuration(&self) -> ErrorResult<()> { Ok(()) }
}
/* -------------------------------------------------------------
- Default application
+ Base application as concrete implementation
:: Application
:: sui generis implementation
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/// The application structure allows to encapsulate the service into a CLI application.
+///
+/// The application takes care to run the service and quits with a correct exit code.
+///
+/// It also takes care of initialisation logic like parse the environment to extract
+/// the configuration.
+pub struct Application<U>
+ where U: Config
+{
+ service: Box<dyn Service>,
+ config_type: PhantomData<U>,
+}
+
+impl<U> Application<U>
+ where U: Config + EnvironmentConfigurable
+{
+ pub fn new (config: U, routes: Vec<Route>) -> Self {
+ Application {
+ service: config.into_service(routes),
+ config_type: PhantomData,
+ }
+ }
+
+ /// Starts the application
+ ///
+ /// # Exit codes
+ ///
+ /// The software will exit with the following error codes:
+ ///
+ /// - 0: Graceful exit (currently not in use, as the application never stops)
+ /// - 1: Error during the application run (e.g. routes conflict or Rocket fairings issues)
+ /// - 2: Error parsing the configuration (e.g. no database URL has been defined)
+ pub fn start (&mut self) {
+ info!(target: "runner", "Server initialized.");
+
+ if let Err(error) = self.service.run() {
+ error!(target: "runner", "{}", error.description());
+ process::exit(1);
+ }
+
+ process::exit(0);
+ }
+
+ pub fn start_application (routes: Vec<Route>) {
+ let config = <U>::parse_environment().unwrap_or_else(|_error| {
+ process::exit(2);
+ });
+
+ let mut app = Application::new(config, routes);
+ app.start();
+ }
+}
+
+/* -------------------------------------------------------------
+ Default application
+
+ :: Application
+ :: sui generis implementation, wrapper for Application
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
/// The default application implements CLI program behavior to prepare a configuration from the
/// `DefaultConfig` implementation, test if it's possible to connect to the database, and if so,
/// launch a Rocket server.
@@ -84,52 +214,27 @@
/// ```
///
/// The default configuration will be used and the server started.
-pub struct DefaultApplication {
- config: DefaultConfig,
- routes: Box<Vec<Route>>,
-}
-
-impl Application for DefaultApplication {
- fn get_config(&self) -> &dyn Config {
- &self.config
- }
-
- fn get_routes(&self) -> &[Route] {
- self.routes.as_slice()
- }
-}
+#[cfg(feature = "pgsql")]
+pub struct DefaultApplication {}
+#[cfg(feature = "pgsql")]
impl DefaultApplication {
- pub fn new (config: DefaultConfig, routes: Vec<Route>) -> Self {
- DefaultApplication {
- config,
- routes: Box::new(routes),
- }
- }
-
- /// Starts the application, prepares default configuration
- ///
- /// # Exit codes
- ///
- /// The software will exit with the following error codes:
- ///
- /// - 0: Graceful exit (currently not in use, as the application never stops)
- /// - 1: Error during the application run (e.g. routes conflict or Rocket fairings issues)
- /// - 2: Error parsing the configuration (e.g. no database URL has been defined)
pub fn start_application (routes: Vec<Route>) {
- info!(target: "runner", "Server initialized.");
+ Application::<DefaultConfig>::start_application(routes);
+ }
+}
- let config = DefaultConfig::parse_environment().unwrap_or_else(|_error| {
- process::exit(2);
- });
+/* -------------------------------------------------------------
+ Minimal application
- let mut app = Self::new(config, routes);
+ :: Application
+ :: sui generis implementation, wrapper for Application
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
- if let Err(error) = app.run() {
- error!(target: "runner", "{}", error.description());
- process::exit(1);
- }
+pub struct MinimalApplication {}
- process::exit(0);
+impl MinimalApplication {
+ pub fn start_application (routes: Vec<Route>) {
+ Application::<MinimalConfig>::start_application(routes);
}
}
diff --git a/src/lib.rs b/src/lib.rs
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -28,10 +28,16 @@
//! "ALIVE"
//! }
//! ```
+//!
+//! Replacing `DefaultApplication` by `MinimalApplication` allows to use a lighter version
+//! of the library without Diesel dependencies or database use.
+#[cfg(feature = "pgsql")]
extern crate diesel;
extern crate dotenv;
-#[macro_use] extern crate log;
+#[macro_use]
+extern crate log;
+#[cfg(feature = "pgsql")]
extern crate r2d2;
extern crate rocket;
extern crate rocket_contrib;
@@ -42,10 +48,16 @@
pub mod api;
pub mod config;
-pub mod database;
pub mod kernel;
/* -------------------------------------------------------------
+ Optional public features modules offered by this crate
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+#[cfg(feature = "pgsql")]
+pub mod database;
+
+/* -------------------------------------------------------------
Custom types
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 23, 09:19 (18 h, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2257646
Default Alt Text
D1863.diff (16 KB)

Event Timeline