Skip to content

Commit

Permalink
add envparse package (#17)
Browse files Browse the repository at this point in the history
* add envparse package

Signed-off-by: Sarah Funkhouser <[email protected]>

* go mod tidy

Signed-off-by: Sarah Funkhouser <[email protected]>

---------

Signed-off-by: Sarah Funkhouser <[email protected]>
  • Loading branch information
golanglemonade authored Sep 19, 2024
1 parent 58ec6db commit 55fce63
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 0 deletions.
2 changes: 2 additions & 0 deletions envparse/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package envparse provides a way to parse environment variables from a struct
package envparse
128 changes: 128 additions & 0 deletions envparse/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package envparse

import (
"errors"
"fmt"
"reflect"
"strings"

"github.com/stoewer/go-strcase"
)

// ErrInvalidSpecification indicates that a specification is of the wrong type.
var ErrInvalidSpecification = errors.New("specification must be a struct pointer")

type Config struct {
// FieldTagName is the name of the struct tag to use for the field name
FieldTagName string
// Skipper is the value of the tag to skip parsing of the field
Skipper string
}

// varInfo maintains information about the configuration variable
type varInfo struct {
FieldName string
FullPath string
Key string
Type reflect.Type
Tags reflect.StructTag
}

// GatherEnvInfo gathers information about the specified struct, including defaults and environment variable names.
func (c Config) GatherEnvInfo(prefix string, spec interface{}) ([]varInfo, error) {
s := reflect.ValueOf(spec)

// Ensure the specification is a pointer to a struct
if s.Kind() != reflect.Ptr {
return nil, ErrInvalidSpecification
}

s = s.Elem()
if s.Kind() != reflect.Struct {
return nil, ErrInvalidSpecification
}

typeOfSpec := s.Type()

// Create a slice to hold the information about the configuration variables
var infos []varInfo

// Iterate over the struct fields
for i := range s.NumField() {
f := s.Field(i)
ftype := typeOfSpec.Field(i)

if !f.CanSet() {
continue
}

for f.Kind() == reflect.Ptr {
if f.IsNil() {
if f.Type().Elem().Kind() != reflect.Struct {
// nil pointer to a non-struct: leave it alone
break
}

// nil pointer to struct: create a zero instance
f.Set(reflect.New(f.Type().Elem()))
}

f = f.Elem()
}

// Capture information about the config variable
fieldName := c.getFieldName(ftype)
if fieldName == c.Skipper {
continue
}

info := varInfo{
FieldName: fieldName,
FullPath: ftype.Name,
Type: ftype.Type,
Tags: ftype.Tag,
}

// Default to the field name as the env var name (will be upcased)
info.Key = info.FieldName

if prefix != "" {
info.Key = fmt.Sprintf("%s_%s", prefix, info.Key)
info.FullPath = fmt.Sprintf("%s.%s", strcase.LowerCamelCase(strings.Replace(prefix, "_", ".", -1)), info.FieldName) // nolint: gocritic
}

info.Key = strings.ToUpper(info.Key)
infos = append(infos, info)

if f.Kind() == reflect.Struct {
innerPrefix := prefix

if !ftype.Anonymous {
innerPrefix = prefix + "_" + info.Tags.Get("json")
}

embeddedPtr := f.Addr().Interface()

// Recursively gather information about the embedded struct
embeddedInfos, err := c.GatherEnvInfo(innerPrefix, embeddedPtr)
if err != nil {
return nil, err
}

infos = append(infos[:len(infos)-1], embeddedInfos...)

continue
}
}

return infos, nil
}

func (c Config) getFieldName(ftype reflect.StructField) string {
if ftype.Tag.Get(c.FieldTagName) != "" {
return ftype.Tag.Get(c.FieldTagName)
}

// default to skip if the koanf tag is not present
return c.Skipper
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/rs/zerolog v1.33.0
github.com/sendgrid/rest v2.6.9+incompatible
github.com/sendgrid/sendgrid-go v3.16.0+incompatible
github.com/stoewer/go-strcase v1.3.0
github.com/stretchr/testify v1.9.0
github.com/theopenlane/echox v0.2.0
github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,15 @@ github.com/sendgrid/sendgrid-go v3.16.0+incompatible h1:i8eE6IMkiCy7vusSdacHHSBU
github.com/sendgrid/sendgrid-go v3.16.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/theopenlane/echox v0.2.0 h1:s9DJJrsLOSPsXVfgmQxgXmSVtxzztBnSmcVX4ax7tIM=
Expand Down

0 comments on commit 55fce63

Please sign in to comment.