gonfig/gonfig.go
2024-12-11 05:29:46 +01:00

86 lines
2.4 KiB
Go

// Package gonfig provides utilities for reading, unmarshaling, and validating
// configuration files.
package gonfig
import (
"errors"
"fmt"
"os"
)
// UnmarshalFunc is a function that unmarshals raw bytes into a configuration
// object of type T.
type UnmarshalFunc[T any] func([]byte, T) error
// FinalizeFunc is a function that validates a configuration object of type T
// and returns an error if validation fails.
type FinalizeFunc[T any] func(T) error
// ReadConfig reads a configuration file, unmarshals its content into the given
// configuration object, and validates it.
//
// If the primary path is empty, it searches for the configuration file in the
// fallback paths. Returns the resolved path or an error if the file cannot be
// located, read, unmarshaled, or validated.
func ReadConfig[T any](path string, searchPaths []string, c *T, unmarshal UnmarshalFunc[*T], finalize FinalizeFunc[*T]) (string, error) {
var err error
path, err = FindConfig(path, searchPaths)
if err != nil {
return "", err
}
return path, ReadFoundConfig(path, c, unmarshal, finalize)
}
// ReadFoundConfig reads and processes a configuration file from a known path.
//
// Unmarshals the file's content into the given configuration object and
// validates it. Returns an error if the file cannot be read, unmarshaled, or
// validated.
func ReadFoundConfig[T any](path string, c *T, unmarshal UnmarshalFunc[*T], finalize FinalizeFunc[*T]) error {
content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("unable to read configuration file %s: %w", path, err)
}
err = unmarshal(content, c)
if err != nil {
return fmt.Errorf("unable to unmarshal configuration file %s: %w", path, err)
}
return finalize(c)
}
// FindConfig determines the path to the configuration file by using the
// provided primary path or searching through a list of fallback paths if the
// primary path is empty.
//
// Returns the resolved path or an error if no valid file is found or if the
// file is inaccessible.
func FindConfig(path string, paths []string) (string, error) {
var err error
if path == "" {
for _, p := range paths {
_, err = os.Stat(p)
if err != nil {
continue
}
path = p
}
if path == "" {
return "", errors.New("could not locate configuration file")
}
} else {
_, err = os.Stat(path)
if err != nil {
return "", fmt.Errorf("could not stat configuration file %s: %w", path, err)
}
}
return path, nil
}