Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 44 additions & 32 deletions cmd/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,72 +111,84 @@ func LintCmd() *cobra.Command {
// printLintErrors prints the recipe errors
func printLintErrors(errs []error, rcp recipe.Recipe) {
var (
notFoundError plugins.NotFoundError
invalidConfigError plugins.InvalidConfigError
notFoundErr plugins.NotFoundError
invalidCfgErr plugins.InvalidConfigError
)

for _, err := range errs {
if errors.As(err, &notFoundError) {
printPluginErrors(rcp, notFoundError)
if errors.As(err, &notFoundErr) {
printPluginErrors(rcp, notFoundErr)
continue
}
if errors.As(err, &invalidConfigError) {
printConfigErrors(rcp, invalidConfigError)
if errors.As(err, &invalidCfgErr) {
printConfigErrors(rcp, invalidCfgErr)
continue
}
fmt.Printf("recipe error: %s\n", err.Error())
fmt.Printf("%s: recipe error: %s\n", rcp.Name, err.Error())
}
}

// printPluginErrors print the plugin's type error
func printPluginErrors(rcp recipe.Recipe, notFoundError plugins.NotFoundError) {
if notFoundError.Type == plugins.PluginTypeExtractor {
printPluginError(rcp, rcp.Source, notFoundError)
} else if notFoundError.Type == plugins.PluginTypeProcessor {
plugin, exists := findPluginByName(rcp.Processors, notFoundError.Name)
func printPluginErrors(rcp recipe.Recipe, err plugins.NotFoundError) {
switch err.Type {
case plugins.PluginTypeExtractor:
printPluginError(rcp, rcp.Source, err)

case plugins.PluginTypeProcessor:
plugin, exists := findPluginByName(rcp.Processors, err.Name)
if exists {
printPluginError(rcp, plugin, notFoundError)
printPluginError(rcp, plugin, err)
}
} else if notFoundError.Type == plugins.PluginTypeSink {
plugin, exists := findPluginByName(rcp.Sinks, notFoundError.Name)

case plugins.PluginTypeSink:
plugin, exists := findPluginByName(rcp.Sinks, err.Name)
if exists {
printPluginError(rcp, plugin, notFoundError)
printPluginError(rcp, plugin, err)
}
}
}

// printPluginError prints the plugin type error
func printPluginError(rcp recipe.Recipe, plugin recipe.PluginRecipe, notFoundError plugins.NotFoundError) {
func printPluginError(rcp recipe.Recipe, plugin recipe.PluginRecipe, err plugins.NotFoundError) {
line := plugin.Node.Name.Line
fmt.Printf("%s: invalid %s on line: %d\n", rcp.Name, notFoundError.Type, line)
fmt.Printf("%s: invalid '%s' %s on line: %d\n", rcp.Name, err.Name, err.Type, line)
}

// printConfigErrors print the plugin's config error
func printConfigErrors(rcp recipe.Recipe, invalidConfigError plugins.InvalidConfigError) {
if invalidConfigError.Type == plugins.PluginTypeExtractor {
printConfigError(rcp, rcp.Source.Node, invalidConfigError)
} else if invalidConfigError.Type == plugins.PluginTypeProcessor {
plugin, exists := findPluginByName(rcp.Processors, invalidConfigError.PluginName)
func printConfigErrors(rcp recipe.Recipe, err plugins.InvalidConfigError) {
switch err.Type {
case plugins.PluginTypeExtractor:
printConfigError(rcp, rcp.Source.Node, err)

case plugins.PluginTypeProcessor:
plugin, exists := findPluginByName(rcp.Processors, err.PluginName)
if exists {
printConfigError(rcp, plugin.Node, invalidConfigError)
printConfigError(rcp, plugin.Node, err)
}
} else if invalidConfigError.Type == plugins.PluginTypeSink {
plugin, exists := findPluginByName(rcp.Sinks, invalidConfigError.PluginName)

case plugins.PluginTypeSink:
plugin, exists := findPluginByName(rcp.Sinks, err.PluginName)
if exists {
printConfigError(rcp, plugin.Node, invalidConfigError)
printConfigError(rcp, plugin.Node, err)
}
}
}

// printConfigError prints the config error in plugin by searching key
func printConfigError(rcp recipe.Recipe, pluginNode recipe.PluginNode, invalidConfigError plugins.InvalidConfigError) {
for _, configError := range invalidConfigError.Errors {
cfg, ok := pluginNode.Config[configError.Key]
func printConfigError(rcp recipe.Recipe, pluginNode recipe.PluginNode, err plugins.InvalidConfigError) {
for _, cfgErr := range err.Errors {
cfg, ok := pluginNode.Config[cfgErr.Key]
if ok {
line := cfg.Line
fmt.Printf("%s: invalid %s %s config on line: %d\n", rcp.Name, invalidConfigError.PluginName, invalidConfigError.Type, line)
fmt.Printf(
"%s: invalid %s %s config on line: %d: %s\n",
rcp.Name, err.PluginName, err.Type, line, cfgErr.Message,
)
} else {
fmt.Printf("%s: invalid %s %s config: %s\n", rcp.Name, invalidConfigError.PluginName, invalidConfigError.Type, configError.Message)
fmt.Printf(
"%s: invalid %s %s config: %s\n",
rcp.Name, err.PluginName, err.Type, cfgErr.Message,
)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func RunCmd() *cobra.Command {
lg.Debug("recipe details", "recipe", run.Recipe)
var row []string
if run.Error != nil {
lg.Error(run.Error.Error(), "recipe")
lg.Error(run.Error.Error(), "recipe", run.Recipe.Name)
failures++
row = append(row, cs.FailureIcon(), run.Recipe.Name, cs.Grey(run.Recipe.Source.Name), cs.Greyf("%v ms", strconv.Itoa(run.DurationInMs)), cs.Greyf(strconv.Itoa(run.RecordCount)))
} else {
Expand Down
13 changes: 11 additions & 2 deletions plugins/base_plugin_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build plugins
// +build plugins

package plugins_test

import (
Expand Down Expand Up @@ -49,7 +52,8 @@ func TestBasePluginValidate(t *testing.T) {

t.Run("should return InvalidConfigError if config is invalid", func(t *testing.T) {
invalidConfig := struct {
FieldA string `validate:"required"`
FieldA string `mapstructure:"field_a" validate:"required"`
FieldB string `mapstructure:"field_b" validate:"url"`
}{}

basePlugin := plugins.NewBasePlugin(plugins.Info{}, &invalidConfig)
Expand All @@ -58,7 +62,12 @@ func TestBasePluginValidate(t *testing.T) {
RawConfig: map[string]interface{}{},
})

assert.ErrorAs(t, err, &plugins.InvalidConfigError{})
assert.Equal(t, err, plugins.InvalidConfigError{
Errors: []plugins.ConfigError{
{Key: "field_a", Message: "validation for field 'field_a' failed on the 'required' tag"},
{Key: "field_b", Message: "validation for field 'field_b' failed on the 'url' tag"},
},
})
})

t.Run("should return no error if config is valid", func(t *testing.T) {
Expand Down
20 changes: 16 additions & 4 deletions plugins/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ package plugins
import (
"errors"
"fmt"
"strings"
)

var (
ErrEmptyURNScope = errors.New("urn scope is required to generate unique urn")
)
var ErrEmptyURNScope = errors.New("urn scope is required to generate unique urn")

// ConfigError contains fields to check error
type ConfigError struct {
Expand All @@ -23,7 +22,20 @@ type InvalidConfigError struct {
}

func (err InvalidConfigError) Error() string {
return fmt.Sprintf("invalid %s config", err.Type)
ss := make([]string, 0, len(err.Errors))
for _, e := range err.Errors {
ss = append(ss, e.Message)
}

var details string
if len(ss) != 0 {
details = ":\n\t * " + strings.Join(ss, "\n\t * ")
}

if err.Type == "" {
return "invalid config" + details
}
return fmt.Sprintf("invalid %s config", err.Type) + details
}

func (err InvalidConfigError) HasError() bool {
Expand Down
60 changes: 60 additions & 0 deletions plugins/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//go:build plugins
// +build plugins

package plugins

import (
"testing"

"github.com/MakeNowJust/heredoc"
"github.com/stretchr/testify/assert"
)

func TestInvalidConfigError(t *testing.T) {
cases := []struct {
name string
err InvalidConfigError
expected string
}{
{
name: "WithType",
err: InvalidConfigError{
Type: "extractor",
PluginName: "caramlstore",
Errors: []ConfigError{
{Key: "engine", Message: "validation for field 'engine' failed on the 'oneof' tag"},
{Key: "script", Message: "validation for field 'script' failed on the 'required' tag"},
},
},
expected: heredoc.Doc(`
invalid extractor config:
* validation for field 'engine' failed on the 'oneof' tag
* validation for field 'script' failed on the 'required' tag`),
},
{
name: "WithoutType",
err: InvalidConfigError{
PluginName: "caramlstore",
Errors: []ConfigError{
{Key: "engine", Message: "validation for field 'engine' failed on the 'oneof' tag"},
},
},
expected: heredoc.Doc(`
invalid config:
* validation for field 'engine' failed on the 'oneof' tag`),
},
{
name: "WithoutErrors",
err: InvalidConfigError{
Type: "extractor",
PluginName: "caramlstore",
},
expected: "invalid extractor config",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.err.Error())
})
}
}
5 changes: 2 additions & 3 deletions plugins/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@ func buildConfig(configMap map[string]interface{}, c interface{}) (err error) {
if errors.As(err, &validationErr) {
var configErrors []ConfigError
for _, fieldErr := range validationErr {
key := strings.TrimPrefix(fieldErr.Namespace(), "Config.")
configErrors = append(configErrors, ConfigError{
Key: key,
Message: fieldErr.Error(),
Key: fieldErr.Field(),
Message: fmt.Sprintf("validation for field '%s' failed on the '%s' tag", fieldErr.Field(), fieldErr.Tag()),
})
}
return InvalidConfigError{
Expand Down