feat: better yaml parsing and error handling (#1619)

This commit is contained in:
Pete Davison
2024-05-16 02:24:02 +01:00
committed by GitHub
parent 635e3f4e7d
commit 8d138a5eea
21 changed files with 299 additions and 77 deletions

View File

@@ -1,10 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
)
@@ -46,7 +45,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var cmd string
if err := node.Decode(&cmd); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
c.Cmd = cmd
return nil
@@ -110,8 +109,8 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: invalid keys in command", node.Line)
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in command")
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into command", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("command")
}

View File

@@ -1,9 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
// Dep is a task dependency
@@ -32,7 +32,7 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var task string
if err := node.Decode(&task); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
d.Task = task
return nil
@@ -45,7 +45,7 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
Silent bool
}
if err := node.Decode(&taskCall); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
d.Task = taskCall.Task
d.For = taskCall.For
@@ -54,5 +54,5 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("cannot unmarshal %s into dependency", node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("dependency")
}

View File

@@ -1,10 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
)
@@ -22,7 +21,7 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var from string
if err := node.Decode(&from); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
f.From = from
return nil
@@ -30,7 +29,7 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
case yaml.SequenceNode:
var list []any
if err := node.Decode(&list); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
f.List = list
return nil
@@ -41,17 +40,19 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
Split string
As string
}
if err := node.Decode(&forStruct); err == nil && forStruct.Var != "" {
f.Var = forStruct.Var
f.Split = forStruct.Split
f.As = forStruct.As
return nil
if err := node.Decode(&forStruct); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}
return fmt.Errorf("yaml: line %d: invalid keys in for", node.Line)
if forStruct.Var == "" {
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in for")
}
f.Var = forStruct.Var
f.Split = forStruct.Split
f.As = forStruct.As
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into for", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("for")
}
func (f *For) DeepCopy() *For {

View File

@@ -1,9 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
type Glob struct {
@@ -13,20 +13,22 @@ type Glob struct {
func (g *Glob) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
g.Glob = node.Value
return nil
case yaml.MappingNode:
var glob struct {
Exclude string
}
if err := node.Decode(&glob); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
g.Glob = glob.Exclude
g.Negate = true
return nil
default:
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
}
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("glob")
}

View File

@@ -1,10 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
omap "github.com/go-task/task/v3/internal/omap"
)
@@ -38,7 +37,7 @@ func (includes *Includes) UnmarshalYAML(node *yaml.Node) error {
var v Include
if err := valueNode.Decode(&v); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Namespace = keyNode.Value
includes.Set(keyNode.Value, &v)
@@ -46,7 +45,7 @@ func (includes *Includes) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfiles", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("includes")
}
// Len returns the length of the map
@@ -71,7 +70,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var str string
if err := node.Decode(&str); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
include.Taskfile = str
return nil
@@ -86,7 +85,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
Vars *Vars
}
if err := node.Decode(&includedTaskfile); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
include.Taskfile = includedTaskfile.Taskfile
include.Dir = includedTaskfile.Dir
@@ -98,7 +97,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfile", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("include")
}
// DeepCopy creates a new instance of IncludedTaskfile and copies

View File

@@ -1,9 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
// Output of the Task output
@@ -25,7 +25,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var name string
if err := node.Decode(&name); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
s.Name = name
return nil
@@ -35,10 +35,10 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
Group *OutputGroup
}
if err := node.Decode(&tmp); err != nil {
return fmt.Errorf("task: output style must be a string or mapping with a \"group\" key: %w", err)
return errors.NewTaskfileDecodeError(err, node)
}
if tmp.Group == nil {
return fmt.Errorf("task: output style must have the \"group\" key when in mapping form")
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`output style must have the "group" key when in mapping form`)
}
*s = Output{
Name: "group",
@@ -47,7 +47,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into output", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("output")
}
// OutputGroup is the style options specific to the Group style.

View File

@@ -6,6 +6,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/goext"
)
@@ -30,7 +31,7 @@ type ErrInvalidPlatform struct {
}
func (err *ErrInvalidPlatform) Error() string {
return fmt.Sprintf(`task: Invalid platform "%s"`, err.Platform)
return fmt.Sprintf(`invalid platform "%s"`, err.Platform)
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
@@ -39,14 +40,14 @@ func (p *Platform) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var platform string
if err := node.Decode(&platform); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
if err := p.parsePlatform(platform); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into platform", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("platform")
}
// parsePlatform takes a string representing an OS/Arch combination (or either on their own)

View File

@@ -26,10 +26,10 @@ func TestPlatformParsing(t *testing.T) {
{Input: "windows/amd64", ExpectedOS: "windows", ExpectedArch: "amd64"},
{Input: "windows/arm64", ExpectedOS: "windows", ExpectedArch: "arm64"},
{Input: "invalid", Error: `task: Invalid platform "invalid"`},
{Input: "invalid/invalid", Error: `task: Invalid platform "invalid/invalid"`},
{Input: "windows/invalid", Error: `task: Invalid platform "windows/invalid"`},
{Input: "invalid/amd64", Error: `task: Invalid platform "invalid/amd64"`},
{Input: "invalid", Error: `invalid platform "invalid"`},
{Input: "invalid/invalid", Error: `invalid platform "invalid/invalid"`},
{Input: "windows/invalid", Error: `invalid platform "windows/invalid"`},
{Input: "invalid/amd64", Error: `invalid platform "invalid/amd64"`},
}
for _, test := range tests {

View File

@@ -1,14 +1,12 @@
package ast
import (
"errors"
"fmt"
"gopkg.in/yaml.v3"
)
// ErrCantUnmarshalPrecondition is returned for invalid precond YAML.
var ErrCantUnmarshalPrecondition = errors.New("task: Can't unmarshal precondition value")
"github.com/go-task/task/v3/errors"
)
// Precondition represents a precondition necessary for a task to run
type Precondition struct {
@@ -33,7 +31,7 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var cmd string
if err := node.Decode(&cmd); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
p.Sh = cmd
p.Msg = fmt.Sprintf("`%s` failed", cmd)
@@ -45,7 +43,7 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
Msg string
}
if err := node.Decode(&sh); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
p.Sh = sh.Sh
p.Msg = sh.Msg
@@ -55,5 +53,5 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into precondition", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("precondition")
}

View File

@@ -7,6 +7,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
)
@@ -83,7 +84,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var cmd Cmd
if err := node.Decode(&cmd); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
t.Cmds = append(t.Cmds, &cmd)
return nil
@@ -92,7 +93,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
case yaml.SequenceNode:
var cmds []*Cmd
if err := node.Decode(&cmds); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
t.Cmds = cmds
return nil
@@ -130,11 +131,11 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
Watch bool
}
if err := node.Decode(&task); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
if task.Cmd != nil {
if task.Cmds != nil {
return fmt.Errorf("yaml: line %d: task cannot have both cmd and cmds", node.Line)
return errors.NewTaskfileDecodeError(nil, node).WithMessage("task cannot have both cmd and cmds")
}
t.Cmds = []*Cmd{task.Cmd}
} else {
@@ -169,7 +170,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("task")
}
// DeepCopy creates a new instance of Task and copies

View File

@@ -1,12 +1,13 @@
package ast
import (
"errors"
"fmt"
"time"
"github.com/Masterminds/semver/v3"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
// NamespaceSeparator contains the character that separates namespaces
@@ -77,7 +78,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
Interval time.Duration
}
if err := node.Decode(&taskfile); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
tf.Version = taskfile.Version
tf.Output = taskfile.Output
@@ -101,5 +102,5 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into taskfile", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("taskfile")
}

View File

@@ -6,6 +6,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/omap"
)
@@ -118,7 +119,7 @@ func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
case yaml.MappingNode:
tasks := omap.New[string, *Task]()
if err := node.Decode(&tasks); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
// nolint: errcheck
@@ -150,7 +151,7 @@ func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into tasks", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("tasks")
}
func taskNameWithNamespace(taskName string, namespace string) string {

View File

@@ -1,11 +1,11 @@
package ast
import (
"fmt"
"strings"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/experiments"
"github.com/go-task/task/v3/internal/omap"
)
@@ -95,7 +95,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
if experiments.MapVariables.Value == "1" {
var value any
if err := node.Decode(&value); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
// If the value is a string and it starts with $, then it's a shell command
if str, ok := value.(string); ok {
@@ -123,7 +123,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
Yaml string
}
if err := node.Decode(&m); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Sh = m.Sh
v.Ref = m.Ref
@@ -132,12 +132,12 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
v.Yaml = m.Yaml
return nil
default:
return fmt.Errorf(`yaml: line %d: %q is not a valid variable type. Try "sh", "ref", "map", "json", "yaml" or using a scalar value`, node.Line, key)
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map", "json", "yaml" or using a scalar value`, key)
}
default:
var value any
if err := node.Decode(&value); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Value = value
return nil
@@ -149,13 +149,13 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
case yaml.MappingNode:
if len(node.Content) > 2 || node.Content[0].Value != "sh" {
return fmt.Errorf(`task: line %d: maps cannot be assigned to variables`, node.Line)
return errors.NewTaskfileDecodeError(nil, node).WithMessage("maps cannot be assigned to variables")
}
var sh struct {
Sh string
}
if err := node.Decode(&sh); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Sh = sh.Sh
return nil
@@ -163,7 +163,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
default:
var value any
if err := node.Decode(&value); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Value = value
return nil

View File

@@ -265,6 +265,11 @@ func (r *Reader) readNode(node Node) (*ast.Taskfile, error) {
var tf ast.Taskfile
if err := yaml.Unmarshal(b, &tf); err != nil {
// Decode the taskfile and add the file info the any errors
taskfileInvalidErr := &errors.TaskfileDecodeError{}
if errors.As(err, &taskfileInvalidErr) {
return nil, taskfileInvalidErr.WithFileInfo(node.Location(), b, 2)
}
return nil, &errors.TaskfileInvalidError{URI: filepathext.TryAbsToRel(node.Location()), Err: err}
}