A typed, friendly configuration system for Apollo products.
The #[configuration] attribute
apollo-configuration's #[configuration] attribute provides:
- Parsing of YAML files with friendly error reporting
- JSON schema generation
- Validation of user input according to that JSON schema
- Defaulting of field values
- Expansion of environment variables
use configuration;
use parse_yaml;
JSON Schema Generation
Use [export_json_schema] to generate a JSON schema for a [Configuration] struct.
YAML Parsing
Use [parse_yaml] to parse and validate YAML text into a [Configuration] struct.
Validation
Configuration structs are validated using JSON Schema, and each field is validated according to
its own validation rules. It's common for configuration structures to
require "cross-cutting" validation: for example, disallowing two features that cannot be used
together. To do things like this, you can add an additional custom validation function using
the #[configuration(validate)] attribute.
use configuration;
use ErrorCollector;
The #[config] attribute
The #[config] attribute can be used on the structure's fields to change how fields are
parsed, validated, and displayed.
Optional/Required fields
All fields are assumed to be optional by default. The type's [Default] impl is used when a
field is absent during deserialization. If a field is required, mark it with
#[config(required)]. If a configuration structure contains any required fields, it will not
implement [Default].
If a field's type does not have a [Default] impl, you can provide a default value:
# use NonZeroUsize;
# use configuration;
#
Field validation
apollo-configuration supports schema-level validation and fully custom validation.
Schema-level validation works by using one of the below sub-attributes inside the #[config]
attribute:
range(min = 0, max = 1024)- Restrict the value of an integer field to the given range.length(min = 0, max = 256)- Restrict the length of a string or array field to the given range.length(exact = 10)- Require a string or array field to have the given length exactly.pattern(r"^\d+$")- Require a string field to match the given regular expression.
Schema-level validation is also reflected in the generated JSON schema.
Custom validation works by specifying a validation function in the #[config(validate)]
attribute:
use configuration;
use ErrorCollector;
Custom validation is not reflected in the JSON schema. Generally, schema-level validation
should be preferred. However, custom validation supports rich diagnostics using
[ErrorCollector::report], so it can still be a good choice if schema-level validation gives poor
errors.
Custom validation can be disabled using the #[config(skip_validate)] attribute. This
can be necessary when using external types that do not implement Validate:
use configuration;
Expansion
Your users can write ${env.VAR_NAME} syntax inside YAML values to insert the value of an
environment variable into the configuration. This can be used to include more dynamic values
or secrets without having to write them into the configuration file.
It is an error to reference an environment variable that is not defined. The bash-style
defaulting syntax can be used to handle optional environment variables:
${env.VAR_NAME:-default}.
If your configuration structure contains secrets, API keys, passwords, or sensitive URLs,
remember to use a redaction helper such as apollo-redaction.
Types for use as configuration values
Primitive Rust types and several ecosystem types are supported as configuration values.
The [types] module provides wrappers for common external types that can be
used in configuration.
Available newtypes:
- [
types::Duration] - human-readable format (30s,5m,1h30m,100ms) - [
types::IpAddr], [types::Ipv4Addr], [types::Ipv6Addr] - [
types::SocketAddr], [types::SocketAddrV4], [types::SocketAddrV6] - [
types::Url] - [
types::Method], [types::Uri], [types::HeaderName], [types::HeaderValue] - [
types::MetadataKey], [types::MetadataValue] - gRPC metadata types - [
types::Regex]
To enable these types add the appropriate feature to the apollo-configuration dependency:
duration, grpc, http, regex, url
All newtypes support wrapping with [apollo_redaction::Redacted] for sensitive values
like API keys or passwords:
use configuration;
use Redacted;
let config: ApiConfig = parse_yaml.unwrap;
// The value is accessible but protected from accidental logging
assert_eq!;
assert_eq!;
See the apollo-redaction crate for details.
Other types
To use a type as a configuration value, it must implement these traits:
- [
Debug] - [
schemars::JsonSchema] - [
serde::Deserialize]
Where it makes sense, types used in configuration values should also implement [Clone],
[Default], and [serde::Serialize].
Enum configuration
Enums can use the #[configuration] attribute. This is useful for configuration options
where the user selects between different modes or backends.
use configuration;
Default variant
Mark a variant with #[config(default)] to make the enum implement [Default]. When parsing
from YAML, omitted enum fields will use this default:
use configuration;
use parse_yaml;
let config: Config = parse_yaml.unwrap;
assert_eq!;
let config: Config = parse_yaml.unwrap;
assert_eq!;
Unit variant fields
Unit variant enums can be parsed directly from string values in YAML. The #[configuration]
attribute applies #[serde(rename_all = "snake_case")] automatically:
use configuration;
let config: DemandControlConfig = parse_yaml.unwrap;
assert_eq!;
Struct variant fields
Struct variant fields support the same #[config(...)] attributes as struct fields:
use configuration;
Tuple variant fields
Tuple variants wrap a single inner type. The #[config(...)] attributes apply to the inner
type:
use configuration;