derive(Config)
TODO
This crate was built specifically for moon, and many of the design decisions are based around that project and its needs. Because of that, this crate is quite opinionated and won't change heavily.
Define a struct and derive the Config
trait. This struct represents the final state, after all
layers have been merged.
```rust use schematic::Config;
struct AppConfig { #[setting(default = 3000, env = "PORT")] port: usize,
#[setting(default = true)]
secure: bool,
#[setting(default = vec!["localhost".into()])]
allowed_hosts: Vec<String>,
} ```
Then load, parse, and validate the configuration from one or many sources. A source is either a file path, secure URL, or code block.
```rust use schematic::ConfigLoader;
let result = ConfigLoader::
result.config; result.layers; ```
TODO
TODO
TODO
TODO
TODO
By default the Config
macro will apply
#[serde(default, deny_unknown_fields, rename_all = "camelCase")]
to the
partial config. The default
and deny_unknown_fields
cannot be customized, as they
ensure proper parsing and layer merging.
However, the rename_all
field can be customized, and we also support the rename
field, both via
the top-level #[config]
attribute.
```rust
struct Example { // ... } ```
The
rename
field will also update the metadata name.
Settings are the individual fields/members of a configuration struct, and can be annotated with the
optional #[setting]
attribute.
In schematic, there are 2 forms of default values:
#[setting]
attribute, and
is the first layer of the configuration to be merged.Default
to generate the
final value if none was provided. This acts more like a fallback.This section will talk about the #[setting]
attribute, and the supported default
, default_str
,
and default_fn
attribute fields.
The default
attribute field is used for declaring primitive values, like numbers and booleans. It
can also be used for array and tuple literals, as well as function (mainly for from()
) and macros
calls.
```rust
struct AppConfig { #[setting(default = 3000)] port: usize,
#[setting(default = true)]
secure: bool,
#[setting(default = vec!["localhost".into()])]
allowed_hosts: Vec<String>,
} ```
For string literals, the default_str
attribute field should be used instead.
```rust
struct AppConfig { #[setting(default_str = "/")] base: String, } ```
And lastly, if you need more control or need to calculate a complex value, you can use the
default_fn
attribute field, which requires a path to a function to call.
This function receives the context as the first argument (use ()
or generics if you
don't have context), and can return an optional value. If None
is returned, the Default
value
will be used instead.
```rust
fn findunusedport(ctx: &Context) -> Option
struct AppConfig { #[setting(defaultfn = findunused_port)] port: usize, } ```
Settings can also inherit values from environment variables via the env
attribute field. When
using this, variables take the highest precedence, and are merged as the last layer.
```rust
struct AppConfig { #[setting(default = 3000, env = "PORT")] port: usize, } ```
We also support parsing environment variables into the required type. For example, the variable may be a comma separated list of values, or a JSON string.
The parse_env
attribute field can be used, which requires a path to a function to handle the
parsing, and receives the variable value as a single argument.
```rust
struct AppConfig {
#[setting(env = "ALLOWEDHOSTS", parseenv = schematic::env::splitcomma)]
allowedhosts: Vec
We provide a handful of built-in parsing functions in the
env
module.
When defining a custom parse function, you should return an error with ConfigError::Message
if
parsing fails.
```rust use schematic::ConfigError;
pub fn customparse(var: String) -> Result
Configs can extend other configs, generating an accurate layer chain, via the extend
attribute
field. Extended configs can either be a file path (relative from the current config) or a secure
URL. For example:
yaml
extends:
- "./another/file.yml"
- "https://domain.com/some/other/file.yml"
When defining extend
, we currently support 3 types of patterns. The first is with a single string,
which only allows a single file to be extended.
```rust
struct AppConfig {
#[setting(extend, validate = schematic::validate::extends_string)]
extends: Option
The second is with a list of strings, allowing multiple files to be extended. This is the YAML example above.
```rust
struct AppConfig {
#[setting(extend, validate = schematic::validate::extends_list)]
extends: Option
And lastly, supporting both a string or a list, using our built-in enum.
```rust
struct AppConfig {
#[setting(extend, validate = schematic::validate::extends_from)]
extends: Option
We suggest making this field optional, so that extending is not required by consumers!
A common requirement for configuration is to merge multiple sources/layers into a final result. By
default schematic will replace the previous value with the next value if the next value is Some
,
but sometimes you want far more control, like shallow merging or deep merging collections.
This can be achieved with the merge
attribute field, which requires a path to a function to call.
```rust
struct AppConfig {
#[setting(merge = schematic::merge::appendvec)]
allowedhosts: Vec
We provide a handful of built-in merge functions in the
merge
module.
When defining a custom merge function, the previous and next values are passed as arguments, and the
function must return an optional merged result. If None
is provided, neither value will be used.
Here's an example of the merge function above.
```rust
pub fn append_vec
Some(prev)
} ```
What kind of configuration crate would this be without built-in validation? As such, we support it as a first-class feature, with built-in validation rules provided by garde.
In schematic, validation does not happen as part of the serde parsing process, and instead happens after the final configuration has been merged. This means we only validate the end result, not partial values (which may be incorrect).
Validation can be applied on a per-setting basis with the validate
attribute field, which requires
a path to a function to call. Furthermore, some functions are factories which can be called to
produce a validator.
```rust
struct AppConfig { #[setting(validate = schematic::validate::alphanumeric)] secret_key: String,
#[setting(validate = schematic::validate::regex("^\.env"))]
env_file: String,
} ```
We provide a handful of built-in validation functions in the
validate
module.
When defining a custom validate function, the value to check is passed as the first argument, the
current struct as the second, and the context as the third. The ValidateError
type
must be used for failures.
```rust use schematic::ValidateError;
fn validatestring( value: &str, config: &AppConfig, context: &Context ) -> Result<(), ValidateError> { if !docheck(value) { return Err(ValidateError::new("Some failure message")); }
Ok(())
} ```
If validating an item in a vector or collection, you can specifiy the nested path when erroring. This is extremely useful when building error messages.
```rust use schematic::Segment;
ValidateError::withsegments( "Some failure message", // [i].key [Segment::Index(i), Segment::Key(key.tostring())] ) ```
The rename
and skip
attribute fields are currently supported and will apply a #[serde]
attribute to the partial setting.
```rust
struct Example { #[setting(rename = "type")] type_of: SomeEnum, } ```
The following Cargo features are available:
json
(default) - Enables JSON.toml
- Enables TOML.yaml
- Enables YAML.valid_email
- Enables email validation with the schematic::validate::email
function.valid_url
- Enables URL validation with the schematic::validate::url
and url_secure
functions.