yodel
Yodel is a type-safe configuration loader for Gleam that handles JSON, YAML, and TOML configs with automatic format detection, environment variable resolution, profile-based configuration, and an intuitive dot-notation API.
Quick Start
import yodel
let assert Ok(ctx) = yodel.load("config.toml")
yodel.get_string(ctx, "database.host") // "localhost"
Key Features
Multiple Format Support
Load JSON, YAML, or TOML configuration files with automatic format detection:
yodel.load("config.yaml") // Auto-detects YAML
yodel.load("config.toml") // Auto-detects TOML
yodel.load("config.json") // Auto-detects JSON
Environment Variable Resolution
Use placeholders to inject environment variables into your configuration:
export DATABASE_HOST="prod.db.example.com"
export API_KEY="secret-key-123"
# config.yaml
database:
host: ${DATABASE_HOST}
password: ${DB_PASSWORD:default-password}
api:
key: ${API_KEY}
Placeholders support:
- Simple substitution:
${VAR_NAME}
- Default values:
${VAR_NAME:default}
- Nested placeholders:
${VAR1:${VAR2:fallback}}
Profile-Based Configuration
Manage environment-specific configurations with profiles that automatically merge over your base configuration:
config/
├── config.yaml # Base configuration (all environments)
├── config-dev.yaml # Development overrides
├── config-staging.yaml # Staging overrides
└── config-prod.yaml # Production overrides
Activate profiles via environment variable:
export YODEL_PROFILES=dev,local
// Loads config.yaml, then config-dev.yaml, then config-local.yaml
// Later profiles override earlier ones
let assert Ok(ctx) = yodel.load("./config")
Or programmatically:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.with_profiles(["dev", "local"])
|> yodel.load_with_options("./config")
Note: The YODEL_PROFILES
environment variable takes precedence over
programmatically set profiles, allowing runtime override without code changes.
This makes it easy to change environments via deployment configuration.
Type-Safe Value Access
Access configuration values with type safety and helpful error messages:
let assert Ok(ctx) = yodel.load("config.yaml")
// Get values with type checking
let assert Ok(host) = yodel.get_string(ctx, "database.host")
let assert Ok(port) = yodel.get_int(ctx, "database.port")
let assert Ok(timeout) = yodel.get_float(ctx, "api.timeout")
let assert Ok(enabled) = yodel.get_bool(ctx, "features.new_ui")
// Provide defaults for optional values
let host = yodel.get_string_or(ctx, "cache.host", "localhost")
let port = yodel.get_int_or(ctx, "cache.port", 6379)
// Parse values from strings when needed
let assert Ok(port) = yodel.parse_int(ctx, "port") // "8080" → 8080
Types
Errors that can occur when loading or parsing configuration.
pub type ConfigError =
errors.ConfigError
The Context type, representing a loaded configuration.
This is an opaque type that holds your parsed configuration values.
Create a context using load()
or load_with_options()
, then access
values using the getter functions like get_string()
, get_int()
, etc.
let assert Ok(ctx) = yodel.load("config.yaml")
let value = yodel.get_string(ctx, "database.host")
pub type Context =
@internal Context
Errors that occur when reading configuration files from disk.
pub type FileError =
errors.FileError
The format of a configuration file.
Use the constants format_auto
(default), format_json
, format_toml
, or format_yaml
,
or pass this type to with_format()
.
pub type Format =
options.Format
Location of a syntax error in the source file.
pub type Location =
errors.Location
Configuration options for loading config files.
Create using default_options()
and configure using builder functions:
let ctx =
yodel.default_options()
|> yodel.as_yaml()
|> yodel.with_resolve_strict()
|> yodel.load_with_options("config.yaml")
pub type Options =
options.Options
Errors that occur when parsing configuration content.
pub type ParseError =
errors.ParseError
Errors that occur when accessing configuration values.
pub type PropertiesError =
errors.PropertiesError
The resolve mode for environment variable placeholders.
Use the constants resolve_strict
or resolve_lenient
, or pass this type
to with_resolve_mode()
.
resolve_strict
: Fail if any placeholder cannot be resolvedresolve_lenient
: Preserve unresolved placeholders as-is (default)
pub type ResolveMode =
options.ResolveMode
Errors that occur when resolving environment variable placeholders.
pub type ResolverError =
errors.ResolverError
Syntax errors with location information.
pub type SyntaxError =
errors.SyntaxError
Type mismatch errors when accessing configuration values.
The got
field contains the actual value that was found, which can be
helpful for debugging configuration issues.
pub type TypeError =
errors.TypeError
Errors that occur during configuration validation.
pub type ValidationError =
errors.ValidationError
Represents a value stored in the configuration.
This type appears in TypeError
when a type mismatch occurs, allowing
you to see what value was actually present in the configuration.
pub type Value =
value.Value
Values
pub fn as_auto(
options options: options.Options,
) -> options.Options
Attempt to automatically detect the format of the configuration file.
If the input is a file, we first try to detect the format from the file extension. If that fails, we try to detect the format from the content of the file.
If the input is a string, we try to detect the format from the content.
If Auto Detection fails, an error will be returned because we can’t safely proceed.
If this happens, try specifying the format using as_json
, as_toml
, as_yaml
, or with_format
.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.as_auto()
|> yodel.load_with_options("config.yaml")
pub fn as_json(
options options: options.Options,
) -> options.Options
Set the format of the configuration file to JSON.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.as_json()
|> yodel.load_with_options(config_content)
pub fn as_toml(
options options: options.Options,
) -> options.Options
Set the format of the configuration file to TOML.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.as_toml()
|> yodel.load_with_options("config.toml")
pub fn as_yaml(
options options: options.Options,
) -> options.Options
Set the format of the configuration file to YAML.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.as_yaml()
|> yodel.load_with_options("config.json")
pub fn default_options() -> options.Options
The default options for loading a configuration file.
Default Options:
- Format:
format_auto
- Resolve Enabled:
True
- Resolve Mode:
resolve_lenient
- Config Base Name:
"config"
- Profile Environment Variable:
"YODEL_PROFILES"
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.load_with_options("config.toml")
pub fn describe_config_error(error: errors.ConfigError) -> String
Format a ConfigError
into a human-readable string.
pub const format_auto: options.Format
Attempt to automatically detect the format of the configuration file.
If the input is a file, we first try to detect the format from the file extension. If that fails, we try to detect the format from the content of the file.
If the input is a string, we try to detect the format from the content.
If Auto Detection fails, an error will be returned because we can’t safely proceed.
If this happens, try specifying the format using as_json
, as_toml
, as_yaml
, or with_format
.
This is the default.
pub fn get_bool(
ctx: @internal Context,
key: String,
) -> Result(Bool, errors.PropertiesError)
Get a boolean value from the configuration. If the value is not a boolean, an error will be returned.
Example:
case yodel.get_bool(ctx, "foo") {
Ok(value) -> value // True
Error(e) -> Error(e)
}
pub fn get_bool_or(
ctx: @internal Context,
key: String,
default: Bool,
) -> Bool
Get a boolean value from the configuration, or a default value if the key is not found.
Example:
let value = yodel.get_bool_or(ctx, "foo", False)
pub fn get_float(
ctx: @internal Context,
key: String,
) -> Result(Float, errors.PropertiesError)
Get a float value from the configuration. If the value is not a float, an error will be returned.
Example:
case yodel.get_float(ctx, "foo") {
Ok(value) -> value // 42.0
Error(e) -> Error(e)
}
pub fn get_float_or(
ctx: @internal Context,
key: String,
default: Float,
) -> Float
Get a float value from the configuration, or a default value if the key is not found.
Example:
let value = yodel.get_float_or(ctx, "foo", 42.0)
pub fn get_int(
ctx: @internal Context,
key: String,
) -> Result(Int, errors.PropertiesError)
Get an integer value from the configuration. If the value is not an integer, an error will be returned.
Example:
case yodel.get_int(ctx, "foo") {
Ok(value) -> value // 42
Error(e) -> Error(e)
}
pub fn get_int_or(
ctx: @internal Context,
key: String,
default: Int,
) -> Int
Get an integer value from the configuration, or a default value if the key is not found.
Example:
let value = yodel.get_int_or(ctx, "foo", 42)
pub fn get_string(
ctx: @internal Context,
key: String,
) -> Result(String, errors.PropertiesError)
Get a string value from the configuration. If the value is not a string, an error will be returned.
Example:
case yodel.get_string(ctx, "foo") {
Ok(value) -> value // "bar"
Error(e) -> Error(e)
}
pub fn get_string_or(
ctx: @internal Context,
key: String,
default: String,
) -> String
Get a string value from the configuration, or a default value if the key is not found.
Example:
let value = yodel.get_string_or(ctx, "foo", "default")
pub fn load(
from input: String,
) -> Result(@internal Context, errors.ConfigError)
Load a configuration file.
This function will read the config content, detect the format,
resolve the placeholders, parse the config content, returning a Context
if successful.
input
can be a file path or a string containing the configuration content.
Example with file path:
let assert Ok(ctx) = yodel.load("config.toml")
Example with string content:
let yaml_content = "database:\n host: localhost"
case yodel.load(yaml_content) {
Ok(ctx) -> ctx
Error(e) -> {
// Handle error appropriately
panic as yodel.describe_config_error(e)
}
}
pub fn load_with_options(
with options: options.Options,
from input: String,
) -> Result(@internal Context, errors.ConfigError)
Load a configuration file with options.
This function will use the provided options to read and parse the config content,
returning a Context
if successful.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.as_yaml()
|> yodel.with_resolve_strict()
|> yodel.load_with_options("config.yaml")
pub fn parse_bool(
ctx: @internal Context,
key: String,
) -> Result(Bool, errors.PropertiesError)
Parse a bool value from the configuration.
If the value is not a bool, it will be converted to a bool. An error will be returned if the value is not a bool or cannot be converted to a bool.
Example:
case yodel.parse_bool(ctx, "foo") {
Ok(value) -> value // True
Error(e) -> Error(e)
}
pub fn parse_float(
ctx: @internal Context,
key: String,
) -> Result(Float, errors.PropertiesError)
Parse a float value from the configuration.
If the value is not a float, it will be converted to a float. An error will be returned if the value is not a float or cannot be converted to a float.
Example:
case yodel.parse_float(ctx, "foo") {
Ok(value) -> value // 99.999
Error(e) -> Error(e)
}
pub fn parse_int(
ctx: @internal Context,
key: String,
) -> Result(Int, errors.PropertiesError)
Parse an integer value from the configuration.
If the value is not an integer, it will be converted to an integer. An error will be returned if the value is not an integer or cannot be converted to an integer.
Example:
case yodel.parse_int(ctx, "foo") {
Ok(value) -> value // 42
Error(e) -> Error(e)
}
pub fn parse_string(
ctx: @internal Context,
key: String,
) -> Result(String, errors.PropertiesError)
Parse a string value from the configuration.
If the value is not a string, it will be converted to a string. An error will be returned if the value is not a string or cannot be converted to a string.
Example:
case yodel.parse_string(ctx, "foo") {
Ok(value) -> value // "42"
Error(e) -> Error(e)
}
pub fn resolve_disabled(
options options: options.Options,
) -> options.Options
Disable placeholder resolution.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.resolve_disabled()
|> yodel.load_with_options("config.yaml")
pub fn resolve_enabled(
options options: options.Options,
) -> options.Options
Enable placeholder resolution.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.resolve_enabled()
|> yodel.load_with_options("config.yaml")
pub const resolve_lenient: options.ResolveMode
Lenient Resolve Mode - Preserve unresolved placeholders.
This means ${foo}
will remain as ${foo}
if foo
is not defined.
This is the default.
pub const resolve_strict: options.ResolveMode
Strict Resolve Mode - Fail if any placeholder is unresolved.
pub fn with_config_base_name(
options options: options.Options,
config_base_name config_base_name: String,
) -> options.Options
Set the base name for configuration files when loading from a directory.
The base name is used to identify configuration files matching the pattern
{base_name}[-{profile}].{ext}
. For example, with base name "settings"
:
settings.yaml
→ base configsettings-dev.yaml
→ dev profilesettings-prod.toml
→ prod profile
Default: "config"
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.with_config_base_name("settings")
|> yodel.load_with_options("./config-dir")
// Looks for: settings.yaml, settings-dev.yaml, etc.
pub fn with_format(
options options: options.Options,
format format: options.Format,
) -> options.Options
Set the format of the configuration file.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.with_format(yodel.format_json)
|> yodel.load_with_options("config.json")
pub fn with_profile_env_var(
options options: options.Options,
profile_env_var profile_env_var: String,
) -> options.Options
Set the environment variable name used to read active profiles.
By default, Yodel reads the YODEL_PROFILES
environment variable.
Use this to customize the environment variable name.
Default: "YODEL_PROFILES"
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.with_profile_env_var("APP_PROFILES")
|> yodel.load_with_options("./config")
// Now reads from APP_PROFILES instead of YODEL_PROFILES
pub fn with_profiles(
options options: options.Options,
profiles profiles: List(String),
) -> options.Options
Set the active configuration profiles to load.
Profiles allow environment-specific configuration overrides. Profile configs are merged in the order specified, with later profiles overriding earlier ones.
Note: The YODEL_PROFILES
environment variable takes precedence over
programmatically set profiles.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.with_profiles(["dev", "local"])
|> yodel.load_with_options("./config-dir")
// Loads: config.yaml, config-dev.yaml, config-local.yaml
// Values in config-local.yaml override config-dev.yaml
// Values in config-dev.yaml override config.yaml
pub fn with_resolve_enabled(
options options: options.Options,
resolve_enabled resolve_enabled: Bool,
) -> options.Options
Enable or disable placeholder resolution.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.with_resolve_enabled(False)
|> yodel.load_with_options("config.yaml")
pub fn with_resolve_lenient(
options options: options.Options,
) -> options.Options
Set the resolve mode to lenient.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.with_resolve_lenient()
|> yodel.load_with_options(config_content)
pub fn with_resolve_mode(
options options: options.Options,
resolve_mode resolve_mode: options.ResolveMode,
) -> options.Options
Set the resolve mode.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.with_resolve_mode(yodel.resolve_strict)
|> yodel.load_with_options("config.json")
pub fn with_resolve_strict(
options options: options.Options,
) -> options.Options
Set the resolve mode to strict.
Example:
let assert Ok(ctx) =
yodel.default_options()
|> yodel.with_resolve_strict()
|> yodel.load_with_options(config_content)