NAME

Config::Resolver - Recursively resolve placeholders in a data structure

SYNOPSIS

use Config::Resolver;

# 1. Base use (default, safe functions)
my $resolver = Config::Resolver->new();
my $config = $resolver->resolve(
    '${uc(greeting)}', { greeting => 'hello' }
);
# $config is now 'HELLO'

# 2. Extended use (injecting a custom "allowed" function)
my $resolver_ext = Config::Resolver->new(
    functions => {
        'reverse' => sub { return scalar reverse( $_[0] // '' ) },
    }
);
my $config_ext = $resolver_ext->resolve(
    '${reverse(greeting)}', { greeting => 'hello' }
);
# $config_ext is now 'olleh'

# 3. Pluggable Backends (for ssm://, vault://, etc.)

# A) Dynamically load installed plugins...

my $my_plugin_config = {
    'ssm' => { 'endpoint_url' => 'http://localhost:4566' }
};

my $resolver_plugins = Config::Resolver->new(
    plugins       => [ 'SSM' ],
    plugin_config => $my_plugin_config,
);

my $ssm_val = $resolver_plugins->resolve('ssm://my/ssm/path');

# B) Manual "shim" injection
my $resolver_manual = Config::Resolver->new(
    backends => {
        'my_db' => sub {
            my ($path, $parameters) = @_;
            # ... logic to resolve $path using $parameters ...
            return "value_for_${path}";
        }
    }
);

my $db_val = $resolver_manual->resolve('my_db://foo');
# $db_val is now 'value_for_foo'

DESCRIPTION

Config::Resolver is a powerful and extensible engine for dynamically resolving placeholders in complex data structures.

While this module can be used directly in any Perl application (see SYNOPSIS), it is primarily designed as the engine for the config-resolver.pl command-line utility .

The config-resolver.pl harness provides a complete, robust, and testable solution for managing configuration files. It is intended to replace complex and brittle sed, awk, or envsubst logic in deployment scripts, such as those found in `docker-entrypoint.sh` scripts or CI/CD pipelines.

This class allows you to define a configuration that contains placeholders that can be resolved from multiple sources.

FEATURES

The Config::Resolver engine (and its harness) are built to solve common, real-world DevOps and configuration challenges.

PLACEHOLDERS

Placeholders in the configuration object can be used to access data from a hash of provided values, a pluggable backend, or a function call.

Accessing values from a hash

You can access values from the $parameters hash using a dot-notation path. The resolver can traverse nested hash references and array references.

To access a hash key, use its name:

${database.host}

To access an array element, use bracket notation with an index:

${servers[0].ip}

The path is split by periods, and each part is checked for either a hash key or an array index.

Accessing values from a function call

You can perform simple, safe data transformations by wrapping a parameter path in a function call.

${function_name(arg_path)}

The arg_path (e.g., database.host) is first resolved using get_value(), and its result is then passed as the only argument to the function.

The function_name must exist in the "allow-list" of functions configured when Config::Resolver was instantiated (see the functions option for new()). This is a safe, eval-free ispatch.

A base set of functions (uc, lc) are provided by default. Example:

# Resolves 'database.host', then passes it to 'uc'
${uc(database.host)}

Accessing values from Backends (Protocols)

This module supports a "protocol" pattern (xxx://path) to resolve values from external data sources.

Batteries Included Backends

Config::Resolver ships with two "B-U-T-FULL," built-in backends that are always available:

Pluggable Backends

You can add *dynamic* plugins for services like AWS or Vault. These are loaded via the plugins and backends options in the new() constructor.

# (Assuming the 'SSM' plugin is loaded) ssm://my/parameter/path

Using the Ternary Operator

The resolver supports a powerful, C-style ternary operator for simple conditional logic directly within your templates. This is the "Merlin" move that avoids complex shell scripting and replaces brittle `sed` commands.

The syntax is:

${variable_path op "value" ? "true_result" : "false_result"}

Example

Given the parameters: { env => 'prod', db_host => 'prod.db', dev_host => 'dev.db' }

This template:

db_host: ${env eq "prod" ? db_host : dev_host}
db_port: ${env eq "prod" ? 5432 : 1234}

Will resolve to:

db_host: prod.db
db_port: 5432

METHODS AND SUBROUTINES

new

Creates a new Resolver object.

my $resolver = Config::Resolver->new(
    {
        functions       => { 'reverse' => sub { ... } },
        plugins         => [ 'SSM' ],
        backends        => { 'file' => sub { ... } },
        warning_level   => 'warn',
        debug           => $FALSE,
    }
); 

Accepts a hash reference with the following keys:

resolve( $obj, $parameters )

Recursively resolves all placeholders within a given data structure. This is the main method you will call after new().

Returns the resolved data structure.

finalize_parameters( $obj, $parameters )

The internal recursive-descent engine. This is called by resolve(). It checks the type of $obj and dispatches to _resolve_array (for ARRAY refs) .

resolve_value( $scalar, $parameters )

Resolves all placeholders within a single scalar value. This method is the "workhorse" of the resolver and applies resolution in the following order:

1. Pluggable Backends (e.g., ssm://...)

2. Simple Hash Lookups (e.g., ${foo.bar})

3. Ternary Operators (e.g., ${... ? ...})

Returns the resolved scalar.

get_parameter( $parameters, $path_string )

Retrieves a value from the $parameters hash, supporting dot-notation (foo.bar), array-indexing (foo.bar[0]), and safe function calls (uc(foo.bar)).

Function calls are validated against the "allow-list" of functions provided to new().

get_value( $parameters, $path_string )

The core path-traversal engine. Given a HASH ref and a dot-notation path, this method walks the data structure and returns the value.

eval_arg( $arg_string, $parameters )

A safe, eval-free parser for arguments within a ternary operator. It correctly identifies and returns:

1. Numbers (123)

2. Quoted Strings ("foo" or 'bar'), with un-escaping.

3. Other values, which are assumed to be parameter paths (foo.bar) and are resolved.

PLUGIN API

This module is extensible via a plugin architecture. A plugin is a class in the Config::Resolver::Plugin::* namespace. It must adhere to the following contract:

SEE ALSO

Config::Resolver::Utils

AUTHOR

Rob Lauer - rclauer@gmail.com