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