Summary
Allow different crates within a project to independently define and register their own config types, which would then be automatically combined without requiring a manual "big combination struct" at the application level.
Motivation
From facet-rs/facet#1905:
I often find myself building eg a server composed of several crates with "different" functionality but all requiring config. It would be REALLY nice to let them all define their own config individually, and access it individually without having to somewhere create a big combination of them.
Ie in a crate "functionality1" define Config and let the code in that crate dynamically register (and later access) that "I want this Config be part of what should be required config - for whatever application uses me.
This pattern is similar to what perkeep (Go project) implements, allowing decoupling between different parts of an application that can be reused in multiple contexts.
Proposed API (conceptual)
In library crate A:
#[derive(Facet)]
#[facet(args::register_config = "database")] // or via inventory
struct DatabaseConfig {
host: String,
port: u16,
}
In library crate B:
#[derive(Facet)]
#[facet(args::register_config = "cache")]
struct CacheConfig {
redis_url: String,
ttl_secs: u64,
}
In application:
let config = figue::builder_with_registered()?
.include_registered(filter_by_crate_name, optional_field_name_override)
.cli(|c| c.args(std::env::args().skip(1)))
.build();
// Access registered configs
let db_config: &DatabaseConfig = config.get("database")?;
let cache_config: &CacheConfig = config.get("cache")?;
The field name becomes the prefix, so environment variables would be:
APP__DATABASE__HOST
APP__DATABASE__PORT
APP__CACHE__REDIS_URL
APP__CACHE__TTL_SECS
Current Workaround
Today, this can be achieved manually via #[facet(flatten)]:
#[derive(Facet)]
struct AppConfig {
#[facet(flatten)]
database: crate_a::DatabaseConfig,
#[facet(flatten)]
cache: crate_b::CacheConfig,
}
However, this requires the application to explicitly enumerate all config types, which doesn't scale well for plugin-style architectures.
Implementation Considerations
- The
inventory crate could be used for the registration mechanism
- Schema would need to be built at runtime by collecting registered types
- Type safety for access might require a type map pattern or
TypeId-based lookup
- Potential for field name conflicts needs addressing (error? prefix with crate name?)
- Order of registration may affect help output
Related
Summary
Allow different crates within a project to independently define and register their own config types, which would then be automatically combined without requiring a manual "big combination struct" at the application level.
Motivation
From facet-rs/facet#1905:
This pattern is similar to what perkeep (Go project) implements, allowing decoupling between different parts of an application that can be reused in multiple contexts.
Proposed API (conceptual)
In library crate A:
In library crate B:
In application:
The field name becomes the prefix, so environment variables would be:
APP__DATABASE__HOSTAPP__DATABASE__PORTAPP__CACHE__REDIS_URLAPP__CACHE__TTL_SECSCurrent Workaround
Today, this can be achieved manually via
#[facet(flatten)]:However, this requires the application to explicitly enumerate all config types, which doesn't scale well for plugin-style architectures.
Implementation Considerations
inventorycrate could be used for the registration mechanismTypeId-based lookupRelated
src/schema/from_schema.rs(lines 410-481)src/builder.rs