Page MenuHomeDevCentral

No OneTemporary

diff --git a/src/config.rs b/src/config.rs
index a330405..5e84024 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,186 +1,186 @@
//! # Service configuration.
//!
//! This module allows to configure the service.
//!
//! It provides a Config trait to build custom configuration implementation.
//!
//! It also provides a `DefaultConfig` implementation of this `Config` trait to
//! 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 ErrorResult;
/* -------------------------------------------------------------
Config trait
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/// This trait allows to provide a configuration for the resources needed by the API.
pub trait Config {
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;
}
/* -------------------------------------------------------------
DefaultConfig
:: Config
:: sui generis implementation
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/// This is a default implementation of the `Config` trait, which extracts the following variables
/// from an .env file or environment:
///
/// - `API_ENTRY_POINT` (facultative, by default `/`): the mounting 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_entry_point(&self) -> &str { &self.entry_point }
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),
+ routes,
};
Box::new(service)
}
}
#[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);
};
let with_database = env::var("LF_DISABLE_DATABASE").is_err();
let database_url = match env::var("DATABASE_URL") {
Ok(url) => url,
Err(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()
}
};
let entry_point = env::var("API_ENTRY_POINT").unwrap_or(String::from("/"));
let database_pool_size = match env::var("DATABASE_POOL_SIZE") {
Ok(variable) => {
match variable.parse::<u32>() {
Ok(size) => size,
Err(_) => {
warn!(target: "config", "The DATABASE_POOL_SIZE variable must be an unsigned integer.");
DefaultConfig::DEFAULT_DATABASE_POOL_SIZE
},
}
},
Err(_) => DefaultConfig::DEFAULT_DATABASE_POOL_SIZE,
};
Ok(DefaultConfig {
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 mounting 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),
+ 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);
};
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
index 3ed1545..77fdd6f 100644
--- a/src/kernel.rs
+++ b/src/kernel.rs
@@ -1,240 +1,240 @@
//! # Service execution utilities.
//!
//! Provides methods to start the server and handle the application
use config::{Config, MinimalConfig};
#[cfg(feature = "pgsql")]
use config::DefaultConfig;
#[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;
/* -------------------------------------------------------------
Service
Allow to define config and routes. Launch a server.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
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>>,
+ pub routes: 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();
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 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.");
}
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>>,
+ pub routes: 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(()) }
}
/* -------------------------------------------------------------
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);
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.
///
/// # Examples
///
/// To run an application with some routes in a `requests` module:
///
/// ```
/// use limiting_factor::kernel::DefaultApplication;
/// use requests::*;
///
/// pub fn main () {
/// let routes = routes![
/// status,
/// favicon,
/// users::register,
/// users::get_player,
/// ];
///
/// DefaultApplication::start_application(routes);
/// }
/// ```
///
/// The default configuration will be used and the server started.
#[cfg(feature = "pgsql")]
pub struct DefaultApplication {}
#[cfg(feature = "pgsql")]
impl DefaultApplication {
pub fn start_application (routes: Vec<Route>) {
Application::<DefaultConfig>::start_application(routes);
}
}
/* -------------------------------------------------------------
Minimal application
:: Application
:: sui generis implementation, wrapper for Application
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
pub struct MinimalApplication {}
impl MinimalApplication {
pub fn start_application (routes: Vec<Route>) {
Application::<MinimalConfig>::start_application(routes);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 16:38 (1 d, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2260569
Default Alt Text
(12 KB)

Event Timeline