-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 4286dd4
Showing
7 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.iml | ||
/.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}, | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/redickowii/cfg | ||
|
||
go 1.18 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package cfg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}, | ||
) | ||
} | ||
} |