Repository: goccy/go-yaml (go)

Issue:
FormatError does not work for unexpected nodes
```go
package main

import (
	"fmt"
	"os"

	"github.com/goccy/go-yaml"
)

type Document struct {
	Name  string
	Roles []Role
}

type Role struct {
	Name        string
	Permissions []string
}

var input = []byte(`
name: myDocument
roles:
  name: myRole
  permissions:
    - hello
    - how
    - are
    - you `)

func main() {
	var d Document
	err := yaml.Unmarshal(input, &d)
	if err != nil {
		fmt.Fprintln(os.Stderr, yaml.FormatError(err, true, true))
		os.Exit(2)
	}
	fmt.Printf("%v\n", d)
}
```

the following error is printed.

```
Mapping node found where ArrayNode is expected
exit status 2
```
This happens whenever the input contains some unexpected node type.
From what I was reading from the docs I would expect a nicely formated error pointing to the location of the unexpected node.
Am I missing something or is the pretty print feature just not (yet) supported for unexpected node types?

Pull request:
Nicely format unexpected node type errors
This fixes #182 by emitting a ErrSyntax error when the actual field type doesn't match the expected field type.  I've encountered a similar issue in another project and this change would enable my tool to provide cleaner error message as well.

Patch:
diff --git a/ast/ast.go b/ast/ast.go
index ed5366f..eee3a87 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -110,6 +110,54 @@ func (t NodeType) String() string {
 	return ""
 }
 
+// String node type identifier to YAML Structure name
+// based on https://yaml.org/spec/1.2/spec.html
+func (t NodeType) YAMLName() string {
+	switch t {
+	case UnknownNodeType:
+		return "unknown"
+	case DocumentType:
+		return "document"
+	case NullType:
+		return "null"
+	case BoolType:
+		return "boolean"
+	case IntegerType:
+		return "int"
+	case FloatType:
+		return "float"
+	case InfinityType:
+		return "inf"
+	case NanType:
+		return "nan"
+	case StringType:
+		return "string"
+	case MergeKeyType:
+		return "merge key"
+	case LiteralType:
+		return "scalar"
+	case MappingType:
+		return "mapping"
+	case MappingKeyType:
+		return "key"
+	case MappingValueType:
+		return "value"
+	case SequenceType:
+		return "sequence"
+	case AnchorType:
+		return "anchor"
+	case AliasType:
+		return "alias"
+	case DirectiveType:
+		return "directive"
+	case TagType:
+		return "tag"
+	case CommentType:
+		return "comment"
+	}
+	return ""
+}
+
 // Node type of node
 type Node interface {
 	io.Reader
diff --git a/decode.go b/decode.go
index 40e3116..f3e583a 100644
--- a/decode.go
+++ b/decode.go
@@ -281,7 +281,7 @@ func (d *Decoder) getMapNode(node ast.Node) (ast.MapNode, error) {
 		if ok {
 			return mapNode, nil
 		}
-		return nil, xerrors.Errorf("%s node found where MapNode is expected", anchor.Value.Type())
+		return nil, errUnexpectedNodeType(anchor.Value.Type(), ast.MappingType, node.GetToken())
 	}
 	if alias, ok := node.(*ast.AliasNode); ok {
 		aliasName := alias.Value.GetToken().Value
@@ -293,11 +293,11 @@ func (d *Decoder) getMapNode(node ast.Node) (ast.MapNode, error) {
 		if ok {
 			return mapNode, nil
 		}
-		return nil, xerrors.Errorf("%s node found where MapNode is expected", node.Type())
+		return nil, errUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken())
 	}
 	mapNode, ok := node.(ast.MapNode)
 	if !ok {
-		return nil, xerrors.Errorf("%s node found where MapNode is expected", node.Type())
+		return nil, errUnexpectedNodeType(node.Type(), ast.MappingType, node.GetToken())
 	}
 	return mapNode, nil
 }
@@ -311,7 +311,8 @@ func (d *Decoder) getArrayNode(node ast.Node) (ast.ArrayNode, error) {
 		if ok {
 			return arrayNode, nil
 		}
-		return nil, xerrors.Errorf("%s node found where ArrayNode is expected", anchor.Value.Type())
+
+		return nil, errUnexpectedNodeType(anchor.Value.Type(), ast.SequenceType, node.GetToken())
 	}
 	if alias, ok := node.(*ast.AliasNode); ok {
 		aliasName := alias.Value.GetToken().Value
@@ -323,11 +324,11 @@ func (d *Decoder) getArrayNode(node ast.Node) (ast.ArrayNode, error) {
 		if ok {
 			return arrayNode, nil
 		}
-		return nil, xerrors.Errorf("%s node found where ArrayNode is expected", node.Type())
+		return nil, errUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken())
 	}
 	arrayNode, ok := node.(ast.ArrayNode)
 	if !ok {
-		return nil, xerrors.Errorf("%s node found where ArrayNode is expected", node.Type())
+		return nil, errUnexpectedNodeType(node.Type(), ast.SequenceType, node.GetToken())
 	}
 	return arrayNode, nil
 }
@@ -407,6 +408,10 @@ func errUnknownField(msg string, tk *token.Token) *unknownFieldError {
 	return &unknownFieldError{err: errors.ErrSyntax(msg, tk)}
 }
 
+func errUnexpectedNodeType(actual, expected ast.NodeType, tk *token.Token) error {
+	return errors.ErrSyntax(fmt.Sprintf("%s was used where %s is expected", actual.YAMLName(), expected.YAMLName()), tk)
+}
+
 type duplicateKeyError struct {
 	err error
 }


Test patch:
diff --git a/validate_test.go b/validate_test.go
index 3727dfc..b20679e 100644
--- a/validate_test.go
+++ b/validate_test.go
@@ -105,6 +105,36 @@ addr:
 				} `yaml:"addr" validate:"required"`
 			}{},
 		},
+		{
+			TestName: "Test Validation with wrong field type",
+			YAMLContent: `---
+name: myDocument
+roles:
+  name: myRole
+  permissions:
+	- hello
+	- how
+	- are
+	- you
+	`,
+			ExpectedErr: `[4:7] mapping was used where sequence is expected
+   1 | ---
+   2 | name: myDocument
+   3 | roles:
+>  4 |   name: myRole
+             ^
+   5 |   permissions:
+   6 | 	- hello
+   7 | 	- how
+   8 | `,
+			Instance: &struct {
+				Name  string `yaml:"name"`
+				Roles []struct {
+					Name        string   `yaml:"name"`
+					Permissions []string `yaml:"permissions"`
+				} `yaml:"roles"`
+			}{},
+		},
 	}
 
 	for _, tc := range cases {