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,11 +55,17 @@ func parsePythonDiagnostics(input io.Reader) (diag.Diagnostics, error) {
|
||||||
return nil, fmt.Errorf("failed to parse path: %s", err)
|
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{
|
diag := diag.Diagnostic{
|
||||||
Severity: severity,
|
Severity: severity,
|
||||||
Summary: parsedLine.Summary,
|
Summary: parsedLine.Summary,
|
||||||
Detail: parsedLine.Detail,
|
Detail: parsedLine.Detail,
|
||||||
Location: convertPythonLocation(parsedLine.Location),
|
Locations: locations,
|
||||||
Path: path,
|
Path: path,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,8 @@ func TestParsePythonDiagnostics(t *testing.T) {
|
||||||
{
|
{
|
||||||
Severity: diag.Error,
|
Severity: diag.Error,
|
||||||
Summary: "error summary",
|
Summary: "error summary",
|
||||||
Location: dyn.Location{
|
Locations: []dyn.Location{
|
||||||
|
{
|
||||||
File: "src/examples/file.py",
|
File: "src/examples/file.py",
|
||||||
Line: 1,
|
Line: 1,
|
||||||
Column: 2,
|
Column: 2,
|
||||||
|
@ -47,6 +48,7 @@ func TestParsePythonDiagnostics(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "short error with path",
|
name: "short error with path",
|
||||||
input: `{"severity": "error", "summary": "error summary", "path": "resources.jobs.job0.name"}`,
|
input: `{"severity": "error", "summary": "error summary", "path": "resources.jobs.job0.name"}`,
|
||||||
|
|
|
@ -97,11 +97,14 @@ func TestPythonMutator_load(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, 1, len(diags))
|
assert.Equal(t, 1, len(diags))
|
||||||
assert.Equal(t, "job doesn't have any tasks", diags[0].Summary)
|
assert.Equal(t, "job doesn't have any tasks", diags[0].Summary)
|
||||||
assert.Equal(t, dyn.Location{
|
assert.Equal(t, []dyn.Location{
|
||||||
|
{
|
||||||
File: "src/examples/file.py",
|
File: "src/examples/file.py",
|
||||||
Line: 10,
|
Line: 10,
|
||||||
Column: 5,
|
Column: 5,
|
||||||
}, diags[0].Location)
|
},
|
||||||
|
}, diags[0].Locations)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPythonMutator_load_disallowed(t *testing.T) {
|
func TestPythonMutator_load_disallowed(t *testing.T) {
|
||||||
|
|
|
@ -181,7 +181,7 @@ func (m *setRunAs) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics {
|
||||||
Severity: diag.Warning,
|
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.",
|
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"),
|
Path: dyn.MustPathFromString("experimental.use_legacy_run_as"),
|
||||||
Location: b.Config.GetLocation("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()
|
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
|
// 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.
|
// is the source of truth and is kept in sync with values in the typed configuration.
|
||||||
func (r Root) Value() dyn.Value {
|
func (r Root) Value() dyn.Value {
|
||||||
|
|
|
@ -45,7 +45,9 @@ func (v *filesToSync) Apply(ctx context.Context, rb bundle.ReadOnlyBundle) diag.
|
||||||
diags = diags.Append(diag.Diagnostic{
|
diags = diags.Append(diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: "There are no files to sync, please check your .gitignore and sync.exclude configuration",
|
Summary: "There are no files to sync, please check your .gitignore and sync.exclude configuration",
|
||||||
Location: loc.Location(),
|
// Show all locations where sync.exclude is defined, since merging
|
||||||
|
// sync.exclude is additive.
|
||||||
|
Locations: loc.Locations(),
|
||||||
Path: loc.Path(),
|
Path: loc.Path(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/databricks/cli/bundle"
|
"github.com/databricks/cli/bundle"
|
||||||
"github.com/databricks/cli/libs/diag"
|
"github.com/databricks/cli/libs/diag"
|
||||||
|
"github.com/databricks/cli/libs/dyn"
|
||||||
)
|
)
|
||||||
|
|
||||||
func JobClusterKeyDefined() bundle.ReadOnlyMutator {
|
func JobClusterKeyDefined() bundle.ReadOnlyMutator {
|
||||||
|
@ -41,7 +42,10 @@ func (v *jobClusterKeyDefined) Apply(ctx context.Context, rb bundle.ReadOnlyBund
|
||||||
diags = diags.Append(diag.Diagnostic{
|
diags = diags.Append(diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: fmt.Sprintf("job_cluster_key %s is not defined", task.JobClusterKey),
|
Summary: fmt.Sprintf("job_cluster_key %s is not defined", task.JobClusterKey),
|
||||||
Location: loc.Location(),
|
// 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(),
|
Path: loc.Path(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,10 @@ func (l location) Location() dyn.Location {
|
||||||
return l.rb.Config().GetLocation(l.path)
|
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 {
|
func (l location) Path() dyn.Path {
|
||||||
return dyn.MustPathFromString(l.path)
|
return dyn.MustPathFromString(l.path)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/databricks/cli/bundle"
|
"github.com/databricks/cli/bundle"
|
||||||
"github.com/databricks/cli/libs/diag"
|
"github.com/databricks/cli/libs/diag"
|
||||||
|
"github.com/databricks/cli/libs/dyn"
|
||||||
"github.com/databricks/cli/libs/fileset"
|
"github.com/databricks/cli/libs/fileset"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
@ -66,7 +67,7 @@ func checkPatterns(patterns []string, path string, rb bundle.ReadOnlyBundle) (di
|
||||||
diags = diags.Append(diag.Diagnostic{
|
diags = diags.Append(diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: fmt.Sprintf("Pattern %s does not match any files", p),
|
Summary: fmt.Sprintf("Pattern %s does not match any files", p),
|
||||||
Location: loc.Location(),
|
Locations: []dyn.Location{loc.Location()},
|
||||||
Path: loc.Path(),
|
Path: loc.Path(),
|
||||||
})
|
})
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
|
|
|
@ -32,8 +32,8 @@ const errorTemplate = `{{ "Error" | red }}: {{ .Summary }}
|
||||||
{{- if .Path.String }}
|
{{- if .Path.String }}
|
||||||
{{ "at " }}{{ .Path.String | green }}
|
{{ "at " }}{{ .Path.String | green }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Location.File }}
|
{{- range $index, $element := .Locations }}
|
||||||
{{ "in " }}{{ .Location.String | cyan }}
|
{{ if eq $index 0 }}in {{else}} {{ end}}{{ $element.String | cyan }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Detail }}
|
{{- if .Detail }}
|
||||||
|
|
||||||
|
@ -46,8 +46,8 @@ const warningTemplate = `{{ "Warning" | yellow }}: {{ .Summary }}
|
||||||
{{- if .Path.String }}
|
{{- if .Path.String }}
|
||||||
{{ "at " }}{{ .Path.String | green }}
|
{{ "at " }}{{ .Path.String | green }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Location.File }}
|
{{- range $index, $element := .Locations }}
|
||||||
{{ "in " }}{{ .Location.String | cyan }}
|
{{ if eq $index 0 }}in {{else}} {{ end}}{{ $element.String | cyan }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Detail }}
|
{{- if .Detail }}
|
||||||
|
|
||||||
|
@ -141,12 +141,18 @@ func renderDiagnostics(out io.Writer, b *bundle.Bundle, diags diag.Diagnostics)
|
||||||
t = warningT
|
t = warningT
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make file relative to bundle root
|
for i := range d.Locations {
|
||||||
if d.Location.File != "" && b != nil {
|
if b == nil {
|
||||||
out, err := filepath.Rel(b.RootPath, d.Location.File)
|
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 we can't relativize the path, just use path as-is
|
||||||
if err == nil {
|
if err == nil {
|
||||||
d.Location.File = out
|
d.Locations[i].File = out
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,31 +91,19 @@ func TestRenderTextOutput(t *testing.T) {
|
||||||
Severity: diag.Error,
|
Severity: diag.Error,
|
||||||
Summary: "error (1)",
|
Summary: "error (1)",
|
||||||
Detail: "detail (1)",
|
Detail: "detail (1)",
|
||||||
Location: dyn.Location{
|
Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}},
|
||||||
File: "foo.py",
|
|
||||||
Line: 1,
|
|
||||||
Column: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
diag.Diagnostic{
|
diag.Diagnostic{
|
||||||
Severity: diag.Error,
|
Severity: diag.Error,
|
||||||
Summary: "error (2)",
|
Summary: "error (2)",
|
||||||
Detail: "detail (2)",
|
Detail: "detail (2)",
|
||||||
Location: dyn.Location{
|
Locations: []dyn.Location{{File: "foo.py", Line: 2, Column: 1}},
|
||||||
File: "foo.py",
|
|
||||||
Line: 2,
|
|
||||||
Column: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
diag.Diagnostic{
|
diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: "warning (3)",
|
Summary: "warning (3)",
|
||||||
Detail: "detail (3)",
|
Detail: "detail (3)",
|
||||||
Location: dyn.Location{
|
Locations: []dyn.Location{{File: "foo.py", Line: 3, Column: 1}},
|
||||||
File: "foo.py",
|
|
||||||
Line: 3,
|
|
||||||
Column: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
opts: RenderOptions{RenderSummaryTable: true},
|
opts: RenderOptions{RenderSummaryTable: true},
|
||||||
|
@ -177,21 +165,13 @@ func TestRenderTextOutput(t *testing.T) {
|
||||||
Severity: diag.Error,
|
Severity: diag.Error,
|
||||||
Summary: "error (1)",
|
Summary: "error (1)",
|
||||||
Detail: "detail (1)",
|
Detail: "detail (1)",
|
||||||
Location: dyn.Location{
|
Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}},
|
||||||
File: "foo.py",
|
|
||||||
Line: 1,
|
|
||||||
Column: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
diag.Diagnostic{
|
diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: "warning (2)",
|
Summary: "warning (2)",
|
||||||
Detail: "detail (2)",
|
Detail: "detail (2)",
|
||||||
Location: dyn.Location{
|
Locations: []dyn.Location{{File: "foo.py", Line: 3, Column: 1}},
|
||||||
File: "foo.py",
|
|
||||||
Line: 3,
|
|
||||||
Column: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
opts: RenderOptions{RenderSummaryTable: false},
|
opts: RenderOptions{RenderSummaryTable: false},
|
||||||
|
@ -252,17 +232,42 @@ func TestRenderDiagnostics(t *testing.T) {
|
||||||
Severity: diag.Error,
|
Severity: diag.Error,
|
||||||
Summary: "failed to load xxx",
|
Summary: "failed to load xxx",
|
||||||
Detail: "'name' is required",
|
Detail: "'name' is required",
|
||||||
Location: dyn.Location{
|
Locations: []dyn.Location{{
|
||||||
File: "foo.yaml",
|
File: "foo.yaml",
|
||||||
Line: 1,
|
Line: 1,
|
||||||
Column: 2,
|
Column: 2}},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: "Error: failed to load xxx\n" +
|
expected: "Error: failed to load xxx\n" +
|
||||||
" in foo.yaml:1:2\n\n" +
|
" in foo.yaml:1:2\n\n" +
|
||||||
"'name' is required\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",
|
name: "error with path",
|
||||||
diags: diag.Diagnostics{
|
diags: diag.Diagnostics{
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/databricks/cli/bundle"
|
"github.com/databricks/cli/bundle"
|
||||||
"github.com/databricks/cli/bundle/config/validate"
|
"github.com/databricks/cli/bundle/config/validate"
|
||||||
"github.com/databricks/cli/libs/diag"
|
"github.com/databricks/cli/libs/diag"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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].Severity, diag.Warning)
|
||||||
require.Equal(t, diags[0].Summary, "Pattern dist does not match any files")
|
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]")
|
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{
|
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("src", "*")),
|
||||||
fmt.Sprintf("Pattern %s does not match any files", filepath.Join("tests", "*")),
|
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.
|
// This may be multiple lines and may be nil.
|
||||||
Detail string
|
Detail string
|
||||||
|
|
||||||
// Location is a source code location associated with the diagnostic message.
|
// Locations are the source code locations associated with the diagnostic message.
|
||||||
// It may be zero if there is no associated location.
|
// It may be empty if there are no associated locations.
|
||||||
Location dyn.Location
|
Locations []dyn.Location
|
||||||
|
|
||||||
// Path is a path to the value in a configuration tree that the diagnostic is associated with.
|
// 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.
|
// It may be nil if there is no associated path.
|
||||||
|
|
|
@ -67,7 +67,7 @@ func nullWarning(expected dyn.Kind, src dyn.Value, path dyn.Path) diag.Diagnosti
|
||||||
return diag.Diagnostic{
|
return diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: fmt.Sprintf("expected a %s value, found null", expected),
|
Summary: fmt.Sprintf("expected a %s value, found null", expected),
|
||||||
Location: src.Location(),
|
Locations: []dyn.Location{src.Location()},
|
||||||
Path: path,
|
Path: path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ func typeMismatch(expected dyn.Kind, src dyn.Value, path dyn.Path) diag.Diagnost
|
||||||
return diag.Diagnostic{
|
return diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: fmt.Sprintf("expected %s, found %s", expected, src.Kind()),
|
Summary: fmt.Sprintf("expected %s, found %s", expected, src.Kind()),
|
||||||
Location: src.Location(),
|
Locations: []dyn.Location{src.Location()},
|
||||||
Path: path,
|
Path: path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,8 @@ func (n normalizeOptions) normalizeStruct(typ reflect.Type, src dyn.Value, seen
|
||||||
diags = diags.Append(diag.Diagnostic{
|
diags = diags.Append(diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: fmt.Sprintf("unknown field: %s", pk.MustString()),
|
Summary: fmt.Sprintf("unknown field: %s", pk.MustString()),
|
||||||
Location: pk.Location(),
|
// Show all locations the unknown field is defined at.
|
||||||
|
Locations: pk.Locations(),
|
||||||
Path: path,
|
Path: path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -322,7 +323,7 @@ func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn
|
||||||
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: fmt.Sprintf(`cannot accurately represent "%g" as integer due to precision loss`, src.MustFloat()),
|
Summary: fmt.Sprintf(`cannot accurately represent "%g" as integer due to precision loss`, src.MustFloat()),
|
||||||
Location: src.Location(),
|
Locations: []dyn.Location{src.Location()},
|
||||||
Path: path,
|
Path: path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -338,7 +339,7 @@ func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn
|
||||||
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: fmt.Sprintf("cannot parse %q as an integer", src.MustString()),
|
Summary: fmt.Sprintf("cannot parse %q as an integer", src.MustString()),
|
||||||
Location: src.Location(),
|
Locations: []dyn.Location{src.Location()},
|
||||||
Path: path,
|
Path: path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -365,7 +366,7 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d
|
||||||
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: fmt.Sprintf(`cannot accurately represent "%d" as floating point number due to precision loss`, src.MustInt()),
|
Summary: fmt.Sprintf(`cannot accurately represent "%d" as floating point number due to precision loss`, src.MustInt()),
|
||||||
Location: src.Location(),
|
Locations: []dyn.Location{src.Location()},
|
||||||
Path: path,
|
Path: path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -381,7 +382,7 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d
|
||||||
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: fmt.Sprintf("cannot parse %q as a floating point number", src.MustString()),
|
Summary: fmt.Sprintf("cannot parse %q as a floating point number", src.MustString()),
|
||||||
Location: src.Location(),
|
Locations: []dyn.Location{src.Location()},
|
||||||
Path: path,
|
Path: path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ func TestNormalizeStructElementDiagnostic(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected string, found map`,
|
Summary: `expected string, found map`,
|
||||||
Location: dyn.Location{},
|
Locations: []dyn.Location{{}},
|
||||||
Path: dyn.NewPath(dyn.Key("bar")),
|
Path: dyn.NewPath(dyn.Key("bar")),
|
||||||
}, err[0])
|
}, err[0])
|
||||||
|
|
||||||
|
@ -58,23 +58,33 @@ func TestNormalizeStructUnknownField(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var typ Tmp
|
var typ Tmp
|
||||||
vin := dyn.V(map[string]dyn.Value{
|
|
||||||
"foo": dyn.V("bar"),
|
m := dyn.NewMapping()
|
||||||
"bar": dyn.V("baz"),
|
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)
|
vout, err := Normalize(typ, vin)
|
||||||
assert.Len(t, err, 1)
|
assert.Len(t, err, 1)
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `unknown field: bar`,
|
Summary: `unknown field: bar`,
|
||||||
Location: vin.Get("foo").Location(),
|
// 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,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
|
|
||||||
// The field that can be mapped to the struct field is retained.
|
// The field that can be mapped to the struct field is retained.
|
||||||
assert.Equal(t, map[string]any{
|
assert.Equal(t, map[string]any{
|
||||||
"foo": "bar",
|
"foo": "val-foo",
|
||||||
}, vout.AsAny())
|
}, vout.AsAny())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +112,7 @@ func TestNormalizeStructError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected map, found string`,
|
Summary: `expected map, found string`,
|
||||||
Location: vin.Get("foo").Location(),
|
Locations: []dyn.Location{vin.Get("foo").Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -247,7 +257,7 @@ func TestNormalizeStructRandomStringError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected map, found string`,
|
Summary: `expected map, found string`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -264,7 +274,7 @@ func TestNormalizeStructIntError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected map, found int`,
|
Summary: `expected map, found int`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -293,7 +303,7 @@ func TestNormalizeMapElementDiagnostic(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected string, found map`,
|
Summary: `expected string, found map`,
|
||||||
Location: dyn.Location{},
|
Locations: []dyn.Location{{}},
|
||||||
Path: dyn.NewPath(dyn.Key("bar")),
|
Path: dyn.NewPath(dyn.Key("bar")),
|
||||||
}, err[0])
|
}, err[0])
|
||||||
|
|
||||||
|
@ -319,7 +329,7 @@ func TestNormalizeMapError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected map, found string`,
|
Summary: `expected map, found string`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -374,7 +384,7 @@ func TestNormalizeMapRandomStringError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected map, found string`,
|
Summary: `expected map, found string`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -387,7 +397,7 @@ func TestNormalizeMapIntError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected map, found int`,
|
Summary: `expected map, found int`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -417,7 +427,7 @@ func TestNormalizeSliceElementDiagnostic(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected string, found map`,
|
Summary: `expected string, found map`,
|
||||||
Location: dyn.Location{},
|
Locations: []dyn.Location{{}},
|
||||||
Path: dyn.NewPath(dyn.Index(2)),
|
Path: dyn.NewPath(dyn.Index(2)),
|
||||||
}, err[0])
|
}, err[0])
|
||||||
|
|
||||||
|
@ -441,7 +451,7 @@ func TestNormalizeSliceError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected sequence, found string`,
|
Summary: `expected sequence, found string`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -496,7 +506,7 @@ func TestNormalizeSliceRandomStringError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected sequence, found string`,
|
Summary: `expected sequence, found string`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -509,7 +519,7 @@ func TestNormalizeSliceIntError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected sequence, found int`,
|
Summary: `expected sequence, found int`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -530,7 +540,7 @@ func TestNormalizeStringNil(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected a string value, found null`,
|
Summary: `expected a string value, found null`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -567,7 +577,7 @@ func TestNormalizeStringError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected string, found map`,
|
Summary: `expected string, found map`,
|
||||||
Location: dyn.Location{},
|
Locations: []dyn.Location{{}},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -588,7 +598,7 @@ func TestNormalizeBoolNil(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected a bool value, found null`,
|
Summary: `expected a bool value, found null`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -630,7 +640,7 @@ func TestNormalizeBoolFromStringError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected bool, found string`,
|
Summary: `expected bool, found string`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -643,7 +653,7 @@ func TestNormalizeBoolError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected bool, found map`,
|
Summary: `expected bool, found map`,
|
||||||
Location: dyn.Location{},
|
Locations: []dyn.Location{{}},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -664,7 +674,7 @@ func TestNormalizeIntNil(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected a int value, found null`,
|
Summary: `expected a int value, found null`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -685,7 +695,7 @@ func TestNormalizeIntFromFloatError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `cannot accurately represent "1.5" as integer due to precision loss`,
|
Summary: `cannot accurately represent "1.5" as integer due to precision loss`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -714,7 +724,7 @@ func TestNormalizeIntFromStringError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `cannot parse "abc" as an integer`,
|
Summary: `cannot parse "abc" as an integer`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -727,7 +737,7 @@ func TestNormalizeIntError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected int, found map`,
|
Summary: `expected int, found map`,
|
||||||
Location: dyn.Location{},
|
Locations: []dyn.Location{{}},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -748,7 +758,7 @@ func TestNormalizeFloatNil(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected a float value, found null`,
|
Summary: `expected a float value, found null`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -773,7 +783,7 @@ func TestNormalizeFloatFromIntError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `cannot accurately represent "9007199254740993" as floating point number due to precision loss`,
|
Summary: `cannot accurately represent "9007199254740993" as floating point number due to precision loss`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -802,7 +812,7 @@ func TestNormalizeFloatFromStringError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `cannot parse "abc" as a floating point number`,
|
Summary: `cannot parse "abc" as a floating point number`,
|
||||||
Location: vin.Location(),
|
Locations: []dyn.Location{vin.Location()},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
@ -815,7 +825,7 @@ func TestNormalizeFloatError(t *testing.T) {
|
||||||
assert.Equal(t, diag.Diagnostic{
|
assert.Equal(t, diag.Diagnostic{
|
||||||
Severity: diag.Warning,
|
Severity: diag.Warning,
|
||||||
Summary: `expected float, found map`,
|
Summary: `expected float, found map`,
|
||||||
Location: dyn.Location{},
|
Locations: []dyn.Location{{}},
|
||||||
Path: dyn.EmptyPath,
|
Path: dyn.EmptyPath,
|
||||||
}, err[0])
|
}, err[0])
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue