@@ -96,8 +96,8 @@ func (l *Map) Unmarshal(rawVal interface{}) error {
96
96
}
97
97
98
98
// UnmarshalExact unmarshalls the config into a struct, erroring if a field is nonexistent.
99
- func (l * Map ) UnmarshalExact (intoCfg interface {}) error {
100
- dc := decoderConfig (intoCfg )
99
+ func (l * Map ) UnmarshalExact (rawVal interface {}) error {
100
+ dc := decoderConfig (rawVal )
101
101
dc .ErrorUnused = true
102
102
decoder , err := mapstructure .NewDecoder (dc )
103
103
if err != nil {
@@ -164,13 +164,23 @@ func decoderConfig(result interface{}) *mapstructure.DecoderConfig {
164
164
TagName : "mapstructure" ,
165
165
WeaklyTypedInput : true ,
166
166
DecodeHook : mapstructure .ComposeDecodeHookFunc (
167
- expandNilStructPointers (),
168
- mapstructure .StringToTimeDurationHookFunc (),
169
- mapstructure .StringToSliceHookFunc ("," ),
167
+ expandNilStructPointersFunc ,
168
+ stringToSliceHookFunc ,
169
+ mapStringToMapComponentIDHookFunc ,
170
+ stringToTimeDurationHookFunc ,
171
+ textUnmarshallerHookFunc ,
170
172
),
171
173
}
172
174
}
173
175
176
+ var (
177
+ stringToSliceHookFunc = mapstructure .StringToSliceHookFunc ("," )
178
+ stringToTimeDurationHookFunc = mapstructure .StringToTimeDurationHookFunc ()
179
+ textUnmarshallerHookFunc = mapstructure .TextUnmarshallerHookFunc ()
180
+
181
+ componentIDType = reflect .TypeOf (NewComponentID ("foo" ))
182
+ )
183
+
174
184
// In cases where a config has a mapping of something to a struct pointers
175
185
// we want nil values to resolve to a pointer to the zero value of the
176
186
// underlying struct just as we want nil values of a mapping of something
@@ -185,26 +195,51 @@ func decoderConfig(result interface{}) *mapstructure.DecoderConfig {
185
195
//
186
196
// we want an unmarshaled Config to be equivalent to
187
197
// Config{Thing: &SomeStruct{}} instead of Config{Thing: nil}
188
- func expandNilStructPointers () mapstructure.DecodeHookFunc {
189
- return func (from reflect.Value , to reflect.Value ) (interface {}, error ) {
190
- // ensure we are dealing with map to map comparison
191
- if from .Kind () == reflect .Map && to .Kind () == reflect .Map {
192
- toElem := to .Type ().Elem ()
193
- // ensure that map values are pointers to a struct
194
- // (that may be nil and require manual setting w/ zero value)
195
- if toElem .Kind () == reflect .Ptr && toElem .Elem ().Kind () == reflect .Struct {
196
- fromRange := from .MapRange ()
197
- for fromRange .Next () {
198
- fromKey := fromRange .Key ()
199
- fromValue := fromRange .Value ()
200
- // ensure that we've run into a nil pointer instance
201
- if fromValue .IsNil () {
202
- newFromValue := reflect .New (toElem .Elem ())
203
- from .SetMapIndex (fromKey , newFromValue )
204
- }
198
+ var expandNilStructPointersFunc = func (from reflect.Value , to reflect.Value ) (interface {}, error ) {
199
+ // ensure we are dealing with map to map comparison
200
+ if from .Kind () == reflect .Map && to .Kind () == reflect .Map {
201
+ toElem := to .Type ().Elem ()
202
+ // ensure that map values are pointers to a struct
203
+ // (that may be nil and require manual setting w/ zero value)
204
+ if toElem .Kind () == reflect .Ptr && toElem .Elem ().Kind () == reflect .Struct {
205
+ fromRange := from .MapRange ()
206
+ for fromRange .Next () {
207
+ fromKey := fromRange .Key ()
208
+ fromValue := fromRange .Value ()
209
+ // ensure that we've run into a nil pointer instance
210
+ if fromValue .IsNil () {
211
+ newFromValue := reflect .New (toElem .Elem ())
212
+ from .SetMapIndex (fromKey , newFromValue )
205
213
}
206
214
}
207
215
}
208
- return from .Interface (), nil
209
216
}
217
+ return from .Interface (), nil
218
+ }
219
+
220
+ // mapStringToMapComponentIDHookFunc returns a DecodeHookFunc that converts a map[string]interface{} to
221
+ // map[ComponentID]interface{}.
222
+ // This is needed in combination since the ComponentID.UnmarshalText may produce equal IDs for different strings,
223
+ // and an error needs to be returned in that case, otherwise the last equivalent ID overwrites the previous one.
224
+ var mapStringToMapComponentIDHookFunc = func (f reflect.Type , t reflect.Type , data interface {}) (interface {}, error ) {
225
+ if f .Kind () != reflect .Map || f .Key ().Kind () != reflect .String {
226
+ return data , nil
227
+ }
228
+
229
+ if t .Kind () != reflect .Map || t .Key () != componentIDType {
230
+ return data , nil
231
+ }
232
+
233
+ m := make (map [ComponentID ]interface {})
234
+ for k , v := range data .(map [string ]interface {}) {
235
+ id , err := NewComponentIDFromString (k )
236
+ if err != nil {
237
+ return nil , err
238
+ }
239
+ if _ , ok := m [id ]; ok {
240
+ return nil , fmt .Errorf ("duplicate name %q after trimming spaces %v" , k , id )
241
+ }
242
+ m [id ] = v
243
+ }
244
+ return m , nil
210
245
}
0 commit comments