1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! # 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(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_else(|_| 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(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_else(|_| String::from("/"));

        Ok(MinimalConfig {
            entry_point,
        })
    }
}