Skip to content

Repository files navigation

Resolver Package

The resolver package provides a flexible and extensible way to resolve configuration values from various sources including environment variables, plain files, JSON, YAML, INI, TOML, and key-value files. It uses a prefix-based system to determine which resolver to use, and returns either the resolved value or an error.

Installation

go get github.com/containeroo/resolver/resolver

Usage

The primary entry point is the ResolveVariable function. It takes a string and attempts to resolve it based on its prefix:

  • env: - Environment variables. Example:

    env:PATH
    

    → value of $PATH.

  • file: - Simple key-value files. Supports KEY=VAL lines, with optional export prefixes and # comments. Example:

    file:/config/app.txt//USERNAME
    

    → value of USERNAME in app.txt.

  • json: - JSON files. Supports dot-notation for nested keys and array indexing. Examples:

    json:/config/app.json//server.host
    json:/config/app.json//servers.0.host
    json:/config/app.json//servers.[name=api].port
    
  • yaml: - YAML files. Same dot/array/filter notation as JSON. Example:

    yaml:/config/app.yaml//servers.[host=example.org].port
    
  • ini: - INI files. Supports section+key or default section. Examples:

    ini:/config/app.ini//Database.User
    ini:/config/app.ini//Key1
    
  • toml: - TOML files. Dot-notation for nested keys and array indexing. Example:

    toml:/config/app.toml//server.host
    
  • No prefix - Returns the value unchanged. Example:

    just-a-literal
    

    "just-a-literal".

String interpolation (ResolveString)

Interpolate ${...} tokens inside a larger string and resolve each token with the same rules as ResolveVariable.

// Replace ${...} tokens using the default registry (up to 8 passes).
func ResolveString(s string) (string, error)

// Registry method, if you use a custom registry:
func (*Registry) ResolveString(s string) (string, error)

Features & rules

  • ${scheme:...} tokens are resolved; the ${...} wrapper is removed.

  • \${ emits a literal "${" (escape) and is not expanded.

  • A bare $ not followed by { is copied literally.

  • Malformed tokens error with ErrBadPath:

    • missing closing } (e.g., "${env:HOME")
    • empty token "${}"
  • Multi-pass expansion: tokens that produce new ${...} are expanded in subsequent passes (depth limit 8).

  • Unknown schemes follow your registry policy:

    • Default (PassThrough): the token's content is inserted unchanged (e.g., "${nosuch:x}" → "nosuch:x").
    • ErrorOnUnknown: unknown tokens yield ErrNotFound.

Examples

os.Setenv("USER", "alice")

s, _ := resolver.ResolveString("db://u=${env:USER}@${json:/cfg/app.json//db.host}")
// → "db://u=alice@localhost"

s, _ = resolver.ResolveString(`literal \${env:USER}`)
// → "literal ${env:USER}"

s, _ = resolver.ResolveString("price is $$5 (not a token)")
// → "price is $$5 (not a token)"

// Multi-pass: a token that expands to another token
r := resolver.NewRegistry()
resolver.RegisterResolver("a:", resolver.ResolverFunc(func(_ string) (string, error) { return "${b:x}", nil }))
resolver.RegisterResolver("b:", resolver.ResolverFunc(func(_ string) (string, error) { return "OK", nil }))
s, _ = r.ResolveString("s=${a:any}")
// → "s=OK"

Batch resolution

When you need to resolve a list of strings (e.g., CLI args, YAML arrays), use the slice helpers. Both preserve order, return a new slice, and leave inputs unchanged. Unknown schemes still pass through unchanged, just like ResolveVariable.

ResolveSlice (strict)

func ResolveSlice(values []string) ([]string, error)

Resolves each element using the default registry. If any element fails, the function stops at the first error and returns it. No partial results are returned.

ResolveSliceBestEffort

func ResolveSliceBestEffort(values []string) ([]string, []error)

Attempts to resolve all elements and never fails fast. Returns:

  • out: resolved values (same length as input; failed items are "").
  • errs: per-index errors you can inspect or log.

Registry methods are also available: (*Registry).ResolveSlice and (*Registry).ResolveSliceBestEffort.

Example

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/containeroo/resolver"
)

func main() {
    os.Setenv("MY_VAR", "HelloWorld")

    val, err := resolver.ResolveVariable("env:MY_VAR")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(val) // Output: HelloWorld

    // Example: resolve a JSON key
    // File: /config/app.json
    // {
    //   "server": {
    //     "host": "localhost",
    //     "port": 8080
    //   }
    // }
    host, err := resolver.ResolveVariable("json:/config/app.json//server.host")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(host) // Output: localhost

    // Interpolation example
    os.Setenv("USER", "alice")
    s, err := resolver.ResolveString("db://${env:USER}@${json:/config/app.json//server.host}")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(s) // db://alice@localhost
}

Extensibility

You can register your own resolver schemes at runtime:

resolver.RegisterResolver("secret:", myCustomResolver)

Resolvers must implement:

type Resolver interface {
    Resolve(value string) (string, error)
}

You can also use the ergonomic adapter:

resolver.RegisterResolver("secret:", resolver.ResolverFunc(func(v string) (string, error) {
    return fetchSecret(v), nil
}))

This allows you to plug in custom backends (e.g., Vault, Consul, HTTP endpoints).

About

golang library to get variables from environment, json, yaml, ini or other files

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages