Skip to content

Commit 29d8e1d

Browse files
authored
Merge pull request #60 from kitagry/fix-disallow-unknown-field
Fix disallow unknown field error
2 parents 97dfcc5 + 5ce623a commit 29d8e1d

File tree

2 files changed

+95
-3
lines changed

2 files changed

+95
-3
lines changed

decode.go

+54-3
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,45 @@ func errTypeMismatch(dstType, srcType reflect.Type) *typeError {
269269
return &typeError{dstType: dstType, srcType: srcType}
270270
}
271271

272+
type unknownFieldError struct {
273+
err error
274+
}
275+
276+
func (e *unknownFieldError) Error() string {
277+
return e.err.Error()
278+
}
279+
280+
func errUnknownField(msg string, tk *token.Token) *unknownFieldError {
281+
return &unknownFieldError{err: errors.ErrSyntax(msg, tk)}
282+
}
283+
284+
func (d *Decoder) deleteStructKeys(structValue reflect.Value, unknownFields map[string]ast.Node) error {
285+
strType := structValue.Type()
286+
structFieldMap, err := structFieldMap(strType)
287+
if err != nil {
288+
return errors.Wrapf(err, "failed to create struct field map")
289+
}
290+
291+
for j := 0; j < strType.NumField(); j++ {
292+
field := structValue.Type().Field(j)
293+
if isIgnoredStructField(field) {
294+
continue
295+
}
296+
297+
structField, ok := structFieldMap[field.Name]
298+
if !ok {
299+
continue
300+
}
301+
302+
if structField.IsInline {
303+
d.deleteStructKeys(structValue.FieldByName(field.Name), unknownFields)
304+
} else {
305+
delete(unknownFields, structField.RenderName)
306+
}
307+
}
308+
return nil
309+
}
310+
272311
func (d *Decoder) decodeValue(dst reflect.Value, src ast.Node) error {
273312
valueType := dst.Type()
274313
if unmarshaler, ok := dst.Addr().Interface().(BytesUnmarshaler); ok {
@@ -578,10 +617,22 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
578617
te.structFieldName = &fieldName
579618
}
580619
foundErr = te
620+
continue
621+
}
622+
623+
if d.disallowUnknownField {
624+
var ufe *unknownFieldError
625+
if !xerrors.As(err, &ufe) {
626+
foundErr = err
627+
continue
628+
}
629+
630+
if err = d.deleteStructKeys(fieldValue, unknownFields); err != nil {
631+
return errors.Wrapf(err, "cannot delete struct keys")
632+
}
581633
} else {
582-
foundErr = err
634+
continue
583635
}
584-
continue
585636
}
586637
d.setDefaultValueIfConflicted(newFieldValue, structFieldMap)
587638
fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type()))
@@ -637,7 +688,7 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
637688
}
638689
if len(unknownFields) != 0 && d.disallowUnknownField {
639690
for key, node := range unknownFields {
640-
return errors.ErrSyntax(fmt.Sprintf(`unknown field "%s"`, key), node.GetToken())
691+
return errUnknownField(fmt.Sprintf(`unknown field "%s"`, key), node.GetToken())
641692
}
642693
}
643694
if foundErr != nil {

decode_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,47 @@ b_yaml: b_yaml_value
11941194
}
11951195
}
11961196

1197+
func TestDecoder_DisallowUnknownField(t *testing.T) {
1198+
t.Run("different level keys with same name", func(t *testing.T) {
1199+
var v struct {
1200+
C Child `yaml:"c"`
1201+
}
1202+
yml := `---
1203+
b: 1
1204+
c:
1205+
b: 1
1206+
`
1207+
1208+
err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowUnknownField()).Decode(&v)
1209+
if err == nil {
1210+
t.Fatalf("error expected")
1211+
}
1212+
})
1213+
t.Run("inline", func(t *testing.T) {
1214+
var v struct {
1215+
Child `yaml:",inline"`
1216+
A string `yaml:"a"`
1217+
}
1218+
yml := `---
1219+
a: a
1220+
b: 1
1221+
`
1222+
1223+
if err := yaml.NewDecoder(strings.NewReader(yml), yaml.DisallowUnknownField()).Decode(&v); err != nil {
1224+
t.Fatalf(`parsing should succeed: %s`, err)
1225+
}
1226+
if v.A != "a" {
1227+
t.Fatalf("v.A should be `a`, got `%s`", v.A)
1228+
}
1229+
if v.B != 1 {
1230+
t.Fatalf("v.B should be 1, got %d", v.B)
1231+
}
1232+
if v.C != 0 {
1233+
t.Fatalf("v.C should be 0, got %d", v.C)
1234+
}
1235+
})
1236+
}
1237+
11971238
func TestDecoder_DefaultValues(t *testing.T) {
11981239
v := struct {
11991240
A string `yaml:"a"`

0 commit comments

Comments
 (0)