Skip to content

Commit a020e7d

Browse files
committed
Initial commit.
0 parents  commit a020e7d

7 files changed

+339
-0
lines changed

claim_map.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package config
2+
3+
import (
4+
"reflect"
5+
)
6+
7+
type ClaimMap map[string]interface{}
8+
9+
func GetClaimMap[T any](claims T, claimMap ClaimMap) ClaimMap {
10+
if claimMap == nil {
11+
claimMap = ClaimMap{}
12+
}
13+
14+
t := reflect.TypeOf(claims)
15+
v := reflect.ValueOf(claims)
16+
length := t.NumField()
17+
for index := 0; index < length; index++ {
18+
typeField := t.Field(index)
19+
valueField := v.Field(index)
20+
tag := typeField.Tag
21+
if isNonZero(valueField) {
22+
value := valueField.Interface()
23+
claim := tag.Get("claim")
24+
if claim != "" {
25+
claimMap[claim] = value
26+
}
27+
}
28+
}
29+
30+
return claimMap
31+
}
32+
33+
func GetConfigMap[T any](claims T) T {
34+
t := reflect.TypeOf(claims)
35+
v := reflect.ValueOf(&claims).Elem()
36+
length := t.NumField()
37+
for index := 0; index < length; index++ {
38+
typeField := t.Field(index)
39+
valueField := v.Field(index)
40+
tag := typeField.Tag
41+
if isNonZero(valueField) {
42+
value := valueField.Interface()
43+
claim := tag.Get("config")
44+
if claim == "" {
45+
continue
46+
}
47+
switch val := value.(type) {
48+
case string:
49+
valueField.SetString(GetEnvString(claim, val))
50+
case int:
51+
valueField.SetInt(int64(GetEnvInt(claim, &val)))
52+
}
53+
}
54+
}
55+
return claims
56+
}
57+
58+
func isNonZero(value reflect.Value) bool {
59+
if value.IsZero() {
60+
return false
61+
}
62+
63+
switch value.Kind() {
64+
case reflect.Chan:
65+
fallthrough
66+
case reflect.Func:
67+
fallthrough
68+
case reflect.Interface:
69+
fallthrough
70+
case reflect.Map:
71+
fallthrough
72+
case reflect.Pointer:
73+
fallthrough
74+
case reflect.Slice:
75+
return !value.IsNil()
76+
default:
77+
return true
78+
}
79+
}

claim_map_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package config
2+
3+
import "testing"
4+
5+
func TestGetClaimsTags(t *testing.T) {
6+
type Claims struct {
7+
TestField string `claim:"test_field"`
8+
}
9+
10+
want := "Test Field Value"
11+
claims := GetClaimMap(Claims{
12+
TestField: want,
13+
}, nil)
14+
15+
if len(claims) == 0 {
16+
t.Fatalf("Claims map is empty")
17+
}
18+
result := claims["test_field"]
19+
if result != want {
20+
t.Fatalf("Received incorrect value for key. Received: %v, Expected: %v", result, want)
21+
}
22+
}

config_file.go

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package config
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"io"
7+
"io/fs"
8+
"log"
9+
"os"
10+
"strconv"
11+
12+
"github.com/joho/godotenv"
13+
"gopkg.in/yaml.v3"
14+
)
15+
16+
// Read a ConfigFile by name from an [fs.FS].
17+
func ReadConfigFile[T any](fsys fs.FS, name string) (T, error) {
18+
buffer, err := getBuffer(fsys, name)
19+
if err != nil {
20+
var t T
21+
return t, err
22+
}
23+
24+
conf, err := getConfigFile[T](buffer)
25+
if err != nil {
26+
return conf, err
27+
}
28+
29+
conf = GetConfigMap(conf)
30+
31+
return conf, nil
32+
}
33+
34+
func getBuffer(fsys fs.FS, name string) (*bytes.Buffer, error) {
35+
b, err := fs.ReadFile(fsys, name)
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
buf := bytes.NewBuffer(b)
41+
if buf == nil {
42+
return nil, ErrNilReader
43+
}
44+
45+
return buf, nil
46+
}
47+
48+
var ErrNilReader = errors.New("nil Reader")
49+
50+
func getConfigFile[T any](r io.Reader) (T, error) {
51+
var configFile T
52+
53+
decoder := yaml.NewDecoder(r)
54+
err := decoder.Decode(&configFile)
55+
56+
return configFile, err
57+
}
58+
59+
func LoadEnv(filenames ...string) error {
60+
err := godotenv.Load(filenames...)
61+
if err != nil {
62+
log.Fatal("Error loading .env file")
63+
}
64+
return nil
65+
}
66+
67+
func GetEnvString(name string, defaultValues ...string) string {
68+
value := os.Getenv(name)
69+
if value != "" {
70+
return value
71+
}
72+
73+
for _, newValue := range defaultValues {
74+
value = newValue
75+
if value != "" {
76+
break
77+
}
78+
}
79+
80+
return value
81+
}
82+
83+
func GetEnvInt[U ~int](name string, defaultValues ...*U) U {
84+
if i, ok := getOsInt(name); ok {
85+
return U(i)
86+
}
87+
88+
for _, value := range defaultValues {
89+
if value != nil {
90+
return U(*value)
91+
}
92+
}
93+
94+
return 0
95+
}
96+
97+
func GetEnvInt64[U ~int64](name string, defaultValues ...*U) U {
98+
if i, ok := getOsInt(name); ok {
99+
return U(i)
100+
}
101+
102+
for _, value := range defaultValues {
103+
if value != nil {
104+
return U(*value)
105+
}
106+
}
107+
108+
return 0
109+
}
110+
111+
func Nullable[U any](value U) *U {
112+
return &value
113+
}
114+
115+
func getOsInt(name string) (int, bool) {
116+
var i int
117+
118+
s := os.Getenv(name)
119+
if s == "" {
120+
return 0, false
121+
}
122+
123+
i, err := strconv.Atoi(s)
124+
if err != nil {
125+
return 0, false
126+
}
127+
128+
return i, true
129+
}

config_file_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package config
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
"testing/fstest"
7+
)
8+
9+
type configFile struct {
10+
DbString string `yaml:"dbString"`
11+
}
12+
13+
func TestConfigFile(t *testing.T) {
14+
var data = `
15+
dbString: mongodb://mongodb:27017
16+
`
17+
18+
var fileName = "file.yml"
19+
20+
var fileSystem = fstest.MapFS{
21+
fileName: {Data: []byte(data)},
22+
}
23+
24+
t.Run("should return error on nil data", func(t *testing.T) {
25+
t.Skip()
26+
_, err := getConfigFile[configFile](nil)
27+
if err == nil {
28+
t.Errorf("err should not be nil. err: %v", err)
29+
}
30+
})
31+
32+
t.Run("should read from a reader", func(t *testing.T) {
33+
r := bytes.NewBufferString(data)
34+
_, err := getConfigFile[configFile](r)
35+
if err != nil {
36+
t.Errorf("%v", err)
37+
}
38+
})
39+
40+
t.Run("should read data", func(t *testing.T) {
41+
r := bytes.NewBufferString(data)
42+
c, err := getConfigFile[configFile](r)
43+
if err != nil {
44+
t.Errorf("%v", err)
45+
}
46+
if c.DbString == "" {
47+
t.Errorf("DB_STRING is empty")
48+
}
49+
})
50+
51+
t.Run("should read from a file", func(t *testing.T) {
52+
_, err := ReadConfigFile[configFile](fileSystem, fileName)
53+
if err != nil {
54+
t.Errorf("%v", err)
55+
}
56+
})
57+
}

config_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package config
2+
3+
import "testing"
4+
5+
func TestGetString(t *testing.T) {
6+
table := []struct {
7+
Name string
8+
EnvValue string
9+
DefaultValues []string
10+
Result string
11+
}{{
12+
Name: "Name0",
13+
EnvValue: "Env",
14+
DefaultValues: []string{"A", "B"},
15+
Result: "Env",
16+
}, {
17+
Name: "Name1",
18+
DefaultValues: []string{"A", "B"},
19+
Result: "A",
20+
}, {
21+
Name: "Name2",
22+
DefaultValues: []string{"", "B"},
23+
Result: "B",
24+
}, {
25+
Name: "Name3",
26+
DefaultValues: []string{},
27+
Result: "",
28+
}}
29+
for _, item := range table {
30+
if item.EnvValue != "" {
31+
t.Setenv(item.Name, item.EnvValue)
32+
}
33+
result := GetEnvString(item.Name, item.DefaultValues...)
34+
if result != item.Result {
35+
t.Errorf("Received: %v, Expected %v", result, item.Result)
36+
}
37+
}
38+
}

go.mod

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/cardboardrobots/config
2+
3+
go 1.23.1
4+
5+
require (
6+
github.com/joho/godotenv v1.5.1
7+
gopkg.in/yaml.v3 v3.0.1
8+
)

go.sum

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
2+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
3+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
4+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
5+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
6+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)