orale

package module
v1.9.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 10, 2026 License: MIT Imports: 19 Imported by: 0

README

Órale (Oh-rah-leh)

A fantastic little config loader for Go that collects configuration from flags, environment variables, and configuration files, then marshals the values into your structs.

Órale is pronounced "Odelay" in English. The name is Mexican slang which translates roughly to "listen up" or "what's up?", and is pronounced like "Oh-rah-leh".



Features

  • 🎯 Single unified API - Load from flags, environment variables, and config files
  • 📝 Multiple sources - Configurable precedence: flags > environment > config files
  • 🔄 Automatic type conversion - String, int, bool, float, time.Duration, time.Time, and more
  • 🪆 Nested configuration - Support for nested structs and embedded fields
  • 📋 Slice support - Multi-value configuration (multiple flags, array values)
  • 🎨 Flexible naming - Automatically converts between camelCase, snake_case, and kebab-case
  • 🔀 Variable expansion - Reference environment variables, other config values, or file contents
  • 🔐 Local dev encryption - Commit encrypted secrets safely for team development
  • ⚙️ Environment-specific configs - Load different configs for dev, staging, production
  • 🔧 Default values - Keep defaults in your structs, override only what you need

Installation

Library:

go get github.com/RobertWHurst/orale

CLI (for encryption):

go install github.com/RobertWHurst/orale/cmd/orale@latest

Quick Start

package main

import (
    "fmt"
    "time"
    "github.com/RobertWHurst/orale"
)

type Config struct {
    Port    int           `config:"port"`
    Timeout time.Duration `config:"timeout"`
}

func main() {
    loader, err := orale.Load("myApp")
    if err != nil {
        panic(err)
    }

    config := Config{
        Port:    8080,  // defaults
        Timeout: time.Second * 30,
    }

    if err := loader.GetAll(&config); err != nil {
        panic(err)
    }

    fmt.Printf("Server starting on port %d with timeout %v\n", config.Port, config.Timeout)
}

Now you can configure it via:

  • Flag: ./myApp --port=3000 --timeout=5m
  • Environment: MY_APP__PORT=3000 MY_APP__TIMEOUT=5m
  • Config file (myApp.config.toml):
    port = 3000
    timeout = "5m"
    

Application Name

The application name you pass to Load() should be in camelCase. Orale uses this name to:

  • Generate the environment variable prefix by converting to uppercase with underscores (myAppMY_APP__)
  • Locate the config file by converting to lowercase with hyphens (myAppmy-app.config.toml)

For example, orale.Load("myApp") will look for environment variables like MY_APP__PORT and a config file named my-app.config.toml.

Configuration Sources

Orale loads configuration from three sources, with the following precedence (highest to lowest):

  1. Command-line flags - --key=value
  2. Environment variables - APP_NAME__KEY=value
  3. Configuration files - app-name.config.toml
Naming Conventions and Path Syntax

Orale automatically converts between different naming conventions and path delimiters:

Source Format Nesting Delimiter Example
Struct tags camelCase . (dot) config:"database.connectionUri"
Flags kebab-case -- (double dash) --database--connection-uri=...
Environment vars UPPER_SNAKE_CASE __ (double underscore) APP__DATABASE__CONNECTION_URI=...
TOML files snake_case [sections] [database] then connection_uri = ...

Important path rules:

  1. Flags use double-dashes for nesting:

    • --database--host=localhost
    • --server--tls--cert-path=/path/to/cert
    • The double-dash separates path segments
  2. Environment variables use double underscores for nesting:

    # Single level
    APP__PORT=8080
    
    # Nested
    APP__DATABASE__HOST=localhost
    APP__DATABASE__CONNECTION_POOL_SIZE=10
    
    # Deeply nested
    APP__SERVER__TLS__CERT_PATH=/path/to/cert
    
  3. TOML files use sections and snake_case:

    port = 8080
    
    [database]
    host = "localhost"
    connection_pool_size = 10
    
    [server.tls]
    cert_path = "/path/to/cert"
    
  4. All formats automatically convert to camelCase paths internally:

    • --server-portserverPort
    • APP__SERVER_PORTserverPort
    • TOML server_portserverPort
    • Important: Struct config tags must be in camelCase to match these converted paths:
      Port int `config:"serverPort"`  // Correct - matches converted paths
      Port int `config:"server_port"` // Wrong - won't match
      

Variable Expansion

Orale supports variable expansion in configuration values, allowing you to reference environment variables, other configuration values, or file contents. This is useful for building dynamic configuration values, referencing secrets, and avoiding duplication.

Variable Types

Orale supports three types of variable expansion:

Syntax Type Description Example
${VAR} Environment Variable Expands to the value of the environment variable VAR ${HOME}, ${DATABASE_PASSWORD}
%{key} Config Variable Expands to the value of another config key %{baseUrl}, %{database.host}
@{path} File Variable Expands to the contents of the file at path (strips trailing newline) @{/secrets/api-key}, @{./cert.pem}

Key features:

  • Variables can be combined in a single value
  • Expansion happens recursively (config variables can contain other variables)
  • Config variables support any naming convention (snake_case, kebab-case, camelCase)
  • Config variables can reference any value type (strings, integers, floats, bools)
  • File contents have trailing newlines automatically stripped
  • Circular references are detected and return an error
Environment Variables: ${VAR}

Reference environment variables using ${VAR} syntax:

type Config struct {
    DatabaseURL string `config:"databaseUrl"`
    APIKey      string `config:"apiKey"`
}

Configuration file (myApp.config.toml):

database_url = "postgres://${DB_HOST}:${DB_PORT}/mydb"
api_key = "${API_SECRET}"

Run with environment variables:

DB_HOST=localhost DB_PORT=5432 API_SECRET=secret123 ./myApp

Result:

  • config.DatabaseURL = "postgres://localhost:5432/mydb"
  • config.APIKey = "secret123"

You can also use environment variables directly:

# Environment variables (higher precedence than config files)
MY_APP__DATABASE_URL='postgres://${DB_HOST}:${DB_PORT}/mydb' \
MY_APP__API_KEY='${API_SECRET}' \
DB_HOST=localhost \
DB_PORT=5432 \
API_SECRET=secret123 \
./myApp
Config Variables: %{key}

Reference other configuration values using %{key} syntax. This is useful for building related URLs or avoiding duplication. Config variable paths support any naming convention - use whatever matches your configuration source (snake_case for TOML, kebab-case for flags, or direct camelCase):

type Config struct {
    BaseURL    string `config:"baseUrl"`
    APIURL     string `config:"apiUrl"`
    WebhookURL string `config:"webhookUrl"`
}

Configuration file (myApp.config.toml):

base_url = "https://api.example.com"
api_url = "%{base_url}/v1"
webhook_url = "%{base_url}/webhooks/incoming"

Result:

  • config.BaseURL = "https://api.example.com"
  • config.APIURL = "https://api.example.com/v1"
  • config.WebhookURL = "https://api.example.com/webhooks/incoming"

Nested paths: Use dots, double-underscores, or double-dashes to reference nested values:

[database]
host = "db.example.com"
port = 5432

[database]
# All of these work the same:
connection_string = "postgres://%{database.host}:%{database.port}/mydb"
# Or with double-underscore (TOML/env style):
connection_string = "postgres://%{database__host}:%{database__port}/mydb"
# Or with double-dash (flag style):
connection_string = "postgres://%{database--host}:%{database--port}/mydb"

Note: Config variables automatically convert non-string values (integers, floats, bools) to strings, so you can reference any configuration value.

File Variables: @{path}

Read file contents using @{path} syntax. This is ideal for reading secrets, certificates, or API keys from files:

type Config struct {
    APIKey      string `config:"apiKey"`
    Certificate string `config:"certificate"`
}

Configuration file (myApp.config.toml):

api_key = "@{/run/secrets/api-key}"
certificate = "@{/etc/ssl/certs/app-cert.pem}"

With files:

# /run/secrets/api-key
sk_live_abc123def456

# /etc/ssl/certs/app-cert.pem
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKZ...
-----END CERTIFICATE-----

Result:

  • config.APIKey = "sk_live_abc123def456"
  • config.Certificate = Full certificate content (trailing newline removed)

Security note: File variables are perfect for Docker secrets, Kubernetes mounted secrets, or any secrets management system that writes secrets to files.

Combining Variable Types

You can combine all three variable types in a single value:

type Config struct {
    ConnectionString string `config:"connectionString"`
}

Configuration file (myApp.config.toml):

[database]
host = "dbserver.example.com"

connection_string = "host=%{database.host};password=${DB_PASSWORD};cert=@{/secrets/db-cert.pem}"

Run with:

DB_PASSWORD=securepass123 ./myApp

Result:

config.ConnectionString = "host=dbserver.example.com;password=securepass123;cert=-----BEGIN CERTIFICATE-----..."
Practical Examples
Docker Secrets Pattern
# myApp.config.toml
[database]
host = "postgres"
port = 5432
name = "myapp"
user = "myapp"
password = "@{/run/secrets/db_password}"

[database]
url = "postgres://%{database.user}:%{database.password}@%{database.host}:%{database.port}/%{database.name}"
# docker-compose.yml
services:
  app:
    image: myapp:latest
    secrets:
      - db_password
secrets:
  db_password:
    file: ./secrets/db_password.txt
Multi-Environment Configuration
# myApp.production.config.toml
environment = "production"
base_url = "https://${DOMAIN}"
api_url = "%{baseUrl}/api"
cdn_url = "%{baseUrl}/cdn"

[security]
tls_cert = "@{/etc/letsencrypt/live/${DOMAIN}/fullchain.pem}"
tls_key = "@{/etc/letsencrypt/live/${DOMAIN}/privkey.pem}"
Development with Local Overrides
# myApp.config.toml (checked into git)
[database]
host = "localhost"
port = 5432

[api]
base_url = "%{protocol}://%{database.host}:${API_PORT}"
protocol = "http"
# .env file (not checked into git)
API_PORT=8080
Error Handling

Orale will return an error for:

  • Circular references: a = "%{b}" and b = "%{a}"
  • Missing closing brace: "${UNCLOSED"
  • Non-existent files: "@{/path/that/does/not/exist}"
  • Invalid UTF-8 in files: File contents must be valid UTF-8

Example:

loader, _ := orale.Load("myApp")
if err := loader.GetAll(&config); err != nil {
    // Handle expansion errors
    fmt.Printf("Configuration error: %v\n", err)
}

Local Development Encryption

Orale supports encrypting configuration values that need to be committed to your repository. This is perfect for local development defaults that your team shares, like database passwords for local Docker containers or test API keys.

Use this for:

  • Local development database passwords
  • Test API keys and credentials
  • Shared development secrets that all team members use

Do NOT use this for:

  • Production secrets (use environment variables or proper secret management)
  • User-specific values (use environment variables)
  • Secrets that differ per environment (use environment variables)
Team Setup Workflow

One-time team setup:

  1. One team member generates a key:

    orale keygen dev
    
  2. Share the output key securely with the team (Slack DM, password manager, etc.)

  3. Team members import the key:

    orale keyset Xkn4noje9zkpnSLImQHlkRUfBnlyOfo13hKlIN5vgp2J4k+b3RztePvvHJRGjhtbKn...
    

Encrypting values:

# Encrypt a value
orale encrypt dev "my-local-db-password"
# Output: ENC[q7Q6pVJdK6dHDH/s204CDWTmGqtNQvMA8X78V7uImer...]

# From stdin
echo "my-secret" | orale encrypt dev

Using encrypted values:

Encrypted values work in all configuration sources:

# Config file (myApp.config.toml) - safe to commit!
[database]
password = "ENC[q7Q6pVJdK6dHDH/s204CDWTmGqtNQvMA8X78V7uImer+BC8tYXxYJnyrCJUAKoxkjHTNj2rEd5pcwgd/1QlS37JuES3xMxWsVvWPJAchp40=]"
# Environment variables
MY_APP__DATABASE__PASSWORD='ENC[q7Q6pVJdK6dHDH/s204CDWTmGqtNQvMA8X78V7uImer...]'

# Flags
./myApp --database--password='ENC[q7Q6pVJdK6dHDH/s204CDWTmGqtNQvMA8X78V7uImer...]'

Automatic decryption in your app:

type Config struct {
    Database struct {
        Host     string `config:"host"`
        Port     int    `config:"port"`
        Password string `config:"password"`
    } `config:"database"`
}

func main() {
    loader, _ := orale.Load("myApp")

    config := Config{
        Database: {
            Port: 5432,  // Default port
        },
    }

    loader.GetAll(&config)

    // config.Database.Password is automatically decrypted!
    fmt.Println(config.Database.Password) // "my-local-db-password"
}
How It Works
  1. Encrypted values use the format ENC[base64-data]
  2. During config loading, Orale detects and decrypts these values automatically
  3. Tries all available keys in ~/.config/orale/ until HMAC verification succeeds
  4. Decryption happens before variable expansion
  5. Your application receives the plain text value
Where Encrypted Values Are Decrypted

Orale automatically detects and decrypts ENC[...] values everywhere string values can appear:

1. Configuration Files (TOML)

database_password = "ENC[q7Q6pVJdK6dHDH/s204CDWTmGqtNQvMA8X78V7uImer...]"

2. Command-Line Flags

./myApp --api-key='ENC[q7Q6pVJdK6dHDH/s204CDWTmGqtNQvMA8X78V7uImer...]'

3. Environment Variables

MY_APP__SECRET='ENC[q7Q6pVJdK6dHDH/s204CDWTmGqtNQvMA8X78V7uImer...]'

4. Default Struct Values

type Config struct {
    // Default value is encrypted - will be decrypted automatically!
    APIKey string `config:"apiKey"`
}

config := Config{
    APIKey: "ENC[q7Q6pVJdK6dHDH/s204CDWTmGqtNQvMA8X78V7uImer...]",
}
loader.GetAll(&config)
// config.APIKey is now decrypted

5. Environment Variable Expansions

# Set an encrypted value in an environment variable
export SECRET_TOKEN='ENC[q7Q6pVJdK6dHDH/s204CDWTmGqtNQvMA8X78V7uImer...]'
# Reference it in your config - both the expansion AND the result are decrypted
api_token = "${SECRET_TOKEN}"  # Decrypted automatically

6. File Variable Expansions

# Store encrypted value in a file
echo 'ENC[q7Q6pVJdK6dHDH/s204CDWTmGqtNQvMA8X78V7uImer...]' > /secrets/api-key
# Read from file - content is decrypted automatically
api_key = "@{/secrets/api-key}"  # Decrypted automatically

Key Points:

  • Decryption is completely automatic - you never need to call decrypt functions
  • Works with all configuration sources (files, flags, environment, defaults)
  • Decryption happens before variable expansion, so expanded variables can also be encrypted
  • If a value isn't encrypted (doesn't match ENC[...]), it's used as-is
Multiple Keys

You can have different keys for different purposes:

orale keygen dev        # For local development
orale keygen staging    # For staging environment defaults
orale keygen testing    # For test fixtures

orale keylist           # List all available keys

Orale automatically tries all keys when decrypting - each value works with whichever key encrypted it.

CLI Reference

View loaded configuration:

orale explain myApp
orale explain myApp --port=3000  # Include flags to see final values

Displays a table showing all loaded config values, their current values, and sources (flag/environment/file path).

Security Notes
  • Keys stored in ~/.config/orale/<name>.ok with 0600 permissions
  • AES-256-CBC encryption with HMAC-SHA256 authentication
  • Each encrypted value has a unique random IV
  • Production secrets should use environment variables, not committed encrypted values

Type Conversions

Orale automatically converts configuration values to match your struct field types:

Basic Types
type Config struct {
    Name    string  `config:"name"`
    Port    int     `config:"port"`
    Enabled bool    `config:"enabled"`
    Ratio   float64 `config:"ratio"`
}
Time Types

time.Duration - Accepts human-readable strings or nanoseconds:

type Config struct {
    Timeout        time.Duration `config:"timeout"`
    RetryInterval  time.Duration `config:"retryInterval"`
}

Valid formats:

  • Strings: "5h", "30m", "1h30m", "500ms"
  • Numbers: 1000000000 (nanoseconds)

time.Time - Accepts various timestamp formats:

type Config struct {
    StartTime time.Time `config:"startTime"`
    Birthday  time.Time `config:"birthday"`
}

Valid formats:

  • RFC3339: "2025-01-15T10:30:00Z"
  • RFC3339Nano: "2025-01-15T10:30:00.123456789Z"
  • ISO Date: "2025-01-15"
  • Unix timestamp: 1736938200 (int or float)
Slices

Collect multiple values into slice fields:

Slices of Primitives
type Config struct {
    Servers []string `config:"servers"`
    Ports   []int    `config:"ports"`
}

Flags - Two approaches:

  1. Repeat the same flag multiple times:
./app --servers=host1 --servers=host2 --servers=host3
  1. Use indexed notation:
./app --servers--0=host1 --servers--1=host2 --servers--2=host3

Environment variables - Use indexed notation:

APP__SERVERS__0=host1
APP__SERVERS__1=host2
APP__SERVERS__2=host3

TOML - Use array syntax:

servers = ["host1", "host2", "host3"]
ports = [8080, 8081]
Slices of Structs

For slices of structs, you must use indexed notation to specify individual fields:

type Server struct {
    Host string `config:"host"`
    Port int    `config:"port"`
}

type Config struct {
    Servers []Server `config:"servers"`
}

Flags - Use indexed notation with nested fields:

./app --servers--0--host=localhost --servers--0--port=8080 \
      --servers--1--host=example.com --servers--1--port=9090

Environment variables - Use indexed notation with nested fields:

APP__SERVERS__0__HOST=localhost
APP__SERVERS__0__PORT=8080
APP__SERVERS__1__HOST=example.com
APP__SERVERS__1__PORT=9090

TOML - Use array of tables syntax:

[[servers]]
host = "localhost"
port = 8080

[[servers]]
host = "example.com"
port = 9090

Note: Indices can be sparse (e.g., servers.0, servers.4). Orale will create a slice with the appropriate length, and unset indices will have zero values.

Maps

Maps allow dynamic keys for configuration:

type Config struct {
    Labels   map[string]string `config:"labels"`
    Ports    map[string]int    `config:"ports"`
    Features map[string]bool   `config:"features"`
}

Flags:

./app --labels--env=development --labels--version=1.0.0 --labels--team=backend

Environment variables:

APP__LABELS__ENV=development
APP__LABELS__VERSION=1.0.0
APP__LABELS__TEAM=backend

TOML:

[labels]
env = "development"
version = "1.0.0"
team = "backend"

Maps support any value type (string, int, bool, float, structs, etc.). Keys are extracted from the configuration paths automatically.

Nested Structs

Nested structs create configuration paths using their config tag:

type TLSConfig struct {
    CertPath string `config:"certPath"`
    KeyPath  string `config:"keyPath"`
}

type ServerConfig struct {
    Host string    `config:"host"`
    Port int       `config:"port"`
    TLS  TLSConfig `config:"tls"`
}

type Config struct {
    Server ServerConfig `config:"server"`
}

The resulting paths are:

  • server.host
  • server.port
  • server.tls.certPath
  • server.tls.keyPath

Configure with:

# Flags (double-dash notation for nesting)
./app --server--host=0.0.0.0 --server--port=8080
./app --server--tls--cert-path=/path/to/cert --server--tls--key-path=/path/to/key

# Environment (double underscores)
APP__SERVER__HOST=0.0.0.0
APP__SERVER__PORT=8080
APP__SERVER__TLS__CERT_PATH=/path/to/cert
APP__SERVER__TLS__KEY_PATH=/path/to/key

# TOML (sections and nested sections)
[server]
host = "0.0.0.0"
port = 8080

[server.tls]
cert_path = "/path/to/cert"
key_path = "/path/to/key"
Pointers and Default Values
type Config struct {
    // Nil pointer - will be initialized if config is provided
    Database *DatabaseConfig `config:"database"`

    // Value with default - will be overridden if config is provided
    Port int `config:"port"` // default: 0
}

func main() {
    config := Config{
        Port: 8080, // default value
    }

    loader.GetAll(&config)
    // Port will be 8080 unless overridden by flags/env/file
}
Embedded Structs
type ServerConfig struct {
    Host string `config:"host"`
    Port int    `config:"port"`
}

type Config struct {
    ServerConfig // embedded - fields are promoted
    Debug bool `config:"debug"`
}

// Access: config.Host, config.Port, config.Debug

Configuration Files

Orale looks for TOML configuration files with the following naming pattern:

<app-name>.config.toml
<app-name>.<environment>.config.toml
File Discovery

Files are searched in the current directory and all parent directories, walking up the filesystem tree from the working directory to the root.

This allows you to place config files at the project root and run your app from subdirectories.

Environment-Specific Configs

Load different configs for different environments by setting the configEnvironment value:

Via flag:

./myApp --config-environment=production

Via environment variable:

MY_APP__CONFIG_ENVIRONMENT=production ./myApp

This will look for myApp.production.config.toml instead of myApp.config.toml.

Common patterns:

# Development (default - no environment specified)
./myApp  # loads myApp.config.toml

# Staging
./myApp --config-environment=staging  # loads myApp.staging.config.toml

# Production
MY_APP__CONFIG_ENVIRONMENT=production ./myApp  # loads myApp.production.config.toml

# QA
./myApp --config-environment=qa  # loads myApp.qa.config.toml
TOML Format
# Basic values
port = 8080
enabled = true
timeout = "5m"

# Nested configuration
[database]
host = "localhost"
port = 5432
connection_pool_size = 10

# Arrays
servers = ["host1:8080", "host2:8080", "host3:8080"]

# Time values
start_time = "2025-01-15T10:30:00Z"
retry_interval = "30s"

Advanced Usage

Loading Specific Paths (Sub-pathing)

You can load just a subset of your configuration using path strings with Get():

type DatabaseConfig struct {
    Host string `config:"host"`
    Port int    `config:"port"`
}

type ServerConfig struct {
    Port int `config:"port"`
}

type Config struct {
    Database DatabaseConfig `config:"database"`
    Server   ServerConfig   `config:"server"`
}

loader, _ := orale.Load("myApp")

// Load entire config
var fullConfig Config
loader.GetAll(&fullConfig)

// Load only database config
var dbConfig DatabaseConfig
loader.Get("database", &dbConfig)

// Load only server config
var serverConfig ServerConfig
loader.Get("server", &serverConfig)

Path syntax:

  • Paths use dot notation: "database", "server.tls", "database.connection"
  • Paths map to struct field config tags (in camelCase)

How sources map to paths:

Given the path "database.host":

# Flags (double-dash notation for nesting)
./app --database--host=localhost

# Environment (double-underscore notation for nesting)
APP__DATABASE__HOST=localhost

# TOML (sections for nesting)
[database]
host = "localhost"
Array and Slice Paths

Arrays and slices use dot notation with numeric indices:

type Config struct {
    Servers []ServerConfig `config:"servers"`
}

Configuration:

[[servers]]
host = "server1"
port = 8080

[[servers]]
host = "server2"
port = 8081

Internally, Orale resolves these as:

  • servers.0.host"server1"
  • servers.0.port8080
  • servers.1.host"server2"
  • servers.1.port8081

Sparse indices are supported: If you set non-contiguous indices (e.g., servers.0 and servers.4), Orale will create a slice with the appropriate length (5 in this case), and unset indices will have zero values.

You cannot directly load a single array element with Get(), but you can load the entire array and access elements in code.

Multiple Configuration Structs
type DatabaseConfig struct {
    Host string `config:"host"`
    Port int    `config:"port"`
}

type ServerConfig struct {
    Port    int           `config:"port"`
    Timeout time.Duration `config:"timeout"`
}

type Config struct {
    Database DatabaseConfig `config:"database"`
    Server   ServerConfig   `config:"server"`
}

Configure with namespaced keys:

[database]
host = "db.example.com"
port = 5432

[server]
port = 8080
timeout = "30s"
Must Variants

Panic instead of returning errors:

loader := orale.MustLoad("myApp")
loader.MustGetAll(&config)

Complete Example

package main

import (
    "fmt"
    "time"
    "github.com/RobertWHurst/orale"
)

type DatabaseConfig struct {
    Host               string `config:"host"`
    Port               int    `config:"port"`
    ConnectionPoolSize int    `config:"connectionPoolSize"`
}

type ServerConfig struct {
    Host    string        `config:"host"`
    Port    int           `config:"port"`
    Timeout time.Duration `config:"timeout"`
}

type Config struct {
    Database DatabaseConfig `config:"database"`
    Server   ServerConfig   `config:"server"`
    Debug    bool           `config:"debug"`
}

func main() {
    // Set defaults
    config := Config{
        Database: DatabaseConfig{
            Host:               "localhost",
            Port:               5432,
            ConnectionPoolSize: 5,
        },
        Server: ServerConfig{
            Host:    "0.0.0.0",
            Port:    8080,
            Timeout: 30 * time.Second,
        },
        Debug: false,
    }

    // Load configuration
    loader, err := orale.Load("myApp")
    if err != nil {
        panic(err)
    }

    if err := loader.GetAll(&config); err != nil {
        panic(err)
    }

    fmt.Printf("Database: %s:%d (pool: %d)\n",
        config.Database.Host,
        config.Database.Port,
        config.Database.ConnectionPoolSize)

    fmt.Printf("Server: %s:%d (timeout: %v)\n",
        config.Server.Host,
        config.Server.Port,
        config.Server.Timeout)

    fmt.Printf("Debug mode: %v\n", config.Debug)
}

Configuration file (myApp.config.toml):

debug = true

[database]
host = "db.example.com"
port = 5432
connection_pool_size = 20

[server]
host = "0.0.0.0"
port = 3000
timeout = "5m"

Override with environment:

MY_APP__SERVER__PORT=8080 MY_APP__DEBUG=false ./myApp

Override with flags:

./myApp --server--port=8080 --debug=false

API Reference

Load(appName string) (*Loader, error)

Creates a new configuration loader for the given application name.

The application name is used to:

  • Generate the environment variable prefix (e.g., "myApp"MY_APP__)
  • Locate configuration files (e.g., "myApp"myApp.config.toml)

Returns: A Loader instance or an error if configuration files cannot be parsed.

Example:

loader, err := orale.Load("myApp")
MustLoad(appName string) *Loader

Like Load, but panics on error instead of returning an error.

(*Loader).Get(path string, target interface{}) error

Loads configuration at the given path into the target struct.

Parameters:

  • path - Dot-separated path to configuration (camelCase)
    • Single level: "database", "server", "port"
    • Nested: "database.connection", "server.tls.certPath"
    • Paths must match struct field config tags (in camelCase)
  • target - Pointer to struct to populate

Examples:

// Load subset at path "database"
var dbConfig DatabaseConfig
loader.Get("database", &dbConfig)

// Load deeply nested path
var tlsConfig TLSConfig
loader.Get("server.tls", &tlsConfig)

Returns: Error if path doesn't exist or target is not a pointer.

(*Loader).GetAll(target interface{}) error

Loads all configuration into the target struct.

Example:

var config Config
loader.GetAll(&config)
(*Loader).MustGet(path string, target interface{})

Like Get, but panics on error instead of returning an error.

(*Loader).MustGetAll(target interface{})

Like GetAll, but panics on error instead of returning an error.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

[Include your license here]

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Encrypt added in v1.8.0

func Encrypt(plaintext, encryptionKey, hmacKey []byte) (string, error)

Encrypt encrypts plaintext using AES-256-CBC with HMAC-SHA256 authentication.

func Test_SetArgs added in v1.2.0

func Test_SetArgs(args []string)

func Test_SetEnvironment added in v1.2.0

func Test_SetEnvironment(env []string)

func Test_SetWorkingDir added in v1.2.0

func Test_SetWorkingDir(dir string)

Types

type File

type File struct {
	// Path is the absolute path to the configuration file.
	Path string
	// Values is a map of configuration values loaded from the file. Note that
	// these values are flattened into paths separated by periods. Slice indexes
	// are represented by a period followed by the numeric index. The value is
	// always a slice of any. It's a slice because theoretically a configuration
	// file could have multiple values for the same path. This is not the case with
	// toml so as of now it's always a slice of length 1.
	Values map[string][]any
}

File represents a configuration file loaded from disk.

type Loader

type Loader struct {
	// FlagValues is a map of flag values by path.
	FlagValues map[string][]any
	// EnvironmentValues is a map of environment variable values by path.
	EnvironmentValues map[string][]any
	// ConfigurationFiles is a slice of configuration files.
	ConfigurationFiles []*File
	// contains filtered or unexported fields
}

Loader is a struct that contains all the values loaded from flags, environment variables, and configuration files. It can be used to marshal the values into a struct.

func Load

func Load(applicationName string) (*Loader, error)

Load loads configuration values from flags, environment variables, and configuration files. Flags are taken from `os.Args[1:]`. Environment variables are taken from `os.Environ()`. Configuration files are taken from the working directory and all parent directories. The configuration file name is the application name with the extension `.config.toml`.

func LoadFromValues

func LoadFromValues(programArgs []string, envVarPrefix string, envVars []string, configSearchStartPath string, configFileNames []string) (*Loader, error)

LoadFromValues works like Load, but allows the caller to specify configuration such as flag and environment values, as well as which path to start searching for configuration files and which configuration file names to look for.

func (*Loader) Get

func (l *Loader) Get(path string, target any) error

```go

type TestConfig struct {
	Database struct {
		ConnectionUri string `config:"connection_uri"`
	} `config:"database"`
	Server struct {
		Port int `config:"port"`
	} `config:"server"`
	Channels []struct {
		Name string `config:"name"`
		Id   int    `config:"id"`
	} `config:"channels"`
}

loader, err := orale.Load("my-app")
if err != nil {
	panic(err)
}

var testConfig TestConfig
if err := loader.Get("", &testConfig); err != nil {
	panic(err)
}

```

As you can see in the example above, the TestConfig struct is populated with the loaded configuration values. The property names of each field are specified by the `config` tag. If the `config` tag is not specified, the property name is converted to snake case. For example `ConnectionUri` becomes `connection_uri` path.

func (*Loader) GetAll added in v1.1.0

func (l *Loader) GetAll(target any) error

GetAll populates loaded configuration values into the target value. This should be a pointer type, usually a pointer to a struct. All loaded configuration values will be set into the target value. Any default values in the target value that are not present in the loaded configuration will be left unchanged.

func (*Loader) MustGet

func (l *Loader) MustGet(path string, target any)

MustGet is the same as Get except it panics if an error occurs.

func (*Loader) MustGetAll added in v1.1.0

func (l *Loader) MustGetAll(target any)

MustGetAll is the same as GetAll except it panics if an error occurs.

Directories

Path Synopsis
cmd
orale command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL