Derive Macro smart_config::DescribeConfig

source ·
#[derive(DescribeConfig)]
{
    // Attributes available to this derive:
    #[config]
}
Expand description

Derives the DescribeConfig trait for a type.

This macro supports both structs and enums. It is conceptually similar to Deserialize macro from serde. Macro behavior can be configured with #[config(_)] attributes. Multiple #[config(_)] attributes on a single item are supported.

Each field in the struct / each enum variant is considered a configuration param (by default), or a sub-config (if #[config(nest)] or #[config(flatten)] is present for the field).

§Container attributes

§validate

Type: One of the following:

  • Expression evaluating to a Validate implementation (e.g., a Range; see the Validate docs for implementations). An optional human-readable string validation description may be provided delimited by the comma (e.g., to make the description more domain-specific).
  • Pointer to a function with the fn(&_) -> Result<(), ErrorWithOrigin> signature and the validation description separated by a comma.
  • Pointer to a function with the fn(&_) -> bool signature and the validation description separated by a comma. Validation fails if the function returns false.

See the examples in the validation module.

Specifies a post-deserialization validation for the config. This is useful to check invariants involving multiple params. Multiple validations are supported by specifying the attribute multiple times.

§tag

Type: string

Specifies the param name holding the enum tag, similar to the corresponding attribute in serde. Unlike serde, this attribute is required for enums; this is to ensure that source merging is well-defined.

§rename_all

Type: string; one of lowercase, UPPERCASE, camelCase, snake_case, SCREAMING_SNAKE_CASE, kebab-case, SCREAMING-KEBAB-CASE

Renames all variants in an enum config according to the provided transform. Unlike in serde, this attribute only works on enum variants. Params / sub-configs are always expected to have snake_case naming.

Caveats:

  • rename_all assumes that original variant names are in PascalCase (i.e., follow Rust naming conventions).
  • rename_all requires original variant names to consist of ASCII chars.
  • Each letter of capitalized acronyms (e.g., “HTTP” in HTTPServer) is treated as a separate word. E.g., rename_all = "snake_case" will rename HTTPServer to h_t_t_p_server. Note that it is recommended to not capitalize acronyms (i.e., use HttpServer).
  • No spacing is inserted before numbers or other non-letter chars. E.g., rename_all = "snake_case" will rename Status500 to status500, not to status_500.

§derive(Default)

Derives Default according to the default values of params (+ the default variant for enum configs). To work, all params must have a default value specified.

§Variant attributes

§rename, alias

Type: string

Have the same meaning as in serde; i.e. allow to rename / specify additional names for the tag(s) corresponding to the variant. alias can be specified multiple times.

§default

If specified, marks the variant as default – one which will be used if the tag param is not set in the input. At most one variant can be marked as default.

§Field attributes

§rename, alias

Type: string

Have the same meaning as in serde; i.e. allow to rename / specify additional names for the param or a nested config. Names are validated in compile time.

In addition to simple names, path aliases are supported as well. A path alias starts with . and consists of dot-separated segments, e.g. .experimental.value or ..value. The paths are resolved relative to the config prefix. As in Python, more than one dot at the start of the path signals that the path is relative to the parent(s) of the config.

  • alias = ".experimental.value" with config prefix test resolves to the absolute path test.experimental.value.
  • alias = "..value" with config prefix test.experimental resolves to the absolute path test.value.

If an alias requires more parents than is present in the config prefix, the alias is not applicable. (E.g., alias = "...value" with config prefix test.)

Path aliases are somewhat difficult to reason about, so avoid using them unless necessary.

§deprecated

Type: string

Similar to alias, with the difference that the alias is marked as deprecated in the schema docs, and its usages are logged on the WARN level.

§default

Type: path to function (optional)

Has the same meaning as in serde, i.e. allows to specify a constructor of the default value for the param. Without a value, Default is used for this purpose. Unlike serde, the path shouldn’t be quoted.

§default_t

Type: expression with param type

Allows to specify the default typed value for the param. The provided expression doesn’t need to be constant.

§example

Type: expression with field type

Allows to specify the example value for the param. The example value can be specified together with the default / default_t attribute. In this case, the example value can be more “complex” than the default, to better illustrate how the configuration works.

§fallback

Type: constant expression evaluating to &'static dyn FallbackSource

Allows to provide a fallback source for the param. See the fallback module docs for the discussion of fallbacks and intended use cases.

§with

Type: const expression implementing DeserializeParam

Allows changing the param deserializer. See de module docs for the overview of available deserializers. For Options, with refers to the internal type deserializer; it will be wrapped into an Optional automatically.

Note that there is an alternative: implementing WellKnown for the param type.

§nest

If specified, the field is treated as a nested sub-config rather than a param. Correspondingly, its type must implement DescribeConfig, or wrap such a type in an Option.

§flatten

If specified, the field is treated as a flattened sub-config rather than a param. Unlike nest, its params will be added to the containing config instead of a separate object. The sub-config type must implement DescribeConfig.

§validate

Has same semantics as config validations, but applies to a specific config parameter.

§deserialize_if

Type: same as config validations

Filters an Optional value. This is useful to coerce semantically invalid values (e.g., empty strings for URLs) to None in the case automated null coercion doesn’t apply. See the validation module for examples of usage.

§Validations

The following validations are performed by the macro in compile time:

  • Param / sub-config names and aliases must be non-empty, consist of lowercase ASCII alphanumeric chars or underscore and not start with a digit (i.e., follow the [a-z_][a-z0-9_]* regex).
  • Param names / aliases cannot coincide with nested config names.

§Examples

use smart_config::metadata::TimeUnit;

#[derive(DescribeConfig, DeserializeConfig)]
struct TestConfig {
    /// Doc comments are parsed as a description.
    #[config(default_t = 3)]
    int: u32,
    #[config(default)] // multiple `config` attrs are supported
    #[config(rename = "str", alias = "string")]
    renamed: String,
    /// Nested sub-config. E.g., the tag will be read from path `nested.version`.
    #[config(nest)]
    nested: NestedConfig,
    /// Flattened sub-config. E.g., `array` param will be read from `array`, not `flat.array`.
    #[config(flatten)]
    flat: FlattenedConfig,
}

#[derive(DescribeConfig, DeserializeConfig)]
#[config(tag = "version", rename_all = "snake_case", derive(Default))]
enum NestedConfig {
    #[config(default)]
    V0,
    #[config(alias = "latest")]
    V1 {
        /// Param with a custom deserializer. In this case, it will deserialize
        /// a duration from a number with milliseconds unit of measurement.
        #[config(default_t = Duration::from_millis(50), with = TimeUnit::Millis)]
        latency_ms: Duration,
        /// `Vec`s, sets and other containers are supported out of the box.
        set: HashSet<NonZeroUsize>,
    },
}

#[derive(DescribeConfig, DeserializeConfig)]
struct FlattenedConfig {
    #[config(default = FlattenedConfig::default_array)]
    array: [f32; 2],
}

impl FlattenedConfig {
    const fn default_array() -> [f32; 2] { [1.0, 2.0] }
}