simple_config

Cookbook

Cookbook

Ready-to-use recipes for common configuration patterns.

Recipe 1: Database Configuration

Load database settings with environment override for production.

config.json

{
    "database": {
        "host": "localhost",
        "port": 5432,
        "name": "myapp_dev",
        "pool_size": 5
    }
}

Eiffel Code

class
    DATABASE_SETTINGS

create
    make

feature

    host: STRING
    port: INTEGER
    name: STRING
    pool_size: INTEGER

    make (a_config: SIMPLE_CONFIG)
        do
            -- Environment variables override config in production
            host := a_config.string_value_or_env_or_default (
                "database.host", "DB_HOST", "localhost")
            port := a_config.integer_value_or_env (
                "database.port", "DB_PORT", 5432)
            name := a_config.string_value_or_env_or_default (
                "database.name", "DB_NAME", "myapp")
            pool_size := a_config.integer_value_or_default (
                "database.pool_size", 10)
        end

    connection_string: STRING
        do
            Result := "host=" + host + " port=" + port.out +
                      " dbname=" + name
        end

end

Recipe 2: Multi-Environment Config

Base config with environment-specific overrides.

config.json (base)

{
    "app": {
        "name": "MyApp",
        "debug": false,
        "log_level": "info"
    },
    "server": {
        "port": 8080,
        "host": "0.0.0.0"
    }
}

config.development.json

{
    "app": {
        "debug": true,
        "log_level": "debug"
    }
}

config.production.json

{
    "server": {
        "port": 80
    }
}

Eiffel Code

local
    config: SIMPLE_CONFIG
    env: STRING
do
    -- Load base config
    create config.make_with_file ("config.json")

    -- Get current environment
    env := ((create {SIMPLE_ENV}).get ("APP_ENV")).to_string_8
    if env = Void then
        env := "development"
    end

    -- Merge environment-specific config
    config.merge_file ("config." + env + ".json")

    -- Now config has merged values
    print ("Debug: " + config.boolean_value ("app.debug").out)
end

Recipe 3: Feature Flags

Toggle features on/off without code changes.

config.json

{
    "features": {
        "new_ui": false,
        "beta_api": false,
        "metrics": true,
        "cache": true
    }
}

Eiffel Code

class
    FEATURE_FLAGS

create
    make

feature

    config: SIMPLE_CONFIG

    make (a_config: SIMPLE_CONFIG)
        do
            config := a_config
        end

    is_enabled (a_feature: STRING): BOOLEAN
        do
            Result := config.boolean_value_or_default (
                "features." + a_feature, False)
        end

    with_feature (a_feature: STRING; a_action: PROCEDURE)
        -- Execute action only if feature is enabled
        do
            if is_enabled (a_feature) then
                a_action.call
            end
        end

end

-- Usage
flags.with_feature ("new_ui", agent render_new_ui)
flags.with_feature ("metrics", agent collect_metrics)

Recipe 4: Service Registry

Configure multiple service endpoints.

config.json

{
    "services": {
        "auth": {
            "url": "https://auth.example.com",
            "timeout": 30,
            "retries": 3
        },
        "storage": {
            "url": "https://storage.example.com",
            "timeout": 60,
            "retries": 5
        },
        "email": {
            "url": "https://email.example.com",
            "timeout": 10,
            "retries": 2
        }
    }
}

Eiffel Code

class
    SERVICE_REGISTRY

create
    make

feature

    config: SIMPLE_CONFIG

    make (a_config: SIMPLE_CONFIG)
        do
            config := a_config
        end

    service_url (a_service: STRING): STRING
        do
            Result := config.string_value_or_default (
                "services." + a_service + ".url", "")
        end

    service_timeout (a_service: STRING): INTEGER
        do
            Result := config.integer_value_or_default (
                "services." + a_service + ".timeout", 30)
        end

    service_retries (a_service: STRING): INTEGER
        do
            Result := config.integer_value_or_default (
                "services." + a_service + ".retries", 3)
        end

end

-- Usage
registry.service_url ("auth")      -- "https://auth.example.com"
registry.service_timeout ("storage") -- 60

Recipe 5: User Preferences with Save

Save user preferences to a config file.

Eiffel Code

class
    USER_PREFERENCES

create
    make

feature

    config: SIMPLE_CONFIG
    file_path: STRING

    make (a_path: STRING)
        do
            file_path := a_path
            create config.make_with_file (a_path)
        end

    theme: STRING
        do
            Result := config.string_value_or_default ("theme", "light")
        end

    set_theme (a_theme: STRING)
        do
            config.set_string ("theme", a_theme)
        end

    font_size: INTEGER
        do
            Result := config.integer_value_or_default ("font_size", 14)
        end

    set_font_size (a_size: INTEGER)
        do
            config.set_integer ("font_size", a_size)
        end

    recent_files: ARRAYED_LIST [STRING]
        do
            Result := config.string_list ("recent_files")
        end

    save
        do
            if config.is_modified then
                config.save
            end
        end

    is_dirty: BOOLEAN
        do
            Result := config.is_modified
        end

end

Recipe 6: Logging Configuration

Configure logging levels and handlers.

config.json

{
    "logging": {
        "level": "info",
        "handlers": {
            "console": {
                "enabled": true,
                "format": "[%level%] %message%"
            },
            "file": {
                "enabled": true,
                "path": "/var/log/myapp.log",
                "max_size_mb": 10,
                "rotate_count": 5
            }
        }
    }
}

Eiffel Code

class
    LOGGING_CONFIG

create
    make

feature

    level: STRING
    console_enabled: BOOLEAN
    console_format: STRING
    file_enabled: BOOLEAN
    file_path: STRING
    max_size_mb: INTEGER
    rotate_count: INTEGER

    make (a_config: SIMPLE_CONFIG)
        do
            level := a_config.string_value_or_default ("logging.level", "info")

            -- Console handler
            console_enabled := a_config.boolean_value_or_default (
                "logging.handlers.console.enabled", True)
            console_format := a_config.string_value_or_default (
                "logging.handlers.console.format", "[%level%] %message%")

            -- File handler
            file_enabled := a_config.boolean_value_or_default (
                "logging.handlers.file.enabled", False)
            file_path := a_config.string_value_or_default (
                "logging.handlers.file.path", "app.log")
            max_size_mb := a_config.integer_value_or_default (
                "logging.handlers.file.max_size_mb", 10)
            rotate_count := a_config.integer_value_or_default (
                "logging.handlers.file.rotate_count", 5)
        end

end

Recipe 7: Validation on Load

Validate required configuration at startup.

Eiffel Code

class
    CONFIG_VALIDATOR

feature

    validate (a_config: SIMPLE_CONFIG): ARRAYED_LIST [STRING]
            -- Return list of validation errors (empty if valid)
        local
            errors: ARRAYED_LIST [STRING]
        do
            create errors.make (0)

            -- Required keys
            if not a_config.has_key ("database.host") then
                errors.extend ("Missing required: database.host")
            end

            -- Type validation
            if a_config.has_key ("server.port") then
                if a_config.integer_value ("server.port") <= 0 then
                    errors.extend ("server.port must be positive")
                end
            end

            -- Range validation
            if a_config.integer_value ("pool_size") > 100 then
                errors.extend ("pool_size cannot exceed 100")
            end

            Result := errors
        end

    validate_or_die (a_config: SIMPLE_CONFIG)
            -- Validate and exit if errors
        local
            errors: ARRAYED_LIST [STRING]
        do
            errors := validate (a_config)
            if not errors.is_empty then
                print ("Configuration errors:%N")
                across errors as e loop
                    print ("  - " + e + "%N")
                end
                (create {EXCEPTIONS}).die (1)
            end
        end

end

Recipe 8: Hot Reload Configuration

Reload configuration without restarting application.

Eiffel Code

class
    HOT_RELOAD_CONFIG

create
    make

feature

    config: SIMPLE_CONFIG
    file_path: STRING
    on_reload: detachable PROCEDURE [SIMPLE_CONFIG]

    make (a_path: STRING)
        do
            file_path := a_path
            create config.make_with_file (a_path)
        end

    set_on_reload (a_handler: PROCEDURE [SIMPLE_CONFIG])
            -- Set callback for reload events
        do
            on_reload := a_handler
        end

    reload
            -- Reload configuration from disk
        do
            config.load

            -- Notify listeners
            if attached on_reload as handler then
                handler.call ([config])
            end
        end

    check_and_reload
            -- Check if file changed and reload if needed
        local
            l_file: SIMPLE_FILE
        do
            create l_file.make (file_path)
            if l_file.modified_timestamp > last_check then
                reload
                last_check := l_file.modified_timestamp
            end
        end

    last_check: INTEGER_64

end

-- Usage
hot_config.set_on_reload (agent handle_config_change)
-- In main loop or timer:
hot_config.check_and_reload