mirror of https://github.com/databricks/cli.git
Support multiple locations for diagnostics (#1610)
## Changes This PR changes `diag.Diagnostics` to allow including multiple locations associated with the diagnostic message. The diagnostics that now return multiple locations with this PR are: 1. Warning for unknown keys in config. 2. Use of experimental.run_as 3. Accidental sync.exludes that exclude all files. ## Tests Existing unit tests pass. New unit test case to assert on error message when multiple locations are included. Example output: ``` ➜ bundle-playground-2 ~/cli2/cli/cli bundle validate Warning: You are using the legacy mode of run_as. The support for this mode is experimental and might be removed in a future release of the CLI. In order to run the DLT pipelines in your DAB as the run_as user this mode changes the owners of the pipelines to the run_as identity, which requires the user deploying the bundle to be a workspace admin, and also a Metastore admin if the pipeline target is in UC. at experimental.use_legacy_run_as in resources.yml:10:22 databricks.yml:13:22 Name: fix run_if Target: default Workspace: User: shreyas.goenka@databricks.com Path: /Users/shreyas.goenka@databricks.com/.bundle/fix run_if/default Found 1 warning ```
This commit is contained in:
parent
52ca599cd5
commit
4bf88b4209
|
@ -55,12 +55,18 @@ func parsePythonDiagnostics(input io.Reader) (diag.Diagnostics, error) {
|
|||
return nil, fmt.Errorf("failed to parse path: %s", err)
|
||||
}
|
||||
|
||||
var locations []dyn.Location
|
||||
location := convertPythonLocation(parsedLine.Location)
|
||||
if location != (dyn.Location{}) {
|
||||
locations = append(locations, location)
|
||||
}
|
||||
|
||||
diag := diag.Diagnostic{
|
||||
Severity: severity,
|
||||
Summary: parsedLine.Summary,
|
||||
Detail: parsedLine.Detail,
|
||||
Location: convertPythonLocation(parsedLine.Location),
|
||||
Path: path,
|
||||
Severity: severity,
|
||||
Summary: parsedLine.Summary,
|
||||
Detail: parsedLine.Detail,
|
||||
Locations: locations,
|
||||
Path: path,
|
||||
}
|
||||
|
||||
diags = diags.Append(diag)
|
||||
|
|
|
@ -39,10 +39,12 @@ func TestParsePythonDiagnostics(t *testing.T) {
|
|||
{
|
||||
Severity: diag.Error,
|
||||
Summary: "error summary",
|
||||
Location: dyn.Location{
|
||||
File: "src/examples/file.py",
|
||||
Line: 1,
|
||||
Column: 2,
|
||||
Locations: []dyn.Location{
|
||||
{
|
||||
File: "src/examples/file.py",
|
||||
Line: 1,
|
||||
Column: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -97,11 +97,14 @@ func TestPythonMutator_load(t *testing.T) {
|
|||
|
||||
assert.Equal(t, 1, len(diags))
|
||||
assert.Equal(t, "job doesn't have any tasks", diags[0].Summary)
|
||||
assert.Equal(t, dyn.Location{
|
||||
File: "src/examples/file.py",
|
||||
Line: 10,
|
||||
Column: 5,
|
||||
}, diags[0].Location)
|
||||
assert.Equal(t, []dyn.Location{
|
||||
{
|
||||
File: "src/examples/file.py",
|
||||
Line: 10,
|
||||
Column: 5,
|
||||
},
|
||||
}, diags[0].Locations)
|
||||
|
||||
}
|
||||
|
||||
func TestPythonMutator_load_disallowed(t *testing.T) {
|
||||
|
|
|
@ -178,10 +178,10 @@ func (m *setRunAs) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics {
|
|||
setRunAsForJobs(b)
|
||||
return diag.Diagnostics{
|
||||
{
|
||||
Severity: diag.Warning,
|
||||
Summary: "You are using the legacy mode of run_as. The support for this mode is experimental and might be removed in a future release of the CLI. In order to run the DLT pipelines in your DAB as the run_as user this mode changes the owners of the pipelines to the run_as identity, which requires the user deploying the bundle to be a workspace admin, and also a Metastore admin if the pipeline target is in UC.",
|
||||
Path: dyn.MustPathFromString("experimental.use_legacy_run_as"),
|
||||
Location: b.Config.GetLocation("experimental.use_legacy_run_as"),
|
||||
Severity: diag.Warning,
|
||||
Summary: "You are using the legacy mode of run_as. The support for this mode is experimental and might be removed in a future release of the CLI. In order to run the DLT pipelines in your DAB as the run_as user this mode changes the owners of the pipelines to the run_as identity, which requires the user deploying the bundle to be a workspace admin, and also a Metastore admin if the pipeline target is in UC.",
|
||||
Path: dyn.MustPathFromString("experimental.use_legacy_run_as"),
|
||||
Locations: b.Config.GetLocations("experimental.use_legacy_run_as"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -524,6 +524,17 @@ func (r Root) GetLocation(path string) dyn.Location {
|
|||
return v.Location()
|
||||
}
|
||||
|
||||
// Get all locations of the configuration value at the specified path. We need both
|
||||
// this function and it's singular version (GetLocation) because some diagnostics just need
|
||||
// the primary location and some need all locations associated with a configuration value.
|
||||
func (r Root) GetLocations(path string) []dyn.Location {
|
||||
v, err := dyn.Get(r.value, path)
|
||||
if err != nil {
|
||||
return []dyn.Location{}
|
||||
}
|
||||
return v.Locations()
|
||||
}
|
||||
|
||||
// Value returns the dynamic configuration value of the root object. This value
|
||||
// is the source of truth and is kept in sync with values in the typed configuration.
|
||||
func (r Root) Value() dyn.Value {
|
||||
|
|
|
@ -45,8 +45,10 @@ func (v *filesToSync) Apply(ctx context.Context, rb bundle.ReadOnlyBundle) diag.
|
|||
diags = diags.Append(diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: "There are no files to sync, please check your .gitignore and sync.exclude configuration",
|
||||
Location: loc.Location(),
|
||||
Path: loc.Path(),
|
||||
// Show all locations where sync.exclude is defined, since merging
|
||||
// sync.exclude is additive.
|
||||
Locations: loc.Locations(),
|
||||
Path: loc.Path(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/databricks/cli/bundle"
|
||||
"github.com/databricks/cli/libs/diag"
|
||||
"github.com/databricks/cli/libs/dyn"
|
||||
)
|
||||
|
||||
func JobClusterKeyDefined() bundle.ReadOnlyMutator {
|
||||
|
@ -41,8 +42,11 @@ func (v *jobClusterKeyDefined) Apply(ctx context.Context, rb bundle.ReadOnlyBund
|
|||
diags = diags.Append(diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("job_cluster_key %s is not defined", task.JobClusterKey),
|
||||
Location: loc.Location(),
|
||||
Path: loc.Path(),
|
||||
// Show only the location where the job_cluster_key is defined.
|
||||
// Other associated locations are not relevant since they are
|
||||
// overridden during merging.
|
||||
Locations: []dyn.Location{loc.Location()},
|
||||
Path: loc.Path(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@ func (l location) Location() dyn.Location {
|
|||
return l.rb.Config().GetLocation(l.path)
|
||||
}
|
||||
|
||||
func (l location) Locations() []dyn.Location {
|
||||
return l.rb.Config().GetLocations(l.path)
|
||||
}
|
||||
|
||||
func (l location) Path() dyn.Path {
|
||||
return dyn.MustPathFromString(l.path)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/databricks/cli/bundle"
|
||||
"github.com/databricks/cli/libs/diag"
|
||||
"github.com/databricks/cli/libs/dyn"
|
||||
"github.com/databricks/cli/libs/fileset"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
@ -64,10 +65,10 @@ func checkPatterns(patterns []string, path string, rb bundle.ReadOnlyBundle) (di
|
|||
loc := location{path: fmt.Sprintf("%s[%d]", path, index), rb: rb}
|
||||
mu.Lock()
|
||||
diags = diags.Append(diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("Pattern %s does not match any files", p),
|
||||
Location: loc.Location(),
|
||||
Path: loc.Path(),
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("Pattern %s does not match any files", p),
|
||||
Locations: []dyn.Location{loc.Location()},
|
||||
Path: loc.Path(),
|
||||
})
|
||||
mu.Unlock()
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ const errorTemplate = `{{ "Error" | red }}: {{ .Summary }}
|
|||
{{- if .Path.String }}
|
||||
{{ "at " }}{{ .Path.String | green }}
|
||||
{{- end }}
|
||||
{{- if .Location.File }}
|
||||
{{ "in " }}{{ .Location.String | cyan }}
|
||||
{{- range $index, $element := .Locations }}
|
||||
{{ if eq $index 0 }}in {{else}} {{ end}}{{ $element.String | cyan }}
|
||||
{{- end }}
|
||||
{{- if .Detail }}
|
||||
|
||||
|
@ -46,8 +46,8 @@ const warningTemplate = `{{ "Warning" | yellow }}: {{ .Summary }}
|
|||
{{- if .Path.String }}
|
||||
{{ "at " }}{{ .Path.String | green }}
|
||||
{{- end }}
|
||||
{{- if .Location.File }}
|
||||
{{ "in " }}{{ .Location.String | cyan }}
|
||||
{{- range $index, $element := .Locations }}
|
||||
{{ if eq $index 0 }}in {{else}} {{ end}}{{ $element.String | cyan }}
|
||||
{{- end }}
|
||||
{{- if .Detail }}
|
||||
|
||||
|
@ -141,12 +141,18 @@ func renderDiagnostics(out io.Writer, b *bundle.Bundle, diags diag.Diagnostics)
|
|||
t = warningT
|
||||
}
|
||||
|
||||
// Make file relative to bundle root
|
||||
if d.Location.File != "" && b != nil {
|
||||
out, err := filepath.Rel(b.RootPath, d.Location.File)
|
||||
// if we can't relativize the path, just use path as-is
|
||||
if err == nil {
|
||||
d.Location.File = out
|
||||
for i := range d.Locations {
|
||||
if b == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Make location relative to bundle root
|
||||
if d.Locations[i].File != "" {
|
||||
out, err := filepath.Rel(b.RootPath, d.Locations[i].File)
|
||||
// if we can't relativize the path, just use path as-is
|
||||
if err == nil {
|
||||
d.Locations[i].File = out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,34 +88,22 @@ func TestRenderTextOutput(t *testing.T) {
|
|||
bundle: loadingBundle,
|
||||
diags: diag.Diagnostics{
|
||||
diag.Diagnostic{
|
||||
Severity: diag.Error,
|
||||
Summary: "error (1)",
|
||||
Detail: "detail (1)",
|
||||
Location: dyn.Location{
|
||||
File: "foo.py",
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
},
|
||||
Severity: diag.Error,
|
||||
Summary: "error (1)",
|
||||
Detail: "detail (1)",
|
||||
Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}},
|
||||
},
|
||||
diag.Diagnostic{
|
||||
Severity: diag.Error,
|
||||
Summary: "error (2)",
|
||||
Detail: "detail (2)",
|
||||
Location: dyn.Location{
|
||||
File: "foo.py",
|
||||
Line: 2,
|
||||
Column: 1,
|
||||
},
|
||||
Severity: diag.Error,
|
||||
Summary: "error (2)",
|
||||
Detail: "detail (2)",
|
||||
Locations: []dyn.Location{{File: "foo.py", Line: 2, Column: 1}},
|
||||
},
|
||||
diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: "warning (3)",
|
||||
Detail: "detail (3)",
|
||||
Location: dyn.Location{
|
||||
File: "foo.py",
|
||||
Line: 3,
|
||||
Column: 1,
|
||||
},
|
||||
Severity: diag.Warning,
|
||||
Summary: "warning (3)",
|
||||
Detail: "detail (3)",
|
||||
Locations: []dyn.Location{{File: "foo.py", Line: 3, Column: 1}},
|
||||
},
|
||||
},
|
||||
opts: RenderOptions{RenderSummaryTable: true},
|
||||
|
@ -174,24 +162,16 @@ func TestRenderTextOutput(t *testing.T) {
|
|||
bundle: nil,
|
||||
diags: diag.Diagnostics{
|
||||
diag.Diagnostic{
|
||||
Severity: diag.Error,
|
||||
Summary: "error (1)",
|
||||
Detail: "detail (1)",
|
||||
Location: dyn.Location{
|
||||
File: "foo.py",
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
},
|
||||
Severity: diag.Error,
|
||||
Summary: "error (1)",
|
||||
Detail: "detail (1)",
|
||||
Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}},
|
||||
},
|
||||
diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: "warning (2)",
|
||||
Detail: "detail (2)",
|
||||
Location: dyn.Location{
|
||||
File: "foo.py",
|
||||
Line: 3,
|
||||
Column: 1,
|
||||
},
|
||||
Severity: diag.Warning,
|
||||
Summary: "warning (2)",
|
||||
Detail: "detail (2)",
|
||||
Locations: []dyn.Location{{File: "foo.py", Line: 3, Column: 1}},
|
||||
},
|
||||
},
|
||||
opts: RenderOptions{RenderSummaryTable: false},
|
||||
|
@ -252,17 +232,42 @@ func TestRenderDiagnostics(t *testing.T) {
|
|||
Severity: diag.Error,
|
||||
Summary: "failed to load xxx",
|
||||
Detail: "'name' is required",
|
||||
Location: dyn.Location{
|
||||
Locations: []dyn.Location{{
|
||||
File: "foo.yaml",
|
||||
Line: 1,
|
||||
Column: 2,
|
||||
},
|
||||
Column: 2}},
|
||||
},
|
||||
},
|
||||
expected: "Error: failed to load xxx\n" +
|
||||
" in foo.yaml:1:2\n\n" +
|
||||
"'name' is required\n\n",
|
||||
},
|
||||
{
|
||||
name: "error with multiple source locations",
|
||||
diags: diag.Diagnostics{
|
||||
{
|
||||
Severity: diag.Error,
|
||||
Summary: "failed to load xxx",
|
||||
Detail: "'name' is required",
|
||||
Locations: []dyn.Location{
|
||||
{
|
||||
File: "foo.yaml",
|
||||
Line: 1,
|
||||
Column: 2,
|
||||
},
|
||||
{
|
||||
File: "bar.yaml",
|
||||
Line: 3,
|
||||
Column: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "Error: failed to load xxx\n" +
|
||||
" in foo.yaml:1:2\n" +
|
||||
" bar.yaml:3:4\n\n" +
|
||||
"'name' is required\n\n",
|
||||
},
|
||||
{
|
||||
name: "error with path",
|
||||
diags: diag.Diagnostics{
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/databricks/cli/bundle"
|
||||
"github.com/databricks/cli/bundle/config/validate"
|
||||
"github.com/databricks/cli/libs/diag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -21,11 +22,13 @@ func TestSyncIncludeExcludeNoMatchesTest(t *testing.T) {
|
|||
|
||||
require.Equal(t, diags[0].Severity, diag.Warning)
|
||||
require.Equal(t, diags[0].Summary, "Pattern dist does not match any files")
|
||||
require.Equal(t, diags[0].Location.File, filepath.Join("sync", "override", "databricks.yml"))
|
||||
require.Equal(t, diags[0].Location.Line, 17)
|
||||
require.Equal(t, diags[0].Location.Column, 11)
|
||||
require.Equal(t, diags[0].Path.String(), "sync.exclude[0]")
|
||||
|
||||
assert.Len(t, diags[0].Locations, 1)
|
||||
require.Equal(t, diags[0].Locations[0].File, filepath.Join("sync", "override", "databricks.yml"))
|
||||
require.Equal(t, diags[0].Locations[0].Line, 17)
|
||||
require.Equal(t, diags[0].Locations[0].Column, 11)
|
||||
|
||||
summaries := []string{
|
||||
fmt.Sprintf("Pattern %s does not match any files", filepath.Join("src", "*")),
|
||||
fmt.Sprintf("Pattern %s does not match any files", filepath.Join("tests", "*")),
|
||||
|
|
|
@ -17,9 +17,9 @@ type Diagnostic struct {
|
|||
// This may be multiple lines and may be nil.
|
||||
Detail string
|
||||
|
||||
// Location is a source code location associated with the diagnostic message.
|
||||
// It may be zero if there is no associated location.
|
||||
Location dyn.Location
|
||||
// Locations are the source code locations associated with the diagnostic message.
|
||||
// It may be empty if there are no associated locations.
|
||||
Locations []dyn.Location
|
||||
|
||||
// Path is a path to the value in a configuration tree that the diagnostic is associated with.
|
||||
// It may be nil if there is no associated path.
|
||||
|
|
|
@ -65,19 +65,19 @@ func (n normalizeOptions) normalizeType(typ reflect.Type, src dyn.Value, seen []
|
|||
|
||||
func nullWarning(expected dyn.Kind, src dyn.Value, path dyn.Path) diag.Diagnostic {
|
||||
return diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("expected a %s value, found null", expected),
|
||||
Location: src.Location(),
|
||||
Path: path,
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("expected a %s value, found null", expected),
|
||||
Locations: []dyn.Location{src.Location()},
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func typeMismatch(expected dyn.Kind, src dyn.Value, path dyn.Path) diag.Diagnostic {
|
||||
return diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("expected %s, found %s", expected, src.Kind()),
|
||||
Location: src.Location(),
|
||||
Path: path,
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("expected %s, found %s", expected, src.Kind()),
|
||||
Locations: []dyn.Location{src.Location()},
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,8 +98,9 @@ func (n normalizeOptions) normalizeStruct(typ reflect.Type, src dyn.Value, seen
|
|||
diags = diags.Append(diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("unknown field: %s", pk.MustString()),
|
||||
Location: pk.Location(),
|
||||
Path: path,
|
||||
// Show all locations the unknown field is defined at.
|
||||
Locations: pk.Locations(),
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
continue
|
||||
|
@ -320,10 +321,10 @@ func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn
|
|||
out = int64(src.MustFloat())
|
||||
if src.MustFloat() != float64(out) {
|
||||
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf(`cannot accurately represent "%g" as integer due to precision loss`, src.MustFloat()),
|
||||
Location: src.Location(),
|
||||
Path: path,
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf(`cannot accurately represent "%g" as integer due to precision loss`, src.MustFloat()),
|
||||
Locations: []dyn.Location{src.Location()},
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
case dyn.KindString:
|
||||
|
@ -336,10 +337,10 @@ func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn
|
|||
}
|
||||
|
||||
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("cannot parse %q as an integer", src.MustString()),
|
||||
Location: src.Location(),
|
||||
Path: path,
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("cannot parse %q as an integer", src.MustString()),
|
||||
Locations: []dyn.Location{src.Location()},
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
case dyn.KindNil:
|
||||
|
@ -363,10 +364,10 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d
|
|||
out = float64(src.MustInt())
|
||||
if src.MustInt() != int64(out) {
|
||||
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf(`cannot accurately represent "%d" as floating point number due to precision loss`, src.MustInt()),
|
||||
Location: src.Location(),
|
||||
Path: path,
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf(`cannot accurately represent "%d" as floating point number due to precision loss`, src.MustInt()),
|
||||
Locations: []dyn.Location{src.Location()},
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
case dyn.KindString:
|
||||
|
@ -379,10 +380,10 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d
|
|||
}
|
||||
|
||||
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("cannot parse %q as a floating point number", src.MustString()),
|
||||
Location: src.Location(),
|
||||
Path: path,
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("cannot parse %q as a floating point number", src.MustString()),
|
||||
Locations: []dyn.Location{src.Location()},
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
case dyn.KindNil:
|
||||
|
|
|
@ -40,10 +40,10 @@ func TestNormalizeStructElementDiagnostic(t *testing.T) {
|
|||
vout, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected string, found map`,
|
||||
Location: dyn.Location{},
|
||||
Path: dyn.NewPath(dyn.Key("bar")),
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected string, found map`,
|
||||
Locations: []dyn.Location{{}},
|
||||
Path: dyn.NewPath(dyn.Key("bar")),
|
||||
}, err[0])
|
||||
|
||||
// Elements that encounter an error during normalization are dropped.
|
||||
|
@ -58,23 +58,33 @@ func TestNormalizeStructUnknownField(t *testing.T) {
|
|||
}
|
||||
|
||||
var typ Tmp
|
||||
vin := dyn.V(map[string]dyn.Value{
|
||||
"foo": dyn.V("bar"),
|
||||
"bar": dyn.V("baz"),
|
||||
})
|
||||
|
||||
m := dyn.NewMapping()
|
||||
m.Set(dyn.V("foo"), dyn.V("val-foo"))
|
||||
// Set the unknown field, with location information.
|
||||
m.Set(dyn.NewValue("bar", []dyn.Location{
|
||||
{File: "hello.yaml", Line: 1, Column: 1},
|
||||
{File: "world.yaml", Line: 2, Column: 2},
|
||||
}), dyn.V("var-bar"))
|
||||
|
||||
vin := dyn.V(m)
|
||||
|
||||
vout, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `unknown field: bar`,
|
||||
Location: vin.Get("foo").Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
// Assert location of the unknown field is included in the diagnostic.
|
||||
Locations: []dyn.Location{
|
||||
{File: "hello.yaml", Line: 1, Column: 1},
|
||||
{File: "world.yaml", Line: 2, Column: 2},
|
||||
},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
|
||||
// The field that can be mapped to the struct field is retained.
|
||||
assert.Equal(t, map[string]any{
|
||||
"foo": "bar",
|
||||
"foo": "val-foo",
|
||||
}, vout.AsAny())
|
||||
}
|
||||
|
||||
|
@ -100,10 +110,10 @@ func TestNormalizeStructError(t *testing.T) {
|
|||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found string`,
|
||||
Location: vin.Get("foo").Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found string`,
|
||||
Locations: []dyn.Location{vin.Get("foo").Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -245,10 +255,10 @@ func TestNormalizeStructRandomStringError(t *testing.T) {
|
|||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found string`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found string`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -262,10 +272,10 @@ func TestNormalizeStructIntError(t *testing.T) {
|
|||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found int`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found int`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -291,10 +301,10 @@ func TestNormalizeMapElementDiagnostic(t *testing.T) {
|
|||
vout, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected string, found map`,
|
||||
Location: dyn.Location{},
|
||||
Path: dyn.NewPath(dyn.Key("bar")),
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected string, found map`,
|
||||
Locations: []dyn.Location{{}},
|
||||
Path: dyn.NewPath(dyn.Key("bar")),
|
||||
}, err[0])
|
||||
|
||||
// Elements that encounter an error during normalization are dropped.
|
||||
|
@ -317,10 +327,10 @@ func TestNormalizeMapError(t *testing.T) {
|
|||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found string`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found string`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -372,10 +382,10 @@ func TestNormalizeMapRandomStringError(t *testing.T) {
|
|||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found string`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found string`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -385,10 +395,10 @@ func TestNormalizeMapIntError(t *testing.T) {
|
|||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found int`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected map, found int`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -415,10 +425,10 @@ func TestNormalizeSliceElementDiagnostic(t *testing.T) {
|
|||
vout, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected string, found map`,
|
||||
Location: dyn.Location{},
|
||||
Path: dyn.NewPath(dyn.Index(2)),
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected string, found map`,
|
||||
Locations: []dyn.Location{{}},
|
||||
Path: dyn.NewPath(dyn.Index(2)),
|
||||
}, err[0])
|
||||
|
||||
// Elements that encounter an error during normalization are dropped.
|
||||
|
@ -439,10 +449,10 @@ func TestNormalizeSliceError(t *testing.T) {
|
|||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected sequence, found string`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected sequence, found string`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -494,10 +504,10 @@ func TestNormalizeSliceRandomStringError(t *testing.T) {
|
|||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected sequence, found string`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected sequence, found string`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -507,10 +517,10 @@ func TestNormalizeSliceIntError(t *testing.T) {
|
|||
_, err := Normalize(typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected sequence, found int`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected sequence, found int`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -528,10 +538,10 @@ func TestNormalizeStringNil(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected a string value, found null`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected a string value, found null`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -565,10 +575,10 @@ func TestNormalizeStringError(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected string, found map`,
|
||||
Location: dyn.Location{},
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected string, found map`,
|
||||
Locations: []dyn.Location{{}},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -586,10 +596,10 @@ func TestNormalizeBoolNil(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected a bool value, found null`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected a bool value, found null`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -628,10 +638,10 @@ func TestNormalizeBoolFromStringError(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected bool, found string`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected bool, found string`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -641,10 +651,10 @@ func TestNormalizeBoolError(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected bool, found map`,
|
||||
Location: dyn.Location{},
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected bool, found map`,
|
||||
Locations: []dyn.Location{{}},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -662,10 +672,10 @@ func TestNormalizeIntNil(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected a int value, found null`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected a int value, found null`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -683,10 +693,10 @@ func TestNormalizeIntFromFloatError(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `cannot accurately represent "1.5" as integer due to precision loss`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `cannot accurately represent "1.5" as integer due to precision loss`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -712,10 +722,10 @@ func TestNormalizeIntFromStringError(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `cannot parse "abc" as an integer`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `cannot parse "abc" as an integer`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -725,10 +735,10 @@ func TestNormalizeIntError(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected int, found map`,
|
||||
Location: dyn.Location{},
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected int, found map`,
|
||||
Locations: []dyn.Location{{}},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -746,10 +756,10 @@ func TestNormalizeFloatNil(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected a float value, found null`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected a float value, found null`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -771,10 +781,10 @@ func TestNormalizeFloatFromIntError(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `cannot accurately represent "9007199254740993" as floating point number due to precision loss`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `cannot accurately represent "9007199254740993" as floating point number due to precision loss`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -800,10 +810,10 @@ func TestNormalizeFloatFromStringError(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `cannot parse "abc" as a floating point number`,
|
||||
Location: vin.Location(),
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `cannot parse "abc" as a floating point number`,
|
||||
Locations: []dyn.Location{vin.Location()},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
@ -813,10 +823,10 @@ func TestNormalizeFloatError(t *testing.T) {
|
|||
_, err := Normalize(&typ, vin)
|
||||
assert.Len(t, err, 1)
|
||||
assert.Equal(t, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected float, found map`,
|
||||
Location: dyn.Location{},
|
||||
Path: dyn.EmptyPath,
|
||||
Severity: diag.Warning,
|
||||
Summary: `expected float, found map`,
|
||||
Locations: []dyn.Location{{}},
|
||||
Path: dyn.EmptyPath,
|
||||
}, err[0])
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue