-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for encoding.TextUnmarshaler #6
base: master
Are you sure you want to change the base?
Changes from 7 commits
2faffc6
9399199
94f54e5
d6464b8
bdbe441
e1209b7
a855d92
e51fbdf
eb92c71
486ae73
60011fb
37828bd
527e673
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
package ssmconfig | ||
|
||
import ( | ||
"encoding" | ||
"path" | ||
"reflect" | ||
"strconv" | ||
|
@@ -33,6 +34,10 @@ type Provider struct { | |
SSM ssmiface.SSMAPI | ||
} | ||
|
||
const unmarshalTextMethod = "UnmarshalText" | ||
|
||
var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() | ||
|
||
// Process loads config values from smm (parameter store) into c. Encrypted parameters | ||
// will automatically be decrypted. c must be a pointer to a struct. | ||
// | ||
|
@@ -59,7 +64,8 @@ func (p *Provider) Process(configPath string, c interface{}) error { | |
return errors.New("ssmconfig: c must be a pointer to a struct") | ||
} | ||
|
||
spec := buildStructSpec(configPath, v.Type()) | ||
t := v.Type() | ||
spec := buildStructSpec(configPath, t) | ||
|
||
params, invalidPrams, err := p.getParameters(spec) | ||
if err != nil { | ||
|
@@ -84,9 +90,16 @@ func (p *Provider) Process(configPath string, c interface{}) error { | |
continue | ||
} | ||
|
||
err = setValue(v.Field(i), value) | ||
valueField := v.Field(i) | ||
typeField := t.Field(i) | ||
var err error | ||
if isTextUnmarshaler(typeField) { | ||
err = unmarshalText(typeField, valueField, value) | ||
} else { | ||
err = setValue(valueField, value) | ||
} | ||
if err != nil { | ||
return errors.Wrapf(err, "ssmconfig: error setting field %s", v.Type().Field(i).Name) | ||
return errors.Wrapf(err, "ssmconfig: error setting field %s", typeField.Name) | ||
} | ||
} | ||
|
||
|
@@ -129,6 +142,31 @@ func (p *Provider) getParameters(spec structSpec) (params map[string]string, inv | |
return params, invalidParams, nil | ||
} | ||
|
||
// Checks whether the value implements the TextUnmarshaler interface. | ||
func isTextUnmarshaler(f reflect.StructField) bool { | ||
return reflect.PtrTo(f.Type).Implements(textUnmarshalerType) | ||
} | ||
|
||
// Create a new instance of the field's type and call its UnmarshalText([]byte) method. | ||
// Set the value after execution and fail if the method returns an error. | ||
func unmarshalText(f reflect.StructField, v reflect.Value, s string) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be simplified quite a bit. The We also need to handle the case where the value is already a pointer to the implementation of My implementation of this would probably look something like: func unmarshalText(t reflect.Type, v reflect.Value, s string, shouldAddr bool) error {
if shouldAddr {
v = v.Addr()
}
if t.Kind() == reflect.Ptr && v.IsNil() {
v.Set(reflect.New(t.Elem()))
}
return v.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(s))
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will definitely change the much simpler method invocation, thanks! I didn't add support for pointers on purpose as all the other types don't support pointers yet. I thought this could be addressed in a new issue and possibly implemented using a more dynamic / recursive approach for all supported types. What do you think? Should I add pointer support already or not? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmmmmm, Good point. I think I would go ahead and add pointer support for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ I also added tests for empty non-required parameters and added another example to the readme. |
||
newV := reflect.New(f.Type) | ||
method := newV.MethodByName(unmarshalTextMethod) | ||
|
||
args := []reflect.Value{reflect.ValueOf([]byte(s))} | ||
values := method.Call(args) | ||
|
||
v.Set(newV.Elem()) | ||
|
||
// implementation only returns an error value | ||
if !values[0].IsNil() { | ||
err := values[0].Elem().Interface().(error) | ||
return errors.Errorf("could not decode %q into type %v: %v", s, f.Type.String(), err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func setValue(v reflect.Value, s string) error { | ||
switch v.Kind() { | ||
case reflect.String: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would move all of the logic for
TextUnmarshaler
into thesetValue
function.We also need to handle the case where the value is already a pointer to the implementation of
TextUnmarshaled
.Something like: