Expand description
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 apollo_configuration::configuration;
use apollo_configuration::parse_yaml;
#[configuration]
struct CsrfConfig {
#[config(default = true)]
enabled: bool,
#[config(default = default_required_headers())]
required_headers: Vec<String>,
}
fn default_required_headers() -> Vec<String> {
vec![
"x-apollo-operation-name".to_string(),
"apollo-require-preflight".to_string(),
]
}§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 apollo_configuration::configuration;
use apollo_configuration::ErrorCollector;
#[configuration]
struct HomepageConfig {
enabled: bool,
graph_ref: Option<String>,
}
#[configuration]
struct ApolloSandboxConfig {
enabled: bool,
}
#[configuration(validate = validate_config)]
struct Config {
homepage: HomepageConfig,
sandbox: ApolloSandboxConfig,
}
fn validate_config(config: &Config, mut errors: ErrorCollector<'_>) {
if config.homepage.enabled && config.sandbox.enabled {
errors.nest("sandbox")
.report_simple("sandbox can only be enabled if homepage is disabled");
}
}§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:
#[configuration]
struct CacheConfig {
#[config(default = NonZeroUsize::new(500).unwrap())]
cache_size: NonZeroUsize,
}§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 apollo_configuration::configuration;
use apollo_configuration::ErrorCollector;
#[configuration]
struct Config {
#[config(validate = validate_div_by_3)]
magic_number: usize,
}
fn validate_div_by_3(magic_number: &usize, mut errors: ErrorCollector<'_>) {
if !magic_number.is_multiple_of(3) {
errors.report_simple("magic number must be divisible by 3");
}
}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 apollo_configuration::configuration;
#[configuration]
struct Config {
#[config(skip_validate, default = tinystr::TinyAsciiStr::try_from_utf8(b"unnamed").unwrap())]
#[schemars(with = "String")]
name: tinystr::TinyAsciiStr<16>,
}§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::Ipv6Addrtypes::SocketAddr,types::SocketAddrV4,types::SocketAddrV6types::Urltypes::Method,types::Uri,types::HeaderName,types::HeaderValuetypes::MetadataKey,types::MetadataValue- gRPC metadata typestypes::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 apollo_configuration::configuration;
use apollo_redaction::Redacted;
#[configuration]
struct ApiConfig {
#[config(required)]
api_key: Redacted<String>,
}
let config: ApiConfig = apollo_configuration::parse_yaml(
"api_key: secret-key-123",
&Default::default(),
).unwrap();
// The value is accessible but protected from accidental logging
assert_eq!(config.api_key.unredact(), "secret-key-123");
assert_eq!(config.api_key.to_string(), "[REDACTED]");See the apollo-redaction crate for details.
§Other types
To use a type as a configuration value, it must implement these traits:
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 apollo_configuration::configuration;
#[configuration]
enum Endpoint {
Http { url: String, timeout: u64 },
Grpc { host: String, port: u16 },
}§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 apollo_configuration::configuration;
use apollo_configuration::parse_yaml;
#[configuration]
#[derive(PartialEq, Eq)]
enum LogLevel {
Debug,
#[config(default)]
Info,
Warn,
Error,
}
#[configuration]
#[derive(PartialEq, Eq)]
struct Config {
log_level: LogLevel,
}
let config: Config = parse_yaml(r#"log_level: debug"#, &Default::default()).unwrap();
assert_eq!(config.log_level, LogLevel::Debug);
let config: Config = parse_yaml(r#"{}"#, &Default::default()).unwrap();
assert_eq!(config.log_level, LogLevel::Info);§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 apollo_configuration::configuration;
#[configuration]
#[derive(PartialEq, Eq)]
enum EnabledMode {
Measure,
#[config(default)]
Enforce,
}
#[configuration]
struct DemandControlConfig {
enabled: bool,
mode: EnabledMode,
}
let config: DemandControlConfig = apollo_configuration::parse_yaml(r#"
enabled: true
mode: measure
"#, &Default::default()).unwrap();
assert_eq!(config.mode, EnabledMode::Measure);§Struct variant fields
Struct variant fields support the same #[config(...)] attributes as struct fields:
use apollo_configuration::configuration;
#[configuration]
enum Endpoint {
#[config(default)]
Http {
#[config(default = "http://localhost".to_string())]
url: String,
#[config(default = 30)]
timeout: u64,
},
Grpc {
#[config(required)]
host: String,
#[config(default = 9090)]
port: u16,
},
}§Tuple variant fields
Tuple variants wrap a single inner type. The #[config(...)] attributes apply to the inner
type:
use apollo_configuration::configuration;
#[configuration]
struct HttpConfig {
#[config(default = "http://localhost".to_string())]
url: String,
}
#[configuration]
enum Endpoint {
#[config(default)]
Http(HttpConfig),
Custom(String),
}Modules§
- expansion
- Traits and types to customize
${}expansion functionality. - types
- Type support for configuration values.
Structs§
- Error
Collector - Error collector for custom validation rules.
- Expand
Error - Overridable
- Parse
Yaml Options - Options for parsing configuration from YAML.
- Validation
Error - A JSON schema validation error.
- Validation
Errors - A list of JSON schema validation errors.
Enums§
- Config
Error - Top-level error type.
- Yaml
ToJson Error - Errors that can occur when transforming the YAML input to a JSON-compatible value.
Traits§
- Configuration
- Marker trait for configuration structs. See the
#[configuration]proc macro for documentation. - Validate
- Validation trait for configuration values.
Functions§
- export_
json_ schema - Return a JSON schema for the given configuration structure.
- parse_
yaml - validate
- Validate a programmatically created value.
Attribute Macros§
- configuration
- Turn a Rust structure or enum into a configuration structure.