-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add configuration data source (#36)
* feat(config): Add valuable type to allow data source reference * feat(storage): Use valuable configuration type for redis credentials * feat(storage): Use valuable configuration type for postgres credentials * fix(config): Valuable needed to be recovered once to work * feat(security): Factory compareWithStaticValue use Valuable now * docs(example): Add Valuable to the example * docs(readme): Update roadmap for 0.3
- Loading branch information
Showing
10 changed files
with
484 additions
and
33 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package valuable | ||
|
||
import ( | ||
"reflect" | ||
|
||
"github.com/mitchellh/mapstructure" | ||
) | ||
|
||
// Decode decodes the given data into the given result. | ||
// In case of the target Type if a Valuable, we serialize it with | ||
// `SerializeValuable` func. | ||
// @param input is the data to decode | ||
// @param output is the result of the decoding | ||
// @return an error if the decoding failed | ||
func Decode(input, output interface{}) (err error) { | ||
var decoder *mapstructure.Decoder | ||
|
||
decoder, err = mapstructure.NewDecoder(&mapstructure.DecoderConfig{ | ||
Result: output, | ||
DecodeHook: valuableDecodeHook, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return decoder.Decode(input) | ||
} | ||
|
||
// valuableDecodeHook is a mapstructure.DecodeHook that serializes | ||
// the given data into a Valuable. | ||
func valuableDecodeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { | ||
if t != reflect.TypeOf(Valuable{}) { | ||
return data, nil | ||
} | ||
|
||
return SerializeValuable(data) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package valuable | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
type TestSuiteValuableDecode struct { | ||
suite.Suite | ||
|
||
testValue, testValueCommaSeparated string | ||
testValues []string | ||
} | ||
|
||
func (suite *TestSuiteValuableDecode) BeforeTest(suiteName, testName string) { | ||
suite.testValue = "testValue" | ||
suite.testValues = []string{"testValue1", "testValue2"} | ||
suite.testValueCommaSeparated = "testValue3,testValue4" | ||
} | ||
|
||
func (suite *TestSuiteValuableDecode) TestDecodeInvalidOutput() { | ||
assert := assert.New(suite.T()) | ||
|
||
err := Decode(map[string]interface{}{"value": suite.testValue}, nil) | ||
assert.Error(err) | ||
} | ||
|
||
func (suite *TestSuiteValuableDecode) TestDecodeString() { | ||
assert := assert.New(suite.T()) | ||
|
||
type strukt struct { | ||
Value string `mapstructure:"value"` | ||
} | ||
|
||
output := strukt{} | ||
err := Decode(map[string]interface{}{"value": suite.testValue}, &output) | ||
assert.NoError(err) | ||
assert.Equal(suite.testValue, output.Value) | ||
} | ||
|
||
func (suite *TestSuiteValuableDecode) TestDecodeValuableRootString() { | ||
assert := assert.New(suite.T()) | ||
|
||
type strukt struct { | ||
Value Valuable `mapstructure:"value"` | ||
} | ||
|
||
output := strukt{} | ||
err := Decode(map[string]interface{}{"value": suite.testValue}, &output) | ||
assert.NoError(err) | ||
assert.Equal(suite.testValue, output.Value.First()) | ||
} | ||
|
||
func (suite *TestSuiteValuableDecode) TestDecodeValuableRootBool() { | ||
assert := assert.New(suite.T()) | ||
|
||
type strukt struct { | ||
Value Valuable `mapstructure:"value"` | ||
} | ||
|
||
output := strukt{} | ||
err := Decode(map[string]interface{}{"value": true}, &output) | ||
assert.NoError(err) | ||
assert.Equal("true", output.Value.First()) | ||
} | ||
|
||
func (suite *TestSuiteValuableDecode) TestDecodeValuableValue() { | ||
assert := assert.New(suite.T()) | ||
|
||
type strukt struct { | ||
Value Valuable `mapstructure:"value"` | ||
} | ||
|
||
output := strukt{} | ||
err := Decode(map[string]interface{}{"value": map[string]interface{}{"value": suite.testValue}}, &output) | ||
assert.NoError(err) | ||
assert.Equal(suite.testValue, output.Value.First()) | ||
} | ||
|
||
func (suite *TestSuiteValuableDecode) TestDecodeValuableValues() { | ||
assert := assert.New(suite.T()) | ||
|
||
type strukt struct { | ||
Value Valuable `mapstructure:"value"` | ||
} | ||
|
||
output := strukt{} | ||
err := Decode(map[string]interface{}{"value": map[string]interface{}{"values": suite.testValues}}, &output) | ||
assert.NoError(err) | ||
assert.Equal(suite.testValues, output.Value.Get()) | ||
} | ||
|
||
func (suite *TestSuiteValuableDecode) TestDecodeValuableStaticValuesWithComma() { | ||
assert := assert.New(suite.T()) | ||
|
||
type strukt struct { | ||
Value Valuable `mapstructure:"value"` | ||
} | ||
|
||
output := strukt{} | ||
err := Decode(map[string]interface{}{"value": map[string]interface{}{"valueFrom": map[string]interface{}{"staticRef": suite.testValueCommaSeparated}}}, &output) | ||
assert.NoError(err) | ||
assert.Equal(strings.Split(suite.testValueCommaSeparated, ","), output.Value.Get()) | ||
} | ||
|
||
func TestRunSuiteValuableDecode(t *testing.T) { | ||
suite.Run(t, new(TestSuiteValuableDecode)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package valuable | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
"strings" | ||
|
||
"github.com/mitchellh/mapstructure" | ||
) | ||
|
||
// Valuable represent value who it is possible to retrieve the data | ||
// in multiple ways. From a simple value without nesting, | ||
// or from a deep data source. | ||
type Valuable struct { | ||
// Value represents the `value` field of a configuration entry that | ||
// contains only one value | ||
Value *string `json:"value,omitempty"` | ||
// Values represents the `value` field of a configuration entry that | ||
// contains multiple values stored in a list | ||
Values []string `json:"values,omitempty"` | ||
// ValueFrom represents the `valueFrom` field of a configuration entry | ||
// that contains a reference to a data source | ||
ValueFrom *ValueFromSource `json:"valueFrom,omitempty"` | ||
} | ||
|
||
type ValueFromSource struct { | ||
// StaticRef represents the `staticRef` field of a configuration entry | ||
// that contains a static value. Can contain a comma separated list | ||
StaticRef *string `json:"staticRef,omitempty"` | ||
// EnvRef represents the `envRef` field of a configuration entry | ||
// that contains a reference to an environment variable | ||
EnvRef *string `json:"envRef,omitempty"` | ||
} | ||
|
||
// SerializeValuable serialize anything to a Valuable | ||
// @param data is the data to serialize | ||
// @return the serialized Valuable | ||
func SerializeValuable(data interface{}) (*Valuable, error) { | ||
switch t := data.(type) { | ||
case string: | ||
return &Valuable{Value: &t}, nil | ||
case int, float32, float64, bool: | ||
str := fmt.Sprint(t) | ||
return &Valuable{Value: &str}, nil | ||
case nil: | ||
return &Valuable{}, nil | ||
default: | ||
valuable := Valuable{} | ||
if err := mapstructure.Decode(data, &valuable); err != nil { | ||
return nil, errors.New("unimplemented valuable type") | ||
} | ||
return &valuable, nil | ||
} | ||
} | ||
|
||
// Get returns all values of the Valuable as a slice | ||
// @return the slice of values | ||
func (v *Valuable) Get() []string { | ||
var computedValues []string | ||
|
||
computedValues = append(computedValues, v.Values...) | ||
|
||
if v.Value != nil && !contains(computedValues, *v.Value) { | ||
computedValues = append(computedValues, *v.Value) | ||
} | ||
|
||
if v.ValueFrom == nil { | ||
return computedValues | ||
} | ||
|
||
if v.ValueFrom.StaticRef != nil && !contains(computedValues, *v.ValueFrom.StaticRef) { | ||
computedValues = appendCommaListIfAbsent(computedValues, *v.ValueFrom.StaticRef) | ||
} | ||
|
||
if v.ValueFrom.EnvRef != nil { | ||
computedValues = appendCommaListIfAbsent(computedValues, os.Getenv(*v.ValueFrom.EnvRef)) | ||
} | ||
|
||
return computedValues | ||
} | ||
|
||
// First returns the first value of the Valuable possible values | ||
// as a string. The order of preference is: | ||
// - Values | ||
// - Value | ||
// - ValueFrom.StaticRef | ||
// - ValueFrom.EnvRef | ||
// @return the first value | ||
func (v *Valuable) First() string { | ||
if len(v.Get()) == 0 { | ||
return "" | ||
} | ||
|
||
return v.Get()[0] | ||
} | ||
|
||
// Contains returns true if the Valuable contains the given value | ||
// @param value is the value to check | ||
// @return true if the Valuable contains the given value | ||
func (v *Valuable) Contains(element string) bool { | ||
for _, s := range v.Get() { | ||
if s == element { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// contains returns true if the Valuable contains the given value. | ||
// This function is private to prevent stack overflow during the initialization | ||
// of the Valuable object. | ||
// @param | ||
// @param value is the value to check | ||
// @return true if the Valuable contains the given value | ||
func contains(slice []string, element string) bool { | ||
for _, s := range slice { | ||
if s == element { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// appendCommaListIfAbsent accept a string list separated with commas to append | ||
// to the Values all elements of this list only if element is absent | ||
// of the Values | ||
func appendCommaListIfAbsent(slice []string, commaList string) []string { | ||
for _, s := range strings.Split(commaList, ",") { | ||
if contains(slice, s) { | ||
continue | ||
} | ||
|
||
slice = append(slice, s) | ||
} | ||
return slice | ||
} |
Oops, something went wrong.