Skip to content

Commit

Permalink
Merge pull request #56 from kitagry/disallowUnknownField
Browse files Browse the repository at this point in the history
Add disallowUnknownField Option
  • Loading branch information
goccy authored Dec 11, 2019
2 parents 45618f6 + e8ac292 commit d65a7f6
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 22 deletions.
75 changes: 53 additions & 22 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,30 @@ import (

// Decoder reads and decodes YAML values from an input stream.
type Decoder struct {
reader io.Reader
referenceReaders []io.Reader
anchorMap map[string]ast.Node
opts []DecodeOption
referenceFiles []string
referenceDirs []string
isRecursiveDir bool
isResolvedReference bool
validator StructValidator
reader io.Reader
referenceReaders []io.Reader
anchorMap map[string]ast.Node
opts []DecodeOption
referenceFiles []string
referenceDirs []string
isRecursiveDir bool
isResolvedReference bool
validator StructValidator
disallowUnknownField bool
}

// NewDecoder returns a new decoder that reads from r.
func NewDecoder(r io.Reader, opts ...DecodeOption) *Decoder {
return &Decoder{
reader: r,
anchorMap: map[string]ast.Node{},
opts: opts,
referenceReaders: []io.Reader{},
referenceFiles: []string{},
referenceDirs: []string{},
isRecursiveDir: false,
isResolvedReference: false,
reader: r,
anchorMap: map[string]ast.Node{},
opts: opts,
referenceReaders: []io.Reader{},
referenceFiles: []string{},
referenceDirs: []string{},
isRecursiveDir: false,
isResolvedReference: false,
disallowUnknownField: false,
}
}

Expand Down Expand Up @@ -407,7 +409,7 @@ func (d *Decoder) castToAssignableValue(value reflect.Value, target reflect.Type
return value
}

func (d *Decoder) keyToNodeMap(node ast.Node) (map[string]ast.Node, error) {
func (d *Decoder) keyToNodeMap(node ast.Node, getKeyOrValueNode func(*ast.MapNodeIter) ast.Node) (map[string]ast.Node, error) {
mapNode, err := d.getMapNode(node)
if err != nil {
return nil, errors.Wrapf(err, "failed to get map node")
Expand All @@ -420,7 +422,7 @@ func (d *Decoder) keyToNodeMap(node ast.Node) (map[string]ast.Node, error) {
for mapIter.Next() {
keyNode := mapIter.Key()
if keyNode.Type() == ast.MergeKeyType {
mergeMap, err := d.keyToNodeMap(mapIter.Value())
mergeMap, err := d.keyToNodeMap(mapIter.Value(), getKeyOrValueNode)
if err != nil {
return nil, errors.Wrapf(err, "failed to get keyToNodeMap by MergeKey node")
}
Expand All @@ -432,12 +434,28 @@ func (d *Decoder) keyToNodeMap(node ast.Node) (map[string]ast.Node, error) {
if !ok {
return nil, errors.Wrapf(err, "failed to decode map key")
}
keyToNodeMap[key] = mapIter.Value()
keyToNodeMap[key] = getKeyOrValueNode(mapIter)
}
}
return keyToNodeMap, nil
}

func (d *Decoder) keyToKeyNodeMap(node ast.Node) (map[string]ast.Node, error) {
m, err := d.keyToNodeMap(node, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Key() })
if err != nil {
return nil, errors.Wrapf(err, "failed to get keyToNodeMap")
}
return m, nil
}

func (d *Decoder) keyToValueNodeMap(node ast.Node) (map[string]ast.Node, error) {
m, err := d.keyToNodeMap(node, func(nodeMap *ast.MapNodeIter) ast.Node { return nodeMap.Value() })
if err != nil {
return nil, errors.Wrapf(err, "failed to get keyToNodeMap")
}
return m, nil
}

func (d *Decoder) setDefaultValueIfConflicted(v reflect.Value, fieldMap StructFieldMap) error {
typ := v.Type()
if typ.Kind() != reflect.Struct {
Expand Down Expand Up @@ -516,9 +534,16 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
if err != nil {
return errors.Wrapf(err, "failed to create struct field map")
}
keyToNodeMap, err := d.keyToNodeMap(src)
keyToNodeMap, err := d.keyToValueNodeMap(src)
if err != nil {
return errors.Wrapf(err, "failed to get keyToNodeMap")
return errors.Wrapf(err, "failed to get keyToValueNodeMap")
}
var unknownFields map[string]ast.Node
if d.disallowUnknownField {
unknownFields, err = d.keyToKeyNodeMap(src)
if err != nil {
return errors.Wrapf(err, "failed to get keyToKeyNodeMap")
}
}

var foundErr error
Expand Down Expand Up @@ -567,6 +592,7 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
if !exists {
continue
}
delete(unknownFields, structField.RenderName)
fieldValue := structValue.Elem().FieldByName(field.Name)
if fieldValue.Type().Kind() == reflect.Ptr && src.Type() == ast.NullType {
// set nil value to pointer
Expand Down Expand Up @@ -610,6 +636,11 @@ func (d *Decoder) decodeStruct(dst reflect.Value, src ast.Node) error {
}
}
}
if len(unknownFields) != 0 && d.disallowUnknownField {
for key, node := range unknownFields {
return errors.ErrSyntax(fmt.Sprintf(`unknown field "%s"`, key), node.GetToken())
}
}
dst.Set(structValue.Elem())
if foundErr != nil {
return errors.Wrapf(foundErr, "failed to decode value")
Expand Down
21 changes: 21 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1233,3 +1233,24 @@ bar: c
// 1
// c
}

func Example_DisallowUnknownField() {
var v struct {
A string `yaml:"simple"`
C string `yaml:"complicated"`
}

const src = `---
simple: string
complecated: string
`
err := yaml.NewDecoder(strings.NewReader(src), yaml.DisallowUnknownField()).Decode(&v)
fmt.Printf("%v\n", err)

// OUTPUT:
// [3:1] unknown field "complecated"
// 1 | ---
// 2 | simple: string
// > 3 | complecated: string
// ^
}
10 changes: 10 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ func Validator(v StructValidator) DecodeOption {
}
}

// DisallowUnknownField causes the Decoder to return an error when the destination
// is a struct and the input contains object keys which do not match any
// non-ignored, exported fields in the destination.
func DisallowUnknownField() DecodeOption {
return func(d *Decoder) error {
d.disallowUnknownField = true
return nil
}
}

// EncodeOption functional option type for Encoder
type EncodeOption func(e *Encoder) error

Expand Down

0 comments on commit d65a7f6

Please sign in to comment.