Skip to content

Feature: Dynamic config registration from multiple crates via inventory pattern #7

@fasterthanlime

Description

@fasterthanlime

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions