Skip to content

Commit bdfbb4f

Browse files
denikIsaac
andcommitted
Generate typed walkers for the required and enum field validators
Replace the generated pattern tables, matched against every node of the dynamic configuration with a trie, by generated walkers that descend only along the patterns' paths of the typed configuration. Null, zero, pure-reference values and locations still come from the dyn.Value at each node (walk_node.go), so the diagnostics are unchanged. Slice of #7098. Co-authored-by: Isaac <no-reply@databricks.com>
1 parent cd53363 commit bdfbb4f

18 files changed

Lines changed: 7531 additions & 1196 deletions

File tree

‎.agents/rules/auto-generated-files.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ globs:
1414
- "internal/genkit/tagging.py"
1515
- "internal/mocks/**/*.go"
1616
- "bundle/direct/dresources/configs/*.generated.yml"
17-
- "bundle/internal/validation/generated/*.go"
17+
- "bundle/config/validate/*_walk.go"
1818
- "bundle/schema/jsonschema.json"
1919
- "python/databricks/bundles/version.py"
2020
- "python/databricks/bundles/*/__init__.py"
@@ -33,7 +33,7 @@ paths:
3333
- "internal/genkit/tagging.py"
3434
- "internal/mocks/**/*.go"
3535
- "bundle/direct/dresources/configs/*.generated.yml"
36-
- "bundle/internal/validation/generated/*.go"
36+
- "bundle/config/validate/*_walk.go"
3737
- "bundle/schema/jsonschema.json"
3838
- "python/databricks/bundles/version.py"
3939
- "python/databricks/bundles/*/__init__.py"

‎.agents/skills/bump-sdk/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ allowed-tools: Read, Edit, Write, Bash, Glob, Grep, WebFetch, AskUserQuestion
99

1010
The SDK version lives in `go.mod` (`github.com/databricks/databricks-sdk-go`) and the pinned spec SHA lives in `.codegen/_openapi_sha`.
1111
These two move as a pair; everything else in this skill is regenerated from them or is fallout you fix by hand.
12-
Do not hand-edit generated files (`.codegen/cli.json`, `cmd/workspace/*`, `cmd/account/*`, `bundle/schema/jsonschema.json`, `bundle/internal/validation/generated/*`, `bundle/direct/dresources/configs/*.generated.yml`, `python/databricks/bundles/**`); regenerate them.
12+
Do not hand-edit generated files (`.codegen/cli.json`, `cmd/workspace/*`, `cmd/account/*`, `bundle/schema/jsonschema.json`, `bundle/config/validate/*_walk.go`, `bundle/direct/dresources/configs/*.generated.yml`, `python/databricks/bundles/**`); regenerate them.
1313

1414
The Python tasks (`pydabs-*`, and the `pydabs-codegen` step inside `generate-check`) all run through `uv`. If one fails because `uv` is missing or because the host's `python3` is too old (e.g. 3.9), install `uv` (`curl -LsSf https://astral.sh/uv/install.sh | sh`) rather than touching the system Python: `uv run` provisions the interpreter each package pins (`>=3.10`, and `==3.13.*` under `python/codegen/`) and downloads it if needed. Do not chase the system Python version.
1515
The acceptance goldens are the exception: they need CI's `uv` index, see step 4.

‎Taskfile.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,17 +1008,17 @@ tasks:
10081008
- "go run ./bundle/internal/schema ./bundle/internal/schema ./bundle/schema/jsonschema.json .codegen/cli.json"
10091009

10101010
generate-validation:
1011-
desc: Generate enum and required field validation code
1011+
desc: Generate the walkers of the enum and required field validators
10121012
sources:
10131013
- "**/*.go"
10141014
- exclude: "**/*_test.go"
10151015
- go.mod
10161016
- go.sum
10171017
- "{{.EMBED_SOURCES}}"
10181018
generates:
1019-
- bundle/internal/validation/generated/**/*.go
1019+
- bundle/config/validate/*_walk.go
10201020
cmds:
1021-
- "sh -c 'go run ./bundle/internal/validation/. && gofmt -w -s ./bundle/internal/validation/generated'"
1021+
- "sh -c 'go run ./bundle/internal/validation/. && gofmt -w -s ./bundle/config/validate/required_walk.go ./bundle/config/validate/enum_walk.go'"
10221022

10231023
generate-direct:
10241024
desc: Generate direct engine config (apitypes + resources)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*_walk.go linguist-generated=true

‎bundle/config/validate/enum.go‎

Lines changed: 10 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ import (
77
"slices"
88

99
"github.com/databricks/cli/bundle"
10-
"github.com/databricks/cli/bundle/internal/validation/generated"
1110
"github.com/databricks/cli/libs/diag"
12-
"github.com/databricks/cli/libs/dyn"
13-
"github.com/databricks/cli/libs/dyn/dynvar"
1411
)
1512

1613
type enum struct{}
@@ -23,66 +20,17 @@ func (f *enum) Name() string {
2320
return "validate:enum"
2421
}
2522

23+
// Apply reports enum fields with invalid values. The walker is generated (enum_walk.go)
24+
// by bundle/internal/validation.
2625
func (f *enum) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
27-
diags := diag.Diagnostics{}
28-
29-
// Generate prefix tree for all enum fields.
30-
trie := &dyn.TrieNode{}
31-
for k := range generated.EnumFields {
32-
pattern, err := dyn.NewPatternFromString(k)
33-
if err != nil {
34-
return diag.FromErr(fmt.Errorf("invalid pattern %q for enum field validation: %w", k, err))
35-
}
36-
37-
err = trie.Insert(pattern)
38-
if err != nil {
39-
return diag.FromErr(fmt.Errorf("failed to insert pattern %q into trie: %w", k, err))
40-
}
41-
}
42-
43-
err := dyn.WalkReadOnly(b.Config.Value(), func(p dyn.Path, v dyn.Value) error {
44-
// If the path is not found in the prefix tree, we do not need to validate any enum
45-
// fields in it.
46-
pattern, ok := trie.SearchPath(p)
47-
if !ok {
48-
return nil
49-
}
50-
51-
// Get the string value for comparison
52-
strValue, ok := v.AsString()
53-
if !ok {
54-
return nil
55-
}
56-
57-
// Skip validation for values containing variable references (e.g.
58-
// ${resources.jobs.my_job.id}) since they are not yet resolved.
59-
if dynvar.ContainsVariableReference(strValue) {
60-
return nil
61-
}
62-
63-
// Get valid values for this pattern
64-
validValues := generated.EnumFields[pattern.String()]
65-
66-
// Check if the value is in the list of valid enum values
67-
validValue := slices.Contains(validValues, strValue)
68-
69-
if !validValue {
70-
// p is a slice of path components. We need to clone it before using it in diagnostics
71-
// since the WalkReadOnly function will mutate it while walking the config tree.
72-
73-
diags = diags.Append(diag.Diagnostic{
74-
Severity: diag.Warning,
75-
Summary: fmt.Sprintf("invalid value %q for enum field. Valid values are %v", strValue, validValues),
76-
Locations: v.Locations(),
77-
Paths: dyn.ToStructPaths(p),
78-
})
79-
}
80-
81-
return nil
82-
})
83-
if err != nil {
84-
return diag.FromErr(err)
85-
}
26+
w := &walker{diags: diag.Diagnostics{}}
27+
n := rootNode(b.Config.Value())
28+
enumConfigRoot(w, &n, &b.Config)
29+
diags := w.diags
30+
31+
// Start from the order of a walk of the configuration tree, so that the sort
32+
// below breaks ties between diagnostics as it did with that walk.
33+
sortByWalkOrder(b.Config.Value(), diags)
8634

8735
// Sort diagnostics to make them deterministic
8836
slices.SortFunc(diags, func(a, b diag.Diagnostic) int {

0 commit comments

Comments
 (0)