Skip to content

Commit

Permalink
Init
Browse files Browse the repository at this point in the history
  • Loading branch information
redickowii committed Jul 23, 2022
0 parents commit 4286dd4
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.iml
/.idea
9 changes: 9 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cfg

type GetEnvError struct {
field string
}

func (e GetEnvError) Error() string {
return "cant find env tag for field: " + e.field
}
34 changes: 34 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cfg

import (
"testing"
)

func TestGetEnvError_Error(t *testing.T) {
type fields struct {
field string
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "test Error func",
fields: fields{"string"},
want: "cant find env tag for field: string",
},
}
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
e := GetEnvError{
field: tt.fields.field,
}
if got := e.Error(); got != tt.want {
t.Errorf("Error() = %v, want %v", got, tt.want)
}
},
)
}
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/redickowii/cfg

go 1.18
1 change: 1 addition & 0 deletions log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package cfg
87 changes: 87 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package cfg

import (
"errors"
"os"
"reflect"
"strconv"
)

// tag in the structure by which values in global env will be searched for
const tagName = "env"

// LoadFromEnv Initialize configuration
func LoadFromEnv(config any) error {
val := reflect.ValueOf(config)
err := unmarshalConfig(val)
if err != nil {
return err
}
return nil
}

// unmarshalConfig load data into configuration fields by tag tagName
func unmarshalConfig(v reflect.Value) error {
if v.Kind() == reflect.Pointer {
v = v.Elem()
}
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Type.Kind() == reflect.Struct {
subStruct := v.Field(i).Addr().Elem()
err := unmarshalConfig(subStruct)
if err != nil {
return err
}
continue
}
if tag := field.Tag.Get(tagName); tag == "-" || tag == "" {
continue
}
if err := setValue(v, field); err != nil {
return err
}
}
return nil
}

// setValue assigns the field.Name field a value from the global ENV
func setValue(ps reflect.Value, field reflect.StructField) error {
tag := field.Tag.Get("env")
f := ps.FieldByName(field.Name)
if f.IsValid() {
if f.Kind() == reflect.Pointer {
f.Set(reflect.New(f.Type().Elem()))
f = f.Elem()
}
s, err := setEnvConfig(tag, f.Kind())
if err != nil {
if errors.As(err, &GetEnvError{}) {
return nil
}
return err
}
f.Set(s)
}
return nil
}

// setEnvConfig returns reflect.Value reduced to the field type of the structure
func setEnvConfig(name string, kind reflect.Kind) (reflect.Value, error) {
if value := os.Getenv(name); len(value) > 0 {
switch kind {
case reflect.String:
return reflect.ValueOf(value), nil
case reflect.Int:
i, err := strconv.Atoi(value)
return reflect.ValueOf(i), err
case reflect.Bool:
b, err := strconv.ParseBool(value)
return reflect.ValueOf(b), err
default:
return reflect.Value{}, errors.New("no converter to the right data type: " + kind.String() + " was found ")
}
}
return reflect.Value{}, GetEnvError{field: name}
}
101 changes: 101 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package cfg

import (
"os"
"testing"
)

type Env struct {
Key string
Val string
}

type Config struct {
String string `env:"str"`
Int int `env:"int"`
Bool bool `env:"bool"`
Int64 int64 `env:"int64"`
Config Config2 `env:"-"`
}

type Config2 struct {
Int *int `env:"int2"`
Bool bool `env:"bool2"`
Skip bool
Skip2 bool `env:"-"`
}

func TestLoadFromEnv(t *testing.T) {
type args struct {
config any
}

tests := []struct {
name string
args args
Envs []Env
wantErr bool
}{
{
name: "test correct string env",
args: args{config: &Config{}},
Envs: []Env{{Key: "str", Val: "test"}},
wantErr: false,
},
{
name: "test correct int env",
args: args{config: &Config{}},
Envs: []Env{{Key: "ini", Val: "123"}},
wantErr: false,
},
{
name: "test correct bool env",
args: args{config: &Config{}},
Envs: []Env{{Key: "bool", Val: "true"}},
wantErr: false,
},
{
name: "test wrong int env",
args: args{config: &Config{}},
Envs: []Env{{Key: "int", Val: "text"}},
wantErr: true,
},
{
name: "test wrong bool env",
args: args{config: &Config{}},
Envs: []Env{{Key: "bool", Val: "truue"}},
wantErr: true,
},
{
name: "test wrong type int64 env",
args: args{config: &Config{}},
Envs: []Env{{Key: "int64", Val: "2112"}},
wantErr: true,
},
{
name: "test point type env",
args: args{config: &Config{Config: Config2{}}},
Envs: []Env{{Key: "int2", Val: "123"}},
wantErr: false,
},
{
name: "test error in child struct env",
args: args{config: &Config{Config: Config2{}}},
Envs: []Env{{Key: "int2", Val: "text"}},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
for _, env := range tt.Envs {
defer os.Unsetenv(env.Key)
_ = os.Setenv(env.Key, env.Val)
}
if err := LoadFromEnv(tt.args.config); (err != nil) != tt.wantErr {
t.Errorf("LoadFromEnv() error = %v, wantErr %v", err, tt.wantErr)
}
},
)
}
}

0 comments on commit 4286dd4

Please sign in to comment.