Initial commit.
This commit is contained in:
commit
3476411569
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"go.testFlags": [
|
||||||
|
"-test.v",
|
||||||
|
"-cover"
|
||||||
|
]
|
||||||
|
}
|
42
README.md
Normal file
42
README.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# SEC. Take a break and configure your application with ease.
|
||||||
|
|
||||||
|
**SEC** stands for "Simple Environment Configuration" and provides really simple way to configure your application.
|
||||||
|
|
||||||
|
After googling around Go applications configuration management packages that able to take parameters from environment configuration I came to a conclusion that there is none packages that able to do everything I want and yet have readable and testable source code.
|
||||||
|
|
||||||
|
Key intentions to create SEC:
|
||||||
|
|
||||||
|
* Parse configuration into structure with support of infinitely nested structures.
|
||||||
|
* Works properly with interfaces.
|
||||||
|
* No goto's.
|
||||||
|
* 100% code coverage.
|
||||||
|
* No external dependencies (only testify for tests).
|
||||||
|
* Readable code and proper variables naming.
|
||||||
|
* Debug mode
|
||||||
|
|
||||||
|
This list might be updated if new key intention arrives :).
|
||||||
|
|
||||||
|
SEC was written under impression from https://github.com/vrischmann/envconfig/.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Go modules and dep are supported. Other package managers might or might not work, MRs are welcome!
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
SEC is designed to be easy to use parser, so there is only one requirement - passed data should be a structure. You cannot do something like:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var Data string
|
||||||
|
sec.Parse(&Data, nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
This will throw errors, as any type you'll pass, except for pointer to structure.
|
||||||
|
|
||||||
|
SEC is unable to parse embedded unexported things except structures due to inability to get embedded field's address. Embed only structures, please.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
Debug output uses standart log package. This may change in future.
|
91
compose_tree.go
Normal file
91
compose_tree.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package sec
|
||||||
|
|
||||||
|
import (
|
||||||
|
// stdlib
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Composes full tree for every structure member.
|
||||||
|
func composeTree(value reflect.Value, prefix string) {
|
||||||
|
typeOf := value.Type()
|
||||||
|
|
||||||
|
// Compose prefix for everything below current field.
|
||||||
|
var curPrefix string
|
||||||
|
if prefix != "" {
|
||||||
|
curPrefix = prefix
|
||||||
|
if !strings.HasSuffix(curPrefix, "_") {
|
||||||
|
curPrefix += "_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
fieldToProcess := value.Field(i)
|
||||||
|
fieldToProcessType := typeOf.Field(i)
|
||||||
|
|
||||||
|
// If currently processed field - interface, then we should
|
||||||
|
// get underlying value.
|
||||||
|
//if fieldToProcess.Kind() == reflect.Interface {
|
||||||
|
// fieldToProcess = fieldToProcess.Elem()
|
||||||
|
//}
|
||||||
|
|
||||||
|
// In 99% of cases we will get uninitialized things we should
|
||||||
|
// initialize.
|
||||||
|
switch fieldToProcess.Kind() {
|
||||||
|
case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan:
|
||||||
|
if fieldToProcess.IsNil() {
|
||||||
|
printDebug("Field '%s' is nil, initializing new one", fieldToProcessType.Name)
|
||||||
|
|
||||||
|
// We should use only exported fields as unexported aren't
|
||||||
|
// settable using 'reflect' package. Can be possibly solved
|
||||||
|
// using unsafe pointers?
|
||||||
|
if fieldToProcess.CanSet() {
|
||||||
|
fieldToProcess.Set(reflect.New(fieldToProcess.Type().Elem()))
|
||||||
|
} else {
|
||||||
|
printDebug("Field '%s' is unexported and will be ignored", fieldToProcessType.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fieldToProcess = fieldToProcess.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printDebug("Field: '%s', type: %s, (anonymous or embedded: %t)", fieldToProcessType.Name, fieldToProcess.Type().Kind().String(), fieldToProcessType.Anonymous)
|
||||||
|
|
||||||
|
// Dealing with embedded things.
|
||||||
|
if fieldToProcessType.Anonymous {
|
||||||
|
// We should not allow anything other than struct.
|
||||||
|
if fieldToProcess.Kind() != reflect.Struct {
|
||||||
|
printDebug("Field is embedded, but not a struct (%s), which cannot be used", fieldToProcess.Kind().String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldToProcess.Kind() != reflect.Struct && !fieldToProcess.CanSet() {
|
||||||
|
printDebug("Field '%s' can't be set, skipping", fieldToProcessType.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
printDebug("All underlying elements will have prefix '%s'", curPrefix)
|
||||||
|
|
||||||
|
// Hello, I'm recursion and I'm here to make you happy.
|
||||||
|
// I'll be launched only for structures to get their fields.
|
||||||
|
if fieldToProcess.Kind() == reflect.Struct {
|
||||||
|
newElementPrefix := curPrefix
|
||||||
|
if !fieldToProcessType.Anonymous {
|
||||||
|
newElementPrefix = strings.ToUpper(newElementPrefix + typeOf.Field(i).Name)
|
||||||
|
}
|
||||||
|
composeTree(fieldToProcess, newElementPrefix)
|
||||||
|
} else {
|
||||||
|
f := &field{
|
||||||
|
Name: typeOf.Field(i).Name,
|
||||||
|
EnvVar: curPrefix + strings.ToUpper(typeOf.Field(i).Name),
|
||||||
|
Pointer: fieldToProcess,
|
||||||
|
Kind: fieldToProcess.Kind(),
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTree = append(parsedTree, f)
|
||||||
|
|
||||||
|
printDebug("Field data constructed: %+v", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
field.go
Normal file
22
field.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package sec
|
||||||
|
|
||||||
|
import (
|
||||||
|
// stdlib
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This structure represents every parsable field that was found while
|
||||||
|
// reading passed structure.
|
||||||
|
type field struct {
|
||||||
|
// Name is a field name. Mostly for debugging purpose.
|
||||||
|
Name string
|
||||||
|
// Pointer is a pointer to field wrapped in reflect.Value.
|
||||||
|
Pointer reflect.Value
|
||||||
|
// EnvVar is a name of environment variable we will try to read.
|
||||||
|
EnvVar string
|
||||||
|
// Kind is a reflect.Kind value.
|
||||||
|
Kind reflect.Kind
|
||||||
|
|
||||||
|
// Next variables are tag-related.
|
||||||
|
optional bool
|
||||||
|
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module gitlab.com/pztrn/sec
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.4.0
|
11
go.sum
Normal file
11
go.sum
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
18
options.go
Normal file
18
options.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package sec
|
||||||
|
|
||||||
|
// Options represents configuration for SEC. Note that this is parser
|
||||||
|
// configuration, per-field configuration should be defined in tags.
|
||||||
|
type Options struct {
|
||||||
|
// ErrorsAreCritical indicates that on every parsing error we should
|
||||||
|
// stop processing things. By default we will proceed even if errors
|
||||||
|
// will occur except critical errors like passing invalid value to
|
||||||
|
// SEC_DEBUG environment variable and passing not a pointer to
|
||||||
|
// structure to Parse() function.
|
||||||
|
ErrorsAreCritical bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultOptions = &Options{
|
||||||
|
ErrorsAreCritical: false,
|
||||||
|
}
|
||||||
|
)
|
169
parse_env.go
Normal file
169
parse_env.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package sec
|
||||||
|
|
||||||
|
import (
|
||||||
|
// stdlib
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNotBool = errors.New("environment variable doesn't contain boolean")
|
||||||
|
|
||||||
|
errNotFloat = errors.New("environment variable doesn't contain floating point number")
|
||||||
|
errNotFloat32 = errors.New("environment variable doesn't contain float32")
|
||||||
|
errNotFloat64 = errors.New("environment variable doesn't contain float64")
|
||||||
|
|
||||||
|
errNotInt = errors.New("environment variable doesn't contain integer")
|
||||||
|
errNotInt8 = errors.New("environment variable doesn't contain int8")
|
||||||
|
errNotInt16 = errors.New("environment variable doesn't contain int16")
|
||||||
|
errNotInt32 = errors.New("environment variable doesn't contain int32")
|
||||||
|
errNotInt64 = errors.New("environment variable doesn't contain int64")
|
||||||
|
|
||||||
|
errNotUint = errors.New("environment variable doesn't contain unsigned integer")
|
||||||
|
errNotUint8 = errors.New("environment variable doesn't contain uint8")
|
||||||
|
errNotUint16 = errors.New("environment variable doesn't contain uint16")
|
||||||
|
errNotUint32 = errors.New("environment variable doesn't contain uint32")
|
||||||
|
errNotUint64 = errors.New("environment variable doesn't contain uint64")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parses environment for data.
|
||||||
|
func parseEnv() error {
|
||||||
|
printDebug("Starting parsing data into tree from environment variables...")
|
||||||
|
|
||||||
|
for _, element := range parsedTree {
|
||||||
|
printDebug("Processing element '%s'", element.EnvVar)
|
||||||
|
data, found := os.LookupEnv(element.EnvVar)
|
||||||
|
if !found {
|
||||||
|
printDebug("Value for '%s' environment variable wasn't found", element.EnvVar)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
printDebug("Value for '%s' will be: %s", element.EnvVar, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
740
parse_env_test.go
Normal file
740
parse_env_test.go
Normal file
@ -0,0 +1,740 @@
|
|||||||
|
package sec
|
||||||
|
|
||||||
|
import (
|
||||||
|
// stdlib
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
// other
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseString(t *testing.T) {
|
||||||
|
type testStruct struct {
|
||||||
|
StringData string
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("TESTSTRUCT_STRINGDATA", "test")
|
||||||
|
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseBoolean(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
TestData string
|
||||||
|
ValidData bool
|
||||||
|
}{
|
||||||
|
{"1", true},
|
||||||
|
{"t", true},
|
||||||
|
{"T", true},
|
||||||
|
{"TRUE", true},
|
||||||
|
{"true", true},
|
||||||
|
{"True", true},
|
||||||
|
{"0", false},
|
||||||
|
{"f", false},
|
||||||
|
{"F", false},
|
||||||
|
{"FALSE", false},
|
||||||
|
{"false", false},
|
||||||
|
{"False", false},
|
||||||
|
{"not a boolean", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
BoolData bool
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Logf("Testing: %+v", testCase)
|
||||||
|
os.Setenv("TESTSTRUCT_BOOLDATA", testCase.TestData)
|
||||||
|
|
||||||
|
// If ErrorsAreCritical == false, then we should check only
|
||||||
|
// equality of parsed data and valid data.
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testCase.ValidData, s.BoolData)
|
||||||
|
|
||||||
|
// If errors are critical - we should check if test data is within
|
||||||
|
// int8 range and check for error if it isn't.
|
||||||
|
s1 := &testStruct{}
|
||||||
|
err1 := Parse(s1, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
var checkNotBoolError bool
|
||||||
|
_, err2 := strconv.ParseBool(testCase.TestData)
|
||||||
|
if err2 != nil {
|
||||||
|
checkNotBoolError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkNotBoolError {
|
||||||
|
if err1 == nil {
|
||||||
|
t.Log("No error returned!")
|
||||||
|
}
|
||||||
|
require.NotNil(t, err1)
|
||||||
|
require.Equal(t, errNotBool, err1)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("TESTSTRUCT_BOOLDATA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInt8(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
TestData string
|
||||||
|
ValidData int8
|
||||||
|
}{
|
||||||
|
{"0", 0},
|
||||||
|
{"1", 1},
|
||||||
|
{"2", 2},
|
||||||
|
{"-128", -128},
|
||||||
|
{"127", 127},
|
||||||
|
{"-129", 0},
|
||||||
|
{"128", 0},
|
||||||
|
{"not an integer", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
IntData int8
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Logf("Testing: %+v", testCase)
|
||||||
|
os.Setenv("TESTSTRUCT_INTDATA", testCase.TestData)
|
||||||
|
|
||||||
|
// If ErrorsAreCritical == false, then we should check only
|
||||||
|
// equality of parsed data and valid data.
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testCase.ValidData, s.IntData)
|
||||||
|
|
||||||
|
// If errors are critical - we should check if test data is within
|
||||||
|
// int8 range and check for error if it isn't.
|
||||||
|
s1 := &testStruct{}
|
||||||
|
err1 := Parse(s1, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
var checkNotIntError bool
|
||||||
|
var checkRangeError bool
|
||||||
|
passedData, err2 := strconv.ParseInt(testCase.TestData, 10, 64)
|
||||||
|
if err2 != nil {
|
||||||
|
checkNotIntError = true
|
||||||
|
}
|
||||||
|
if passedData != int64(testCase.ValidData) {
|
||||||
|
checkRangeError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkNotIntError || checkRangeError {
|
||||||
|
if err1 == nil {
|
||||||
|
t.Log("No error returned!")
|
||||||
|
}
|
||||||
|
require.NotNil(t, err1)
|
||||||
|
|
||||||
|
if checkNotIntError {
|
||||||
|
require.Equal(t, errNotInt, err1)
|
||||||
|
}
|
||||||
|
if checkRangeError {
|
||||||
|
require.Equal(t, errNotInt8, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("TESTSTRUCT_INTDATA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInt16(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
TestData string
|
||||||
|
ValidData int16
|
||||||
|
}{
|
||||||
|
{"0", 0},
|
||||||
|
{"1", 1},
|
||||||
|
{"2", 2},
|
||||||
|
{"-32768", -32768},
|
||||||
|
{"32767", 32767},
|
||||||
|
{"-32770", 0},
|
||||||
|
{"32770", 0},
|
||||||
|
{"not an integer", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
IntData int16
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Logf("Testing: %+v", testCase)
|
||||||
|
os.Setenv("TESTSTRUCT_INTDATA", testCase.TestData)
|
||||||
|
|
||||||
|
// If ErrorsAreCritical == false, then we should check only
|
||||||
|
// equality of parsed data and valid data.
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testCase.ValidData, s.IntData)
|
||||||
|
|
||||||
|
// If errors are critical - we should check if test data is within
|
||||||
|
// int8 range and check for error if it isn't.
|
||||||
|
s1 := &testStruct{}
|
||||||
|
err1 := Parse(s1, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
var checkNotIntError bool
|
||||||
|
var checkRangeError bool
|
||||||
|
passedData, err2 := strconv.ParseInt(testCase.TestData, 10, 64)
|
||||||
|
if err2 != nil {
|
||||||
|
checkNotIntError = true
|
||||||
|
}
|
||||||
|
if passedData != int64(testCase.ValidData) {
|
||||||
|
checkRangeError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkNotIntError || checkRangeError {
|
||||||
|
if err1 == nil {
|
||||||
|
t.Log("No error returned!")
|
||||||
|
}
|
||||||
|
require.NotNil(t, err1)
|
||||||
|
|
||||||
|
if checkNotIntError {
|
||||||
|
require.Equal(t, errNotInt, err1)
|
||||||
|
}
|
||||||
|
if checkRangeError {
|
||||||
|
require.Equal(t, errNotInt16, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("TESTSTRUCT_INTDATA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInt32(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
TestData string
|
||||||
|
ValidData int32
|
||||||
|
}{
|
||||||
|
{"0", 0},
|
||||||
|
{"1", 1},
|
||||||
|
{"2", 2},
|
||||||
|
{"-2147483648", -2147483648},
|
||||||
|
{"2147483647", 2147483647},
|
||||||
|
{"-2147483650", 0},
|
||||||
|
{"2147483650", 0},
|
||||||
|
{"not an integer", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
IntData int32
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Logf("Testing: %+v", testCase)
|
||||||
|
os.Setenv("TESTSTRUCT_INTDATA", testCase.TestData)
|
||||||
|
|
||||||
|
// If ErrorsAreCritical == false, then we should check only
|
||||||
|
// equality of parsed data and valid data.
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testCase.ValidData, s.IntData)
|
||||||
|
|
||||||
|
// If errors are critical - we should check if test data is within
|
||||||
|
// int8 range and check for error if it isn't.
|
||||||
|
s1 := &testStruct{}
|
||||||
|
err1 := Parse(s1, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
var checkNotIntError bool
|
||||||
|
var checkRangeError bool
|
||||||
|
passedData, err2 := strconv.ParseInt(testCase.TestData, 10, 64)
|
||||||
|
if err2 != nil {
|
||||||
|
checkNotIntError = true
|
||||||
|
}
|
||||||
|
if passedData != int64(testCase.ValidData) {
|
||||||
|
checkRangeError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkNotIntError || checkRangeError {
|
||||||
|
if err1 == nil {
|
||||||
|
t.Log("No error returned!")
|
||||||
|
}
|
||||||
|
require.NotNil(t, err1)
|
||||||
|
|
||||||
|
if checkNotIntError {
|
||||||
|
require.Equal(t, errNotInt, err1)
|
||||||
|
}
|
||||||
|
if checkRangeError {
|
||||||
|
require.Equal(t, errNotInt32, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("TESTSTRUCT_INTDATA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInt64(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
TestData string
|
||||||
|
ValidData int64
|
||||||
|
}{
|
||||||
|
{"0", 0},
|
||||||
|
{"1", 1},
|
||||||
|
{"2", 2},
|
||||||
|
{"-9223372036854775808", -9223372036854775808},
|
||||||
|
{"9223372036854775807", 9223372036854775807},
|
||||||
|
{"-9223372036854775810", -9223372036854775808},
|
||||||
|
{"9223372036854775810", 9223372036854775807},
|
||||||
|
{"not an integer", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
IntData int64
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Logf("Testing: %+v", testCase)
|
||||||
|
os.Setenv("TESTSTRUCT_INTDATA", testCase.TestData)
|
||||||
|
|
||||||
|
// If ErrorsAreCritical == false, then we should check only
|
||||||
|
// equality of parsed data and valid data.
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testCase.ValidData, s.IntData)
|
||||||
|
|
||||||
|
// If errors are critical - we should check if test data is within
|
||||||
|
// int8 range and check for error if it isn't.
|
||||||
|
s1 := &testStruct{}
|
||||||
|
err1 := Parse(s1, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
var checkNotIntError bool
|
||||||
|
var checkRangeError bool
|
||||||
|
passedData, err2 := strconv.ParseInt(testCase.TestData, 10, 64)
|
||||||
|
if err2 != nil {
|
||||||
|
checkNotIntError = true
|
||||||
|
}
|
||||||
|
if passedData != testCase.ValidData {
|
||||||
|
checkRangeError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkNotIntError || checkRangeError {
|
||||||
|
if err1 == nil {
|
||||||
|
t.Log("No error returned!")
|
||||||
|
}
|
||||||
|
require.NotNil(t, err1)
|
||||||
|
|
||||||
|
if checkNotIntError {
|
||||||
|
require.Equal(t, errNotInt, err1)
|
||||||
|
}
|
||||||
|
if checkRangeError {
|
||||||
|
require.Equal(t, errNotInt64, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("TESTSTRUCT_INTDATA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseUint8(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
TestData string
|
||||||
|
ValidData uint8
|
||||||
|
}{
|
||||||
|
{"0", 0},
|
||||||
|
{"1", 1},
|
||||||
|
{"2", 2},
|
||||||
|
{"255", 255},
|
||||||
|
{"256", 0},
|
||||||
|
{"-1", 0},
|
||||||
|
{"not an integer", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
UintData uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Logf("Testing: %+v", testCase)
|
||||||
|
os.Setenv("TESTSTRUCT_UINTDATA", testCase.TestData)
|
||||||
|
|
||||||
|
// If ErrorsAreCritical == false, then we should check only
|
||||||
|
// equality of parsed data and valid data.
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testCase.ValidData, s.UintData)
|
||||||
|
|
||||||
|
// If errors are critical - we should check if test data is within
|
||||||
|
// int8 range and check for error if it isn't.
|
||||||
|
s1 := &testStruct{}
|
||||||
|
err1 := Parse(s1, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
var checkNotIntError bool
|
||||||
|
var checkRangeError bool
|
||||||
|
passedData, err2 := strconv.ParseUint(testCase.TestData, 10, 64)
|
||||||
|
if err2 != nil {
|
||||||
|
checkNotIntError = true
|
||||||
|
}
|
||||||
|
if passedData != uint64(testCase.ValidData) {
|
||||||
|
checkRangeError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkNotIntError || checkRangeError {
|
||||||
|
if err1 == nil {
|
||||||
|
t.Log("No error returned!")
|
||||||
|
}
|
||||||
|
require.NotNil(t, err1)
|
||||||
|
|
||||||
|
if checkNotIntError {
|
||||||
|
require.Equal(t, errNotUint, err1)
|
||||||
|
}
|
||||||
|
if checkRangeError {
|
||||||
|
require.Equal(t, errNotUint8, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("TESTSTRUCT_UINTDATA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseUint16(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
TestData string
|
||||||
|
ValidData uint16
|
||||||
|
}{
|
||||||
|
{"0", 0},
|
||||||
|
{"1", 1},
|
||||||
|
{"2", 2},
|
||||||
|
{"65535", 65535},
|
||||||
|
{"65536", 0},
|
||||||
|
{"-1", 0},
|
||||||
|
{"not an integer", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
UintData uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Logf("Testing: %+v", testCase)
|
||||||
|
os.Setenv("TESTSTRUCT_UINTDATA", testCase.TestData)
|
||||||
|
|
||||||
|
// If ErrorsAreCritical == false, then we should check only
|
||||||
|
// equality of parsed data and valid data.
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testCase.ValidData, s.UintData)
|
||||||
|
|
||||||
|
// If errors are critical - we should check if test data is within
|
||||||
|
// int8 range and check for error if it isn't.
|
||||||
|
s1 := &testStruct{}
|
||||||
|
err1 := Parse(s1, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
var checkNotIntError bool
|
||||||
|
var checkRangeError bool
|
||||||
|
passedData, err2 := strconv.ParseUint(testCase.TestData, 10, 64)
|
||||||
|
if err2 != nil {
|
||||||
|
checkNotIntError = true
|
||||||
|
}
|
||||||
|
if passedData != uint64(testCase.ValidData) {
|
||||||
|
checkRangeError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkNotIntError || checkRangeError {
|
||||||
|
if err1 == nil {
|
||||||
|
t.Log("No error returned!")
|
||||||
|
}
|
||||||
|
require.NotNil(t, err1)
|
||||||
|
|
||||||
|
if checkNotIntError {
|
||||||
|
require.Equal(t, errNotUint, err1)
|
||||||
|
}
|
||||||
|
if checkRangeError {
|
||||||
|
require.Equal(t, errNotUint16, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("TESTSTRUCT_UINTDATA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseUint32(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
TestData string
|
||||||
|
ValidData uint32
|
||||||
|
}{
|
||||||
|
{"0", 0},
|
||||||
|
{"1", 1},
|
||||||
|
{"2", 2},
|
||||||
|
{"4294967295", 4294967295},
|
||||||
|
{"4294967296", 0},
|
||||||
|
{"-1", 0},
|
||||||
|
{"not an integer", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
UintData uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Logf("Testing: %+v", testCase)
|
||||||
|
os.Setenv("TESTSTRUCT_UINTDATA", testCase.TestData)
|
||||||
|
|
||||||
|
// If ErrorsAreCritical == false, then we should check only
|
||||||
|
// equality of parsed data and valid data.
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testCase.ValidData, s.UintData)
|
||||||
|
|
||||||
|
// If errors are critical - we should check if test data is within
|
||||||
|
// int8 range and check for error if it isn't.
|
||||||
|
s1 := &testStruct{}
|
||||||
|
err1 := Parse(s1, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
var checkNotIntError bool
|
||||||
|
var checkRangeError bool
|
||||||
|
passedData, err2 := strconv.ParseUint(testCase.TestData, 10, 64)
|
||||||
|
if err2 != nil {
|
||||||
|
checkNotIntError = true
|
||||||
|
}
|
||||||
|
if passedData != uint64(testCase.ValidData) {
|
||||||
|
checkRangeError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkNotIntError || checkRangeError {
|
||||||
|
if err1 == nil {
|
||||||
|
t.Log("No error returned!")
|
||||||
|
}
|
||||||
|
require.NotNil(t, err1)
|
||||||
|
|
||||||
|
if checkNotIntError {
|
||||||
|
require.Equal(t, errNotUint, err1)
|
||||||
|
}
|
||||||
|
if checkRangeError {
|
||||||
|
require.Equal(t, errNotUint32, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("TESTSTRUCT_UINTDATA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseUint64(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
TestData string
|
||||||
|
ValidData uint64
|
||||||
|
}{
|
||||||
|
{"0", 0},
|
||||||
|
{"1", 1},
|
||||||
|
{"2", 2},
|
||||||
|
{"18446744073709551615", 18446744073709551615},
|
||||||
|
{"18446744073709551616", 18446744073709551615},
|
||||||
|
{"-1", 0},
|
||||||
|
{"not an integer", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
UintData uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Logf("Testing: %+v", testCase)
|
||||||
|
os.Setenv("TESTSTRUCT_UINTDATA", testCase.TestData)
|
||||||
|
|
||||||
|
// If ErrorsAreCritical == false, then we should check only
|
||||||
|
// equality of parsed data and valid data.
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testCase.ValidData, s.UintData)
|
||||||
|
|
||||||
|
// If errors are critical - we should check if test data is within
|
||||||
|
// int8 range and check for error if it isn't.
|
||||||
|
s1 := &testStruct{}
|
||||||
|
err1 := Parse(s1, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
var checkNotIntError bool
|
||||||
|
var checkRangeError bool
|
||||||
|
passedData, err2 := strconv.ParseUint(testCase.TestData, 10, 64)
|
||||||
|
if err2 != nil {
|
||||||
|
checkNotIntError = true
|
||||||
|
}
|
||||||
|
if passedData != testCase.ValidData {
|
||||||
|
checkRangeError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkNotIntError || checkRangeError {
|
||||||
|
if err1 == nil {
|
||||||
|
t.Log("No error returned!")
|
||||||
|
}
|
||||||
|
require.NotNil(t, err1)
|
||||||
|
|
||||||
|
if checkNotIntError {
|
||||||
|
require.Equal(t, errNotUint, err1)
|
||||||
|
}
|
||||||
|
if checkRangeError {
|
||||||
|
require.Equal(t, errNotUint64, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("TESTSTRUCT_UINTDATA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next tests should be improved.
|
||||||
|
func TestParseFloat32(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
TestData string
|
||||||
|
ValidData float32
|
||||||
|
}{
|
||||||
|
{"0.00", 0.00},
|
||||||
|
{"1.00", 1.00},
|
||||||
|
{"2.00", 2.00},
|
||||||
|
{"-1", -1},
|
||||||
|
{"not a float", 0.00},
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
FloatData float32
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Logf("Testing: %+v", testCase)
|
||||||
|
os.Setenv("TESTSTRUCT_FLOATDATA", testCase.TestData)
|
||||||
|
|
||||||
|
// If ErrorsAreCritical == false, then we should check only
|
||||||
|
// equality of parsed data and valid data.
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testCase.ValidData, s.FloatData)
|
||||||
|
|
||||||
|
// If errors are critical - we should check if test data is within
|
||||||
|
// int8 range and check for error if it isn't.
|
||||||
|
s1 := &testStruct{}
|
||||||
|
err1 := Parse(s1, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
var checkNotIntError bool
|
||||||
|
var checkRangeError bool
|
||||||
|
passedData, err2 := strconv.ParseFloat(testCase.TestData, 64)
|
||||||
|
if err2 != nil {
|
||||||
|
checkNotIntError = true
|
||||||
|
}
|
||||||
|
if passedData != float64(testCase.ValidData) {
|
||||||
|
checkRangeError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkNotIntError || checkRangeError {
|
||||||
|
if err1 == nil {
|
||||||
|
t.Log("No error returned!")
|
||||||
|
}
|
||||||
|
require.NotNil(t, err1)
|
||||||
|
|
||||||
|
if checkNotIntError {
|
||||||
|
require.Equal(t, errNotFloat, err1)
|
||||||
|
}
|
||||||
|
if checkRangeError {
|
||||||
|
require.Equal(t, errNotFloat32, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("TESTSTRUCT_FLOATDATA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFloat64(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
TestData string
|
||||||
|
ValidData float64
|
||||||
|
}{
|
||||||
|
{"0.00", 0.00},
|
||||||
|
{"1.00", 1.00},
|
||||||
|
{"2.00", 2.00},
|
||||||
|
{"-1", -1},
|
||||||
|
{"not a float", 0.00},
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
FloatData float64
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Logf("Testing: %+v", testCase)
|
||||||
|
os.Setenv("TESTSTRUCT_FLOATDATA", testCase.TestData)
|
||||||
|
|
||||||
|
// If ErrorsAreCritical == false, then we should check only
|
||||||
|
// equality of parsed data and valid data.
|
||||||
|
s := &testStruct{}
|
||||||
|
err := Parse(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testCase.ValidData, s.FloatData)
|
||||||
|
|
||||||
|
// If errors are critical - we should check if test data is within
|
||||||
|
// int8 range and check for error if it isn't.
|
||||||
|
s1 := &testStruct{}
|
||||||
|
err1 := Parse(s1, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
var checkNotIntError bool
|
||||||
|
var checkRangeError bool
|
||||||
|
passedData, err2 := strconv.ParseFloat(testCase.TestData, 64)
|
||||||
|
if err2 != nil {
|
||||||
|
checkNotIntError = true
|
||||||
|
}
|
||||||
|
if passedData != testCase.ValidData {
|
||||||
|
checkRangeError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkNotIntError || checkRangeError {
|
||||||
|
if err1 == nil {
|
||||||
|
t.Log("No error returned!")
|
||||||
|
}
|
||||||
|
require.NotNil(t, err1)
|
||||||
|
|
||||||
|
if checkNotIntError {
|
||||||
|
require.Equal(t, errNotFloat, err1)
|
||||||
|
}
|
||||||
|
if checkRangeError {
|
||||||
|
require.Equal(t, errNotFloat64, err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Unsetenv("TESTSTRUCT_FLOATDATA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float
|
93
sec.go
Normal file
93
sec.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package sec
|
||||||
|
|
||||||
|
import (
|
||||||
|
// stdlib
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Errors.
|
||||||
|
errNotPTR = errors.New("passed data is not a pointer")
|
||||||
|
errNotStructure = errors.New("passed data is not a structure")
|
||||||
|
|
||||||
|
// Debug flag.
|
||||||
|
debugFlagEnvName = "SEC_DEBUG"
|
||||||
|
debug bool
|
||||||
|
|
||||||
|
// Parsed structure fields.
|
||||||
|
parsedTree []*field
|
||||||
|
|
||||||
|
// Options for current run.
|
||||||
|
options *Options
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse parses environment variables into passed structure.
|
||||||
|
func Parse(structure interface{}, config *Options) error {
|
||||||
|
parsedTree = []*field{}
|
||||||
|
|
||||||
|
options = config
|
||||||
|
if config == nil {
|
||||||
|
options = defaultOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set debug flag if defined in environment.
|
||||||
|
debugFlagRaw, found := os.LookupEnv(debugFlagEnvName)
|
||||||
|
if found {
|
||||||
|
var err error
|
||||||
|
debug, err = strconv.ParseBool(debugFlagRaw)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Invalid '" + debugFlagEnvName + "' environment variable data: '" + debugFlagRaw + "'. Error: " + err.Error())
|
||||||
|
if options.ErrorsAreCritical {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printDebug("Debug mode activated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printDebug("Parsing started with configuration: %+v", options)
|
||||||
|
|
||||||
|
value := reflect.ValueOf(structure)
|
||||||
|
|
||||||
|
// Figure out passed data type. We should accept ONLY pointers
|
||||||
|
// to structure.
|
||||||
|
printDebug("Passed structure kind: %s, want: %s", value.Type().Kind().String(), reflect.Ptr.String())
|
||||||
|
|
||||||
|
// If passed data isn't a pointer - return error in any case because
|
||||||
|
// we can't support anything except pointer.
|
||||||
|
if value.Type().Kind() != reflect.Ptr {
|
||||||
|
return errNotPTR
|
||||||
|
}
|
||||||
|
|
||||||
|
printDebug("Passed data kind: %s, want: %s", value.Elem().Type().Kind().String(), reflect.Struct.String())
|
||||||
|
|
||||||
|
value = value.Elem()
|
||||||
|
|
||||||
|
// Passed data should be a pointer to structure. Otherwise we should
|
||||||
|
// return error in any case.
|
||||||
|
if value.Type().Kind() != reflect.Struct {
|
||||||
|
return errNotStructure
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse structure.
|
||||||
|
composeTree(value, strings.ToUpper(value.Type().Name()))
|
||||||
|
|
||||||
|
return parseEnv()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produces debug output into stdout using standard log module if debug
|
||||||
|
// mode was activated by setting SEC_DEBUG environment variable to true.
|
||||||
|
func printDebug(text string, params ...interface{}) {
|
||||||
|
if debug {
|
||||||
|
if len(params) == 0 {
|
||||||
|
log.Println(text)
|
||||||
|
} else {
|
||||||
|
log.Printf(text, params...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
204
sec_test.go
Normal file
204
sec_test.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package sec
|
||||||
|
|
||||||
|
import (
|
||||||
|
// stdlib
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
// other
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testString = "Test string"
|
||||||
|
testInt8 = int8(8)
|
||||||
|
testInt16 = int16(16)
|
||||||
|
testInt32 = int32(32)
|
||||||
|
testInt64 = int64(64)
|
||||||
|
testUint8 = uint8(8)
|
||||||
|
testUint16 = uint16(16)
|
||||||
|
testUint32 = uint32(32)
|
||||||
|
testUint64 = uint64(64)
|
||||||
|
testFloat32 = float32(32.00)
|
||||||
|
testFloat64 = float64(64.00)
|
||||||
|
testBool = true
|
||||||
|
)
|
||||||
|
|
||||||
|
type testDatas struct {
|
||||||
|
TestString string
|
||||||
|
TestInt8 int8
|
||||||
|
TestInt16 int16
|
||||||
|
TestInt32 int32
|
||||||
|
TestInt64 int64
|
||||||
|
TestUint8 uint8
|
||||||
|
TestUint16 uint16
|
||||||
|
TestUint32 uint32
|
||||||
|
TestUint64 uint64
|
||||||
|
TestFloat32 float32
|
||||||
|
TestFloat64 float64
|
||||||
|
TestBool bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStringType string
|
||||||
|
|
||||||
|
type testStruct1 struct {
|
||||||
|
testDatas
|
||||||
|
testStringType
|
||||||
|
TestNestAnonymous struct {
|
||||||
|
TestString string
|
||||||
|
TestInt8 int8
|
||||||
|
TestInt16 int16
|
||||||
|
TestInt32 int32
|
||||||
|
TestInt64 int64
|
||||||
|
TestUint8 uint8
|
||||||
|
TestUint16 uint16
|
||||||
|
TestUint32 uint32
|
||||||
|
TestUint64 uint64
|
||||||
|
TestFloat32 float32
|
||||||
|
TestFloat64 float64
|
||||||
|
TestBool bool
|
||||||
|
}
|
||||||
|
TestNestAnonymousPointer *struct {
|
||||||
|
TestString string
|
||||||
|
TestInt8 int8
|
||||||
|
TestInt16 int16
|
||||||
|
TestInt32 int32
|
||||||
|
TestInt64 int64
|
||||||
|
TestUint8 uint8
|
||||||
|
TestUint16 uint16
|
||||||
|
TestUint32 uint32
|
||||||
|
TestUint64 uint64
|
||||||
|
TestFloat32 float32
|
||||||
|
TestFloat64 float64
|
||||||
|
TestBool bool
|
||||||
|
}
|
||||||
|
TestNestPointer *testDatas
|
||||||
|
TestNest testDatas
|
||||||
|
TestNestInterfacePointer interface{}
|
||||||
|
TestNestInterface interface{}
|
||||||
|
testUnexported string
|
||||||
|
testUnexportedNest *testDatas
|
||||||
|
}
|
||||||
|
|
||||||
|
func setenv(prefix string) {
|
||||||
|
os.Setenv(prefix+"TESTSTRING", testString)
|
||||||
|
os.Setenv(prefix+"TESTINT8", strconv.FormatInt(int64(testInt8), 10))
|
||||||
|
os.Setenv(prefix+"TESTINT16", strconv.FormatInt(int64(testInt16), 10))
|
||||||
|
os.Setenv(prefix+"TESTINT32", strconv.FormatInt(int64(testInt32), 10))
|
||||||
|
os.Setenv(prefix+"TESTINT64", strconv.FormatInt(int64(testInt64), 10))
|
||||||
|
os.Setenv(prefix+"TESTUINT8", strconv.FormatInt(int64(testUint8), 10))
|
||||||
|
os.Setenv(prefix+"TESTUINT16", strconv.FormatInt(int64(testUint16), 10))
|
||||||
|
os.Setenv(prefix+"TESTUINT32", strconv.FormatInt(int64(testUint32), 10))
|
||||||
|
os.Setenv(prefix+"TESTUINT64", strconv.FormatInt(int64(testUint64), 10))
|
||||||
|
os.Setenv(prefix+"TESTFLOAT32", strconv.FormatFloat(float64(testFloat32), 'f', 2, 32))
|
||||||
|
os.Setenv(prefix+"TESTFLOAT64", strconv.FormatFloat(testFloat64, 'f', 2, 64))
|
||||||
|
os.Setenv(prefix+"TESTBOOL", "true")
|
||||||
|
|
||||||
|
os.Setenv(debugFlagEnvName, "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsetenv(prefix string) {
|
||||||
|
os.Unsetenv(prefix + "TESTSTRING")
|
||||||
|
os.Unsetenv(prefix + "TESTINT8")
|
||||||
|
os.Unsetenv(prefix + "TESTINT16")
|
||||||
|
os.Unsetenv(prefix + "TESTINT32")
|
||||||
|
os.Unsetenv(prefix + "TESTINT64")
|
||||||
|
os.Unsetenv(prefix + "TESTUINT8")
|
||||||
|
os.Unsetenv(prefix + "TESTUINT16")
|
||||||
|
os.Unsetenv(prefix + "TESTUINT32")
|
||||||
|
os.Unsetenv(prefix + "TESTUINT64")
|
||||||
|
os.Unsetenv(prefix + "TESTFLOAT32")
|
||||||
|
os.Unsetenv(prefix + "TESTFLOAT64")
|
||||||
|
os.Unsetenv(prefix + "TESTBOOL")
|
||||||
|
|
||||||
|
os.Unsetenv(debugFlagEnvName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseValidData(t *testing.T) {
|
||||||
|
setenv("TESTSTRUCT1_")
|
||||||
|
setenv("TESTSTRUCT1_TESTNEST_")
|
||||||
|
setenv("TESTSTRUCT1_TESTNESTANONYMOUS_")
|
||||||
|
setenv("TESTSTRUCT1_TESTNESTANONYMOUSPOINTER_")
|
||||||
|
setenv("TESTSTRUCT1_TESTNESTINTERFACE_")
|
||||||
|
setenv("TESTSTRUCT1_TESTNESTINTERFACEPOINTER_")
|
||||||
|
setenv("TESTSTRUCT1_TESTNESTPOINTER_")
|
||||||
|
setenv("TESTSTRUCT1_TESTUNEXPORTEDNEST_")
|
||||||
|
|
||||||
|
ts := &testStruct1{}
|
||||||
|
err := Parse(ts, nil)
|
||||||
|
t.Logf("Parsed data: %+v\n", ts)
|
||||||
|
t.Logf("Parsed nested data: %+v\n", ts.TestNest)
|
||||||
|
t.Logf("Parsed nested interface data: %+v\n", ts.TestNestInterface)
|
||||||
|
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, testBool, ts.TestBool)
|
||||||
|
|
||||||
|
unsetenv("TESTSTRUCT1_")
|
||||||
|
unsetenv("TESTSTRUCT1_TESTNEST_")
|
||||||
|
unsetenv("TESTSTRUCT1_TESTNESTANONYMOUS_")
|
||||||
|
unsetenv("TESTSTRUCT1_TESTNESTANONYMOUSPOINTER_")
|
||||||
|
unsetenv("TESTSTRUCT1_TESTNESTINTERFACE_")
|
||||||
|
unsetenv("TESTSTRUCT1_TESTNESTINTERFACEPOINTER_")
|
||||||
|
unsetenv("TESTSTRUCT1_TESTNESTPOINTER_")
|
||||||
|
unsetenv("TESTSTRUCT1_TESTUNEXPORTEDNEST_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseNotPointerToStructurePassed(t *testing.T) {
|
||||||
|
setenv("")
|
||||||
|
|
||||||
|
var data string
|
||||||
|
err := Parse(&data, nil)
|
||||||
|
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Equal(t, errNotStructure, err)
|
||||||
|
|
||||||
|
unsetenv("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseNotPointerPassed(t *testing.T) {
|
||||||
|
setenv("")
|
||||||
|
|
||||||
|
c := testStruct1{}
|
||||||
|
err := Parse(c, nil)
|
||||||
|
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Equal(t, errNotPTR, err)
|
||||||
|
|
||||||
|
unsetenv("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseNotStructurePassed(t *testing.T) {
|
||||||
|
d := "invalid data"
|
||||||
|
err := Parse(d, nil)
|
||||||
|
t.Log(err.Error())
|
||||||
|
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Equal(t, errNotPTR, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidDebugFlagValue(t *testing.T) {
|
||||||
|
_ = os.Setenv(debugFlagEnvName, "INVALID")
|
||||||
|
c := &testStruct1{}
|
||||||
|
err := Parse(c, nil)
|
||||||
|
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.False(t, debug)
|
||||||
|
|
||||||
|
os.Unsetenv(debugFlagEnvName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidDebugFlagValueWithErrorsAreCritical(t *testing.T) {
|
||||||
|
_ = os.Setenv(debugFlagEnvName, "INVALID")
|
||||||
|
c := &testStruct1{}
|
||||||
|
err := Parse(c, &Options{ErrorsAreCritical: true})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.False(t, debug)
|
||||||
|
|
||||||
|
os.Unsetenv(debugFlagEnvName)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user