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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
//! Support for common configuration behaviour and loading them from various sources.
//!
//! # Direct configuration example
//!
//! This example shows how to use the direct configuration. Please note that the direct
//! configuration is usually useful in testing scenarios. Production usage is discouraged.
//!
//! ```
//! # #[cfg(feature = "configuration")]
//! # {
//! # use blockz::prelude::*;
//! #[derive(Configuration)]
//! #[configuration(direct)]
//! struct ServerConfig {
//!     address: String,
//!     port: u16,
//! }
//!
//! # #[tokio::main]
//! # async fn main() {
//! let config = <ServerConfig as Configuration>::load(ServerConfig {
//!     address: "127.0.0.1".to_string(),
//!     port: 58732,
//! }).await;
//! # assert_eq!(config.address.as_str(), "127.0.0.1");
//! # assert_eq!(config.port, 58732);
//! println!("Server binding to {}:{}.", config.address, config.port);
//! // Server binding to 127.0.0.1:58732.
//! # }
//! # }
//! # #[cfg(not(feature = "configuration"))]
//! # fn main() {}
//! ```
//!
//! If the configuration type implements Default, you can use EasyConfiguration to load the default
//! value, like so:
//!
//! ```
//! # use blockz::prelude::*;
//! #[derive(Configuration)]
//! #[configuration(direct)]
//! struct ServerConfig {
//!     address: String,
//!     port: u16,
//! }
//!
//! impl Default for ServerConfig {
//!     fn default() -> ServerConfig {
//!         ServerConfig {
//!             address: "0.0.0.0".to_string(),
//!             port: 9999,
//!         }
//!     }
//! }
//!
//! # #[tokio::main]
//! # async fn main() {
//! let config = <ServerConfig as EasyConfiguration>::load().await;
//! # assert_eq!(config.address.as_str(), "0.0.0.0");
//! # assert_eq!(config.port, 9999);
//! println!("Server binding to {}:{}.", config.address, config.port);
//! // Server binding to 0.0.0.0:9999.
//! # }
//! ```
//!
//! # Env configuration examples
//!
//! **NOTE**: These examples require the __env_configuration__ feature.
//!
//! The first example shows how to use the env configuration with a prefix.
//!
//! ```
//! # #[cfg(feature = "env_configuration")]
//! # {
//! # use blockz::prelude::*;
//! # use serde::Deserialize;
//! #[derive(Configuration, Deserialize)]
//! #[configuration(env(prefix = "COOL_APP_"))]
//! struct ServerConfig {
//!     #[serde(rename = "bind_addr")]
//!     address: String,
//!     #[serde(rename = "bind_port")]
//!     port: u16,
//! }
//!
//! # #[tokio::main]
//! # async fn main() {
//! # std::env::set_var("COOL_APP_BIND_ADDR", "0.0.0.0");
//! # std::env::set_var("COOL_APP_BIND_PORT", "58732");
//! let config = <ServerConfig as EasyConfiguration>::load()
//!     .await
//!     .expect("Failed to load configuration from the environment!");
//! # assert_eq!(config.address.as_str(), "0.0.0.0");
//! # assert_eq!(config.port, 58732);
//! println!("Server binding to {}:{}.", config.address, config.port);
//! // Server binding to aaa.bbb.ccc.ddd:ppppp.
//! # }
//! # }
//! # #[cfg(not(feature = "env_configuration"))]
//! # fn main() {}
//! ```
//!
//! The second example shows how to use the env configuration with a prefix source in the form of a
//! constant.
//!
//! ```
//! # #[cfg(feature = "env_configuration")]
//! # {
//! # use blockz::prelude::*;
//! # use serde::Deserialize;
//! /// This is our prefix.
//! const PREFIX: &str = "COOL_APP_";
//!
//! #[derive(Configuration, Deserialize)]
//! #[configuration(env(prefix_source = "PREFIX.to_string()"))]
//! struct ServerConfig {
//!     #[serde(rename = "bind_addr")]
//!     address: String,
//!     #[serde(rename = "bind_port")]
//!     port: u16,
//! }
//!
//! # #[tokio::main]
//! # async fn main() {
//! # std::env::set_var("COOL_APP_BIND_ADDR", "0.0.0.0");
//! # std::env::set_var("COOL_APP_BIND_PORT", "58732");
//! let config = <ServerConfig as EasyConfiguration>::load()
//!     .await
//!     .expect("Failed to load configuration from the environment!");
//! # assert_eq!(config.address.as_str(), "0.0.0.0");
//! # assert_eq!(config.port, 58732);
//! println!("Server binding to {}:{}.", config.address, config.port);
//! // Server binding to aaa.bbb.ccc.ddd:ppppp.
//! # }
//! # }
//! # #[cfg(not(feature = "env_configuration"))]
//! # fn main() {}
//! ```
//!
//! The third example shows how to use the env configuration with a prefix source in the form of a
//! function that can return an error. The error must be mapped to an `envy::Error`.
//!
//! ```
//! # #[cfg(feature = "env_configuration")]
//! # {
//! # use blockz::prelude::*;
//! # use serde::Deserialize;
//! /// This function sources our prefix.
//! fn source_prefix() -> Result<String, envy::Error> {
//!     std::env::var("COOL_APP_PREFIX")
//!         .map_err(|err| envy::Error::Custom(format!("failed to source the prefix: {}", err)))
//! }
//!
//! #[derive(Configuration, Deserialize)]
//! #[configuration(env(prefix_source = "source_prefix()?"))]
//! struct ServerConfig {
//!     #[serde(rename = "bind_addr")]
//!     address: String,
//!     #[serde(rename = "bind_port")]
//!     port: u16,
//! }
//!
//! # #[tokio::main]
//! # async fn main() {
//! # std::env::set_var("COOL_APP_PREFIX", "COOL_APP_");
//! # std::env::set_var("COOL_APP_BIND_ADDR", "0.0.0.0");
//! # std::env::set_var("COOL_APP_BIND_PORT", "58732");
//! let config = <ServerConfig as EasyConfiguration>::load()
//!     .await
//!     .expect("Failed to load configuration from the environment!");
//! # assert_eq!(config.address.as_str(), "0.0.0.0");
//! # assert_eq!(config.port, 58732);
//! println!("Server binding to {}:{}.", config.address, config.port);
//! // Server binding to aaa.bbb.ccc.ddd:ppppp.
//! # }
//! # }
//! # #[cfg(not(feature = "env_configuration"))]
//! # fn main() {}
//! ```

#[cfg(feature = "env_configuration")]
use serde::Deserialize;

use std::marker::PhantomData;

/// Common behaviour of configurations.
#[async_trait::async_trait]
pub trait Configuration {
    /// The type of options container this Configuration accepts for the purpose of loading the
    /// configuration.
    type Opts: Send;

    /// The result type that can be produced by loading the configuration.
    type Result: Send;

    /// Load the configuration.
    async fn load(opts: Self::Opts) -> Self::Result;
}

/// An easy configuration is a configuration that can be loaded without other parameters.
#[async_trait::async_trait]
pub trait EasyConfiguration {
    /// The result type that can be produced by loading the configuration.
    type Result: Send;

    /// Load the configuration.
    async fn load() -> Self::Result;
}

/// Automatically implement EasyConfiguration on Configuration implementations whose Opts type is
/// Default.
#[async_trait::async_trait]
impl<C, O, R> EasyConfiguration for C
where
    C: Configuration<Opts = O, Result = R>,
    O: Default + Send + 'static,
    R: Send + 'static,
{
    type Result = R;

    async fn load() -> Self::Result {
        C::load(O::default()).await
    }
}

/// Direct configuration that just returns the passed value.
pub struct DirectConfiguration<T>
where
    T: Send,
{
    _phantom: PhantomData<T>,
}

#[async_trait::async_trait]
impl<T> Configuration for DirectConfiguration<T>
where
    T: Send,
{
    type Opts = T;
    type Result = T;

    async fn load(opts: Self::Opts) -> Self::Result {
        opts
    }
}

/// Configuration that can be sourced from environment variables.
#[cfg(feature = "env_configuration")]
#[cfg_attr(docsrs, doc(cfg(feature = "env_configuration")))]
pub struct EnvConfiguration<T>
where
    T: for<'de> Deserialize<'de> + Send,
{
    _phantom_t: PhantomData<T>,
}

#[cfg(feature = "env_configuration")]
#[cfg_attr(docsrs, doc(cfg(feature = "env_configuration")))]
#[async_trait::async_trait]
impl<T> Configuration for EnvConfiguration<T>
where
    T: for<'de> Deserialize<'de> + Send,
{
    type Opts = Option<String>;
    type Result = Result<T, envy::Error>;

    async fn load(opts: Self::Opts) -> Self::Result {
        if let Some(prefix) = opts {
            envy::prefixed(prefix).from_env::<T>()
        } else {
            envy::from_env::<T>()
        }
    }
}