Configuration

Roadster provides sensible defaults, but is highly customizable via configuration files and environment variables. Virtually all behavior of Roadster can be configured; to see the available configuration keys, see the AppConfig struct.

To see the full app config that's loaded for your app, use the print-config CLI command. This will print the app config after all the config sources (files, env vars, cli args) have been loaded and merged.

cargo run -- roadster print-config -f toml

Configuration files

The primary way to customize the app's settings is via configuration files. Roadster supports Toml config files by default, and supports Yaml config files with the config-yml feature flag. By default, Roadster looks for config files in the config directory of your project (at the same level as your Cargo.toml). However, if you have the cli feature flag enabled, you can specify a different config directory using the --config-dir CLI parameter.

You can provide a default configuration and override the defaults for each environment stage. Config files can either be in a file named default or the environment name, or in a directory matching the environment name. Example config file structure:

my-app/
└── config/
    ├── default.toml
    ├── development.toml
    ├── test.toml
    ├── production.toml
    ├── default/
    │   └── db.toml
    ├── development/
    │   └── db.toml
    ├── test/
    │   └── db.toml
    └── production/
        └── db.toml

If there are multiple files in an environment's directory, they are loaded into the config in lexicographical order, and the last file loaded takes precedence if there are duplicate fields in the files. For example, if an environment directory contains the files a.toml and b.toml, a.toml is loaded first and b.toml is loaded second, and any duplicate fields in b.toml will override the values from a.toml.

Environment variables

You can set environment variables to customize fields. This is useful to provide values for sensitive fields such as DB passwords. Note that setting passwords as env vars does come with some amount of security risk as they are readable by anyone who has access to your server, but they're better than checking your sensitive values into git or other source control.

Env vars can either be set on the command line, or in a .env file. Environment variables should be prefixed with ROADSTER__ named according to the AppConfig structure, where each level of the structure is separated by a double underscore (__). For example, to override the AppConfig#environment, you would use an environment variable named ROADSTER__ENVIRONMENT, and to override AppConfig#app#shutdown_on_error, you would use ROADSTER__APP__SHUTDOWN_ON_ERROR. E.g.:

export ROADSTER__ENVIRONMENT=dev
export ROADSTER__APP__SHUTDOWN_ON_ERROR=true

Custom Sources

You can also provide one or more Sources to add to the configuration. This is primarilty intended to allow overriding specific app config fields for tests, but it can also be used to provide other custom config sources outside of tests.

type App = RoadsterApp<AppContext>;

async fn prepare_app(app: App) -> RoadsterResult<PreparedApp<App, AppContext>> {
    /*
    Config fields can be set using the name of the field, where each level in the config
    is separated by a `.`

    For example, `service.sidekiq.redis.uri` overrides the `AppConfig#service#sidekiq#redis#uri` field.
    See: <https://docs.rs/roadster/latest/roadster/config/service/worker/sidekiq/struct.Redis.html#structfield.uri>

    Note: Take care to not hard-code any sensitive values when providing a custom config source.
    However, it may be okay to hard-code a generic local connection URI (as we're doing here) if
    it's only used for testing (the primary intended purpose of allowing custom `Source`s).
     */
    let options = PrepareOptions::builder()
        .add_config_source(
            ConfigOverrideSource::builder()
                .name("service.sidekiq.redis.uri")
                .value("redis://localhost:6379")
                .build(),
        )
        .build();
    let app = prepare(app, options).await?;
    Ok(app)
}

Custom Async sources

You can also provide one or more AsyncSources to add to the configuration. This is useful to load configuration fields (particularly sensitive ones) from an external service, such as AWS or GCS secrets manager services.

AsyncSources are loaded into the configuration after all the other sources, so they have the highest precedence (they will override any duplicate fields from other sources).

#[derive(Debug)]
pub struct ExampleAsyncSource;

#[async_trait]
impl AsyncSource for ExampleAsyncSource {
    async fn collect(&self) -> Result<config::Map<String, Value>, ConfigError> {
        let mut config = config::Map::new();

        /*
        Config fields can be set using the name of the field, where each level in the config
        is separated by a `.`

        For example, `service.sidekiq.redis.uri` overrides the `AppConfig#service#sidekiq#redis#uri` field.
        See: <https://docs.rs/roadster/latest/roadster/config/service/worker/sidekiq/struct.Redis.html#structfield.uri>

        Note: a hard-coded value is used here for demonstration purposes only. In a real application,
        an `AsyncSource` is intended to fetch the value from an external service, such as AWS or GCS
        secrets manager services.
         */
        config.insert(
            "service.sidekiq.redis.uri".into(),
            "redis://localhost:6379".into(),
        );

        Ok(config)
    }
}

type App = RoadsterApp<AppContext>;

fn build_app() -> App {
    let builder = RoadsterApp::builder();

    let builder = builder.add_async_config_source(ExampleAsyncSource);

    let builder = builder.state_provider(|context| Ok(context));
    builder.build()
}

Config mechanism precedence

  • default.toml (lowest)
  • default/<filename>.toml
  • <env>.toml
  • <env>/<filename>.toml
  • Environment variables
  • Sources
  • AsyncSources (highest -- overrides lower precedence values)

If the config-yml feature is enabled, files with extensions .yml and .yaml. The precedence of all supported file extensions is the following:

  • .yml (lowest)
  • .yaml
  • .toml (highest -- overrides lower precedence values)

Environment names

Roadster provides some pre-defined environment names in the Environment enum.

  • development (alias: dev)
  • test
  • production (alias: prod)

In addition, apps can define a custom environment name, which is mapped to the Environment::Custom enum variant. This is useful for special app-specific environments, such as additional pre-prod or canary environments.