2024-05-16 02:24:02 +01:00
|
|
|
package errors
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2025-02-22 15:44:22 +00:00
|
|
|
"cmp"
|
2024-05-16 02:24:02 +01:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/fatih/color"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
)
|
|
|
|
|
|
|
|
var typeErrorRegex = regexp.MustCompile(`line \d+: (.*)`)
|
|
|
|
|
|
|
|
type (
|
|
|
|
TaskfileDecodeError struct {
|
|
|
|
Message string
|
|
|
|
Location string
|
|
|
|
Line int
|
|
|
|
Column int
|
|
|
|
Tag string
|
2025-02-22 15:44:22 +00:00
|
|
|
Snippet string
|
2024-05-16 02:24:02 +01:00
|
|
|
Err error
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func NewTaskfileDecodeError(err error, node *yaml.Node) *TaskfileDecodeError {
|
|
|
|
// If the error is already a DecodeError, return it
|
|
|
|
taskfileInvalidErr := &TaskfileDecodeError{}
|
|
|
|
if errors.As(err, &taskfileInvalidErr) {
|
|
|
|
return taskfileInvalidErr
|
|
|
|
}
|
|
|
|
return &TaskfileDecodeError{
|
|
|
|
Line: node.Line,
|
|
|
|
Column: node.Column,
|
|
|
|
Tag: node.ShortTag(),
|
|
|
|
Err: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err *TaskfileDecodeError) Error() string {
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
|
|
|
|
// Print the error message
|
|
|
|
if err.Message != "" {
|
|
|
|
fmt.Fprintln(buf, color.RedString("err: %s", err.Message))
|
|
|
|
} else {
|
|
|
|
// Extract the errors from the TypeError
|
|
|
|
te := &yaml.TypeError{}
|
|
|
|
if errors.As(err.Err, &te) {
|
|
|
|
if len(te.Errors) > 1 {
|
|
|
|
fmt.Fprintln(buf, color.RedString("errs:"))
|
|
|
|
for _, message := range te.Errors {
|
|
|
|
fmt.Fprintln(buf, color.RedString("- %s", extractTypeErrorMessage(message)))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fmt.Fprintln(buf, color.RedString("err: %s", extractTypeErrorMessage(te.Errors[0])))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Otherwise print the error message normally
|
|
|
|
fmt.Fprintln(buf, color.RedString("err: %s", err.Err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintln(buf, color.RedString("file: %s:%d:%d", err.Location, err.Line, err.Column))
|
2025-02-22 15:44:22 +00:00
|
|
|
fmt.Fprint(buf, err.Snippet)
|
|
|
|
return buf.String()
|
|
|
|
}
|
2024-05-16 02:24:02 +01:00
|
|
|
|
2025-02-22 15:44:22 +00:00
|
|
|
func (err *TaskfileDecodeError) Debug() string {
|
|
|
|
const indentWidth = 2
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
fmt.Fprintln(buf, "TaskfileDecodeError:")
|
2024-05-16 02:24:02 +01:00
|
|
|
|
2025-02-22 15:44:22 +00:00
|
|
|
// Recursively loop through the error chain and print any details
|
|
|
|
var debug func(error, int)
|
|
|
|
debug = func(err error, indent int) {
|
|
|
|
indentStr := strings.Repeat(" ", indent*indentWidth)
|
|
|
|
|
|
|
|
// Nothing left to unwrap
|
|
|
|
if err == nil {
|
|
|
|
fmt.Fprintf(buf, "%sEnd of chain\n", indentStr)
|
|
|
|
return
|
2024-05-16 02:24:02 +01:00
|
|
|
}
|
|
|
|
|
2025-02-22 15:44:22 +00:00
|
|
|
// Taskfile decode error
|
|
|
|
decodeErr := &TaskfileDecodeError{}
|
|
|
|
if errors.As(err, &decodeErr) {
|
|
|
|
fmt.Fprintf(buf, "%s%s (%s:%d:%d)\n",
|
|
|
|
indentStr,
|
|
|
|
cmp.Or(decodeErr.Message, "<no_message>"),
|
|
|
|
decodeErr.Location,
|
|
|
|
decodeErr.Line,
|
|
|
|
decodeErr.Column,
|
|
|
|
)
|
|
|
|
debug(errors.Unwrap(err), indent+1)
|
|
|
|
return
|
2024-05-16 02:24:02 +01:00
|
|
|
}
|
|
|
|
|
2025-02-22 15:44:22 +00:00
|
|
|
fmt.Fprintf(buf, "%s%s\n", indentStr, err)
|
|
|
|
debug(errors.Unwrap(err), indent+1)
|
|
|
|
}
|
|
|
|
debug(err, 0)
|
2024-05-16 02:24:02 +01:00
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err *TaskfileDecodeError) Unwrap() error {
|
|
|
|
return err.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err *TaskfileDecodeError) Code() int {
|
|
|
|
return CodeTaskfileDecode
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err *TaskfileDecodeError) WithMessage(format string, a ...any) *TaskfileDecodeError {
|
|
|
|
err.Message = fmt.Sprintf(format, a...)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err *TaskfileDecodeError) WithTypeMessage(t string) *TaskfileDecodeError {
|
|
|
|
err.Message = fmt.Sprintf("cannot unmarshal %s into %s", err.Tag, t)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2025-02-22 15:44:22 +00:00
|
|
|
func (err *TaskfileDecodeError) WithFileInfo(location string, snippet string) *TaskfileDecodeError {
|
2024-05-16 02:24:02 +01:00
|
|
|
err.Location = location
|
2025-02-22 15:44:22 +00:00
|
|
|
err.Snippet = snippet
|
2024-05-16 02:24:02 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func extractTypeErrorMessage(message string) string {
|
|
|
|
matches := typeErrorRegex.FindStringSubmatch(message)
|
|
|
|
if len(matches) == 2 {
|
|
|
|
return matches[1]
|
|
|
|
}
|
|
|
|
return message
|
|
|
|
}
|