Refactoring and tests for keeping values in interfaces.
This commit is contained in:
parent
1e54436f69
commit
e34e443897
50
README.md
50
README.md
@ -24,14 +24,14 @@ Go modules and dep are supported. Other package managers might or might not work
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
SEC is designed to be easy to use parser, so there is only one requirement - passed data should be a pointer to structure. You cannot do something like:
|
SEC is designed to be easy to use parser. There is only one requirement - passed data should be a pointer to structure. You cannot do something like:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var Data string
|
var Data string
|
||||||
sec.Parse(&Data, nil)
|
sec.Parse(&Data, nil)
|
||||||
```
|
```
|
||||||
|
|
||||||
This will throw errors, as any type you'll pass, except for pointer to structure.
|
This will throw errors, as any type you'll pass, except for pointer to structure. It is fine to use anonymous structures inside passed one as well as use structures and pointers to them.
|
||||||
|
|
||||||
SEC is unable to parse embedded unexported things except structures due to inability to get embedded field's address. Embed only structures, please.
|
SEC is unable to parse embedded unexported things except structures due to inability to get embedded field's address. Embed only structures, please.
|
||||||
|
|
||||||
@ -54,8 +54,54 @@ if err != nil {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can set database URI using ``DATABASE_URI`` environment variable. Same for others variables, so you should define environment variables in uppercase despite on how they're written in struct definition. Taking example above, other fields can be set with ``DATABASE_OPTIONS`` and ``HTTPTIMEOUT`` environment variables.
|
||||||
|
|
||||||
|
### Field tags
|
||||||
|
|
||||||
No field tags supported yet, this in ToDo.
|
No field tags supported yet, this in ToDo.
|
||||||
|
|
||||||
|
### Underlying interface{}
|
||||||
|
|
||||||
|
Due to nature how Go works with variables you can do something like that, if you want to work with interfaces to keep variables:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type config struct {
|
||||||
|
DataToKeep interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeout int
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := &config{}
|
||||||
|
c.DataToKeep = &timeout
|
||||||
|
err := sec.Parse(c, nil)
|
||||||
|
...
|
||||||
|
log.Printf("Timeout is %d\n", (*c.DataToKeep.(*int)))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can't do something like:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type config struct {
|
||||||
|
DataToKeep interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeout int
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := &config{}
|
||||||
|
c.DataToKeep = 0
|
||||||
|
err := sec.Parse(c, nil)
|
||||||
|
...
|
||||||
|
log.Printf("Timeout is %d\n", (*c.DataToKeep.(*int)))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
``c.DataToKeep`` here will be always 0 because SEC will skip this field.
|
||||||
|
|
||||||
|
It is because values behind interface{} aren't addressable in Go. Anyway, it is not recommended way to store variables at all and might break.
|
||||||
|
|
||||||
### Debug
|
### Debug
|
||||||
|
|
||||||
To get additional debug output set ``SEC_DEBUG`` environment variable to ``true``. If invalid boolean value will be passed it'll output error about that.
|
To get additional debug output set ``SEC_DEBUG`` environment variable to ``true``. If invalid boolean value will be passed it'll output error about that.
|
||||||
|
@ -32,7 +32,7 @@ func composeTree(value reflect.Value, prefix string) {
|
|||||||
// In 99% of cases we will get uninitialized things we should
|
// In 99% of cases we will get uninitialized things we should
|
||||||
// initialize.
|
// initialize.
|
||||||
switch fieldToProcess.Kind() {
|
switch fieldToProcess.Kind() {
|
||||||
case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan:
|
case reflect.Ptr, reflect.Map, reflect.Slice:
|
||||||
if fieldToProcess.IsNil() {
|
if fieldToProcess.IsNil() {
|
||||||
printDebug("Field '%s' is nil, initializing new one", fieldToProcessType.Name)
|
printDebug("Field '%s' is nil, initializing new one", fieldToProcessType.Name)
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ func composeTree(value reflect.Value, prefix string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printDebug("Field: '%s', type: %s, (anonymous or embedded: %t)", fieldToProcessType.Name, fieldToProcess.Type().Kind().String(), fieldToProcessType.Anonymous)
|
printDebug("Field: '%s', type: %s (anonymous or embedded: %t)", fieldToProcessType.Name, fieldToProcess.Type().Kind().String(), fieldToProcessType.Anonymous)
|
||||||
|
|
||||||
// Dealing with embedded things.
|
// Dealing with embedded things.
|
||||||
if fieldToProcessType.Anonymous {
|
if fieldToProcessType.Anonymous {
|
||||||
@ -61,7 +61,7 @@ func composeTree(value reflect.Value, prefix string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fieldToProcess.Kind() != reflect.Struct && !fieldToProcess.CanSet() {
|
if fieldToProcess.Kind() != reflect.Struct && !fieldToProcess.CanSet() {
|
||||||
printDebug("Field '%s' can't be set, skipping", fieldToProcessType.Name)
|
printDebug("Field '%s' of type '%s' can't be set, skipping", fieldToProcessType.Name, fieldToProcess.Type().Kind().String())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
149
fill_value.go
Normal file
149
fill_value.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package sec
|
||||||
|
|
||||||
|
import (
|
||||||
|
// stdlib
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fillValue(element *field, data string) error {
|
||||||
|
switch element.Kind {
|
||||||
|
case reflect.String:
|
||||||
|
element.Pointer.SetString(data)
|
||||||
|
case reflect.Bool:
|
||||||
|
val, err := strconv.ParseBool(data)
|
||||||
|
if err != nil {
|
||||||
|
printDebug("Error occurred while parsing boolean: %s", err.Error())
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return errNotBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.Pointer.SetBool(val)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
// Bitsize 64 here specified for a reason - actual ints
|
||||||
|
// ranges checking goes below and we should expect it to
|
||||||
|
// be 0 in case of configuration.
|
||||||
|
val, err := strconv.ParseInt(data, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
printDebug("Error occurred while parsing int: %s", err.Error())
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return errNotInt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch element.Kind {
|
||||||
|
case reflect.Int8:
|
||||||
|
// int8 is an integer in [-128...127] range.
|
||||||
|
if val >= -128 && val <= 127 {
|
||||||
|
element.Pointer.SetInt(val)
|
||||||
|
} else {
|
||||||
|
printDebug("Data in environment variable '%s' isn't int8", element.EnvVar)
|
||||||
|
element.Pointer.SetInt(0)
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return errNotInt8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int16:
|
||||||
|
// int16 is an integer in [-32768...32767] range.
|
||||||
|
if val >= -32768 && val <= 32767 {
|
||||||
|
element.Pointer.SetInt(val)
|
||||||
|
} else {
|
||||||
|
printDebug("Data in environment variable '%s' isn't int16", element.EnvVar)
|
||||||
|
element.Pointer.SetInt(0)
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return errNotInt16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int32:
|
||||||
|
// int32 is an integer in [-2147483648...2147483647] range.
|
||||||
|
if val >= -2147483648 && val <= 2147483647 {
|
||||||
|
element.Pointer.SetInt(val)
|
||||||
|
} else {
|
||||||
|
printDebug("Data in environment variable '%s' isn't int32", element.EnvVar)
|
||||||
|
element.Pointer.SetInt(0)
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return errNotInt32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int64, reflect.Int:
|
||||||
|
// int64 is an integer in [-9223372036854775808...9223372036854775807] range.
|
||||||
|
// This is currently maximum allowed int values, so we'll
|
||||||
|
// just set it.
|
||||||
|
element.Pointer.SetInt(val)
|
||||||
|
}
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
val, err := strconv.ParseUint(data, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
printDebug("Error occurred while parsing unsigned integer: %s", err.Error())
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return errNotUint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch element.Kind {
|
||||||
|
case reflect.Uint8:
|
||||||
|
// uint8 is an integer in [0...255] range.
|
||||||
|
if val <= 255 {
|
||||||
|
element.Pointer.SetUint(val)
|
||||||
|
} else {
|
||||||
|
printDebug("Data in environment variable '%s' isn't uint8", element.EnvVar)
|
||||||
|
element.Pointer.SetUint(0)
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return errNotUint8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint16:
|
||||||
|
// uint16 is an integer in [0...65535] range.
|
||||||
|
if val <= 65535 {
|
||||||
|
element.Pointer.SetUint(val)
|
||||||
|
} else {
|
||||||
|
printDebug("Data in environment variable '%s' isn't uint16", element.EnvVar)
|
||||||
|
element.Pointer.SetUint(0)
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return errNotUint16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint32:
|
||||||
|
// uint32 is an integer in [0...4294967295] range.
|
||||||
|
if val <= 4294967295 {
|
||||||
|
element.Pointer.SetUint(val)
|
||||||
|
} else {
|
||||||
|
printDebug("Data in environment variable '%s' isn't uint32", element.EnvVar)
|
||||||
|
element.Pointer.SetUint(0)
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return errNotUint32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint64:
|
||||||
|
// uint64 is an integer in [0...18446744073709551615] range.
|
||||||
|
// This is currently maximum allowed int values, so we'll
|
||||||
|
// just set it.
|
||||||
|
element.Pointer.SetUint(val)
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
val, err := strconv.ParseFloat(data, 64)
|
||||||
|
if err != nil {
|
||||||
|
printDebug("Error occurred while parsing float: %s", err.Error())
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return errNotFloat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.Pointer.SetFloat(val)
|
||||||
|
case reflect.Interface:
|
||||||
|
// We should not attempt to work with data in interface{}
|
||||||
|
// unless it is a pointer to value.
|
||||||
|
if element.Pointer.Elem().Kind() != reflect.Ptr {
|
||||||
|
printDebug("Element for environment variable '%s' isn't a pointer and put into interface{}. Nothing will be done with this element.", element.EnvVar)
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return errors.New("element for environment variable '" + element.EnvVar + "' isn't a pointer and put into interface")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should get actual value. Two Elem()'s for that.
|
||||||
|
// It goes interface{} -> ptr -> real element.
|
||||||
|
element.Pointer = element.Pointer.Elem().Elem()
|
||||||
|
element.Kind = element.Pointer.Kind()
|
||||||
|
return fillValue(element, data)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
123
parse_env.go
123
parse_env.go
@ -4,8 +4,6 @@ import (
|
|||||||
// stdlib
|
// stdlib
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -42,126 +40,9 @@ func parseEnv() error {
|
|||||||
printDebug("Value for '%s' will be: %s", element.EnvVar, data)
|
printDebug("Value for '%s' will be: %s", element.EnvVar, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch element.Kind {
|
err := fillValue(element, data)
|
||||||
case reflect.String:
|
|
||||||
element.Pointer.SetString(data)
|
|
||||||
case reflect.Bool:
|
|
||||||
val, err := strconv.ParseBool(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printDebug("Error occurred while parsing boolean: %s", err.Error())
|
return err
|
||||||
if options.ErrorsAreCritical {
|
|
||||||
return errNotBool
|
|
||||||
}
|
|
||||||
}
|
|
||||||
element.Pointer.SetBool(val)
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
// Bitsize 64 here specified for a reason - actual ints
|
|
||||||
// ranges checking goes below and we should expect it to
|
|
||||||
// be 0 in case of configuration.
|
|
||||||
val, err := strconv.ParseInt(data, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
printDebug("Error occurred while parsing int: %s", err.Error())
|
|
||||||
if options.ErrorsAreCritical {
|
|
||||||
return errNotInt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch element.Kind {
|
|
||||||
case reflect.Int8:
|
|
||||||
// int8 is an integer in [-128...127] range.
|
|
||||||
if val >= -128 && val <= 127 {
|
|
||||||
element.Pointer.SetInt(val)
|
|
||||||
} else {
|
|
||||||
printDebug("Data in environment variable '%s' isn't int8", element.EnvVar)
|
|
||||||
element.Pointer.SetInt(0)
|
|
||||||
if options.ErrorsAreCritical {
|
|
||||||
return errNotInt8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Int16:
|
|
||||||
// int16 is an integer in [-32768...32767] range.
|
|
||||||
if val >= -32768 && val <= 32767 {
|
|
||||||
element.Pointer.SetInt(val)
|
|
||||||
} else {
|
|
||||||
printDebug("Data in environment variable '%s' isn't int16", element.EnvVar)
|
|
||||||
element.Pointer.SetInt(0)
|
|
||||||
if options.ErrorsAreCritical {
|
|
||||||
return errNotInt16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Int32:
|
|
||||||
// int32 is an integer in [-2147483648...2147483647] range.
|
|
||||||
if val >= -2147483648 && val <= 2147483647 {
|
|
||||||
element.Pointer.SetInt(val)
|
|
||||||
} else {
|
|
||||||
printDebug("Data in environment variable '%s' isn't int32", element.EnvVar)
|
|
||||||
element.Pointer.SetInt(0)
|
|
||||||
if options.ErrorsAreCritical {
|
|
||||||
return errNotInt32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Int64, reflect.Int:
|
|
||||||
// int64 is an integer in [-9223372036854775808...9223372036854775807] range.
|
|
||||||
// This is currently maximum allowed int values, so we'll
|
|
||||||
// just set it.
|
|
||||||
element.Pointer.SetInt(val)
|
|
||||||
}
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
val, err := strconv.ParseUint(data, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
printDebug("Error occurred while parsing unsigned integer: %s", err.Error())
|
|
||||||
if options.ErrorsAreCritical {
|
|
||||||
return errNotUint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch element.Kind {
|
|
||||||
case reflect.Uint8:
|
|
||||||
// uint8 is an integer in [0...255] range.
|
|
||||||
if val <= 255 {
|
|
||||||
element.Pointer.SetUint(val)
|
|
||||||
} else {
|
|
||||||
printDebug("Data in environment variable '%s' isn't uint8", element.EnvVar)
|
|
||||||
element.Pointer.SetUint(0)
|
|
||||||
if options.ErrorsAreCritical {
|
|
||||||
return errNotUint8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Uint16:
|
|
||||||
// uint16 is an integer in [0...65535] range.
|
|
||||||
if val <= 65535 {
|
|
||||||
element.Pointer.SetUint(val)
|
|
||||||
} else {
|
|
||||||
printDebug("Data in environment variable '%s' isn't uint16", element.EnvVar)
|
|
||||||
element.Pointer.SetUint(0)
|
|
||||||
if options.ErrorsAreCritical {
|
|
||||||
return errNotUint16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Uint32:
|
|
||||||
// uint32 is an integer in [0...4294967295] range.
|
|
||||||
if val <= 4294967295 {
|
|
||||||
element.Pointer.SetUint(val)
|
|
||||||
} else {
|
|
||||||
printDebug("Data in environment variable '%s' isn't uint32", element.EnvVar)
|
|
||||||
element.Pointer.SetUint(0)
|
|
||||||
if options.ErrorsAreCritical {
|
|
||||||
return errNotUint32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Uint64:
|
|
||||||
// uint64 is an integer in [0...18446744073709551615] range.
|
|
||||||
// This is currently maximum allowed int values, so we'll
|
|
||||||
// just set it.
|
|
||||||
element.Pointer.SetUint(val)
|
|
||||||
}
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
val, err := strconv.ParseFloat(data, 64)
|
|
||||||
if err != nil {
|
|
||||||
printDebug("Error occurred while parsing float: %s", err.Error())
|
|
||||||
if options.ErrorsAreCritical {
|
|
||||||
return errNotFloat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
element.Pointer.SetFloat(val)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -737,4 +737,32 @@ func TestParseFloat64(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Float
|
func TestParseStructWithInterfaceFields(t *testing.T) {
|
||||||
|
type testStruct struct {
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv(debugFlagEnvName, "true")
|
||||||
|
|
||||||
|
testCase := &testStruct{}
|
||||||
|
testCase.Data = 0
|
||||||
|
|
||||||
|
os.Setenv("DATA", "64")
|
||||||
|
|
||||||
|
err := Parse(testCase, nil)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotEqual(t, 64, testCase.Data)
|
||||||
|
|
||||||
|
testCase1 := &testStruct{}
|
||||||
|
d := 0
|
||||||
|
shouldBeRaw := 64
|
||||||
|
shouldBe := &shouldBeRaw
|
||||||
|
testCase1.Data = &d
|
||||||
|
|
||||||
|
err1 := Parse(testCase1, nil)
|
||||||
|
require.Nil(t, err1)
|
||||||
|
require.Equal(t, (*shouldBe), (*testCase1.Data.(*int)))
|
||||||
|
|
||||||
|
os.Unsetenv("DATA")
|
||||||
|
os.Unsetenv(debugFlagEnvName)
|
||||||
|
}
|
||||||
|
@ -129,6 +129,7 @@ func TestParseValidData(t *testing.T) {
|
|||||||
err := Parse(ts, nil)
|
err := Parse(ts, nil)
|
||||||
t.Logf("Parsed data: %+v\n", ts)
|
t.Logf("Parsed data: %+v\n", ts)
|
||||||
t.Logf("Parsed nested data: %+v\n", ts.TestNest)
|
t.Logf("Parsed nested data: %+v\n", ts.TestNest)
|
||||||
|
t.Logf("Parsed nested data as pointer: %+v\n", ts.TestNestPointer)
|
||||||
t.Logf("Parsed nested interface data: %+v\n", ts.TestNestInterface)
|
t.Logf("Parsed nested interface data: %+v\n", ts.TestNestInterface)
|
||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
Loading…
Reference in New Issue
Block a user