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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
//! Salak is a multi layered configuration loader and zero-boilerplate configuration parser, with many predefined sources.
//!
//! 1. [About](#about)
//! 2. [Quick Start](#quick-start)
//! 3. [Features](#features)
//!    * [Predefined Sources](#predefined-sources)
//!    * [Key Convention](#key-convention)
//!    * [Value Placeholder Parsing](#value-placeholder-parsing)
//!    * [Attributes For Derive](#attributes-for-derive)
//!    * [Reload Configuration](#reload-configuration)
//!    * [Resource Factory](#resource-factory)
//!
//! ## About
//! `salak` is a multi layered configuration loader with many predefined sources. Also it
//! is a zero-boilerplate configuration parser which provides an auto-derive procedure macro
//! to derive [`FromEnvironment`] so that we can parse configuration structs without any additional codes.
//!
//! ## Quick Start
//! A simple example of `salak`:
//!
//! ```
//! use salak::*;
//!
//! #[derive(Debug, FromEnvironment)]
//! #[salak(prefix = "config")]
//! struct Config {
//!     #[salak(default = false)]
//!     verbose: bool,
//!     optional: Option<String>,
//!     #[salak(name = "val")]
//!     value: i64,
//! }
//! let env = Salak::builder()
//!     .set("config.val", "2021")
//!     .build()
//!     .unwrap();
//! let config = env.get::<Config>().unwrap();
//! assert_eq!(2021, config.value);
//! assert_eq!(None, config.optional);
//! assert_eq!(false, config.verbose);
//! ```
//!
//! ## Features
//!
//! #### Predefined Sources
//! Predefined sources has the following order, [`Salak`] will find by sequence of these orders,
//! if the property with specified key is found at the current source, than return immediately. Otherwise,
//! it will search the next source.
//!
//! 1. Random source provides a group of keys can return random values.
//!    * `random.u8`
//!    * `random.u16`
//!    * `random.u32`
//!    * `random.u64`
//!    * `random.u128`
//!    * `random.usize`
//!    * `random.i8`
//!    * `random.i16`
//!    * `random.i32`
//!    * `random.i64`
//!    * `random.i128`
//!    * `random.isize`
//! 2. Custom arguments source. [`SalakBuilder::set()`] can set a single kv,
//! and [`SalakBuilder::set_args()`] can set a group of kvs.
//! 3. System environment source. Implemented by [`source::system_environment`].
//! 4. Profile specified file source, eg. `app-dev.toml`, supports reloading.
//! 5. No profile file source, eg. `app.toml`, supports reloading.
//! 6. Custom sources, which can register by [`Salak::register()`].
//!
//! #### Key Convention
//! Key is used for search configuration from [`Environment`], normally it is represented by string.
//! Key is a group of SubKey separated by dot(`.`), and SubKey is a name or a name followed by index.
//! 1. SubKey Format (`[a-z][_a-z0-9]+(\[[0-9]+\])*`)
//!    * `a`
//!    * `a0`
//!    * `a_b`
//!    * `a[0]`
//!    * `a[0][0]`
//! 2. Key Format (`SubKey(\.SubKey)*`)
//!    * `a`
//!    * `a.b`
//!    * `a.val[0]`
//!    * `a_b[0]`
//!
//! #### Value Placeholder Parsing
//! 1. Placeholder Format
//!    * `${key}` => Get value of `key`.
//!    * `${key:default}` => Get value of `key`, if not exists return `default`.
//! 2. Escape Format
//!    * `\$\{key\}` => Return `${key}`.
//!    * `$`, `\`, `{`, `}` must use escape format.
//!
//! #### Attributes For Derive
//! `salak` supports some attributes for automatically derive [`FromEnvironment`].
//! All attributes have format `#[salak(..)]`, eg. `#[salak(default = "default value")]`.
//! 1. Struct Header Attribute.
//!    * `#[salak(prefix = "salak.application")]`, has this attr will auto implement [`PrefixedFromEnvironment`].
//! 2. Struct Field Attribute.
//!    * `#[salak(default = "value")]`, this attr can specify default value.
//!    * `#[salak(name = "key")]`, this attr can specify property key, default convension is use field name.
//!    * `#[salak(desc = "Field Description")]`, this attr can be describe this property.
//!
//! #### Reload Configuration
//! `salak` supports reload configurations. Since in rust mutable
//! and alias can't be used together, here we introduce a wrapper
//! [`wrapper::IORef`] for updating values when reloading.
//!
//! #### Resource Factory
//! [`Resource`] defines a standard way to create instance. [`Factory`] provides functions to initialize resource
//! and cache resource. Please refer to [salak_factory](https://docs.rs/salak_factory) for resource usage.
//! Feature 'app' should be open for this feature.
//!
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(
    anonymous_parameters,
    missing_copy_implementations,
    missing_debug_implementations,
    missing_docs,
    nonstandard_style,
    rust_2018_idioms,
    single_use_lifetimes,
    trivial_casts,
    trivial_numeric_casts,
    unreachable_pub,
    unused_extern_crates,
    unused_qualifications,
    variant_size_differences
)]

use parking_lot::Mutex;

#[cfg(feature = "derive")]
use crate::derive::KeyDesc;
#[cfg(feature = "derive")]
mod derive;
#[cfg(feature = "derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub use crate::derive::{
    AutoDeriveFromEnvironment, DescFromEnvironment, PrefixedFromEnvironment, SalakDescContext,
};
use raw_ioref::IORefT;
/// Auto derive [`FromEnvironment`] for struct.
#[cfg(feature = "derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub use salak_derive::FromEnvironment;
/// Auto derive [`Service`] for struct.
#[cfg(all(feature = "derive", feature = "app"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "derive", feature = "app"))))]
pub use salak_derive::Service;
use source_raw::PropertyRegistryInternal;

#[cfg(feature = "args")]
#[cfg_attr(docsrs, doc(cfg(feature = "args")))]
mod args;
#[cfg(feature = "args")]
#[cfg_attr(docsrs, doc(cfg(feature = "args")))]
pub use crate::args::AppInfo;

mod err;
mod raw;
use crate::raw::SubKey;
pub use crate::raw::{IsProperty, Property};
mod raw_ioref;
mod raw_vec;
use crate::env::PREFIX;
pub use crate::env::{Salak, SalakBuilder};
mod env;
mod raw_enum;

pub use crate::err::PropertyError;
pub use crate::raw_enum::EnumProperty;

mod source_map;
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
mod source_rand;
mod source_raw;
#[cfg(feature = "toml")]
#[cfg_attr(docsrs, doc(cfg(feature = "toml")))]
mod source_toml;
#[cfg(feature = "yaml")]
#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
mod source_yaml;

use crate::source::Key;
use crate::source::SubKeys;

#[cfg(feature = "app")]
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
mod app;
#[cfg(feature = "app")]
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
pub use crate::app::*;

#[cfg(test)]
#[macro_use(quickcheck)]
extern crate quickcheck_macros;

/// Salak wrapper for configuration parsing.
///
/// Wrapper can determine extra behavior for parsing.
/// Such as check empty of vec or update when reloading.
pub mod wrapper {
    pub use crate::raw_ioref::IORef;
    pub use crate::raw_vec::NonEmptyVec;
}

/// Salak sources.
///
/// This mod exports all pub sources.
pub mod source {

    #[cfg(feature = "args")]
    #[cfg_attr(docsrs, doc(cfg(feature = "args")))]
    pub(crate) use crate::args::from_args;
    pub use crate::raw::Key;
    pub use crate::raw::SubKeys;
    pub use crate::source_map::system_environment;
    pub use crate::source_map::HashMapSource;
}

pub(crate) type Res<T> = Result<T, PropertyError>;
pub(crate) type Void = Res<()>;

/// A property source defines how to load properties.
/// `salak` has some predefined sources, user can
/// provide custom source by implementing this trait.
///
/// Sources provided by `salak`.
///
/// * hashmap source
/// * std::env source
/// * toml source
/// * yaml source
pub trait PropertySource: Send + Sync {
    /// [`PropertySource`] name.
    fn name(&self) -> &str;

    /// Get property by key.
    fn get_property(&self, key: &Key<'_>) -> Option<Property<'_>>;

    /// Get all subkeys with given key.
    ///
    /// Subkeys are keys without dot('.').
    /// This method is unstable, and will be simplified by hidding
    /// Key and SubKeys.
    fn get_sub_keys<'a>(&'a self, key: &Key<'_>, sub_keys: &mut SubKeys<'a>);

    /// Check whether the [`PropertySource`] is empty.
    /// Empty source will be ignored when registering to `salak`.
    fn is_empty(&self) -> bool;

    /// Reload source, if nothing changes, then return none.
    #[inline]
    fn reload_source(&self) -> Res<Option<Box<dyn PropertySource>>> {
        Ok(None)
    }
}

/// Environment defines interface for getting values, and reloading
/// configurations.
///
/// The implementor of this trait is [`Salak`].
pub trait Environment {
    /// Get value by key.
    /// * `key` - Configuration key.
    ///
    /// Require means is if the value `T` is not found,
    /// then error will be returned. But if you try to get
    /// `Option<T>`, then not found will return `None`.
    fn require<T: FromEnvironment>(&self, key: &str) -> Res<T>;

    /// Reload configuration. If reloading is completed,
    /// all values wrapped by [`wrapper::IORef`] will be updated.
    ///
    /// Currently, this feature is unstable, the returned bool
    /// value means reloading is completed without error.
    fn reload(&self) -> Res<bool>;

    #[cfg(feature = "derive")]
    #[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
    #[inline]
    /// Get value with predefined key.
    ///
    /// [`PrefixedFromEnvironment`] can be auto derives by
    /// [`salak_derive::FromEnvironment`] macro. It provides
    /// a standard key for getting value `T`.
    fn get<T: PrefixedFromEnvironment>(&self) -> Res<T> {
        self.require::<T>(T::prefix())
    }
}

/// Context for implementing [`FromEnvironment`].
#[allow(missing_debug_implementations)]
pub struct SalakContext<'a> {
    registry: &'a PropertyRegistryInternal<'a>,
    iorefs: &'a Mutex<Vec<Box<dyn IORefT + Send>>>,
    key: &'a mut Key<'a>,
}

/// Parsing value from environment by [`SalakContext`].
pub trait FromEnvironment: Sized {
    /// Generate object from [`SalakContext`].
    /// * `val` - Property value can be parsed from.
    /// * `env` - Context.
    ///
    /// ```no_run
    /// use salak::*;
    /// pub struct Config {
    ///   key: String
    /// }
    /// impl FromEnvironment for Config {
    ///   fn from_env(
    ///       val: Option<Property<'_>>,
    ///       env: &mut SalakContext<'_>,
    ///   ) -> Result<Self, PropertyError> {
    ///     Ok(Self{
    ///       key: env.require_def("key", None)?,
    ///     })
    ///   }
    /// }
    ///
    /// ```
    fn from_env(val: Option<Property<'_>>, env: &mut SalakContext<'_>) -> Res<Self>;
}