Skip to content

Commit

Permalink
refactor type walker
Browse files Browse the repository at this point in the history
  • Loading branch information
beckend committed May 25, 2021
1 parent 3fa1b70 commit 4e9d52c
Show file tree
Hide file tree
Showing 13 changed files with 1,357 additions and 171 deletions.
81 changes: 49 additions & 32 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import (

environment "github.com/beckend/go-config/pkg/environment"
file "github.com/beckend/go-config/pkg/file"
"github.com/beckend/go-config/pkg/reflection"
singletons "github.com/beckend/go-config/pkg/singletons"
validation "github.com/beckend/go-config/pkg/validation"
walkertype "github.com/beckend/go-config/pkg/walker-type"
validator "github.com/go-playground/validator/v10"

envutil "github.com/gookit/goutil/envutil"
jsoniter "github.com/json-iterator/go"

conditional "github.com/mileusna/conditional"
mapstructure "github.com/mitchellh/mapstructure"
)
Expand Down Expand Up @@ -67,18 +68,21 @@ func New(options *NewOptions) (*Config, error) {
_, envKeyUserExists := os.LookupEnv(options.EnvKeyRunEnv)
envRun := environment.GetEnv(conditional.String(envKeyUserExists, options.EnvKeyRunEnv, "RUN_ENV"), "")
var filesToBeMerged []string
var filesToLoad []string

// the order to load is base, env specific, then local, where the next overrides the previous values
filesToLoad = append(filesToLoad, path.Join(options.PathConfigs, "base.toml"))
if envRun != "" {
filesToLoad = append(filesToLoad, path.Join(options.PathConfigs, envRun+".toml"))
}
filesToLoad = append(filesToLoad, path.Join(options.PathConfigs, "local.toml"))
if options.PathConfigs != "" {
var filesToLoad []string

// the order to load is base, env specific, then local, where the next overrides the previous values
filesToLoad = append(filesToLoad, path.Join(options.PathConfigs, "base.toml"))
if envRun != "" {
filesToLoad = append(filesToLoad, path.Join(options.PathConfigs, envRun+".toml"))
}
filesToLoad = append(filesToLoad, path.Join(options.PathConfigs, "local.toml"))

for _, pathFile := range filesToLoad {
if _, err := os.Stat(pathFile); err == nil {
filesToBeMerged = append(filesToBeMerged, pathFile)
for _, pathFile := range filesToLoad {
if _, err := os.Stat(pathFile); err == nil {
filesToBeMerged = append(filesToBeMerged, pathFile)
}
}
}

Expand Down Expand Up @@ -112,27 +116,33 @@ func New(options *NewOptions) (*Config, error) {
}

// convert to a generic map interface to replace env variables
var configMap walkertype.TypeMap
var configMap map[string]interface{}
err = json.Unmarshal(bytesJSONMerged, &configMap)
if err != nil {
return nil, err
}

(&walkertype.Walker{}).WalkMap(configMap, func(options *walkertype.WalkerOnWalkMapOptions) *walkertype.WalkerReturn {
if options.Kind == reflect.String {
options.Document[options.Key] = envutil.ParseEnvValue(options.Value.(string))
configMapped := walkertype.Walk(&walkertype.WalkOptions{
Object: configMap,
OnKind: func(oosvo *walkertype.OnKindOptions) *walkertype.OnKindWalkReturn {
if oosvo.CaseKind == reflect.String {
oosvo.Copy.SetString(envutil.ParseEnvValue(oosvo.Original.String()))

return &walkertype.WalkerReturn{
Handled: true,
return &walkertype.OnKindWalkReturn{
Handled: true,
}
}
}

return &walkertype.WalkerReturn{
Handled: false,
}
return &walkertype.OnKindWalkReturn{
Handled: false,
}
},
})
if err != nil {
return nil, err
}

mapstructure.Decode(configMap, &options.ConfigUnmarshal)
mapstructure.Decode(configMapped, &options.ConfigUnmarshal)

if options.OnConfigBeforeValidation != nil {
err = options.OnConfigBeforeValidation(&OnConfigBeforeValidationOptions{
Expand All @@ -144,19 +154,26 @@ func New(options *NewOptions) (*Config, error) {
}
}

errsValidation := singletons.New().Validation.ValidateStruct(validation.ValidatorUtilsValidateStructOptions{
PrefixError: "Config struct validation error - ",
TheStruct: options.ConfigUnmarshal,
PanicOnError: false,
})
// validator cannot handlle unamed types such as "var result map[string]interface{}", it needs a struct
if !reflection.HasElement([]string{"*", ""}, reflection.GetType(options.ConfigUnmarshal)) {
errsValidation := singletons.New().Validation.ValidateStruct(validation.ValidatorUtilsValidateStructOptions{
PrefixError: "Config struct validation error - ",
TheStruct: options.ConfigUnmarshal,
PanicOnError: false,
})

if errsValidation != nil && len(*errsValidation) > 0 {
err = errors.New("config struct validation failed")
} else {
err = nil
}

if errsValidation != nil && len(*errsValidation) > 0 {
err = errors.New("config struct validation failed")
} else {
err = nil
return &Config{
ErrorsValidation: errsValidation,
}, err
}

return &Config{
ErrorsValidation: errsValidation,
ErrorsValidation: nil,
}, err
}
39 changes: 28 additions & 11 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package config_test

import (
"errors"
errors "errors"
fs "io/fs"
os "os"
path "path"
filepath "path/filepath"
runtime "runtime"
strings "strings"

config "github.com/beckend/go-config"
common "github.com/beckend/go-config/pkg/common"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand All @@ -35,7 +37,7 @@ type TestValidateStructOneFail struct {
RunEnV string `validate:"required"`
}

var _ = Describe("pkg validation", func() {
var _ = Describe("pkg main", func() {
_, pathCurrentFile, _, _ := runtime.Caller(0)
pathDirCurrent, _ := filepath.Split(pathCurrentFile)
pathFixtures := path.Join(pathDirCurrent, "tests/fixtures")
Expand Down Expand Up @@ -186,19 +188,15 @@ var _ = Describe("pkg validation", func() {
keyEnvTarget := "RUN_ENV_CUSTOM"
keyEnvTargetValue := "staging"
err := os.Setenv(keyEnvTarget, keyEnvTargetValue)
if err != nil {
panic(err)
}
common.FailOnError(err)
defer os.Unsetenv(keyEnvTarget)

_, err = config.New(&config.NewOptions{
ConfigUnmarshal: &result,
EnvKeyRunEnv: keyEnvTarget,
PathConfigs: path.Join(pathFixtures, "configs-env"),
})
if err != nil {
panic(err)
}
common.FailOnError(err)

Expect(result.RunEnV).To(Equal(keyEnvTargetValue))
})
Expand All @@ -211,13 +209,32 @@ var _ = Describe("pkg validation", func() {
ConfigUnmarshal: &result,
PathConfigs: path.Join(pathFixtures, "configs-local"),
})
if err != nil {
panic(err)
}
common.FailOnError(err)

Expect(result.RunEnV).To(Equal("local-overwritten"))
})
})

When("configs-custom test1.toml", func() {
It("works", func() {
var result map[string]interface{}
_, err := config.New(&config.NewOptions{
ConfigUnmarshal: &result,
LoadConfigs: func(options *config.LoadConfigsOptions) ([][]byte, error) {
b1, err := options.TOML.FileReaderCallbackToJSON(func() (fs.File, error) {
return os.Open(path.Join(pathFixtures, "configs-custom/test1.toml"))
})
common.FailOnError(err)

return [][]byte{b1}, nil
},
})
common.FailOnError(err)

Expect(result["RunEnv"]).To(Equal("development"))
Expect(result["Shell"]).ToNot(Equal("${SHELL}"))
})
})
})
})
})
28 changes: 28 additions & 0 deletions pkg/file/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
bytes "bytes"
jsonOriginal "encoding/json"
fmt "fmt"
io "io"
os "os"
time "time"

jsonpatch "github.com/evanphx/json-patch"
Expand Down Expand Up @@ -122,3 +124,29 @@ func JSONGenericMapToBytes(input map[string]interface{}) ([]byte, error) {

return returned, nil
}

func JSONFileToBytes(pathFile string) ([]byte, error) {
fileJson, err := os.Open(pathFile)
if err != nil {
return nil, err
}
defer fileJson.Close()

returned, err := io.ReadAll(fileJson)
if err != nil {
return nil, err
}

return returned, nil
}

func JSONFileToMap(pathFile string) (map[string]interface{}, error) {
bytes, err := JSONFileToBytes(pathFile)
if err != nil {
return nil, err
}

var result map[string]interface{}
json.Unmarshal(bytes, &result)
return result, nil
}
28 changes: 28 additions & 0 deletions pkg/reflection/reflection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Package reflection reflect stuff
package reflection

import "reflect"

func GetType(input interface{}) string {
if t := reflect.TypeOf(input); t.Kind() == reflect.Ptr {
return "*" + t.Elem().Name()
} else {
return t.Name()
}
}

func HasElement(input interface{}, search interface{}) bool {
theValue := reflect.ValueOf(input)

if theValue.Kind() == reflect.Slice {
for i := 0; i < theValue.Len(); i++ {
if theValue.Index(i).CanSet() {
if theValue.Index(i).Interface() == search {
return true
}
}
}
}

return false
}
51 changes: 51 additions & 0 deletions pkg/reflection/reflection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package reflection_test

import (
testing "testing"

reflection "github.com/beckend/go-config/pkg/reflection"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestPkg(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "pkg reflection Suite")
}

var _ = Describe("pkg reflection", func() {
Context("GetType", func() {
When("named struct", func() {
It("pointer outputs correct name", func() {
type OTest struct{}
Expect(reflection.GetType(&OTest{})).To(Equal("*OTest"))
})

It("non pointer outputs correct name", func() {
type OTest struct{}
Expect(reflection.GetType(OTest{})).To(Equal("OTest"))
})
})

When("unnamed non struct", func() {
It("pointer outputs correct name", func() {
var input map[string]interface{}
Expect(reflection.GetType(&input)).To(Equal("*"))
})

It("unnamed non struct non pointer outputs correct name", func() {
var input map[string]interface{}
Expect(reflection.GetType(input)).To(Equal(""))
})
})
})

Context("HasElement", func() {
It("works for slices", func() {
collection := []string{"hello", "my", "friend"}
Expect(reflection.HasElement(collection, "friend")).To(Equal(true))
Expect(reflection.HasElement(collection, "apple")).To(Equal(false))
})
})
})
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 4e9d52c

Please sign in to comment.