package confiq
import (
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"reflect"
"time"
)
var (
errIPCannotBeNil = errors.New("IP address cannot be nil")
errCannotParseIP = errors.New("cannot parse IP address")
errURLCannotBeNil = errors.New("URL cannot be nil")
errCannotParseURL = errors.New("cannot parse URL")
errDurationCannotBeNil = errors.New("duration cannot be nil")
errCannotParseDuration = errors.New("cannot parse duration")
errTimeCannotBeNil = errors.New("time cannot be nil")
errCannotParseTime = errors.New("cannot parse time")
errCannotParseNonStringTime = errors.New("cannot parse time from non-string type")
errJSONRawMessageCannotBeNil = errors.New("JSON raw message cannot be nil")
errCannotParseJSONRawMessage = errors.New("cannot marshal source value to JSON")
)
type typeDefinition struct {
kind reflect.Kind
pkgPath string
typeName string
}
var commonDecoders = map[typeDefinition]decoderFunc{
{reflect.Int64, "time", "Duration"}: decodeDuration,
{reflect.Slice, "net", "IP"}: decodeIP,
{reflect.Slice, "encoding/json", "RawMessage"}: decodeJSONRawMessage,
{reflect.Struct, "time", "Time"}: decodeTime,
{reflect.Struct, "net/url", "URL"}: decodeURL,
}
func getCommonDecoder(targetValType reflect.Type) decoderFunc {
if targetValType.Kind() == reflect.Ptr {
targetValType = targetValType.Elem()
}
if decoder, decoderFound := commonDecoders[typeDefinition{targetValType.Kind(), targetValType.PkgPath(), targetValType.Name()}]; decoderFound {
return decoder
}
return nil
}
func decodeDuration(targetValue reflect.Value, sourceValue any) error {
if sourceValue == nil {
return errDurationCannotBeNil
}
parsedDuration, err := time.ParseDuration(castToString(sourceValue))
if err != nil {
return fmt.Errorf("%w: %w", errCannotParseDuration, err)
}
targetValue.SetInt(int64(parsedDuration))
return nil
}
func decodeIP(targetValue reflect.Value, sourceValue any) error {
if sourceValue == nil {
return errIPCannotBeNil
}
parsedIP := net.ParseIP(castToString(sourceValue))
if parsedIP == nil {
return errCannotParseIP
}
targetValue.Set(reflect.ValueOf(parsedIP))
return nil
}
func decodeJSONRawMessage(targetValue reflect.Value, sourceValue any) error {
if sourceValue == nil {
return errJSONRawMessageCannotBeNil
}
marshaledSourceValue, err := json.Marshal(sourceValue)
if err != nil {
return fmt.Errorf("%w: %w", errCannotParseJSONRawMessage, err)
}
targetValue.Set(reflect.ValueOf(marshaledSourceValue))
return nil
}
func decodeTime(targetValue reflect.Value, sourceValue any) error {
if sourceValue == nil {
return errTimeCannotBeNil
}
if timeValue, isTimeValue := sourceValue.(time.Time); isTimeValue {
targetValue.Set(reflect.ValueOf(timeValue))
return nil
}
stringValue, isStringValue := sourceValue.(string)
if !isStringValue {
return fmt.Errorf("%w: %T", errCannotParseNonStringTime, sourceValue)
}
parsedTime, err := time.Parse(time.RFC3339, stringValue)
if err != nil {
return fmt.Errorf("%w: %w", errCannotParseTime, err)
}
targetValue.Set(reflect.ValueOf(parsedTime))
return nil
}
func decodeURL(targetValue reflect.Value, sourceValue any) error {
if sourceValue == nil {
return errURLCannotBeNil
}
parsedURL, err := url.Parse(castToString(sourceValue))
if err != nil {
return fmt.Errorf("%w: %w", errCannotParseURL, err)
}
targetValue.Set(reflect.ValueOf(*parsedURL))
return nil
}
// Package confiq is a Go package for populating structs from structured data formats such JSON, TOML, YAML or Env.
package confiq
import (
"errors"
"fmt"
"reflect"
)
const defaultTag = "cfg"
var (
errCannotGetKeyFromNonMap = errors.New("cannot get key from non-map type")
errKeyNotFound = errors.New("key not found")
errCannotGetIndexFromNonSlice = errors.New("cannot get index from non-slice type")
errIndexOutOfBounds = errors.New("index out of bounds")
)
type decoder struct {
tag string
}
type decodeSettings struct {
strict bool
prefix string
}
// ConfigSet is a configuration set that can be used to load and decode configuration values into a struct.
type ConfigSet struct {
value *any
decoder *decoder
}
// New creates a new ConfigSet with the given options.
func New(options ...configSetOption) *ConfigSet {
var (
value any
configSet = &ConfigSet{
value: &value,
decoder: &decoder{tag: defaultTag},
}
)
for _, option := range options {
option(configSet)
}
return configSet
}
// Get returns the configuration value at the given path as an interface.
func (c *ConfigSet) Get(path string) (any, error) {
return c.getByPath(path)
}
func (c *ConfigSet) getByPath(path string) (any, error) {
currentValue := *c.value
for path != "" {
var currentSegment segment
currentSegment, path = getNextSegment(path)
switch v := currentSegment.(type) {
case keySegment:
segmentValue, err := getMapValue(currentValue, v.asString())
if err != nil {
return nil, err
}
currentValue = segmentValue
case indexSegment:
segmentValue, err := getSliceValue(currentValue, v.asInt())
if err != nil {
return nil, err
}
currentValue = segmentValue
}
}
return currentValue, nil
}
func getMapValue(originMap any, key string) (any, error) {
v := reflect.ValueOf(originMap)
if v.Kind() != reflect.Map {
return nil, fmt.Errorf("%w: %s(%T)", errCannotGetKeyFromNonMap, key, originMap)
}
value := v.MapIndex(reflect.ValueOf(key))
if !value.IsValid() {
return nil, fmt.Errorf("%w: %s", errKeyNotFound, key)
}
return value.Interface(), nil
}
func getSliceValue(originSlice any, index int) (any, error) {
v := reflect.ValueOf(originSlice)
if v.Kind() != reflect.Slice {
return nil, fmt.Errorf("%w: %d(%T)", errCannotGetIndexFromNonSlice, index, originSlice)
}
if v.Len() <= index || index < 0 {
return nil, fmt.Errorf("%w: %d", errIndexOutOfBounds, index)
}
return v.Index(index).Interface(), nil
}
package confiq
import (
"encoding"
"errors"
"fmt"
"reflect"
"strings"
)
const sliceSplitChar = ";"
// Collection of decode errors.
var (
ErrInvalidTarget = errors.New("target must be non-nil pointer to a slice, map or struct that has at least one exported field with a the configured tag")
ErrNoTargetFieldsAreSet = errors.New("none of the target fields were set from config values")
)
var (
errCannotDecodeCustomTypeField = errors.New("cannot decode field with custom decoder")
errCannotDecodeNonRequiredField = errors.New("cannot decode non-strict field")
errCannotDecodeNonSliceValueToTarget = errors.New("cannot decode non-slice value to target")
errCannotUnmarshalPrimitive = errors.New("cannot unmarshal primitive as text")
errCannotHaveDefaultForRequiredField = errors.New("cannot have default value for required field")
errUnsupportedPrimitiveKind = errors.New("unsupported primitive kind")
)
type (
decoderFunc func(targetField reflect.Value, value any) error
fieldDecoderFunc func(targetField reflect.Value, value any, strict bool) (int, error)
)
type fieldOptions struct {
path string
strict bool
required bool
defaultValue *string
}
type Decoder interface {
Decode(value any) error
}
// Decode decodes the configuration values into the target struct.
func (c *ConfigSet) Decode(target interface{}, options ...decodeOption) error {
return c.decode(target, options)
}
func (c *ConfigSet) decode(target interface{}, options []decodeOption) error {
decodeSettings := &decodeSettings{
strict: false,
prefix: "",
}
for _, option := range options {
option(decodeSettings)
}
targetValue := reflect.ValueOf(target)
if targetValue.Kind() != reflect.Ptr || targetValue.IsNil() {
return ErrInvalidTarget
}
targetValue = targetValue.Elem()
decodedFieldCount, err := c.decodeField(targetValue, fieldOptions{
path: decodeSettings.prefix,
strict: decodeSettings.strict,
required: false,
defaultValue: nil,
})
if err != nil {
return err
} else if decodedFieldCount == 0 {
return ErrNoTargetFieldsAreSet
}
return nil
}
func (c *ConfigSet) getFieldConfigValue(fieldOpts fieldOptions) (any, error) {
if fieldOpts.required && fieldOpts.defaultValue != nil {
return nil, fmt.Errorf("%w: %s", errCannotHaveDefaultForRequiredField, fieldOpts.path)
}
configValue, err := c.getByPath(fieldOpts.path)
if err != nil {
if fieldOpts.required {
return nil, fmt.Errorf("field is required: %w", err)
}
if fieldOpts.defaultValue != nil {
return *fieldOpts.defaultValue, nil
}
return nil, errCannotDecodeNonRequiredField
}
return configValue, nil
}
func (c *ConfigSet) decodeField(targetValue reflect.Value, fieldOpts fieldOptions) (int, error) {
fieldConfigValue, err := c.getFieldConfigValue(fieldOpts)
if err != nil {
if !errors.Is(err, errCannotDecodeNonRequiredField) {
return 0, err
}
}
var (
decodedFields int
decodeErr error
fieldDecoder fieldDecoderFunc
)
if commonDecoder := getCommonDecoder(targetValue.Type()); commonDecoder != nil {
return c.decodeCommon(commonDecoder, targetValue, fieldConfigValue, fieldOpts.strict)
}
if targetValue.Kind() == reflect.Ptr {
if fieldConfigValue == nil {
return 0, nil
}
dereferencedTargetValue := reflect.New(targetValue.Type().Elem()).Elem()
decodedFields, err := c.decodeField(dereferencedTargetValue, fieldOpts)
if err != nil {
if fieldOpts.strict {
return 0, fmt.Errorf("error decoding pointer value: %w", err)
}
return 0, nil
}
targetValue.Set(dereferencedTargetValue.Addr())
return decodedFields, nil
}
// check if targetValue implements Decoder interface
if decoder, ok := targetValue.Addr().Interface().(Decoder); ok {
if err := decoder.Decode(fieldConfigValue); err != nil {
return 0, fmt.Errorf("%w: %w", errCannotDecodeCustomTypeField, err)
}
return 1, nil
}
switch targetValue.Kind() {
case reflect.Map:
fieldDecoder = c.decodeMap
case reflect.Slice:
fieldDecoder = c.decodeSlice
case reflect.Struct:
fieldDecoder = c.decodeStruct
default:
fieldDecoder = c.decodePrimitiveType
}
decodedFields, decodeErr = fieldDecoder(targetValue, fieldConfigValue, fieldOpts.strict)
if decodeErr != nil {
return 0, decodeErr
}
return decodedFields, nil
}
func (c *ConfigSet) decodeCommon(commonDecoder decoderFunc, targetValue reflect.Value, fieldConfigValue any, strict bool) (int, error) {
for targetValue.Kind() == reflect.Ptr {
if fieldConfigValue == nil {
return 0, nil
}
if targetValue.IsNil() {
targetValue.Set(reflect.New(targetValue.Type().Elem()))
}
targetValue = targetValue.Elem()
}
if err := commonDecoder(targetValue, fieldConfigValue); err != nil {
if strict {
return 0, fmt.Errorf("error decoding field value: %w", err)
}
return 0, nil
}
return 1, nil
}
func (c *ConfigSet) decodeMap(targetMapValue reflect.Value, configValue any, strict bool) (int, error) {
var (
configMapValue = reflect.ValueOf(configValue)
targetMapValueType = targetMapValue.Type()
targetKeyType = targetMapValueType.Key()
targetValueType = targetMapValueType.Elem()
setFieldCount = 0
)
// setup empty map
targetMapValue.Set(reflect.MakeMap(targetMapValueType))
for _, key := range configMapValue.MapKeys() {
var (
k = reflect.New(targetKeyType).Elem()
v = reflect.New(targetValueType).Elem()
)
// decode map key
_, err := c.decodePrimitiveType(k, key.Interface(), strict)
if err != nil {
return 0, fmt.Errorf("error decoding map key: %w", err)
}
// decode map value
decodedFieldCount, err := c.subValue(configValue).
decodeField(v, fieldOptions{
path: keySegment(key.String()).String(),
strict: strict,
required: false,
defaultValue: nil,
})
if err != nil {
return 0, fmt.Errorf("error decoding map value: %w", err)
}
targetMapValue.SetMapIndex(k, v)
setFieldCount += decodedFieldCount
}
return setFieldCount, nil
}
func (c *ConfigSet) decodeStruct(targetStructValue reflect.Value, configValue any, strict bool) (int, error) {
var (
targetStructType = targetStructValue.Type()
setFieldCount = 0
)
for i := range targetStructValue.NumField() {
// get the struct field's tag and options
targetStructFieldOpts := c.readTag(targetStructType.Field(i), c.decoder.tag)
// get the struct field's reflection value
targetStructFieldValue := targetStructValue.Field(i)
// check if the field is exported
if !targetStructFieldValue.CanSet() || !targetStructFieldValue.Addr().CanInterface() {
continue
}
// set the field's strictness
targetStructFieldOpts.strict = strict || targetStructFieldOpts.strict
// decode the field
decodedFieldCount, err := c.subValue(configValue).
decodeField(targetStructFieldValue, targetStructFieldOpts)
if err != nil {
return 0, fmt.Errorf("error decoding struct field value: %w", err)
}
setFieldCount += decodedFieldCount
}
return setFieldCount, nil
}
func (c *ConfigSet) decodeSlice(targetSliceValue reflect.Value, configValue any, strict bool) (int, error) {
var (
configSliceValue = reflect.ValueOf(configValue)
configSliceValueKind = configSliceValue.Kind()
setFieldCount = 0
)
if configSliceValueKind != reflect.Slice {
if configSliceValueKind != reflect.String {
return 0, fmt.Errorf("%w: %v", errCannotDecodeNonSliceValueToTarget, configSliceValueKind)
}
return c.decodeSlice(targetSliceValue, strings.Split(configSliceValue.String(), sliceSplitChar), strict)
}
configSliceValueLength := configSliceValue.Len()
// Set slice to the same length as the config slice
targetSliceValue.Set(reflect.MakeSlice(targetSliceValue.Type(), configSliceValueLength, configSliceValueLength))
// Decode each element based on its type
for i := range configSliceValueLength {
decodedFieldCount, err := c.subValue(configValue).
decodeField(targetSliceValue.Index(i), fieldOptions{
path: indexSegment(i).String(),
strict: strict,
required: false,
defaultValue: nil,
})
if err != nil {
return setFieldCount, fmt.Errorf("error decoding slice element value: %w", err)
}
setFieldCount += decodedFieldCount
}
return setFieldCount, nil
}
func (c *ConfigSet) decodePrimitiveType(primitiveValue reflect.Value, configValue any, strict bool) (int, error) {
primitiveInterface := primitiveValue.Addr().Interface()
// check if primitive implements encoding.TextUnmarshaler interface
if unmarshaler, ok := primitiveInterface.(encoding.TextUnmarshaler); ok {
if err := unmarshaler.UnmarshalText(castToBytes(configValue)); err != nil {
return 0, fmt.Errorf("%w: %w", errCannotUnmarshalPrimitive, err)
}
primitiveValue.Set(reflect.ValueOf(primitiveInterface).Elem())
return 1, nil
}
// select the appropriate decoder function based on the primitive's kind
var (
primitiveDecoderFunc decoderFunc
primitiveValueKind = primitiveValue.Kind()
)
switch primitiveValueKind {
case reflect.Bool:
primitiveDecoderFunc = decodeBool
case reflect.String:
primitiveDecoderFunc = decodeString
case reflect.Float32, reflect.Float64:
primitiveDecoderFunc = decodeFloat
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
primitiveDecoderFunc = decodeInt
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
primitiveDecoderFunc = decodeUint
default:
return 0, fmt.Errorf("%w: %v", errUnsupportedPrimitiveKind, primitiveValueKind)
}
if err := primitiveDecoderFunc(primitiveValue, configValue); err != nil {
if strict {
return 0, fmt.Errorf("error decoding primitive value: %w", err)
}
return 0, nil
}
return 1, nil
}
func (c *ConfigSet) readTag(field reflect.StructField, tag string) fieldOptions {
fieldOpts := fieldOptions{
path: "",
strict: false,
required: false,
defaultValue: nil,
}
tagValue := field.Tag.Get(tag)
if tagValue == "" {
return fieldOpts
}
tagParts := strings.Split(tagValue, ",")
fieldOpts.path = tagParts[0]
// read the remaining tag parts
for _, part := range tagParts[1:] {
if part == "strict" {
fieldOpts.strict = true
continue
}
if part == "required" {
fieldOpts.required = true
continue
}
if strings.HasPrefix(part, "default=") {
devaultValue := part[8:]
fieldOpts.defaultValue = &devaultValue
continue
}
}
return fieldOpts
}
func (c *ConfigSet) subValue(value any) *ConfigSet {
return &ConfigSet{
value: &value,
decoder: c.decoder,
}
}
func castToBytes(value any) []byte {
if value == nil {
return nil
}
return []byte(castToString(value))
}
func castToString(value any) string {
if value == nil {
return ""
}
if stringValue, ok := value.(string); ok {
return stringValue
}
return fmt.Sprintf("%v", value)
}
package confiq
import (
"errors"
"fmt"
"maps"
)
const noPrefix = ""
// Collection of loader errors.
var (
ErrCannotOpenConfig = errors.New("cannot open config")
ErrCannotLoadConfig = errors.New("cannot load config")
)
var (
errValueCannotBeNil = errors.New("value cannot be nil")
errCannotApplyValueOfThisType = errors.New("cannot apply value of this type")
errCannotApplyMapValue = errors.New("cannot apply map value")
errCannotApplySliceValue = errors.New("cannot apply slice value")
)
type loader struct {
prefix string
}
func (c *ConfigSet) Load(valueContainer IValueContainer, options ...loadOption) error {
if errs := valueContainer.Errors(); len(errs) > 0 {
return fmt.Errorf("%w: %w", ErrCannotLoadConfig, errors.Join(errs...))
}
return c.applyValues(valueContainer.Get(), options...)
}
// LoadRawValue loads a raw value into the config set.
// The value must be a map[string]any or a slice of any.
func (c *ConfigSet) LoadRawValue(newValues []any, options ...loadOption) error {
if newValues == nil {
return errValueCannotBeNil
}
return c.applyValues(newValues, options...)
}
func newLoader() *loader {
return &loader{
prefix: noPrefix,
}
}
func (c *ConfigSet) applyValues(newValues []any, options ...loadOption) error {
loader := newLoader()
for _, option := range options {
option(loader)
}
for _, newValue := range newValues {
switch v := newValue.(type) {
case map[string]any:
if err := c.applyMap(v, loader.prefix); err != nil {
return err
}
case []any:
if err := c.applySlice(v, loader.prefix); err != nil {
return err
}
default:
return fmt.Errorf("%w: %T", errCannotApplyValueOfThisType, newValue)
}
}
return nil
}
func (c *ConfigSet) applyMap(newValue map[string]any, prefix string) error {
if prefix == "" {
return c.applyMapWithoutPrefix(newValue)
}
return c.applyMapWithPrefix(newValue, prefix)
}
func (c *ConfigSet) applySlice(newValue []any, prefix string) error {
if prefix == "" {
return c.applySliceWithoutPrefix(newValue)
}
return c.applySliceWithPrefix(newValue, prefix)
}
func (c *ConfigSet) applyMapWithoutPrefix(newValue map[string]any) error {
if *c.value == nil {
*c.value = newValue
return nil
}
valueMap, ok := (*c.value).(map[string]any)
if !ok {
return errCannotApplyMapValue
}
maps.Copy(valueMap, newValue)
return nil
}
func (c *ConfigSet) applyMapWithPrefix(newValue map[string]any, prefix string) error {
if *c.value == nil {
*c.value = map[string]any{}
}
valueMap, ok := (*c.value).(map[string]any)
if !ok {
return errCannotApplyMapValue
}
valueMapAtPath, ok := valueMap[prefix]
if !ok {
valueMap[prefix] = newValue
return nil
}
valueMapAtPathMap, ok := valueMapAtPath.(map[string]any)
if !ok {
return errCannotApplyMapValue
}
maps.Copy(valueMapAtPathMap, newValue)
return nil
}
func (c *ConfigSet) applySliceWithoutPrefix(newValue []any) error {
if *c.value == nil {
*c.value = newValue
return nil
}
valueSlice, ok := (*c.value).([]any)
if !ok {
return errCannotApplySliceValue
}
valueSlice = append(valueSlice, newValue...)
*c.value = valueSlice
return nil
}
func (c *ConfigSet) applySliceWithPrefix(newValue []any, prefix string) error {
if *c.value == nil {
*c.value = map[string]any{}
}
valueMap, ok := (*c.value).(map[string]any)
if !ok {
return errCannotApplySliceValue
}
valueMapAtPath, ok := valueMap[prefix]
if !ok {
valueMap[prefix] = newValue
return nil
}
valueMapAtPathSlice, ok := valueMapAtPath.([]any)
if !ok {
return errCannotApplySliceValue
}
valueMapAtPathSlice = append(valueMapAtPathSlice, newValue...)
valueMap[prefix] = valueMapAtPathSlice
return nil
}
// Package confiqenv allows confiq values to be loaded from Env format.
package confiqenv
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/go-envparse"
)
const (
envSplitChar = "="
envSplitElements = 2
)
var (
ErrCannotGetBytesFromReader = errors.New("cannot get bytes from reader")
ErrCannotOpenEnvFile = errors.New("cannot open Env file")
ErrCannotReadEnvData = errors.New("cannot read Env data")
)
// Container is a struct that holds the loaded values.
type Container struct {
values []any
errors []error
}
// Get returns the loaded JSON values.
func (c *Container) Get() []any {
return c.values
}
// Errors returns the errors that occurred during the loading process.
func (c *Container) Errors() []error {
return c.errors
}
// Load creates an empty container, into which the JSON values can be loaded.
func Load() *Container {
container := new(Container)
return container
}
// FromEnvironment loads config data from the environment variables.
func (c *Container) FromEnvironment() *Container {
var (
envSlice = os.Environ()
envMap = make(map[string]any)
)
for _, envElement := range envSlice {
envKeyValue := strings.SplitN(envElement, envSplitChar, envSplitElements)
envMap[envKeyValue[0]] = envKeyValue[1]
}
c.values = append(c.values, envMap)
return nil
}
// FromFile loads a Env file from the given path.
func (c *Container) FromFile(path string) *Container {
inputBytes, err := os.ReadFile(filepath.Clean(path))
if err != nil {
c.errors = append(c.errors, fmt.Errorf("%w: %w", ErrCannotOpenEnvFile, err))
return c
}
return c.FromBytes(inputBytes)
}
// FromString loads a Env file from the given string.
func (c *Container) FromString(input string) *Container {
return c.FromBytes([]byte(input))
}
// FromBytes loads a Env file from the given bytes.
func (c *Container) FromBytes(input []byte) *Container {
return c.FromReader(bytes.NewReader(input))
}
// FromReader loads a Env file from a reader stream.
func (c *Container) FromReader(reader io.Reader) *Container {
if reader == nil {
c.errors = append(c.errors, ErrCannotReadEnvData)
return c
}
parsedEnvMap, err := envparse.Parse(reader)
if err != nil {
c.errors = append(c.errors, fmt.Errorf("%w: %w", ErrCannotReadEnvData, err))
return c
}
c.values = append(c.values, envMapAsAny(parsedEnvMap))
return c
}
func envMapAsAny(envMap map[string]string) map[string]any {
anyMap := make(map[string]any)
for key, value := range envMap {
anyMap[key] = value
}
return anyMap
}
// Package confiqjson allows confiq values to be loaded from JSON format.
package confiqjson
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
)
var (
ErrCannotOpenJSONFile = errors.New("cannot open JSON file")
ErrCannotReadJSONData = errors.New("cannot read JSON data")
ErrCannotReadJSONBytes = errors.New("cannot read JSON bytes")
)
// Container is a struct that holds the loaded values.
type Container struct {
values []any
errors []error
}
// Get returns the loaded JSON values.
func (c *Container) Get() []any {
return c.values
}
// Errors returns the errors that occurred during the loading process.
func (c *Container) Errors() []error {
return c.errors
}
// Load creates an empty container, into which the JSON values can be loaded.
func Load() *Container {
container := new(Container)
return container
}
// FromFile loads a JSON file from the given path.
func (c *Container) FromFile(path string) *Container {
bytes, err := os.ReadFile(filepath.Clean(path))
if err != nil {
c.errors = append(c.errors, fmt.Errorf("%w: %w", ErrCannotOpenJSONFile, err))
return c
}
c.readFromBytes(bytes)
return c
}
// FromString loads a JSON file from the given string.
func (c *Container) FromString(input string) *Container {
c.readFromBytes([]byte(input))
return c
}
// FromReader loads a JSON file from a reader stream.
func (c *Container) FromReader(reader io.Reader) *Container {
if reader == nil {
c.errors = append(c.errors, ErrCannotReadJSONData)
return c
}
buffer := new(bytes.Buffer)
if _, err := buffer.ReadFrom(reader); err != nil {
c.errors = append(c.errors, fmt.Errorf("%w: %w", ErrCannotReadJSONData, err))
return c
}
c.readFromBytes(buffer.Bytes())
return c
}
// FromBytes loads a JSON file from the given bytes.
func (c *Container) FromBytes(input []byte) *Container {
c.readFromBytes(input)
return c
}
func (c *Container) readFromBytes(input []byte) {
var value any
if err := json.Unmarshal(input, &value); err != nil {
c.errors = append(c.errors, fmt.Errorf("%w: %w", ErrCannotReadJSONBytes, err))
return
}
c.values = append(c.values, value)
}
// Package confiqtoml allows confiq values to be loaded from TOML format.
package confiqtoml
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"github.com/pelletier/go-toml"
)
var (
ErrCannotOpenTOMLFile = errors.New("cannot open TOML file")
ErrCannotReadTOMLData = errors.New("cannot read TOML data")
ErrCannotReadTOMLBytes = errors.New("cannot read TOML bytes")
)
// Container is a struct that holds the loaded values.
type Container struct {
values []any
errors []error
}
// Get returns the loaded TOML values.
func (c *Container) Get() []any {
return c.values
}
// Errors returns the errors that occurred during the loading process.
func (c *Container) Errors() []error {
return c.errors
}
// Load creates an empty container, into which the TOML values can be loaded.
func Load() *Container {
container := new(Container)
return container
}
// FromFile loads a TOML file from the given path.
func (c *Container) FromFile(path string) *Container {
bytes, err := os.ReadFile(filepath.Clean(path))
if err != nil {
c.errors = append(c.errors, fmt.Errorf("%w: %w", ErrCannotOpenTOMLFile, err))
return c
}
c.readFromBytes(bytes)
return c
}
// FromString loads a TOML file from the given string.
func (c *Container) FromString(input string) *Container {
c.readFromBytes([]byte(input))
return c
}
// FromReader loads a TOML file from a reader stream.
func (c *Container) FromReader(reader io.Reader) *Container {
if reader == nil {
c.errors = append(c.errors, ErrCannotReadTOMLData)
return c
}
buffer := new(bytes.Buffer)
if _, err := buffer.ReadFrom(reader); err != nil {
c.errors = append(c.errors, fmt.Errorf("%w: %w", ErrCannotReadTOMLData, err))
return c
}
c.readFromBytes(buffer.Bytes())
return c
}
// FromBytes loads a TOML file from the given bytes.
func (c *Container) FromBytes(input []byte) *Container {
c.readFromBytes(input)
return c
}
func (c *Container) readFromBytes(input []byte) {
var value any
if err := toml.Unmarshal(input, &value); err != nil {
c.errors = append(c.errors, fmt.Errorf("%w: %w", ErrCannotReadTOMLBytes, err))
return
}
c.values = append(c.values, value)
}
// Package confiqyaml allows confiq values to be loaded from YAML format.
package confiqyaml
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"github.com/goccy/go-yaml"
)
var (
ErrCannotOpenYAMLFile = errors.New("cannot open YAML file")
ErrCannotReadYAMLData = errors.New("cannot read YAML data")
ErrCannotReadYAMLBytes = errors.New("cannot read YAML bytes")
)
// Container is a struct that holds the loaded values.
type Container struct {
values []any
errors []error
}
// Get returns the loaded YAML values.
func (c *Container) Get() []any {
return c.values
}
// Errors returns the errors that occurred during the loading process.
func (c *Container) Errors() []error {
return c.errors
}
// Load creates an empty container, into which the YAML values can be loaded.
func Load() *Container {
container := new(Container)
return container
}
// FromFile loads a YAML file from the given path.
func (c *Container) FromFile(path string) *Container {
bytes, err := os.ReadFile(filepath.Clean(path))
if err != nil {
c.errors = append(c.errors, fmt.Errorf("%w: %w", ErrCannotOpenYAMLFile, err))
return c
}
c.readFromBytes(bytes)
return c
}
// FromString loads a YAML file from the given string.
func (c *Container) FromString(input string) *Container {
c.readFromBytes([]byte(input))
return c
}
// FromReader loads a YAML file from a reader stream.
func (c *Container) FromReader(reader io.Reader) *Container {
if reader == nil {
c.errors = append(c.errors, ErrCannotReadYAMLData)
return c
}
buffer := new(bytes.Buffer)
if _, err := buffer.ReadFrom(reader); err != nil {
c.errors = append(c.errors, fmt.Errorf("%w: %w", ErrCannotReadYAMLData, err))
return c
}
c.readFromBytes(buffer.Bytes())
return c
}
// FromBytes loads a YAML file from the given bytes.
func (c *Container) FromBytes(input []byte) *Container {
c.readFromBytes(input)
return c
}
func (c *Container) readFromBytes(input []byte) {
var value any
if err := yaml.Unmarshal(input, &value); err != nil {
c.errors = append(c.errors, fmt.Errorf("%w: %w", ErrCannotReadYAMLBytes, err))
return
}
c.values = append(c.values, value)
}
package confiq
// ConfigSetOptions is exposed so that functions which wrap the New function can make adding the WithTag option easier.
type ConfigSetOptions []loadOption
type configSetOption func(*ConfigSet)
// WithTag sets the struct tag to be used by the decoder for reading configuration values of struct fields.
func WithTag(tag string) configSetOption {
return func(s *ConfigSet) {
s.decoder.tag = tag
}
}
// LoadOptions is exposed so that functions which wrap the Load function can make adding the WithPrefix option easier.
type LoadOptions []loadOption
type loadOption func(*loader)
// WithPrefix sets the prefix to be used when loading configuration values into the ConfigSet.
func WithPrefix(prefix string) loadOption {
return func(l *loader) {
l.prefix = prefix
}
}
// DecodeOptions is exposed so that functions which wrap the Decode function can make adding the AsStrict and FromPrefix options easier.
type DecodeOptions []decodeOption
type decodeOption func(*decodeSettings)
// AsStrict sets the decoder to decode the configuration values as if all fields are set to strict.
func AsStrict() decodeOption {
return func(d *decodeSettings) {
d.strict = true
}
}
// FromPrefix sets the prefix to be used when decoding configuration values into the target struct.
func FromPrefix(prefix string) decodeOption {
return func(d *decodeSettings) {
d.prefix = prefix
}
}
package confiq
import (
"errors"
"fmt"
"reflect"
"strconv"
)
var (
errCannotParseBool = errors.New("cannot parse bool")
errCannotParseFloat = errors.New("cannot parse float")
errCannotParseInt = errors.New("cannot parse int")
errCannotParseUint = errors.New("cannot parse uint")
)
func decodeString(targetValue reflect.Value, sourceValue any) error {
targetValue.SetString(castToString(sourceValue))
return nil
}
func decodeBool(targetValue reflect.Value, sourceValue any) error {
if boolValue, ok := sourceValue.(bool); ok {
targetValue.SetBool(boolValue)
return nil
} else {
parsedBool, err := strconv.ParseBool(castToString(sourceValue))
if err != nil {
return fmt.Errorf("%w: %w", errCannotParseBool, err)
}
targetValue.SetBool(parsedBool)
}
return nil
}
func decodeFloat(targetValue reflect.Value, sourceValue any) error {
switch sV := sourceValue.(type) {
case float32:
targetValue.SetFloat(float64(sV))
case float64:
targetValue.SetFloat(sV)
default:
parsedFloat, err := strconv.ParseFloat(castToString(sourceValue), targetValue.Type().Bits())
if err != nil {
return fmt.Errorf("%w: %w", errCannotParseFloat, err)
}
targetValue.SetFloat(parsedFloat)
}
return nil
}
func decodeInt(targetValue reflect.Value, sourceValue any) error {
switch sV := sourceValue.(type) {
case int:
targetValue.SetInt(int64(sV))
case int8:
targetValue.SetInt(int64(sV))
case int16:
targetValue.SetInt(int64(sV))
case int32:
targetValue.SetInt(int64(sV))
case int64:
targetValue.SetInt(sV)
default:
parsedInt, err := strconv.ParseInt(castToString(sourceValue), 0, targetValue.Type().Bits())
if err != nil {
return fmt.Errorf("%w: %w", errCannotParseInt, err)
}
targetValue.SetInt(parsedInt)
}
return nil
}
func decodeUint(targetValue reflect.Value, sourceValue any) error {
switch sV := sourceValue.(type) {
case uint:
targetValue.SetUint(uint64(sV))
case uint8:
targetValue.SetUint(uint64(sV))
case uint16:
targetValue.SetUint(uint64(sV))
case uint32:
targetValue.SetUint(uint64(sV))
case uint64:
targetValue.SetUint(sV)
default:
parsedUint, err := strconv.ParseUint(castToString(sourceValue), 0, targetValue.Type().Bits())
if err != nil {
return fmt.Errorf("%w: %w", errCannotParseUint, err)
}
targetValue.SetUint(parsedUint)
}
return nil
}
package confiq
import (
"strconv"
"strings"
)
const (
segmentDividerChar = "."
openBraceChar = "["
closeBraceChar = "]"
)
type segment interface {
String() string
}
type keySegment string
func (kS keySegment) String() string {
return string(kS)
}
func (kS keySegment) asString() string {
return string(kS)
}
type indexSegment int
func (iS indexSegment) String() string {
return openBraceChar + strconv.Itoa(int(iS)) + closeBraceChar
}
func (iS indexSegment) asInt() int {
return int(iS)
}
func getNextSegment(path string) (segment, string) {
var (
nextSegment, remainingPath string
segmentDividerIndex = strings.Index(path, segmentDividerChar)
)
if segmentDividerIndex == -1 {
nextSegment = path
remainingPath = ""
} else {
nextSegment = path[:segmentDividerIndex]
remainingPath = path[segmentDividerIndex+1:]
}
openBraceIndex := strings.Index(path, openBraceChar)
if openBraceIndex == -1 {
return keySegment(nextSegment), remainingPath
}
closeBraceIndex := strings.Index(nextSegment, closeBraceChar)
if closeBraceIndex == -1 || closeBraceIndex < openBraceIndex {
return keySegment(nextSegment), remainingPath
}
if openBraceIndex != 0 {
return keySegment(nextSegment[:openBraceIndex]), nextSegment[openBraceIndex:closeBraceIndex+1] + segmentDividerChar + remainingPath
}
index := nextSegment[openBraceIndex+1 : closeBraceIndex]
if indexInt, err := strconv.Atoi(index); err == nil {
return indexSegment(indexInt), remainingPath
}
return keySegment(index), remainingPath
}