flinn

package module
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: May 16, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

README

Flinn

Flinn is a declarative, type-safe config loader for Go. It allows you to define your config schema as a tree of typed fields. Actual values are loaded from env vars, JSON, or TOML file, based on source configuration.

Features

  • Supported field typesString, Int, Float, Bool, UUID
  • Supported sources — env vars, JSON files, TOML files
  • Single source of truth - define both data loading and validation rules in one place
  • Easily extend — one interface method and you're done

Install

go get github.com/aleosd/flinn

For TOML support:

go get github.com/aleosd/flinn/source/toml

Quick start

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/aleosd/flinn"
)

type Config struct {
    Database struct {
        Host     string
        Port     int
        Password string
    }
    API struct {
        Host string
        Port int
    }
}

func main() {
    source, err := flinn.NewJSONSource("config.json")
    if err != nil {
        log.Fatal(err)
    }

    loader := flinn.NewLoader(
        flinn.WithSource(source),
        flinn.WithEnvPrefix("APP"),
        flinn.WithAutoEnv(),
    )

    var cfg Config
    fields := flinn.DefineSchema(
        flinn.FieldsGroup("database",
            flinn.String("host", &cfg.Database.Host).Default("localhost"),
            flinn.Int("port", &cfg.Database.Port).Default(5432),
            flinn.String("password", &cfg.Database.Password).
                Env("DB_PASSWORD").Required(),
        ).EnvPrefix("DB"),
        flinn.FieldsGroup("api",
            flinn.String("host", &cfg.API.Host).Default("0.0.0.0"),
            flinn.Int("port", &cfg.API.Port).Default(8080),
        ),
    )

    if err := loader.Load(fields); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    fmt.Printf("%+v\n", cfg)
}

With WithEnvPrefix("APP"), WithAutoEnv(), and EnvPrefix("DB") on the database group, here's how a few fields resolve:

Field Env var File key Fallback
Database.Host APP_DB_HOST database.host "localhost"
Database.Password APP_DB_DB_PASSWORD database.password error (required)
API.Port APP_PORT api.port 8080

Env names are built by concatenating the loader prefix (WithEnvPrefix), group prefixes (EnvPrefix), and the field key in that order. Explicit Env(...) values are prefixed too — they are not treated as absolute names.

Field types

String
flinn.String("fieldName", &dest).Required().Default("value")
Int / Float

Both return a NumericField so you can chain Min and Max validators:

flinn.Int("fieldName", &dest).Min(1).Max(100).Default(80)
flinn.Float("fieldName", &dest).Min(0.0).Max(1.0)
Bool
flinn.Bool("debug", &cfg.Debug).Default(false)
UUID
flinn.UUID("requestID", &cfg.RequestID).Required()
FieldsGroup

Groups nest fields under a path segment. They don't hold values themselves.

flinn.FieldsGroup("database",
    flinn.String("host", &cfg.Database.Host),
    flinn.Int("port", &cfg.Database.Port),
).EnvPrefix("DB")

The group's name becomes the file lookup path segment (database). Its env prefix only contributes when you call EnvPrefix.

Field options

Method What it does
Env("VAR") Set the env var name explicitly
EnvPrefix("P") Set the env prefix for a Group
FileKey("key") Override the file lookup key
Default(v) Use v if nothing else is found
Required() Error if no value resolves and there's no default
Min(v) (Numeric only) Error if value < v
Max(v) (Numeric only) Error if value > v
AddValidator(fn) Add your own func(T) error validator

Where values come from

For each field, Flinn checks in this order:

  1. Environment variable
  2. Source files
  3. Provided default values

Loader options

WithSource(source)

Attach a config file source.

source, _ := flinn.NewJSONSource("config.json")
loader := flinn.NewLoader(flinn.WithSource(source))
WithEnvPrefix(prefix)

Prepend a global prefix to auto-generated env names.

flinn.NewLoader(flinn.WithEnvPrefix("MYAPP"))
// field "host" → env "MYAPP_HOST" (with WithAutoEnv)
WithAutoEnv()

Turn on automatic loading from env vars. Otherwise, only config fields with Env() will get their value from env vars.

WithLogger(logger)

Attach an *slog.Logger to manage logging output. Disabled by default.

flinn.NewLoader(flinn.WithLogger(slog.Default()))

Sources

JSON (built-in)
source, err := flinn.NewJSONSource("config.json")

Root must be a JSON object. Nested objects map to FieldsGroup segments.

{
  "database": {
    "host": "localhost",
    "port": 5432
  },
  "debug": true
}
TOML (separate module)
go get github.com/aleosd/flinn/source/toml
import flinntoml "github.com/aleosd/flinn/source/toml"

source, err := flinntoml.NewTOMLSource("config.toml")
loader := flinn.NewLoader(flinn.WithSource(source))

Errors

loader.Load returns an error and may return a flinn.FieldErrors value when one or more fields fail. You can inspect individual errors if you want:

if err := loader.Load(fields); err != nil {
    if fieldErrs, ok := err.(flinn.FieldErrors); ok {
        for _, fe := range fieldErrs {
            fmt.Printf("field %q: [%s] %s\n", fe.Path, fe.Rule, fe.Msg)
        }
    }
    os.Exit(1)
}

Each FieldError has:

Field Meaning
Path Dot-separated path, e.g. "database.port"
Rule What failed: "required", "parse", "resolve", "validate", etc.
Value The raw string value, if any (nil for missing required fields)
Msg Human-readable message

Write your own source

Implement Source and Flinn will use it:

type Source interface {
    Get(path []string) (string, bool, error)
}

path is the nested key segments, e.g. ["database", "host"]. Return ("", false, nil) when the key is missing, (value, true, nil) when found, or ("", false, err) on a real error.

Auto key naming

If you skip Env() and FileKey(), Flinn converts field names to snake_case automatically:

Field name File key Auto env var
host host HOST
dbHost db_host DB_HOST
APIPort api_port API_PORT

Contributing

Bug reports and PRs are welcome. Run the quality suite before submitting:

make verify

That hits gofmt, go vet, golangci-lint, go test, and govulncheck across all modules.

Documentation

Overview

Package flinn provides a declarative, type-safe configuration loader for Go. It resolves values from environment variables and structured (JSON/TOML) files, with support for defaults, and validation.

Core Concepts

  • Loader: Orchestrates loading from multiple sources.
  • Field: Definition for a single configuration value, created by String, Int, etc.
  • Source: An interface for providing values from a structured source (e.g., a JSON file).
  • Group: A collection of fields that share a namespace for file paths and env prefixes.

Values are resolved in the following order of precedence (highest to lowest):

  1. Environment variable (if enabled for the field or loader)
  2. Source file
  3. Default value (if set)

If one or more fields fail to resolve or validate, loading returns a FieldErrors collection so every problem is reported at once.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ConfigItem added in v0.1.4

type ConfigItem interface {
	// contains filtered or unexported methods
}

ConfigItem is a general node in a configuration schema. It is implemented by both Field and Group.

func DefineSchema added in v0.1.4

func DefineSchema(fields ...ConfigItem) []ConfigItem

DefineSchema groups configuration items into a slice. It is a syntactic sugar to create a slice of ConfigItem for Loader.Load.

type Field

type Field[T comparable] struct {
	// contains filtered or unexported fields
}

Field represents a single configuration leaf node that parses values into type T. It holds configuration for environment variable keys, file keys, default values, and validation rules.

func Bool

func Bool(name string, dest *bool) *Field[bool]

Bool creates a Field that parses boolean values. It accepts the same formats as strconv.ParseBool (e.g., "true", "1", "false", "0").

func String

func String(name string, dest *string) *Field[string]

String creates a configuration leaf Field that handles string values.

func UUID

func UUID(name string, dest *uuid.UUID) *Field[uuid.UUID]

UUID creates a Field that parses UUID values.

func (*Field[T]) AddValidator added in v0.1.4

func (f *Field[T]) AddValidator(fn func(T) error) *Field[T]

AddValidator adds a custom validation function to the field. Validators are run after the value is parsed and assigned to the destination.

func (*Field[T]) Default added in v0.1.4

func (f *Field[T]) Default(val T) *Field[T]

Default is a Field option to set a default value for a field. This value will be used if other sources (env, file) do not provide a value.

func (*Field[T]) Env added in v0.1.4

func (f *Field[T]) Env(key string) *Field[T]

Env is a Field option to set an environment variable name to load a value from.

func (*Field[T]) FileKey added in v0.1.4

func (f *Field[T]) FileKey(key string) *Field[T]

FileKey overrides the key used when looking up this field in a config source. The default is the snake_case conversion of the field name.

func (*Field[T]) OneOf added in v0.1.5

func (f *Field[T]) OneOf(v ...T) *Field[T]

OneOf adds a validator that ensures the field value is one of the provided values.

func (*Field[T]) Required added in v0.1.4

func (f *Field[T]) Required() *Field[T]

Required is a Field option that marks this field as required. Loader will return an error during loading if this field is not set.

type FieldError

type FieldError struct {
	Path  string // dot-separated path, e.g., "database.port"
	Rule  string // "required", "parse", "resolve", "validate", "type", "min", "max"
	Value any    // the offending value, nil if absent
	Msg   string
}

FieldError is a single error for a specific configuration field.

type FieldErrors

type FieldErrors []FieldError

FieldErrors is a collection of FieldError values. It implements the error interface and provides a formatted string of all collected errors.

func (FieldErrors) Error

func (e FieldErrors) Error() string

Error returns a string representation of all collected field errors, one per line.

type Group

type Group struct {
	// contains filtered or unexported fields
}

Group represents a collection of configuration items under a named scope. It can be used to model nested structures in configuration files.

func FieldsGroup added in v0.1.4

func FieldsGroup(name string, children ...ConfigItem) *Group

FieldsGroup creates a new Group that wraps multiple configuration items under a named scope. It is used to represent nested configuration structures.

func (*Group) EnvPrefix added in v0.1.4

func (g *Group) EnvPrefix(prefix string) *Group

EnvPrefix sets the environment variable prefix for all children of this group.

func (*Group) FileKey added in v0.1.4

func (g *Group) FileKey(key string) *Group

FileKey sets the configuration file key for this group.

type Loader

type Loader struct {
	// contains filtered or unexported fields
}

Loader is the main object to configure and load configuration values.

func NewLoader

func NewLoader(opts ...LoaderOption) *Loader

NewLoader returns a new Loader instance.

func (*Loader) Load

func (l *Loader) Load(fields []ConfigItem) error

Load populates the configuration based on the provided fields. Each field is resolved sequentially, with environment variables taking precedence over other sources. It returns a FieldErrors collection if any errors occur.

type LoaderOption

type LoaderOption func(*Loader)

LoaderOption is a function type used to configure a Loader.

func WithAutoEnv

func WithAutoEnv() LoaderOption

WithAutoEnv enables automatic resolution of environment variables based on field names. If Env() is not explicitly called on a field, the environment variable name will be derived from the field's path (e.g., "DATABASE_PORT"). Without this option, only fields that have explicit Env() option will be loaded from environment variables.

func WithEnvPrefix

func WithEnvPrefix(envPrefix string) LoaderOption

WithEnvPrefix sets a global prefix applied to all environment variable names, both auto-generated keys and explicit names set via Field.Env. The prefix is joined with [joinEnvPrefix].

func WithLogger

func WithLogger(logger *slog.Logger) LoaderOption

WithLogger sets the logger used by the loader for debugging and warnings.

func WithSource

func WithSource(source Source) LoaderOption

WithSource is a loader option that sets the configuration source (e.g., JSONSource) for the loader.

type NumericField added in v0.1.4

type NumericField[T cmp.Ordered] struct {
	*Field[T]
}

NumericField is a specialized Field for ordered types (integers, floats) that supports additional range-based validators like Min and Max.

func Float

func Float(name string, dest *float64) *NumericField[float64]

Float creates a configuration Field that parses a floating-point value. It returns a NumericField, allowing for range-based validation (Min, Max).

func Int

func Int(name string, dest *int) *NumericField[int]

Int creates a configuration leaf Field that parses string values as base-10 integers. It returns a NumericField, allowing for range-based validation (Min, Max).

func (*NumericField[T]) AddValidator added in v0.1.4

func (f *NumericField[T]) AddValidator(fn func(T) error) *NumericField[T]

AddValidator delegates to the embedded Field.AddValidator and returns f for chaining.

func (*NumericField[T]) Default added in v0.1.4

func (f *NumericField[T]) Default(v T) *NumericField[T]

Default delegates to the embedded Field.Default and returns f for chaining.

func (*NumericField[T]) Env added in v0.1.4

func (f *NumericField[T]) Env(key string) *NumericField[T]

Env delegates to the embedded Field.Env and returns f for chaining.

func (*NumericField[T]) FileKey added in v0.1.4

func (f *NumericField[T]) FileKey(key string) *NumericField[T]

FileKey delegates to the embedded Field.FileKey and returns f for chaining.

func (*NumericField[T]) Max added in v0.1.4

func (f *NumericField[T]) Max(v T) *NumericField[T]

Max adds a validator that ensures the field value is less than or equal to v.

func (*NumericField[T]) Min added in v0.1.4

func (f *NumericField[T]) Min(v T) *NumericField[T]

Min adds a validator that ensures the field value is greater than or equal to v.

func (*NumericField[T]) Required added in v0.1.4

func (f *NumericField[T]) Required() *NumericField[T]

Required delegates to the embedded Field.Required and returns f for chaining.

type Source

type Source interface {
	// Get retrieves a configuration value at the given path.
	// path is a sequence of key segments corresponding to nested positions
	// (e.g., ["database", "host"]).
	// Returns the raw string value, true when found, or an error on retrieval failure.
	// When the key is absent, return ("", false, nil).
	Get(path []string) (string, bool, error)
}

Source is the interface for configuration backends. Implementations provide values from structured sources such as JSON or TOML files.

func NewJSONSource

func NewJSONSource(path string) (Source, error)

NewJSONSource reads and parses the JSON file at the given path. The root of the JSON document must be an object. Returns an error if the file cannot be read or is not valid JSON.

Directories

Path Synopsis
source
toml module

Jump to

Keyboard shortcuts

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