diff --git a/bundle/config/mutator/python/python_diagnostics.go b/bundle/config/mutator/python/python_diagnostics.go index 96baa50937..12822065bb 100644 --- a/bundle/config/mutator/python/python_diagnostics.go +++ b/bundle/config/mutator/python/python_diagnostics.go @@ -54,6 +54,10 @@ func parsePythonDiagnostics(input io.Reader) (diag.Diagnostics, error) { if err != nil { return nil, fmt.Errorf("failed to parse path: %s", err) } + var paths []dyn.Path + if path != nil { + paths = []dyn.Path{path} + } var locations []dyn.Location location := convertPythonLocation(parsedLine.Location) @@ -66,7 +70,7 @@ func parsePythonDiagnostics(input io.Reader) (diag.Diagnostics, error) { Summary: parsedLine.Summary, Detail: parsedLine.Detail, Locations: locations, - Path: path, + Paths: paths, } diags = diags.Append(diag) diff --git a/bundle/config/mutator/python/python_diagnostics_test.go b/bundle/config/mutator/python/python_diagnostics_test.go index 09d9f93bd2..b73b0f73cd 100644 --- a/bundle/config/mutator/python/python_diagnostics_test.go +++ b/bundle/config/mutator/python/python_diagnostics_test.go @@ -56,7 +56,7 @@ func TestParsePythonDiagnostics(t *testing.T) { { Severity: diag.Error, Summary: "error summary", - Path: dyn.MustPathFromString("resources.jobs.job0.name"), + Paths: []dyn.Path{dyn.MustPathFromString("resources.jobs.job0.name")}, }, }, }, diff --git a/bundle/config/mutator/run_as.go b/bundle/config/mutator/run_as.go index 168918d0db..423bc38e2d 100644 --- a/bundle/config/mutator/run_as.go +++ b/bundle/config/mutator/run_as.go @@ -180,7 +180,7 @@ func (m *setRunAs) Apply(_ context.Context, b *bundle.Bundle) 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"), + Paths: []dyn.Path{dyn.MustPathFromString("experimental.use_legacy_run_as")}, Locations: b.Config.GetLocations("experimental.use_legacy_run_as"), }, } diff --git a/bundle/config/validate/files_to_sync.go b/bundle/config/validate/files_to_sync.go index ae6bfef1a9..7cdad772ac 100644 --- a/bundle/config/validate/files_to_sync.go +++ b/bundle/config/validate/files_to_sync.go @@ -6,6 +6,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/deploy/files" "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/dyn" ) func FilesToSync() bundle.ReadOnlyMutator { @@ -48,7 +49,7 @@ func (v *filesToSync) Apply(ctx context.Context, rb bundle.ReadOnlyBundle) diag. // Show all locations where sync.exclude is defined, since merging // sync.exclude is additive. Locations: loc.Locations(), - Path: loc.Path(), + Paths: []dyn.Path{loc.Path()}, }) } diff --git a/bundle/config/validate/job_cluster_key_defined.go b/bundle/config/validate/job_cluster_key_defined.go index 168303d83b..368c3edb13 100644 --- a/bundle/config/validate/job_cluster_key_defined.go +++ b/bundle/config/validate/job_cluster_key_defined.go @@ -46,7 +46,7 @@ func (v *jobClusterKeyDefined) Apply(ctx context.Context, rb bundle.ReadOnlyBund // Other associated locations are not relevant since they are // overridden during merging. Locations: []dyn.Location{loc.Location()}, - Path: loc.Path(), + Paths: []dyn.Path{loc.Path()}, }) } } diff --git a/bundle/config/validate/validate_sync_patterns.go b/bundle/config/validate/validate_sync_patterns.go index f3655ca949..573077b66e 100644 --- a/bundle/config/validate/validate_sync_patterns.go +++ b/bundle/config/validate/validate_sync_patterns.go @@ -68,7 +68,7 @@ func checkPatterns(patterns []string, path string, rb bundle.ReadOnlyBundle) (di Severity: diag.Warning, Summary: fmt.Sprintf("Pattern %s does not match any files", p), Locations: []dyn.Location{loc.Location()}, - Path: loc.Path(), + Paths: []dyn.Path{loc.Path()}, }) mu.Unlock() } diff --git a/bundle/render/render_text_output.go b/bundle/render/render_text_output.go index 2ef6b26569..ea0b9a944f 100644 --- a/bundle/render/render_text_output.go +++ b/bundle/render/render_text_output.go @@ -29,8 +29,8 @@ var renderFuncMap = template.FuncMap{ } const errorTemplate = `{{ "Error" | red }}: {{ .Summary }} -{{- if .Path.String }} - {{ "at " }}{{ .Path.String | green }} +{{- range $index, $element := .Paths }} + {{ if eq $index 0 }}at {{else}} {{ end}}{{ $element.String | green }} {{- end }} {{- range $index, $element := .Locations }} {{ if eq $index 0 }}in {{else}} {{ end}}{{ $element.String | cyan }} @@ -43,8 +43,8 @@ const errorTemplate = `{{ "Error" | red }}: {{ .Summary }} ` const warningTemplate = `{{ "Warning" | yellow }}: {{ .Summary }} -{{- if .Path.String }} - {{ "at " }}{{ .Path.String | green }} +{{- range $index, $element := .Paths }} + {{ if eq $index 0 }}at {{else}} {{ end}}{{ $element.String | green }} {{- end }} {{- range $index, $element := .Locations }} {{ if eq $index 0 }}in {{else}} {{ end}}{{ $element.String | cyan }} diff --git a/bundle/render/render_text_output_test.go b/bundle/render/render_text_output_test.go index 81e2761995..976f86e79c 100644 --- a/bundle/render/render_text_output_test.go +++ b/bundle/render/render_text_output_test.go @@ -275,7 +275,7 @@ func TestRenderDiagnostics(t *testing.T) { Severity: diag.Error, Detail: "'name' is required", Summary: "failed to load xxx", - Path: dyn.MustPathFromString("resources.jobs.xxx"), + Paths: []dyn.Path{dyn.MustPathFromString("resources.jobs.xxx")}, }, }, expected: "Error: failed to load xxx\n" + @@ -283,6 +283,27 @@ func TestRenderDiagnostics(t *testing.T) { "\n" + "'name' is required\n\n", }, + { + name: "error with multiple paths", + diags: diag.Diagnostics{ + { + Severity: diag.Error, + Detail: "'name' is required", + Summary: "failed to load xxx", + Paths: []dyn.Path{ + dyn.MustPathFromString("resources.jobs.xxx"), + dyn.MustPathFromString("resources.jobs.yyy"), + dyn.MustPathFromString("resources.jobs.zzz"), + }, + }, + }, + expected: "Error: failed to load xxx\n" + + " at resources.jobs.xxx\n" + + " resources.jobs.yyy\n" + + " resources.jobs.zzz\n" + + "\n" + + "'name' is required\n\n", + }, } for _, tc := range testCases { diff --git a/bundle/tests/sync_include_exclude_no_matches_test.go b/bundle/tests/sync_include_exclude_no_matches_test.go index 5f4fa47ce4..23f99b3a75 100644 --- a/bundle/tests/sync_include_exclude_no_matches_test.go +++ b/bundle/tests/sync_include_exclude_no_matches_test.go @@ -22,7 +22,9 @@ 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].Path.String(), "sync.exclude[0]") + + require.Len(t, diags[0].Paths, 1) + require.Equal(t, diags[0].Paths[0].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")) diff --git a/libs/diag/diagnostic.go b/libs/diag/diagnostic.go index 305089d228..93334c067a 100644 --- a/libs/diag/diagnostic.go +++ b/libs/diag/diagnostic.go @@ -21,9 +21,9 @@ type Diagnostic struct { // 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. - Path dyn.Path + // Paths are paths to the values in the configuration tree that the diagnostic is associated with. + // It may be nil if there are no associated paths. + Paths []dyn.Path } // Errorf creates a new error diagnostic. diff --git a/libs/dyn/convert/normalize.go b/libs/dyn/convert/normalize.go index bf5756e7f1..c80a914f14 100644 --- a/libs/dyn/convert/normalize.go +++ b/libs/dyn/convert/normalize.go @@ -68,7 +68,7 @@ func nullWarning(expected dyn.Kind, src dyn.Value, path dyn.Path) diag.Diagnosti Severity: diag.Warning, Summary: fmt.Sprintf("expected a %s value, found null", expected), Locations: []dyn.Location{src.Location()}, - Path: path, + Paths: []dyn.Path{path}, } } @@ -77,7 +77,7 @@ func typeMismatch(expected dyn.Kind, src dyn.Value, path dyn.Path) diag.Diagnost Severity: diag.Warning, Summary: fmt.Sprintf("expected %s, found %s", expected, src.Kind()), Locations: []dyn.Location{src.Location()}, - Path: path, + Paths: []dyn.Path{path}, } } @@ -100,7 +100,7 @@ func (n normalizeOptions) normalizeStruct(typ reflect.Type, src dyn.Value, seen Summary: fmt.Sprintf("unknown field: %s", pk.MustString()), // Show all locations the unknown field is defined at. Locations: pk.Locations(), - Path: path, + Paths: []dyn.Path{path}, }) } continue @@ -324,7 +324,7 @@ func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn 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, + Paths: []dyn.Path{path}, }) } case dyn.KindString: @@ -340,7 +340,7 @@ func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn Severity: diag.Warning, Summary: fmt.Sprintf("cannot parse %q as an integer", src.MustString()), Locations: []dyn.Location{src.Location()}, - Path: path, + Paths: []dyn.Path{path}, }) } case dyn.KindNil: @@ -367,7 +367,7 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d 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, + Paths: []dyn.Path{path}, }) } case dyn.KindString: @@ -383,7 +383,7 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d Severity: diag.Warning, Summary: fmt.Sprintf("cannot parse %q as a floating point number", src.MustString()), Locations: []dyn.Location{src.Location()}, - Path: path, + Paths: []dyn.Path{path}, }) } case dyn.KindNil: diff --git a/libs/dyn/convert/normalize_test.go b/libs/dyn/convert/normalize_test.go index df9a1a9a53..c2256615e9 100644 --- a/libs/dyn/convert/normalize_test.go +++ b/libs/dyn/convert/normalize_test.go @@ -43,7 +43,7 @@ func TestNormalizeStructElementDiagnostic(t *testing.T) { Severity: diag.Warning, Summary: `expected string, found map`, Locations: []dyn.Location{{}}, - Path: dyn.NewPath(dyn.Key("bar")), + Paths: []dyn.Path{dyn.NewPath(dyn.Key("bar"))}, }, err[0]) // Elements that encounter an error during normalization are dropped. @@ -79,7 +79,7 @@ func TestNormalizeStructUnknownField(t *testing.T) { {File: "hello.yaml", Line: 1, Column: 1}, {File: "world.yaml", Line: 2, Column: 2}, }, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) // The field that can be mapped to the struct field is retained. @@ -113,7 +113,7 @@ func TestNormalizeStructError(t *testing.T) { Severity: diag.Warning, Summary: `expected map, found string`, Locations: []dyn.Location{vin.Get("foo").Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -258,7 +258,7 @@ func TestNormalizeStructRandomStringError(t *testing.T) { Severity: diag.Warning, Summary: `expected map, found string`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -275,7 +275,7 @@ func TestNormalizeStructIntError(t *testing.T) { Severity: diag.Warning, Summary: `expected map, found int`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -304,7 +304,7 @@ func TestNormalizeMapElementDiagnostic(t *testing.T) { Severity: diag.Warning, Summary: `expected string, found map`, Locations: []dyn.Location{{}}, - Path: dyn.NewPath(dyn.Key("bar")), + Paths: []dyn.Path{dyn.NewPath(dyn.Key("bar"))}, }, err[0]) // Elements that encounter an error during normalization are dropped. @@ -330,7 +330,7 @@ func TestNormalizeMapError(t *testing.T) { Severity: diag.Warning, Summary: `expected map, found string`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -385,7 +385,7 @@ func TestNormalizeMapRandomStringError(t *testing.T) { Severity: diag.Warning, Summary: `expected map, found string`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -398,7 +398,7 @@ func TestNormalizeMapIntError(t *testing.T) { Severity: diag.Warning, Summary: `expected map, found int`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -428,7 +428,7 @@ func TestNormalizeSliceElementDiagnostic(t *testing.T) { Severity: diag.Warning, Summary: `expected string, found map`, Locations: []dyn.Location{{}}, - Path: dyn.NewPath(dyn.Index(2)), + Paths: []dyn.Path{dyn.NewPath(dyn.Index(2))}, }, err[0]) // Elements that encounter an error during normalization are dropped. @@ -452,7 +452,7 @@ func TestNormalizeSliceError(t *testing.T) { Severity: diag.Warning, Summary: `expected sequence, found string`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -507,7 +507,7 @@ func TestNormalizeSliceRandomStringError(t *testing.T) { Severity: diag.Warning, Summary: `expected sequence, found string`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -520,7 +520,7 @@ func TestNormalizeSliceIntError(t *testing.T) { Severity: diag.Warning, Summary: `expected sequence, found int`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -541,7 +541,7 @@ func TestNormalizeStringNil(t *testing.T) { Severity: diag.Warning, Summary: `expected a string value, found null`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -578,7 +578,7 @@ func TestNormalizeStringError(t *testing.T) { Severity: diag.Warning, Summary: `expected string, found map`, Locations: []dyn.Location{{}}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -599,7 +599,7 @@ func TestNormalizeBoolNil(t *testing.T) { Severity: diag.Warning, Summary: `expected a bool value, found null`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -641,7 +641,7 @@ func TestNormalizeBoolFromStringError(t *testing.T) { Severity: diag.Warning, Summary: `expected bool, found string`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -654,7 +654,7 @@ func TestNormalizeBoolError(t *testing.T) { Severity: diag.Warning, Summary: `expected bool, found map`, Locations: []dyn.Location{{}}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -675,7 +675,7 @@ func TestNormalizeIntNil(t *testing.T) { Severity: diag.Warning, Summary: `expected a int value, found null`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -696,7 +696,7 @@ func TestNormalizeIntFromFloatError(t *testing.T) { Severity: diag.Warning, Summary: `cannot accurately represent "1.5" as integer due to precision loss`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -725,7 +725,7 @@ func TestNormalizeIntFromStringError(t *testing.T) { Severity: diag.Warning, Summary: `cannot parse "abc" as an integer`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -738,7 +738,7 @@ func TestNormalizeIntError(t *testing.T) { Severity: diag.Warning, Summary: `expected int, found map`, Locations: []dyn.Location{{}}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -759,7 +759,7 @@ func TestNormalizeFloatNil(t *testing.T) { Severity: diag.Warning, Summary: `expected a float value, found null`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -784,7 +784,7 @@ func TestNormalizeFloatFromIntError(t *testing.T) { Severity: diag.Warning, Summary: `cannot accurately represent "9007199254740993" as floating point number due to precision loss`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -813,7 +813,7 @@ func TestNormalizeFloatFromStringError(t *testing.T) { Severity: diag.Warning, Summary: `cannot parse "abc" as a floating point number`, Locations: []dyn.Location{vin.Location()}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) } @@ -826,7 +826,7 @@ func TestNormalizeFloatError(t *testing.T) { Severity: diag.Warning, Summary: `expected float, found map`, Locations: []dyn.Location{{}}, - Path: dyn.EmptyPath, + Paths: []dyn.Path{dyn.EmptyPath}, }, err[0]) }