diff --git a/.agents/rules/auto-generated-files.md b/.agents/rules/auto-generated-files.md index d59f3d1470a..6cc0172e934 100644 --- a/.agents/rules/auto-generated-files.md +++ b/.agents/rules/auto-generated-files.md @@ -15,6 +15,9 @@ globs: - "internal/mocks/**/*.go" - "bundle/direct/dresources/configs/*.generated.yml" - "bundle/config/validate/*_walk.go" + - "bundle/config/walk/*_walk.go" + - "bundle/config/mutator/resourcemutator/defaults_walk.go" + - "bundle/config/resources_iterate.go" - "bundle/schema/jsonschema.json" - "python/databricks/bundles/version.py" - "python/databricks/bundles/*/__init__.py" @@ -34,6 +37,9 @@ paths: - "internal/mocks/**/*.go" - "bundle/direct/dresources/configs/*.generated.yml" - "bundle/config/validate/*_walk.go" + - "bundle/config/walk/*_walk.go" + - "bundle/config/mutator/resourcemutator/defaults_walk.go" + - "bundle/config/resources_iterate.go" - "bundle/schema/jsonschema.json" - "python/databricks/bundles/version.py" - "python/databricks/bundles/*/__init__.py" diff --git a/.agents/rules/style-guide-go.md b/.agents/rules/style-guide-go.md index ab2dad3b6e9..bedd86132ca 100644 --- a/.agents/rules/style-guide-go.md +++ b/.agents/rules/style-guide-go.md @@ -113,7 +113,7 @@ return fieldPaths ### Encoding -**RULE: When mutating an API response by round-tripping JSON, decode with `json.Decoder` + `UseNumber()`.** The naive `json.Marshal` → `map[string]any` → `Marshal` path corrupts any int64 larger than 2^53 (it degrades to a float64 mantissa — e.g. a real `spark_context_id`) and alphabetizes object keys. `libs/dyn/jsonloader` preserves key order but also lacks `UseNumber`, so it shares the int64 hazard. +**RULE: When mutating an API response by round-tripping JSON, decode with `json.Decoder` + `UseNumber()`.** The naive `json.Marshal` → `map[string]any` → `Marshal` path corrupts any int64 larger than 2^53 (it degrades to a float64 mantissa — e.g. a real `spark_context_id`) and alphabetizes object keys. **RULE: Be careful with `encoding/csv` `Writer.UseCRLF = true`.** It rewrites both record terminators AND embedded newlines inside quoted fields to `\r\n`, so tests for quoted multiline fields must expect `\r\n`, not just the line endings between rows. @@ -191,7 +191,7 @@ func NewService(deps ServiceDeps) *Service { ... } ### Configuration patterns -- Bundle config uses `dyn.Value` for dynamic typing +- Bundle config is typed structs; `libs/structs/structvar` carries `${...}` references and source locations - Config loading supports includes, variable interpolation, and target overrides - Schema generation is automated from Go struct tags diff --git a/.agents/skills/bump-sdk/SKILL.md b/.agents/skills/bump-sdk/SKILL.md index 2bd35224982..0fa02d3400f 100644 --- a/.agents/skills/bump-sdk/SKILL.md +++ b/.agents/skills/bump-sdk/SKILL.md @@ -9,7 +9,7 @@ allowed-tools: Read, Edit, Write, Bash, Glob, Grep, WebFetch, AskUserQuestion The SDK version lives in `go.mod` (`github.com/databricks/databricks-sdk-go`) and the pinned spec SHA lives in `.codegen/_openapi_sha`. These two move as a pair; everything else in this skill is regenerated from them or is fallout you fix by hand. -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. +Do not hand-edit generated files (`.codegen/cli.json`, `cmd/workspace/*`, `cmd/account/*`, `bundle/schema/jsonschema.json`, `bundle/config/validate/*_walk.go`, `bundle/config/walk/*_walk.go`, `bundle/direct/dresources/configs/*.generated.yml`, `python/databricks/bundles/**`); regenerate them. 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. The acceptance goldens are the exception: they need CI's `uv` index, see step 4. diff --git a/.github/workflows/python_push.yml b/.github/workflows/python_push.yml index 1a391719665..a2db548ddac 100644 --- a/.github/workflows/python_push.yml +++ b/.github/workflows/python_push.yml @@ -39,7 +39,7 @@ jobs: acceptance/bundle/python/ \ go.mod \ go.sum \ - libs/dyn/dynvar/testdata/reference_vectors.json \ + libs/structs/structvar/testdata/reference_vectors.json \ python/ \ tools/task/ \ ) diff --git a/.nextchanges/bundles/7098-presence.md b/.nextchanges/bundles/7098-presence.md new file mode 100644 index 00000000000..86765e9cffc --- /dev/null +++ b/.nextchanges/bundles/7098-presence.md @@ -0,0 +1 @@ +* `bundle validate` no longer treats a written zero value in a field that cannot record it (no `ForceSendFields`, e.g. `job_id: 0` in `run_job_task`) or an empty mapping (e.g. `presets: {}`) as set, and does not show the pipeline `development` deprecation warning for `development: true` under `mode: development`. ([#7098](https://github.com/databricks/cli/pull/7098)) diff --git a/.nextchanges/bundles/7098.md b/.nextchanges/bundles/7098.md new file mode 100644 index 00000000000..09e5f5ca992 --- /dev/null +++ b/.nextchanges/bundles/7098.md @@ -0,0 +1 @@ +* Speed up bundle commands on large bundles: configuration is kept as typed structs instead of being converted to and from a dynamic tree (`bundle validate` and `bundle plan` with 1000 jobs are about 10x faster and use about 3.5x less memory). ([#7098](https://github.com/databricks/cli/pull/7098)) diff --git a/AGENTS.md b/AGENTS.md index 02b63df8169..c0cf2704b63 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ This is the Databricks CLI, a command-line interface for interacting with Databr **RULE: Keep each PR focused on one change.** If you notice an unrelated cleanup, bug fix, or refactor while making your primary change, leave it alone or put it in a separate PR. Reviewers consistently ask to split mixed PRs, especially when a dependency bump or schema diff rides along with a feature change. -**RULE: Before adding a new helper, search the codebase for an existing one.** Common homes: `libs/` (shared utilities), `libs/databrickscfg/` (config), `libs/git/`, `libs/filer/`, `libs/cmdio/` (CLI I/O, spinners, prompts), `libs/env/` (env vars), `libs/testserver/`, `libs/structpath/` and `libs/dyn/` (path / dynamic values), `acceptance/bin/` (acceptance test helpers), `internal/mocks/` (generated mocks). A function that duplicates an existing name and signature in the same package is a compile error waiting to happen; grep before you name. +**RULE: Before adding a new helper, search the codebase for an existing one.** Common homes: `libs/` (shared utilities), `libs/databrickscfg/` (config), `libs/git/`, `libs/filer/`, `libs/cmdio/` (CLI I/O, spinners, prompts), `libs/env/` (env vars), `libs/testserver/`, `libs/structs/structpath/` and `libs/structs/structvar/` (paths / typed config values), `acceptance/bin/` (acceptance test helpers), `internal/mocks/` (generated mocks). A function that duplicates an existing name and signature in the same package is a compile error waiting to happen; grep before you name. # Development Commands @@ -73,7 +73,7 @@ GIT_EDITOR=true GIT_SEQUENCE_EDITOR=true VISUAL=true GIT_PAGER=cat git rebase or - `bundle/phases/` - High-level deployment phases **libs/** - Shared libraries and utilities -- `libs/dyn/` - Dynamic configuration value manipulation +- `libs/structs/structvar/` - Typed configuration values with references and locations - `libs/filer/` - File system abstraction (local, DBFS, workspace) - `libs/auth/` - Databricks authentication handling - `libs/sync/` - File synchronization between local and remote diff --git a/Taskfile.yml b/Taskfile.yml index f05e9731c8b..846f79d83c2 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -913,7 +913,7 @@ tasks: # generate-clijson so the SDK bumper can run the same cleanup after its own # update-sdk invocation. generate-clijson-post: - desc: Clean up genkit update-sdk output, then run bump-sdk-post (shared by generate-clijson and the SDK bumper) + desc: Clean up genkit update-sdk output (shared by generate-clijson and the SDK bumper) cmds: # genkit also writes .gitattributes, but that file is owned by # generate-cligen (which derives it from .gitattributes.manual plus the @@ -946,25 +946,6 @@ tasks: fi - "{{.GO_TOOL}} yamlfmt .github/workflows/tagging.yml" - task: ws - # Last, so generate-cligen rewrites the .gitattributes restored above. - # The SDK bumper aborts before opening a PR on any error from this task, - # and bump-sdk-post fails whenever the bump needs hand fixes, so ignore - # its error: the PR then carries what was regenerated. - - task: bump-sdk-post - ignore_error: true - - # The mechanical rest of an SDK bump once go.mod, .codegen/_openapi_sha and - # .codegen/cli.json are updated: regenerate every artifact derived from them, - # then refresh the acceptance goldens. generate-clijson-post runs it last; - # run it directly to redo it, e.g. after hand fixes. generate-refschema and - # test-update build the CLI, so it fails while the bump breaks compilation - # (code using a removed SDK field). Those fixes, exhaustruct wiring and the - # changelog PR link stay manual (.agents/skills/bump-sdk). - bump-sdk-post: - desc: Regenerate everything downstream of an SDK bump and refresh acceptance goldens - cmds: - - task: generate-check - - task: test-update # Refreshes out.fields.txt, which records the field paths / types emitted by # `bundle debug refschema` (see cmd/bundle/debug/refschema.go). It reflects @@ -1017,8 +998,11 @@ tasks: - "{{.EMBED_SOURCES}}" generates: - bundle/config/validate/*_walk.go + - bundle/config/walk/*_walk.go + - bundle/config/mutator/resourcemutator/defaults_walk.go + - bundle/config/resources_iterate.go cmds: - - "sh -c 'go run ./bundle/internal/validation/. && gofmt -w -s ./bundle/config/validate/required_walk.go ./bundle/config/validate/enum_walk.go'" + - "go run ./bundle/internal/validation/." generate-direct: desc: Generate direct engine config (apitypes + resources) diff --git a/acceptance/bundle/override/preset_clears_forced_schedule_reference/output.txt b/acceptance/bundle/override/preset_clears_forced_schedule_reference/output.txt index eb07e4ec538..8acc0bdd17b 100644 --- a/acceptance/bundle/override/preset_clears_forced_schedule_reference/output.txt +++ b/acceptance/bundle/override/preset_clears_forced_schedule_reference/output.txt @@ -1,5 +1,15 @@ >>> [CLI] bundle deploy +Warning: required field "quartz_cron_expression" is not set + at resources.quality_monitors.dst.schedule + in databricks.yml:39:21 + databricks.yml:30:17 + +Warning: required field "timezone_id" is not set + at resources.quality_monitors.dst.schedule + in databricks.yml:39:21 + databricks.yml:30:17 + Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/dev/files... Created quality_monitors.dst Created quality_monitors.src diff --git a/acceptance/bundle/override/preset_clears_forced_schedule_reference/test.toml b/acceptance/bundle/override/preset_clears_forced_schedule_reference/test.toml index 159efe02696..d25c11b418d 100644 --- a/acceptance/bundle/override/preset_clears_forced_schedule_reference/test.toml +++ b/acceptance/bundle/override/preset_clears_forced_schedule_reference/test.toml @@ -1 +1,2 @@ RecordRequests = true +Badness = "Required-field warnings are reported for a schedule that is a reference to a complete schedule." diff --git a/acceptance/bundle/python/pipeline-development-deprecated/output.txt b/acceptance/bundle/python/pipeline-development-deprecated/output.txt index 0150edea922..d1beb802f92 100644 --- a/acceptance/bundle/python/pipeline-development-deprecated/output.txt +++ b/acceptance/bundle/python/pipeline-development-deprecated/output.txt @@ -1,22 +1,14 @@ >>> uv run [UV_ARGS] -q [CLI] bundle validate -Warning: The "development" property of pipelines is deprecated. Use "mode: development" in your databricks.yml instead. See https://docs.databricks.com/dev-tools/bundles/deployment-modes. - at resources.pipelines.with_development.development - in resources.py:7:1 - Name: my_project Target: dev Workspace: User: [USERNAME] Path: /Workspace/Users/[USERNAME]/.bundle/my_project/dev -Found 1 warning +Validation OK! >>> uv run [UV_ARGS] -q [CLI] bundle validate --output json -Warning: The "development" property of pipelines is deprecated. Use "mode: development" in your databricks.yml instead. See https://docs.databricks.com/dev-tools/bundles/deployment-modes. - at resources.pipelines.with_development.development - in resources.py:7:1 - { "with_development": true, "without_development": true diff --git a/acceptance/bundle/strict_profile_match/default_profile/host/output.txt b/acceptance/bundle/strict_profile_match/default_profile/host/output.txt index 2d194ce17e2..c2043c72c28 100644 --- a/acceptance/bundle/strict_profile_match/default_profile/host/output.txt +++ b/acceptance/bundle/strict_profile_match/default_profile/host/output.txt @@ -18,7 +18,7 @@ Error: cannot resolve bundle auth configuration: resolve: strict profile matchin Name: strict-profile-match Target: classic_other_workspace Workspace: - Host: [DATABRICKS_URL] + Host: [DATABRICKS_URL]/?o=2222222222 Found 1 error diff --git a/acceptance/bundle/strict_profile_match/host/output.txt b/acceptance/bundle/strict_profile_match/host/output.txt index 6c3dcbeefab..54e165fc1b4 100644 --- a/acceptance/bundle/strict_profile_match/host/output.txt +++ b/acceptance/bundle/strict_profile_match/host/output.txt @@ -22,7 +22,7 @@ Error: cannot resolve bundle auth configuration: resolve: strict profile matchin Name: strict-profile-match Target: classic_other_workspace Workspace: - Host: [DATABRICKS_URL] + Host: [DATABRICKS_URL]/?o=2222222222 Found 1 error @@ -48,7 +48,7 @@ Error: cannot resolve bundle auth configuration: resolve: strict profile matchin Name: strict-profile-match Target: custom_host_other_workspace Workspace: - Host: [CUSTOM_HOST] + Host: [CUSTOM_HOST]/?o=2222222222 Found 1 error diff --git a/acceptance/bundle/summary/modified_status/output.txt b/acceptance/bundle/summary/modified_status/output.txt index 59d922c8b85..4c197a9b4ef 100644 --- a/acceptance/bundle/summary/modified_status/output.txt +++ b/acceptance/bundle/summary/modified_status/output.txt @@ -200,4 +200,4 @@ All files and directories at the following location will be deleted: /Workspace/ Destroy: 4 deleted >>> [CLI] bundle summary -o json -{} +null diff --git a/acceptance/bundle/validate/app_resource_duplicate_name_location/output.txt b/acceptance/bundle/validate/app_resource_duplicate_name_location/output.txt index a8eac9a3ace..77dc6d4c97a 100644 --- a/acceptance/bundle/validate/app_resource_duplicate_name_location/output.txt +++ b/acceptance/bundle/validate/app_resource_duplicate_name_location/output.txt @@ -2,7 +2,7 @@ >>> [CLI] bundle validate Warning: invalid value "INVALID" for enum field. Valid values are [CAN_MANAGE CAN_MANAGE_RUN CAN_VIEW IS_OWNER] at resources.apps.app.resources[0].job.permission - in databricks.yml:13:25 + in databricks.yml:17:25 Name: test-bundle Target: default @@ -15,7 +15,7 @@ Found 1 warning >>> [CLI] bundle validate -o json Warning: invalid value "INVALID" for enum field. Valid values are [CAN_MANAGE CAN_MANAGE_RUN CAN_VIEW IS_OWNER] at resources.apps.app.resources[0].job.permission - in databricks.yml:13:25 + in databricks.yml:17:25 [ { diff --git a/acceptance/bundle/validate/app_resource_duplicate_name_location/script b/acceptance/bundle/validate/app_resource_duplicate_name_location/script index 3531c01dcef..0f67cd891c8 100644 --- a/acceptance/bundle/validate/app_resource_duplicate_name_location/script +++ b/acceptance/bundle/validate/app_resource_duplicate_name_location/script @@ -1,4 +1,4 @@ -# A diagnostic on a field of a duplicated app resource entry points at the first entry when the duplicate's value is the same. +# A diagnostic on a field of a duplicated app resource entry points at the entry that is kept (the later one). errcode trace $CLI bundle validate # The entries are merged by name, the later one wins. diff --git a/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/output.txt b/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/output.txt index f5f6443e809..b836ae5fa73 100644 --- a/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/output.txt +++ b/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/output.txt @@ -1,9 +1,13 @@ >>> [CLI] bundle validate +Warning: unknown field: unexpected_after_failure + at bundle + in b.yml:2:3 + Error: cannot merge map with string Name: test-bundle -Found 1 error +Found 1 error and 1 warning Exit code: 1 diff --git a/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/script b/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/script index 6f34e8010d2..16c1bf033ce 100644 --- a/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/script +++ b/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/script @@ -1,2 +1,2 @@ -# Includes after one that fails to merge do not report their diagnostics. +# Includes after one that fails to merge still report their diagnostics. errcode trace $CLI bundle validate diff --git a/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/test.toml b/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/test.toml index b11d0b76cd4..d4ff7f89d2b 100644 --- a/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/test.toml +++ b/acceptance/bundle/validate/includes/merge_failure_short_circuits_diagnostics/test.toml @@ -1 +1 @@ -Badness = "The merge error has no location or path, and diagnostics of includes loaded after the failed merge (the unknown field in b.yml) are not reported, although they do not depend on the merge." +Badness = "The merge error has no location or path." diff --git a/acceptance/bundle/validate/nulls/resources_container/output.txt b/acceptance/bundle/validate/nulls/resources_container/output.txt index 1241151fda5..d44a21b582a 100644 --- a/acceptance/bundle/validate/nulls/resources_container/output.txt +++ b/acceptance/bundle/validate/nulls/resources_container/output.txt @@ -1,9 +1,9 @@ >>> [CLI] bundle validate -Error: expected a map at "resources", found nil - Name: test-bundle +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default -Found 1 error - -Exit code: 1 +Validation OK! diff --git a/acceptance/bundle/validate/nulls/resources_container/script b/acceptance/bundle/validate/nulls/resources_container/script index 253cc3d205d..afd083482ed 100644 --- a/acceptance/bundle/validate/nulls/resources_container/script +++ b/acceptance/bundle/validate/nulls/resources_container/script @@ -1,2 +1,2 @@ -# resources: null is rejected. +# resources: null is accepted. errcode trace $CLI bundle validate diff --git a/acceptance/bundle/validate/nulls/resources_container/test.toml b/acceptance/bundle/validate/nulls/resources_container/test.toml deleted file mode 100644 index 022e0f2c7e9..00000000000 --- a/acceptance/bundle/validate/nulls/resources_container/test.toml +++ /dev/null @@ -1 +0,0 @@ -Badness = 'An empty (null) resources section is an error without a location, while empty targets, variables or sync sections are accepted.' diff --git a/acceptance/bundle/validate/references/resolved_invalid_map_value/script b/acceptance/bundle/validate/references/resolved_invalid_map_value/script index 835edef52e9..cc299050ee2 100644 --- a/acceptance/bundle/validate/references/resolved_invalid_map_value/script +++ b/acceptance/bundle/validate/references/resolved_invalid_map_value/script @@ -1,2 +1,2 @@ -# A map value whose reference resolves to the wrong type is dropped, with a warning at its path. +# A map value whose reference resolves to the wrong type becomes an empty string, with a warning at its path. trace $CLI bundle validate -o json | jq .resources.jobs.j.tags diff --git a/acceptance/bundle/validate/references/resolved_invalid_sequence_item/script b/acceptance/bundle/validate/references/resolved_invalid_sequence_item/script index 61ba88b4d75..91e0e7c9cf0 100644 --- a/acceptance/bundle/validate/references/resolved_invalid_sequence_item/script +++ b/acceptance/bundle/validate/references/resolved_invalid_sequence_item/script @@ -1,2 +1,2 @@ -# A list element whose reference resolves to the wrong type is dropped, with a warning at its path. +# A list element whose reference resolves to the wrong type becomes an empty string, with a warning at its path. trace $CLI bundle validate -o json | jq .resources.jobs.j.email_notifications diff --git a/acceptance/bundle/validate/references/resolved_invalid_sequence_items/databricks.yml b/acceptance/bundle/validate/references/resolved_invalid_sequence_items/databricks.yml new file mode 100644 index 00000000000..94f3c6ee784 --- /dev/null +++ b/acceptance/bundle/validate/references/resolved_invalid_sequence_items/databricks.yml @@ -0,0 +1,15 @@ +bundle: + name: test-bundle + +variables: + bad: + type: complex + default: + x: 1 + +resources: + jobs: + j: + name: test + email_notifications: + on_failure: ["a@example.test", "${var.bad}", "b@example.test", "${var.bad}", "${var.bad}", "c@example.test"] diff --git a/acceptance/bundle/validate/references/resolved_invalid_sequence_items/out.test.toml b/acceptance/bundle/validate/references/resolved_invalid_sequence_items/out.test.toml new file mode 100644 index 00000000000..a927a5fbc06 --- /dev/null +++ b/acceptance/bundle/validate/references/resolved_invalid_sequence_items/out.test.toml @@ -0,0 +1,2 @@ +Cloud = false +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/validate/references/resolved_invalid_sequence_items/output.txt b/acceptance/bundle/validate/references/resolved_invalid_sequence_items/output.txt new file mode 100644 index 00000000000..e5cba00f5d2 --- /dev/null +++ b/acceptance/bundle/validate/references/resolved_invalid_sequence_items/output.txt @@ -0,0 +1,21 @@ + +>>> [CLI] bundle validate -o json +Warning: expected string, found map + at resources.jobs.j.email_notifications.on_failure[1] + in databricks.yml:15:40 + +Warning: expected string, found map + at resources.jobs.j.email_notifications.on_failure[3] + in databricks.yml:15:72 + +Warning: expected string, found map + at resources.jobs.j.email_notifications.on_failure[4] + in databricks.yml:15:86 + +{ + "on_failure": [ + "a@example.test", + "b@example.test", + "c@example.test" + ] +} diff --git a/acceptance/bundle/validate/references/resolved_invalid_sequence_items/script b/acceptance/bundle/validate/references/resolved_invalid_sequence_items/script new file mode 100644 index 00000000000..674c170cabb --- /dev/null +++ b/acceptance/bundle/validate/references/resolved_invalid_sequence_items/script @@ -0,0 +1,2 @@ +# Several list elements whose reference resolves to the wrong type are dropped, the others keep their order. +trace $CLI bundle validate -o json | jq .resources.jobs.j.email_notifications diff --git a/acceptance/bundle/validate/references/to_omitted_struct_defaults/output.txt b/acceptance/bundle/validate/references/to_omitted_struct_defaults/output.txt index 479ce92fe43..2888dfa1557 100644 --- a/acceptance/bundle/validate/references/to_omitted_struct_defaults/output.txt +++ b/acceptance/bundle/validate/references/to_omitted_struct_defaults/output.txt @@ -2,18 +2,10 @@ >>> [CLI] bundle validate -o json { "default": { - "actual_branch": "", - "branch": "", - "bundle_root_path": ".", - "commit": "", - "origin_url": "" + "bundle_root_path": "." }, "type": "complex", "value": { - "actual_branch": "", - "branch": "", - "bundle_root_path": ".", - "commit": "", - "origin_url": "" + "bundle_root_path": "." } } diff --git a/acceptance/bundle/validate/references/to_omitted_struct_defaults/script b/acceptance/bundle/validate/references/to_omitted_struct_defaults/script index cb09a7b5bec..0f7627f7115 100644 --- a/acceptance/bundle/validate/references/to_omitted_struct_defaults/script +++ b/acceptance/bundle/validate/references/to_omitted_struct_defaults/script @@ -1,3 +1,3 @@ # A reference to a struct that is not set in the configuration (here outside a git repository) -# resolves to the struct with its fields filled in, as empty values. +# resolves to the fields that are set; unset fields are omitted. trace $CLI bundle validate -o json | jq .variables.git_info diff --git a/acceptance/bundle/validate/required/databricks.yml b/acceptance/bundle/validate/required/databricks.yml index 82175fb553c..0dc5e459fb3 100644 --- a/acceptance/bundle/validate/required/databricks.yml +++ b/acceptance/bundle/validate/required/databricks.yml @@ -19,7 +19,7 @@ resources: jobs: my_job_1: tasks: - # job_id being 0 should not trigger a warning. + # job_id 0 is not set either: RunJobTask has no ForceSendFields to record it. - task_key: "task_key1" run_job_task: job_id: 0 diff --git a/acceptance/bundle/validate/required/output.txt b/acceptance/bundle/validate/required/output.txt index ae3e6dad2f8..b16e64d49e1 100644 --- a/acceptance/bundle/validate/required/output.txt +++ b/acceptance/bundle/validate/required/output.txt @@ -4,6 +4,10 @@ Warning: required field "catalog_name" is not set at resources.volumes.my_volume in databricks.yml:35:7 +Warning: required field "job_id" is not set + at resources.jobs.my_job_1.tasks[0].run_job_task + in databricks.yml:25:13 + Warning: required field "job_id" is not set at resources.jobs.my_job_1.tasks[1].run_job_task in databricks.yml:29:24 @@ -26,4 +30,4 @@ Workspace: User: [USERNAME] Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default -Found 5 warnings +Found 6 warnings diff --git a/acceptance/bundle/validate/targets/inherited_from_root_merge/output.txt b/acceptance/bundle/validate/targets/inherited_from_root_merge/output.txt index d3be1ebf0eb..53bc56aa958 100644 --- a/acceptance/bundle/validate/targets/inherited_from_root_merge/output.txt +++ b/acceptance/bundle/validate/targets/inherited_from_root_merge/output.txt @@ -1,6 +1,10 @@ >>> [CLI] bundle validate -t dev -o json +Warning: expected map, found string + at targets.dev.variables.x + in databricks.yml:13:14 + { - "default": "overridden", - "value": "overridden" + "default": "base", + "value": "base" } diff --git a/acceptance/bundle/validate/targets/inherited_from_root_merge/test.toml b/acceptance/bundle/validate/targets/inherited_from_root_merge/test.toml new file mode 100644 index 00000000000..bfb5631e081 --- /dev/null +++ b/acceptance/bundle/validate/targets/inherited_from_root_merge/test.toml @@ -0,0 +1 @@ +Badness = "A scalar target variable override inherited through a merge key is not applied (a warning says: expected map, found string)." diff --git a/acceptance/bundle/validate/targets/scalar_rewrite_diagnostic/output.txt b/acceptance/bundle/validate/targets/scalar_rewrite_diagnostic/output.txt index 8020d180559..9682b7b460d 100644 --- a/acceptance/bundle/validate/targets/scalar_rewrite_diagnostic/output.txt +++ b/acceptance/bundle/validate/targets/scalar_rewrite_diagnostic/output.txt @@ -1,8 +1,13 @@ >>> [CLI] bundle validate -Error: failed to rewrite [TEST_TMP_DIR]/databricks.yml: expected a map or sequence, found bool +Warning: expected map, found bool + at targets + in databricks.yml:4:10 +Name: test-bundle +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default -Found 1 error - -Exit code: 1 +Found 1 warning diff --git a/acceptance/bundle/validate/targets/scalar_rewrite_diagnostic/script b/acceptance/bundle/validate/targets/scalar_rewrite_diagnostic/script index 4f218738be7..400175c2880 100644 --- a/acceptance/bundle/validate/targets/scalar_rewrite_diagnostic/script +++ b/acceptance/bundle/validate/targets/scalar_rewrite_diagnostic/script @@ -1,2 +1,2 @@ -# A scalar targets section is an error. +# A scalar targets section is reported with a warning at its location. errcode trace $CLI bundle validate diff --git a/acceptance/bundle/validate/targets/scalar_rewrite_diagnostic/test.toml b/acceptance/bundle/validate/targets/scalar_rewrite_diagnostic/test.toml deleted file mode 100644 index b1bb8151234..00000000000 --- a/acceptance/bundle/validate/targets/scalar_rewrite_diagnostic/test.toml +++ /dev/null @@ -1 +0,0 @@ -Badness = 'The error names an internal step ("failed to rewrite") and has no location, instead of reporting a type error at targets.' diff --git a/acceptance/bundle/validate/variables/complex_default_nested_timestamp/output.txt b/acceptance/bundle/validate/variables/complex_default_nested_timestamp/output.txt index 3c236fb1c2b..d44a21b582a 100644 --- a/acceptance/bundle/validate/variables/complex_default_nested_timestamp/output.txt +++ b/acceptance/bundle/validate/variables/complex_default_nested_timestamp/output.txt @@ -1,10 +1,9 @@ >>> [CLI] bundle validate -Warn: unable to convert typed configuration to dynamic configuration: cannot convert struct field to dynamic type "time": src=time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC) ref=time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC) -Error: exit error: cannot convert struct field to dynamic type "time": src=time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC) ref=time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC) - Name: test-bundle +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default -Found 1 error - -Exit code: 1 +Validation OK! diff --git a/acceptance/bundle/validate/variables/complex_default_nested_timestamp/test.toml b/acceptance/bundle/validate/variables/complex_default_nested_timestamp/test.toml deleted file mode 100644 index b1b00a5e46a..00000000000 --- a/acceptance/bundle/validate/variables/complex_default_nested_timestamp/test.toml +++ /dev/null @@ -1 +0,0 @@ -Badness = "A valid config fails with an internal conversion error: the nested timestamp cannot be converted back to the dynamic configuration." diff --git a/acceptance/bundle/validate/variables/complex_unknown_field_location/output.txt b/acceptance/bundle/validate/variables/complex_unknown_field_location/output.txt index 118399cba34..a1405eadd8d 100644 --- a/acceptance/bundle/validate/variables/complex_unknown_field_location/output.txt +++ b/acceptance/bundle/validate/variables/complex_unknown_field_location/output.txt @@ -2,7 +2,6 @@ >>> [CLI] bundle validate Warning: unknown field: misspelled_field at resources.jobs.j - in databricks.yml:9:7 Name: test-bundle Target: default diff --git a/acceptance/bundle/validate/variables/complex_unknown_field_location/test.toml b/acceptance/bundle/validate/variables/complex_unknown_field_location/test.toml new file mode 100644 index 00000000000..ea2c5233725 --- /dev/null +++ b/acceptance/bundle/validate/variables/complex_unknown_field_location/test.toml @@ -0,0 +1 @@ +Badness = "The unknown field warning has no location; it should point at the key in the variable default (databricks.yml:9:7)." diff --git a/acceptance/bundle/validate/variables/target_inherited_from_target_merge/output.txt b/acceptance/bundle/validate/variables/target_inherited_from_target_merge/output.txt index d3be1ebf0eb..2b544427b79 100644 --- a/acceptance/bundle/validate/variables/target_inherited_from_target_merge/output.txt +++ b/acceptance/bundle/validate/variables/target_inherited_from_target_merge/output.txt @@ -1,6 +1,10 @@ >>> [CLI] bundle validate -t dev -o json +Warning: expected map, found string + at targets.dev.variables.x + in databricks.yml:11:10 + { - "default": "overridden", - "value": "overridden" + "default": "base", + "value": "base" } diff --git a/acceptance/bundle/validate/variables/target_inherited_from_target_merge/test.toml b/acceptance/bundle/validate/variables/target_inherited_from_target_merge/test.toml new file mode 100644 index 00000000000..bfb5631e081 --- /dev/null +++ b/acceptance/bundle/validate/variables/target_inherited_from_target_merge/test.toml @@ -0,0 +1 @@ +Badness = "A scalar target variable override inherited through a merge key is not applied (a warning says: expected map, found string)." diff --git a/acceptance/bundle/validate/variables/target_timestamp_shorthand/output.txt b/acceptance/bundle/validate/variables/target_timestamp_shorthand/output.txt index 07eae4cbd80..3b2992722ad 100644 --- a/acceptance/bundle/validate/variables/target_timestamp_shorthand/output.txt +++ b/acceptance/bundle/validate/variables/target_timestamp_shorthand/output.txt @@ -1,10 +1,6 @@ >>> [CLI] bundle validate -t dev -o json -Warning: expected map, found time - at targets.dev.variables.x - in databricks.yml:11:10 - { - "default": "base", - "value": "base" + "default": "2020-01-02", + "value": "2020-01-02" } diff --git a/acceptance/bundle/validate/variables/target_timestamp_shorthand/test.toml b/acceptance/bundle/validate/variables/target_timestamp_shorthand/test.toml deleted file mode 100644 index 83f0c9957ac..00000000000 --- a/acceptance/bundle/validate/variables/target_timestamp_shorthand/test.toml +++ /dev/null @@ -1 +0,0 @@ -Badness = 'The timestamp shorthand is dropped with "expected map, found time" instead of being used as the default.' diff --git a/acceptance/bundle/validate/yaml_anchors/alias_mutates_complex_default/output.txt b/acceptance/bundle/validate/yaml_anchors/alias_mutates_complex_default/output.txt index e324cc30cf1..21b26325bcb 100644 --- a/acceptance/bundle/validate/yaml_anchors/alias_mutates_complex_default/output.txt +++ b/acceptance/bundle/validate/yaml_anchors/alias_mutates_complex_default/output.txt @@ -7,11 +7,15 @@ }, "payload": { "default": { - "x": "overridden" + "x": { + "default": "overridden" + } }, "type": "complex", "value": { - "x": "overridden" + "x": { + "default": "overridden" + } } } } diff --git a/acceptance/bundle/validate/yaml_anchors/alias_mutates_complex_default/test.toml b/acceptance/bundle/validate/yaml_anchors/alias_mutates_complex_default/test.toml new file mode 100644 index 00000000000..b277d32330e --- /dev/null +++ b/acceptance/bundle/validate/yaml_anchors/alias_mutates_complex_default/test.toml @@ -0,0 +1 @@ +Badness = "The shorthand override rewrites the aliased complex default in place: its x becomes {default: overridden}." diff --git a/acceptance/bundle/validate/yaml_anchors/duplicate_merge_keys/output.txt b/acceptance/bundle/validate/yaml_anchors/duplicate_merge_keys/output.txt index 2efbf8faf73..0e1631fa14b 100644 --- a/acceptance/bundle/validate/yaml_anchors/duplicate_merge_keys/output.txt +++ b/acceptance/bundle/validate/yaml_anchors/duplicate_merge_keys/output.txt @@ -1,9 +1,15 @@ >>> [CLI] bundle validate -t dev -Error: duplicate YAML merge key ('<<') is not allowed; to merge multiple maps, use a sequence: '<<: [*anchor1, *anchor2]' - in databricks.yml:18:7 - - -Found 1 error - -Exit code: 1 +Name: test-bundle +Target: dev +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/dev + +Validation OK! + +>>> [CLI] bundle validate -t dev -o json +{ + "default": "second", + "value": "second" +} diff --git a/acceptance/bundle/validate/yaml_anchors/duplicate_merge_keys/script b/acceptance/bundle/validate/yaml_anchors/duplicate_merge_keys/script index e316f5883d8..7ce2255cfee 100644 --- a/acceptance/bundle/validate/yaml_anchors/duplicate_merge_keys/script +++ b/acceptance/bundle/validate/yaml_anchors/duplicate_merge_keys/script @@ -1,2 +1,4 @@ # Two '<<' keys in target variables are an error. errcode trace $CLI bundle validate -t dev +# What was merged into the variable: +errcode trace $CLI bundle validate -t dev -o json | jq .variables.x diff --git a/acceptance/bundle/validate/yaml_anchors/duplicate_merge_keys/test.toml b/acceptance/bundle/validate/yaml_anchors/duplicate_merge_keys/test.toml new file mode 100644 index 00000000000..6f02a67bf2c --- /dev/null +++ b/acceptance/bundle/validate/yaml_anchors/duplicate_merge_keys/test.toml @@ -0,0 +1 @@ +Badness = "A second '<<' key in a mapping is accepted silently instead of being rejected as a duplicate key." diff --git a/acceptance/bundle/validate/yaml_anchors/inline_merge_mapping/output.txt b/acceptance/bundle/validate/yaml_anchors/inline_merge_mapping/output.txt index 85dc1ee379b..ce9d6f908d4 100644 --- a/acceptance/bundle/validate/yaml_anchors/inline_merge_mapping/output.txt +++ b/acceptance/bundle/validate/yaml_anchors/inline_merge_mapping/output.txt @@ -1,8 +1,9 @@ >>> [CLI] bundle validate -Error: failed to load [TEST_TMP_DIR]/databricks.yml: yaml ([TEST_TMP_DIR]/databricks.yml:11:11): map merge requires map or sequence of maps as the value +Name: test-bundle +Target: dev +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/dev - -Found 1 error - -Exit code: 1 +Validation OK! diff --git a/acceptance/bundle/validate/yaml_anchors/inline_merge_mapping/test.toml b/acceptance/bundle/validate/yaml_anchors/inline_merge_mapping/test.toml deleted file mode 100644 index 84101454028..00000000000 --- a/acceptance/bundle/validate/yaml_anchors/inline_merge_mapping/test.toml +++ /dev/null @@ -1 +0,0 @@ -Badness = "The YAML merge key spec allows a single mapping as the value of the merge key, but it is rejected." diff --git a/acceptance/bundle/validate/yaml_anchors/invalid_merge_sequence_member/output.txt b/acceptance/bundle/validate/yaml_anchors/invalid_merge_sequence_member/output.txt index 770f0193a58..166aeb69530 100644 --- a/acceptance/bundle/validate/yaml_anchors/invalid_merge_sequence_member/output.txt +++ b/acceptance/bundle/validate/yaml_anchors/invalid_merge_sequence_member/output.txt @@ -1,8 +1,15 @@ >>> [CLI] bundle validate -t dev -Error: failed to load [TEST_TMP_DIR]/databricks.yml: yaml ([TEST_TMP_DIR]/databricks.yml:15:11): map merge requires map or sequence of maps as the value - - -Found 1 error - -Exit code: 1 +Name: test-bundle +Target: dev +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/dev + +Validation OK! + +>>> [CLI] bundle validate -t dev -o json +{ + "default": "overridden", + "value": "overridden" +} diff --git a/acceptance/bundle/validate/yaml_anchors/invalid_merge_sequence_member/script b/acceptance/bundle/validate/yaml_anchors/invalid_merge_sequence_member/script index 497e2fefa39..f7617ec1ae1 100644 --- a/acceptance/bundle/validate/yaml_anchors/invalid_merge_sequence_member/script +++ b/acceptance/bundle/validate/yaml_anchors/invalid_merge_sequence_member/script @@ -1,2 +1,4 @@ # A scalar among the maps merged with '<<: [...]' is an error. errcode trace $CLI bundle validate -t dev +# What was merged into the variable: +errcode trace $CLI bundle validate -t dev -o json | jq .variables.x diff --git a/acceptance/bundle/validate/yaml_anchors/invalid_merge_sequence_member/test.toml b/acceptance/bundle/validate/yaml_anchors/invalid_merge_sequence_member/test.toml new file mode 100644 index 00000000000..fbde0163df7 --- /dev/null +++ b/acceptance/bundle/validate/yaml_anchors/invalid_merge_sequence_member/test.toml @@ -0,0 +1 @@ +Badness = "A scalar among the maps merged with '<<: [...]' is accepted silently instead of being rejected." diff --git a/acceptance/bundle/validate/yaml_duplicate_keys/shadowed_invalid_subtree/output.txt b/acceptance/bundle/validate/yaml_duplicate_keys/shadowed_invalid_subtree/output.txt index 8c0f7149fad..d44a21b582a 100644 --- a/acceptance/bundle/validate/yaml_duplicate_keys/shadowed_invalid_subtree/output.txt +++ b/acceptance/bundle/validate/yaml_duplicate_keys/shadowed_invalid_subtree/output.txt @@ -1,8 +1,9 @@ >>> [CLI] bundle validate -Error: failed to load [TEST_TMP_DIR]/databricks.yml: yaml ([TEST_TMP_DIR]/databricks.yml:9:9): invalid key tag: !!int +Name: test-bundle +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default - -Found 1 error - -Exit code: 1 +Validation OK! diff --git a/acceptance/bundle/validate/yaml_duplicate_keys/shadowed_invalid_subtree/script b/acceptance/bundle/validate/yaml_duplicate_keys/shadowed_invalid_subtree/script index 58b74e2f574..a2059f0648f 100644 --- a/acceptance/bundle/validate/yaml_duplicate_keys/shadowed_invalid_subtree/script +++ b/acceptance/bundle/validate/yaml_duplicate_keys/shadowed_invalid_subtree/script @@ -1,2 +1,2 @@ -# A value replaced by a later duplicate key is still loaded, so its invalid (integer) key is rejected. +# An invalid (integer) key inside a value that a later duplicate key replaces. errcode trace $CLI bundle validate diff --git a/acceptance/bundle/validate/yaml_duplicate_keys/target_variable_default/output.txt b/acceptance/bundle/validate/yaml_duplicate_keys/target_variable_default/output.txt index 5c429a0449c..bf00392ad0b 100644 --- a/acceptance/bundle/validate/yaml_duplicate_keys/target_variable_default/output.txt +++ b/acceptance/bundle/validate/yaml_duplicate_keys/target_variable_default/output.txt @@ -1,6 +1,10 @@ >>> [CLI] bundle validate -t dev -o json { - "default": "second", - "value": "second" + "default": { + "default": "second" + }, + "value": { + "default": "second" + } } diff --git a/acceptance/bundle/validate/yaml_duplicate_keys/target_variable_default/test.toml b/acceptance/bundle/validate/yaml_duplicate_keys/target_variable_default/test.toml index 42d04e8a1ef..7155c7ccc3a 100644 --- a/acceptance/bundle/validate/yaml_duplicate_keys/target_variable_default/test.toml +++ b/acceptance/bundle/validate/yaml_duplicate_keys/target_variable_default/test.toml @@ -1 +1 @@ -Badness = 'The duplicate default key is accepted silently (the later one wins); YAML requires keys to be unique.' +Badness = "The duplicate default key is accepted silently (the later one wins), and the override is then used as the default value instead of as a full definition." diff --git a/acceptance/invariant_test.go b/acceptance/invariant_test.go index ad8e1d14022..99ac4f4707c 100644 --- a/acceptance/invariant_test.go +++ b/acceptance/invariant_test.go @@ -8,10 +8,9 @@ import ( "testing" "github.com/databricks/cli/bundle/config" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlloader" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v3" ) const invariantConfigsDir = "bundle/invariant/configs" @@ -88,31 +87,22 @@ func scanInvariantConfigs(t *testing.T) (present, withPermissions, withGrants ma contents, err := os.ReadFile(path) require.NoError(t, err) - v, err := yamlloader.LoadYAML(path, strings.NewReader(string(contents))) - require.NoError(t, err, "failed to parse %s", path) - - resources := v.Get("resources") - if resources.Kind() != dyn.KindMap { - // Some configs (e.g. PyDABs) declare resources outside of YAML. - continue + var doc struct { + Resources map[string]any `yaml:"resources"` } + require.NoError(t, yaml.Unmarshal(contents, &doc), "failed to parse %s", path) - for _, group := range resources.MustMap().Pairs() { - groupName := group.Key.MustString() + // Some configs (e.g. PyDABs) declare resources outside of YAML. + for groupName, group := range doc.Resources { present[groupName] = true - if group.Value.Kind() != dyn.KindMap { - continue - } - for _, resource := range group.Value.MustMap().Pairs() { - cfg := resource.Value - if cfg.Kind() != dyn.KindMap { - continue - } - if cfg.Get("permissions").Kind() != dyn.KindInvalid { + resources, _ := group.(map[string]any) + for _, resource := range resources { + cfg, _ := resource.(map[string]any) + if _, ok := cfg["permissions"]; ok { withPermissions[groupName] = true } - if cfg.Get("grants").Kind() != dyn.KindInvalid { + if _, ok := cfg["grants"]; ok { withGrants[groupName] = true } } diff --git a/bundle/apps/validate.go b/bundle/apps/validate.go index 2ab3384f991..d64422bfd04 100644 --- a/bundle/apps/validate.go +++ b/bundle/apps/validate.go @@ -6,9 +6,9 @@ import ( "regexp" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/apps" ) @@ -92,34 +92,12 @@ type appResourceReference struct { permission string } -// hasPermissions checks if a bundle resource at the given dyn path has a non-empty permissions list. -func hasPermissions(b *bundle.Bundle, resourcePath string) bool { - pv, err := dyn.Get(b.Config.Value(), resourcePath+".permissions") - if err != nil { - return false - } - s, ok := pv.AsSequence() - return ok && len(s) > 0 -} - // hasAppSPInPermissions checks if any permission entry for the given resource // references the app's service principal via variable interpolation. -func hasAppSPInPermissions(b *bundle.Bundle, resourcePath, appKey string) bool { +func hasAppSPInPermissions(perms config.PermissionList, appKey string) bool { appSPRef := fmt.Sprintf("${resources.apps.%s.service_principal_client_id}", appKey) - pv, err := dyn.Get(b.Config.Value(), resourcePath+".permissions") - if err != nil { - return false - } - s, ok := pv.AsSequence() - if !ok { - return false - } - for _, entry := range s { - spn, err := dyn.Get(entry, "service_principal_name") - if err != nil { - continue - } - if str, ok := spn.AsString(); ok && str == appSPRef { + for i := range perms.Len() { + if perms.At(i).ServicePrincipalName == appSPRef { return true } } @@ -146,12 +124,12 @@ func warnForAppResourcePermissions(b *bundle.Bundle, appKey string, app *resourc } refType, resourceKey := matches[1], matches[2] - resourcePath := fmt.Sprintf("resources.%s.%s", refType, resourceKey) - if !hasPermissions(b, resourcePath) { + perms, ok := b.Config.Resources.PermissionsOf(refType, resourceKey) + if !ok || perms.Len() == 0 { continue } - if hasAppSPInPermissions(b, resourcePath, appKey) { + if hasAppSPInPermissions(perms, appKey) { continue } diff --git a/bundle/artifacts/expand_globs.go b/bundle/artifacts/expand_globs.go index cc5d1f3fb60..933f9e0c537 100644 --- a/bundle/artifacts/expand_globs.go +++ b/bundle/artifacts/expand_globs.go @@ -6,18 +6,21 @@ import ( "path/filepath" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/patchwheel" + "github.com/databricks/cli/libs/structs/structpath" ) -func createGlobError(v dyn.Value, p dyn.Path, message string) diag.Diagnostic { - // The pattern contained in v is an absolute path. +func createGlobError(source string, locs []diag.Location, p *structpath.PathNode, message string) diag.Diagnostic { + var loc diag.Location + if len(locs) > 0 { + loc = locs[0] + } + // The pattern is an absolute path. // Make it relative to the value's location to make it more readable. - source := v.MustString() - if l := v.Location(); l.File != "" { - rel, err := filepath.Rel(filepath.Dir(l.File), source) + if loc.File != "" { + rel, err := filepath.Rel(filepath.Dir(loc.File), source) if err == nil { source = rel } @@ -26,8 +29,8 @@ func createGlobError(v dyn.Value, p dyn.Path, message string) diag.Diagnostic { return diag.Diagnostic{ Severity: diag.Error, Summary: fmt.Sprintf("%s: %s", source, message), - Locations: []diag.Location{v.Location()}, - Paths: dyn.ToStructPaths(p), + Locations: []diag.Location{loc}, + Paths: []*structpath.PathNode{p}, } } @@ -42,82 +45,75 @@ func (e expandGlobs) Name() string { func (e expandGlobs) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { // Base path for this mutator. // This path is set with the list of expanded globs when done. - base := dyn.NewPath( - dyn.Key("artifacts"), - dyn.Key(e.name), - dyn.Key("files"), - ) - - // Pattern to match the source key in the files sequence. - pattern := dyn.NewPatternFromPath(base).Append( - dyn.AnyIndex(), - dyn.Key("source"), - ) + base := structpath.NewPath(nil, "artifacts", e.name, "files") + + artifact := b.Config.Artifacts[e.name] + if artifact == nil { + return nil + } var diags diag.Diagnostics - err := b.Config.Mutate(func(rootv dyn.Value) (dyn.Value, error) { - var output []dyn.Value - _, err := dyn.MapByPattern(rootv, pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - if v.Kind() != dyn.KindString { - return v, nil - } - - source := v.MustString() - - // Expand any glob reference in files source path - matches, err := filepath.Glob(source) - if err != nil { - diags = diags.Append(createGlobError(v, p, err.Error())) - - // Drop this value from the list; this does not matter since we've raised an error anyway - return v, nil - } - - // Note, we're applying this for all artifact types, not just "whl". - // Rationale: - // 1. type is optional - // 2. if you have wheels in other artifact type, maybe you still want the filter logic? impossible to say. - matches = patchwheel.FilterLatestWheels(ctx, matches) - - if len(matches) == 1 && matches[0] == source { - // No glob expansion was performed. - // Keep node unchanged. We need to ensure that "patched" field remains and not wiped out by code below. - parent, err := dyn.GetByPath(rootv, p[0:len(p)-1]) - if err != nil { - log.Debugf(ctx, "Failed to get parent of %s", p.String()) - } else { - output = append(output, parent) - } - return v, nil - } - - if len(matches) == 0 { - diags = diags.Append(createGlobError(v, p, "no matching files")) - - // Drop this value from the list; this does not matter since we've raised an error anyway - return v, nil - } - - for _, match := range matches { - output = append(output, dyn.V( - map[string]dyn.Value{ - "source": dyn.NewValue(match, v.Locations()), - }, - )) - } - - return v, nil - }) - - if err != nil || diags.HasError() { - return rootv, err + var output []config.ArtifactFile + var sources [][]int + var locations [][]diag.Location + for index, f := range artifact.Files { + np := structpath.NewPath(base, index, "source") + locs := b.Config.GetLocationsOfExact(np) + // An empty source is missing (ArtifactFile has no ForceSendFields), which the + // required-field validation reports. + if f.Source == "" { + continue + } + source := f.Source + + // Expand any glob reference in files source path + matches, err := filepath.Glob(source) + if err != nil { + diags = diags.Append(createGlobError(source, locs, np, err.Error())) + + // Drop this value from the list; this does not matter since we've raised an error anyway + continue + } + + // Note, we're applying this for all artifact types, not just "whl". + // Rationale: + // 1. type is optional + // 2. if you have wheels in other artifact type, maybe you still want the filter logic? impossible to say. + matches = patchwheel.FilterLatestWheels(ctx, matches) + + if len(matches) == 1 && matches[0] == source { + // No glob expansion was performed. + // Keep node unchanged. We need to ensure that "patched" field remains and not wiped out by code below. + output = append(output, artifact.Files[index]) + sources = append(sources, []int{index}) + locations = append(locations, nil) + continue } - // Set the expanded globs back into the configuration. - return dyn.SetByPath(rootv, base, dyn.V(output)) - }) - if err != nil { - diags = diags.Extend(diag.FromErr(err)) + if len(matches) == 0 { + diags = diags.Append(createGlobError(source, locs, np, "no matching files")) + + // Drop this value from the list; this does not matter since we've raised an error anyway + continue + } + + for _, match := range matches { + output = append(output, config.ArtifactFile{Source: match}) + sources = append(sources, []int{index}) + locations = append(locations, locs) + } + } + if diags.HasError() { + return diags + } + + // Set the expanded globs back into the configuration. + artifact.Files = output + b.Config.UpdateSequence(base, sources) + for i, locs := range locations { + if locs != nil { + b.Config.SetLocations(structpath.NewPath(base, i, "source"), locs) + } } return diags diff --git a/bundle/artifacts/prepare.go b/bundle/artifacts/prepare.go index e285eb1ee30..f15add69f74 100644 --- a/bundle/artifacts/prepare.go +++ b/bundle/artifacts/prepare.go @@ -17,6 +17,7 @@ import ( "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" "github.com/databricks/cli/libs/python" + "github.com/databricks/cli/libs/structs/structpath" ) func Prepare() bundle.Mutator { @@ -61,7 +62,7 @@ func (m *prepare) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics logdiag.LogError(ctx, fmt.Errorf("artifact %q: a tgz artifact needs a `files` entry naming the output path", artifactName)) } - l := b.Config.GetLocation("artifacts." + artifactName) + l := b.Config.GetLocationOfExact(structpath.NewPath(nil, "artifacts", artifactName)) dirPath := filepath.Dir(l.File) // Check if source paths are absolute, if not, make them absolute diff --git a/bundle/bundle_test.go b/bundle/bundle_test.go index 9bd667afe62..05df7f14228 100644 --- a/bundle/bundle_test.go +++ b/bundle/bundle_test.go @@ -9,7 +9,6 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/env" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/logdiag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -157,9 +156,6 @@ func TestBundleGetResourceConfigJobsPointer(t *testing.T) { }, } - // Initialize the dynamic representation so GetResourceConfig can query it. - require.NoError(t, rootCfg.Mutate(func(v dyn.Value) (dyn.Value, error) { return v, nil })) - b := &Bundle{Config: rootCfg} res, err := b.Config.GetResourceConfig("resources.jobs.my_job") diff --git a/bundle/config/.gitattributes b/bundle/config/.gitattributes new file mode 100644 index 00000000000..e127006baf8 --- /dev/null +++ b/bundle/config/.gitattributes @@ -0,0 +1 @@ +resources_iterate.go linguist-generated=true diff --git a/bundle/config/libraries_iterate.go b/bundle/config/libraries_iterate.go new file mode 100644 index 00000000000..6b0ff477bcb --- /dev/null +++ b/bundle/config/libraries_iterate.go @@ -0,0 +1,108 @@ +package config + +import ( + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go/service/compute" + "github.com/databricks/databricks-sdk-go/service/jobs" +) + +// The iterators below visit the places that hold libraries, in the order of the job +// names (and task indices). They visit lists that are set (not nil); the path is built +// for each visited list. + +// IterateJobTasks calls fn for the tasks of all jobs, or (nested) for the tasks of +// their for_each_task, with the path of the task, until fn returns false. +func (r *Resources) IterateJobTasks(nested bool, fn func(p *structpath.PathNode, task *jobs.Task) bool) { + for _, name := range sortedKeys(r.Jobs) { + job := r.Jobs[name] + if job == nil { + continue + } + for i := range job.Tasks { + task := &job.Tasks[i] + if nested { + if task.ForEachTask == nil { + continue + } + task = &task.ForEachTask.Task + } + p := structpath.NewPath(nil, "resources", "jobs", name, "tasks", i) + if nested { + p = structpath.NewPath(p, "for_each_task", "task") + } + if !fn(p, task) { + return + } + } + } +} + +// IterateTaskLibraries calls fn for the libraries of the tasks of all jobs, or (nested) +// of the tasks of their for_each_task, until fn returns false. +func (r *Resources) IterateTaskLibraries(nested bool, fn func(p *structpath.PathNode, libs *[]compute.Library) bool) { + r.IterateJobTasks(nested, func(p *structpath.PathNode, task *jobs.Task) bool { + if task.Libraries == nil { + return true + } + return fn(structpath.NewPath(p, "libraries"), &task.Libraries) + }) +} + +// IterateAiRuntimeCodeSources calls fn for the code_source_path of the AI Runtime tasks +// of all jobs, or (nested) of the tasks of their for_each_task, until fn returns false. +func (r *Resources) IterateAiRuntimeCodeSources(nested bool, fn func(p *structpath.PathNode, s *string) bool) { + r.IterateJobTasks(nested, func(p *structpath.PathNode, task *jobs.Task) bool { + if task.AiRuntimeTask == nil || task.AiRuntimeTask.CodeSourcePath == "" { + return true + } + return fn(structpath.NewPath(p, "ai_runtime_task", "code_source_path"), &task.AiRuntimeTask.CodeSourcePath) + }) +} + +// IterateJobEnvironmentDependencies calls fn for the dependencies of the environments of +// all jobs, until fn returns false. +func (r *Resources) IterateJobEnvironmentDependencies(fn func(p *structpath.PathNode, deps *[]string) bool) { + for _, name := range sortedKeys(r.Jobs) { + job := r.Jobs[name] + if job == nil { + continue + } + for i := range job.Environments { + spec := job.Environments[i].Spec + if spec == nil || spec.Dependencies == nil { + continue + } + if !fn(structpath.NewPath(nil, "resources", "jobs", name, "environments", i, "spec", "dependencies"), &spec.Dependencies) { + return + } + } + } +} + +// IteratePipelineEnvironmentDependencies calls fn for the environment dependencies of +// all pipelines, until fn returns false. +func (r *Resources) IteratePipelineEnvironmentDependencies(fn func(p *structpath.PathNode, deps *[]string) bool) { + for _, name := range sortedKeys(r.Pipelines) { + pl := r.Pipelines[name] + if pl == nil || pl.Environment == nil || pl.Environment.Dependencies == nil { + continue + } + if !fn(structpath.NewPath(nil, "resources", "pipelines", name, "environment", "dependencies"), &pl.Environment.Dependencies) { + return + } + } +} + +// IterateClusterLibraries calls fn for the libraries of all clusters, until fn returns +// false. +func (r *Resources) IterateClusterLibraries(fn func(p *structpath.PathNode, libs *[]compute.Library) bool) { + for _, name := range sortedKeys(r.Clusters) { + c := r.Clusters[name] + if c == nil || c.Libraries == nil { + continue + } + if !fn(structpath.NewPath(nil, "resources", "clusters", name, "libraries"), &c.Libraries) { + return + } + } +} diff --git a/bundle/config/loader/process_include.go b/bundle/config/loader/process_include.go index 064855caca2..0d4dd6e1d30 100644 --- a/bundle/config/loader/process_include.go +++ b/bundle/config/loader/process_include.go @@ -4,17 +4,17 @@ import ( "cmp" "context" "fmt" + "maps" "slices" "strings" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" ) -func validateFileFormat(configRoot dyn.Value, filePath string) diag.Diagnostics { +func validateFileFormat(configRoot *config.Root, filePath string) diag.Diagnostics { for _, resourceDescription := range config.SupportedResources() { singularName := resourceDescription.SingularName @@ -29,49 +29,38 @@ func validateFileFormat(configRoot dyn.Value, filePath string) diag.Diagnostics return nil } -func validateSingleResourceDefined(configRoot dyn.Value, ext, typ string) diag.Diagnostics { +func validateSingleResourceDefined(configRoot *config.Root, ext, typ string) diag.Diagnostics { type resource struct { - path dyn.Path - value dyn.Value - typ string - key string + path *structpath.PathNode + typ string + key string } var resources []resource supportedResources := config.SupportedResources() - // Gather all resources defined in the resources block. - _, err := dyn.MapByPattern( - configRoot, - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), - func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - // The key for the resource, e.g. "my_job" for jobs.my_job. - k := p[2].Key() - // The type of the resource, e.g. "job" for jobs.my_job. - typ := supportedResources[p[1].Key()].SingularName - - resources = append(resources, resource{path: p, value: v, typ: typ, key: k}) - return v, nil + // Gather all resources defined in the resources block, and in target blocks. + configRoot.Resources.IterateAllResources(func(group, name string, _ config.ConfigResource) bool { + resources = append(resources, resource{ + path: structpath.NewPath(nil, "resources", group, name), + typ: supportedResources[group].SingularName, + key: name, }) - if err != nil { - return diag.FromErr(err) - } - - // Gather all resources defined in a target block. - _, err = dyn.MapByPattern( - configRoot, - dyn.NewPattern(dyn.Key("targets"), dyn.AnyKey(), dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), - func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - // The key for the resource, e.g. "my_job" for jobs.my_job. - k := p[4].Key() - // The type of the resource, e.g. "job" for jobs.my_job. - typ := supportedResources[p[3].Key()].SingularName - - resources = append(resources, resource{path: p, value: v, typ: typ, key: k}) - return v, nil + return true + }) + for _, target := range slices.Sorted(maps.Keys(configRoot.Targets)) { + t := configRoot.Targets[target] + if t == nil || t.Resources == nil { + continue + } + t.Resources.IterateAllResources(func(group, name string, _ config.ConfigResource) bool { + resources = append(resources, resource{ + path: structpath.NewPath(nil, "targets", target, "resources", group, name), + typ: supportedResources[group].SingularName, + key: name, + }) + return true }) - if err != nil { - return diag.FromErr(err) } typeMatch := true @@ -111,8 +100,8 @@ func validateSingleResourceDefined(configRoot dyn.Value, ext, typ string) diag.D var locations []diag.Location var paths []*structpath.PathNode for _, rr := range resources { - locations = append(locations, rr.value.Locations()...) - paths = append(paths, dyn.ToStructPath(rr.path)) + locations = append(locations, configRoot.GetLocationsOfExact(rr.path)...) + paths = append(paths, rr.path) } // Sort the locations and paths to make the output deterministic. slices.SortFunc(locations, func(a, b diag.Location) int { @@ -151,15 +140,29 @@ func (m *processInclude) Name() string { } func (m *processInclude) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { - this, diags := config.Load(m.fullPath) + this, diags := m.load() if diags.HasError() { return diags } + err := b.Config.Merge(this) + if err != nil { + diags = diags.Extend(diag.FromErr(err)) + } + return diags +} + +// load loads and validates the included file. +func (m *processInclude) load() (*config.Root, diag.Diagnostics) { + this, diags := config.Load(m.fullPath) + if diags.HasError() { + return nil, diags + } + // Add any diagnostics associated with the file format. - diags = append(diags, validateFileFormat(this.Value(), m.relPath)...) + diags = append(diags, validateFileFormat(this, m.relPath)...) if diags.HasError() { - return diags + return nil, diags } if len(this.Include) > 0 { @@ -173,9 +176,5 @@ Only includes defined in databricks.yml are applied.`, }) } - err := b.Config.Merge(this) - if err != nil { - diags = diags.Extend(diag.FromErr(err)) - } - return diags + return this, diags } diff --git a/bundle/config/loader/process_root_includes.go b/bundle/config/loader/process_root_includes.go index 20e80564b0f..36166fa8512 100644 --- a/bundle/config/loader/process_root_includes.go +++ b/bundle/config/loader/process_root_includes.go @@ -10,6 +10,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/logdiag" ) type processRootIncludes struct{} @@ -37,12 +38,11 @@ func hasGlobCharacters(path string) (string, bool) { } func (m *processRootIncludes) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - var out []bundle.Mutator + var out []*processInclude // Preserve the raw include patterns before they are replaced below with the // expanded list of loaded files, so IsFileIncluded can re-match against them. - patterns := b.Config.Include - b.SetIncludePatterns(patterns) + b.SetIncludePatterns(b.Config.Include) // Map with files we've already seen to avoid loading them twice. seen := map[string]bool{} @@ -127,25 +127,40 @@ func (m *processRootIncludes) Apply(ctx context.Context, b *bundle.Bundle) diag. slices.Sort(includes) files = append(files, includes...) for _, include := range includes { - out = append(out, ProcessInclude(filepath.Join(b.BundleRootPath, include), include)) + out = append(out, &processInclude{fullPath: filepath.Join(b.BundleRootPath, include), relPath: include}) } } + // Swap out the original includes list with the expanded globs. + // Include is omitempty in the typed configuration, so an empty list must stay + // absent rather than be written as []. + b.Config.Include = nil + if len(files) > 0 { + b.Config.Include = files + } + // Track number of bundle YAML (or JSON) files in the configuration. The +1 is there // to account for the root databricks.yaml file. b.Metrics.ConfigurationFileCount = int64(len(files)) + 1 - // ProcessInclude merges into the configuration via [config.Root.Merge], so it does - // not need its own mutator scope. Giving each included file one would re-convert the - // whole accumulated configuration per file, making load quadratic in the number of - // included files (~20 minutes for 6000 files). - bundle.ApplySeqInScopeContext(ctx, b, out...) - - // Swap out the original include patterns with the expanded globs. This is done after - // the includes are merged: [config.Root.Merge] reloads the typed configuration from - // the dynamic tree, which still holds the patterns, followed by the include sections - // of included files (merging concatenates sequences). Those are kept as they are. - // Include is omitempty, so an empty list is absent from the configuration. - b.Config.Include = append(files, b.Config.Include[len(patterns):]...) + // Load all included files first and merge them into the configuration at once. + // Merging them one by one would convert the whole accumulated configuration per + // file, making load quadratic in the number of included files. + var roots []*config.Root + for _, m := range out { + this, diags := m.load() + for _, d := range diags { + logdiag.LogDiag(ctx, d) + } + if diags.HasError() { + return nil + } + roots = append(roots, this) + } + + err := b.Config.Merge(roots...) + if err != nil { + return diag.FromErr(err) + } return nil } diff --git a/bundle/config/loader/process_root_includes_test.go b/bundle/config/loader/process_root_includes_test.go index a3d1c2fec88..9b246d769f6 100644 --- a/bundle/config/loader/process_root_includes_test.go +++ b/bundle/config/loader/process_root_includes_test.go @@ -10,7 +10,7 @@ import ( "github.com/databricks/cli/bundle/config/loader" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structvar" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -120,7 +120,7 @@ func TestProcessRootIncludesUpdatesDynamicValue(t *testing.T) { require.NoError(t, diags.Error()) assert.Equal(t, []string{"a.yml"}, b.Config.Include) - assert.Equal(t, []any{"a.yml"}, b.Config.Value().Get("include").AsAny()) + assert.Equal(t, []any{"a.yml"}, b.Config.View().Get("include").AsAny()) } // An empty include list must stay absent from the dynamic tree: the typed field is @@ -140,7 +140,7 @@ func TestProcessRootIncludesEmptyOmitsDynamicValue(t *testing.T) { diags := bundle.Apply(t.Context(), b, loader.ProcessRootIncludes()) require.NoError(t, diags.Error()) assert.Empty(t, b.Config.Include) - assert.Equal(t, dyn.KindInvalid, b.Config.Value().Get("include").Kind()) + assert.Equal(t, structvar.KindInvalid, b.Config.View().Get("include").Kind()) } // Include sections of included files are not applied, but they are merged into the @@ -162,7 +162,6 @@ func TestProcessRootIncludesKeepsIncludesOfIncludedFiles(t *testing.T) { diags := bundle.Apply(t.Context(), b, loader.ProcessRootIncludes()) require.NoError(t, diags.Error()) assert.Equal(t, []string{"a.yml", "b.yml", "c.yml"}, b.Config.Include) - assert.Equal(t, []any{"a.yml", "b.yml", "c.yml"}, b.Config.Value().Get("include").AsAny()) } func TestProcessRootIncludesNotExists(t *testing.T) { diff --git a/libs/dyn/dynloc/locations.go b/bundle/config/loctable/locations.go similarity index 61% rename from libs/dyn/dynloc/locations.go rename to bundle/config/loctable/locations.go index d343e24533c..e7307e04c1e 100644 --- a/libs/dyn/dynloc/locations.go +++ b/bundle/config/loctable/locations.go @@ -1,4 +1,4 @@ -package dynloc +package loctable import ( "fmt" @@ -7,7 +7,6 @@ import ( "slices" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" ) const ( @@ -16,7 +15,7 @@ const ( Version = 1 ) -// Locations is a structure that holds location information for (a subset of) a [dyn.Value] value. +// Locations is a structure that holds location information for (a subset of) a configuration. type Locations struct { // Version is the version of the location information. Version int `json:"version"` @@ -24,9 +23,9 @@ type Locations struct { // Files is a list of file paths. Files []string `json:"files"` - // Locations maps the string representation of a [dyn.Path] to a list of 3-tuples that represent the index + // Locations maps the string representation of a path to a list of 3-tuples that represent the index // of the file in the [Files] array, followed by the line and column number. - // A single [dyn.Path] can have multiple locations (e.g. the effective location and original definition). + // A single path can have multiple locations (e.g. the effective location and original definition). Locations map[string][][]int `json:"locations"` // fileToIndex maps file paths to their index in the [Files] array. @@ -38,29 +37,6 @@ type Locations struct { basePath string } -func (l *Locations) gatherLocations(v dyn.Value) (map[string][]diag.Location, error) { - locs := map[string][]diag.Location{} - patterns := []dyn.Pattern{ - dyn.NewPattern(dyn.AnyKey()), // Top level fields - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey()), // Resource groups ("resources.jobs") - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), // Resources for all types ("resources.jobs.my_job") - dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey(), dyn.Key("tasks")), // Job tasks ("resources.jobs.my_job.tasks") - dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey(), dyn.Key("tasks"), dyn.AnyIndex()), // Job task items ("resources.jobs.my_job.tasks[2]") - } - - for _, pattern := range patterns { - _, err := dyn.MapByPattern(v, pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - locs[p.String()] = v.Locations() - return v, nil - }) - if err != nil { - return nil, err - } - } - - return locs, nil -} - func (l *Locations) normalizeFilePath(file string) (string, error) { var err error @@ -113,7 +89,7 @@ func (l *Locations) addLocation(path, file string, line, col int) error { // Expect the file to be present in the lookup map. if _, ok := l.fileToIndex[file]; !ok { // This indicates a logic problem below, but we rather not panic. - return fmt.Errorf("dynloc: unknown file %q", file) + return fmt.Errorf("loctable: unknown file %q", file) } // Add the location to the map. @@ -125,8 +101,8 @@ func (l *Locations) addLocation(path, file string, line, col int) error { return nil } -// Build constructs a [Locations] object from a [dyn.Value]. -func Build(v dyn.Value, basePath string) (Locations, error) { +// Build constructs a [Locations] object from locations keyed by the string representation of a path. +func Build(pathToLocations map[string][]diag.Location, basePath string) (Locations, error) { l := Locations{ Version: Version, Files: make([]string, 0), @@ -137,15 +113,9 @@ func Build(v dyn.Value, basePath string) (Locations, error) { basePath: basePath, } - // Traverse the value and collect locations. - pathToLocations, err := l.gatherLocations(v) - if err != nil { - return l, err - } - // Normalize file paths and add locations. // This step adds files to the [Files] array in alphabetical order. - err = l.registerFileNames(slices.Concat(slices.Collect(maps.Values(pathToLocations))...)) + err := l.registerFileNames(slices.Concat(slices.Collect(maps.Values(pathToLocations))...)) if err != nil { return l, err } diff --git a/bundle/config/mutator/collect_escape_telemetry.go b/bundle/config/mutator/collect_escape_telemetry.go index b24922bb5b3..2a844446abc 100644 --- a/bundle/config/mutator/collect_escape_telemetry.go +++ b/bundle/config/mutator/collect_escape_telemetry.go @@ -5,8 +5,8 @@ import ( "strings" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config/walk" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" ) // Temporary: this mutator collects telemetry on escape patterns ($${}, $$, \${}, \$) @@ -24,12 +24,7 @@ func (*collectEscapeTelemetry) Name() string { func (*collectEscapeTelemetry) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { var hasDoubleDollarBrace, hasDoubleDollar, hasBackslashDollarBrace, hasBackslashDollar bool - _, err := dyn.Walk(b.Config.Value(), func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - s, ok := v.AsString() - if !ok { - return v, nil - } - + walk.Strings(&b.Config, walk.NonEmpty, func(_ *walk.Node, s string) (string, bool) { if !hasDoubleDollarBrace && strings.Contains(s, "$${") { hasDoubleDollarBrace = true } @@ -42,12 +37,8 @@ func (*collectEscapeTelemetry) Apply(ctx context.Context, b *bundle.Bundle) diag if !hasBackslashDollar && containsBackslashDollarWithoutBrace(s) { hasBackslashDollar = true } - - return v, nil + return "", false }) - if err != nil { - return diag.FromErr(err) - } if hasDoubleDollarBrace { b.Metrics.SetBoolValue("config_has_double_dollar_brace", true) diff --git a/bundle/config/mutator/compute_id_compat.go b/bundle/config/mutator/compute_id_compat.go index 35568441ce8..c1f99becd83 100644 --- a/bundle/config/mutator/compute_id_compat.go +++ b/bundle/config/mutator/compute_id_compat.go @@ -2,11 +2,13 @@ package mutator import ( "context" + "maps" "slices" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) type computeIdToClusterId struct{} @@ -22,68 +24,45 @@ func (m *computeIdToClusterId) Name() string { func (m *computeIdToClusterId) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { var diags diag.Diagnostics - // The "compute_id" key is set; rewrite it to "cluster_id". - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - v, d := rewriteComputeIdToClusterId(v, dyn.NewPath(dyn.Key("bundle")), nil) - diags = diags.Extend(d) + // Skip if "compute_id" is not set anywhere. + if b.Config.Bundle.ComputeId == "" && !slices.ContainsFunc(slices.Collect(maps.Values(b.Config.Targets)), func(t *config.Target) bool { + return t != nil && t.ComputeId != "" + }) { + return nil + } - // Check if the "compute_id" key is set in any target overrides. - return dyn.MapByPattern(v, dyn.NewPattern(dyn.Key("targets"), dyn.AnyKey()), func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - v, d := rewriteComputeIdToClusterId(v, dyn.Path{}, p) - diags = diags.Extend(d) - return v, nil - }) - }) + // The "compute_id" key is set; rewrite it to "cluster_id". + b2 := &b.Config.Bundle + diags = diags.Extend(rewriteComputeIdToClusterId(b, structpath.NewStringKey(nil, "bundle"), &b2.ComputeId, &b2.ClusterId)) - diags = diags.Extend(diag.FromErr(err)) + // Check if the "compute_id" key is set in any target overrides. + for _, name := range slices.Sorted(maps.Keys(b.Config.Targets)) { + if t := b.Config.Targets[name]; t != nil { + diags = diags.Extend(rewriteComputeIdToClusterId(b, structpath.NewPath(nil, "targets", name), &t.ComputeId, &t.ClusterId)) + } + } return diags } -// rewriteComputeIdToClusterId rewrites p.compute_id to p.cluster_id in v; prefix is the -// path of v in the configuration, used in the diagnostic. -func rewriteComputeIdToClusterId(v dyn.Value, p, prefix dyn.Path) (dyn.Value, diag.Diagnostics) { - var diags diag.Diagnostics - computeIdPath := p.Append(dyn.Key("compute_id")) - computeId, err := dyn.GetByPath(v, computeIdPath) +// rewriteComputeIdToClusterId moves the compute_id of the value at p to its cluster_id, +// with its locations. +func rewriteComputeIdToClusterId(b *bundle.Bundle, p *structpath.PathNode, computeId, clusterId *string) diag.Diagnostics { // If the "compute_id" key is not set, we don't need to do anything. - if err != nil { - return v, nil + if *computeId == "" { + return nil } - if computeId.Kind() == dyn.KindInvalid { - return v, nil - } - - diags = diags.Append(diag.Diagnostic{ + computeIdPath := structpath.NewStringKey(p, "compute_id") + locs := b.Config.GetLocationsOfExact(computeIdPath) + diags := diag.Diagnostics{{ Severity: diag.Warning, Summary: "compute_id is deprecated, please use cluster_id instead", - Locations: computeId.Locations(), - Paths: dyn.ToStructPaths(slices.Concat(prefix, computeIdPath)), - }) + Locations: locs, + Paths: []*structpath.PathNode{computeIdPath}, + }} - clusterIdPath := p.Append(dyn.Key("cluster_id")) - nv, err := dyn.SetByPath(v, clusterIdPath, computeId) - if err != nil { - return dyn.InvalidValue, diag.FromErr(err) - } + *clusterId = *computeId + b.Config.SetLocations(structpath.NewStringKey(p, "cluster_id"), locs) // Drop the "compute_id" key. - vout, err := dyn.Walk(nv, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - switch len(p) { - case 0: - return v, nil - case 1: - if p[0] == dyn.Key("compute_id") { - return v, dyn.ErrDrop - } - return v, nil - case 2: - if p[1] == dyn.Key("compute_id") { - return v, dyn.ErrDrop - } - } - return v, dyn.ErrSkip - }) - - diags = diags.Extend(diag.FromErr(err)) - return vout, diags + return diags.Extend(diag.FromErr(b.Config.Delete(computeIdPath))) } diff --git a/bundle/config/mutator/drop_empty_strings.go b/bundle/config/mutator/drop_empty_strings.go index 028351ed943..a193cc26575 100644 --- a/bundle/config/mutator/drop_empty_strings.go +++ b/bundle/config/mutator/drop_empty_strings.go @@ -4,10 +4,9 @@ import ( "context" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/walk" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" + "github.com/databricks/cli/libs/structs/structpath" ) type dropEmptyStrings struct{} @@ -17,10 +16,8 @@ type dropEmptyStrings struct{} // either literally (policy_id: "") or via a variable that resolved to "", so // this must run after variable resolution. // -// The direct engine converts the resolved config through convert.ToTyped, which -// force-sends explicitly-set zero values, defeating the omitempty tag. Dropping -// here fixes it and makes the result visible in `bundle validate -o json`, which -// serializes the dynamic value. +// An explicitly-set zero value is force-sent, defeating the omitempty tag. +// Dropping it here fixes it and makes the result visible in `bundle validate -o json`. func DropEmptyStrings() bundle.Mutator { return &dropEmptyStrings{} } @@ -30,31 +27,29 @@ func (m *dropEmptyStrings) Name() string { } func (m *dropEmptyStrings) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - var diags diag.Diagnostics - err := b.Config.Mutate(func(root dyn.Value) (dyn.Value, error) { - root, err := dyn.Map(root, "resources", func(_ dyn.Path, resources dyn.Value) (dyn.Value, error) { - // Normalize against the resources type so omitempty is known per field. - // Only DropEmptyStrings is set: existing values are kept as-is otherwise. - out, normDiags := convert.Normalize(config.Resources{}, resources, convert.DropEmptyStrings) - diags = diags.Extend(normDiags) - return out, nil - }) - if err != nil { - return root, err + // Drop the empty strings on all struct fields except the ones that must send their zero value. + var empty []*structpath.PathNode + walk.ResourceStrings(&b.Config, walk.EmptyFields, func(n *walk.Node, _ string) (string, bool) { + empty = append(empty, n.Path()) + return "", false + }) + for _, p := range empty { + if err := b.Config.Delete(p); err != nil { + return diag.FromErr(err) } + } - // Keep an empty apps description instead of dropping it: an app update without - // the field leaves the remote description unchanged, so removing description - // from config would never converge. Force-sending "" clears it. - return dyn.MapByPattern(root, dyn.NewPattern(dyn.Key("resources"), dyn.Key("apps"), dyn.AnyKey()), func(_ dyn.Path, app dyn.Value) (dyn.Value, error) { - if _, err := dyn.Get(app, "description"); err != nil { - return dyn.Set(app, "description", dyn.V("")) - } - return app, nil - }) - }) - if err != nil { - diags = diags.Extend(diag.FromErr(err)) + // Keep an empty apps description instead of dropping it: an app update without + // the field leaves the remote description unchanged, so removing description + // from config would never converge. Force-sending "" clears it. + for name, app := range b.Config.Resources.Apps { + if app != nil && (app.Description != "" || walk.Forced(app.ForceSendFields, "Description")) { + continue + } + err := b.Config.Set(structpath.NewPath(nil, "resources", "apps", name, "description"), "") + if err != nil { + return diag.FromErr(err) + } } - return diags + return nil } diff --git a/bundle/config/mutator/environments_compat.go b/bundle/config/mutator/environments_compat.go index fb898edea36..69862a5c2d2 100644 --- a/bundle/config/mutator/environments_compat.go +++ b/bundle/config/mutator/environments_compat.go @@ -2,11 +2,10 @@ package mutator import ( "context" - "fmt" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) type environmentsToTargets struct{} @@ -27,40 +26,20 @@ func (m *environmentsToTargets) Apply(ctx context.Context, b *bundle.Bundle) dia } // The "environments" key is set; validate and rewrite it to "targets". - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - environments := v.Get("environments") - targets := v.Get("targets") - - // Return an error if both "environments" and "targets" are set. - if environments.Kind() != dyn.KindInvalid && targets.Kind() != dyn.KindInvalid { - return dyn.InvalidValue, fmt.Errorf( - "both 'environments' and 'targets' are specified; only 'targets' should be used: %s", - environments.Location().String(), - ) - } - - // Rewrite "environments" to "targets". - if environments.Kind() != dyn.KindInvalid && targets.Kind() == dyn.KindInvalid { - nv, err := dyn.Set(v, "targets", environments) - if err != nil { - return dyn.InvalidValue, err - } - // Drop the "environments" key. - return dyn.Walk(nv, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - switch len(p) { - case 0: - return v, nil - case 1: - if p[0] == dyn.Key("environments") { - return v, dyn.ErrDrop - } - } - return v, dyn.ErrSkip - }) - } - - return v, nil - }) + environmentsPath := structpath.NewStringKey(nil, "environments") + targetsPath := structpath.NewStringKey(nil, "targets") + + // Return an error if both "environments" and "targets" are set. + if b.Config.Targets != nil || b.Config.IsReferenceAt(targetsPath) { + return diag.Errorf( + "both 'environments' and 'targets' are specified; only 'targets' should be used: %s", + b.Config.GetLocationOfExact(environmentsPath).String(), + ) + } - return diag.FromErr(err) + // Rewrite "environments" to "targets" and drop the "environments" key. + b.Config.Targets = b.Config.Environments + b.Config.Environments = nil + b.Config.MoveMetadata(environmentsPath, targetsPath) + return nil } diff --git a/bundle/config/mutator/initialize_volume_paths.go b/bundle/config/mutator/initialize_volume_paths.go index 20ea9ae6b38..945038ff175 100644 --- a/bundle/config/mutator/initialize_volume_paths.go +++ b/bundle/config/mutator/initialize_volume_paths.go @@ -3,13 +3,13 @@ package mutator import ( "context" "fmt" + "maps" + "slices" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/dynvar" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) type initializeVolumePaths struct{} @@ -32,29 +32,35 @@ func (m *initializeVolumePaths) Name() string { } func (m *initializeVolumePaths) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { - err := b.Config.Mutate(func(root dyn.Value) (dyn.Value, error) { - pattern := dyn.NewPattern(dyn.Key("resources"), dyn.Key("volumes"), dyn.AnyKey()) - return dyn.MapByPattern(root, pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - // volume_path is computed and read-only; reject a user-provided value instead of overwriting it. - if existing, ok := v.Get("volume_path").AsString(); ok && existing != "" { - return dyn.InvalidValue, fmt.Errorf("%s.volume_path is computed and read-only; remove it from the configuration", p.String()) - } + // Skip converting the configuration if there are no volumes. + if len(b.Config.Resources.Volumes) == 0 { + return nil + } + + // References are resolved against the configuration tree (they can point anywhere). + view := b.Config.View() + volumes := b.Config.Resources.Volumes + for _, name := range slices.Sorted(maps.Keys(volumes)) { + v := volumes[name] + if v == nil { + continue + } + p := structpath.NewPath(nil, "resources", "volumes", name) - var vol resources.Volume - if err := convert.ToTyped(&vol, v); err != nil { - return dyn.InvalidValue, err - } + // volume_path is computed and read-only; reject a user-provided value instead of overwriting it. + if v.VolumePath != "" { + return diag.FromErr(fmt.Errorf("%s.volume_path is computed and read-only; remove it from the configuration", p.String())) + } - // Resolve references to compute the path only; the field values in v are left untouched. - vol.CatalogName = resolveResourceReference(root, vol.CatalogName) - vol.SchemaName = resolveResourceReference(root, vol.SchemaName) - vol.Name = resolveResourceReference(root, vol.Name) + // Resolve references to compute the path only; the field values are left untouched. + vol := *v + vol.CatalogName = resolveResourceReference(view, vol.CatalogName) + vol.SchemaName = resolveResourceReference(view, vol.SchemaName) + vol.Name = resolveResourceReference(view, vol.Name) - return dyn.Set(v, "volume_path", dyn.V(vol.ComputeVolumePath())) - }) - }) - if err != nil { - return diag.FromErr(err) + if err := b.Config.Set(structpath.NewStringKey(p, "volume_path"), vol.ComputeVolumePath()); err != nil { + return diag.FromErr(err) + } } return nil } @@ -62,16 +68,12 @@ func (m *initializeVolumePaths) Apply(_ context.Context, b *bundle.Bundle) diag. // resolveResourceReference resolves a pure ${resources....} reference by looking it up in root. // Values that are not such a reference, or cannot be resolved, are returned unchanged (still // containing "${"), so the caller embeds the reference verbatim to be resolved later. -func resolveResourceReference(root dyn.Value, s string) string { - p, ok := dynvar.PureReferenceToPath(s) - if !ok || p[0].Key() != "resources" { - return s - } - rv, err := dyn.GetByPath(root, p) - if err != nil { +func resolveResourceReference(root structvar.View, s string) string { + p, ok := structvar.PureReferenceToPath(s) + if !ok || p.KeyAt(0) != "resources" { return s } - rs, ok := rv.AsString() + rs, ok := root.Lookup(p).AsString() if !ok { return s } diff --git a/bundle/config/mutator/load_dbalert_files.go b/bundle/config/mutator/load_dbalert_files.go index 437095aa218..b6914230731 100644 --- a/bundle/config/mutator/load_dbalert_files.go +++ b/bundle/config/mutator/load_dbalert_files.go @@ -11,9 +11,8 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/sql" ) @@ -57,26 +56,21 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia continue } - alertV, err := dyn.GetByPath(b.Config.Value(), dyn.NewPath(dyn.Key("resources"), dyn.Key("alerts"), dyn.Key(alertKey))) - if err != nil { - return diag.FromErr(err) - } + alertPath := structpath.NewPath(nil, "resources", "alerts", alertKey) + alertV := b.Config.View().Lookup(alertPath) // No other fields other than allowedInYAML should be set in the bundle YAML. - m, ok := alertV.AsMap() - if !ok { + if alertV.Kind() != structvar.KindMap { return diag.FromErr(fmt.Errorf("internal error: alert value is not a map, got %s", alertV.Kind())) } - for _, p := range m.Pairs() { - k := p.Key.MustString() - v := p.Value + for k, v := range alertV.MapItems() { if slices.Contains(allowedInYAML, k) { continue } - if v.Kind() == dyn.KindNil || v.Kind() == dyn.KindInvalid { + if v.Kind() == structvar.KindNil || v.Kind() == structvar.KindInvalid { continue } @@ -86,7 +80,7 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia Severity: diag.Error, Summary: fmt.Sprintf("field %s is not allowed in the bundle configuration.", k), Detail: "When a .dbalert.json is specified, only the following fields are allowed in the bundle configuration: " + strings.Join(allowedInYAML, ", "), - Paths: structpath.NewPathSlice("resources", "alerts", alertKey, k), + Paths: []*structpath.PathNode{structpath.NewStringKey(alertPath, k)}, Locations: v.Locations(), }, } @@ -108,7 +102,7 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia Severity: diag.Error, Summary: fmt.Sprintf("failed to read .dbalert.json file %s: %s", alert.FilePath, err), Detail: "", - Paths: structpath.NewPathSlice("resources", "alerts", alertKey, "file_path"), + Paths: []*structpath.PathNode{structpath.NewStringKey(alertPath, "file_path")}, Locations: alertV.Get("file_path").Locations(), }, } @@ -123,21 +117,21 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia Severity: diag.Error, Summary: fmt.Sprintf("failed to parse .dbalert.json file %s: %s", alert.FilePath, err), Detail: "", - Paths: structpath.NewPathSlice("resources", "alerts", alertKey, "file_path"), + Paths: []*structpath.PathNode{structpath.NewStringKey(alertPath, "file_path")}, Locations: alertV.Get("file_path").Locations(), }, } } // Check that the file does not have any variable interpolations. - if dynvar.ContainsVariableReference(string(content)) { + if structvar.ContainsVariableReference(string(content)) { return diag.Diagnostics{ { ID: diag.ID(""), Severity: diag.Error, Summary: fmt.Sprintf(".alert file %s must not contain variable interpolations.", alert.FilePath), Detail: "Please inline the alert configuration in the bundle configuration to use variables", - Paths: structpath.NewPathSlice("resources", "alerts", alertKey, "file_path"), + Paths: []*structpath.PathNode{structpath.NewStringKey(alertPath, "file_path")}, Locations: alertV.Get("file_path").Locations(), }, } diff --git a/bundle/config/mutator/normalize_paths.go b/bundle/config/mutator/normalize_paths.go index 7256ebad4b7..f67495aedd0 100644 --- a/bundle/config/mutator/normalize_paths.go +++ b/bundle/config/mutator/normalize_paths.go @@ -14,7 +14,7 @@ import ( "github.com/databricks/cli/bundle/config/mutator/paths" "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) type normalizePaths struct{} @@ -43,41 +43,35 @@ func (a normalizePaths) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnost // Do not normalize job task paths if using git source gitSourcePaths := collectGitSourcePaths(b) - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return paths.VisitPaths(v, func(path dyn.Path, kind paths.TranslateMode, v dyn.Value) (dyn.Value, error) { - if slices.ContainsFunc(gitSourcePaths, path.HasPrefix) { - return v, nil - } - - value, ok := v.AsString() - if !ok { - return dyn.InvalidValue, fmt.Errorf("value at %s is not a string", path.String()) - } + err := paths.VisitPaths(&b.Config, func(path *structpath.PathNode, kind paths.TranslateMode, s *string) error { + if slices.ContainsFunc(gitSourcePaths, path.HasPrefix) { + return nil + } - newValue, err := normalizePath(value, v.Location(), b.BundleRootPath) - if err != nil { - return dyn.InvalidValue, err - } + newValue, err := normalizePath(*s, b.Config.GetLocationOfExact(path), b.BundleRootPath) + if err != nil { + return err + } - return dyn.NewValue(newValue, v.Locations()), nil - }) + *s = newValue + return nil }) if err != nil { return diag.FromErr(fmt.Errorf("failed to normalize paths: %w", err)) } - return diag.FromErr(err) + return nil } -func collectGitSourcePaths(b *bundle.Bundle) []dyn.Path { - var jobs []dyn.Path +func collectGitSourcePaths(b *bundle.Bundle) []*structpath.PathNode { + var jobs []*structpath.PathNode for name, job := range b.Config.Resources.Jobs { if job == nil { continue } if job.GitSource != nil { - jobs = append(jobs, dyn.NewPath(dyn.Key("resources"), dyn.Key("jobs"), dyn.Key(name))) + jobs = append(jobs, structpath.NewPath(nil, "resources", "jobs", name)) } } diff --git a/bundle/config/mutator/normalize_paths_test.go b/bundle/config/mutator/normalize_paths_test.go index 9c9085a27c6..2f0e8a726d6 100644 --- a/bundle/config/mutator/normalize_paths_test.go +++ b/bundle/config/mutator/normalize_paths_test.go @@ -8,7 +8,7 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -38,20 +38,14 @@ func TestNormalizePaths(t *testing.T) { // update config as if 'notebook_path' property is defined in resources/job_1.yml location := diag.Location{File: filepath.Join(tmpDir, "resources", "job_1.yml")} - path := dyn.MustPathFromString("resources.jobs.job1.tasks[0].notebook_task.notebook_path") - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.MapByPath(v, path, func(path dyn.Path, value dyn.Value) (dyn.Value, error) { - return dyn.NewValue(value.MustString(), []diag.Location{location}), nil - }) - }) - require.NoError(t, err) + path := structpath.MustParsePath("resources.jobs.job1.tasks[0].notebook_task.notebook_path") + b.Config.SetLocations(path, []diag.Location{location}) diags := bundle.Apply(t.Context(), b, m) require.NoError(t, diags.Error()) - newValue, err := dyn.GetByPath(b.Config.Value(), path) - require.NoError(t, err) - require.Equal(t, "src/notebook.py", newValue.MustString()) + require.Equal(t, "src/notebook.py", b.Config.Resources.Jobs["job1"].Tasks[0].NotebookTask.NotebookPath) + require.Equal(t, []diag.Location{location}, b.Config.GetLocationsOfExact(path)) } func TestNormalizePaths_jobRunOnFileChange(t *testing.T) { @@ -77,20 +71,14 @@ func TestNormalizePaths_jobRunOnFileChange(t *testing.T) { } location := diag.Location{File: filepath.Join(tmpDir, "resources", "run.yml")} - path := dyn.MustPathFromString("resources.job_runs.run1.lifecycle.triggers[0].on_file_change") - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.MapByPath(v, path, func(path dyn.Path, value dyn.Value) (dyn.Value, error) { - return dyn.NewValue(value.MustString(), []diag.Location{location}), nil - }) - }) - require.NoError(t, err) + path := structpath.MustParsePath("resources.job_runs.run1.lifecycle.triggers[0].on_file_change") + b.Config.SetLocations(path, []diag.Location{location}) diags := bundle.Apply(t.Context(), b, m) require.NoError(t, diags.Error()) - newValue, err := dyn.GetByPath(b.Config.Value(), path) - require.NoError(t, err) - require.Equal(t, "data/*.txt", newValue.MustString()) + require.Equal(t, "data/*.txt", *b.Config.Resources.JobRuns["run1"].Lifecycle.Triggers[0].OnFileChange) + require.Equal(t, []diag.Location{location}, b.Config.GetLocationsOfExact(path)) } func TestNormalizePath_absolutePath(t *testing.T) { diff --git a/bundle/config/mutator/paths/alert_paths_visitor.go b/bundle/config/mutator/paths/alert_paths_visitor.go index 1f1a010c798..dbc73350151 100644 --- a/bundle/config/mutator/paths/alert_paths_visitor.go +++ b/bundle/config/mutator/paths/alert_paths_visitor.go @@ -1,18 +1,23 @@ package paths import ( - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" ) -func VisitAlertPaths(value dyn.Value, fn VisitFunc) (dyn.Value, error) { - pattern := dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("alerts"), - dyn.AnyKey(), - dyn.Key("file_path"), - ) - - return dyn.MapByPattern(value, pattern, func(path dyn.Path, value dyn.Value) (dyn.Value, error) { - return fn(path, TranslateModeLocalRelative, value) - }) +func VisitAlertPaths(root *config.Root, fn VisitFunc) error { + for _, name := range sortedKeys(root.Resources.Alerts) { + if a := root.Resources.Alerts[name]; a != nil { + if err := visit(fn, visitArgs{ + owner: a, + field: "file_path", + value: &a.FilePath, + mode: TranslateModeLocalRelative, + path: structpath.NewPath(resourcePath("alerts", name), "file_path"), + }); err != nil { + return err + } + } + } + return nil } diff --git a/bundle/config/mutator/paths/app_paths_visitor.go b/bundle/config/mutator/paths/app_paths_visitor.go index 21a7e467ae2..445b3c5857e 100644 --- a/bundle/config/mutator/paths/app_paths_visitor.go +++ b/bundle/config/mutator/paths/app_paths_visitor.go @@ -1,18 +1,23 @@ package paths import ( - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" ) -func VisitAppPaths(value dyn.Value, fn VisitFunc) (dyn.Value, error) { - pattern := dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("apps"), - dyn.AnyKey(), - dyn.Key("source_code_path"), - ) - - return dyn.MapByPattern(value, pattern, func(path dyn.Path, value dyn.Value) (dyn.Value, error) { - return fn(path, TranslateModeDirectory, value) - }) +func VisitAppPaths(root *config.Root, fn VisitFunc) error { + for _, name := range sortedKeys(root.Resources.Apps) { + if a := root.Resources.Apps[name]; a != nil { + if err := visit(fn, visitArgs{ + owner: a, + field: "source_code_path", + value: &a.SourceCodePath, + mode: TranslateModeDirectory, + path: structpath.NewPath(resourcePath("apps", name), "source_code_path"), + }); err != nil { + return err + } + } + } + return nil } diff --git a/bundle/config/mutator/paths/app_paths_visitor_test.go b/bundle/config/mutator/paths/app_paths_visitor_test.go index 9ec01aeaf83..bf08e3d94d2 100644 --- a/bundle/config/mutator/paths/app_paths_visitor_test.go +++ b/bundle/config/mutator/paths/app_paths_visitor_test.go @@ -5,7 +5,7 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/apps" "github.com/stretchr/testify/assert" ) @@ -24,9 +24,7 @@ func TestAppPathsVisitor(t *testing.T) { } actual := collectVisitedPaths(t, root, VisitAppPaths) - expected := []dyn.Path{ - dyn.MustPathFromString("resources.apps.app0.source_code_path"), - } + expected := structpath.NewPathSlice("resources", "apps", "app0", "source_code_path") assert.ElementsMatch(t, expected, actual) } diff --git a/bundle/config/mutator/paths/artifact_paths_visitor.go b/bundle/config/mutator/paths/artifact_paths_visitor.go index c37ae19fee2..4a1d871f264 100644 --- a/bundle/config/mutator/paths/artifact_paths_visitor.go +++ b/bundle/config/mutator/paths/artifact_paths_visitor.go @@ -1,42 +1,23 @@ package paths import ( - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" ) -type artifactRewritePattern struct { - pattern dyn.Pattern - mode TranslateMode -} - -func artifactRewritePatterns() []artifactRewritePattern { - // Base pattern to match all artifacts. - base := dyn.NewPattern( - dyn.Key("artifacts"), - dyn.AnyKey(), - ) - - // Compile list of configuration paths to rewrite. - return []artifactRewritePattern{ - { - pattern: base.Append(dyn.Key("path")), - mode: TranslateModeLocalAbsoluteDirectory, - }, - } -} - -func VisitArtifactPaths(value dyn.Value, fn VisitFunc) (dyn.Value, error) { - var err error - newValue := value - - for _, rewritePattern := range artifactRewritePatterns() { - newValue, err = dyn.MapByPattern(newValue, rewritePattern.pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - return fn(p, rewritePattern.mode, v) - }) - if err != nil { - return dyn.InvalidValue, err +func VisitArtifactPaths(root *config.Root, fn VisitFunc) error { + for _, name := range sortedKeys(root.Artifacts) { + if a := root.Artifacts[name]; a != nil { + if err := visit(fn, visitArgs{ + owner: a, + field: "path", + value: &a.Path, + mode: TranslateModeLocalAbsoluteDirectory, + path: structpath.NewPath(nil, "artifacts", name, "path"), + }); err != nil { + return err + } } } - - return newValue, nil + return nil } diff --git a/bundle/config/mutator/paths/artifact_paths_visitor_test.go b/bundle/config/mutator/paths/artifact_paths_visitor_test.go index 5d43036b813..980b284cb11 100644 --- a/bundle/config/mutator/paths/artifact_paths_visitor_test.go +++ b/bundle/config/mutator/paths/artifact_paths_visitor_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/databricks/cli/bundle/config" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/stretchr/testify/assert" ) @@ -18,9 +18,7 @@ func TestArtifactPathsVisitor(t *testing.T) { } actual := collectVisitedPaths(t, root, VisitArtifactPaths) - expected := []dyn.Path{ - dyn.MustPathFromString("artifacts.artifact0.path"), - } + expected := structpath.NewPathSlice("artifacts", "artifact0", "path") assert.ElementsMatch(t, expected, actual) } diff --git a/bundle/config/mutator/paths/dashboard_paths_visitor.go b/bundle/config/mutator/paths/dashboard_paths_visitor.go index b0a25dfe2ff..ae2701c9591 100644 --- a/bundle/config/mutator/paths/dashboard_paths_visitor.go +++ b/bundle/config/mutator/paths/dashboard_paths_visitor.go @@ -1,18 +1,23 @@ package paths import ( - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" ) -func VisitDashboardPaths(value dyn.Value, fn VisitFunc) (dyn.Value, error) { - pattern := dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("dashboards"), - dyn.AnyKey(), - dyn.Key("file_path"), - ) - - return dyn.MapByPattern(value, pattern, func(path dyn.Path, value dyn.Value) (dyn.Value, error) { - return fn(path, TranslateModeLocalRelative, value) - }) +func VisitDashboardPaths(root *config.Root, fn VisitFunc) error { + for _, name := range sortedKeys(root.Resources.Dashboards) { + if d := root.Resources.Dashboards[name]; d != nil { + if err := visit(fn, visitArgs{ + owner: d, + field: "file_path", + value: &d.FilePath, + mode: TranslateModeLocalRelative, + path: structpath.NewPath(resourcePath("dashboards", name), "file_path"), + }); err != nil { + return err + } + } + } + return nil } diff --git a/bundle/config/mutator/paths/dashboard_paths_visitor_test.go b/bundle/config/mutator/paths/dashboard_paths_visitor_test.go index 4827e49d3ff..da360776db6 100644 --- a/bundle/config/mutator/paths/dashboard_paths_visitor_test.go +++ b/bundle/config/mutator/paths/dashboard_paths_visitor_test.go @@ -5,7 +5,7 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/stretchr/testify/assert" ) @@ -21,9 +21,7 @@ func TestVisitDashboardPaths(t *testing.T) { } actual := collectVisitedPaths(t, root, VisitDashboardPaths) - expected := []dyn.Path{ - dyn.MustPathFromString("resources.dashboards.dashboard0.file_path"), - } + expected := structpath.NewPathSlice("resources", "dashboards", "dashboard0", "file_path") assert.ElementsMatch(t, expected, actual) } diff --git a/bundle/config/mutator/paths/genie_space_paths_visitor.go b/bundle/config/mutator/paths/genie_space_paths_visitor.go index edd6ff2d8df..974e5f31979 100644 --- a/bundle/config/mutator/paths/genie_space_paths_visitor.go +++ b/bundle/config/mutator/paths/genie_space_paths_visitor.go @@ -1,18 +1,23 @@ package paths import ( - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" ) -func VisitGenieSpacePaths(value dyn.Value, fn VisitFunc) (dyn.Value, error) { - pattern := dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("genie_spaces"), - dyn.AnyKey(), - dyn.Key("file_path"), - ) - - return dyn.MapByPattern(value, pattern, func(path dyn.Path, value dyn.Value) (dyn.Value, error) { - return fn(path, TranslateModeLocalRelative, value) - }) +func VisitGenieSpacePaths(root *config.Root, fn VisitFunc) error { + for _, name := range sortedKeys(root.Resources.GenieSpaces) { + if g := root.Resources.GenieSpaces[name]; g != nil { + if err := visit(fn, visitArgs{ + owner: g, + field: "file_path", + value: &g.FilePath, + mode: TranslateModeLocalRelative, + path: structpath.NewPath(resourcePath("genie_spaces", name), "file_path"), + }); err != nil { + return err + } + } + } + return nil } diff --git a/bundle/config/mutator/paths/job_libraries_paths_visitor.go b/bundle/config/mutator/paths/job_libraries_paths_visitor.go index ad56b1933dc..7a67e52fda5 100644 --- a/bundle/config/mutator/paths/job_libraries_paths_visitor.go +++ b/bundle/config/mutator/paths/job_libraries_paths_visitor.go @@ -1,100 +1,75 @@ package paths import ( + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/libraries" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go/service/jobs" ) -func jobTaskLibrariesRewritePatterns(base dyn.Pattern) []jobRewritePattern { - return []jobRewritePattern{ - { - base.Append(dyn.Key("libraries"), dyn.AnyIndex(), dyn.Key("whl")), - TranslateModeLocalRelative, - noSkipRewrite, - }, - { - base.Append(dyn.Key("libraries"), dyn.AnyIndex(), dyn.Key("jar")), - TranslateModeLocalRelative, - noSkipRewrite, - }, - } +func libraryRule(key string, field func(*jobs.Task, int) *string) taskRule { + return taskRule{TranslateModeLocalRelative, func(t *jobs.Task, c *taskCursor) error { + for i := range t.Libraries { + if err := c.yield(&t.Libraries[i], key, field(t, i), "libraries", i, key); err != nil { + return err + } + } + return nil + }} } -func jobLibrariesRewritePatterns() []jobRewritePattern { - // Base pattern to match all tasks in all jobs. - base := dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("jobs"), - dyn.AnyKey(), - dyn.Key("tasks"), - dyn.AnyIndex(), - ) - - // Compile list of patterns and their respective rewrite functions. - jobEnvironmentsPatterns := []jobRewritePattern{ - { - dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("jobs"), - dyn.AnyKey(), - dyn.Key("environments"), - dyn.AnyIndex(), - dyn.Key("spec"), - dyn.Key("dependencies"), - dyn.AnyIndex(), - ), - TranslateModeLocalRelativeWithPrefix, - func(s string) bool { - return !libraries.IsLibraryLocal(s) - }, - }, - } +var jobTaskLibrariesRules = []taskRule{ + libraryRule("whl", func(t *jobs.Task, i int) *string { return &t.Libraries[i].Whl }), + libraryRule("jar", func(t *jobs.Task, i int) *string { return &t.Libraries[i].Jar }), +} - jobEnvironmentsWithRequirementsPatterns := []jobRewritePattern{ - { - dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("jobs"), - dyn.AnyKey(), - dyn.Key("environments"), - dyn.AnyIndex(), - dyn.Key("spec"), - dyn.Key("dependencies"), - dyn.AnyIndex(), - ), - TranslateModeEnvironmentPipFlag, - func(s string) bool { - _, _, ok := libraries.IsLocalPathInPipFlag(s) - return !ok - }, - }, - } +func notLocalLibrary(s string) bool { + return !libraries.IsLibraryLocal(s) +} - taskPatterns := jobTaskLibrariesRewritePatterns(base) - forEachPatterns := jobTaskLibrariesRewritePatterns(base.Append(dyn.Key("for_each_task"), dyn.Key("task"))) - allPatterns := append(taskPatterns, jobEnvironmentsPatterns...) - allPatterns = append(allPatterns, jobEnvironmentsWithRequirementsPatterns...) - allPatterns = append(allPatterns, forEachPatterns...) - return allPatterns +func noLocalPathInPipFlag(s string) bool { + _, _, ok := libraries.IsLocalPathInPipFlag(s) + return !ok } // VisitJobLibrariesPaths visits all libraries related paths in job resources and applies a function to each path. -func VisitJobLibrariesPaths(value dyn.Value, fn VisitFunc) (dyn.Value, error) { - var err error - newValue := value - - for _, rewritePattern := range jobLibrariesRewritePatterns() { - newValue, err = dyn.MapByPattern(newValue, rewritePattern.pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - if rewritePattern.skipRewrite(v.MustString()) { - return v, nil +func VisitJobLibrariesPaths(root *config.Root, fn VisitFunc) error { + // Task libraries first, then environment dependencies (local libraries, then + // local paths in pip flags), then the libraries of tasks nested in for_each_task. + if err := visitTasksAt(root, false, jobTaskLibrariesRules, fn); err != nil { + return err + } + for _, rule := range []struct { + mode TranslateMode + skip func(string) bool + }{ + {TranslateModeLocalRelativeWithPrefix, notLocalLibrary}, + {TranslateModeEnvironmentPipFlag, noLocalPathInPipFlag}, + } { + for _, name := range sortedKeys(root.Resources.Jobs) { + job := root.Resources.Jobs[name] + if job == nil { + continue + } + for i, env := range job.Environments { + if env.Spec == nil { + continue + } + for j := range env.Spec.Dependencies { + // Most dependencies are not local paths: check before building the path. + if rule.skip(env.Spec.Dependencies[j]) { + continue + } + if err := visit(fn, visitArgs{ + value: &env.Spec.Dependencies[j], + mode: rule.mode, + path: structpath.NewPath(resourcePath("jobs", name), "environments", i, "spec", "dependencies", j), + }); err != nil { + return err + } + } } - - return fn(p, rewritePattern.mode, v) - }) - if err != nil { - return dyn.InvalidValue, err } } - - return newValue, nil + return visitTasksAt(root, true, jobTaskLibrariesRules, fn) } diff --git a/bundle/config/mutator/paths/job_libraries_paths_visitor_test.go b/bundle/config/mutator/paths/job_libraries_paths_visitor_test.go index 07b3c6d6641..789a2cc72b5 100644 --- a/bundle/config/mutator/paths/job_libraries_paths_visitor_test.go +++ b/bundle/config/mutator/paths/job_libraries_paths_visitor_test.go @@ -5,7 +5,7 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/assert" @@ -71,10 +71,10 @@ func TestVisitJobLibrariesPaths(t *testing.T) { } actual := collectVisitedPaths(t, root, VisitJobLibrariesPaths) - expected := []dyn.Path{ - dyn.MustPathFromString("resources.jobs.job0.tasks[4].libraries[0].whl"), - dyn.MustPathFromString("resources.jobs.job0.tasks[5].libraries[0].jar"), - } + expected := structpath.MustParsePaths( + "resources.jobs.job0.tasks[4].libraries[0].whl", + "resources.jobs.job0.tasks[5].libraries[0].jar", + ) assert.ElementsMatch(t, expected, actual) } @@ -103,10 +103,10 @@ func TestVisitJobLibrariesPaths_environments(t *testing.T) { } actual := collectVisitedPaths(t, root, VisitJobLibrariesPaths) - expected := []dyn.Path{ - dyn.MustPathFromString("resources.jobs.job0.environments[0].spec.dependencies[0]"), - dyn.MustPathFromString("resources.jobs.job0.environments[0].spec.dependencies[1]"), - } + expected := structpath.MustParsePaths( + "resources.jobs.job0.environments[0].spec.dependencies[0]", + "resources.jobs.job0.environments[0].spec.dependencies[1]", + ) assert.ElementsMatch(t, expected, actual) } diff --git a/bundle/config/mutator/paths/job_paths_visitor.go b/bundle/config/mutator/paths/job_paths_visitor.go index a3abd0090a4..72edeb2ef47 100644 --- a/bundle/config/mutator/paths/job_paths_visitor.go +++ b/bundle/config/mutator/paths/job_paths_visitor.go @@ -1,124 +1,185 @@ package paths import ( - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go/service/jobs" ) -type jobRewritePattern struct { - pattern dyn.Pattern - mode TranslateMode - skipRewrite func(string) bool +// taskRule visits one kind of path value of a job task. +type taskRule struct { + mode TranslateMode + visit func(task *jobs.Task, c *taskCursor) error } -func noSkipRewrite(string) bool { - return false +// taskCursor is the task being visited. It is reused for all tasks so visiting does +// not allocate; the path is only built for values that are visited. +type taskCursor struct { + name string + index int + nested bool + rule *taskRule + fn VisitFunc } -func jobTaskRewritePatterns(base dyn.Pattern) []jobRewritePattern { - return []jobRewritePattern{ - { - base.Append(dyn.Key("notebook_task"), dyn.Key("notebook_path")), - TranslateModeNotebook, - noSkipRewrite, - }, - { - base.Append(dyn.Key("spark_python_task"), dyn.Key("python_file")), - TranslateModeFile, - noSkipRewrite, - }, - { - base.Append(dyn.Key("dbt_task"), dyn.Key("project_directory")), - TranslateModeDirectory, - noSkipRewrite, - }, - { - base.Append(dyn.Key("sql_task"), dyn.Key("file"), dyn.Key("path")), - TranslateModeFile, - noSkipRewrite, - }, - { - base.Append(dyn.Key("alert_task"), dyn.Key("workspace_path")), - TranslateModeFile, - noSkipRewrite, - }, - { - base.Append(dyn.Key("libraries"), dyn.AnyIndex(), dyn.Key("requirements")), - TranslateModeFile, - noSkipRewrite, - }, - { - // The AI Runtime task runs this bash script on each node; the backend - // reads it as a workspace file, so translate the local path to its - // remote (or immutable-snapshot) location like any other file. - base.Append(dyn.Key("ai_runtime_task"), dyn.Key("deployments"), dyn.AnyIndex(), dyn.Key("command_path")), - TranslateModeFile, - noSkipRewrite, - }, +// yield visits the value s, the field key of owner, at the parts below the task. +func (c *taskCursor) yield(owner any, key string, s *string, parts ...any) error { + // Most candidates (e.g. the whl, jar and requirements of every library) are empty. + if !present(owner, key, s) { + return nil } + p := structpath.NewPath(resourcePath("jobs", c.name), "tasks", c.index) + if c.nested { + p = structpath.NewPath(p, "for_each_task", "task") + } + return visit(c.fn, visitArgs{ + owner: owner, + field: key, + value: s, + mode: c.rule.mode, + path: structpath.NewPath(p, parts...), + }) } -func jobRewritePatterns() []jobRewritePattern { - // Task-level base pattern for all jobs. - base := dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("jobs"), - dyn.AnyKey(), - dyn.Key("tasks"), - dyn.AnyIndex(), - ) - - taskPatterns := jobTaskRewritePatterns(base) - forEachPatterns := jobTaskRewritePatterns(base.Append(dyn.Key("for_each_task"), dyn.Key("task"))) - patterns := append(taskPatterns, forEachPatterns...) - return append(patterns, - jobRewritePattern{ - dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("jobs"), - dyn.AnyKey(), - dyn.Key("environment_variables"), - dyn.AnyIndex(), - dyn.Key("spec"), - dyn.Key("files"), - dyn.AnyIndex(), - ), - TranslateModeFile, - noSkipRewrite, - }, - // Environment-level pattern for all jobs. - jobRewritePattern{ - dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("jobs"), - dyn.AnyKey(), - dyn.Key("environments"), - dyn.AnyIndex(), - dyn.Key("spec"), - dyn.Key("project_environment"), - ), - TranslateModeFile, - noSkipRewrite, - }, - ) +var jobTaskRules = []taskRule{ + {TranslateModeNotebook, func(t *jobs.Task, c *taskCursor) error { + if t.NotebookTask == nil { + return nil + } + return c.yield(t.NotebookTask, "notebook_path", &t.NotebookTask.NotebookPath, "notebook_task", "notebook_path") + }}, + {TranslateModeFile, func(t *jobs.Task, c *taskCursor) error { + if t.SparkPythonTask == nil { + return nil + } + return c.yield(t.SparkPythonTask, "python_file", &t.SparkPythonTask.PythonFile, "spark_python_task", "python_file") + }}, + {TranslateModeDirectory, func(t *jobs.Task, c *taskCursor) error { + if t.DbtTask == nil { + return nil + } + return c.yield(t.DbtTask, "project_directory", &t.DbtTask.ProjectDirectory, "dbt_task", "project_directory") + }}, + {TranslateModeFile, func(t *jobs.Task, c *taskCursor) error { + if t.SqlTask == nil || t.SqlTask.File == nil { + return nil + } + return c.yield(t.SqlTask.File, "path", &t.SqlTask.File.Path, "sql_task", "file", "path") + }}, + {TranslateModeFile, func(t *jobs.Task, c *taskCursor) error { + if t.AlertTask == nil { + return nil + } + return c.yield(t.AlertTask, "workspace_path", &t.AlertTask.WorkspacePath, "alert_task", "workspace_path") + }}, + {TranslateModeFile, func(t *jobs.Task, c *taskCursor) error { + for i := range t.Libraries { + if err := c.yield(&t.Libraries[i], "requirements", &t.Libraries[i].Requirements, "libraries", i, "requirements"); err != nil { + return err + } + } + return nil + }}, + // The AI Runtime task runs this bash script on each node; the backend reads it as + // a workspace file, so translate the local path to its remote (or + // immutable-snapshot) location like any other file. + {TranslateModeFile, func(t *jobs.Task, c *taskCursor) error { + if t.AiRuntimeTask == nil { + return nil + } + for i := range t.AiRuntimeTask.Deployments { + if err := c.yield(&t.AiRuntimeTask.Deployments[i], "command_path", &t.AiRuntimeTask.Deployments[i].CommandPath, "ai_runtime_task", "deployments", i, "command_path"); err != nil { + return err + } + } + return nil + }}, } -// VisitJobPaths visits all paths in job resources and applies a function to each path. -func VisitJobPaths(value dyn.Value, fn VisitFunc) (dyn.Value, error) { - var err error - newValue := value +// visitTasks applies the rules to the tasks of all jobs, then to the tasks nested in +// their for_each_task, one rule at a time. +func visitTasks(root *config.Root, rules []taskRule, fn VisitFunc) error { + if err := visitTasksAt(root, false, rules, fn); err != nil { + return err + } + return visitTasksAt(root, true, rules, fn) +} - for _, rewritePattern := range jobRewritePatterns() { - newValue, err = dyn.MapByPattern(newValue, rewritePattern.pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - if rewritePattern.skipRewrite(v.MustString()) { - return v, nil +// visitTasksAt applies the rules, one at a time, to the tasks of all jobs, or (nested) +// to the tasks in their for_each_task. +func visitTasksAt(root *config.Root, nested bool, rules []taskRule, fn VisitFunc) error { + c := &taskCursor{nested: nested, fn: fn} + names := sortedKeys(root.Resources.Jobs) + for r := range rules { + c.rule = &rules[r] + for _, name := range names { + job := root.Resources.Jobs[name] + if job == nil { + continue + } + c.name = name + for i := range job.Tasks { + task := &job.Tasks[i] + if nested { + if task.ForEachTask == nil { + continue + } + task = &task.ForEachTask.Task + } + c.index = i + if err := c.rule.visit(task, c); err != nil { + return err + } } - - return fn(p, rewritePattern.mode, v) - }) - if err != nil { - return dyn.InvalidValue, err } } + return nil +} - return newValue, nil +// VisitJobPaths visits all paths in job resources and applies a function to each path. +func VisitJobPaths(root *config.Root, fn VisitFunc) error { + if err := visitTasks(root, jobTaskRules, fn); err != nil { + return err + } + for _, name := range sortedKeys(root.Resources.Jobs) { + job := root.Resources.Jobs[name] + if job == nil { + continue + } + for i, ev := range job.EnvironmentVariables { + if ev.Spec == nil { + continue + } + for j := range ev.Spec.Files { + if err := visit(fn, visitArgs{ + value: &ev.Spec.Files[j], + mode: TranslateModeFile, + path: structpath.NewPath(resourcePath("jobs", name), "environment_variables", i, "spec", "files", j), + }); err != nil { + return err + } + } + } + } + for _, name := range sortedKeys(root.Resources.Jobs) { + job := root.Resources.Jobs[name] + if job == nil { + continue + } + for i, env := range job.Environments { + if env.Spec == nil { + continue + } + if err := visit(fn, visitArgs{ + owner: env.Spec, + field: "project_environment", + value: &env.Spec.ProjectEnvironment, + mode: TranslateModeFile, + path: structpath.NewPath(resourcePath("jobs", name), "environments", i, "spec", "project_environment"), + }); err != nil { + return err + } + } + } + return nil } diff --git a/bundle/config/mutator/paths/job_paths_visitor_test.go b/bundle/config/mutator/paths/job_paths_visitor_test.go index b9fe29d154e..2d5e0616312 100644 --- a/bundle/config/mutator/paths/job_paths_visitor_test.go +++ b/bundle/config/mutator/paths/job_paths_visitor_test.go @@ -5,7 +5,7 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/assert" @@ -85,15 +85,15 @@ func TestVisitJobPaths(t *testing.T) { } actual := collectVisitedPaths(t, root, VisitJobPaths) - expected := []dyn.Path{ - dyn.MustPathFromString("resources.jobs.job0.tasks[0].notebook_task.notebook_path"), - dyn.MustPathFromString("resources.jobs.job0.tasks[1].spark_python_task.python_file"), - dyn.MustPathFromString("resources.jobs.job0.tasks[2].dbt_task.project_directory"), - dyn.MustPathFromString("resources.jobs.job0.tasks[3].sql_task.file.path"), - dyn.MustPathFromString("resources.jobs.job0.tasks[6].libraries[0].requirements"), - dyn.MustPathFromString("resources.jobs.job0.tasks[7].alert_task.workspace_path"), - dyn.MustPathFromString("resources.jobs.job0.tasks[8].ai_runtime_task.deployments[0].command_path"), - } + expected := structpath.MustParsePaths( + "resources.jobs.job0.tasks[0].notebook_task.notebook_path", + "resources.jobs.job0.tasks[1].spark_python_task.python_file", + "resources.jobs.job0.tasks[2].dbt_task.project_directory", + "resources.jobs.job0.tasks[3].sql_task.file.path", + "resources.jobs.job0.tasks[6].libraries[0].requirements", + "resources.jobs.job0.tasks[7].alert_task.workspace_path", + "resources.jobs.job0.tasks[8].ai_runtime_task.deployments[0].command_path", + ) assert.ElementsMatch(t, expected, actual) } @@ -124,10 +124,10 @@ func TestVisitJobPaths_environments(t *testing.T) { } actual := collectVisitedPaths(t, root, VisitJobPaths) - expected := []dyn.Path{ - dyn.MustPathFromString("resources.jobs.job0.environments[1].spec.project_environment"), - dyn.MustPathFromString("resources.jobs.job0.environments[2].spec.project_environment"), - } + expected := structpath.MustParsePaths( + "resources.jobs.job0.environments[1].spec.project_environment", + "resources.jobs.job0.environments[2].spec.project_environment", + ) assert.ElementsMatch(t, expected, actual) } @@ -156,10 +156,10 @@ func TestVisitJobPaths_environmentVariables(t *testing.T) { } actual := collectVisitedPaths(t, root, VisitJobPaths) - expected := []dyn.Path{ - dyn.MustPathFromString("resources.jobs.job0.environment_variables[0].spec.files[0]"), - dyn.MustPathFromString("resources.jobs.job0.environment_variables[0].spec.files[1]"), - } + expected := structpath.MustParsePaths( + "resources.jobs.job0.environment_variables[0].spec.files[0]", + "resources.jobs.job0.environment_variables[0].spec.files[1]", + ) assert.ElementsMatch(t, expected, actual) } @@ -199,10 +199,10 @@ func TestVisitJobPaths_foreach(t *testing.T) { } actual := collectVisitedPaths(t, root, VisitJobPaths) - expected := []dyn.Path{ - dyn.MustPathFromString("resources.jobs.job0.tasks[0].for_each_task.task.notebook_task.notebook_path"), - dyn.MustPathFromString("resources.jobs.job0.tasks[1].for_each_task.task.alert_task.workspace_path"), - } + expected := structpath.MustParsePaths( + "resources.jobs.job0.tasks[0].for_each_task.task.notebook_task.notebook_path", + "resources.jobs.job0.tasks[1].for_each_task.task.alert_task.workspace_path", + ) assert.ElementsMatch(t, expected, actual) } diff --git a/bundle/config/mutator/paths/job_run_paths_visitor.go b/bundle/config/mutator/paths/job_run_paths_visitor.go index 3bc56bacf72..bc5cd3af98f 100644 --- a/bundle/config/mutator/paths/job_run_paths_visitor.go +++ b/bundle/config/mutator/paths/job_run_paths_visitor.go @@ -1,24 +1,32 @@ package paths import ( - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" ) // VisitJobRunPaths visits local paths on job_runs so NormalizePaths can rewrite // them relative to the bundle root. Not used by TranslatePaths: hashing still // needs a local glob, not a workspace path. -func VisitJobRunPaths(value dyn.Value, fn VisitFunc) (dyn.Value, error) { - pattern := dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("job_runs"), - dyn.AnyKey(), - dyn.Key("lifecycle"), - dyn.Key("triggers"), - dyn.AnyIndex(), - dyn.Key("on_file_change"), - ) - - return dyn.MapByPattern(value, pattern, func(path dyn.Path, value dyn.Value) (dyn.Value, error) { - return fn(path, TranslateModeLocalRelative, value) - }) +func VisitJobRunPaths(root *config.Root, fn VisitFunc) error { + for _, name := range sortedKeys(root.Resources.JobRuns) { + r := root.Resources.JobRuns[name] + if r == nil || r.Lifecycle == nil { + continue + } + for i := range r.Lifecycle.Triggers { + s := r.Lifecycle.Triggers[i].OnFileChange + if s == nil { + continue + } + if err := visit(fn, visitArgs{ + value: s, + mode: TranslateModeLocalRelative, + path: structpath.NewPath(resourcePath("job_runs", name), "lifecycle", "triggers", i, "on_file_change"), + }); err != nil { + return err + } + } + } + return nil } diff --git a/bundle/config/mutator/paths/job_run_paths_visitor_test.go b/bundle/config/mutator/paths/job_run_paths_visitor_test.go index 93df73d3c9e..ac552583a68 100644 --- a/bundle/config/mutator/paths/job_run_paths_visitor_test.go +++ b/bundle/config/mutator/paths/job_run_paths_visitor_test.go @@ -5,7 +5,7 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/stretchr/testify/assert" ) @@ -27,9 +27,7 @@ func TestVisitJobRunPaths(t *testing.T) { } actual := collectVisitedPaths(t, root, VisitJobRunPaths) - expected := []dyn.Path{ - dyn.MustPathFromString("resources.job_runs.run0.lifecycle.triggers[0].on_file_change"), - } + expected := structpath.MustParsePaths("resources.job_runs.run0.lifecycle.triggers[0].on_file_change") assert.ElementsMatch(t, expected, actual) } diff --git a/bundle/config/mutator/paths/pipeline_paths_visitor.go b/bundle/config/mutator/paths/pipeline_paths_visitor.go index b5af086dd34..7cb234baf21 100644 --- a/bundle/config/mutator/paths/pipeline_paths_visitor.go +++ b/bundle/config/mutator/paths/pipeline_paths_visitor.go @@ -1,137 +1,106 @@ package paths import ( - "github.com/databricks/cli/bundle/libraries" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go/service/pipelines" ) -type pipelineRewritePattern struct { - pattern dyn.Pattern - mode TranslateMode - - // If function defined in skipRewrite returns true, we skip rewriting the path. - // For example, for environment dependencies, we skip rewriting if the path is not a local library. - skipRewrite func(string) bool -} - -// Base pattern to match all libraries in all pipelines. -var base = dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("pipelines"), - dyn.AnyKey(), -) - -func pipelineRewritePatterns() []pipelineRewritePattern { - // Compile list of configuration paths to rewrite. - allPatterns := []pipelineRewritePattern{ - { - pattern: base.Append(dyn.Key("libraries"), dyn.AnyIndex(), dyn.Key("notebook"), dyn.Key("path")), - mode: TranslateModeNotebook, - skipRewrite: noSkipRewrite, - }, - { - pattern: base.Append(dyn.Key("libraries"), dyn.AnyIndex(), dyn.Key("file"), dyn.Key("path")), - mode: TranslateModeFile, - skipRewrite: noSkipRewrite, - }, - { - pattern: base.Append(dyn.Key("libraries"), dyn.AnyIndex(), dyn.Key("glob"), dyn.Key("include")), - mode: TranslateModeGlob, - skipRewrite: noSkipRewrite, - }, - { - pattern: base.Append(dyn.Key("root_path")), - mode: TranslateModeDirectory, - skipRewrite: noSkipRewrite, - }, - } - - return allPatterns +// pipelineLibraryRule visits one kind of path value of the libraries of a pipeline. +type pipelineLibraryRule struct { + mode TranslateMode + keys []any + field func(*pipelines.PipelineLibrary) (owner any, s *string) } -func pipelineLibrariesRewritePatterns() []pipelineRewritePattern { - pipelineEnvironmentsPatterns := []pipelineRewritePattern{ - { - pattern: dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("pipelines"), - dyn.AnyKey(), - dyn.Key("environment"), - dyn.Key("dependencies"), - dyn.AnyIndex(), - ), - mode: TranslateModeLocalRelativeWithPrefix, - skipRewrite: func(s string) bool { - return !libraries.IsLibraryLocal(s) - }, - }, - } - - pipelineEnvironmentsPatternsWithPipFlags := []pipelineRewritePattern{ - { - dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("pipelines"), - dyn.AnyKey(), - dyn.Key("environment"), - dyn.Key("dependencies"), - dyn.AnyIndex(), - ), - TranslateModeEnvironmentPipFlag, - func(s string) bool { - _, _, ok := libraries.IsLocalPathInPipFlag(s) - return !ok - }, - }, - } - - return append(pipelineEnvironmentsPatterns, pipelineEnvironmentsPatternsWithPipFlags...) +var pipelineLibraryRules = []pipelineLibraryRule{ + {TranslateModeNotebook, []any{"notebook", "path"}, func(l *pipelines.PipelineLibrary) (any, *string) { + if l.Notebook == nil { + return nil, nil + } + return l.Notebook, &l.Notebook.Path + }}, + {TranslateModeFile, []any{"file", "path"}, func(l *pipelines.PipelineLibrary) (any, *string) { + if l.File == nil { + return nil, nil + } + return l.File, &l.File.Path + }}, + {TranslateModeGlob, []any{"glob", "include"}, func(l *pipelines.PipelineLibrary) (any, *string) { + if l.Glob == nil { + return nil, nil + } + return l.Glob, &l.Glob.Include + }}, } -func VisitPipelinePaths(value dyn.Value, fn VisitFunc) (dyn.Value, error) { - var err error - newValue := value - - for _, rewritePattern := range pipelineRewritePatterns() { - newValue, err = dyn.MapByPattern(newValue, rewritePattern.pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - sv, ok := v.AsString() - if !ok { - return v, nil +func VisitPipelinePaths(root *config.Root, fn VisitFunc) error { + for _, rule := range pipelineLibraryRules { + for _, name := range sortedKeys(root.Resources.Pipelines) { + pl := root.Resources.Pipelines[name] + if pl == nil { + continue } - if rewritePattern.skipRewrite(sv) { - return v, nil + for i := range pl.Libraries { + owner, s := rule.field(&pl.Libraries[i]) + if s == nil { + continue + } + if err := visit(fn, visitArgs{ + owner: owner, + field: rule.keys[len(rule.keys)-1].(string), + value: s, + mode: rule.mode, + path: structpath.NewPath(structpath.NewPath(resourcePath("pipelines", name), "libraries", i), rule.keys...), + }); err != nil { + return err + } } - - return fn(p, rewritePattern.mode, v) - }) - if err != nil { - return dyn.InvalidValue, err } } - - return newValue, nil + for _, name := range sortedKeys(root.Resources.Pipelines) { + if pl := root.Resources.Pipelines[name]; pl != nil { + if err := visit(fn, visitArgs{ + owner: pl, + field: "root_path", + value: &pl.RootPath, + mode: TranslateModeDirectory, + path: structpath.NewPath(resourcePath("pipelines", name), "root_path"), + }); err != nil { + return err + } + } + } + return nil } -func VisitPipelineLibrariesPaths(value dyn.Value, fn VisitFunc) (dyn.Value, error) { - var err error - newValue := value - - for _, rewritePattern := range pipelineLibrariesRewritePatterns() { - newValue, err = dyn.MapByPattern(newValue, rewritePattern.pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - sv, ok := v.AsString() - if !ok { - return v, nil +func VisitPipelineLibrariesPaths(root *config.Root, fn VisitFunc) error { + for _, rule := range []struct { + mode TranslateMode + skip func(string) bool + }{ + {TranslateModeLocalRelativeWithPrefix, notLocalLibrary}, + {TranslateModeEnvironmentPipFlag, noLocalPathInPipFlag}, + } { + for _, name := range sortedKeys(root.Resources.Pipelines) { + pl := root.Resources.Pipelines[name] + if pl == nil || pl.Environment == nil { + continue } - if rewritePattern.skipRewrite(sv) { - return v, nil + for i := range pl.Environment.Dependencies { + // Most dependencies are not local paths: check before building the path. + if rule.skip(pl.Environment.Dependencies[i]) { + continue + } + if err := visit(fn, visitArgs{ + value: &pl.Environment.Dependencies[i], + mode: rule.mode, + path: structpath.NewPath(resourcePath("pipelines", name), "environment", "dependencies", i), + }); err != nil { + return err + } } - - return fn(p, rewritePattern.mode, v) - }) - if err != nil { - return dyn.InvalidValue, err } } - - return newValue, nil + return nil } diff --git a/bundle/config/mutator/paths/pipeline_paths_visitor_test.go b/bundle/config/mutator/paths/pipeline_paths_visitor_test.go index eb9748bba37..6a7b2c6183d 100644 --- a/bundle/config/mutator/paths/pipeline_paths_visitor_test.go +++ b/bundle/config/mutator/paths/pipeline_paths_visitor_test.go @@ -5,7 +5,7 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/pipelines" "github.com/stretchr/testify/assert" ) @@ -47,13 +47,13 @@ func TestVisitPipelinePaths(t *testing.T) { actual := collectVisitedPaths(t, root, VisitPipelinePaths) actual = append(actual, collectVisitedPaths(t, root, VisitPipelineLibrariesPaths)...) - expected := []dyn.Path{ - dyn.MustPathFromString("resources.pipelines.pipeline0.libraries[0].file.path"), - dyn.MustPathFromString("resources.pipelines.pipeline0.libraries[1].notebook.path"), - dyn.MustPathFromString("resources.pipelines.pipeline0.libraries[2].glob.include"), - dyn.MustPathFromString("resources.pipelines.pipeline0.root_path"), - dyn.MustPathFromString("resources.pipelines.pipeline0.environment.dependencies[0]"), - } + expected := structpath.MustParsePaths( + "resources.pipelines.pipeline0.libraries[0].file.path", + "resources.pipelines.pipeline0.libraries[1].notebook.path", + "resources.pipelines.pipeline0.libraries[2].glob.include", + "resources.pipelines.pipeline0.root_path", + "resources.pipelines.pipeline0.environment.dependencies[0]", + ) assert.ElementsMatch(t, expected, actual) } diff --git a/bundle/config/mutator/paths/visitor.go b/bundle/config/mutator/paths/visitor.go index cadc52b75fe..dc3752debe7 100644 --- a/bundle/config/mutator/paths/visitor.go +++ b/bundle/config/mutator/paths/visitor.go @@ -1,14 +1,22 @@ package paths import ( - "github.com/databricks/cli/libs/dyn" + "maps" + "reflect" + "slices" + + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) -type VisitFunc func(path dyn.Path, mode TranslateMode, value dyn.Value) (dyn.Value, error) +// VisitFunc is called for every path value. It may rewrite the value through the +// pointer; the value keeps its locations. +type VisitFunc func(path *structpath.PathNode, mode TranslateMode, value *string) error // VisitPaths visits all paths in bundle configuration -func VisitPaths(root dyn.Value, fn VisitFunc) (dyn.Value, error) { - visitors := []func(dyn.Value, VisitFunc) (dyn.Value, error){ +func VisitPaths(root *config.Root, fn VisitFunc) error { + visitors := []func(*config.Root, VisitFunc) error{ VisitJobPaths, VisitJobRunPaths, VisitJobLibrariesPaths, @@ -21,14 +29,61 @@ func VisitPaths(root dyn.Value, fn VisitFunc) (dyn.Value, error) { VisitPipelineLibrariesPaths, } - newRoot := root for _, visitor := range visitors { - updatedRoot, err := visitor(newRoot, fn) - if err != nil { - return dyn.InvalidValue, err + if err := visitor(root, fn); err != nil { + return err } - newRoot = updatedRoot } - return newRoot, nil + return nil +} + +// visitArgs describes one path value for [visit]. +type visitArgs struct { + // owner is the struct that holds the field, for its ForceSendFields; nil for list + // elements, which are always present. + owner any + + // field is the JSON key of the field in owner. + field string + + // value is the path value; fn may rewrite it. + value *string + + mode TranslateMode + + // path is the path of the value. + path *structpath.PathNode +} + +// present reports whether the path value is present: an empty value is present only if +// it is in the owner's ForceSendFields (as [structvar.View] decides). Callers that visit +// many empty values check it before building the path. +func present(owner any, field string, value *string) bool { + return *value != "" || owner == nil || isForced(owner, field) +} + +// visit calls fn for the path value described by a, unless it is not present. +func visit(fn VisitFunc, a visitArgs) error { + if !present(a.owner, a.field, a.value) { + return nil + } + return fn(a.path, a.mode, a.value) +} + +// isForced reports whether the field key of the struct *owner is in its ForceSendFields. +// It is only called for empty values. +func isForced(owner any, key string) bool { + v := reflect.ValueOf(owner).Elem() + return structvar.GetStructInfo(v.Type()).IsForceSend(v, key) +} + +// sortedKeys returns the keys of a resource map in the order the paths are visited. +func sortedKeys[V any](m map[string]V) []string { + return slices.Sorted(maps.Keys(m)) +} + +// resourcePath returns the path of the resource name of the given type. +func resourcePath(typ, name string) *structpath.PathNode { + return structpath.NewPath(nil, "resources", typ, name) } diff --git a/bundle/config/mutator/paths/visitor_test.go b/bundle/config/mutator/paths/visitor_test.go index 019342404c8..5f9c8dc2af6 100644 --- a/bundle/config/mutator/paths/visitor_test.go +++ b/bundle/config/mutator/paths/visitor_test.go @@ -4,18 +4,16 @@ import ( "testing" "github.com/databricks/cli/bundle/config" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/stretchr/testify/require" ) // collectVisitedPaths is a helper function that collects all visited paths for testing -func collectVisitedPaths(t *testing.T, root config.Root, visitFn func(value dyn.Value, fn VisitFunc) (dyn.Value, error)) []dyn.Path { - var actual []dyn.Path - err := root.Mutate(func(value dyn.Value) (dyn.Value, error) { - return visitFn(value, func(p dyn.Path, mode TranslateMode, v dyn.Value) (dyn.Value, error) { - actual = append(actual, p) - return v, nil - }) +func collectVisitedPaths(t *testing.T, root config.Root, visitFn func(root *config.Root, fn VisitFunc) error) []*structpath.PathNode { + var actual []*structpath.PathNode + err := visitFn(&root, func(p *structpath.PathNode, mode TranslateMode, s *string) error { + actual = append(actual, p) + return nil }) require.NoError(t, err) return actual diff --git a/bundle/config/mutator/populate_locations.go b/bundle/config/mutator/populate_locations.go index 8f7a1258b1e..31bafec647c 100644 --- a/bundle/config/mutator/populate_locations.go +++ b/bundle/config/mutator/populate_locations.go @@ -2,10 +2,15 @@ package mutator import ( "context" + "fmt" + "strings" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/loctable" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn/dynloc" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) type populateLocations struct{} @@ -20,9 +25,67 @@ func (m *populateLocations) Name() string { return "PopulateLocations" } +// pathString returns the path with keys separated by dots, whatever characters they contain. +func pathString(p *structpath.PathNode) string { + var b strings.Builder + for i, n := range p.AsSlice() { + if k, ok := n.StringKey(); ok { + if i > 0 { + b.WriteByte('.') + } + b.WriteString(k) + } else if idx, ok := n.Index(); ok { + fmt.Fprintf(&b, "[%d]", idx) + } + } + return b.String() +} + +// gatherLocations returns the locations of the top level fields, the resource groups +// ("resources.jobs"), the resources ("resources.jobs.my_job"), and job tasks +// ("resources.jobs.my_job.tasks", "resources.jobs.my_job.tasks[2]"). Resources and tasks +// come from the configuration, their locations from the locations tree (values without +// locations have no entry). +func gatherLocations(r *config.Root) map[string][]diag.Location { + out := map[string][]diag.Location{} + tree := r.LocationTree() + for k, top := range tree.KeyItems() { + addLocations(out, structpath.NewPath(nil, k), top) + } + groups := tree.Key("resources") + for group, g := range groups.KeyItems() { + addLocations(out, structpath.NewPath(nil, "resources", group), g) + } + r.Resources.IterateAllResources(func(group, name string, _ config.ConfigResource) bool { + rp := structpath.NewPath(nil, "resources", group, name) + res := groups.Key(group).Key(name) + addLocations(out, rp, res) + if group == "jobs" { + if job := r.Resources.Jobs[name]; job != nil { + tasks := res.Key("tasks") + addLocations(out, structpath.NewPath(rp, "tasks"), tasks) + for i := range job.Tasks { + addLocations(out, structpath.NewPath(rp, "tasks", i), tasks.Index(i)) + } + } + } + return true + }) + return out +} + +// addLocations records the locations of the value at p (from its tree l), if any. +func addLocations(out map[string][]diag.Location, p *structpath.PathNode, l *structvar.Locations) { + if locs := l.Get(); len(locs) > 0 { + out[pathString(p)] = locs + } +} + func (m *populateLocations) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - locs, err := dynloc.Build( - b.Config.Value(), + pathToLocations := gatherLocations(&b.Config) + + locs, err := loctable.Build( + pathToLocations, // Make all paths relative to the bundle root. b.BundleRootPath, ) diff --git a/bundle/config/mutator/python/apply_python_output.go b/bundle/config/mutator/python/apply_python_output.go index fb5452d3b4f..33a957f3e48 100644 --- a/bundle/config/mutator/python/apply_python_output.go +++ b/bundle/config/mutator/python/apply_python_output.go @@ -3,10 +3,10 @@ package python import ( "fmt" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator/resourcemutator" - - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/merge" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) // applyPythonOutputResult contains which resources where added, updated, or deleted by Python mutator. @@ -24,162 +24,150 @@ type applyPythonOutputResult struct { // Return value is equivalent to output except for: // - if property is unchanged in output, it's original location will be preserved // - if empty sequence/mapping is deleted in output, it's original value will be preserved -func applyPythonOutput(root, output dyn.Value) (dyn.Value, applyPythonOutputResult, error) { +func applyPythonOutput(root, output *config.Root) (*structvar.OverridePlan, applyPythonOutputResult, error) { result, visitor := createOverrideVisitor(root, output) - merged, err := merge.Override(root, output, visitor) + plan, err := structvar.PlanOverride(root.View(), output.View(), visitor) if err != nil { - return dyn.InvalidValue, result, err + return nil, result, err } - return merged, result, nil + return plan, result, nil } -func createOverrideVisitor(leftRoot, rightRoot dyn.Value) (applyPythonOutputResult, merge.OverrideVisitor) { - resourcesPath := dyn.NewPath(dyn.Key("resources")) +func createOverrideVisitor(leftRoot, rightRoot *config.Root) (applyPythonOutputResult, structvar.OverrideVisitor) { + resourcesPath := structpath.NewPath(nil, "resources") deleted := resourcemutator.NewResourceKeySet() updated := resourcemutator.NewResourceKeySet() added := resourcemutator.NewResourceKeySet() - visitor := merge.OverrideVisitor{ - VisitDelete: func(valuePath dyn.Path, left dyn.Value) error { + visitor := structvar.OverrideVisitor{ + VisitDelete: func(np *structpath.PathNode, left structvar.View) error { if isOmitemptyDelete(left) { - return merge.ErrOverrideUndoDelete + return structvar.ErrOverrideUndoDelete } - if !valuePath.HasPrefix(resourcesPath) { - return fmt.Errorf("unexpected change at %q (delete)", valuePath.String()) + if !np.HasPrefix(resourcesPath) { + return fmt.Errorf("unexpected change at %q (delete)", np.String()) } // use leftRoot below because it contains deleted resources - if len(valuePath) == 1 { + if np.Len() == 1 { // Example: // // valuePath: "resources" // leftRoot: {"bundle": ..., "resources": ...}, // rightRoot: {"bundle": ...} - return deleted.AddPattern( - dyn.NewPatternFromPath(valuePath).Append(dyn.AnyKey(), dyn.AnyKey()), - leftRoot, - ) - } else if len(valuePath) == 2 { + deleted.AddResources(&leftRoot.Resources) + return nil + } else if np.Len() == 2 { // Example: // // valuePath: "resources.jobs" // leftRoot: {"resources": { "jobs": ..., "pipeline": ...}}}, // rightRoot: {"resources": { "jobs": ...}}}, - return deleted.AddPattern( - dyn.NewPatternFromPath(valuePath).Append(dyn.AnyKey()), - leftRoot, - ) - } else if len(valuePath) == 3 { + deleted.AddResourcesOfType(&leftRoot.Resources, np.KeyAt(1)) + return nil + } else if np.Len() == 3 { // Example: "resources.jobs.job_0" - resourceKey := resourcemutator.ResourceKey{Type: valuePath[1].Key(), Name: valuePath[2].Key()} + resourceKey := resourcemutator.ResourceKey{Type: np.KeyAt(1), Name: np.KeyAt(2)} deleted.AddResourceKey(resourceKey) return nil } else { // Example: "resources.jobs.job_0.tags" - resourceKey := resourcemutator.ResourceKey{Type: valuePath[1].Key(), Name: valuePath[2].Key()} + resourceKey := resourcemutator.ResourceKey{Type: np.KeyAt(1), Name: np.KeyAt(2)} updated.AddResourceKey(resourceKey) return nil } }, - VisitInsert: func(valuePath dyn.Path, right dyn.Value) (dyn.Value, error) { - if !valuePath.HasPrefix(resourcesPath) { - return dyn.InvalidValue, fmt.Errorf("unexpected change at %q (insert)", valuePath.String()) + VisitInsert: func(np *structpath.PathNode, right structvar.View) error { + if !np.HasPrefix(resourcesPath) { + return fmt.Errorf("unexpected change at %q (insert)", np.String()) } // use rightRoot below because it contains result - if len(valuePath) == 1 { + if np.Len() == 1 { // Example: // // valuePath: "resources" // leftRoot: {"bundle": ..., } // rightRoot: {"bundle": ..., "resources": {...} } - return right, added.AddPattern( - dyn.NewPatternFromPath(valuePath).Append(dyn.AnyKey(), dyn.AnyKey()), - rightRoot, - ) - } else if len(valuePath) == 2 { + added.AddResources(&rightRoot.Resources) + return nil + } else if np.Len() == 2 { // Example: // // valuePath: "resources.jobs" // leftRoot: {"resources": { }} // rightRoot: {"resources": { "jobs": {...} }} - return right, added.AddPattern( - dyn.NewPatternFromPath(valuePath).Append(dyn.AnyKey()), - rightRoot, - ) - } else if len(valuePath) == 3 { + added.AddResourcesOfType(&rightRoot.Resources, np.KeyAt(1)) + return nil + } else if np.Len() == 3 { // Example: // // valuePath: "resources.jobs" // leftRoot: {"resources": { "jobs": { }}} // rightRoot: {"resources": { "jobs": {"job_0": {...} }}} - resourceKey := resourcemutator.ResourceKey{Type: valuePath[1].Key(), Name: valuePath[2].Key()} + resourceKey := resourcemutator.ResourceKey{Type: np.KeyAt(1), Name: np.KeyAt(2)} added.AddResourceKey(resourceKey) - return right, nil + return nil } else { // Example: "resources.jobs.job_0.email_notifications" - resourceKey := resourcemutator.ResourceKey{Type: valuePath[1].Key(), Name: valuePath[2].Key()} + resourceKey := resourcemutator.ResourceKey{Type: np.KeyAt(1), Name: np.KeyAt(2)} updated.AddResourceKey(resourceKey) - return right, nil + return nil } }, - VisitUpdate: func(valuePath dyn.Path, _, right dyn.Value) (dyn.Value, error) { - if !valuePath.HasPrefix(resourcesPath) { - return dyn.InvalidValue, fmt.Errorf("unexpected change at %q (update)", valuePath.String()) + VisitUpdate: func(np *structpath.PathNode, _, right structvar.View) error { + if !np.HasPrefix(resourcesPath) { + return fmt.Errorf("unexpected change at %q (update)", np.String()) } // use rightRoot below because it contains result - if len(valuePath) == 1 { + if np.Len() == 1 { // Example: // // valuePath: "resources" // leftRoot: {"bundle": ..., "resources": null } // rightRoot: {"bundle": ..., "resources": {...} } - return right, added.AddPattern( - dyn.NewPatternFromPath(valuePath).Append(dyn.AnyKey(), dyn.AnyKey()), - rightRoot, - ) - } else if len(valuePath) == 2 { + added.AddResources(&rightRoot.Resources) + return nil + } else if np.Len() == 2 { // Example: // // valuePath: "resources.jobs" // leftRoot: {"resources": { "jobs": null }} // rightRoot: {"resources": { "jobs": {...} }} - return right, added.AddPattern( - dyn.NewPatternFromPath(valuePath).Append(dyn.AnyKey()), - rightRoot, - ) - } else if len(valuePath) == 3 { + added.AddResourcesOfType(&rightRoot.Resources, np.KeyAt(1)) + return nil + } else if np.Len() == 3 { // Example: // // valuePath: "resources.jobs.job_0" // leftRoot: {"resources": { "jobs": {"job_0": null }}} // rightRoot: {"resources": { "jobs": {"job_0": {...} }}} - resourceKey := resourcemutator.ResourceKey{Type: valuePath[1].Key(), Name: valuePath[2].Key()} + resourceKey := resourcemutator.ResourceKey{Type: np.KeyAt(1), Name: np.KeyAt(2)} added.AddResourceKey(resourceKey) - return right, nil + return nil } else { // Example: "resources.jobs.job_0.name" - resourceKey := resourcemutator.ResourceKey{Type: valuePath[1].Key(), Name: valuePath[2].Key()} + resourceKey := resourcemutator.ResourceKey{Type: np.KeyAt(1), Name: np.KeyAt(2)} updated.AddResourceKey(resourceKey) - return right, nil + return nil } }, } @@ -191,19 +179,25 @@ func createOverrideVisitor(leftRoot, rightRoot dyn.Value) (applyPythonOutputResu }, visitor } -func isOmitemptyDelete(left dyn.Value) bool { +func isOmitemptyDelete(left structvar.View) bool { // Python output can omit empty sequences/mappings, because we don't track them as optional, // there is no semantic difference between empty and missing, so we keep them as they were before // Python mutator deleted them. switch left.Kind() { - case dyn.KindMap: - return left.MustMap().Len() == 0 + case structvar.KindMap: + for range left.MapItems() { + return false + } + return true - case dyn.KindSequence: - return len(left.MustSequence()) == 0 + case structvar.KindSequence: + for range left.Sequence() { + return false + } + return true - case dyn.KindNil: + case structvar.KindNil: // map/sequence can be nil, for instance, bad YAML like: `foo:` return true diff --git a/bundle/config/mutator/python/apply_python_output_test.go b/bundle/config/mutator/python/apply_python_output_test.go index 94d7e7c39cc..bd27baf8eec 100644 --- a/bundle/config/mutator/python/apply_python_output_test.go +++ b/bundle/config/mutator/python/apply_python_output_test.go @@ -1,22 +1,24 @@ package python import ( + "encoding/json" "testing" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator/resourcemutator" - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn/merge" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" - "github.com/databricks/cli/libs/dyn" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type applyPythonOutputTestCase struct { name string - input dyn.Value - output dyn.Value + input any + output any added []resourcemutator.ResourceKey updated []resourcemutator.ResourceKey @@ -24,8 +26,8 @@ type applyPythonOutputTestCase struct { } func TestApplyPythonOutput(t *testing.T) { - job1 := mapOf("name", dyn.V("job 1")) - job2 := mapOf("name", dyn.V("job 2")) + job1 := mapOf("name", "job 1") + job2 := mapOf("name", "job 2") testCases := []applyPythonOutputTestCase{ { @@ -130,10 +132,10 @@ func TestApplyPythonOutput(t *testing.T) { { name: "update job through 'description' insert", input: mapOf("resources", mapOf("jobs", - mapOf("job_1", mapOf("name", dyn.V("name"))), + mapOf("job_1", mapOf("name", "name")), )), output: mapOf("resources", mapOf("jobs", - mapOf("job_1", mapOf2("name", dyn.V("name"), "description", dyn.V("description"))), + mapOf("job_1", mapOf2("name", "name", "description", "description")), )), updated: []resourcemutator.ResourceKey{ { @@ -146,10 +148,13 @@ func TestApplyPythonOutput(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - merged, state, err := applyPythonOutput(tc.input, tc.output) + input := loadRoot(t, tc.input) + output := loadRoot(t, tc.output) + plan, state, err := applyPythonOutput(input, output) + require.NoError(t, err) - assert.NoError(t, err) - assert.Equal(t, tc.output, merged) + require.NoError(t, input.Override(plan)) + assert.Equal(t, output.View().AsAny(), input.View().AsAny()) assert.ElementsMatch(t, tc.added, state.AddedResources.ToArray()) assert.ElementsMatch(t, tc.updated, state.UpdatedResources.ToArray()) @@ -159,36 +164,45 @@ func TestApplyPythonOutput(t *testing.T) { } func TestMergeOutput_disallowDelete(t *testing.T) { - input := mapOf("not_resource", dyn.V("value")) - output := emptyMap() + input := loadRoot(t, mapOf("bundle", mapOf("name", "value"))) + output := loadRoot(t, emptyMap()) _, _, err := applyPythonOutput(input, output) - assert.EqualError(t, err, `unexpected change at "not_resource" (delete)`) + assert.EqualError(t, err, `unexpected change at "bundle" (delete)`) } func TestMergeOutput_disallowInsert(t *testing.T) { - output := mapOf("not_resource", dyn.V("value")) - input := emptyMap() + output := loadRoot(t, mapOf("bundle", mapOf("name", "value"))) + input := loadRoot(t, emptyMap()) _, _, err := applyPythonOutput(input, output) - assert.EqualError(t, err, `unexpected change at "not_resource" (insert)`) + assert.EqualError(t, err, `unexpected change at "bundle" (insert)`) } func TestMergeOutput_disallowUpdate(t *testing.T) { - output := mapOf("not_resource", dyn.V("value")) - input := mapOf("not_resource", dyn.V("new value")) + output := loadRoot(t, mapOf("bundle", mapOf("name", "value"))) + input := loadRoot(t, mapOf("bundle", mapOf("name", "new value"))) _, _, err := applyPythonOutput(input, output) - assert.EqualError(t, err, `unexpected change at "not_resource" (update)`) + assert.EqualError(t, err, `unexpected change at "bundle.name" (update)`) +} + +// loadRoot returns the configuration for the configuration tree v. +func loadRoot(t *testing.T, v any) *config.Root { + raw, err := json.Marshal(v) + require.NoError(t, err) + r, diags := config.LoadFromBytes("output.json", raw) + require.NoError(t, diags.Error()) + return r } type overrideVisitorOmitemptyTestCase struct { name string - path dyn.Path - left dyn.Value + path *structpath.PathNode + left any expectedErr error } @@ -197,76 +211,70 @@ func TestCreateOverrideVisitor_omitempty(t *testing.T) { // there is no semantic difference between empty and missing, so we keep them as they were before // Python code deleted them. - location := diag.Location{ - File: "databricks.yml", - Line: 10, - Column: 20, - } - testCases := []overrideVisitorOmitemptyTestCase{ { name: "undo delete of empty variables", - path: dyn.MustPathFromString("variables"), - left: dyn.NewValue([]dyn.Value{}, []diag.Location{location}), - expectedErr: merge.ErrOverrideUndoDelete, + path: structpath.MustParsePath("variables"), + left: &[]string{}, + expectedErr: structvar.ErrOverrideUndoDelete, }, { name: "undo delete of empty job clusters", - path: dyn.MustPathFromString("resources.jobs.job0.job_clusters"), - left: dyn.NewValue([]dyn.Value{}, []diag.Location{location}), - expectedErr: merge.ErrOverrideUndoDelete, + path: structpath.MustParsePath("resources.jobs.job0.job_clusters"), + left: &[]string{}, + expectedErr: structvar.ErrOverrideUndoDelete, }, { name: "allow delete of non-empty job clusters", - path: dyn.MustPathFromString("resources.jobs.job0.job_clusters"), - left: dyn.NewValue([]dyn.Value{dyn.NewValue("abc", []diag.Location{location})}, []diag.Location{location}), + path: structpath.MustParsePath("resources.jobs.job0.job_clusters"), + left: &[]string{"abc"}, expectedErr: nil, }, { name: "undo delete of empty tags", - path: dyn.MustPathFromString("resources.jobs.job0.tags"), - left: dyn.NewValue(map[string]dyn.Value{}, []diag.Location{location}), - expectedErr: merge.ErrOverrideUndoDelete, + path: structpath.MustParsePath("resources.jobs.job0.tags"), + left: &map[string]string{}, + expectedErr: structvar.ErrOverrideUndoDelete, }, { name: "allow delete of non-empty tags", - path: dyn.MustPathFromString("resources.jobs.job0.tags"), - left: dyn.NewValue(map[string]dyn.Value{"dev": dyn.NewValue("true", []diag.Location{location})}, []diag.Location{location}), + path: structpath.MustParsePath("resources.jobs.job0.tags"), + left: &map[string]string{"dev": "true"}, expectedErr: nil, }, { name: "undo delete of nil", - path: dyn.MustPathFromString("resources.jobs.job0.tags"), - left: dyn.NilValue.WithLocations([]diag.Location{location}), - expectedErr: merge.ErrOverrideUndoDelete, + path: structpath.MustParsePath("resources.jobs.job0.tags"), + left: (*map[string]string)(nil), + expectedErr: structvar.ErrOverrideUndoDelete, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - _, visitor := createOverrideVisitor(dyn.NilValue, dyn.NilValue) + _, visitor := createOverrideVisitor(nil, nil) - err := visitor.VisitDelete(tc.path, tc.left) + err := visitor.VisitDelete(tc.path, structvar.NewView(tc.left, nil, nil)) assert.Equal(t, tc.expectedErr, err) }) } } -func mapOf(key string, value dyn.Value) dyn.Value { - return dyn.V(map[string]dyn.Value{ +func mapOf(key string, value any) any { + return map[string]any{ key: value, - }) + } } -func mapOf2(key1 string, value1 dyn.Value, key2 string, value2 dyn.Value) dyn.Value { - return dyn.V(map[string]dyn.Value{ +func mapOf2(key1 string, value1 any, key2 string, value2 any) any { + return map[string]any{ key1: value1, key2: value2, - }) + } } -func emptyMap() dyn.Value { - return dyn.V(map[string]dyn.Value{}) +func emptyMap() any { + return map[string]any{} } diff --git a/bundle/config/mutator/python/python_diagnostics.go b/bundle/config/mutator/python/python_diagnostics.go index 3006ee8321b..9c01a9b349d 100644 --- a/bundle/config/mutator/python/python_diagnostics.go +++ b/bundle/config/mutator/python/python_diagnostics.go @@ -6,7 +6,6 @@ import ( "io" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" ) @@ -58,7 +57,7 @@ func parsePythonDiagnostics(input io.Reader) (diag.Diagnostics, error) { } var paths []*structpath.PathNode if path != nil { - paths = dyn.ToStructPaths(path) + paths = []*structpath.PathNode{path} } var locations []diag.Location @@ -81,12 +80,12 @@ func parsePythonDiagnostics(input io.Reader) (diag.Diagnostics, error) { return diags, nil } -func convertPythonPath(path string) (dyn.Path, error) { +func convertPythonPath(path string) (*structpath.PathNode, error) { if path == "" { return nil, nil } - return dyn.NewPathFromString(path) + return structpath.ParsePath(path) } func convertPythonSeverity(severity pythonSeverity) (diag.Severity, error) { diff --git a/bundle/config/mutator/python/python_locations.go b/bundle/config/mutator/python/python_locations.go index bb301da9755..ab4d319c4b7 100644 --- a/bundle/config/mutator/python/python_locations.go +++ b/bundle/config/mutator/python/python_locations.go @@ -8,7 +8,8 @@ import ( "path/filepath" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) // generatedFileName is used as the virtual file name for YAML generated by Python code. @@ -19,7 +20,7 @@ const generatedFileName = "__generated_by_python__.yml" // pythonLocations is data structure for efficient location lookup for a given path // -// Locations form a tree, and we assign locations of the closest ancestor to each dyn.Value based on its path. +// Locations form a tree, and we assign locations of the closest ancestor to each value based on its path. // We implement it as a trie (prefix tree) where keys are components of the path. With that, lookups are O(n) // where n is the number of components in the path. // @@ -57,15 +58,16 @@ type pythonLocationEntry struct { Column int `json:"column"` } -// mergePythonLocations applies locations from Python mutator into given dyn.Value +// mergePythonLocations returns a location mapper that applies locations from the +// Python mutator. // // The primary use-case is to merge locations.json with output.json, so that any // validation errors will point to Python source code instead of generated YAML. -func mergePythonLocations(value dyn.Value, locations *pythonLocations) (dyn.Value, error) { - return dyn.Walk(value, func(path dyn.Path, value dyn.Value) (dyn.Value, error) { +func mergePythonLocations(locations *pythonLocations) structvar.LocationMapper { + return func(path *structpath.PathNode, locs []diag.Location) []diag.Location { newLocation, ok := findPythonLocation(locations, path) if !ok { - return value, nil + return locs } // The first item in the list is the "last" location used for error reporting @@ -75,13 +77,8 @@ func mergePythonLocations(value dyn.Value, locations *pythonLocations) (dyn.Valu // // We preserve any previous non-virtual locations in case when Python function modified // resource defined in YAML. - newLocations := append( - []diag.Location{newLocation}, - removeVirtualLocations(value.Locations())..., - ) - - return value.WithLocations(newLocations), nil - }) + return append([]diag.Location{newLocation}, removeVirtualLocations(locs)...) + } } func removeVirtualLocations(locations []diag.Location) []diag.Location { @@ -113,7 +110,7 @@ func parsePythonLocations(bundleRoot string, input io.Reader) (*pythonLocations, return nil, fmt.Errorf("failed to parse python location: %w", err) } - path, err := dyn.NewPathFromString(entry.Path) + path, err := structpath.ParsePath(entry.Path) if err != nil { return nil, fmt.Errorf("failed to parse python location: %w", err) } @@ -137,18 +134,18 @@ func parsePythonLocations(bundleRoot string, input io.Reader) (*pythonLocations, } // putPythonLocation puts the location to the trie for the given path -func putPythonLocation(trie *pythonLocations, path dyn.Path, location diag.Location) { +func putPythonLocation(trie *pythonLocations, path *structpath.PathNode, location diag.Location) { currentNode := trie - for _, component := range path { - if key := component.Key(); key != "" { + for _, component := range path.AsSlice() { + if key, ok := component.StringKey(); ok { if _, ok := currentNode.keys[key]; !ok { currentNode.keys[key] = newPythonLocations() } currentNode = currentNode.keys[key] } else { - index := component.Index() + index, _ := component.Index() if _, ok := currentNode.indexes[index]; !ok { currentNode.indexes[index] = newPythonLocations() } @@ -171,20 +168,20 @@ func newPythonLocations() *pythonLocations { // findPythonLocation finds the location or closest ancestor location in the trie for the given path // if no ancestor or exact location is found, false is returned. -func findPythonLocation(locations *pythonLocations, path dyn.Path) (diag.Location, bool) { +func findPythonLocation(locations *pythonLocations, path *structpath.PathNode) (diag.Location, bool) { currentNode := locations lastLocation := locations.location exists := locations.exists - for _, component := range path { - if key := component.Key(); key != "" { + for _, component := range path.AsSlice() { + if key, ok := component.StringKey(); ok { if _, ok := currentNode.keys[key]; !ok { break } currentNode = currentNode.keys[key] } else { - index := component.Index() + index, _ := component.Index() if _, ok := currentNode.indexes[index]; !ok { break } diff --git a/bundle/config/mutator/python/python_locations_test.go b/bundle/config/mutator/python/python_locations_test.go index 2bc815e7cdc..7db88971064 100644 --- a/bundle/config/mutator/python/python_locations_test.go +++ b/bundle/config/mutator/python/python_locations_test.go @@ -6,8 +6,7 @@ import ( "testing" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynassert" + "github.com/databricks/cli/libs/structs/structpath" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -18,42 +17,25 @@ func TestMergeLocations(t *testing.T) { yamlLocation := diag.Location{File: "foo.yml", Line: 1, Column: 1} locations := newPythonLocations() - putPythonLocation(locations, dyn.MustPathFromString("foo"), pythonLocation) - - input := dyn.NewValue( - map[string]dyn.Value{ - "foo": dyn.V( - map[string]dyn.Value{ - "baz": dyn.NewValue("baz", []diag.Location{yamlLocation}), - "qux": dyn.NewValue("baz", []diag.Location{generatedLocation, yamlLocation}), - }, - ), - "bar": dyn.NewValue("baz", []diag.Location{generatedLocation}), - }, - []diag.Location{yamlLocation}, - ) - - expected := dyn.NewValue( - map[string]dyn.Value{ - "foo": dyn.NewValue( - map[string]dyn.Value{ - // pythonLocation is appended to the beginning of the list if absent - "baz": dyn.NewValue("baz", []diag.Location{pythonLocation, yamlLocation}), - // generatedLocation is replaced by pythonLocation - "qux": dyn.NewValue("baz", []diag.Location{pythonLocation, yamlLocation}), - }, - []diag.Location{pythonLocation}, - ), - // if location is unknown, we keep it as-is - "bar": dyn.NewValue("baz", []diag.Location{generatedLocation}), - }, - []diag.Location{yamlLocation}, - ) - - actual, err := mergePythonLocations(input, locations) + putPythonLocation(locations, structpath.MustParsePath("foo"), pythonLocation) - assert.NoError(t, err) - dynassert.Equal(t, expected, actual) + mapper := mergePythonLocations(locations) + at := func(path string, locs ...diag.Location) []diag.Location { + var p *structpath.PathNode + if path != "" { + p = structpath.MustParsePath(path) + } + return mapper(p, locs) + } + + // pythonLocation is prepended if absent + assert.Equal(t, []diag.Location{pythonLocation, yamlLocation}, at("foo.baz", yamlLocation)) + // generatedLocation is replaced by pythonLocation + assert.Equal(t, []diag.Location{pythonLocation, yamlLocation}, at("foo.qux", generatedLocation, yamlLocation)) + assert.Equal(t, []diag.Location{pythonLocation}, at("foo")) + // if location is unknown, we keep it as-is + assert.Equal(t, []diag.Location{generatedLocation}, at("bar", generatedLocation)) + assert.Equal(t, []diag.Location{yamlLocation}, at("", yamlLocation)) } func TestFindLocation(t *testing.T) { @@ -61,10 +43,10 @@ func TestFindLocation(t *testing.T) { location1 := diag.Location{File: "foo.py", Line: 2, Column: 1} locations := newPythonLocations() - putPythonLocation(locations, dyn.MustPathFromString("foo"), location0) - putPythonLocation(locations, dyn.MustPathFromString("foo.bar"), location1) + putPythonLocation(locations, structpath.MustParsePath("foo"), location0) + putPythonLocation(locations, structpath.MustParsePath("foo.bar"), location1) - actual, exists := findPythonLocation(locations, dyn.MustPathFromString("foo.bar")) + actual, exists := findPythonLocation(locations, structpath.MustParsePath("foo.bar")) assert.True(t, exists) assert.Equal(t, location1, actual) @@ -76,11 +58,11 @@ func TestFindLocation_indexPathComponent(t *testing.T) { location2 := diag.Location{File: "foo.py", Line: 3, Column: 1} locations := newPythonLocations() - putPythonLocation(locations, dyn.MustPathFromString("foo"), location0) - putPythonLocation(locations, dyn.MustPathFromString("foo.bar"), location1) - putPythonLocation(locations, dyn.MustPathFromString("foo.bar[0]"), location2) + putPythonLocation(locations, structpath.MustParsePath("foo"), location0) + putPythonLocation(locations, structpath.MustParsePath("foo.bar"), location1) + putPythonLocation(locations, structpath.MustParsePath("foo.bar[0]"), location2) - actual, exists := findPythonLocation(locations, dyn.MustPathFromString("foo.bar[0]")) + actual, exists := findPythonLocation(locations, structpath.MustParsePath("foo.bar[0]")) assert.True(t, exists) assert.Equal(t, location2, actual) @@ -91,10 +73,10 @@ func TestFindLocation_closestAncestorLocation(t *testing.T) { location1 := diag.Location{File: "foo.py", Line: 2, Column: 1} locations := newPythonLocations() - putPythonLocation(locations, dyn.MustPathFromString("foo"), location0) - putPythonLocation(locations, dyn.MustPathFromString("foo.bar"), location1) + putPythonLocation(locations, structpath.MustParsePath("foo"), location0) + putPythonLocation(locations, structpath.MustParsePath("foo.bar"), location1) - actual, exists := findPythonLocation(locations, dyn.MustPathFromString("foo.bar.baz")) + actual, exists := findPythonLocation(locations, structpath.MustParsePath("foo.bar.baz")) assert.True(t, exists) assert.Equal(t, location1, actual) @@ -105,10 +87,10 @@ func TestFindLocation_unknownLocation(t *testing.T) { location1 := diag.Location{File: "foo.py", Line: 2, Column: 1} locations := newPythonLocations() - putPythonLocation(locations, dyn.MustPathFromString("foo"), location0) - putPythonLocation(locations, dyn.MustPathFromString("foo.bar"), location1) + putPythonLocation(locations, structpath.MustParsePath("foo"), location0) + putPythonLocation(locations, structpath.MustParsePath("foo.bar"), location1) - _, exists := findPythonLocation(locations, dyn.MustPathFromString("bar")) + _, exists := findPythonLocation(locations, structpath.MustParsePath("bar")) assert.False(t, exists) } @@ -137,21 +119,18 @@ func TestLoadOutput(t *testing.T) { locations := newPythonLocations() putPythonLocation( locations, - dyn.MustPathFromString("resources.jobs.my_job"), + structpath.MustParsePath("resources.jobs.my_job"), location, ) - value, diags := loadOutput( + root, diags := loadOutput( bundleRoot, bytes.NewReader([]byte(output)), locations, ) assert.Equal(t, diag.Diagnostics{}, diags) - - name, err := dyn.Get(value, "resources.jobs.my_job.name") - require.NoError(t, err) - require.Equal(t, []diag.Location{location}, name.Locations()) + require.Equal(t, []diag.Location{location}, root.GetLocationsOfExact(structpath.MustParsePath("resources.jobs.my_job.name"))) } func TestParsePythonLocations_absolutePath(t *testing.T) { diff --git a/bundle/config/mutator/python/python_mutator.go b/bundle/config/mutator/python/python_mutator.go index 5d6e8b0d801..4c433f4ff32 100644 --- a/bundle/config/mutator/python/python_mutator.go +++ b/bundle/config/mutator/python/python_mutator.go @@ -11,6 +11,7 @@ import ( "os" "path/filepath" "reflect" + "slices" "strings" "github.com/databricks/cli/bundle/config/mutator/resourcemutator" @@ -28,10 +29,9 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/yamlloader" "github.com/databricks/cli/libs/process" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) type phase string @@ -159,50 +159,33 @@ func getOpts(b *bundle.Bundle, phase phase) (opts, error) { // // Later version of Python package should reject 'experimental.python' unless 'python' section // is set to equivalent value, and after that reject 'experimental.python' altogether. -func applyBackwardsCompatibilityFixes(b *bundle.Bundle) error { - return b.Config.Mutate(func(value dyn.Value) (dyn.Value, error) { - outValue := value - - pythonValue, _ := dyn.Get(outValue, "python") - if !pythonValue.IsValid() { - // if 'python' section doesn't exist, nothing to do - return value, nil - } - - experimentalPythonValue, _ := dyn.Get(outValue, "experimental.python") - - if experimentalPythonValue.IsValid() { - // if 'experimental.python' section exists, nothing to do - return value, nil - } - - experimentalValue, _ := dyn.Get(outValue, "experimental") - if !experimentalValue.IsValid() { - updated, err := dyn.Set(outValue, "experimental", dyn.NewValue(map[string]dyn.Value{}, nil)) - if err != nil { - return dyn.InvalidValue, fmt.Errorf("failed to create 'experimental' section: %w", err) - } else { - outValue = updated - } - } +func applyBackwardsCompatibilityFixes(b *bundle.Bundle) { + // if 'python' section doesn't exist, nothing to do + if reflect.DeepEqual(b.Config.Python, config.Python{}) { + return + } - // move 'python' section to 'experimental.python' - updated, err := dyn.Set(outValue, "experimental.python", pythonValue) - if err != nil { - return dyn.InvalidValue, fmt.Errorf("failed to set 'experimental.python' section: %w", err) - } else { - outValue = updated - } + // if 'experimental.python' section exists, nothing to do + experimental := b.Config.Experimental + if experimental != nil && !reflect.DeepEqual(experimental.Python, config.Python{}) { + return + } - return outValue, nil - }) + // copy 'python' section to 'experimental.python' (creating 'experimental' if needed) + if experimental == nil { + experimental = &config.Experimental{} + b.Config.Experimental = experimental + } + experimental.Python = config.Python{ + Resources: slices.Clone(b.Config.Python.Resources), + Mutators: slices.Clone(b.Config.Python.Mutators), + VEnvPath: b.Config.Python.VEnvPath, + } + b.Config.CopyMetadataAt(structpath.NewStringKey(nil, "python"), structpath.NewPath(nil, "experimental", "python")) } func (m *pythonMutator) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - err := applyBackwardsCompatibilityFixes(b) - if err != nil { - return diag.FromErr(fmt.Errorf("failed to apply backwards compatibility fixes: %w", err)) - } + applyBackwardsCompatibilityFixes(b) opts, err := getOpts(b, m.phase) if err != nil { @@ -227,93 +210,82 @@ func (m *pythonMutator) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagno return diag.FromErr(err) } - // mutateDiags is used because Mutate returns 'error' instead of 'diag.Diagnostics' - var mutateDiags diag.Diagnostics - var result applyPythonOutputResult - mutateDiagsHasError := errors.New("unexpected error") - - err = b.Config.Mutate(func(leftRoot dyn.Value) (dyn.Value, error) { - pythonPath, err := detectExecutable(ctx, opts.venvPath) - if err != nil { - return dyn.InvalidValue, fmt.Errorf("failed to get Python interpreter path: %w", err) - } + result, diags := m.run(ctx, b, opts, authEnv) + // we can precisely track resources that are added/updated, so sum doesn't double-count + b.Metrics.PythonUpdatedResourcesCount += int64(result.UpdatedResources.Size()) + b.Metrics.PythonAddedResourcesCount += int64(result.AddedResources.Size()) - cacheDir, cleanup, err := createCacheDir(ctx) - if err != nil { - return dyn.InvalidValue, fmt.Errorf("failed to create cache dir: %w", err) - } - defer cleanup() - - rightRoot, diags := m.runPythonMutator(ctx, leftRoot, runPythonMutatorOpts{ - cacheDir: cacheDir, - bundleRootPath: b.BundleRootPath, - pythonPath: pythonPath, - loadLocations: opts.loadLocations, - authEnv: authEnv, - }) - mutateDiags = diags - if diags.HasError() { - return dyn.InvalidValue, mutateDiagsHasError - } + if diags.HasError() { + return diags + } - newRoot, result0, err := applyPythonOutput(leftRoot, rightRoot) - result = result0 - if err != nil { - return dyn.InvalidValue, fmt.Errorf("internal error when merging output of Python mutator: %w", err) - } + resourcemutator.NormalizeAndInitializeResources(ctx, b, result.AddedResources) + if logdiag.HasError(ctx) { + return diags + } - for _, resourceKey := range result.AddedResources.ToArray() { - log.Debugf(ctx, "added resource at 'resources.%s.%s'", resourceKey.Type, resourceKey.Name) - } + resourcemutator.NormalizeResources(ctx, b, result.UpdatedResources) + return diags +} - for _, resourceKey := range result.UpdatedResources.ToArray() { - log.Debugf(ctx, "updated resource at 'resources.%s.%s'", resourceKey.Type, resourceKey.Name) - } +// run runs the Python mutator and replaces the configuration with its output. +// The configuration is left unchanged if there are errors. +func (m *pythonMutator) run(ctx context.Context, b *bundle.Bundle, opts opts, authEnv map[string]string) (applyPythonOutputResult, diag.Diagnostics) { + var result applyPythonOutputResult - for _, resourceKey := range result.DeletedResources.ToArray() { - log.Debugf(ctx, "deleted resource at 'resources.%s.%s'", resourceKey.Type, resourceKey.Name) - } + pythonPath, err := detectExecutable(ctx, opts.venvPath) + if err != nil { + return result, diag.FromErr(fmt.Errorf("failed to get Python interpreter path: %w", err)) + } - if !result.DeletedResources.IsEmpty() { - return dyn.InvalidValue, fmt.Errorf("unexpected deleted resources: %s", result.DeletedResources.ToArray()) - } + cacheDir, cleanup, err := createCacheDir(ctx) + if err != nil { + return result, diag.FromErr(fmt.Errorf("failed to create cache dir: %w", err)) + } + defer cleanup() - if !result.AddedResources.IsEmpty() && m.phase == PythonMutatorPhaseApplyMutators { - return dyn.InvalidValue, fmt.Errorf("unexpected added resources: %s", result.AddedResources.ToArray()) - } + leftRoot := b.Config.View() + rightRoot, diags := m.runPythonMutator(ctx, leftRoot, runPythonMutatorOpts{ + cacheDir: cacheDir, + bundleRootPath: b.BundleRootPath, + pythonPath: pythonPath, + loadLocations: opts.loadLocations, + authEnv: authEnv, + }) + if diags.HasError() { + return result, diags + } - if !result.UpdatedResources.IsEmpty() && m.phase == PythonMutatorPhaseLoadResources { - return dyn.InvalidValue, fmt.Errorf("unexpected updated resources: %s", result.UpdatedResources.ToArray()) - } + plan, result, err := applyPythonOutput(&b.Config, rightRoot) + if err != nil { + return result, diags.Extend(diag.FromErr(fmt.Errorf("internal error when merging output of Python mutator: %w", err))) + } - return newRoot, nil - }) + for _, resourceKey := range result.AddedResources.ToArray() { + log.Debugf(ctx, "added resource at 'resources.%s.%s'", resourceKey.Type, resourceKey.Name) + } - // we can precisely track resources that are added/updated, so sum doesn't double-count - b.Metrics.PythonUpdatedResourcesCount += int64(result.UpdatedResources.Size()) - b.Metrics.PythonAddedResourcesCount += int64(result.AddedResources.Size()) + for _, resourceKey := range result.UpdatedResources.ToArray() { + log.Debugf(ctx, "updated resource at 'resources.%s.%s'", resourceKey.Type, resourceKey.Name) + } - if errors.Is(err, mutateDiagsHasError) { - if !mutateDiags.HasError() { - panic("mutateDiags has no error, but error is expected") - } + for _, resourceKey := range result.DeletedResources.ToArray() { + log.Debugf(ctx, "deleted resource at 'resources.%s.%s'", resourceKey.Type, resourceKey.Name) + } - return mutateDiags - } else { - mutateDiags = mutateDiags.Extend(diag.FromErr(err)) + if !result.DeletedResources.IsEmpty() { + return result, diags.Extend(diag.Errorf("unexpected deleted resources: %s", result.DeletedResources.ToArray())) } - if mutateDiags.HasError() { - return mutateDiags + if !result.AddedResources.IsEmpty() && m.phase == PythonMutatorPhaseApplyMutators { + return result, diags.Extend(diag.Errorf("unexpected added resources: %s", result.AddedResources.ToArray())) } - resourcemutator.NormalizeAndInitializeResources(ctx, b, result.AddedResources) - if logdiag.HasError(ctx) { - return mutateDiags + if !result.UpdatedResources.IsEmpty() && m.phase == PythonMutatorPhaseLoadResources { + return result, diags.Extend(diag.Errorf("unexpected updated resources: %s", result.UpdatedResources.ToArray())) } - resourcemutator.NormalizeResources(ctx, b, result.UpdatedResources) - return mutateDiags + return result, diags.Extend(diag.FromErr(b.Config.Override(plan))) } // createCacheDir returns the directory for input/output files of the Python subprocess, and a cleanup function. @@ -343,7 +315,7 @@ func createCacheDir(ctx context.Context) (string, func(), error) { return cacheDir, func() { _ = os.RemoveAll(cacheDir) }, nil } -func (m *pythonMutator) runPythonMutator(ctx context.Context, root dyn.Value, opts runPythonMutatorOpts) (dyn.Value, diag.Diagnostics) { +func (m *pythonMutator) runPythonMutator(ctx context.Context, root structvar.View, opts runPythonMutatorOpts) (*config.Root, diag.Diagnostics) { inputPath := filepath.Join(opts.cacheDir, "input.json") outputPath := filepath.Join(opts.cacheDir, "output.json") diagnosticsPath := filepath.Join(opts.cacheDir, "diagnostics.json") @@ -368,7 +340,7 @@ func (m *pythonMutator) runPythonMutator(ctx context.Context, root dyn.Value, op } if err := writeInputFile(inputPath, root); err != nil { - return dyn.InvalidValue, diag.Errorf("failed to write input file: %s", err) + return nil, diag.Errorf("failed to write input file: %s", err) } stderrBuf := bytes.Buffer{} @@ -398,7 +370,7 @@ func (m *pythonMutator) runPythonMutator(ctx context.Context, root dyn.Value, op // if diagnostics file exists, it gives the most descriptive errors // if there is any error, we treat it as fatal error, and stop processing if pythonDiagnostics.HasError() { - return dyn.InvalidValue, pythonDiagnostics + return nil, pythonDiagnostics } // process can fail without reporting errors in diagnostics file or creating it, for instance, @@ -410,21 +382,24 @@ func (m *pythonMutator) runPythonMutator(ctx context.Context, root dyn.Value, op Detail: explainProcessErr(ctx, stderrBuf.String()), } - return dyn.InvalidValue, diag.Diagnostics{diagnostic} + return nil, diag.Diagnostics{diagnostic} } // or we can fail to read diagnostics file, that should always be created if pythonDiagnosticsErr != nil { - return dyn.InvalidValue, diag.Errorf("failed to load diagnostics: %s", pythonDiagnosticsErr) + return nil, diag.Errorf("failed to load diagnostics: %s", pythonDiagnosticsErr) } locations, err := loadLocationsFile(opts.bundleRootPath, locationsPath) if err != nil { - return dyn.InvalidValue, diag.Errorf("failed to load locations: %s", err) + return nil, diag.Errorf("failed to load locations: %s", err) } output, outputDiags := loadOutputFile(opts.bundleRootPath, outputPath, locations) pythonDiagnostics = pythonDiagnostics.Extend(outputDiags) + if pythonDiagnostics.HasError() { + return nil, pythonDiagnostics + } // we pass through pythonDiagnostic because it contains warnings return output, pythonDiagnostics @@ -456,9 +431,9 @@ func explainProcessErr(ctx context.Context, stderr string) string { return stderr } -func writeInputFile(inputPath string, input dyn.Value) error { - // we need to marshal dyn.Value instead of bundle.Config to JSON to support - // non-string fields assigned with bundle variables +func writeInputFile(inputPath string, input structvar.View) error { + // we need to marshal the configuration tree instead of bundle.Config to JSON to + // support non-string fields assigned with bundle variables rootConfigJson, err := json.Marshal(input.AsAny()) if err != nil { return fmt.Errorf("failed to marshal input: %w", err) @@ -481,10 +456,10 @@ func loadLocationsFile(bundleRoot, locationsPath string) (*pythonLocations, erro return parsePythonLocations(bundleRoot, locationsFile) } -func loadOutputFile(rootPath, outputPath string, locations *pythonLocations) (dyn.Value, diag.Diagnostics) { +func loadOutputFile(rootPath, outputPath string, locations *pythonLocations) (*config.Root, diag.Diagnostics) { outputFile, err := os.Open(outputPath) if err != nil { - return dyn.InvalidValue, diag.FromErr(fmt.Errorf("failed to open output file: %w", err)) + return nil, diag.FromErr(fmt.Errorf("failed to open output file: %w", err)) } defer outputFile.Close() @@ -492,7 +467,7 @@ func loadOutputFile(rootPath, outputPath string, locations *pythonLocations) (dy return loadOutput(rootPath, outputFile, locations) } -func loadOutput(rootPath string, outputFile io.Reader, locations *pythonLocations) (dyn.Value, diag.Diagnostics) { +func loadOutput(rootPath string, outputFile io.Reader, locations *pythonLocations) (*config.Root, diag.Diagnostics) { // we need absolute path because later parts of pipeline assume all paths are absolute // and this file will be used as location to resolve relative paths. // @@ -503,41 +478,27 @@ func loadOutput(rootPath string, outputFile io.Reader, locations *pythonLocation // for that, we pass virtualPath instead of outputPath as file location virtualPath, err := filepath.Abs(filepath.Join(rootPath, generatedFileName)) if err != nil { - return dyn.InvalidValue, diag.FromErr(fmt.Errorf("failed to get absolute path: %w", err)) + return nil, diag.FromErr(fmt.Errorf("failed to get absolute path: %w", err)) } - generated, err := yamlloader.LoadYAML(virtualPath, outputFile) + // The output has locations as if it comes from the generated YAML file; earlier we + // loaded locations.json with source locations in Python code. + root, diags, err := config.LoadFromReader(virtualPath, outputFile, mergePythonLocations(locations)) if err != nil { - return dyn.InvalidValue, diag.FromErr(fmt.Errorf("failed to parse output file: %w", err)) + return nil, diag.FromErr(fmt.Errorf("failed to parse output file: %w", err)) } - // generated has diag.Location as if it comes from generated YAML file - // earlier we loaded locations.json with source locations in Python code - generatedWithLocations, err := mergePythonLocations(generated, locations) - if err != nil { - return dyn.InvalidValue, diag.FromErr(fmt.Errorf("failed to update locations: %w", err)) - } - - return strictNormalize(config.Root{}, generatedWithLocations) -} - -func strictNormalize(dst any, generated dyn.Value) (dyn.Value, diag.Diagnostics) { - normalized, diags := convert.Normalize(dst, generated) - // warnings shouldn't happen because output should be already normalized // when it happens, it's a bug in the mutator, and should be treated as an error - strictDiags := diag.Diagnostics{} - for _, d := range diags { if d.Severity == diag.Warning { d.Severity = diag.Error } - strictDiags = strictDiags.Append(d) } - return normalized, strictDiags + return root, strictDiags } // loadDiagnosticsFile loads diagnostics from a file. diff --git a/bundle/config/mutator/python/python_mutator_test.go b/bundle/config/mutator/python/python_mutator_test.go index f2c91749e87..48625e4693c 100644 --- a/bundle/config/mutator/python/python_mutator_test.go +++ b/bundle/config/mutator/python/python_mutator_test.go @@ -9,16 +9,15 @@ import ( "path/filepath" "runtime" "slices" + "strings" "testing" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn/convert" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/bundle/env" "github.com/stretchr/testify/require" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/internal/testutil" @@ -120,11 +119,11 @@ workspace: { current_user: { userName: test }}`) assert.Equal(t, "pipeline_0", pipeline0.Name) } - // output of locations.json should be applied to underlying dyn.Value - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { + // output of locations.json should be applied to underlying configuration + { // location is databricks.yml, because output contains resource as-is - jobName0, err := dyn.GetByPath(v, dyn.MustPathFromString("resources.jobs.job0.name")) - require.NoError(t, err) + jobName0 := b.Config.View().Lookup(structpath.MustParsePath("resources.jobs.job0.name")) + require.True(t, jobName0.IsValid()) assert.Equal(t, []diag.Location{ { File: "databricks.yml", @@ -133,8 +132,8 @@ workspace: { current_user: { userName: test }}`) }, }, jobName0.Locations()) - jobName1, err := dyn.GetByPath(v, dyn.MustPathFromString("resources.jobs.job1.name")) - require.NoError(t, err) + jobName1 := b.Config.View().Lookup(structpath.MustParsePath("resources.jobs.job1.name")) + require.True(t, jobName1.IsValid()) assert.Equal(t, []diag.Location{ { File: filepath.Join(rootPath, "src/examples/job1.py"), @@ -143,8 +142,8 @@ workspace: { current_user: { userName: test }}`) }, }, jobName1.Locations()) - pipelineName0, err := dyn.GetByPath(v, dyn.MustPathFromString("resources.pipelines.pipeline0.name")) - require.NoError(t, err) + pipelineName0 := b.Config.View().Lookup(structpath.MustParsePath("resources.pipelines.pipeline0.name")) + require.True(t, pipelineName0.IsValid()) assert.Equal(t, []diag.Location{ { File: filepath.Join(rootPath, "src/examples/pipeline0.py"), @@ -153,9 +152,7 @@ workspace: { current_user: { userName: test }}`) }, }, pipelineName0.Locations()) - return v, nil - }) - assert.NoError(t, err) + } assert.Equal(t, int64(2), b.Metrics.PythonAddedResourcesCount) assert.Equal(t, int64(0), b.Metrics.PythonUpdatedResourcesCount) @@ -219,24 +216,22 @@ resources: assert.Equal(t, "job_0", b.Config.Resources.Jobs["job0"].Name) assert.Equal(t, "my job", b.Config.Resources.Jobs["job0"].Description) - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { + { // 'name' wasn't changed, so it keeps its location - name, err := dyn.GetByPath(v, dyn.MustPathFromString("resources.jobs.job0.name")) - require.NoError(t, err) + name := b.Config.View().Lookup(structpath.MustParsePath("resources.jobs.job0.name")) + require.True(t, name.IsValid()) assert.Equal(t, "databricks.yml", name.Location().File) // 'description' was updated by Python code and has location of generated file until // we implement source maps - description, err := dyn.GetByPath(v, dyn.MustPathFromString("resources.jobs.job0.description")) - require.NoError(t, err) + description := b.Config.View().Lookup(structpath.MustParsePath("resources.jobs.job0.description")) + require.True(t, description.IsValid()) expectedVirtualPath, err := filepath.Abs(generatedFileName) require.NoError(t, err) assert.Equal(t, expectedVirtualPath, description.Location().File) - return v, nil - }) - assert.NoError(t, err) + } assert.Equal(t, int64(0), b.Metrics.PythonAddedResourcesCount) assert.Equal(t, int64(1), b.Metrics.PythonUpdatedResourcesCount) @@ -416,12 +411,27 @@ func TestApplyBackwardCompatibilityFixes(t *testing.T) { }, } - err := applyBackwardsCompatibilityFixes(b) + applyBackwardsCompatibilityFixes(b) - assert.NoError(t, err) assert.Equal(t, config.Python{VEnvPath: ".venv"}, b.Config.Experimental.Python) } +func TestApplyBackwardCompatibilityFixes_references(t *testing.T) { + b := &bundle.Bundle{ + Config: config.Root{ + Python: config.Python{ + VEnvPath: ".venv", + }, + }, + } + require.NoError(t, b.Config.SetReference(structpath.NewPath(nil, "python", "resources"), "${var.loaders}")) + + applyBackwardsCompatibilityFixes(b) + + assert.Equal(t, "${var.loaders}", b.Config.ReferenceAt(structpath.NewPath(nil, "experimental", "python", "resources"))) + assert.Equal(t, "${var.loaders}", b.Config.ReferenceAt(structpath.NewPath(nil, "python", "resources"))) +} + func TestApplyBackwardCompatibilityFixes_unchanged(t *testing.T) { b := &bundle.Bundle{ Config: config.Root{ @@ -436,9 +446,8 @@ func TestApplyBackwardCompatibilityFixes_unchanged(t *testing.T) { }, } - err := applyBackwardsCompatibilityFixes(b) + applyBackwardsCompatibilityFixes(b) - assert.NoError(t, err) assert.Equal(t, config.Python{VEnvPath: "should_remain_unchanged"}, b.Config.Experimental.Python) } @@ -457,20 +466,14 @@ func TestInterpreterPath(t *testing.T) { } } -func TestStrictNormalize(t *testing.T) { - // NB: there is no way to trigger diag.Error, so we don't test it - - type TestStruct struct { - A int `json:"a"` - } - - value := dyn.NewValue(map[string]dyn.Value{"A": dyn.NewValue("abc", nil)}, nil) +func TestLoadOutputStrict(t *testing.T) { + // Warnings when decoding the output are a bug in the Python code and are errors. + output := `{"resources": {"jobs": {"my_job": {"max_concurrent_runs": "abc"}}}}` - _, diags := convert.Normalize(TestStruct{}, value) - _, strictDiags := strictNormalize(TestStruct{}, value) + _, diags := loadOutput(t.TempDir(), strings.NewReader(output), newPythonLocations()) - assert.False(t, diags.HasError()) - assert.True(t, strictDiags.HasError()) + require.True(t, diags.HasError()) + assert.Equal(t, `cannot parse "abc" as an integer`, diags[0].Summary) } func TestCreateCacheDir(t *testing.T) { diff --git a/bundle/config/mutator/resolve_variable_references.go b/bundle/config/mutator/resolve_variable_references.go index 257309c55f8..aa8c1c765ec 100644 --- a/bundle/config/mutator/resolve_variable_references.go +++ b/bundle/config/mutator/resolve_variable_references.go @@ -1,21 +1,21 @@ package mutator import ( + "cmp" "context" "errors" "fmt" + "maps" "slices" "strings" - "github.com/databricks/cli/libs/dyn/merge" - "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/variable" + "github.com/databricks/cli/bundle/config/walk" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/dynvar" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) /* @@ -45,18 +45,31 @@ var defaultPrefixes = []string{ "variables", } -var artifactPath = dyn.MustPathFromString("artifacts") +var artifactPath = structpath.NewPath(nil, "artifacts") + +// resolveScope is the part of the configuration a [resolveVariableReferences] resolves. +type resolveScope int + +const ( + // scopeConfig is the whole configuration ("resources" only if includeResources). + scopeConfig resolveScope = iota + // scopeResources is "resources". + scopeResources + // scopeVariableLookups is "variables.*.lookup", each lookup resolved separately. + scopeVariableLookups +) type resolveVariableReferences struct { + // prefixes are top-level config keys. prefixes []string - pattern dyn.Pattern - lookupFn func(dyn.Value, dyn.Path, *bundle.Bundle) (dyn.Value, error) - allowPathFn func(dyn.Path) bool + scope resolveScope + lookupFn func(structvar.View, *structpath.PathNode, *bundle.Bundle) (structvar.View, error) + allowPathFn func(*structpath.PathNode) bool extraRounds int // includeResources allows resolving variables in 'resources', otherwise, they are excluded. // - // includeResources can be used with appropriate pattern to avoid resolving variables + // includeResources can be used with scopeResources to avoid resolving variables // outside of 'resources'. includeResources bool @@ -76,7 +89,7 @@ func ResolveVariableReferencesOnlyResources(prefixes ...string) bundle.Mutator { prefixes: prefixes, lookupFn: lookup, extraRounds: maxResolutionRounds - 1, - pattern: dyn.NewPattern(dyn.Key("resources")), + scope: scopeResources, includeResources: true, } } @@ -95,7 +108,7 @@ func ResolveVariableReferencesWithoutResources(prefixes ...string) bundle.Mutato func ResolveVariableReferencesInLookup() bundle.Mutator { return &resolveVariableReferences{ prefixes: defaultPrefixes, - pattern: dyn.NewPattern(dyn.Key("variables"), dyn.AnyKey(), dyn.Key("lookup")), + scope: scopeVariableLookups, lookupFn: lookupForVariables, extraRounds: maxResolutionRounds - 1, } @@ -112,36 +125,44 @@ func ResolveVolumePathReferencesOnlyResources() bundle.Mutator { } } -func lookup(v dyn.Value, path dyn.Path, b *bundle.Bundle) (dyn.Value, error) { +func lookup(v structvar.View, path *structpath.PathNode, b *bundle.Bundle) (structvar.View, error) { if config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) { if path.String() == "workspace.file_path" { - return dyn.V(b.SyncRootPath), nil + return structvar.NewView(&b.SyncRootPath, nil, nil), nil } } // Future opportunity: if we lookup this path in both the given root // and the synthesized root, we know if it was explicitly set or implied to be empty. // Then we can emit a warning if it was not explicitly set. - return dyn.GetByPath(v, path) + return lookupValue(v, path) } -func lookupForVariables(v dyn.Value, path dyn.Path, b *bundle.Bundle) (dyn.Value, error) { - if path[0].Key() != "variables" { - return lookup(v, path, b) - } +// lookupValue returns the value at path. Fields that are declared in the type but not +// set resolve to their zero value. This enables users to interpolate variable references +// to fields that haven't been set, e.g. ${bundle.git.origin_url} resolves to an empty +// string if a bundle isn't located in a Git repository (yet). +func lookupValue(x structvar.View, path *structpath.PathNode) (structvar.View, error) { + return x.LookupWithDefaults(path) +} - varV, err := dyn.GetByPath(v, path[:len(path)-1]) - if err != nil { - return dyn.InvalidValue, err +func lookupForVariables(v structvar.View, path *structpath.PathNode, b *bundle.Bundle) (structvar.View, error) { + if path.KeyAt(0) != "variables" { + return lookup(v, path, b) } - var vv variable.Variable - err = convert.ToTyped(&vv, varV) + varV, err := lookupValue(v, path.Parent()) if err != nil { - return dyn.InvalidValue, err + return structvar.View{}, err } - if vv.Lookup != nil && vv.Lookup.String() != "" { - return dyn.InvalidValue, errors.New("lookup variables cannot contain references to another lookup variables") + if lookupV := varV.Get("lookup"); lookupV.IsValid() { + var vl variable.Lookup + if _, err := (&structvar.StructVar{Value: &vl}).Assign(nil, lookupV); err != nil { + return structvar.View{}, err + } + if vl.String() != "" { + return structvar.View{}, errors.New("lookup variables cannot contain references to another lookup variables") + } } return lookup(v, path, b) @@ -160,14 +181,23 @@ func (m *resolveVariableReferences) Validate(ctx context.Context, b *bundle.Bund } func (m *resolveVariableReferences) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - prefixes := make([]dyn.Path, len(m.prefixes)) + prefixes := make([]*structpath.PathNode, len(m.prefixes)) for i, prefix := range m.prefixes { - prefixes[i] = dyn.MustPathFromString(prefix) + prefixes[i] = structpath.NewPath(nil, prefix) } // The path ${var.foo} is a shorthand for ${variables.foo.value}. // We rewrite it here to make the resolution logic simpler. - varPath := dyn.NewPath(dyn.Key("var")) + varPath := structpath.NewPath(nil, "var") + + // Resolution converts the whole configuration on every round; skip it if no + // reference would be resolved. + if !m.hasReferencesToResolve(b, prefixes, varPath) { + if m.artifactsReferenceUsed { + b.Metrics.SetBoolValue("artifacts_reference_used", true) + } + return nil + } var diags diag.Diagnostics maxRounds := 1 + m.extraRounds @@ -185,6 +215,11 @@ func (m *resolveVariableReferences) Apply(ctx context.Context, b *bundle.Bundle) break } + // Another round would only find out that nothing is left to resolve. + if !m.hasReferencesToResolve(b, prefixes, varPath) { + break + } + if round >= maxRounds-1 { diags = diags.Append(diag.Diagnostic{ Severity: diag.Warning, @@ -202,86 +237,242 @@ func (m *resolveVariableReferences) Apply(ctx context.Context, b *bundle.Bundle) return diags } -func (m *resolveVariableReferences) resolveOnce(b *bundle.Bundle, prefixes []dyn.Path, varPath dyn.Path) (bool, diag.Diagnostics) { +// hasReferencesToResolve reports whether the configuration in scope of this mutator +// has a reference with one of the prefixes. Like resolution itself, it records whether +// "artifacts" is referenced. +func (m *resolveVariableReferences) hasReferencesToResolve(b *bundle.Bundle, prefixes []*structpath.PathNode, varPath *structpath.PathNode) bool { + found := false + m.walkStrings(&b.Config, func(_ *walk.Node, s string) (string, bool) { + // Every string is checked: each can record the "artifacts" reference. + found = m.referencesPrefix(s, prefixes, varPath) || found + return "", false + }) + for path, ref := range b.Config.References() { + if m.inScope(path) { + found = m.referencesPrefix(ref, prefixes, varPath) || found + } + } + return found +} + +// referencesPrefix reports whether s has a reference with one of the prefixes (or one +// that does not parse, to let resolution report it), and records an "artifacts" reference. +func (m *resolveVariableReferences) referencesPrefix(s string, prefixes []*structpath.PathNode, varPath *structpath.PathNode) bool { + ref, ok := structvar.NewRef(s) + if !ok { + return false + } + found := false + for _, r := range ref.References() { + path, err := structpath.ParsePath(r) + if err != nil { + return true + } + if path.HasPrefix(varPath) { + path = structpath.Join(structpath.NewStringKey(nil, "variables"), path.SkipPrefix(1).AsSlice()...) + } + if path.HasPrefix(artifactPath) { + m.artifactsReferenceUsed = true + } + if slices.ContainsFunc(prefixes, path.HasPrefix) { + found = true + } + } + return found +} + +// walkStrings calls fn for the strings with references in the scope of this mutator. +func (m *resolveVariableReferences) walkStrings(r *config.Root, fn walk.StringFunc) { + switch m.scope { + case scopeConfig: + if m.includeResources { + walk.Strings(r, walk.WithReferences, fn) + } else { + walk.StringsWithoutResources(r, walk.WithReferences, fn) + } + case scopeResources: + walk.ResourceStrings(r, walk.WithReferences, fn) + case scopeVariableLookups: + walk.VariableLookupStrings(r, walk.WithReferences, fn) + } +} + +// inScope reports whether path is in the part of the configuration this mutator resolves. +func (m *resolveVariableReferences) inScope(path *structpath.PathNode) bool { + switch m.scope { + case scopeResources: + return path.KeyAt(0) == "resources" + case scopeVariableLookups: + return path.KeyAt(0) == "variables" && path.KeyAt(2) == "lookup" + default: + return m.includeResources || path.KeyAt(0) != "resources" + } +} + +func (m *resolveVariableReferences) resolveOnce(b *bundle.Bundle, prefixes []*structpath.PathNode, varPath *structpath.PathNode) (bool, diag.Diagnostics) { var diags diag.Diagnostics hasUpdates := false - err := m.selectivelyMutate(b, func(root dyn.Value) (dyn.Value, error) { - // Synthesize a copy of the root that has all fields that are present in the type - // but not set in the dynamic value set to their corresponding empty value. - // This enables users to interpolate variable references to fields that haven't - // been explicitly set in the dynamic value. - // - // For example: ${bundle.git.origin_url} should resolve to an empty string - // if a bundle isn't located in a Git repository (yet). - // - // This is consistent with the behavior prior to using the dynamic value system. - // - // We can ignore the diagnostics return value because we know that the dynamic value - // has already been normalized when it was first loaded from the configuration file. - // - normalized, _ := convert.Normalize(b.Config, root, convert.IncludeMissingFields) - - // If the pattern is nil, we resolve references in the entire configuration. - root, err := dyn.MapByPattern(root, m.pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - // Resolve variable references in all values. - return dynvar.Resolve(v, func(path dyn.Path) (dyn.Value, error) { - // Rewrite the shorthand path ${var.foo} into ${variables.foo.value}. - if path.HasPrefix(varPath) { - newPath := dyn.NewPath( - dyn.Key("variables"), - path[1], - dyn.Key("value"), - ) - - if len(path) > 2 { - newPath = newPath.Append(path[2:]...) - } - - path = newPath - } - - // If the path starts with "artifacts", we need to add a metric to track if this reference is used. - if path.HasPrefix(artifactPath) { - m.artifactsReferenceUsed = true - } - - // Perform resolution only if the path starts with one of the specified prefixes. - if slices.ContainsFunc(prefixes, path.HasPrefix) { - if slices.Contains(m.excludePaths, path.String()) { - return dyn.InvalidValue, dynvar.ErrSkipResolution - } - if m.allowPathFn != nil && !m.allowPathFn(path) { - return dyn.InvalidValue, dynvar.ErrSkipResolution - } - value, err := m.lookupFn(normalized, path, b) - hasUpdates = hasUpdates || (err == nil && value.IsValid()) - return value, err - } - - return dyn.InvalidValue, dynvar.ErrSkipResolution - }) - }) - if err != nil { - return dyn.InvalidValue, err + root := b.Config.View() + + lookupFn := func(sp *structpath.PathNode) (structvar.View, error) { + path := sp + // Rewrite the shorthand path ${var.foo} into ${variables.foo.value}. + if path.HasPrefix(varPath) { + path = structpath.Join(structpath.NewPath(nil, "variables", path.KeyAt(1), "value"), path.SkipPrefix(2).AsSlice()...) } - // Normalize the result because variable resolution may have been applied to non-string fields. - // For example, a variable reference may have been resolved to a integer. - root, normaliseDiags := convert.Normalize(b.Config, root) - diags = diags.Extend(normaliseDiags) - return root, nil - }) + // If the path starts with "artifacts", we need to add a metric to track if this reference is used. + if path.HasPrefix(artifactPath) { + m.artifactsReferenceUsed = true + } + + // Perform resolution only if the path starts with one of the specified prefixes. + if slices.ContainsFunc(prefixes, path.HasPrefix) { + if slices.Contains(m.excludePaths, path.String()) { + return structvar.View{}, structvar.ErrSkipResolution + } + if m.allowPathFn != nil && !m.allowPathFn(path) { + return structvar.View{}, structvar.ErrSkipResolution + } + value, err := m.lookupFn(root, path, b) + hasUpdates = hasUpdates || (err == nil && value.IsValid()) + return value, err + } + + return structvar.View{}, structvar.ErrSkipResolution + } + + // Resolve the references in each value of the scope: the whole configuration, + // "resources", or each variable lookup. Template keys are relative to that value, like + // the paths in cycle errors. The results are applied after all of them are resolved. + var updates []resolvedValue + var err error + for _, refs := range m.referenceStrings(&b.Config) { + if updates, err = resolveReferenceStrings(updates, refs, lookupFn); err != nil { + break + } + } if err != nil { - diags = diags.Extend(resolveErrorDiags(err)) + return hasUpdates, diags.Extend(resolveErrorDiags(err)) + } + + // Store the results in the typed configuration, converting them to the type of the + // field (e.g. a variable reference resolved to an integer). + // A value that cannot be converted is dropped, which moves the elements after it in + // a sequence: apply the updates from the last element to the first. + slices.SortStableFunc(updates, func(a, b resolvedValue) int { return -comparePaths(a.path, b.path) }) + var updateDiags []diag.Diagnostics + for _, u := range updates { + d, err := b.Config.Decode(u.path, u.value) + updateDiags = append(updateDiags, d.Extend(diag.FromErr(err))) + } + for _, d := range slices.Backward(updateDiags) { + diags = diags.Extend(d) } return hasUpdates, diags } +// comparePaths orders paths by their components; indices are compared as numbers. +func comparePaths(a, b *structpath.PathNode) int { + as, bs := a.AsSlice(), b.AsSlice() + for i := 0; i < len(as) && i < len(bs); i++ { + ai, aok := as[i].Index() + bi, bok := bs[i].Index() + if aok && bok { + if c := cmp.Compare(ai, bi); c != 0 { + return c + } + continue + } + if c := strings.Compare(as[i].String(), bs[i].String()); c != 0 { + return c + } + } + return cmp.Compare(len(as), len(bs)) +} + +// resolvedValue is the value a reference string at path resolved to. +type resolvedValue struct { + path *structpath.PathNode + value structvar.View +} + +// resolveReferenceStrings resolves the reference strings of one base together and +// appends the results to out, in path order. Template keys are relative to the base. +func resolveReferenceStrings(out []resolvedValue, refs []referenceString, lookup structvar.Lookup) ([]resolvedValue, error) { + templates := map[string]structvar.Template{} + paths := map[string]*structpath.PathNode{} + for _, ref := range refs { + key := ref.path.SkipPrefix(ref.base.Len()).String() + templates[key] = structvar.Template{Value: ref.value, Locations: ref.locs} + paths[key] = ref.path + } + resolved, err := structvar.Resolve(templates, lookup) + if err != nil { + return out, err + } + for _, key := range slices.Sorted(maps.Keys(resolved)) { + out = append(out, resolvedValue{path: paths[key], value: resolved[key]}) + } + return out, nil +} + +// referenceString is a string with a variable reference at path. Strings with the same +// base (the scope value they are in) are resolved together. +type referenceString struct { + base *structpath.PathNode + path *structpath.PathNode + value string + locs []diag.Location +} + +// referenceStrings returns the strings with variable references in the scope of this +// mutator, grouped by base in the order of the bases. Pure references in fields that +// cannot hold a string are included as their reference strings. +func (m *resolveVariableReferences) referenceStrings(r *config.Root) [][]referenceString { + var out []referenceString + m.walkStrings(r, func(n *walk.Node, s string) (string, bool) { + if _, ok := structvar.NewRef(s); ok { + out = append(out, referenceString{path: n.Path(), value: s, locs: n.Locations().Get()}) + } + return "", false + }) + for path, ref := range r.References() { + if m.inScope(path) { + out = append(out, referenceString{path: path, value: ref, locs: r.GetLocationsOfExact(path)}) + } + } + + groups := map[string][]referenceString{} + for _, ref := range out { + ref.base = m.base(ref.path) + key := ref.base.String() + groups[key] = append(groups[key], ref) + } + result := make([][]referenceString, 0, len(groups)) + for _, key := range slices.Sorted(maps.Keys(groups)) { + result = append(result, groups[key]) + } + return result +} + +// base returns the scope value path is in. +func (m *resolveVariableReferences) base(path *structpath.PathNode) *structpath.PathNode { + switch m.scope { + case scopeResources: + return path.Prefix(1) + case scopeVariableLookups: + return path.Prefix(3) + default: + return nil + } +} + // resolveErrorDiags renders "did you mean" suggestions as a diagnostic Detail so // libs/diag owns the multi-line formatting. func resolveErrorDiags(err error) diag.Diagnostics { - refErr, ok := errors.AsType[*dynvar.ReferenceError](err) + refErr, ok := errors.AsType[*structvar.ReferenceError](err) if !ok || len(refErr.Suggestions) == 0 { return diag.FromErr(err) } @@ -303,66 +494,11 @@ func resolveErrorDiags(err error) diag.Diagnostics { }} } -// selectivelyMutate applies a function to a subset of the configuration -func (m *resolveVariableReferences) selectivelyMutate(b *bundle.Bundle, fn func(value dyn.Value) (dyn.Value, error)) error { - return b.Config.Mutate(func(root dyn.Value) (dyn.Value, error) { - allKeys, err := getAllKeys(root) - if err != nil { - return dyn.InvalidValue, err - } - - var included []string - for _, key := range allKeys { - if key == "resources" { - if m.includeResources { - included = append(included, key) - } - } else { - included = append(included, key) - } - } - - includedRoot, err := merge.Select(root, included) - if err != nil { - return dyn.InvalidValue, err - } - - excludedRoot, err := merge.AntiSelect(root, included) - if err != nil { - return dyn.InvalidValue, err - } - - updatedRoot, err := fn(includedRoot) - if err != nil { - return dyn.InvalidValue, err - } - - // merge is recursive, but it doesn't matter because keys are mutually exclusive - return merge.Merge(updatedRoot, excludedRoot) - }) -} - -func getAllKeys(root dyn.Value) ([]string, error) { - var keys []string - - if mapping, ok := root.AsMap(); ok { - for _, key := range mapping.Keys() { - if keyString, ok := key.AsString(); ok { - keys = append(keys, keyString) - } else { - return nil, fmt.Errorf("key is not a string: %v", key) - } - } - } - - return keys, nil -} - -func isVolumePathReferencePath(path dyn.Path) bool { - if len(path) != 4 { +func isVolumePathReferencePath(path *structpath.PathNode) bool { + if path.Len() != 4 { return false } - return path[0].Key() == "resources" && - path[1].Key() == "volumes" && - path[3].Key() == "volume_path" + return path.KeyAt(0) == "resources" && + path.KeyAt(1) == "volumes" && + path.KeyAt(3) == "volume_path" } diff --git a/bundle/config/mutator/resolve_variable_references_test.go b/bundle/config/mutator/resolve_variable_references_test.go index 284b78846b4..4be7f012af2 100644 --- a/bundle/config/mutator/resolve_variable_references_test.go +++ b/bundle/config/mutator/resolve_variable_references_test.go @@ -6,7 +6,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/pipelines" "github.com/stretchr/testify/require" ) @@ -106,7 +106,7 @@ func TestResolveVolumePathReferencesOnlyResources_MissingTarget(t *testing.T) { } func TestIsVolumePathReferencePath(t *testing.T) { - require.True(t, isVolumePathReferencePath(dyn.MustPathFromString("resources.volumes.foo.volume_path"))) - require.False(t, isVolumePathReferencePath(dyn.MustPathFromString("resources.volumes.foo.name"))) - require.False(t, isVolumePathReferencePath(dyn.MustPathFromString("resources.jobs.foo.name"))) + require.True(t, isVolumePathReferencePath(structpath.MustParsePath("resources.volumes.foo.volume_path"))) + require.False(t, isVolumePathReferencePath(structpath.MustParsePath("resources.volumes.foo.name"))) + require.False(t, isVolumePathReferencePath(structpath.MustParsePath("resources.jobs.foo.name"))) } diff --git a/bundle/config/mutator/resourcemutator/.gitattributes b/bundle/config/mutator/resourcemutator/.gitattributes new file mode 100644 index 00000000000..a7515927b7c --- /dev/null +++ b/bundle/config/mutator/resourcemutator/.gitattributes @@ -0,0 +1 @@ +defaults_walk.go linguist-generated=true diff --git a/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go b/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go index 610832c2b92..3d4e5ff3039 100644 --- a/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go +++ b/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go @@ -11,7 +11,6 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -262,8 +261,5 @@ func TestApplyBundlePermissionsEmpty(t *testing.T) { // An empty list of permissions is dropped, an unset one stays unset. for _, name := range []string{"empty", "unset"} { require.Nil(t, b.Config.Resources.Jobs[name].Permissions) - v, err := dyn.GetByPath(b.Config.Value(), dyn.MustPathFromString("resources.jobs."+name)) - require.NoError(t, err) - assert.False(t, v.Get("permissions").IsValid(), name) } } diff --git a/bundle/config/mutator/resourcemutator/apply_default_task_source.go b/bundle/config/mutator/resourcemutator/apply_default_task_source.go index 30e000b1fb1..7789af0155d 100644 --- a/bundle/config/mutator/resourcemutator/apply_default_task_source.go +++ b/bundle/config/mutator/resourcemutator/apply_default_task_source.go @@ -2,10 +2,11 @@ package resourcemutator import ( "context" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/jobs" ) @@ -37,57 +38,61 @@ func (a *applyDefaultTaskSource) Name() string { return "ApplyDefaultTaskSource" } -// sourceAwareTaskTypes are the task types that support the `source` field. -// https://docs.databricks.com/api/workspace/jobs/create -var sourceAwareTaskTypes = []string{ - "dbt_task", - "gen_ai_compute_task", - "notebook_task", - "spark_python_task", - "sql_task.file", -} - func (a *applyDefaultTaskSource) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - jobsPattern := dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey()) + for name, job := range b.Config.Resources.Jobs { + // Only git_source jobs need an explicit source; leave the rest untouched. + // A missing key or an explicit `git_source: null` both count as absent, + // matching the typed nil pointer TranslatePaths keys off; otherwise we would + // set source: GIT on a job whose paths get translated to workspace paths. + // A reference is not absent, although the typed field is nil. + if job == nil || (job.GitSource == nil && !b.Config.IsReferenceAt(structpath.NewPath(nil, "resources", "jobs", name, "git_source"))) { + continue + } - err := b.Config.Mutate(func(root dyn.Value) (dyn.Value, error) { - return dyn.MapByPattern(root, jobsPattern, func(_ dyn.Path, job dyn.Value) (dyn.Value, error) { - // Only git_source jobs need an explicit source; leave the rest untouched. - // A missing key (KindInvalid) or an explicit `git_source: null` (KindNil) - // both count as absent, matching the typed nil pointer TranslatePaths keys - // off; otherwise we would set source: GIT on a job whose paths get - // translated to workspace paths. - gitSource := job.Get("git_source") - if gitSource.Kind() == dyn.KindInvalid || gitSource.Kind() == dyn.KindNil { - return job, nil + for i := range job.Tasks { + task := &job.Tasks[i] + if task.ForEachTask != nil { + setGitTaskSource(&task.ForEachTask.Task) } - - return dyn.Map(job, "tasks", dyn.Foreach(func(_ dyn.Path, task dyn.Value) (dyn.Value, error) { - task, err := dyn.Map(task, "for_each_task.task", func(_ dyn.Path, foreachTask dyn.Value) (dyn.Value, error) { - return setGitTaskSource(foreachTask) - }) - if err != nil { - return dyn.InvalidValue, err - } - return setGitTaskSource(task) - })) - }) - }) - - return diag.FromErr(err) + setGitTaskSource(task) + } + } + return nil } // setGitTaskSource sets source: GIT on the first task-type block present that // supports the field, unless the user already set it. -func setGitTaskSource(task dyn.Value) (dyn.Value, error) { - for _, taskType := range sourceAwareTaskTypes { - t, err := dyn.Get(task, taskType) - if err != nil { +// The task types are the ones that support the `source` field: +// https://docs.databricks.com/api/workspace/jobs/create +func setGitTaskSource(task *jobs.Task) { + type taskSource struct { + source *jobs.Source + // fsf records an explicit `source: ""`. spark_python_task and sql_task.file + // have no ForceSendFields, so an explicit empty source there reads as unset + // and gets the GIT default. + fsf []string + } + var sources []taskSource + if t := task.DbtTask; t != nil { + sources = append(sources, taskSource{&t.Source, t.ForceSendFields}) + } + if t := task.GenAiComputeTask; t != nil { + sources = append(sources, taskSource{&t.Source, t.ForceSendFields}) + } + if t := task.NotebookTask; t != nil { + sources = append(sources, taskSource{&t.Source, t.ForceSendFields}) + } + if t := task.SparkPythonTask; t != nil { + sources = append(sources, taskSource{&t.Source, nil}) + } + if task.SqlTask != nil && task.SqlTask.File != nil { + sources = append(sources, taskSource{&task.SqlTask.File.Source, nil}) + } + for _, s := range sources { + if *s.source != "" || slices.Contains(s.fsf, "Source") { continue } - if _, err := dyn.Get(t, "source"); err != nil { - return dyn.Set(task, taskType+".source", dyn.V(string(jobs.SourceGit))) - } + *s.source = jobs.SourceGit + return } - return task, nil } diff --git a/bundle/config/mutator/resourcemutator/capture_uc_dependencies.go b/bundle/config/mutator/resourcemutator/capture_uc_dependencies.go index a0e8d05fbde..4ca98d19bed 100644 --- a/bundle/config/mutator/resourcemutator/capture_uc_dependencies.go +++ b/bundle/config/mutator/resourcemutator/capture_uc_dependencies.go @@ -8,7 +8,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn/dynvar" + "github.com/databricks/cli/libs/structs/structvar" ) type captureUCDependencies struct{} @@ -159,7 +159,7 @@ func (m *captureUCDependencies) Apply(ctx context.Context, b *bundle.Bundle) dia } // A name that already contains a reference is left as is: we only rewrite a // fully literal name and do not support a mix of references and literals. - if dynvar.ContainsVariableReference(qm.OutputSchemaName) { + if structvar.ContainsVariableReference(qm.OutputSchemaName) { continue } // OutputSchemaName is a compound "catalog.schema" string. @@ -179,7 +179,7 @@ func (m *captureUCDependencies) Apply(ctx context.Context, b *bundle.Bundle) dia } // A name that already contains a reference is left as is; a mix of // references and literals is not supported. - if dynvar.ContainsVariableReference(idx.Name) { + if structvar.ContainsVariableReference(idx.Name) { continue } // Name is a three-part "catalog.schema.index" UC identifier. @@ -194,7 +194,7 @@ func (m *captureUCDependencies) Apply(ctx context.Context, b *bundle.Bundle) dia if f == nil { continue } - if dynvar.ContainsVariableReference(f.FullName) { + if structvar.ContainsVariableReference(f.FullName) { continue } parts, ok := splitUCName(f.FullName, 3) @@ -226,7 +226,7 @@ func (m *captureUCDependencies) Apply(ctx context.Context, b *bundle.Bundle) dia } // A parent that already contains a reference is left as is; a mix of // references and literals is not supported. - if dynvar.ContainsVariableReference(ms.Parent) { + if structvar.ContainsVariableReference(ms.Parent) { continue } ms.Parent = resolveAiGatewayParent(b, ms.Parent) diff --git a/bundle/config/mutator/resourcemutator/configure_cluster_policy_definition.go b/bundle/config/mutator/resourcemutator/configure_cluster_policy_definition.go index 1e7a0d6f64b..8a6ae9fb7e4 100644 --- a/bundle/config/mutator/resourcemutator/configure_cluster_policy_definition.go +++ b/bundle/config/mutator/resourcemutator/configure_cluster_policy_definition.go @@ -2,17 +2,15 @@ package resourcemutator import ( "context" - "encoding/json" "fmt" + "maps" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) -// jsonPolicyFields are the JSON-policy fields normalized from inline YAML to a JSON string. -var jsonPolicyFields = []string{"definition", "policy_family_definition_overrides"} - type configureClusterPolicyDefinition struct{} func ConfigureClusterPolicyDefinition() bundle.Mutator { @@ -26,46 +24,47 @@ func (c configureClusterPolicyDefinition) Name() string { func (c configureClusterPolicyDefinition) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { var diags diag.Diagnostics - pattern := dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("cluster_policies"), - dyn.AnyKey(), - ) - - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.MapByPattern(v, pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - for _, field := range jsonPolicyFields { - def := v.Get(field) - - // Marshal an inline structured value to a JSON string so both - // config-side and state-side carry the same plain string. Otherwise - // YAML decodes small ints as Go `int` while state JSON round-trip - // decodes them as `float64`, and structdiff reports false drift. - switch def.Kind() { - case dyn.KindInvalid, dyn.KindNil, dyn.KindString: - // KindInvalid means the field is absent; leave it for backend validation. - continue - case dyn.KindMap: - jsonBytes, err := json.Marshal(def.AsAny()) - if err != nil { - return dyn.InvalidValue, fmt.Errorf("failed to marshal inline %s: %w", field, err) - } - v, err = dyn.Set(v, field, dyn.V(string(jsonBytes))) - if err != nil { - return dyn.InvalidValue, err - } - default: - diags = diags.Append(diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("%s must be a string or map, got %s", field, def.Kind()), - Locations: def.Locations(), - }) - } - } - return v, nil - }) - }) + policies := b.Config.Resources.ClusterPolicies + for _, name := range slices.Sorted(maps.Keys(policies)) { + policy := policies[name] + if policy == nil { + continue + } + d, err := inlinePolicyField(b, name, "definition", policy.Definition) + diags = diags.Extend(d) + if err != nil { + return diags.Extend(diag.FromErr(err)) + } + d, err = inlinePolicyField(b, name, "policy_family_definition_overrides", policy.PolicyFamilyDefinitionOverrides) + diags = diags.Extend(d) + if err != nil { + return diags.Extend(diag.FromErr(err)) + } + } - diags = diags.Extend(diag.FromErr(err)) return diags } + +// inlinePolicyField marshals the inline structured value of the field key of the +// cluster policy name to a JSON string, so both config-side and state-side carry the +// same plain string. Otherwise YAML decodes small ints as Go `int` while state JSON +// round-trip decodes them as `float64`, and structdiff reports false drift. An absent +// value is left for backend validation. +func inlinePolicyField(b *bundle.Bundle, name, key string, value any) (diag.Diagnostics, error) { + fieldPath := structpath.NewPath(nil, "resources", "cluster_policies", name, key) + s, ok, kind, err := inlineJSON(value) + if err != nil { + return nil, fmt.Errorf("failed to marshal inline %s: %w", key, err) + } + if kind != "" { + return diag.Diagnostics{{ + Severity: diag.Error, + Summary: fmt.Sprintf("%s must be a string or map, got %s", key, kind), + Locations: b.Config.GetLocationsOfExact(fieldPath), + }}, nil + } + if !ok { + return nil, nil + } + return nil, b.Config.Set(fieldPath, s) +} diff --git a/bundle/config/mutator/resourcemutator/configure_dashboards_serialized_dashboard.go b/bundle/config/mutator/resourcemutator/configure_dashboards_serialized_dashboard.go index a17e5c6b07c..133b7ef6b50 100644 --- a/bundle/config/mutator/resourcemutator/configure_dashboards_serialized_dashboard.go +++ b/bundle/config/mutator/resourcemutator/configure_dashboards_serialized_dashboard.go @@ -2,12 +2,13 @@ package resourcemutator import ( "context" - "encoding/json" "fmt" + "maps" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) const ( @@ -28,64 +29,60 @@ func (c configureDashboardSerializedDashboard) Name() string { func (c configureDashboardSerializedDashboard) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { var diags diag.Diagnostics - pattern := dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("dashboards"), - dyn.AnyKey(), - ) + dashboards := b.Config.Resources.Dashboards + for _, name := range slices.Sorted(maps.Keys(dashboards)) { + d := dashboards[name] + if d == nil { + continue + } + sdPath := structpath.NewPath(nil, "resources", "dashboards", name, serializedDashboardFieldName) - // Configure serialized_dashboard field for all dashboards. - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.MapByPattern(v, pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - // Include "serialized_dashboard" field if "file_path" is set. - // Note: the Terraform resource supports "file_path" natively, but we read the contents of the dashboard here - // to be able to read file contents in Databricks Workspace (reading a dashboard file via file system fails there) - filePath, hasFilePath := v.Get(filePathFieldName).AsString() - sd := v.Get(serializedDashboardFieldName) - - if hasFilePath { - // file_path and serialized_dashboard are two ways to provide the - // same content. Accepting both is ambiguous, so reject it instead - // of silently picking one. - if sd.IsValid() && sd.Kind() != dyn.KindNil { - diags = diags.Append(diag.Diagnostic{ - Severity: diag.Error, - Summary: "both file_path and serialized_dashboard are set; specify only one", - Locations: sd.Locations(), - }) - return v, nil - } - - contents, err := b.SyncRoot.ReadFile(filePath) - if err != nil { - return dyn.InvalidValue, fmt.Errorf("failed to read serialized dashboard from file_path %s: %w", filePath, err) - } - return dyn.Set(v, serializedDashboardFieldName, dyn.V(string(contents))) - } - - // Marshal an inline structured serialized_dashboard to a JSON string - switch sd.Kind() { - case dyn.KindInvalid, dyn.KindNil, dyn.KindString: - // KindInvalid means serialized_dashboard is absent (neither it nor - // file_path is set); leave it for backend validation to reject. - return v, nil - case dyn.KindMap: - jsonBytes, err := json.Marshal(sd.AsAny()) - if err != nil { - return dyn.InvalidValue, fmt.Errorf("failed to marshal inline serialized_dashboard: %w", err) - } - return dyn.Set(v, serializedDashboardFieldName, dyn.V(string(jsonBytes))) - default: + // Include "serialized_dashboard" field if "file_path" is set. + // Note: the Terraform resource supports "file_path" natively, but we read the contents of the dashboard here + // to be able to read file contents in Databricks Workspace (reading a dashboard file via file system fails there) + if d.FilePath != "" { + // file_path and serialized_dashboard are two ways to provide the + // same content. Accepting both is ambiguous, so reject it instead + // of silently picking one. + if d.SerializedDashboard != nil { diags = diags.Append(diag.Diagnostic{ Severity: diag.Error, - Summary: fmt.Sprintf("serialized_dashboard must be a string or map, got %s", sd.Kind()), - Locations: sd.Locations(), + Summary: "both file_path and serialized_dashboard are set; specify only one", + Locations: b.Config.GetLocationsOfExact(sdPath), }) - return v, nil + continue + } + + contents, err := b.SyncRoot.ReadFile(d.FilePath) + if err != nil { + return diags.Extend(diag.FromErr(fmt.Errorf("failed to read serialized dashboard from file_path %s: %w", d.FilePath, err))) + } + if err := b.Config.Set(sdPath, string(contents)); err != nil { + return diags.Extend(diag.FromErr(err)) + } + continue + } + + // Marshal an inline structured serialized_dashboard to a JSON string. + // Absent (neither it nor file_path is set) is left for backend validation. + s, ok, kind, err := inlineJSON(d.SerializedDashboard) + if err != nil { + return diags.Extend(diag.FromErr(fmt.Errorf("failed to marshal inline serialized_dashboard: %w", err))) + } + if kind != "" { + diags = diags.Append(diag.Diagnostic{ + Severity: diag.Error, + Summary: "serialized_dashboard must be a string or map, got " + kind, + Locations: b.Config.GetLocationsOfExact(sdPath), + }) + continue + } + if ok { + if err := b.Config.Set(sdPath, s); err != nil { + return diags.Extend(diag.FromErr(err)) } - }) - }) + } + } - diags = diags.Extend(diag.FromErr(err)) return diags } diff --git a/bundle/config/mutator/resourcemutator/configure_genie_space_serialized_space.go b/bundle/config/mutator/resourcemutator/configure_genie_space_serialized_space.go index e16bd57625b..fca28f59d4b 100644 --- a/bundle/config/mutator/resourcemutator/configure_genie_space_serialized_space.go +++ b/bundle/config/mutator/resourcemutator/configure_genie_space_serialized_space.go @@ -2,12 +2,13 @@ package resourcemutator import ( "context" - "encoding/json" "fmt" + "maps" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) const serializedSpaceFieldName = "serialized_space" @@ -25,66 +26,60 @@ func (c configureGenieSpaceSerializedSpace) Name() string { func (c configureGenieSpaceSerializedSpace) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { var diags diag.Diagnostics - pattern := dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("genie_spaces"), - dyn.AnyKey(), - ) + spaces := b.Config.Resources.GenieSpaces + for _, name := range slices.Sorted(maps.Keys(spaces)) { + g := spaces[name] + if g == nil { + continue + } + ssPath := structpath.NewPath(nil, "resources", "genie_spaces", name, serializedSpaceFieldName) - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.MapByPattern(v, pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - filePath, hasFilePath := v.Get(filePathFieldName).AsString() - ss := v.Get(serializedSpaceFieldName) - - if hasFilePath { - // file_path and serialized_space are two ways to provide the same - // content. Accepting both is ambiguous, so reject it instead of - // silently picking one. - if ss.IsValid() && ss.Kind() != dyn.KindNil { - diags = diags.Append(diag.Diagnostic{ - Severity: diag.Error, - Summary: "both file_path and serialized_space are set; specify only one", - Locations: ss.Locations(), - }) - return v, nil - } - contents, err := b.SyncRoot.ReadFile(filePath) - if err != nil { - return dyn.InvalidValue, fmt.Errorf("failed to read serialized genie space from file_path %s: %w", filePath, err) - } - return dyn.Set(v, serializedSpaceFieldName, dyn.V(string(contents))) - } - - // Marshal an inline structured serialized_space to a JSON string so - // both config-side and state-side carry the same plain string. - // Otherwise YAML decodes small ints as Go `int` while state JSON - // round-trip decodes them as `float64`, and structdiff reports - // false drift on every plan. - switch ss.Kind() { - case dyn.KindInvalid, dyn.KindNil, dyn.KindString: - // KindInvalid means serialized_space is absent (neither it nor - // file_path is set); leave it for backend validation to reject. - return v, nil - case dyn.KindMap: - // A top-level sequence would be valid JSON but is meaningless for a - // genie space, so KindSequence is not accepted here and falls through - // to the default rejection below. - jsonBytes, err := json.Marshal(ss.AsAny()) - if err != nil { - return dyn.InvalidValue, fmt.Errorf("failed to marshal inline serialized_space: %w", err) - } - return dyn.Set(v, serializedSpaceFieldName, dyn.V(string(jsonBytes))) - default: + if g.FilePath != "" { + // file_path and serialized_space are two ways to provide the same + // content. Accepting both is ambiguous, so reject it instead of + // silently picking one. + if g.SerializedSpace != nil { diags = diags.Append(diag.Diagnostic{ Severity: diag.Error, - Summary: fmt.Sprintf("serialized_space must be a string or map, got %s", ss.Kind()), - Locations: ss.Locations(), + Summary: "both file_path and serialized_space are set; specify only one", + Locations: b.Config.GetLocationsOfExact(ssPath), }) - return v, nil + continue + } + contents, err := b.SyncRoot.ReadFile(g.FilePath) + if err != nil { + return diags.Extend(diag.FromErr(fmt.Errorf("failed to read serialized genie space from file_path %s: %w", g.FilePath, err))) + } + if err := b.Config.Set(ssPath, string(contents)); err != nil { + return diags.Extend(diag.FromErr(err)) + } + continue + } + + // Marshal an inline structured serialized_space to a JSON string so + // both config-side and state-side carry the same plain string. + // Otherwise YAML decodes small ints as Go `int` while state JSON + // round-trip decodes them as `float64`, and structdiff reports + // false drift on every plan. A top-level sequence would be valid JSON + // but is meaningless for a genie space, so it is rejected. + s, ok, kind, err := inlineJSON(g.SerializedSpace) + if err != nil { + return diags.Extend(diag.FromErr(fmt.Errorf("failed to marshal inline serialized_space: %w", err))) + } + if kind != "" { + diags = diags.Append(diag.Diagnostic{ + Severity: diag.Error, + Summary: "serialized_space must be a string or map, got " + kind, + Locations: b.Config.GetLocationsOfExact(ssPath), + }) + continue + } + if ok { + if err := b.Config.Set(ssPath, s); err != nil { + return diags.Extend(diag.FromErr(err)) } - }) - }) + } + } - diags = diags.Extend(diag.FromErr(err)) return diags } diff --git a/bundle/config/mutator/resourcemutator/defaults.go b/bundle/config/mutator/resourcemutator/defaults.go new file mode 100644 index 00000000000..9dea31ba6d9 --- /dev/null +++ b/bundle/config/mutator/resourcemutator/defaults.go @@ -0,0 +1,29 @@ +package resourcemutator + +import ( + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config/resourcedefaults" + "github.com/databricks/cli/bundle/config/walk" +) + +// defaultsWalker sets the defaults of resourcedefaults.Defaults found unset by the +// generated walker (defaults_walk.go). +type defaultsWalker struct { + b *bundle.Bundle + err error +} + +// unset sets the default i at n. +func (w *defaultsWalker) unset(n *walk.Node, i int) { + if w.err == nil { + w.err = w.b.Config.Set(n.Path(), resourcedefaults.Defaults[i].Value(&w.b.Config)) + } +} + +// applyResourceDefaults sets the default values of resource fields that are not set. +func applyResourceDefaults(b *bundle.Bundle) error { + w := &defaultsWalker{b: b} + n := walk.RootWithReferences(&b.Config) + defaultsConfigRoot(w, &n, &b.Config) + return w.err +} diff --git a/bundle/config/mutator/resourcemutator/defaults_walk.go b/bundle/config/mutator/resourcemutator/defaults_walk.go new file mode 100644 index 00000000000..46f41bc24d2 --- /dev/null +++ b/bundle/config/mutator/resourcemutator/defaults_walk.go @@ -0,0 +1,358 @@ +// Code generated by bundle/internal/validation. DO NOT EDIT. + +// defaultsWalk calls unset for the fields with a default (resourcedefaults.Defaults) that are not set. + +package resourcemutator + +import ( + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/config/walk" + "github.com/databricks/databricks-sdk-go/service/compute" + "github.com/databricks/databricks-sdk-go/service/jobs" +) + +func defaultsConfigRoot(w *defaultsWalker, n *walk.Node, v *config.Root) { + if v == nil { + return + } + if x := &v.Resources; !walk.IsZeroStruct(x) || n.HasRefs("resources") { + c := n.Child("resources") + defaultsConfigResources(w, &c, x) + } +} + +func defaultsConfigResources(w *defaultsWalker, n *walk.Node, v *config.Resources) { + if v == nil { + return + } + if len(v.Alerts) > 0 { + c := n.Child("alerts") + for k, x := range v.Alerts { + if k == "" { + continue + } + e := c.Child(k) + defaultsResourcesAlert(w, &e, x) + } + } + if len(v.Apps) > 0 { + c := n.Child("apps") + for k, x := range v.Apps { + if k == "" { + continue + } + e := c.Child(k) + defaultsResourcesApp(w, &e, x) + } + } + if len(v.Clusters) > 0 { + c := n.Child("clusters") + for k, x := range v.Clusters { + if k == "" { + continue + } + e := c.Child(k) + defaultsResourcesCluster(w, &e, x) + } + } + if len(v.Dashboards) > 0 { + c := n.Child("dashboards") + for k, x := range v.Dashboards { + if k == "" { + continue + } + e := c.Child(k) + defaultsResourcesDashboard(w, &e, x) + } + } + if len(v.GenieSpaces) > 0 { + c := n.Child("genie_spaces") + for k, x := range v.GenieSpaces { + if k == "" { + continue + } + e := c.Child(k) + defaultsResourcesGenieSpace(w, &e, x) + } + } + if len(v.Jobs) > 0 { + c := n.Child("jobs") + for k, x := range v.Jobs { + if k == "" { + continue + } + e := c.Child(k) + defaultsResourcesJob(w, &e, x) + } + } + if len(v.Pipelines) > 0 { + c := n.Child("pipelines") + for k, x := range v.Pipelines { + if k == "" { + continue + } + e := c.Child(k) + defaultsResourcesPipeline(w, &e, x) + } + } + if len(v.SqlWarehouses) > 0 { + c := n.Child("sql_warehouses") + for k, x := range v.SqlWarehouses { + if k == "" { + continue + } + e := c.Child(k) + defaultsResourcesSqlWarehouse(w, &e, x) + } + } + if len(v.Volumes) > 0 { + c := n.Child("volumes") + for k, x := range v.Volumes { + if k == "" { + continue + } + e := c.Child(k) + defaultsResourcesVolume(w, &e, x) + } + } +} + +func defaultsResourcesAlert(w *defaultsWalker, n *walk.Node, v *resources.Alert) { + if v == nil { + return + } + if !(v.AlertV2.ParentPath != "" || walk.Forced(v.AlertV2.ForceSendFields, "ParentPath")) { + c := n.Child("parent_path") + w.unset(&c, 4) + } +} + +func defaultsResourcesApp(w *defaultsWalker, n *walk.Node, v *resources.App) { + if v == nil { + return + } + if !(v.App.Description != "" || walk.Forced(v.App.ForceSendFields, "Description")) { + c := n.Child("description") + w.unset(&c, 19) + } +} + +func defaultsResourcesCluster(w *defaultsWalker, n *walk.Node, v *resources.Cluster) { + if v == nil { + return + } + if !(v.ClusterSpec.AutoterminationMinutes != 0 || walk.Forced(v.ClusterSpec.ForceSendFields, "AutoterminationMinutes") || n.IsRef("autotermination_minutes")) { + c := n.Child("autotermination_minutes") + w.unset(&c, 20) + } + if x := v.ClusterSpec.WorkloadType; x != nil { + c := n.Child("workload_type") + defaultsComputeWorkloadType(w, &c, x) + } +} + +func defaultsResourcesDashboard(w *defaultsWalker, n *walk.Node, v *resources.Dashboard) { + if v == nil { + return + } + if !(v.DashboardConfig.ParentPath != "" || walk.Forced(v.DashboardConfig.ForceSendFields, "ParentPath")) { + c := n.Child("parent_path") + w.unset(&c, 0) + } + if !(v.DashboardConfig.EmbedCredentials || walk.Forced(v.DashboardConfig.ForceSendFields, "EmbedCredentials") || n.IsRef("embed_credentials")) { + c := n.Child("embed_credentials") + w.unset(&c, 1) + } +} + +func defaultsResourcesGenieSpace(w *defaultsWalker, n *walk.Node, v *resources.GenieSpace) { + if v == nil { + return + } + if !(v.GenieSpaceConfig.ParentPath != "" || walk.Forced(v.GenieSpaceConfig.ForceSendFields, "ParentPath")) { + c := n.Child("parent_path") + w.unset(&c, 2) + } +} + +func defaultsResourcesJob(w *defaultsWalker, n *walk.Node, v *resources.Job) { + if v == nil { + return + } + if !(v.JobSettings.Name != "" || walk.Forced(v.JobSettings.ForceSendFields, "Name")) { + c := n.Child("name") + w.unset(&c, 5) + } + if !(v.JobSettings.MaxConcurrentRuns != 0 || walk.Forced(v.JobSettings.ForceSendFields, "MaxConcurrentRuns") || n.IsRef("max_concurrent_runs")) { + c := n.Child("max_concurrent_runs") + w.unset(&c, 6) + } + if !(v.JobSettings.Queue != nil || walk.Forced(v.JobSettings.ForceSendFields, "Queue") || n.IsRef("queue")) { + c := n.Child("queue") + w.unset(&c, 10) + } + if x := v.JobSettings.Continuous; x != nil { + c := n.Child("continuous") + defaultsJobsContinuous(w, &c, x) + } + if len(v.JobSettings.JobClusters) > 0 { + c := n.Child("job_clusters") + for i := range v.JobSettings.JobClusters { + e := c.Elem(i) + defaultsJobsJobCluster(w, &e, &v.JobSettings.JobClusters[i]) + } + } + if x := v.JobSettings.Schedule; x != nil { + c := n.Child("schedule") + defaultsJobsCronSchedule(w, &c, x) + } + if x := v.JobSettings.Trigger; x != nil { + c := n.Child("trigger") + defaultsJobsTriggerSettings(w, &c, x) + } +} + +func defaultsResourcesPipeline(w *defaultsWalker, n *walk.Node, v *resources.Pipeline) { + if v == nil { + return + } + if !(v.CreatePipeline.Edition != "" || walk.Forced(v.CreatePipeline.ForceSendFields, "Edition")) { + c := n.Child("edition") + w.unset(&c, 13) + } + if !(v.CreatePipeline.Channel != "" || walk.Forced(v.CreatePipeline.ForceSendFields, "Channel")) { + c := n.Child("channel") + w.unset(&c, 14) + } +} + +func defaultsResourcesSqlWarehouse(w *defaultsWalker, n *walk.Node, v *resources.SqlWarehouse) { + if v == nil { + return + } + if !(v.CreateWarehouseRequest.AutoStopMins != 0 || walk.Forced(v.CreateWarehouseRequest.ForceSendFields, "AutoStopMins") || n.IsRef("auto_stop_mins")) { + c := n.Child("auto_stop_mins") + w.unset(&c, 15) + } + if !(v.CreateWarehouseRequest.EnablePhoton || walk.Forced(v.CreateWarehouseRequest.ForceSendFields, "EnablePhoton") || n.IsRef("enable_photon")) { + c := n.Child("enable_photon") + w.unset(&c, 16) + } + if !(v.CreateWarehouseRequest.MaxNumClusters != 0 || walk.Forced(v.CreateWarehouseRequest.ForceSendFields, "MaxNumClusters") || n.IsRef("max_num_clusters")) { + c := n.Child("max_num_clusters") + w.unset(&c, 17) + } + if !(v.CreateWarehouseRequest.SpotInstancePolicy != "" || walk.Forced(v.CreateWarehouseRequest.ForceSendFields, "SpotInstancePolicy")) { + c := n.Child("spot_instance_policy") + w.unset(&c, 18) + } +} + +func defaultsResourcesVolume(w *defaultsWalker, n *walk.Node, v *resources.Volume) { + if v == nil { + return + } + if !(v.CreateVolumeRequestContent.VolumeType != "" || walk.Forced(v.CreateVolumeRequestContent.ForceSendFields, "VolumeType")) { + c := n.Child("volume_type") + w.unset(&c, 3) + } +} + +func defaultsComputeWorkloadType(w *defaultsWalker, n *walk.Node, v *compute.WorkloadType) { + if v == nil { + return + } + if x := &v.Clients; !walk.IsZeroStruct(x) || n.HasRefs("clients") { + c := n.Child("clients") + defaultsComputeClientsTypes(w, &c, x) + } +} + +func defaultsJobsContinuous(w *defaultsWalker, n *walk.Node, v *jobs.Continuous) { + if v == nil { + return + } + if !(v.PauseStatus != "") { + c := n.Child("pause_status") + w.unset(&c, 9) + } +} + +func defaultsJobsJobCluster(w *defaultsWalker, n *walk.Node, v *jobs.JobCluster) { + if v == nil { + return + } + if x := v.NewCluster; x != nil { + c := n.Child("new_cluster") + defaultsComputeClusterSpec(w, &c, x) + } +} + +func defaultsJobsCronSchedule(w *defaultsWalker, n *walk.Node, v *jobs.CronSchedule) { + if v == nil { + return + } + if !(v.PauseStatus != "") { + c := n.Child("pause_status") + w.unset(&c, 7) + } +} + +func defaultsJobsTriggerSettings(w *defaultsWalker, n *walk.Node, v *jobs.TriggerSettings) { + if v == nil { + return + } + if !(v.PauseStatus != "") { + c := n.Child("pause_status") + w.unset(&c, 8) + } +} + +func defaultsComputeClientsTypes(w *defaultsWalker, n *walk.Node, v *compute.ClientsTypes) { + if v == nil { + return + } + if !(v.Notebooks || walk.Forced(v.ForceSendFields, "Notebooks") || n.IsRef("notebooks")) { + c := n.Child("notebooks") + w.unset(&c, 21) + } + if !(v.Jobs || walk.Forced(v.ForceSendFields, "Jobs") || n.IsRef("jobs")) { + c := n.Child("jobs") + w.unset(&c, 22) + } +} + +func defaultsComputeClusterSpec(w *defaultsWalker, n *walk.Node, v *compute.ClusterSpec) { + if v == nil { + return + } + if x := v.WorkloadType; x != nil { + c := n.Child("workload_type") + defaultsComputeWorkloadType2(w, &c, x) + } +} + +func defaultsComputeWorkloadType2(w *defaultsWalker, n *walk.Node, v *compute.WorkloadType) { + if v == nil { + return + } + if x := &v.Clients; !walk.IsZeroStruct(x) || n.HasRefs("clients") { + c := n.Child("clients") + defaultsComputeClientsTypes2(w, &c, x) + } +} + +func defaultsComputeClientsTypes2(w *defaultsWalker, n *walk.Node, v *compute.ClientsTypes) { + if v == nil { + return + } + if !(v.Notebooks || walk.Forced(v.ForceSendFields, "Notebooks") || n.IsRef("notebooks")) { + c := n.Child("notebooks") + w.unset(&c, 11) + } + if !(v.Jobs || walk.Forced(v.ForceSendFields, "Jobs") || n.IsRef("jobs")) { + c := n.Child("jobs") + w.unset(&c, 12) + } +} diff --git a/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths_test.go b/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths_test.go index 7b640df45e1..608ccbf155a 100644 --- a/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths_test.go +++ b/bundle/config/mutator/resourcemutator/expand_pipeline_glob_paths_test.go @@ -15,7 +15,6 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/internal/bundletest" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/databricks/databricks-sdk-go/service/pipelines" "github.com/stretchr/testify/require" @@ -235,8 +234,8 @@ func TestExpandGlobPathsInPipelinesKeepsOrder(t *testing.T) { {File: &pipelines.FileLibrary{Path: "a/1.py"}}, {File: &pipelines.FileLibrary{Path: "a/2.py"}}, {File: &pipelines.FileLibrary{Path: "/Workspace/x.py"}}, - // The empty notebook is kept; the round trip through the dynamic configuration forces it. - {Notebook: &pipelines.NotebookLibrary{}, ForceSendFields: []string{"Notebook"}}, + // The empty notebook is kept. + {Notebook: &pipelines.NotebookLibrary{}}, }, b.Config.Resources.Pipelines["pipeline"].Libraries) // The original library is not modified. @@ -265,8 +264,4 @@ func TestExpandGlobPathsInPipelinesWithoutLibraries(t *testing.T) { assert.Nil(t, b.Config.Resources.Pipelines["unset"].Libraries) assert.Equal(t, []pipelines.PipelineLibrary{}, b.Config.Resources.Pipelines["empty"].Libraries) - - v := b.Config.Value() - assert.False(t, v.Get("resources").Get("pipelines").Get("unset").Get("libraries").IsValid()) - assert.Equal(t, dyn.KindSequence, v.Get("resources").Get("pipelines").Get("empty").Get("libraries").Kind()) } diff --git a/bundle/config/mutator/resourcemutator/fix_permissions.go b/bundle/config/mutator/resourcemutator/fix_permissions.go index 9e6c785b0d9..0e28f3b3ac8 100644 --- a/bundle/config/mutator/resourcemutator/fix_permissions.go +++ b/bundle/config/mutator/resourcemutator/fix_permissions.go @@ -5,10 +5,12 @@ import ( "strings" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/iamutil" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go/service/iam" ) const ( @@ -52,165 +54,132 @@ func (m *fixPermissions) Name() string { return "FixPermissions" } -func processPermissions(currentUser string) dyn.MapFunc { - return func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - // Extract resource type from path: resources...permissions - if len(p) != 4 || p[0].Key() != "resources" || p[3].Key() != "permissions" { - return v, nil - } - - resourceType := p[1].Key() - if ignoredResources[resourceType] { - return v, nil - } - - v, err := ensureCurrentUserMgmtPermissions(v, currentUser, resourceType) - if err != nil { - return v, err - } - - return useMaximumLevel(v, resourceType) - } +// permission is a permission entry: a level and the principal it is granted to. +type permission struct { + level string + userName string + servicePrincipalName string + groupName string } -func readUser(v dyn.Value) string { - userName, _ := dyn.GetValue(v, "user_name").AsString() - if userName != "" { - return userName +func (p permission) user() string { + if p.userName != "" { + return p.userName } - servicePrincipalName, _ := dyn.GetValue(v, "service_principal_name").AsString() - return servicePrincipalName + return p.servicePrincipalName } -func readPrincipal(v dyn.Value) string { - value, _ := dyn.GetValue(v, "user_name").AsString() - if value != "" { - return "user_name:" + value - } - value, _ = dyn.GetValue(v, "service_principal_name").AsString() - if value != "" { - return "service_principal_name:" + value - } - value, _ = dyn.GetValue(v, "group_name").AsString() - if value != "" { - return "group_name:" + value +// principal returns the principal in the form ":", or "" if there is none. +func (p permission) principal() string { + switch { + case p.userName != "": + return "user_name:" + p.userName + case p.servicePrincipalName != "": + return "service_principal_name:" + p.servicePrincipalName + case p.groupName != "": + return "group_name:" + p.groupName } return "" } -func ensureCurrentUserMgmtPermissions(permissions dyn.Value, currentUser, resourceType string) (dyn.Value, error) { +func processPermissions(currentUser, resourceType string, permissions []permission) []permission { + return useMaximumLevel(ensureCurrentUserMgmtPermissions(permissions, currentUser, resourceType)) +} + +func ensureCurrentUserMgmtPermissions(permissions []permission, currentUser, resourceType string) []permission { currentUserHasIsOwner := false currentUserIndCanManage := -1 canAddIsOwner := hasIsOwner[resourceType] - permissionArray, ok := permissions.AsSequence() - if !ok { - return permissions, nil - } - - for ind, permission := range permissionArray { - level, ok := dyn.GetValue(permission, "level").AsString() - if !ok { + for ind, p := range permissions { + if p.level == "" { continue } - user := readUser(permission) - if level == isOwner { + user := p.user() + if p.level == isOwner { canAddIsOwner = false if user == currentUser { currentUserHasIsOwner = true } } - if user == currentUser && level == canManage { + if user == currentUser && p.level == canManage { currentUserIndCanManage = ind } } if currentUserHasIsOwner { - return dyn.V(permissionArray), nil + return permissions } if canAddIsOwner { if currentUserIndCanManage >= 0 { // Upgrade current user's CAN_MANAGE to IS_OWNER. We do this because terraform will add IS_OWNER if it does not see one // and that may confuse backend. We can stop doing it when removed terraform. - v, _ := dyn.Set(permissionArray[currentUserIndCanManage], "level", dyn.V(isOwner)) - permissionArray[currentUserIndCanManage] = v + permissions[currentUserIndCanManage].level = isOwner } else { - permissionArray = append(permissionArray, createPermission(currentUser, isOwner)) + permissions = append(permissions, createPermission(currentUser, isOwner)) } - return dyn.V(permissionArray), nil + return permissions } if currentUserIndCanManage < 0 { - permissionArray = append(permissionArray, createPermission(currentUser, canManage)) + permissions = append(permissions, createPermission(currentUser, canManage)) } - return dyn.V(permissionArray), nil + return permissions } -func useMaximumLevel(permissions dyn.Value, resourceType string) (dyn.Value, error) { - permissionArray, ok := permissions.AsSequence() - if !ok { - return permissions, nil - } - +func useMaximumLevel(permissions []permission) []permission { levelPerPrincipal := make(map[string]string) - principalIndex := make(map[string]int) + seen := make(map[string]bool) var principals []string - for _, permission := range permissionArray { - level, _ := dyn.GetValue(permission, "level").AsString() - if level == "" { + for _, p := range permissions { + if p.level == "" { continue } - principal := readPrincipal(permission) + principal := p.principal() if principal == "" { continue } - _, ok = principalIndex[principal] - if !ok { - ind := len(principalIndex) - principalIndex[principal] = ind + if !seen[principal] { + seen[principal] = true principals = append(principals, principal) } - levelPerPrincipal[principal] = resources.GetMaxLevel(levelPerPrincipal[principal], level) + levelPerPrincipal[principal] = resources.GetMaxLevel(levelPerPrincipal[principal], p.level) } - var newPermissions []dyn.Value - + var newPermissions []permission for _, principal := range principals { newPermissions = append(newPermissions, createPermissionFromPrincipal(principal, levelPerPrincipal[principal])) } - return dyn.V(newPermissions), nil + return newPermissions } -func createPermission(user, level string) dyn.Value { - permission := map[string]dyn.Value{ - "level": dyn.V(level), - } - +func createPermission(user, level string) permission { // Determine if currentUser is a service principal or user if iamutil.IsServicePrincipalName(user) { - permission["service_principal_name"] = dyn.V(user) - } else { - permission["user_name"] = dyn.V(user) + return permission{level: level, servicePrincipalName: user} } - - return dyn.V(permission) + return permission{level: level, userName: user} } -func createPermissionFromPrincipal(principal, level string) dyn.Value { - permission := map[string]dyn.Value{ - "level": dyn.V(level), - } - +func createPermissionFromPrincipal(principal, level string) permission { items := strings.SplitN(principal, ":", 2) field := items[0] value := items[1] - permission[field] = dyn.V(value) - return dyn.V(permission) + p := permission{level: level} + switch field { + case "user_name": + p.userName = value + case "service_principal_name": + p.servicePrincipalName = value + case "group_name": + p.groupName = value + } + return p } func (m *fixPermissions) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { @@ -222,15 +191,38 @@ func (m *fixPermissions) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagn } currentUser := b.Config.Workspace.CurrentUser.UserName - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - // Use MapByPattern to directly process permissions arrays - return dyn.MapByPattern(v, dyn.NewPattern( - dyn.Key("resources"), - dyn.AnyKey(), - dyn.AnyKey(), - dyn.Key("permissions"), - ), processPermissions(currentUser)) + b.Config.Resources.IterateAllPermissions(func(typ, name string, perms config.PermissionList) bool { + if ignoredResources[typ] || perms.IsNil() { + return true + } + + permissions := make([]permission, 0, perms.Len()) + for i := range perms.Len() { + p := perms.At(i) + permissions = append(permissions, permission{ + level: string(p.Level), + userName: p.UserName, + servicePrincipalName: p.ServicePrincipalName, + groupName: p.GroupName, + }) + } + + processed := processPermissions(currentUser, typ, permissions) + out := make([]resources.Permission, 0, len(processed)) + for _, p := range processed { + out = append(out, resources.Permission{ + Level: iam.PermissionLevel(p.level), + UserName: p.userName, + ServicePrincipalName: p.servicePrincipalName, + GroupName: p.groupName, + }) + } + perms.Set(out) + + // The permissions are rebuilt without locations. + b.Config.SetLocations(structpath.NewPath(nil, "resources", typ, name, "permissions"), nil) + return true }) - return diag.FromErr(err) + return nil } diff --git a/bundle/config/mutator/resourcemutator/inline_json.go b/bundle/config/mutator/resourcemutator/inline_json.go new file mode 100644 index 00000000000..ef91cfa93c7 --- /dev/null +++ b/bundle/config/mutator/resourcemutator/inline_json.go @@ -0,0 +1,38 @@ +package resourcemutator + +import ( + "encoding/json" + "reflect" +) + +// inlineJSON converts the value of a field that holds a JSON document as a string but +// may be authored as inline YAML. It returns the JSON encoding of an inline map and +// true; false if v is not set or already a string; and the kind of v (as reported in +// diagnostics) if it is neither. +func inlineJSON(v any) (string, bool, string, error) { + if v == nil { + return "", false, "", nil + } + if _, ok := v.(string); ok { + return "", false, "", nil + } + switch reflect.ValueOf(v).Kind() { + case reflect.Map, reflect.Struct: + b, err := json.Marshal(v) + if err != nil { + return "", false, "", err + } + return string(b), true, "", nil + case reflect.Slice, reflect.Array: + return "", false, "sequence", nil + case reflect.Bool: + return "", false, "bool", nil + case reflect.Float32, reflect.Float64: + return "", false, "float", nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return "", false, "int", nil + default: + return "", false, reflect.ValueOf(v).Kind().String(), nil + } +} diff --git a/bundle/config/mutator/resourcemutator/keyed_merge.go b/bundle/config/mutator/resourcemutator/keyed_merge.go new file mode 100644 index 00000000000..5f9c7ee26bc --- /dev/null +++ b/bundle/config/mutator/resourcemutator/keyed_merge.go @@ -0,0 +1,80 @@ +package resourcemutator + +import ( + "slices" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" +) + +// mergeByKey merges, for every resource in resources (visited in sorted order), the sequence +// at resources... by key, skipping sequences for which the merge +// is a no-op (see [keyedMergeIsNoop]). The key of an element is noopKey(element), which must +// agree with keyFn applied to the key field of the element. +func mergeByKey[R, E any]( + b *bundle.Bundle, + resourceType string, + resources map[string]*R, + field, keyField string, + elems func(*R) []E, + noopKey func(E) string, + keyFn func(structvar.View) string, + sortKeys bool, +) error { + names := make([]string, 0, len(resources)) + for name := range resources { + names = append(names, name) + } + slices.Sort(names) + for _, name := range names { + r := resources[name] + if r == nil { + continue + } + path := structpath.NewPath(nil, "resources", resourceType, name, field) + if keyedMergeIsNoop(b, path, elems(r), noopKey, sortKeys) { + continue + } + if err := b.Config.MergeElementsByKey(path, keyField, keyFn, sortKeys); err != nil { + return err + } + } + return nil +} + +// stringKey returns a key function for sequence elements whose key is a string +// (an absent key is the empty string). +func stringKey(what string) func(structvar.View) string { + return func(v structvar.View) string { + switch v.Kind() { + case structvar.KindInvalid, structvar.KindNil: + return "" + case structvar.KindString: + s, _ := v.AsString() + return s + default: + panic(what + " must be a string") + } + } +} + +// keyedMergeIsNoop reports whether merging the elements of the sequence at path by key +// leaves it unchanged: the sequence is not a reference, and every key is non-empty and +// unique (and sorted, if the merge sorts by key). The merge operates on the dynamic +// configuration, which is built from the whole typed configuration, so mutators skip +// it when it would have no effect. +func keyedMergeIsNoop[T any](b *bundle.Bundle, path *structpath.PathNode, elems []T, key func(T) string, sorted bool) bool { + if b.Config.IsReferenceAt(path) { + return false + } + keys := make([]string, 0, len(elems)) + for _, e := range elems { + k := key(e) + if k == "" || slices.Contains(keys, k) { + return false + } + keys = append(keys, k) + } + return !sorted || slices.IsSorted(keys) +} diff --git a/bundle/config/mutator/resourcemutator/merge_apps.go b/bundle/config/mutator/resourcemutator/merge_apps.go index edbdf7712ce..2a2b5931f48 100644 --- a/bundle/config/mutator/resourcemutator/merge_apps.go +++ b/bundle/config/mutator/resourcemutator/merge_apps.go @@ -2,11 +2,12 @@ package resourcemutator import ( "context" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/merge" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go/service/apps" ) type mergeApps struct{} @@ -19,27 +20,52 @@ func (m *mergeApps) Name() string { return "MergeApps" } -func (m *mergeApps) resourceName(v dyn.Value) string { - switch v.Kind() { - case dyn.KindInvalid, dyn.KindNil: - return "" - case dyn.KindString: - return v.MustString() - default: - panic("app name must be a string") +func (m *mergeApps) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { + names := make([]string, 0, len(b.Config.Resources.Apps)) + for name := range b.Config.Resources.Apps { + names = append(names, name) } -} + slices.Sort(names) -func (m *mergeApps) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - if v.Kind() == dyn.KindNil { - return v, nil + for _, name := range names { + app := b.Config.Resources.Apps[name] + if app == nil { + continue + } + path := structpath.NewPath(nil, "resources", "apps", name, "resources") + if keyedMergeIsNoop(b, path, app.Resources, func(r apps.AppResource) string { return r.Name }, false) { + continue } - return dyn.Map(v, "resources.apps", dyn.Foreach(func(_ dyn.Path, app dyn.Value) (dyn.Value, error) { - return dyn.Map(app, "resources", merge.ElementsByKeyWithOverride("name", m.resourceName)) - })) - }) + // Elements with the same name are overridden by the later ones. The result + // is ordered by the first appearance of the name. + var order []string + last := map[string]int{} + for i, r := range app.Resources { + if _, ok := last[r.Name]; !ok { + order = append(order, r.Name) + } + last[r.Name] = i + } - return diag.FromErr(err) + merged := make([]apps.AppResource, 0, len(order)) + sources := make([][]int, 0, len(order)) + for _, key := range order { + merged = append(merged, app.Resources[last[key]]) + sources = append(sources, []int{last[key]}) + } + app.Resources = merged + b.Config.UpdateSequence(path, sources) + + // The merge sets the key of every element, even if it is empty. + for i, r := range merged { + if r.Name != "" { + continue + } + if err := b.Config.Set(structpath.NewPath(path, i, "name"), ""); err != nil { + return diag.FromErr(err) + } + } + } + return nil } diff --git a/bundle/config/mutator/resourcemutator/merge_grants.go b/bundle/config/mutator/resourcemutator/merge_grants.go index 3966190a5ee..1011c1c2958 100644 --- a/bundle/config/mutator/resourcemutator/merge_grants.go +++ b/bundle/config/mutator/resourcemutator/merge_grants.go @@ -5,21 +5,11 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/merge" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" + "github.com/databricks/databricks-sdk-go/service/catalog" ) -// Resource types that support grants. -var grantResourceTypes = []string{ - "catalogs", - "schemas", - "external_locations", - "secrets", - "volumes", - "registered_models", - "vector_search_indexes", -} - type mergeGrants struct{} // MergeGrants returns a mutator that deduplicates grant entries. @@ -33,58 +23,58 @@ func (m *mergeGrants) Name() string { } func (m *mergeGrants) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - if v.Kind() == dyn.KindNil { - return v, nil - } - - for _, resourceType := range grantResourceTypes { - var mapErr error - v, mapErr = dyn.Map(v, "resources."+resourceType, dyn.Foreach(func(_ dyn.Path, resource dyn.Value) (dyn.Value, error) { - // Merge grant entries by principal. This concatenates privileges - // for entries with the same principal via the standard merge semantics. - resource, err := dyn.Map(resource, "grants", merge.ElementsByKey("principal", func(v dyn.Value) string { - s, _ := v.AsString() - return s - })) - if err != nil { - return resource, err - } - - // Deduplicate privileges within each grant entry. - return dyn.Map(resource, "grants", dyn.Foreach(func(_ dyn.Path, grant dyn.Value) (dyn.Value, error) { - return dyn.Map(grant, "privileges", deduplicateSequence) - })) - })) - if mapErr != nil { - return v, mapErr - } - } - - return v, nil + var err error + b.Config.Resources.IterateAllGrants(func(typ, name string, grants *[]catalog.PrivilegeAssignment) bool { + err = mergeGrantsOf(b, typ, name, grants) + return err == nil }) - return diag.FromErr(err) } -// deduplicateSequence removes duplicate values from a dyn sequence, -// preserving the order of first appearance. -func deduplicateSequence(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - elements, ok := v.AsSequence() - if !ok { - return v, nil +// mergeGrantsOf merges the grants of the resource name of type typ by principal, and +// deduplicates the privileges of each grant. +func mergeGrantsOf(b *bundle.Bundle, typ, name string, grants *[]catalog.PrivilegeAssignment) error { + if len(*grants) == 0 { + return nil + } + + // Merge grant entries by principal. This concatenates privileges + // for entries with the same principal via the standard merge semantics. + path := structpath.NewPath(nil, "resources", typ, name, "grants") + err := b.Config.MergeElementsByKey(path, "principal", func(v structvar.View) string { + s, _ := v.AsString() + return s + }, false) + if err != nil { + return err } - seen := make(map[string]bool, len(elements)) - out := make([]dyn.Value, 0, len(elements)) - for _, elem := range elements { - key, _ := elem.AsString() - if seen[key] { + // Deduplicate privileges within each grant entry (the merge replaced the slice). + gs := *grants + for i := range gs { + privileges, sources := deduplicatePrivileges(gs[i].Privileges) + if len(privileges) == len(gs[i].Privileges) { continue } - seen[key] = true - out = append(out, elem) + gs[i].Privileges = privileges + b.Config.UpdateSequence(structpath.NewPath(path, i, "privileges"), sources) } + return nil +} - return dyn.NewValue(out, v.Locations()), nil +// deduplicatePrivileges removes duplicate privileges, preserving the order of first +// appearance. It also returns, for every remaining privilege, its index in the input. +func deduplicatePrivileges(in []catalog.Privilege) ([]catalog.Privilege, [][]int) { + seen := make(map[catalog.Privilege]bool, len(in)) + out := make([]catalog.Privilege, 0, len(in)) + sources := make([][]int, 0, len(in)) + for i, p := range in { + if seen[p] { + continue + } + seen[p] = true + out = append(out, p) + sources = append(sources, []int{i}) + } + return out, sources } diff --git a/bundle/config/mutator/resourcemutator/merge_job_clusters.go b/bundle/config/mutator/resourcemutator/merge_job_clusters.go index c7c46a3fdef..9d63460dc50 100644 --- a/bundle/config/mutator/resourcemutator/merge_job_clusters.go +++ b/bundle/config/mutator/resourcemutator/merge_job_clusters.go @@ -4,9 +4,9 @@ import ( "context" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/merge" + "github.com/databricks/databricks-sdk-go/service/jobs" ) type mergeJobClusters struct{} @@ -19,27 +19,10 @@ func (m *mergeJobClusters) Name() string { return "MergeJobClusters" } -func (m *mergeJobClusters) jobClusterKey(v dyn.Value) string { - switch v.Kind() { - case dyn.KindInvalid, dyn.KindNil: - return "" - case dyn.KindString: - return v.MustString() - default: - panic("job cluster key must be a string") - } -} - func (m *mergeJobClusters) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - if v.Kind() == dyn.KindNil { - return v, nil - } - - return dyn.Map(v, "resources.jobs", dyn.Foreach(func(_ dyn.Path, job dyn.Value) (dyn.Value, error) { - return dyn.Map(job, "job_clusters", merge.ElementsByKey("job_cluster_key", m.jobClusterKey)) - })) - }) - + err := mergeByKey(b, "jobs", b.Config.Resources.Jobs, "job_clusters", "job_cluster_key", + func(r *resources.Job) []jobs.JobCluster { return r.JobClusters }, + func(c jobs.JobCluster) string { return c.JobClusterKey }, + stringKey("job cluster key"), false) return diag.FromErr(err) } diff --git a/bundle/config/mutator/resourcemutator/merge_job_parameters.go b/bundle/config/mutator/resourcemutator/merge_job_parameters.go index 79458b36a60..8b1413e4bf6 100644 --- a/bundle/config/mutator/resourcemutator/merge_job_parameters.go +++ b/bundle/config/mutator/resourcemutator/merge_job_parameters.go @@ -4,9 +4,9 @@ import ( "context" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/merge" + "github.com/databricks/databricks-sdk-go/service/jobs" ) type mergeJobParameters struct{} @@ -19,27 +19,10 @@ func (m *mergeJobParameters) Name() string { return "MergeJobParameters" } -func (m *mergeJobParameters) parameterNameString(v dyn.Value) string { - switch v.Kind() { - case dyn.KindInvalid, dyn.KindNil: - return "" - case dyn.KindString: - return v.MustString() - default: - panic("task key must be a string") - } -} - func (m *mergeJobParameters) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - if v.Kind() == dyn.KindNil { - return v, nil - } - - return dyn.Map(v, "resources.jobs", dyn.Foreach(func(_ dyn.Path, job dyn.Value) (dyn.Value, error) { - return dyn.Map(job, "parameters", merge.ElementsByKey("name", m.parameterNameString)) - })) - }) - + err := mergeByKey(b, "jobs", b.Config.Resources.Jobs, "parameters", "name", + func(r *resources.Job) []jobs.JobParameterDefinition { return r.Parameters }, + func(p jobs.JobParameterDefinition) string { return p.Name }, + stringKey("parameter name"), false) return diag.FromErr(err) } diff --git a/bundle/config/mutator/resourcemutator/merge_job_tasks.go b/bundle/config/mutator/resourcemutator/merge_job_tasks.go index b85a863d80f..562157a46a6 100644 --- a/bundle/config/mutator/resourcemutator/merge_job_tasks.go +++ b/bundle/config/mutator/resourcemutator/merge_job_tasks.go @@ -4,9 +4,9 @@ import ( "context" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/merge" + "github.com/databricks/databricks-sdk-go/service/jobs" ) type mergeJobTasks struct{} @@ -19,31 +19,14 @@ func (m *mergeJobTasks) Name() string { return "MergeJobTasks" } -func (m *mergeJobTasks) taskKeyString(v dyn.Value) string { - switch v.Kind() { - case dyn.KindInvalid, dyn.KindNil: - return "" - case dyn.KindString: - return v.MustString() - default: - panic("task key must be a string") - } -} - func (m *mergeJobTasks) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - if v.Kind() == dyn.KindNil { - return v, nil - } - - return dyn.Map(v, "resources.jobs", dyn.Foreach(func(_ dyn.Path, job dyn.Value) (dyn.Value, error) { - // Sorting keys here since it'll be sorted by TF anyway - // https://github.com/databricks/terraform-provider-databricks/blob/0a932c2/jobs/resource_job.go#L343 - // However, if we don't sort we have a difference between direct and TF and between configs in - // "bundle validate" and configs sent to backend. - return dyn.Map(job, "tasks", merge.ElementsBySortedKey("task_key", m.taskKeyString)) - })) - }) - + // Sorting keys here since it'll be sorted by TF anyway + // https://github.com/databricks/terraform-provider-databricks/blob/0a932c2/jobs/resource_job.go#L343 + // However, if we don't sort we have a difference between direct and TF and between configs in + // "bundle validate" and configs sent to backend. + err := mergeByKey(b, "jobs", b.Config.Resources.Jobs, "tasks", "task_key", + func(r *resources.Job) []jobs.Task { return r.Tasks }, + func(t jobs.Task) string { return t.TaskKey }, + stringKey("task key"), true) return diag.FromErr(err) } diff --git a/bundle/config/mutator/resourcemutator/merge_pipeline_clusters.go b/bundle/config/mutator/resourcemutator/merge_pipeline_clusters.go index 6496a00d90e..622f15bfec4 100644 --- a/bundle/config/mutator/resourcemutator/merge_pipeline_clusters.go +++ b/bundle/config/mutator/resourcemutator/merge_pipeline_clusters.go @@ -5,9 +5,10 @@ import ( "strings" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/merge" + "github.com/databricks/cli/libs/structs/structvar" + "github.com/databricks/databricks-sdk-go/service/pipelines" ) type mergePipelineClusters struct{} @@ -20,29 +21,31 @@ func (m *mergePipelineClusters) Name() string { return "MergePipelineClusters" } -func (m *mergePipelineClusters) clusterLabel(v dyn.Value) string { +func (m *mergePipelineClusters) clusterLabel(v structvar.View) string { switch v.Kind() { - case dyn.KindInvalid, dyn.KindNil: + case structvar.KindInvalid, structvar.KindNil: // Note: the cluster label is optional and defaults to 'default'. // We therefore ALSO merge all clusters without a label. return "default" - case dyn.KindString: - return strings.ToLower(v.MustString()) + case structvar.KindString: + s, _ := v.AsString() + return strings.ToLower(s) default: panic("task key must be a string") } } func (m *mergePipelineClusters) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - if v.Kind() == dyn.KindNil { - return v, nil - } - - return dyn.Map(v, "resources.pipelines", dyn.Foreach(func(_ dyn.Path, pipeline dyn.Value) (dyn.Value, error) { - return dyn.Map(pipeline, "clusters", merge.ElementsByKey("label", m.clusterLabel)) - })) - }) - + // The merge also lowercases labels and sets the "default" label, so only + // lowercase labels are left unchanged. + err := mergeByKey(b, "pipelines", b.Config.Resources.Pipelines, "clusters", "label", + func(r *resources.Pipeline) []pipelines.PipelineCluster { return r.Clusters }, + func(c pipelines.PipelineCluster) string { + if c.Label != strings.ToLower(c.Label) { + return "" + } + return c.Label + }, + m.clusterLabel, false) return diag.FromErr(err) } diff --git a/bundle/config/mutator/resourcemutator/process_static_resources_test.go b/bundle/config/mutator/resourcemutator/process_static_resources_test.go index f23242d58e0..9c4dc40025f 100644 --- a/bundle/config/mutator/resourcemutator/process_static_resources_test.go +++ b/bundle/config/mutator/resourcemutator/process_static_resources_test.go @@ -2,13 +2,12 @@ package resourcemutator import ( "reflect" + "strings" "testing" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -53,11 +52,12 @@ func TestGetAllResourcesMatchesConfigKeys(t *testing.T) { field.Set(m) } - root, err := convert.FromTyped(b.Config, dyn.NilValue) - require.NoError(t, err) + // The key of a resource type is the JSON name of its field in Resources. expected := NewResourceKeySet() - err = expected.AddPattern(dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), root) - require.NoError(t, err) + for i := range rv.NumField() { + typ, _, _ := strings.Cut(rv.Type().Field(i).Tag.Get("json"), ",") + expected.AddResourceKey(ResourceKey{Type: typ, Name: "my_resource"}) + } require.Equal(t, rv.NumField(), expected.Size()) set, err := getAllResources(b) diff --git a/bundle/config/mutator/resourcemutator/resource_key.go b/bundle/config/mutator/resourcemutator/resource_key.go index 699867b4f40..dffc85b59f4 100644 --- a/bundle/config/mutator/resourcemutator/resource_key.go +++ b/bundle/config/mutator/resourcemutator/resource_key.go @@ -1,10 +1,6 @@ package resourcemutator -import ( - "errors" - - "github.com/databricks/cli/libs/dyn" -) +import "github.com/databricks/cli/bundle/config" // ResourceKey uniquely identifies a resource in configuration. type ResourceKey struct { @@ -47,24 +43,22 @@ func (r ResourceKeySet) Size() int { return size } -// AddPattern adds all resource keys that match the pattern. -func (r ResourceKeySet) AddPattern(pattern dyn.Pattern, root dyn.Value) error { - if len(pattern) != 3 { - return errors.New("pattern must have 3 keys") - } +// AddResources adds the keys of all resources in r. +func (r ResourceKeySet) AddResources(resources *config.Resources) { + resources.IterateAllResources(func(typ, name string, _ config.ConfigResource) bool { + r.AddResourceKey(ResourceKey{Type: typ, Name: name}) + return true + }) +} - _, err := dyn.MapByPattern(root, pattern, func(path dyn.Path, v dyn.Value) (dyn.Value, error) { - parsed, err := getResourceKey(path) - if err != nil { - return dyn.InvalidValue, err +// AddResourcesOfType adds the keys of the resources of type typ in r. +func (r ResourceKeySet) AddResourcesOfType(resources *config.Resources, typ string) { + resources.IterateAllResources(func(t, name string, _ config.ConfigResource) bool { + if t == typ { + r.AddResourceKey(ResourceKey{Type: typ, Name: name}) } - - r.AddResourceKey(parsed) - - return v, nil + return true }) - - return err } // Types returns the types of all resources in the set. @@ -104,25 +98,3 @@ func (r ResourceKeySet) ToArray() []ResourceKey { return result } - -func getResourceKey(path dyn.Path) (ResourceKey, error) { - if len(path) < 3 { - return ResourceKey{}, errors.New("can't parse resource key") - } - - if path[0].Key() != "resources" { - return ResourceKey{}, errors.New("can't parse resource key") - } - - resourceType := path[1].Key() - resourceName := path[2].Key() - - if resourceType == "" || resourceName == "" { - return ResourceKey{}, errors.New("can't parse resource key") - } - - return ResourceKey{ - Type: resourceType, - Name: resourceName, - }, nil -} diff --git a/bundle/config/mutator/resourcemutator/resource_key_test.go b/bundle/config/mutator/resourcemutator/resource_key_test.go index 1af72a9ba2d..ebbcc37710d 100644 --- a/bundle/config/mutator/resourcemutator/resource_key_test.go +++ b/bundle/config/mutator/resourcemutator/resource_key_test.go @@ -3,112 +3,36 @@ package resourcemutator import ( "testing" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/assert" - - "github.com/databricks/cli/libs/dyn" ) -type getResourceKeyTestCase struct { - path string - key ResourceKey - err bool -} - -func TestGetResourceKey(t *testing.T) { - testCases := []getResourceKeyTestCase{ - { - path: "resources.jobs.job_1", - key: ResourceKey{ - Type: "jobs", - Name: "job_1", - }, - }, - { - path: "resources.jobs.job_1.name", - key: ResourceKey{ - Type: "jobs", - Name: "job_1", - }, - }, - { - path: "resources.jobs", - err: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.path, func(t *testing.T) { - key, err := getResourceKey(dyn.MustPathFromString(tc.path)) - if tc.err { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, tc.key, key) - } - }) +func TestResourceKeySet_AddResources(t *testing.T) { + var r config.Resources + r.Jobs = map[string]*resources.Job{ + "job_1": {JobSettings: jobs.JobSettings{Name: "job_1"}}, + "job_2": nil, } -} - -type resourceKeySetAddTestCase struct { - name string - pattern dyn.Pattern - root dyn.Value - expected []ResourceKey -} - -func TestResourceKeySet_AddPattern(t *testing.T) { - root := dyn.V(map[string]dyn.Value{ - "resources": dyn.V(map[string]dyn.Value{ - "jobs": dyn.V(map[string]dyn.Value{ - "job_1": dyn.V(map[string]dyn.Value{ - "name": dyn.V("job_1"), - }), - "job_2": dyn.V(map[string]dyn.Value{ - "name": dyn.V("job_2"), - }), - }), - }), - }) - - testCases := []resourceKeySetAddTestCase{ - { - name: "one job pattern", - pattern: dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.Key("job_1")), - root: root, - expected: []ResourceKey{ - { - Type: "jobs", - Name: "job_1", - }, - }, - }, - { - name: "all resources pattern", - pattern: dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), - root: root, - expected: []ResourceKey{ - { - Type: "jobs", - Name: "job_1", - }, - { - Type: "jobs", - Name: "job_2", - }, - }, - }, + r.Pipelines = map[string]*resources.Pipeline{ + "pipeline_1": {}, } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - set := NewResourceKeySet() - - err := set.AddPattern(tc.pattern, tc.root) - - assert.NoError(t, err) - assert.ElementsMatch(t, tc.expected, set.ToArray()) - }) - } + all := NewResourceKeySet() + all.AddResources(&r) + assert.ElementsMatch(t, []ResourceKey{ + {Type: "jobs", Name: "job_1"}, + {Type: "jobs", Name: "job_2"}, + {Type: "pipelines", Name: "pipeline_1"}, + }, all.ToArray()) + + jobKeys := NewResourceKeySet() + jobKeys.AddResourcesOfType(&r, "jobs") + assert.ElementsMatch(t, []ResourceKey{ + {Type: "jobs", Name: "job_1"}, + {Type: "jobs", Name: "job_2"}, + }, jobKeys.ToArray()) } func TestResourceKeySet_AddResourceKey(t *testing.T) { diff --git a/bundle/config/mutator/resourcemutator/resource_mutator.go b/bundle/config/mutator/resourcemutator/resource_mutator.go index 2262ad6c0a5..eff0ac03aaa 100644 --- a/bundle/config/mutator/resourcemutator/resource_mutator.go +++ b/bundle/config/mutator/resourcemutator/resource_mutator.go @@ -2,17 +2,14 @@ package resourcemutator import ( "context" - "errors" - "fmt" "github.com/databricks/cli/bundle/config/mutator" "github.com/databricks/cli/bundle/config/validate" - "github.com/databricks/cli/libs/dyn/merge" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/libs/dyn" ) // When a new resource is added to configuration, we apply bundle @@ -46,64 +43,12 @@ func applyInitializeMutators(ctx context.Context, b *bundle.Bundle) { return } - defaults := []struct { - pattern string - value any - }{ - {"resources.dashboards.*.parent_path", b.Config.Workspace.ResourcePath}, - {"resources.dashboards.*.embed_credentials", false}, - {"resources.genie_spaces.*.parent_path", b.Config.Workspace.ResourcePath}, - {"resources.volumes.*.volume_type", "MANAGED"}, - - {"resources.alerts.*.parent_path", b.Config.Workspace.ResourcePath}, - - // Jobs: - - // The defaults are the same as for terraform provider latest version (v1.75.0) - // https://github.com/databricks/terraform-provider-databricks/blob/v1.75.0/jobs/resource_job.go#L532 - {"resources.jobs.*.name", "Untitled"}, - {"resources.jobs.*.max_concurrent_runs", 1}, - {"resources.jobs.*.schedule.pause_status", "UNPAUSED"}, - {"resources.jobs.*.trigger.pause_status", "UNPAUSED"}, - {"resources.jobs.*.continuous.pause_status", "UNPAUSED"}, - - // Enable queueing for jobs by default, following the behavior from API 2.2+. - // As of 2024-04, we're still using API 2.1 which has queueing disabled by default. - {"resources.jobs.*.queue", map[string]dyn.Value{ - "enabled": dyn.V(true), - }}, - - // https://github.com/databricks/terraform-provider-databricks/blob/v1.75.0/clusters/resource_cluster.go - {"resources.jobs.*.job_clusters[*].new_cluster.workload_type.clients.notebooks", true}, - {"resources.jobs.*.job_clusters[*].new_cluster.workload_type.clients.jobs", true}, - - // Pipelines (same as terraform) - // https://github.com/databricks/terraform-provider-databricks/blob/v1.75.0/pipelines/resource_pipeline.go#L253 - {"resources.pipelines.*.edition", "ADVANCED"}, - {"resources.pipelines.*.channel", "CURRENT"}, - - // SqlWarehouses (same as terraform) - // https://github.com/databricks/terraform-provider-databricks/blob/v1.75.0/sql/resource_sql_endpoint.go#L59 - {"resources.sql_warehouses.*.auto_stop_mins", 120}, - {"resources.sql_warehouses.*.enable_photon", true}, - {"resources.sql_warehouses.*.max_num_clusters", 1}, - {"resources.sql_warehouses.*.spot_instance_policy", "COST_OPTIMIZED"}, - - // Apps: - {"resources.apps.*.description", ""}, - - // Clusters (same as terraform) - // https://github.com/databricks/terraform-provider-databricks/blob/v1.75.0/clusters/resource_cluster.go#L315 - {"resources.clusters.*.autotermination_minutes", 60}, - {"resources.clusters.*.workload_type.clients.notebooks", true}, - {"resources.clusters.*.workload_type.clients.jobs", true}, + // Set the default values of resource fields that are not set. + if err := applyResourceDefaults(b); err != nil { + logdiag.LogError(ctx, err) } - - for _, defaultDef := range defaults { - bundle.SetDefault(ctx, b, defaultDef.pattern, defaultDef.value) - if logdiag.HasError(ctx) { - return - } + if logdiag.HasError(ctx) { + return } bundle.ApplySeqContext(ctx, b, @@ -220,17 +165,7 @@ func NormalizeAndInitializeResources( return } - var snapshot dyn.Value - - err := b.Config.Mutate(func(root dyn.Value) (dyn.Value, error) { - snapshot = root - - return selectResources(root, addedResources) - }) - if err != nil { - logdiag.LogError(ctx, fmt.Errorf("failed to select resources: %w", err)) - return - } + restore := selectResources(b, addedResources) applyNormalizeMutators(ctx, b) if logdiag.HasError(ctx) { @@ -242,13 +177,8 @@ func NormalizeAndInitializeResources( return } - // after mutators, we merge updated resources back to snapshot to preserve non-selected resources - err = b.Config.Mutate(func(root dyn.Value) (dyn.Value, error) { - return mergeResources(root, snapshot) - }) - if err != nil { - logdiag.LogError(ctx, fmt.Errorf("failed to merge resources: %w", err)) - } + // after mutators, we merge updated resources back to the snapshot to preserve non-selected resources + restore() } // NormalizeResources normalizes resources specified resources, @@ -262,17 +192,7 @@ func NormalizeResources( return } - var snapshot dyn.Value - - err := b.Config.Mutate(func(root dyn.Value) (dyn.Value, error) { - snapshot = root - - return selectResources(root, updatedResources) - }) - if err != nil { - logdiag.LogError(ctx, fmt.Errorf("failed to select resources: %w", err)) - return - } + restore := selectResources(b, updatedResources) applyNormalizeMutators(ctx, b) if logdiag.HasError(ctx) { @@ -290,129 +210,32 @@ func NormalizeResources( return } - // after mutators, we merge updated resources back to snapshot to preserve non-selected resources - err = b.Config.Mutate(func(root dyn.Value) (dyn.Value, error) { - return mergeResources(root, snapshot) - }) - if err != nil { - logdiag.LogError(ctx, fmt.Errorf("failed to merge resources: %w", err)) - } -} - -// selectResources returns bundle configuration with resources only present in resourcePaths. -func selectResources(root dyn.Value, resourcePaths ResourceKeySet) (dyn.Value, error) { - resourcesKeyString := "resources" - resourcesPath := dyn.NewPath(dyn.Key(resourcesKeyString)) - - newRoot := root - var err error - - // remove resource types that are not in resourcePaths - newRoot, err = dyn.MapByPath( - newRoot, - resourcesPath, - func(p dyn.Path, resources dyn.Value) (dyn.Value, error) { - return merge.Select(resources, resourcePaths.Types()) - }, - ) - if err != nil { - return dyn.InvalidValue, err - } - - // for each resource type, remove resources by name - for _, resourceType := range resourcePaths.Types() { - resourceTypePath := resourcesPath.Append(dyn.Key(resourceType)) - - newRoot, err = dyn.MapByPath( - newRoot, - resourceTypePath, - func(p dyn.Path, resource dyn.Value) (dyn.Value, error) { - return merge.Select(resource, resourcePaths.Names(resourceType)) - }, - ) - if err != nil { - return dyn.InvalidValue, err - } - } - - return newRoot, err + // after mutators, we merge updated resources back to the snapshot to preserve non-selected resources + restore() } -// mergeResources returns bundle configuration by merging all resources from src into dst, -// overriding existing resources if they exist. -func mergeResources(src, dst dyn.Value) (dyn.Value, error) { - resourcesKey := dyn.Key("resources") - - newDst := dst - - // merge 'resources..' - _, err := dyn.MapByPattern( - src, - dyn.NewPattern(resourcesKey, dyn.AnyKey(), dyn.AnyKey()), - func(path dyn.Path, v dyn.Value) (dyn.Value, error) { - // if parent 'resources.' doesn't exist, handle it on the next pass - updated, _ := dyn.SetByPath(newDst, path, v) - if !updated.IsValid() { - return v, nil - } else { - newDst = updated - } - - return v, nil - }, - ) - if err != nil { - return newDst, err - } - - // merge 'resources.' - _, err = dyn.MapByPattern( - src, - dyn.NewPattern(resourcesKey, dyn.AnyKey()), - func(path dyn.Path, v dyn.Value) (dyn.Value, error) { - // if already exists, we already handled it in the previous pass - existing, _ := dyn.GetByPath(newDst, path) - if existing.IsValid() { - return v, nil - } - - // if parent 'resources' doesn't exist, handle it on the next pass - updated, _ := dyn.SetByPath(newDst, path, v) - if !updated.IsValid() { - return v, nil - } else { - newDst = updated - return v, nil +// selectResources removes all resources except the ones in resourceKeys from the bundle +// configuration. It returns a function that restores the removed resources: it puts the +// configuration back to what it was before, except for the selected resources, which take +// the values they have at that point. Any other changes to the configuration are discarded. +func selectResources(b *bundle.Bundle, resourceKeys ResourceKeySet) func() { + // A shallow copy of the configuration: the resources maps are replaced below, not + // modified, so the copy keeps all resources. + snapshot := b.Config + b.Config.Resources = b.Config.Resources.Select(resourceKeys) + + return func() { + updated := b.Config + b.Config = snapshot + + for resourceType, names := range resourceKeys { + for name := range names { + // Keep the resource that was updated, it may have more than the configuration describes. + if !b.Config.Resources.CopyResource(&updated.Resources, resourceType, name) { + continue + } + b.Config.CopyMetadata(&updated, structpath.NewPath(nil, "resources", resourceType, name)) } - }, - ) - if err != nil { - return newDst, err - } - - // merge 'resources' - _, err = dyn.MapByPattern( - src, - dyn.NewPattern(resourcesKey), - func(path dyn.Path, v dyn.Value) (dyn.Value, error) { - // if already exists, we already handled it in the previous pass - existing, _ := dyn.GetByPath(newDst, path) - if existing.IsValid() { - return v, nil - } - - updated, _ := dyn.SetByPath(newDst, path, v) - if !updated.IsValid() { - return v, errors.New("failed to update resources") - } else { - newDst = updated - return v, nil - } - }, - ) - if err != nil { - return newDst, err + } } - - return newDst, nil } diff --git a/bundle/config/mutator/resourcemutator/resource_mutator_test.go b/bundle/config/mutator/resourcemutator/resource_mutator_test.go index 42aee5bf883..d95020ed113 100644 --- a/bundle/config/mutator/resourcemutator/resource_mutator_test.go +++ b/bundle/config/mutator/resourcemutator/resource_mutator_test.go @@ -3,114 +3,76 @@ package resourcemutator import ( "testing" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynassert" + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/libs/structs/structpath" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -type mergeResourcesTestCase struct { - name string - src dyn.Value - dst dyn.Value - expected dyn.Value +func newJob(name string) *resources.Job { + return &resources.Job{Name: name} } -func TestMergeResources(t *testing.T) { - job1 := dyn.V("job_1") - job2 := dyn.V("job_2") - job3 := dyn.V("job_3") - - testCases := []mergeResourcesTestCase{ - { - name: "add resources to empty bundle (1)", - src: mapOf("resources", mapOf("jobs", mapOf("job_1", job1))), - dst: mapOf("resources", emptyMap()), - expected: mapOf("resources", mapOf("jobs", mapOf("job_1", job1))), - }, - { - name: "add resources to empty bundle (2)", - src: mapOf("resources", mapOf("jobs", mapOf("job_1", job1))), - dst: emptyMap(), - expected: mapOf("resources", mapOf("jobs", mapOf("job_1", job1))), - }, - { - name: "add new resource", - src: mapOf("resources", mapOf("jobs", mapOf("job_1", job1))), - dst: mapOf("resources", mapOf("jobs", mapOf("job_2", job2))), - expected: mapOf("resources", mapOf("jobs", mapOf2("job_1", job1, "job_2", job2))), - }, - { - name: "override resource", - src: mapOf("resources", mapOf("jobs", mapOf("job_1", job3))), - dst: mapOf("resources", mapOf("jobs", mapOf2("job_1", job1, "job_2", job2))), - expected: mapOf("resources", mapOf("jobs", mapOf2("job_1", job3, "job_2", job2))), +func TestSelectResources(t *testing.T) { + job1 := newJob("job_1") + job2 := newJob("job_2") + + b := &bundle.Bundle{Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{"job_1": job1, "job_2": job2}, + Pipelines: map[string]*resources.Pipeline{"pipeline_1": {}}, }, - } + }} - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actual, err := mergeResources(tc.src, tc.dst) + keys := NewResourceKeySet() + keys.AddResourceKey(ResourceKey{Type: "jobs", Name: "job_1"}) - assert.NoError(t, err) - dynassert.Equal(t, tc.expected, actual) - }) - } -} + restore := selectResources(b, keys) + assert.Equal(t, map[string]*resources.Job{"job_1": job1}, b.Config.Resources.Jobs) + assert.Nil(t, b.Config.Resources.Pipelines) -type selectResourcesTestCase struct { - name string - config dyn.Value - resourcePaths []ResourceKey - expected dyn.Value + restore() + assert.Equal(t, map[string]*resources.Job{"job_1": job1, "job_2": job2}, b.Config.Resources.Jobs) + assert.Len(t, b.Config.Resources.Pipelines, 1) } -func TestSelectResources(t *testing.T) { - job1 := dyn.V("job_1") - job2 := dyn.V("job_2") - - testCases := []selectResourcesTestCase{ - { - name: "extract resources", - config: mapOf("resources", mapOf("jobs", mapOf2("job_1", job1, "job_2", job2))), - resourcePaths: []ResourceKey{ - { - Type: "jobs", - Name: "job_1", - }, - }, - expected: mapOf("resources", mapOf("jobs", mapOf("job_1", job1))), - }, - { - name: "extract no resources", - config: mapOf("resources", mapOf("jobs", mapOf2("job_1", job1, "job_2", job2))), - resourcePaths: []ResourceKey{}, - expected: mapOf("resources", emptyMap()), +func TestSelectResourcesRestoreMergesUpdates(t *testing.T) { + b := &bundle.Bundle{Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{"job_1": newJob("job_1"), "job_2": newJob("job_2")}, }, - } + }} + b.Config.Bundle.Name = "before" - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - resourceKeySet := NewResourceKeySet() - for _, key := range tc.resourcePaths { - resourceKeySet.AddResourceKey(key) - } + keys := NewResourceKeySet() + keys.AddResourceKey(ResourceKey{Type: "jobs", Name: "job_1"}) - actual, err := selectResources(tc.config, resourceKeySet) + restore := selectResources(b, keys) - assert.NoError(t, err) - assert.Equal(t, tc.expected, actual) - }) - } -} + // Changes to selected resources are kept, other changes are discarded. + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.job_1.name"), "updated")) + b.Config.Bundle.Name = "after" -func mapOf(key string, value dyn.Value) dyn.Value { - return dyn.V(map[string]dyn.Value{key: value}) + restore() + assert.Equal(t, "updated", b.Config.Resources.Jobs["job_1"].Name) + assert.Equal(t, "job_2", b.Config.Resources.Jobs["job_2"].Name) + assert.Equal(t, "before", b.Config.Bundle.Name) } -func mapOf2(k0 string, v0 dyn.Value, k1 string, v1 dyn.Value) dyn.Value { - return dyn.V(map[string]dyn.Value{k0: v0, k1: v1}) -} +func TestSelectResourcesRestoreKeepsResourceType(t *testing.T) { + b := &bundle.Bundle{Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{"job_1": newJob("job_1")}, + }, + }} + + keys := NewResourceKeySet() + keys.AddResourceKey(ResourceKey{Type: "jobs", Name: "job_1"}) -func emptyMap() dyn.Value { - return dyn.V(map[string]dyn.Value{}) + restore := selectResources(b, keys) + restore() + assert.Equal(t, "job_1", b.Config.Resources.Jobs["job_1"].Name) + assert.Nil(t, b.Config.Resources.Pipelines) } diff --git a/bundle/config/mutator/resourcemutator/run_as.go b/bundle/config/mutator/resourcemutator/run_as.go index 65279c7f212..21751e32813 100644 --- a/bundle/config/mutator/resourcemutator/run_as.go +++ b/bundle/config/mutator/resourcemutator/run_as.go @@ -8,7 +8,6 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/pipelines" @@ -220,17 +219,17 @@ func (m *setRunAs) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { b.Metrics.AddBoolValue("experimental.use_legacy_run_as", b.Config.Experimental != nil && b.Config.Experimental.UseLegacyRunAs) // Track whether top level run_as is set. - b.Metrics.AddBoolValue("run_as_set", b.Config.Value().Get("run_as").Kind() != dyn.KindInvalid) + b.Metrics.AddBoolValue("run_as_set", b.Config.RunAs != nil) // Mutator is a no-op if run_as is not specified in the bundle - if b.Config.Value().Get("run_as").Kind() == dyn.KindInvalid { + if b.Config.RunAs == nil { return nil } // User has opted to use the legacy behavior of run_as with the // experimental.use_legacy_run_as flag. if b.Config.Experimental != nil && b.Config.Experimental.UseLegacyRunAs { - if b.Config.Value().Get("run_as").Get("group_name").Kind() != dyn.KindInvalid { + if b.Config.RunAs.GroupName != "" || slices.Contains(b.Config.RunAs.ForceSendFields, "GroupName") { return diag.Errorf("run_as.group_name is not supported with experimental.use_legacy_run_as; disable experimental.use_legacy_run_as to use a group identity") } setPipelineOwnersToRunAsIdentity(b) diff --git a/bundle/config/mutator/resourcemutator/run_as_test.go b/bundle/config/mutator/resourcemutator/run_as_test.go index 200939f2dd0..dc7d2f9c862 100644 --- a/bundle/config/mutator/resourcemutator/run_as_test.go +++ b/bundle/config/mutator/resourcemutator/run_as_test.go @@ -11,8 +11,6 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/pipelines" @@ -23,18 +21,15 @@ import ( func allResourceTypes(t *testing.T) []string { // Compute supported resource types based on the `Resources{}` struct. - r := &config.Resources{} - rv, err := convert.FromTyped(r, dyn.NilValue) - require.NoError(t, err) - normalized, _ := convert.Normalize(r, rv, convert.IncludeMissingFields) var resourceTypes []string - for _, k := range normalized.MustMap().Keys() { - resourceTypes = append(resourceTypes, k.MustString()) + for f := range reflect.TypeFor[config.Resources]().Fields() { + name, _, _ := strings.Cut(f.Tag.Get("json"), ",") + resourceTypes = append(resourceTypes, name) } slices.Sort(resourceTypes) // Assert the total list of resource supported, as a sanity check that using - // the dyn library gives us the correct list of all resources supported. Please + // the struct gives us the correct list of all resources supported. Please // also update this check when adding a new resource require.Equal( t, []string{ @@ -300,14 +295,6 @@ func TestRunAsErrorForUnsupportedResources(t *testing.T) { }, } - v, err := convert.FromTyped(base, dyn.NilValue) - require.NoError(t, err) - - // Define top level resources key in the bundle configuration. - // This is not part of the typed configuration, so we need to add it manually. - v, err = dyn.Set(v, "resources", dyn.V(map[string]dyn.Value{})) - require.NoError(t, err) - for _, rt := range allResourceTypes(t) { // Skip allowed resources if slices.Contains(allowList, rt) { @@ -316,23 +303,16 @@ func TestRunAsErrorForUnsupportedResources(t *testing.T) { // Add an instance of the resource type that is not on the allow list to // the bundle configuration. - nv, err := dyn.SetByPath(v, dyn.NewPath(dyn.Key("resources"), dyn.Key(rt)), dyn.V(map[string]dyn.Value{ - "foo": dyn.V(map[string]dyn.Value{ - "path": dyn.V("bar"), - }), - })) - require.NoError(t, err) - - // Get back typed configuration from the newly created invalid bundle configuration. - r := &config.Root{} - err = convert.ToTyped(r, nv) - require.NoError(t, err) + r, diags := config.LoadFromBytes("databricks.yml", fmt.Appendf(nil, "resources:\n %s:\n foo:\n path: bar\n", rt)) + require.NoError(t, diags.Error()) + r.Workspace = base.Workspace + r.RunAs = base.RunAs // Assert this invalid bundle configuration fails validation. b := &bundle.Bundle{ Config: *r, } - diags := bundle.Apply(t.Context(), b, SetRunAs()) + diags = bundle.Apply(t.Context(), b, SetRunAs()) require.Error(t, diags.Error()) assert.Contains(t, diags.Error().Error(), "do not support a setting a run_as user that is different from the owner.\n"+ "Current identity: alice. Run as identity: bob.\n"+ @@ -354,14 +334,6 @@ func TestRunAsNoErrorForSupportedResources(t *testing.T) { }, } - v, err := convert.FromTyped(base, dyn.NilValue) - require.NoError(t, err) - - // Define top level resources key in the bundle configuration. - // This is not part of the typed configuration, so we need to add it manually. - v, err = dyn.Set(v, "resources", dyn.V(map[string]dyn.Value{})) - require.NoError(t, err) - for _, rt := range allResourceTypes(t) { // Skip unsupported resources if !slices.Contains(allowList, rt) { @@ -370,23 +342,16 @@ func TestRunAsNoErrorForSupportedResources(t *testing.T) { // Add an instance of the resource type that is not on the allow list to // the bundle configuration. - nv, err := dyn.SetByPath(v, dyn.NewPath(dyn.Key("resources"), dyn.Key(rt)), dyn.V(map[string]dyn.Value{ - "foo": dyn.V(map[string]dyn.Value{ - "name": dyn.V("bar"), - }), - })) - require.NoError(t, err) - - // Get back typed configuration from the newly created invalid bundle configuration. - r := &config.Root{} - err = convert.ToTyped(r, nv) - require.NoError(t, err) + r, diags := config.LoadFromBytes("databricks.yml", fmt.Appendf(nil, "resources:\n %s:\n foo:\n name: bar\n", rt)) + require.NoError(t, diags.Error()) + r.Workspace = base.Workspace + r.RunAs = base.RunAs // Assert this configuration passes validation. b := &bundle.Bundle{ Config: *r, } - diags := bundle.Apply(t.Context(), b, SetRunAs()) + diags = bundle.Apply(t.Context(), b, SetRunAs()) require.NoError(t, diags.Error()) } } @@ -396,7 +361,8 @@ func TestRunAsIdentities(t *testing.T) { runAs string wantError bool }{ - {`null`, true}, + // A null run_as is the same as not specifying it. + {`null`, false}, {`{}`, true}, {`{user_name: ""}`, true}, {`{service_principal_name: ""}`, true}, @@ -455,14 +421,14 @@ func TestRunAsGroupResources(t *testing.T) { r, diags := config.LoadFromBytes("databricks.yml", []byte(yaml)) require.NoError(t, diags.Error()) b := &bundle.Bundle{Config: *r} - before := b.Config.Value().Get("resources") + before := b.Config.View().Get("resources").AsAny() diags = bundle.Apply(t.Context(), b, SetRunAs()) if tc.wantError != "" { require.Error(t, diags.Error()) assert.Contains(t, diags.Error().Error(), tc.wantError) } else { require.NoError(t, diags.Error()) - assert.Equal(t, before, b.Config.Value().Get("resources")) + assert.Equal(t, before, b.Config.View().Get("resources").AsAny()) } }) } diff --git a/bundle/config/mutator/rewrite_sync_paths.go b/bundle/config/mutator/rewrite_sync_paths.go index 228c5484def..1aab844c11b 100644 --- a/bundle/config/mutator/rewrite_sync_paths.go +++ b/bundle/config/mutator/rewrite_sync_paths.go @@ -2,13 +2,12 @@ package mutator import ( "context" - "fmt" "path/filepath" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) type rewriteSyncPaths struct{} @@ -21,73 +20,52 @@ func (m *rewriteSyncPaths) Name() string { return "RewriteSyncPaths" } -// makeRelativeTo returns a dyn.MapFunc that joins the relative path -// of the file it was defined in w.r.t. the bundle root path, with -// the contents of the string node. +// makeRelativeTo joins the relative path of the file the value was defined in +// (loc) w.r.t. the bundle root path, with the value s. // // For example: // - The bundle root is /foo -// - The configuration file that defines the string node is at /foo/bar/baz.yml -// - The string node contains "somefile.*" +// - The configuration file that defines the value is at /foo/bar/baz.yml +// - The value is "somefile.*" // // Then the resulting value will be "bar/somefile.*". -func (m *rewriteSyncPaths) makeRelativeTo(root string) dyn.MapFunc { - return func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - dir := filepath.Dir(v.Location().File) - rel, err := filepath.Rel(root, dir) +func (m *rewriteSyncPaths) makeRelativeTo(root string, loc diag.Location, s string) (string, error) { + rel, err := filepath.Rel(root, filepath.Dir(loc.File)) + if err != nil { + return "", err + } + return filepath.Join(rel, s), nil +} + +// rewrite rewrites the sync paths in values (the field key of sync) relative to the +// bundle root, with Unix-style slashes if toSlash. +func (m *rewriteSyncPaths) rewrite(b *bundle.Bundle, key string, values []string, toSlash bool) error { + for i := range values { + loc := b.Config.GetLocationOfExact(structpath.NewPath(nil, "sync", key, i)) + path, err := m.makeRelativeTo(b.BundleRootPath, loc, values[i]) if err != nil { - return dyn.InvalidValue, err + return err } - - return dyn.NewValue(filepath.Join(rel, v.MustString()), v.Locations()), nil + if toSlash { + path = filepath.ToSlash(path) + } + values[i] = path } + return nil } func (m *rewriteSyncPaths) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.Map(v, "sync", func(_ dyn.Path, v dyn.Value) (nv dyn.Value, err error) { - v, err = dyn.Map(v, "paths", dyn.Foreach(m.makeRelativeTo(b.BundleRootPath))) - if err != nil { - return dyn.InvalidValue, err - } - - makeRelativeFn := m.makeRelativeTo(b.BundleRootPath) - - // Makes include and exclude paths relative to the bundle root first. - // Then converts them to use Unix-style slashes. - // This is required for the ignore.GitIgnore we use in libs/fileset to work correctly. - v, err = dyn.Map(v, "include", dyn.Foreach(func(p dyn.Path, val dyn.Value) (dyn.Value, error) { - relPath, err := makeRelativeFn(p, val) - if err != nil { - return dyn.InvalidValue, err - } - str, ok := relPath.AsString() - if !ok { - return dyn.InvalidValue, fmt.Errorf("expected string value but got %s", relPath.Kind()) - } - return dyn.NewValue(filepath.ToSlash(str), relPath.Locations()), nil - })) - if err != nil { - return dyn.InvalidValue, err - } - - v, err = dyn.Map(v, "exclude", dyn.Foreach(func(p dyn.Path, val dyn.Value) (dyn.Value, error) { - relPath, err := makeRelativeFn(p, val) - if err != nil { - return dyn.InvalidValue, err - } - str, ok := relPath.AsString() - if !ok { - return dyn.InvalidValue, fmt.Errorf("expected string value but got %s", relPath.Kind()) - } - return dyn.NewValue(filepath.ToSlash(str), relPath.Locations()), nil - })) - if err != nil { - return dyn.InvalidValue, err - } - return v, nil - }) - }) + // Makes include and exclude paths relative to the bundle root first. + // Then converts them to use Unix-style slashes. + // This is required for the ignore.GitIgnore we use in libs/fileset to work correctly. + sync := &b.Config.Sync + err := m.rewrite(b, "paths", sync.Paths, false) + if err == nil { + err = m.rewrite(b, "include", sync.Include, true) + } + if err == nil { + err = m.rewrite(b, "exclude", sync.Exclude, true) + } return diag.FromErr(err) } diff --git a/bundle/config/mutator/rewrite_workspace_prefix.go b/bundle/config/mutator/rewrite_workspace_prefix.go index 30914fd149e..4546badd6ea 100644 --- a/bundle/config/mutator/rewrite_workspace_prefix.go +++ b/bundle/config/mutator/rewrite_workspace_prefix.go @@ -6,8 +6,9 @@ import ( "strings" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config/walk" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) type rewriteWorkspacePrefix struct{} @@ -44,42 +45,28 @@ func (m *rewriteWorkspacePrefix) Apply(ctx context.Context, b *bundle.Bundle) di {"/Workspace${workspace.resource_path}", "${workspace.resource_path}"}, } - err := b.Config.Mutate(func(root dyn.Value) (dyn.Value, error) { - // Walk through the bundle configuration, check all the string leafs and - // see if any of the prefixes are used in the remote path. - return dyn.Walk(root, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - vv, ok := v.AsString() - if !ok { - return v, nil + // Walk through the bundle configuration, check all the string leafs and + // see if any of the prefixes are used in the remote path. + walk.Strings(&b.Config, walk.WithReferences, func(n *walk.Node, vv string) (string, bool) { + newPath := vv + for _, rewrite := range paths { + if !strings.Contains(newPath, rewrite.pattern) { + continue } - newPath := vv - for _, rewrite := range paths { - if !strings.Contains(newPath, rewrite.pattern) { - continue - } + newPath = strings.ReplaceAll(newPath, rewrite.pattern, rewrite.replacement) + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: fmt.Sprintf("substring %q found in %q. Please update this to %q.", rewrite.pattern, vv, newPath), + Detail: "For more information, please refer to: https://docs.databricks.com/en/release-notes/dev-tools/bundles.html#workspace-paths", + Locations: n.Locations().Get(), + Paths: []*structpath.PathNode{n.Path()}, + }) + } - newPath = strings.ReplaceAll(newPath, rewrite.pattern, rewrite.replacement) - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: fmt.Sprintf("substring %q found in %q. Please update this to %q.", rewrite.pattern, vv, newPath), - Detail: "For more information, please refer to: https://docs.databricks.com/en/release-notes/dev-tools/bundles.html#workspace-paths", - Locations: v.Locations(), - Paths: dyn.ToStructPaths(p), - }) - } - - if newPath == vv { - return v, nil - } - - // Remove the workspace prefix from the string. - return dyn.NewValue(newPath, v.Locations()), nil - }) + // Remove the workspace prefix from the string. + return newPath, newPath != vv }) - if err != nil { - return diag.FromErr(err) - } return diags } diff --git a/bundle/config/mutator/select_target.go b/bundle/config/mutator/select_target.go index 43764a9ee38..834255463db 100644 --- a/bundle/config/mutator/select_target.go +++ b/bundle/config/mutator/select_target.go @@ -9,6 +9,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/structs/structpath" ) type selectTarget struct { @@ -59,8 +60,12 @@ func (m *selectTarget) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnosti // Cleanup the original targets and environments sections since they // show up in the JSON output of the 'summary' and 'validate' commands. - b.Config.Targets = nil - b.Config.Environments = nil + // Delete (not just clear) them so their locations go too. + for _, k := range []string{"targets", "environments"} { + if err := b.Config.Delete(structpath.NewPath(nil, k)); err != nil { + return diag.FromErr(err) + } + } return nil } diff --git a/bundle/config/mutator/set_variables.go b/bundle/config/mutator/set_variables.go index f8f3b35394c..be901afb9e8 100644 --- a/bundle/config/mutator/set_variables.go +++ b/bundle/config/mutator/set_variables.go @@ -3,15 +3,18 @@ package mutator import ( "context" "fmt" + "maps" "os" "path/filepath" + "slices" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/variable" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/jsonloader" "github.com/databricks/cli/libs/env" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) const bundleVarPrefix = "BUNDLE_VAR_" @@ -30,90 +33,97 @@ func getDefaultVariableFilePath(target string) string { return ".databricks/bundle/" + target + "/variable-overrides.json" } -func setVariable(ctx context.Context, v dyn.Value, variable *variable.Variable, name string, fileDefault dyn.Value) (dyn.Value, error) { +func setVariable(ctx context.Context, cfg *config.Root, variable *variable.Variable, name string, fileDefault structvar.View) error { // case: variable already has value initialized, so skip if variable.HasValue() { - return v, nil + return nil } + variablePath := structpath.NewPath(nil, "variables", name) + valuePath := structpath.NewStringKey(variablePath, "value") + // case: read and set variable value from process environment envVarName := bundleVarPrefix + name if val, ok := env.Lookup(ctx, envVarName); ok { if variable.IsComplex() { - return dyn.InvalidValue, fmt.Errorf(`setting via environment variables (%s) is not supported for complex variable %s`, envVarName, name) + return fmt.Errorf(`setting via environment variables (%s) is not supported for complex variable %s`, envVarName, name) } - v, err := dyn.Set(v, "value", dyn.V(val)) + err := cfg.Set(valuePath, val) if err != nil { - return dyn.InvalidValue, fmt.Errorf(`failed to assign value "%s" to variable %s from environment variable %s with error: %v`, val, name, envVarName, err) //nolint:errorlint // runs inside dyn.Map, which drops wrapped no-such-key errors + return fmt.Errorf(`failed to assign value "%s" to variable %s from environment variable %s with error: %v`, val, name, envVarName, err) //nolint:errorlint // runs inside dyn.Map, which drops wrapped no-such-key errors } - return v, nil + return nil } // case: Defined a variable for named lookup for a resource // It will be resolved later in ResolveResourceReferences mutator if variable.Lookup != nil { - return v, nil + return nil } // case: Set the variable to the default value from the variable file - if fileDefault.Kind() != dyn.KindInvalid && fileDefault.Kind() != dyn.KindNil { + if fileDefault.Kind() != structvar.KindInvalid && fileDefault.Kind() != structvar.KindNil { hasComplexType := variable.IsComplex() - hasComplexValue := fileDefault.Kind() == dyn.KindMap || fileDefault.Kind() == dyn.KindSequence + hasComplexValue := fileDefault.Kind() == structvar.KindMap || fileDefault.Kind() == structvar.KindSequence if hasComplexType && !hasComplexValue { - return dyn.InvalidValue, fmt.Errorf(`variable %s is of type complex, but the value in the variable file is not a complex type`, name) + return fmt.Errorf(`variable %s is of type complex, but the value in the variable file is not a complex type`, name) } if !hasComplexType && hasComplexValue { - return dyn.InvalidValue, fmt.Errorf(`variable %s is not of type complex, but the value in the variable file is a complex type`, name) + return fmt.Errorf(`variable %s is not of type complex, but the value in the variable file is a complex type`, name) } - v, err := dyn.Set(v, "value", fileDefault) + err := cfg.Assign(valuePath, fileDefault) if err != nil { - return dyn.InvalidValue, fmt.Errorf(`failed to assign default value from variable file to variable %s with error: %v`, name, err) //nolint:errorlint // runs inside dyn.Map, which drops wrapped no-such-key errors + return fmt.Errorf(`failed to assign default value from variable file to variable %s with error: %v`, name, err) //nolint:errorlint // runs inside dyn.Map, which drops wrapped no-such-key errors } - return v, nil + return nil } // case: Set the variable to its default value if variable.HasDefault() { - vDefault, err := dyn.Get(v, "default") + // Assign copies the default (maps and slices are resolved in place later) with + // its locations. + defaultLocs := cfg.LocationTree().Sub(structpath.NewStringKey(variablePath, "default")) + err := cfg.Assign(valuePath, structvar.NewView(&variable.Default, nil, defaultLocs)) if err != nil { - return dyn.InvalidValue, fmt.Errorf(`failed to get default value from config "%s" for variable %s with error: %v`, variable.Default, name, err) //nolint:errorlint // runs inside dyn.Map, which drops wrapped no-such-key errors + return fmt.Errorf(`failed to assign default value from config "%s" to variable %s with error: %w`, variable.Default, name, err) } - - v, err := dyn.Set(v, "value", vDefault) - if err != nil { - return dyn.InvalidValue, fmt.Errorf(`failed to assign default value from config "%s" to variable %s with error: %v`, variable.Default, name, err) //nolint:errorlint // runs inside dyn.Map, which drops wrapped no-such-key errors - } - return v, nil + return nil } // We should have had a value to set for the variable at this point. - return dyn.InvalidValue, fmt.Errorf(`no value assigned to required variable %s. Variables are usually assigned in databricks.yml, and they can be overridden using "--var", the %s environment variable, or %s`, name, bundleVarPrefix+name, getDefaultVariableFilePath("")) + return fmt.Errorf(`no value assigned to required variable %s. Variables are usually assigned in databricks.yml, and they can be overridden using "--var", the %s environment variable, or %s`, name, bundleVarPrefix+name, getDefaultVariableFilePath("")) } -func readVariablesFromFile(b *bundle.Bundle) (dyn.Value, diag.Diagnostics) { +func readVariablesFromFile(b *bundle.Bundle) (structvar.View, diag.Diagnostics) { var diags diag.Diagnostics filePath := filepath.Join(b.BundleRootPath, getDefaultVariableFilePath(b.Config.Bundle.Target)) if _, err := os.Stat(filePath); err != nil { - return dyn.InvalidValue, nil + return structvar.View{}, nil } f, err := os.ReadFile(filePath) if err != nil { - return dyn.InvalidValue, diag.FromErr(fmt.Errorf("failed to read variables file: %w", err)) + return structvar.View{}, diag.FromErr(fmt.Errorf("failed to read variables file: %w", err)) } - val, err := jsonloader.LoadJSON(f, filePath) + node, err := structvar.ParseJSON(filePath, f) + if err != nil { + return structvar.View{}, diag.FromErr(fmt.Errorf("failed to parse variables file %s: %w", filePath, err)) + } + var v any + decoded, _, err := structvar.DecodeYAMLNode(filePath, node, &v, nil) if err != nil { - return dyn.InvalidValue, diag.FromErr(fmt.Errorf("failed to parse variables file %s: %w", filePath, err)) + return structvar.View{}, diag.FromErr(fmt.Errorf("failed to parse variables file %s: %w", filePath, err)) } - if val.Kind() != dyn.KindMap { - return dyn.InvalidValue, diags.Append(diag.Diagnostic{ + val := decoded.View() + if val.Kind() != structvar.KindMap { + return structvar.View{}, diags.Append(diag.Diagnostic{ Severity: diag.Error, Summary: fmt.Sprintf("failed to parse variables file %s: invalid format", filePath), Detail: "Variables file must be a JSON object with the following format:\n{\"var1\": \"value1\", \"var2\": \"value2\"}", @@ -128,18 +138,14 @@ func (m *setVariables) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnos if diags.HasError() { return diags } - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.Map(v, "variables", dyn.Foreach(func(p dyn.Path, variable dyn.Value) (dyn.Value, error) { - name := p[1].Key() - v, ok := b.Config.Variables[name] - if !ok { - return dyn.InvalidValue, fmt.Errorf(`variable "%s" is not defined`, name) - } - - fileDefault, _ := dyn.Get(defaults, name) - return setVariable(ctx, variable, v, name, fileDefault) - })) - }) - - return diags.Extend(diag.FromErr(err)) + for _, name := range slices.Sorted(maps.Keys(b.Config.Variables)) { + v := b.Config.Variables[name] + fileDefault := defaults.Get(name) + err := setVariable(ctx, &b.Config, v, name, fileDefault) + if err != nil { + return diags.Extend(diag.FromErr(err)) + } + } + + return diags } diff --git a/bundle/config/mutator/set_variables_test.go b/bundle/config/mutator/set_variables_test.go index ecc67548017..977e7e94529 100644 --- a/bundle/config/mutator/set_variables_test.go +++ b/bundle/config/mutator/set_variables_test.go @@ -6,12 +6,16 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/variable" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" + "github.com/databricks/cli/libs/structs/structvar" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +// configWithVariable returns a configuration that has the variable with name "foo". +func configWithVariable(v *variable.Variable) *config.Root { + return &config.Root{Variables: map[string]*variable.Variable{"foo": v}} +} + func TestSetVariableFromProcessEnvVar(t *testing.T) { defaultVal := "default" variable := variable.Variable{ @@ -21,13 +25,9 @@ func TestSetVariableFromProcessEnvVar(t *testing.T) { // set value for variable as an environment variable t.Setenv("BUNDLE_VAR_foo", "process-env") - v, err := convert.FromTyped(variable, dyn.NilValue) - require.NoError(t, err) - - v, err = setVariable(t.Context(), v, &variable, "foo", dyn.NilValue) - require.NoError(t, err) + cfg := configWithVariable(&variable) - err = convert.ToTyped(&variable, v) + err := setVariable(t.Context(), cfg, &variable, "foo", structvar.View{}) require.NoError(t, err) assert.Equal(t, "process-env", variable.Value) } @@ -39,13 +39,9 @@ func TestSetVariableUsingDefaultValue(t *testing.T) { Default: defaultVal, } - v, err := convert.FromTyped(variable, dyn.NilValue) - require.NoError(t, err) - - v, err = setVariable(t.Context(), v, &variable, "foo", dyn.NilValue) - require.NoError(t, err) + cfg := configWithVariable(&variable) - err = convert.ToTyped(&variable, v) + err := setVariable(t.Context(), cfg, &variable, "foo", structvar.View{}) require.NoError(t, err) assert.Equal(t, "default", variable.Value) } @@ -61,13 +57,9 @@ func TestSetVariableWhenAlreadyAValueIsAssigned(t *testing.T) { // since a value is already assigned to the variable, it would not be overridden // by the default value - v, err := convert.FromTyped(variable, dyn.NilValue) - require.NoError(t, err) - - v, err = setVariable(t.Context(), v, &variable, "foo", dyn.NilValue) - require.NoError(t, err) + cfg := configWithVariable(&variable) - err = convert.ToTyped(&variable, v) + err := setVariable(t.Context(), cfg, &variable, "foo", structvar.View{}) require.NoError(t, err) assert.Equal(t, "assigned-value", variable.Value) } @@ -86,13 +78,9 @@ func TestSetVariableEnvVarValueDoesNotOverridePresetValue(t *testing.T) { // since a value is already assigned to the variable, it would not be overridden // by the value from environment - v, err := convert.FromTyped(variable, dyn.NilValue) - require.NoError(t, err) + cfg := configWithVariable(&variable) - v, err = setVariable(t.Context(), v, &variable, "foo", dyn.NilValue) - require.NoError(t, err) - - err = convert.ToTyped(&variable, v) + err := setVariable(t.Context(), cfg, &variable, "foo", structvar.View{}) require.NoError(t, err) assert.Equal(t, "assigned-value", variable.Value) } @@ -103,10 +91,9 @@ func TestSetVariablesErrorsIfAValueCouldNotBeResolved(t *testing.T) { } // fails because we could not resolve a value for the variable - v, err := convert.FromTyped(variable, dyn.NilValue) - require.NoError(t, err) + cfg := configWithVariable(&variable) - _, err = setVariable(t.Context(), v, &variable, "foo", dyn.NilValue) + err := setVariable(t.Context(), cfg, &variable, "foo", structvar.View{}) assert.ErrorContains(t, err, "no value assigned to required variable foo. Variables are usually assigned in databricks.yml, and they can be overridden using \"--var\", the BUNDLE_VAR_foo environment variable, or .databricks/bundle//variable-overrides.json") } @@ -153,9 +140,8 @@ func TestSetComplexVariablesViaEnvVariablesIsNotAllowed(t *testing.T) { // set value for variable as an environment variable t.Setenv("BUNDLE_VAR_foo", "process-env") - v, err := convert.FromTyped(variable, dyn.NilValue) - require.NoError(t, err) + cfg := configWithVariable(&variable) - _, err = setVariable(t.Context(), v, &variable, "foo", dyn.NilValue) + err := setVariable(t.Context(), cfg, &variable, "foo", structvar.View{}) assert.ErrorContains(t, err, "setting via environment variables (BUNDLE_VAR_foo) is not supported for complex variable foo") } diff --git a/bundle/config/mutator/sync_default_path.go b/bundle/config/mutator/sync_default_path.go index 16d4a4d6179..92c27488967 100644 --- a/bundle/config/mutator/sync_default_path.go +++ b/bundle/config/mutator/sync_default_path.go @@ -5,7 +5,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) type syncDefaultPath struct{} @@ -20,24 +20,9 @@ func (m *syncDefaultPath) Name() string { } func (m *syncDefaultPath) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - isset := false - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - pv, _ := dyn.Get(v, "sync.paths") - - // If the sync paths field is already set, do nothing. - // We know it is set if its value is either a nil or a sequence (empty or not). - if pv.Kind() == dyn.KindNil || pv.Kind() == dyn.KindSequence { - isset = true - } - - return v, nil - }) - if err != nil { - return diag.FromErr(err) - } - // If the sync paths field is already set, do nothing. - if isset { + // We know it is set if it is a sequence (empty or not) or a reference. + if b.Config.Sync.Paths != nil || b.Config.IsReferenceAt(structpath.NewPath(nil, "sync", "paths")) { return nil } diff --git a/bundle/config/mutator/sync_default_path_test.go b/bundle/config/mutator/sync_default_path_test.go index c4bd6a65af7..a85821b6cc2 100644 --- a/bundle/config/mutator/sync_default_path_test.go +++ b/bundle/config/mutator/sync_default_path_test.go @@ -7,8 +7,8 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structpath" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -28,22 +28,22 @@ func TestSyncDefaultPath_DefaultIfUnset(t *testing.T) { func TestSyncDefaultPath_SkipIfSet(t *testing.T) { tcases := []struct { name string - paths dyn.Value + paths any expect []string }{ { name: "nil", - paths: dyn.V(nil), + paths: nil, expect: []string{"."}, }, { name: "empty sequence", - paths: dyn.V([]dyn.Value{}), + paths: []string{}, expect: []string{}, }, { name: "non-empty sequence", - paths: dyn.V([]dyn.Value{dyn.V("something")}), + paths: []string{"something"}, expect: []string{"something"}, }, } @@ -58,17 +58,7 @@ func TestSyncDefaultPath_SkipIfSet(t *testing.T) { ctx := logdiag.InitContext(t.Context()) bundle.ApplyFuncContext(ctx, b, func(ctx context.Context, b *bundle.Bundle) { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - v, err := dyn.Set(v, "sync", dyn.V(dyn.NewMapping())) - if err != nil { - return dyn.InvalidValue, err - } - v, err = dyn.Set(v, "sync.paths", tcase.paths) - if err != nil { - return dyn.InvalidValue, err - } - return v, nil - }) + err := b.Config.Set(structpath.MustParsePath("sync.paths"), tcase.paths) require.NoError(t, err) }) require.False(t, logdiag.HasError(ctx)) diff --git a/bundle/config/mutator/translate_paths.go b/bundle/config/mutator/translate_paths.go index f44c7235043..ffcacd83164 100644 --- a/bundle/config/mutator/translate_paths.go +++ b/bundle/config/mutator/translate_paths.go @@ -18,8 +18,8 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/notebook" + "github.com/databricks/cli/libs/structs/structpath" ) // translateOptions control path translation behavior. @@ -300,37 +300,54 @@ func (t *translateContext) translateLocalRelativeWithPrefixPath(ctx context.Cont return localRelPath, nil } -func (t *translateContext) rewriteValue(ctx context.Context, p dyn.Path, v dyn.Value, dir string, opts translateOptions) (dyn.Value, error) { - out, err := t.rewritePath(ctx, dir, v.MustString(), opts) +// rewriteValue rewrites the path input found at p. It returns an empty string if the path was not rewritten. +func (t *translateContext) rewriteValue(ctx context.Context, p *structpath.PathNode, input, dir string, opts translateOptions) (string, error) { + out, err := t.rewritePath(ctx, dir, input, opts) if err != nil { if target, ok := errors.AsType[ErrIsNotebook](err); ok { - return dyn.InvalidValue, fmt.Errorf(`expected a file for "%s" but got a notebook: %w`, p, target) + return "", fmt.Errorf(`expected a file for "%s" but got a notebook: %w`, p, target) } if target, ok := errors.AsType[ErrIsNotNotebook](err); ok { - return dyn.InvalidValue, fmt.Errorf(`expected a notebook for "%s" but got a file: %w`, p, target) + return "", fmt.Errorf(`expected a notebook for "%s" but got a file: %w`, p, target) } - return dyn.InvalidValue, err + return "", err } - // If the path was not rewritten, return the original value. - if out == "" { - return v, nil + return out, nil +} + +// setRewritten stores the rewritten path in s (the value keeps its locations) unless +// the path was not rewritten. +func setRewritten(s *string, out string) { + if out != "" { + *s = out } +} - return dyn.NewValue(out, v.Locations()), nil +// rewriteAt rewrites the path value s found at p relative to the bundle root. +func (t *translateContext) rewriteAt(ctx context.Context, p *structpath.PathNode, s *string, opts translateOptions) error { + out, err := t.rewriteValue(ctx, p, *s, t.b.BundleRootPath, opts) + if err != nil { + return err + } + setRewritten(s, out) + return nil } -func applyTranslations(ctx context.Context, b *bundle.Bundle, t *translateContext, translations []func(context.Context, dyn.Value) (dyn.Value, error)) diag.Diagnostics { +func applyTranslations(ctx context.Context, b *bundle.Bundle, t *translateContext, translations []translateFunc) diag.Diagnostics { switch { case b.IsImmutableFolder(): // Reject an explicit workspace.file_path: immutable bundles set it // automatically to the content-addressed snapshot location. A user-supplied // value would be silently discarded during path translation, so we error early. - if loc := b.Config.GetLocation("workspace.file_path"); loc.File != "" { + // DefineDefaultWorkspacePaths sets the snapshot location, or root/files if + // immutable_folder was a reference not resolved yet; any other value is the user's. + // Clean: the default is joined before root_path's references are resolved. + if fp := path.Clean(b.Config.Workspace.FilePath); fp != resources.SnapshotFullPathRef+"/files" && fp != path.Join(b.Config.Workspace.RootPath, "files") { return diag.Diagnostics{{ Severity: diag.Error, Summary: "workspace.file_path cannot be configured when experimental.immutable_folder is enabled", - Locations: []diag.Location{loc}, + Locations: b.Config.GetLocations("workspace.file_path"), }} } t.remoteRoot = resources.SnapshotFullPathRef + "/files" @@ -340,18 +357,13 @@ func applyTranslations(ctx context.Context, b *bundle.Bundle, t *translateContex t.remoteRoot = t.b.Config.Workspace.FilePath } - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - var err error - for _, fn := range translations { - v, err = fn(ctx, v) - if err != nil { - return dyn.InvalidValue, err - } + for _, fn := range translations { + if err := fn(ctx, &b.Config); err != nil { + return diag.FromErr(err) } - return v, nil - }) + } - return diag.FromErr(err) + return nil } func (m *translatePaths) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { @@ -361,7 +373,7 @@ func (m *translatePaths) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagn skipLocalFileValidation: b.SkipLocalFileValidation, } - return applyTranslations(ctx, b, t, []func(context.Context, dyn.Value) (dyn.Value, error){ + return applyTranslations(ctx, b, t, []translateFunc{ t.applyJobTranslations(paths.VisitJobPaths, false), t.applyJobTranslations(paths.VisitJobLibrariesPaths, true), t.applyPipelineTranslations(paths.VisitPipelinePaths, false), @@ -378,7 +390,7 @@ func (m *translatePathsDashboards) Apply(ctx context.Context, b *bundle.Bundle) skipLocalFileValidation: b.SkipLocalFileValidation, } - return applyTranslations(ctx, b, t, []func(context.Context, dyn.Value) (dyn.Value, error){ + return applyTranslations(ctx, b, t, []translateFunc{ t.applyDashboardTranslations, t.applyGenieSpaceTranslations, }) diff --git a/bundle/config/mutator/translate_paths_apps.go b/bundle/config/mutator/translate_paths_apps.go index 8daef7e4857..27588b02571 100644 --- a/bundle/config/mutator/translate_paths_apps.go +++ b/bundle/config/mutator/translate_paths_apps.go @@ -3,20 +3,21 @@ package mutator import ( "context" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator/paths" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) -func (t *translateContext) applyAppsTranslations(ctx context.Context, v dyn.Value) (dyn.Value, error) { +func (t *translateContext) applyAppsTranslations(ctx context.Context, r *config.Root) error { // Convert the `source_code_path` field to a remote absolute path. // We use this path for app deployment to point to the source code. - return paths.VisitAppPaths(v, func(p dyn.Path, mode paths.TranslateMode, v dyn.Value) (dyn.Value, error) { + return paths.VisitAppPaths(r, func(p *structpath.PathNode, mode paths.TranslateMode, s *string) error { opts := translateOptions{ Mode: mode, } - return t.rewriteValue(ctx, p, v, t.b.BundleRootPath, opts) + return t.rewriteAt(ctx, p, s, opts) }) } diff --git a/bundle/config/mutator/translate_paths_artifacts.go b/bundle/config/mutator/translate_paths_artifacts.go index 51ae0d8f8f5..533ac9229cb 100644 --- a/bundle/config/mutator/translate_paths_artifacts.go +++ b/bundle/config/mutator/translate_paths_artifacts.go @@ -3,13 +3,14 @@ package mutator import ( "context" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator/paths" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) -func (t *translateContext) applyArtifactTranslations(ctx context.Context, v dyn.Value) (dyn.Value, error) { - return paths.VisitArtifactPaths(v, func(p dyn.Path, mode paths.TranslateMode, v dyn.Value) (dyn.Value, error) { +func (t *translateContext) applyArtifactTranslations(ctx context.Context, r *config.Root) error { + return paths.VisitArtifactPaths(r, func(p *structpath.PathNode, mode paths.TranslateMode, s *string) error { opts := translateOptions{ Mode: mode, @@ -18,6 +19,6 @@ func (t *translateContext) applyArtifactTranslations(ctx context.Context, v dyn. AllowPathOutsideSyncRoot: true, } - return t.rewriteValue(ctx, p, v, t.b.BundleRootPath, opts) + return t.rewriteAt(ctx, p, s, opts) }) } diff --git a/bundle/config/mutator/translate_paths_dashboards.go b/bundle/config/mutator/translate_paths_dashboards.go index 2e93ed3998f..fe095125a82 100644 --- a/bundle/config/mutator/translate_paths_dashboards.go +++ b/bundle/config/mutator/translate_paths_dashboards.go @@ -3,20 +3,21 @@ package mutator import ( "context" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator/paths" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) -func (t *translateContext) applyDashboardTranslations(ctx context.Context, v dyn.Value) (dyn.Value, error) { +func (t *translateContext) applyDashboardTranslations(ctx context.Context, r *config.Root) error { // Convert the `file_path` field to a local absolute path. // We load the file at this path and use its contents for the dashboard contents. - return paths.VisitDashboardPaths(v, func(p dyn.Path, mode paths.TranslateMode, v dyn.Value) (dyn.Value, error) { + return paths.VisitDashboardPaths(r, func(p *structpath.PathNode, mode paths.TranslateMode, s *string) error { opts := translateOptions{ Mode: mode, } - return t.rewriteValue(ctx, p, v, t.b.BundleRootPath, opts) + return t.rewriteAt(ctx, p, s, opts) }) } diff --git a/bundle/config/mutator/translate_paths_genie_spaces.go b/bundle/config/mutator/translate_paths_genie_spaces.go index 4e6d41f1cec..993204f8281 100644 --- a/bundle/config/mutator/translate_paths_genie_spaces.go +++ b/bundle/config/mutator/translate_paths_genie_spaces.go @@ -3,19 +3,20 @@ package mutator import ( "context" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator/paths" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) -func (t *translateContext) applyGenieSpaceTranslations(ctx context.Context, v dyn.Value) (dyn.Value, error) { +func (t *translateContext) applyGenieSpaceTranslations(ctx context.Context, r *config.Root) error { // Rewrite the `file_path` field to a path relative to the bundle sync root. // We load the file at this path and use its contents for the genie space contents. - return paths.VisitGenieSpacePaths(v, func(p dyn.Path, mode paths.TranslateMode, v dyn.Value) (dyn.Value, error) { + return paths.VisitGenieSpacePaths(r, func(p *structpath.PathNode, mode paths.TranslateMode, s *string) error { opts := translateOptions{ Mode: mode, } - return t.rewriteValue(ctx, p, v, t.b.BundleRootPath, opts) + return t.rewriteAt(ctx, p, s, opts) }) } diff --git a/bundle/config/mutator/translate_paths_jobs.go b/bundle/config/mutator/translate_paths_jobs.go index 2c94e21802b..6e14e70876b 100644 --- a/bundle/config/mutator/translate_paths_jobs.go +++ b/bundle/config/mutator/translate_paths_jobs.go @@ -4,18 +4,18 @@ import ( "context" "slices" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator/paths" - - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) type ( - visitFunc func(dyn.Value, paths.VisitFunc) (dyn.Value, error) - translateFunc func(ctx context.Context, v dyn.Value) (dyn.Value, error) + visitFunc func(*config.Root, paths.VisitFunc) error + translateFunc func(ctx context.Context, r *config.Root) error ) func (t *translateContext) applyJobTranslations(visitor visitFunc, allowOutsideSyncRoot bool) translateFunc { - return func(ctx context.Context, v dyn.Value) (dyn.Value, error) { + return func(ctx context.Context, r *config.Root) error { // Do not translate job task paths if using Git source var ignore []string for key, job := range t.b.Config.Resources.Jobs { @@ -24,12 +24,13 @@ func (t *translateContext) applyJobTranslations(visitor visitFunc, allowOutsideS } } - return visitor(v, func(p dyn.Path, mode paths.TranslateMode, v dyn.Value) (dyn.Value, error) { - key := p[2].Key() + return visitor(r, func(p *structpath.PathNode, mode paths.TranslateMode, s *string) error { + key := p.KeyAt(2) + input := *s // Skip path translation if the job is using git source. if slices.Contains(ignore, key) { - return v, nil + return nil } opts := translateOptions{ @@ -38,12 +39,13 @@ func (t *translateContext) applyJobTranslations(visitor visitFunc, allowOutsideS } // Handle path as if it's relative to the bundle root - nv, err := t.rewriteValue(ctx, p, v, t.b.BundleRootPath, opts) + nv, err := t.rewriteValue(ctx, p, input, t.b.BundleRootPath, opts) if err == nil { - return nv, nil + setRewritten(s, nv) + return nil } - return dyn.InvalidValue, err + return err }) } } diff --git a/bundle/config/mutator/translate_paths_pipelines.go b/bundle/config/mutator/translate_paths_pipelines.go index 07c2e58cd93..fe19d923ad8 100644 --- a/bundle/config/mutator/translate_paths_pipelines.go +++ b/bundle/config/mutator/translate_paths_pipelines.go @@ -3,26 +3,28 @@ package mutator import ( "context" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator/paths" - - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) func (t *translateContext) applyPipelineTranslations(visitor visitFunc, allowOutsideSyncRoot bool) translateFunc { - return func(ctx context.Context, v dyn.Value) (dyn.Value, error) { - return visitor(v, func(p dyn.Path, mode paths.TranslateMode, v dyn.Value) (dyn.Value, error) { + return func(ctx context.Context, r *config.Root) error { + return visitor(r, func(p *structpath.PathNode, mode paths.TranslateMode, s *string) error { + input := *s opts := translateOptions{ Mode: mode, AllowPathOutsideSyncRoot: allowOutsideSyncRoot, } // Handle path as if it's relative to the bundle root - nv, err := t.rewriteValue(ctx, p, v, t.b.BundleRootPath, opts) + nv, err := t.rewriteValue(ctx, p, input, t.b.BundleRootPath, opts) if err == nil { - return nv, nil + setRewritten(s, nv) + return nil } - return dyn.InvalidValue, err + return err }) } } diff --git a/bundle/config/mutator/translate_paths_test.go b/bundle/config/mutator/translate_paths_test.go index 8aaf9ce9c71..63569c0f747 100644 --- a/bundle/config/mutator/translate_paths_test.go +++ b/bundle/config/mutator/translate_paths_test.go @@ -15,7 +15,7 @@ import ( "github.com/databricks/cli/bundle/config/variable" "github.com/databricks/cli/bundle/internal/bundletest" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/vfs" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/databricks/databricks-sdk-go/service/jobs" @@ -855,10 +855,8 @@ func TestTranslatePathWithComplexVariables(t *testing.T) { ctx := t.Context() // Assign the variables to the dynamic configuration. bundle.ApplyFuncContext(ctx, b, func(ctx context.Context, b *bundle.Bundle) { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - p := dyn.MustPathFromString("resources.jobs.job.tasks[0]") - return dyn.SetByPath(v, p.Append(dyn.Key("libraries")), dyn.V("${var.cluster_libraries}")) - }) + p := structpath.MustParsePath("resources.jobs.job.tasks[0]") + err := b.Config.SetReference(structpath.NewStringKey(p, "libraries"), "${var.cluster_libraries}") require.NoError(t, err) }) diff --git a/bundle/config/mutator/validate_secret_value_is_variable.go b/bundle/config/mutator/validate_secret_value_is_variable.go index 1f979e15f74..b8f7108aa6e 100644 --- a/bundle/config/mutator/validate_secret_value_is_variable.go +++ b/bundle/config/mutator/validate_secret_value_is_variable.go @@ -5,9 +5,10 @@ import ( "fmt" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config/walk" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) type validateSecretValueIsVariable struct{} @@ -24,45 +25,32 @@ func (v *validateSecretValueIsVariable) Apply(ctx context.Context, b *bundle.Bun var diags diag.Diagnostics // Iterate over all secrets in the bundle - for key := range b.Config.Resources.Secrets { - p := dyn.NewPath(dyn.Key("resources"), dyn.Key("secrets"), dyn.Key(key), dyn.Key("value")) - val, err := dyn.GetByPath(b.Config.Value(), p) - if dyn.IsNoSuchKeyError(err) { + for key, secret := range b.Config.Resources.Secrets { + p := structpath.NewPath(nil, "resources", "secrets", key, "value") + locs := b.Config.GetLocationsOfExact(p) + // An explicit empty value is forced (it is not a reference either, below). + if secret == nil || (secret.Value == "" && !walk.Forced(secret.ForceSendFields, "Value")) { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Secret value must be a string", Detail: fmt.Sprintf(`The secret value for "%s" must be a string.`, key), - Locations: val.Locations(), - Paths: dyn.ToStructPaths(p), - }) - continue - } - if err != nil { - return diag.FromErr(err) - } - - valueStr, ok := val.AsString() - if !ok { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Secret value must be a string", - Detail: fmt.Sprintf(`The secret value for "%s" must be a string.`, key), - Locations: val.Locations(), - Paths: dyn.ToStructPaths(p), + Locations: locs, + Paths: []*structpath.PathNode{p}, }) continue } + valueStr := secret.Value // Value must be a variable reference to prevent leaking secrets in config files - if !dynvar.IsPureVariableReference(valueStr) { + if !structvar.IsPureVariableReference(valueStr) { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Secret value must be a variable reference", Detail: fmt.Sprintf(`The secret value for "%s" must be a variable reference (e.g., ${var.my_secret}). Plain text secret values are not allowed to prevent leaking secrets in configuration files. Use bundle variables to pass secret values at deployment time.`, key), - Locations: val.Locations(), - Paths: dyn.ToStructPaths(p), + Locations: locs, + Paths: []*structpath.PathNode{p}, }) continue } @@ -71,7 +59,7 @@ Use bundle variables to pass secret values at deployment time.`, key), // check that the referenced variable does not have a default value set. A default // value would be stored in plain text in the config file, defeating the purpose // of using a variable reference. - diags = append(diags, v.checkVariableDefault(b, key, valueStr, p, val)...) + diags = append(diags, v.checkVariableDefault(b, key, valueStr, p, locs)...) } return diags @@ -79,25 +67,21 @@ Use bundle variables to pass secret values at deployment time.`, key), // checkVariableDefault emits an error if valueStr is a pure ${var.} reference // and the referenced variable has a default value set. -func (v *validateSecretValueIsVariable) checkVariableDefault(b *bundle.Bundle, secretKey, valueStr string, p dyn.Path, val dyn.Value) diag.Diagnostics { - refPath, ok := dynvar.PureReferenceToPath(valueStr) - if !ok || len(refPath) < 2 || refPath[0].Key() != "var" { +func (v *validateSecretValueIsVariable) checkVariableDefault(b *bundle.Bundle, secretKey, valueStr string, p *structpath.PathNode, valueLocs []diag.Location) diag.Diagnostics { + refPath, ok := structvar.PureReferenceToPath(valueStr) + if !ok || refPath.Len() < 2 || refPath.KeyAt(0) != "var" { return nil } - varName := refPath[1].Key() + varName := refPath.KeyAt(1) variable, exists := b.Config.Variables[varName] if !exists || variable == nil || !variable.HasDefault() { return nil } // The default path in the dynamic config for the variable's default field. - defaultPath := dyn.NewPath(dyn.Key("variables"), dyn.Key(varName), dyn.Key("default")) - defaultVal, err := dyn.GetByPath(b.Config.Value(), defaultPath) - locations := val.Locations() - if err == nil { - locations = append(defaultVal.Locations(), locations...) - } + defaultPath := structpath.NewPath(nil, "variables", varName, "default") + locations := append(b.Config.GetLocationsOfExact(defaultPath), valueLocs...) return diag.Diagnostics{{ Severity: diag.Error, @@ -106,6 +90,6 @@ func (v *validateSecretValueIsVariable) checkVariableDefault(b *bundle.Bundle, s A default value is stored in plain text in the configuration file, which defeats the purpose of using a variable reference for a secret. Remove the default value and pass the secret value at deployment time using "--var", the BUNDLE_VAR_%s environment variable, or a variable overrides file.`, varName, secretKey, varName), Locations: locations, - Paths: dyn.ToStructPaths(p), + Paths: []*structpath.PathNode{p}, }} } diff --git a/bundle/config/resourcedefaults/defaults.go b/bundle/config/resourcedefaults/defaults.go new file mode 100644 index 00000000000..e2ffcf0aac5 --- /dev/null +++ b/bundle/config/resourcedefaults/defaults.go @@ -0,0 +1,81 @@ +// Package resourcedefaults lists the values set on resources where the configuration +// does not set them. The walker that applies them is generated from the patterns +// (bundle/config/mutator/resourcemutator/defaults_walk.go). +package resourcedefaults + +import ( + "github.com/databricks/cli/bundle/config" + "github.com/databricks/databricks-sdk-go/service/jobs" +) + +// Default is a value to set at every path matching Pattern where it is not set yet. +type Default struct { + Pattern string + Value func(r *config.Root) any +} + +func constant(v any) func(*config.Root) any { + return func(*config.Root) any { return v } +} + +func resourcePath(r *config.Root) any { + return r.Workspace.ResourcePath +} + +// Defaults are applied in order. +var Defaults = []Default{ + {Pattern: "resources.dashboards.*.parent_path", Value: resourcePath}, + {Pattern: "resources.dashboards.*.embed_credentials", Value: constant(false)}, + {Pattern: "resources.genie_spaces.*.parent_path", Value: resourcePath}, + {Pattern: "resources.volumes.*.volume_type", Value: constant("MANAGED")}, + + {Pattern: "resources.alerts.*.parent_path", Value: resourcePath}, + + // Jobs: + + // The defaults are the same as for terraform provider latest version (v1.75.0) + // https://github.com/databricks/terraform-provider-databricks/blob/v1.75.0/jobs/resource_job.go#L532 + {Pattern: "resources.jobs.*.name", Value: constant("Untitled")}, + {Pattern: "resources.jobs.*.max_concurrent_runs", Value: constant(1)}, + {Pattern: "resources.jobs.*.schedule.pause_status", Value: constant("UNPAUSED")}, + {Pattern: "resources.jobs.*.trigger.pause_status", Value: constant("UNPAUSED")}, + {Pattern: "resources.jobs.*.continuous.pause_status", Value: constant("UNPAUSED")}, + + // Enable queueing for jobs by default, following the behavior from API 2.2+. + // As of 2024-04, we're still using API 2.1 which has queueing disabled by default. + {Pattern: "resources.jobs.*.queue", Value: constant(jobs.QueueSettings{Enabled: true})}, + + // https://github.com/databricks/terraform-provider-databricks/blob/v1.75.0/clusters/resource_cluster.go + {Pattern: "resources.jobs.*.job_clusters[*].new_cluster.workload_type.clients.notebooks", Value: constant(true)}, + {Pattern: "resources.jobs.*.job_clusters[*].new_cluster.workload_type.clients.jobs", Value: constant(true)}, + + // Pipelines (same as terraform) + // https://github.com/databricks/terraform-provider-databricks/blob/v1.75.0/pipelines/resource_pipeline.go#L253 + {Pattern: "resources.pipelines.*.edition", Value: constant("ADVANCED")}, + {Pattern: "resources.pipelines.*.channel", Value: constant("CURRENT")}, + + // SqlWarehouses (same as terraform) + // https://github.com/databricks/terraform-provider-databricks/blob/v1.75.0/sql/resource_sql_endpoint.go#L59 + {Pattern: "resources.sql_warehouses.*.auto_stop_mins", Value: constant(120)}, + {Pattern: "resources.sql_warehouses.*.enable_photon", Value: constant(true)}, + {Pattern: "resources.sql_warehouses.*.max_num_clusters", Value: constant(1)}, + {Pattern: "resources.sql_warehouses.*.spot_instance_policy", Value: constant("COST_OPTIMIZED")}, + + // Apps: + {Pattern: "resources.apps.*.description", Value: constant("")}, + + // Clusters (same as terraform) + // https://github.com/databricks/terraform-provider-databricks/blob/v1.75.0/clusters/resource_cluster.go#L315 + {Pattern: "resources.clusters.*.autotermination_minutes", Value: constant(60)}, + {Pattern: "resources.clusters.*.workload_type.clients.notebooks", Value: constant(true)}, + {Pattern: "resources.clusters.*.workload_type.clients.jobs", Value: constant(true)}, +} + +// Patterns returns the patterns of Defaults, in order. +func Patterns() []string { + out := make([]string, len(Defaults)) + for i, d := range Defaults { + out[i] = d.Pattern + } + return out +} diff --git a/bundle/config/resources/job_run.go b/bundle/config/resources/job_run.go index ff9a91a8899..bff6f895b6e 100644 --- a/bundle/config/resources/job_run.go +++ b/bundle/config/resources/job_run.go @@ -41,6 +41,14 @@ type JobRunLifecycle struct { TriggersState *JobRunTriggersState `json:"triggers_state,omitempty" bundle:"internal"` } +// GetLifecycle returns the lifecycle settings, using JobRunLifecycle. +func (r *JobRun) GetLifecycle() LifecycleConfig { + if r.Lifecycle == nil { + return JobRunLifecycle{} + } + return *r.Lifecycle +} + // JobRunTrigger is one lifecycle.triggers entry. type JobRunTrigger struct { OnBundleDeploy *bool `json:"on_bundle_deploy,omitempty"` diff --git a/bundle/config/resources/permission.go b/bundle/config/resources/permission.go index b71194755c0..17e26152f61 100644 --- a/bundle/config/resources/permission.go +++ b/bundle/config/resources/permission.go @@ -94,3 +94,14 @@ func GetMaxLevel(a, b string) string { } return b } + +// PermissionFields is the struct underlying every resource permission type +// (PermissionT and the named types defined from it, SecretScopePermission), so generic +// code can convert between them with P(PermissionFields[L]{...}). +type PermissionFields[L ~string] = struct { + Level L `json:"level"` + + UserName string `json:"user_name,omitempty"` + ServicePrincipalName string `json:"service_principal_name,omitempty"` + GroupName string `json:"group_name,omitempty"` +} diff --git a/bundle/config/resources_iterate.go b/bundle/config/resources_iterate.go new file mode 100644 index 00000000000..3b709c56dfc --- /dev/null +++ b/bundle/config/resources_iterate.go @@ -0,0 +1,2107 @@ +// Code generated by bundle/internal/validation. DO NOT EDIT. + +package config + +import ( + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/databricks-sdk-go/service/apps" + "github.com/databricks/databricks-sdk-go/service/catalog" + "github.com/databricks/databricks-sdk-go/service/compute" + "github.com/databricks/databricks-sdk-go/service/iam" + "github.com/databricks/databricks-sdk-go/service/jobs" + "github.com/databricks/databricks-sdk-go/service/ml" + "github.com/databricks/databricks-sdk-go/service/pipelines" + "github.com/databricks/databricks-sdk-go/service/serving" + "github.com/databricks/databricks-sdk-go/service/sql" + "github.com/databricks/databricks-sdk-go/service/vectorsearch" +) + +// IterateAllResources calls fn for every resource, in the order of the resource type +// and then of the name, until fn returns false. Resources defined as null are +// visited with a nil res. +func (r *Resources) IterateAllResources(fn func(typ, name string, res ConfigResource) bool) { + for _, name := range sortedKeys(r.Alerts) { + var res ConfigResource + if x := r.Alerts[name]; x != nil { + res = x + } + if !fn("alerts", name, res) { + return + } + } + for _, name := range sortedKeys(r.Apps) { + var res ConfigResource + if x := r.Apps[name]; x != nil { + res = x + } + if !fn("apps", name, res) { + return + } + } + for _, name := range sortedKeys(r.Catalogs) { + var res ConfigResource + if x := r.Catalogs[name]; x != nil { + res = x + } + if !fn("catalogs", name, res) { + return + } + } + for _, name := range sortedKeys(r.ClusterPolicies) { + var res ConfigResource + if x := r.ClusterPolicies[name]; x != nil { + res = x + } + if !fn("cluster_policies", name, res) { + return + } + } + for _, name := range sortedKeys(r.Clusters) { + var res ConfigResource + if x := r.Clusters[name]; x != nil { + res = x + } + if !fn("clusters", name, res) { + return + } + } + for _, name := range sortedKeys(r.Dashboards) { + var res ConfigResource + if x := r.Dashboards[name]; x != nil { + res = x + } + if !fn("dashboards", name, res) { + return + } + } + for _, name := range sortedKeys(r.DatabaseCatalogs) { + var res ConfigResource + if x := r.DatabaseCatalogs[name]; x != nil { + res = x + } + if !fn("database_catalogs", name, res) { + return + } + } + for _, name := range sortedKeys(r.DatabaseInstances) { + var res ConfigResource + if x := r.DatabaseInstances[name]; x != nil { + res = x + } + if !fn("database_instances", name, res) { + return + } + } + for _, name := range sortedKeys(r.Experiments) { + var res ConfigResource + if x := r.Experiments[name]; x != nil { + res = x + } + if !fn("experiments", name, res) { + return + } + } + for _, name := range sortedKeys(r.ExternalLocations) { + var res ConfigResource + if x := r.ExternalLocations[name]; x != nil { + res = x + } + if !fn("external_locations", name, res) { + return + } + } + for _, name := range sortedKeys(r.Features) { + var res ConfigResource + if x := r.Features[name]; x != nil { + res = x + } + if !fn("features", name, res) { + return + } + } + for _, name := range sortedKeys(r.GenieSpaces) { + var res ConfigResource + if x := r.GenieSpaces[name]; x != nil { + res = x + } + if !fn("genie_spaces", name, res) { + return + } + } + for _, name := range sortedKeys(r.InstancePools) { + var res ConfigResource + if x := r.InstancePools[name]; x != nil { + res = x + } + if !fn("instance_pools", name, res) { + return + } + } + for _, name := range sortedKeys(r.Snapshots) { + var res ConfigResource + if x := r.Snapshots[name]; x != nil { + res = x + } + if !fn("internal_immutable_snapshots", name, res) { + return + } + } + for _, name := range sortedKeys(r.JobRuns) { + var res ConfigResource + if x := r.JobRuns[name]; x != nil { + res = x + } + if !fn("job_runs", name, res) { + return + } + } + for _, name := range sortedKeys(r.Jobs) { + var res ConfigResource + if x := r.Jobs[name]; x != nil { + res = x + } + if !fn("jobs", name, res) { + return + } + } + for _, name := range sortedKeys(r.McpServices) { + var res ConfigResource + if x := r.McpServices[name]; x != nil { + res = x + } + if !fn("mcp_services", name, res) { + return + } + } + for _, name := range sortedKeys(r.ModelProviderServices) { + var res ConfigResource + if x := r.ModelProviderServices[name]; x != nil { + res = x + } + if !fn("model_provider_services", name, res) { + return + } + } + for _, name := range sortedKeys(r.ModelServices) { + var res ConfigResource + if x := r.ModelServices[name]; x != nil { + res = x + } + if !fn("model_services", name, res) { + return + } + } + for _, name := range sortedKeys(r.ModelServingEndpoints) { + var res ConfigResource + if x := r.ModelServingEndpoints[name]; x != nil { + res = x + } + if !fn("model_serving_endpoints", name, res) { + return + } + } + for _, name := range sortedKeys(r.Models) { + var res ConfigResource + if x := r.Models[name]; x != nil { + res = x + } + if !fn("models", name, res) { + return + } + } + for _, name := range sortedKeys(r.Pipelines) { + var res ConfigResource + if x := r.Pipelines[name]; x != nil { + res = x + } + if !fn("pipelines", name, res) { + return + } + } + for _, name := range sortedKeys(r.PostgresBranches) { + var res ConfigResource + if x := r.PostgresBranches[name]; x != nil { + res = x + } + if !fn("postgres_branches", name, res) { + return + } + } + for _, name := range sortedKeys(r.PostgresCatalogs) { + var res ConfigResource + if x := r.PostgresCatalogs[name]; x != nil { + res = x + } + if !fn("postgres_catalogs", name, res) { + return + } + } + for _, name := range sortedKeys(r.PostgresDatabases) { + var res ConfigResource + if x := r.PostgresDatabases[name]; x != nil { + res = x + } + if !fn("postgres_databases", name, res) { + return + } + } + for _, name := range sortedKeys(r.PostgresEndpoints) { + var res ConfigResource + if x := r.PostgresEndpoints[name]; x != nil { + res = x + } + if !fn("postgres_endpoints", name, res) { + return + } + } + for _, name := range sortedKeys(r.PostgresProjects) { + var res ConfigResource + if x := r.PostgresProjects[name]; x != nil { + res = x + } + if !fn("postgres_projects", name, res) { + return + } + } + for _, name := range sortedKeys(r.PostgresRoles) { + var res ConfigResource + if x := r.PostgresRoles[name]; x != nil { + res = x + } + if !fn("postgres_roles", name, res) { + return + } + } + for _, name := range sortedKeys(r.PostgresSnapshotSchedules) { + var res ConfigResource + if x := r.PostgresSnapshotSchedules[name]; x != nil { + res = x + } + if !fn("postgres_snapshot_schedules", name, res) { + return + } + } + for _, name := range sortedKeys(r.PostgresSyncedTables) { + var res ConfigResource + if x := r.PostgresSyncedTables[name]; x != nil { + res = x + } + if !fn("postgres_synced_tables", name, res) { + return + } + } + for _, name := range sortedKeys(r.QualityMonitors) { + var res ConfigResource + if x := r.QualityMonitors[name]; x != nil { + res = x + } + if !fn("quality_monitors", name, res) { + return + } + } + for _, name := range sortedKeys(r.RegisteredModels) { + var res ConfigResource + if x := r.RegisteredModels[name]; x != nil { + res = x + } + if !fn("registered_models", name, res) { + return + } + } + for _, name := range sortedKeys(r.Schemas) { + var res ConfigResource + if x := r.Schemas[name]; x != nil { + res = x + } + if !fn("schemas", name, res) { + return + } + } + for _, name := range sortedKeys(r.SecretScopes) { + var res ConfigResource + if x := r.SecretScopes[name]; x != nil { + res = x + } + if !fn("secret_scopes", name, res) { + return + } + } + for _, name := range sortedKeys(r.Secrets) { + var res ConfigResource + if x := r.Secrets[name]; x != nil { + res = x + } + if !fn("secrets", name, res) { + return + } + } + for _, name := range sortedKeys(r.SqlWarehouses) { + var res ConfigResource + if x := r.SqlWarehouses[name]; x != nil { + res = x + } + if !fn("sql_warehouses", name, res) { + return + } + } + for _, name := range sortedKeys(r.SyncedDatabaseTables) { + var res ConfigResource + if x := r.SyncedDatabaseTables[name]; x != nil { + res = x + } + if !fn("synced_database_tables", name, res) { + return + } + } + for _, name := range sortedKeys(r.VectorSearchEndpoints) { + var res ConfigResource + if x := r.VectorSearchEndpoints[name]; x != nil { + res = x + } + if !fn("vector_search_endpoints", name, res) { + return + } + } + for _, name := range sortedKeys(r.VectorSearchIndexes) { + var res ConfigResource + if x := r.VectorSearchIndexes[name]; x != nil { + res = x + } + if !fn("vector_search_indexes", name, res) { + return + } + } + for _, name := range sortedKeys(r.Volumes) { + var res ConfigResource + if x := r.Volumes[name]; x != nil { + res = x + } + if !fn("volumes", name, res) { + return + } + } +} + +// Select returns the resources of r whose names are in keys (by type): new maps that +// share the resources. Types not in keys are nil. +func (r *Resources) Select(keys map[string]map[string]struct{}) Resources { + var out Resources + if names, ok := keys["alerts"]; ok { + out.Alerts = make(map[string]*resources.Alert, len(names)) + for name := range names { + if x, ok := r.Alerts[name]; ok { + out.Alerts[name] = x + } + } + } + if names, ok := keys["apps"]; ok { + out.Apps = make(map[string]*resources.App, len(names)) + for name := range names { + if x, ok := r.Apps[name]; ok { + out.Apps[name] = x + } + } + } + if names, ok := keys["catalogs"]; ok { + out.Catalogs = make(map[string]*resources.Catalog, len(names)) + for name := range names { + if x, ok := r.Catalogs[name]; ok { + out.Catalogs[name] = x + } + } + } + if names, ok := keys["cluster_policies"]; ok { + out.ClusterPolicies = make(map[string]*resources.ClusterPolicy, len(names)) + for name := range names { + if x, ok := r.ClusterPolicies[name]; ok { + out.ClusterPolicies[name] = x + } + } + } + if names, ok := keys["clusters"]; ok { + out.Clusters = make(map[string]*resources.Cluster, len(names)) + for name := range names { + if x, ok := r.Clusters[name]; ok { + out.Clusters[name] = x + } + } + } + if names, ok := keys["dashboards"]; ok { + out.Dashboards = make(map[string]*resources.Dashboard, len(names)) + for name := range names { + if x, ok := r.Dashboards[name]; ok { + out.Dashboards[name] = x + } + } + } + if names, ok := keys["database_catalogs"]; ok { + out.DatabaseCatalogs = make(map[string]*resources.DatabaseCatalog, len(names)) + for name := range names { + if x, ok := r.DatabaseCatalogs[name]; ok { + out.DatabaseCatalogs[name] = x + } + } + } + if names, ok := keys["database_instances"]; ok { + out.DatabaseInstances = make(map[string]*resources.DatabaseInstance, len(names)) + for name := range names { + if x, ok := r.DatabaseInstances[name]; ok { + out.DatabaseInstances[name] = x + } + } + } + if names, ok := keys["experiments"]; ok { + out.Experiments = make(map[string]*resources.MlflowExperiment, len(names)) + for name := range names { + if x, ok := r.Experiments[name]; ok { + out.Experiments[name] = x + } + } + } + if names, ok := keys["external_locations"]; ok { + out.ExternalLocations = make(map[string]*resources.ExternalLocation, len(names)) + for name := range names { + if x, ok := r.ExternalLocations[name]; ok { + out.ExternalLocations[name] = x + } + } + } + if names, ok := keys["features"]; ok { + out.Features = make(map[string]*resources.Feature, len(names)) + for name := range names { + if x, ok := r.Features[name]; ok { + out.Features[name] = x + } + } + } + if names, ok := keys["genie_spaces"]; ok { + out.GenieSpaces = make(map[string]*resources.GenieSpace, len(names)) + for name := range names { + if x, ok := r.GenieSpaces[name]; ok { + out.GenieSpaces[name] = x + } + } + } + if names, ok := keys["instance_pools"]; ok { + out.InstancePools = make(map[string]*resources.InstancePool, len(names)) + for name := range names { + if x, ok := r.InstancePools[name]; ok { + out.InstancePools[name] = x + } + } + } + if names, ok := keys["internal_immutable_snapshots"]; ok { + out.Snapshots = make(map[string]*resources.Snapshot, len(names)) + for name := range names { + if x, ok := r.Snapshots[name]; ok { + out.Snapshots[name] = x + } + } + } + if names, ok := keys["job_runs"]; ok { + out.JobRuns = make(map[string]*resources.JobRun, len(names)) + for name := range names { + if x, ok := r.JobRuns[name]; ok { + out.JobRuns[name] = x + } + } + } + if names, ok := keys["jobs"]; ok { + out.Jobs = make(map[string]*resources.Job, len(names)) + for name := range names { + if x, ok := r.Jobs[name]; ok { + out.Jobs[name] = x + } + } + } + if names, ok := keys["mcp_services"]; ok { + out.McpServices = make(map[string]*resources.McpService, len(names)) + for name := range names { + if x, ok := r.McpServices[name]; ok { + out.McpServices[name] = x + } + } + } + if names, ok := keys["model_provider_services"]; ok { + out.ModelProviderServices = make(map[string]*resources.ModelProviderService, len(names)) + for name := range names { + if x, ok := r.ModelProviderServices[name]; ok { + out.ModelProviderServices[name] = x + } + } + } + if names, ok := keys["model_services"]; ok { + out.ModelServices = make(map[string]*resources.ModelService, len(names)) + for name := range names { + if x, ok := r.ModelServices[name]; ok { + out.ModelServices[name] = x + } + } + } + if names, ok := keys["model_serving_endpoints"]; ok { + out.ModelServingEndpoints = make(map[string]*resources.ModelServingEndpoint, len(names)) + for name := range names { + if x, ok := r.ModelServingEndpoints[name]; ok { + out.ModelServingEndpoints[name] = x + } + } + } + if names, ok := keys["models"]; ok { + out.Models = make(map[string]*resources.MlflowModel, len(names)) + for name := range names { + if x, ok := r.Models[name]; ok { + out.Models[name] = x + } + } + } + if names, ok := keys["pipelines"]; ok { + out.Pipelines = make(map[string]*resources.Pipeline, len(names)) + for name := range names { + if x, ok := r.Pipelines[name]; ok { + out.Pipelines[name] = x + } + } + } + if names, ok := keys["postgres_branches"]; ok { + out.PostgresBranches = make(map[string]*resources.PostgresBranch, len(names)) + for name := range names { + if x, ok := r.PostgresBranches[name]; ok { + out.PostgresBranches[name] = x + } + } + } + if names, ok := keys["postgres_catalogs"]; ok { + out.PostgresCatalogs = make(map[string]*resources.PostgresCatalog, len(names)) + for name := range names { + if x, ok := r.PostgresCatalogs[name]; ok { + out.PostgresCatalogs[name] = x + } + } + } + if names, ok := keys["postgres_databases"]; ok { + out.PostgresDatabases = make(map[string]*resources.PostgresDatabase, len(names)) + for name := range names { + if x, ok := r.PostgresDatabases[name]; ok { + out.PostgresDatabases[name] = x + } + } + } + if names, ok := keys["postgres_endpoints"]; ok { + out.PostgresEndpoints = make(map[string]*resources.PostgresEndpoint, len(names)) + for name := range names { + if x, ok := r.PostgresEndpoints[name]; ok { + out.PostgresEndpoints[name] = x + } + } + } + if names, ok := keys["postgres_projects"]; ok { + out.PostgresProjects = make(map[string]*resources.PostgresProject, len(names)) + for name := range names { + if x, ok := r.PostgresProjects[name]; ok { + out.PostgresProjects[name] = x + } + } + } + if names, ok := keys["postgres_roles"]; ok { + out.PostgresRoles = make(map[string]*resources.PostgresRole, len(names)) + for name := range names { + if x, ok := r.PostgresRoles[name]; ok { + out.PostgresRoles[name] = x + } + } + } + if names, ok := keys["postgres_snapshot_schedules"]; ok { + out.PostgresSnapshotSchedules = make(map[string]*resources.PostgresSnapshotSchedule, len(names)) + for name := range names { + if x, ok := r.PostgresSnapshotSchedules[name]; ok { + out.PostgresSnapshotSchedules[name] = x + } + } + } + if names, ok := keys["postgres_synced_tables"]; ok { + out.PostgresSyncedTables = make(map[string]*resources.PostgresSyncedTable, len(names)) + for name := range names { + if x, ok := r.PostgresSyncedTables[name]; ok { + out.PostgresSyncedTables[name] = x + } + } + } + if names, ok := keys["quality_monitors"]; ok { + out.QualityMonitors = make(map[string]*resources.QualityMonitor, len(names)) + for name := range names { + if x, ok := r.QualityMonitors[name]; ok { + out.QualityMonitors[name] = x + } + } + } + if names, ok := keys["registered_models"]; ok { + out.RegisteredModels = make(map[string]*resources.RegisteredModel, len(names)) + for name := range names { + if x, ok := r.RegisteredModels[name]; ok { + out.RegisteredModels[name] = x + } + } + } + if names, ok := keys["schemas"]; ok { + out.Schemas = make(map[string]*resources.Schema, len(names)) + for name := range names { + if x, ok := r.Schemas[name]; ok { + out.Schemas[name] = x + } + } + } + if names, ok := keys["secret_scopes"]; ok { + out.SecretScopes = make(map[string]*resources.SecretScope, len(names)) + for name := range names { + if x, ok := r.SecretScopes[name]; ok { + out.SecretScopes[name] = x + } + } + } + if names, ok := keys["secrets"]; ok { + out.Secrets = make(map[string]*resources.Secret, len(names)) + for name := range names { + if x, ok := r.Secrets[name]; ok { + out.Secrets[name] = x + } + } + } + if names, ok := keys["sql_warehouses"]; ok { + out.SqlWarehouses = make(map[string]*resources.SqlWarehouse, len(names)) + for name := range names { + if x, ok := r.SqlWarehouses[name]; ok { + out.SqlWarehouses[name] = x + } + } + } + if names, ok := keys["synced_database_tables"]; ok { + out.SyncedDatabaseTables = make(map[string]*resources.SyncedDatabaseTable, len(names)) + for name := range names { + if x, ok := r.SyncedDatabaseTables[name]; ok { + out.SyncedDatabaseTables[name] = x + } + } + } + if names, ok := keys["vector_search_endpoints"]; ok { + out.VectorSearchEndpoints = make(map[string]*resources.VectorSearchEndpoint, len(names)) + for name := range names { + if x, ok := r.VectorSearchEndpoints[name]; ok { + out.VectorSearchEndpoints[name] = x + } + } + } + if names, ok := keys["vector_search_indexes"]; ok { + out.VectorSearchIndexes = make(map[string]*resources.VectorSearchIndex, len(names)) + for name := range names { + if x, ok := r.VectorSearchIndexes[name]; ok { + out.VectorSearchIndexes[name] = x + } + } + } + if names, ok := keys["volumes"]; ok { + out.Volumes = make(map[string]*resources.Volume, len(names)) + for name := range names { + if x, ok := r.Volumes[name]; ok { + out.Volumes[name] = x + } + } + } + return out +} + +// CopyResource sets the resource name of type typ to the one in src (shared, not +// copied). It returns false if src has no such resource. +func (r *Resources) CopyResource(src *Resources, typ, name string) bool { + switch typ { + case "alerts": + x, ok := src.Alerts[name] + if !ok { + return false + } + if r.Alerts == nil { + r.Alerts = map[string]*resources.Alert{} + } + r.Alerts[name] = x + return true + case "apps": + x, ok := src.Apps[name] + if !ok { + return false + } + if r.Apps == nil { + r.Apps = map[string]*resources.App{} + } + r.Apps[name] = x + return true + case "catalogs": + x, ok := src.Catalogs[name] + if !ok { + return false + } + if r.Catalogs == nil { + r.Catalogs = map[string]*resources.Catalog{} + } + r.Catalogs[name] = x + return true + case "cluster_policies": + x, ok := src.ClusterPolicies[name] + if !ok { + return false + } + if r.ClusterPolicies == nil { + r.ClusterPolicies = map[string]*resources.ClusterPolicy{} + } + r.ClusterPolicies[name] = x + return true + case "clusters": + x, ok := src.Clusters[name] + if !ok { + return false + } + if r.Clusters == nil { + r.Clusters = map[string]*resources.Cluster{} + } + r.Clusters[name] = x + return true + case "dashboards": + x, ok := src.Dashboards[name] + if !ok { + return false + } + if r.Dashboards == nil { + r.Dashboards = map[string]*resources.Dashboard{} + } + r.Dashboards[name] = x + return true + case "database_catalogs": + x, ok := src.DatabaseCatalogs[name] + if !ok { + return false + } + if r.DatabaseCatalogs == nil { + r.DatabaseCatalogs = map[string]*resources.DatabaseCatalog{} + } + r.DatabaseCatalogs[name] = x + return true + case "database_instances": + x, ok := src.DatabaseInstances[name] + if !ok { + return false + } + if r.DatabaseInstances == nil { + r.DatabaseInstances = map[string]*resources.DatabaseInstance{} + } + r.DatabaseInstances[name] = x + return true + case "experiments": + x, ok := src.Experiments[name] + if !ok { + return false + } + if r.Experiments == nil { + r.Experiments = map[string]*resources.MlflowExperiment{} + } + r.Experiments[name] = x + return true + case "external_locations": + x, ok := src.ExternalLocations[name] + if !ok { + return false + } + if r.ExternalLocations == nil { + r.ExternalLocations = map[string]*resources.ExternalLocation{} + } + r.ExternalLocations[name] = x + return true + case "features": + x, ok := src.Features[name] + if !ok { + return false + } + if r.Features == nil { + r.Features = map[string]*resources.Feature{} + } + r.Features[name] = x + return true + case "genie_spaces": + x, ok := src.GenieSpaces[name] + if !ok { + return false + } + if r.GenieSpaces == nil { + r.GenieSpaces = map[string]*resources.GenieSpace{} + } + r.GenieSpaces[name] = x + return true + case "instance_pools": + x, ok := src.InstancePools[name] + if !ok { + return false + } + if r.InstancePools == nil { + r.InstancePools = map[string]*resources.InstancePool{} + } + r.InstancePools[name] = x + return true + case "internal_immutable_snapshots": + x, ok := src.Snapshots[name] + if !ok { + return false + } + if r.Snapshots == nil { + r.Snapshots = map[string]*resources.Snapshot{} + } + r.Snapshots[name] = x + return true + case "job_runs": + x, ok := src.JobRuns[name] + if !ok { + return false + } + if r.JobRuns == nil { + r.JobRuns = map[string]*resources.JobRun{} + } + r.JobRuns[name] = x + return true + case "jobs": + x, ok := src.Jobs[name] + if !ok { + return false + } + if r.Jobs == nil { + r.Jobs = map[string]*resources.Job{} + } + r.Jobs[name] = x + return true + case "mcp_services": + x, ok := src.McpServices[name] + if !ok { + return false + } + if r.McpServices == nil { + r.McpServices = map[string]*resources.McpService{} + } + r.McpServices[name] = x + return true + case "model_provider_services": + x, ok := src.ModelProviderServices[name] + if !ok { + return false + } + if r.ModelProviderServices == nil { + r.ModelProviderServices = map[string]*resources.ModelProviderService{} + } + r.ModelProviderServices[name] = x + return true + case "model_services": + x, ok := src.ModelServices[name] + if !ok { + return false + } + if r.ModelServices == nil { + r.ModelServices = map[string]*resources.ModelService{} + } + r.ModelServices[name] = x + return true + case "model_serving_endpoints": + x, ok := src.ModelServingEndpoints[name] + if !ok { + return false + } + if r.ModelServingEndpoints == nil { + r.ModelServingEndpoints = map[string]*resources.ModelServingEndpoint{} + } + r.ModelServingEndpoints[name] = x + return true + case "models": + x, ok := src.Models[name] + if !ok { + return false + } + if r.Models == nil { + r.Models = map[string]*resources.MlflowModel{} + } + r.Models[name] = x + return true + case "pipelines": + x, ok := src.Pipelines[name] + if !ok { + return false + } + if r.Pipelines == nil { + r.Pipelines = map[string]*resources.Pipeline{} + } + r.Pipelines[name] = x + return true + case "postgres_branches": + x, ok := src.PostgresBranches[name] + if !ok { + return false + } + if r.PostgresBranches == nil { + r.PostgresBranches = map[string]*resources.PostgresBranch{} + } + r.PostgresBranches[name] = x + return true + case "postgres_catalogs": + x, ok := src.PostgresCatalogs[name] + if !ok { + return false + } + if r.PostgresCatalogs == nil { + r.PostgresCatalogs = map[string]*resources.PostgresCatalog{} + } + r.PostgresCatalogs[name] = x + return true + case "postgres_databases": + x, ok := src.PostgresDatabases[name] + if !ok { + return false + } + if r.PostgresDatabases == nil { + r.PostgresDatabases = map[string]*resources.PostgresDatabase{} + } + r.PostgresDatabases[name] = x + return true + case "postgres_endpoints": + x, ok := src.PostgresEndpoints[name] + if !ok { + return false + } + if r.PostgresEndpoints == nil { + r.PostgresEndpoints = map[string]*resources.PostgresEndpoint{} + } + r.PostgresEndpoints[name] = x + return true + case "postgres_projects": + x, ok := src.PostgresProjects[name] + if !ok { + return false + } + if r.PostgresProjects == nil { + r.PostgresProjects = map[string]*resources.PostgresProject{} + } + r.PostgresProjects[name] = x + return true + case "postgres_roles": + x, ok := src.PostgresRoles[name] + if !ok { + return false + } + if r.PostgresRoles == nil { + r.PostgresRoles = map[string]*resources.PostgresRole{} + } + r.PostgresRoles[name] = x + return true + case "postgres_snapshot_schedules": + x, ok := src.PostgresSnapshotSchedules[name] + if !ok { + return false + } + if r.PostgresSnapshotSchedules == nil { + r.PostgresSnapshotSchedules = map[string]*resources.PostgresSnapshotSchedule{} + } + r.PostgresSnapshotSchedules[name] = x + return true + case "postgres_synced_tables": + x, ok := src.PostgresSyncedTables[name] + if !ok { + return false + } + if r.PostgresSyncedTables == nil { + r.PostgresSyncedTables = map[string]*resources.PostgresSyncedTable{} + } + r.PostgresSyncedTables[name] = x + return true + case "quality_monitors": + x, ok := src.QualityMonitors[name] + if !ok { + return false + } + if r.QualityMonitors == nil { + r.QualityMonitors = map[string]*resources.QualityMonitor{} + } + r.QualityMonitors[name] = x + return true + case "registered_models": + x, ok := src.RegisteredModels[name] + if !ok { + return false + } + if r.RegisteredModels == nil { + r.RegisteredModels = map[string]*resources.RegisteredModel{} + } + r.RegisteredModels[name] = x + return true + case "schemas": + x, ok := src.Schemas[name] + if !ok { + return false + } + if r.Schemas == nil { + r.Schemas = map[string]*resources.Schema{} + } + r.Schemas[name] = x + return true + case "secret_scopes": + x, ok := src.SecretScopes[name] + if !ok { + return false + } + if r.SecretScopes == nil { + r.SecretScopes = map[string]*resources.SecretScope{} + } + r.SecretScopes[name] = x + return true + case "secrets": + x, ok := src.Secrets[name] + if !ok { + return false + } + if r.Secrets == nil { + r.Secrets = map[string]*resources.Secret{} + } + r.Secrets[name] = x + return true + case "sql_warehouses": + x, ok := src.SqlWarehouses[name] + if !ok { + return false + } + if r.SqlWarehouses == nil { + r.SqlWarehouses = map[string]*resources.SqlWarehouse{} + } + r.SqlWarehouses[name] = x + return true + case "synced_database_tables": + x, ok := src.SyncedDatabaseTables[name] + if !ok { + return false + } + if r.SyncedDatabaseTables == nil { + r.SyncedDatabaseTables = map[string]*resources.SyncedDatabaseTable{} + } + r.SyncedDatabaseTables[name] = x + return true + case "vector_search_endpoints": + x, ok := src.VectorSearchEndpoints[name] + if !ok { + return false + } + if r.VectorSearchEndpoints == nil { + r.VectorSearchEndpoints = map[string]*resources.VectorSearchEndpoint{} + } + r.VectorSearchEndpoints[name] = x + return true + case "vector_search_indexes": + x, ok := src.VectorSearchIndexes[name] + if !ok { + return false + } + if r.VectorSearchIndexes == nil { + r.VectorSearchIndexes = map[string]*resources.VectorSearchIndex{} + } + r.VectorSearchIndexes[name] = x + return true + case "volumes": + x, ok := src.Volumes[name] + if !ok { + return false + } + if r.Volumes == nil { + r.Volumes = map[string]*resources.Volume{} + } + r.Volumes[name] = x + return true + } + return false +} + +// GetResource returns the resource name of type typ. ok is false if there is no such +// resource or it is defined as null. +func (r *Resources) GetResource(typ, name string) (res ConfigResource, ok bool) { + switch typ { + case "alerts": + if x := r.Alerts[name]; x != nil { + return x, true + } + case "apps": + if x := r.Apps[name]; x != nil { + return x, true + } + case "catalogs": + if x := r.Catalogs[name]; x != nil { + return x, true + } + case "cluster_policies": + if x := r.ClusterPolicies[name]; x != nil { + return x, true + } + case "clusters": + if x := r.Clusters[name]; x != nil { + return x, true + } + case "dashboards": + if x := r.Dashboards[name]; x != nil { + return x, true + } + case "database_catalogs": + if x := r.DatabaseCatalogs[name]; x != nil { + return x, true + } + case "database_instances": + if x := r.DatabaseInstances[name]; x != nil { + return x, true + } + case "experiments": + if x := r.Experiments[name]; x != nil { + return x, true + } + case "external_locations": + if x := r.ExternalLocations[name]; x != nil { + return x, true + } + case "features": + if x := r.Features[name]; x != nil { + return x, true + } + case "genie_spaces": + if x := r.GenieSpaces[name]; x != nil { + return x, true + } + case "instance_pools": + if x := r.InstancePools[name]; x != nil { + return x, true + } + case "internal_immutable_snapshots": + if x := r.Snapshots[name]; x != nil { + return x, true + } + case "job_runs": + if x := r.JobRuns[name]; x != nil { + return x, true + } + case "jobs": + if x := r.Jobs[name]; x != nil { + return x, true + } + case "mcp_services": + if x := r.McpServices[name]; x != nil { + return x, true + } + case "model_provider_services": + if x := r.ModelProviderServices[name]; x != nil { + return x, true + } + case "model_services": + if x := r.ModelServices[name]; x != nil { + return x, true + } + case "model_serving_endpoints": + if x := r.ModelServingEndpoints[name]; x != nil { + return x, true + } + case "models": + if x := r.Models[name]; x != nil { + return x, true + } + case "pipelines": + if x := r.Pipelines[name]; x != nil { + return x, true + } + case "postgres_branches": + if x := r.PostgresBranches[name]; x != nil { + return x, true + } + case "postgres_catalogs": + if x := r.PostgresCatalogs[name]; x != nil { + return x, true + } + case "postgres_databases": + if x := r.PostgresDatabases[name]; x != nil { + return x, true + } + case "postgres_endpoints": + if x := r.PostgresEndpoints[name]; x != nil { + return x, true + } + case "postgres_projects": + if x := r.PostgresProjects[name]; x != nil { + return x, true + } + case "postgres_roles": + if x := r.PostgresRoles[name]; x != nil { + return x, true + } + case "postgres_snapshot_schedules": + if x := r.PostgresSnapshotSchedules[name]; x != nil { + return x, true + } + case "postgres_synced_tables": + if x := r.PostgresSyncedTables[name]; x != nil { + return x, true + } + case "quality_monitors": + if x := r.QualityMonitors[name]; x != nil { + return x, true + } + case "registered_models": + if x := r.RegisteredModels[name]; x != nil { + return x, true + } + case "schemas": + if x := r.Schemas[name]; x != nil { + return x, true + } + case "secret_scopes": + if x := r.SecretScopes[name]; x != nil { + return x, true + } + case "secrets": + if x := r.Secrets[name]; x != nil { + return x, true + } + case "sql_warehouses": + if x := r.SqlWarehouses[name]; x != nil { + return x, true + } + case "synced_database_tables": + if x := r.SyncedDatabaseTables[name]; x != nil { + return x, true + } + case "vector_search_endpoints": + if x := r.VectorSearchEndpoints[name]; x != nil { + return x, true + } + case "vector_search_indexes": + if x := r.VectorSearchIndexes[name]; x != nil { + return x, true + } + case "volumes": + if x := r.Volumes[name]; x != nil { + return x, true + } + } + return nil, false +} + +// IterateAllPermissions calls fn for the permissions of every resource that can have +// them, in the order of IterateAllResources, until fn returns false. +func (r *Resources) IterateAllPermissions(fn func(typ, name string, perms PermissionList) bool) { + for _, name := range sortedKeys(r.Alerts) { + if x := r.Alerts[name]; x != nil && !fn("alerts", name, permissionList[resources.Permission, iam.PermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.Apps) { + if x := r.Apps[name]; x != nil && !fn("apps", name, permissionList[resources.AppPermission, apps.AppPermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.ClusterPolicies) { + if x := r.ClusterPolicies[name]; x != nil && !fn("cluster_policies", name, permissionList[resources.ClusterPolicyPermission, compute.ClusterPolicyPermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.Clusters) { + if x := r.Clusters[name]; x != nil && !fn("clusters", name, permissionList[resources.ClusterPermission, compute.ClusterPermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.Dashboards) { + if x := r.Dashboards[name]; x != nil && !fn("dashboards", name, permissionList[resources.Permission, iam.PermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.DatabaseInstances) { + if x := r.DatabaseInstances[name]; x != nil && !fn("database_instances", name, permissionList[resources.Permission, iam.PermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.Experiments) { + if x := r.Experiments[name]; x != nil && !fn("experiments", name, permissionList[resources.MlflowExperimentPermission, ml.ExperimentPermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.GenieSpaces) { + if x := r.GenieSpaces[name]; x != nil && !fn("genie_spaces", name, permissionList[resources.Permission, iam.PermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.InstancePools) { + if x := r.InstancePools[name]; x != nil && !fn("instance_pools", name, permissionList[resources.InstancePoolPermission, compute.InstancePoolPermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.Jobs) { + if x := r.Jobs[name]; x != nil && !fn("jobs", name, permissionList[resources.JobPermission, jobs.JobPermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.ModelServingEndpoints) { + if x := r.ModelServingEndpoints[name]; x != nil && !fn("model_serving_endpoints", name, permissionList[resources.ModelServingEndpointPermission, serving.ServingEndpointPermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.Models) { + if x := r.Models[name]; x != nil && !fn("models", name, permissionList[resources.MlflowModelPermission, ml.RegisteredModelPermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.Pipelines) { + if x := r.Pipelines[name]; x != nil && !fn("pipelines", name, permissionList[resources.PipelinePermission, pipelines.PipelinePermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.PostgresProjects) { + if x := r.PostgresProjects[name]; x != nil && !fn("postgres_projects", name, permissionList[resources.Permission, iam.PermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.SecretScopes) { + if x := r.SecretScopes[name]; x != nil && !fn("secret_scopes", name, permissionList[resources.SecretScopePermission, resources.SecretScopePermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.SqlWarehouses) { + if x := r.SqlWarehouses[name]; x != nil && !fn("sql_warehouses", name, permissionList[resources.SqlWarehousePermission, sql.WarehousePermissionLevel]{&x.Permissions}) { + return + } + } + for _, name := range sortedKeys(r.VectorSearchEndpoints) { + if x := r.VectorSearchEndpoints[name]; x != nil && !fn("vector_search_endpoints", name, permissionList[resources.VectorSearchEndpointPermission, vectorsearch.VectorSearchEndpointPermissionLevel]{&x.Permissions}) { + return + } + } +} + +// PermissionsOf returns the permissions of the resource name of type typ. ok is false +// if there is no such resource or resources of type typ have no permissions. +func (r *Resources) PermissionsOf(typ, name string) (perms PermissionList, ok bool) { + switch typ { + case "alerts": + if x := r.Alerts[name]; x != nil { + return permissionList[resources.Permission, iam.PermissionLevel]{&x.Permissions}, true + } + case "apps": + if x := r.Apps[name]; x != nil { + return permissionList[resources.AppPermission, apps.AppPermissionLevel]{&x.Permissions}, true + } + case "cluster_policies": + if x := r.ClusterPolicies[name]; x != nil { + return permissionList[resources.ClusterPolicyPermission, compute.ClusterPolicyPermissionLevel]{&x.Permissions}, true + } + case "clusters": + if x := r.Clusters[name]; x != nil { + return permissionList[resources.ClusterPermission, compute.ClusterPermissionLevel]{&x.Permissions}, true + } + case "dashboards": + if x := r.Dashboards[name]; x != nil { + return permissionList[resources.Permission, iam.PermissionLevel]{&x.Permissions}, true + } + case "database_instances": + if x := r.DatabaseInstances[name]; x != nil { + return permissionList[resources.Permission, iam.PermissionLevel]{&x.Permissions}, true + } + case "experiments": + if x := r.Experiments[name]; x != nil { + return permissionList[resources.MlflowExperimentPermission, ml.ExperimentPermissionLevel]{&x.Permissions}, true + } + case "genie_spaces": + if x := r.GenieSpaces[name]; x != nil { + return permissionList[resources.Permission, iam.PermissionLevel]{&x.Permissions}, true + } + case "instance_pools": + if x := r.InstancePools[name]; x != nil { + return permissionList[resources.InstancePoolPermission, compute.InstancePoolPermissionLevel]{&x.Permissions}, true + } + case "jobs": + if x := r.Jobs[name]; x != nil { + return permissionList[resources.JobPermission, jobs.JobPermissionLevel]{&x.Permissions}, true + } + case "model_serving_endpoints": + if x := r.ModelServingEndpoints[name]; x != nil { + return permissionList[resources.ModelServingEndpointPermission, serving.ServingEndpointPermissionLevel]{&x.Permissions}, true + } + case "models": + if x := r.Models[name]; x != nil { + return permissionList[resources.MlflowModelPermission, ml.RegisteredModelPermissionLevel]{&x.Permissions}, true + } + case "pipelines": + if x := r.Pipelines[name]; x != nil { + return permissionList[resources.PipelinePermission, pipelines.PipelinePermissionLevel]{&x.Permissions}, true + } + case "postgres_projects": + if x := r.PostgresProjects[name]; x != nil { + return permissionList[resources.Permission, iam.PermissionLevel]{&x.Permissions}, true + } + case "secret_scopes": + if x := r.SecretScopes[name]; x != nil { + return permissionList[resources.SecretScopePermission, resources.SecretScopePermissionLevel]{&x.Permissions}, true + } + case "sql_warehouses": + if x := r.SqlWarehouses[name]; x != nil { + return permissionList[resources.SqlWarehousePermission, sql.WarehousePermissionLevel]{&x.Permissions}, true + } + case "vector_search_endpoints": + if x := r.VectorSearchEndpoints[name]; x != nil { + return permissionList[resources.VectorSearchEndpointPermission, vectorsearch.VectorSearchEndpointPermissionLevel]{&x.Permissions}, true + } + } + return nil, false +} + +// IterateAllResourceStates calls fn for the deployed id and modified status of every +// resource that has them, in the order of IterateAllResources, until fn returns false. +func (r *Resources) IterateAllResourceStates(fn func(typ, name string, id *string, status *string) bool) { + for _, name := range sortedKeys(r.Alerts) { + if x := r.Alerts[name]; x != nil && !fn("alerts", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Apps) { + if x := r.Apps[name]; x != nil && !fn("apps", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Catalogs) { + if x := r.Catalogs[name]; x != nil && !fn("catalogs", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.ClusterPolicies) { + if x := r.ClusterPolicies[name]; x != nil && !fn("cluster_policies", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Clusters) { + if x := r.Clusters[name]; x != nil && !fn("clusters", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Dashboards) { + if x := r.Dashboards[name]; x != nil && !fn("dashboards", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.DatabaseCatalogs) { + if x := r.DatabaseCatalogs[name]; x != nil && !fn("database_catalogs", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.DatabaseInstances) { + if x := r.DatabaseInstances[name]; x != nil && !fn("database_instances", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Experiments) { + if x := r.Experiments[name]; x != nil && !fn("experiments", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.ExternalLocations) { + if x := r.ExternalLocations[name]; x != nil && !fn("external_locations", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Features) { + if x := r.Features[name]; x != nil && !fn("features", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.GenieSpaces) { + if x := r.GenieSpaces[name]; x != nil && !fn("genie_spaces", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.InstancePools) { + if x := r.InstancePools[name]; x != nil && !fn("instance_pools", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.JobRuns) { + if x := r.JobRuns[name]; x != nil && !fn("job_runs", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Jobs) { + if x := r.Jobs[name]; x != nil && !fn("jobs", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.McpServices) { + if x := r.McpServices[name]; x != nil && !fn("mcp_services", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.ModelProviderServices) { + if x := r.ModelProviderServices[name]; x != nil && !fn("model_provider_services", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.ModelServices) { + if x := r.ModelServices[name]; x != nil && !fn("model_services", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.ModelServingEndpoints) { + if x := r.ModelServingEndpoints[name]; x != nil && !fn("model_serving_endpoints", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Models) { + if x := r.Models[name]; x != nil && !fn("models", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Pipelines) { + if x := r.Pipelines[name]; x != nil && !fn("pipelines", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.PostgresBranches) { + if x := r.PostgresBranches[name]; x != nil && !fn("postgres_branches", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.PostgresCatalogs) { + if x := r.PostgresCatalogs[name]; x != nil && !fn("postgres_catalogs", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.PostgresDatabases) { + if x := r.PostgresDatabases[name]; x != nil && !fn("postgres_databases", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.PostgresEndpoints) { + if x := r.PostgresEndpoints[name]; x != nil && !fn("postgres_endpoints", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.PostgresProjects) { + if x := r.PostgresProjects[name]; x != nil && !fn("postgres_projects", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.PostgresRoles) { + if x := r.PostgresRoles[name]; x != nil && !fn("postgres_roles", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.PostgresSnapshotSchedules) { + if x := r.PostgresSnapshotSchedules[name]; x != nil && !fn("postgres_snapshot_schedules", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.PostgresSyncedTables) { + if x := r.PostgresSyncedTables[name]; x != nil && !fn("postgres_synced_tables", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.QualityMonitors) { + if x := r.QualityMonitors[name]; x != nil && !fn("quality_monitors", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.RegisteredModels) { + if x := r.RegisteredModels[name]; x != nil && !fn("registered_models", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Schemas) { + if x := r.Schemas[name]; x != nil && !fn("schemas", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.SecretScopes) { + if x := r.SecretScopes[name]; x != nil && !fn("secret_scopes", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Secrets) { + if x := r.Secrets[name]; x != nil && !fn("secrets", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.SqlWarehouses) { + if x := r.SqlWarehouses[name]; x != nil && !fn("sql_warehouses", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.SyncedDatabaseTables) { + if x := r.SyncedDatabaseTables[name]; x != nil && !fn("synced_database_tables", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.VectorSearchEndpoints) { + if x := r.VectorSearchEndpoints[name]; x != nil && !fn("vector_search_endpoints", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.VectorSearchIndexes) { + if x := r.VectorSearchIndexes[name]; x != nil && !fn("vector_search_indexes", name, &x.ID, &x.ModifiedStatus) { + return + } + } + for _, name := range sortedKeys(r.Volumes) { + if x := r.Volumes[name]; x != nil && !fn("volumes", name, &x.ID, &x.ModifiedStatus) { + return + } + } +} + +// GetOrCreateState returns the deployed id and modified status of the resource name of +// type typ, creating the resource if it does not exist (created is then true). ok is +// false if resources of type typ have no state. +func (r *Resources) GetOrCreateState(typ, name string) (id *string, status *string, created, ok bool) { + switch typ { + case "alerts": + x, exists := r.Alerts[name] + if x == nil { + if r.Alerts == nil { + r.Alerts = map[string]*resources.Alert{} + } + x = &resources.Alert{} + r.Alerts[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "apps": + x, exists := r.Apps[name] + if x == nil { + if r.Apps == nil { + r.Apps = map[string]*resources.App{} + } + x = &resources.App{} + r.Apps[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "catalogs": + x, exists := r.Catalogs[name] + if x == nil { + if r.Catalogs == nil { + r.Catalogs = map[string]*resources.Catalog{} + } + x = &resources.Catalog{} + r.Catalogs[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "cluster_policies": + x, exists := r.ClusterPolicies[name] + if x == nil { + if r.ClusterPolicies == nil { + r.ClusterPolicies = map[string]*resources.ClusterPolicy{} + } + x = &resources.ClusterPolicy{} + r.ClusterPolicies[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "clusters": + x, exists := r.Clusters[name] + if x == nil { + if r.Clusters == nil { + r.Clusters = map[string]*resources.Cluster{} + } + x = &resources.Cluster{} + r.Clusters[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "dashboards": + x, exists := r.Dashboards[name] + if x == nil { + if r.Dashboards == nil { + r.Dashboards = map[string]*resources.Dashboard{} + } + x = &resources.Dashboard{} + r.Dashboards[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "database_catalogs": + x, exists := r.DatabaseCatalogs[name] + if x == nil { + if r.DatabaseCatalogs == nil { + r.DatabaseCatalogs = map[string]*resources.DatabaseCatalog{} + } + x = &resources.DatabaseCatalog{} + r.DatabaseCatalogs[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "database_instances": + x, exists := r.DatabaseInstances[name] + if x == nil { + if r.DatabaseInstances == nil { + r.DatabaseInstances = map[string]*resources.DatabaseInstance{} + } + x = &resources.DatabaseInstance{} + r.DatabaseInstances[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "experiments": + x, exists := r.Experiments[name] + if x == nil { + if r.Experiments == nil { + r.Experiments = map[string]*resources.MlflowExperiment{} + } + x = &resources.MlflowExperiment{} + r.Experiments[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "external_locations": + x, exists := r.ExternalLocations[name] + if x == nil { + if r.ExternalLocations == nil { + r.ExternalLocations = map[string]*resources.ExternalLocation{} + } + x = &resources.ExternalLocation{} + r.ExternalLocations[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "features": + x, exists := r.Features[name] + if x == nil { + if r.Features == nil { + r.Features = map[string]*resources.Feature{} + } + x = &resources.Feature{} + r.Features[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "genie_spaces": + x, exists := r.GenieSpaces[name] + if x == nil { + if r.GenieSpaces == nil { + r.GenieSpaces = map[string]*resources.GenieSpace{} + } + x = &resources.GenieSpace{} + r.GenieSpaces[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "instance_pools": + x, exists := r.InstancePools[name] + if x == nil { + if r.InstancePools == nil { + r.InstancePools = map[string]*resources.InstancePool{} + } + x = &resources.InstancePool{} + r.InstancePools[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "job_runs": + x, exists := r.JobRuns[name] + if x == nil { + if r.JobRuns == nil { + r.JobRuns = map[string]*resources.JobRun{} + } + x = &resources.JobRun{} + r.JobRuns[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "jobs": + x, exists := r.Jobs[name] + if x == nil { + if r.Jobs == nil { + r.Jobs = map[string]*resources.Job{} + } + x = &resources.Job{} + r.Jobs[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "mcp_services": + x, exists := r.McpServices[name] + if x == nil { + if r.McpServices == nil { + r.McpServices = map[string]*resources.McpService{} + } + x = &resources.McpService{} + r.McpServices[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "model_provider_services": + x, exists := r.ModelProviderServices[name] + if x == nil { + if r.ModelProviderServices == nil { + r.ModelProviderServices = map[string]*resources.ModelProviderService{} + } + x = &resources.ModelProviderService{} + r.ModelProviderServices[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "model_services": + x, exists := r.ModelServices[name] + if x == nil { + if r.ModelServices == nil { + r.ModelServices = map[string]*resources.ModelService{} + } + x = &resources.ModelService{} + r.ModelServices[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "model_serving_endpoints": + x, exists := r.ModelServingEndpoints[name] + if x == nil { + if r.ModelServingEndpoints == nil { + r.ModelServingEndpoints = map[string]*resources.ModelServingEndpoint{} + } + x = &resources.ModelServingEndpoint{} + r.ModelServingEndpoints[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "models": + x, exists := r.Models[name] + if x == nil { + if r.Models == nil { + r.Models = map[string]*resources.MlflowModel{} + } + x = &resources.MlflowModel{} + r.Models[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "pipelines": + x, exists := r.Pipelines[name] + if x == nil { + if r.Pipelines == nil { + r.Pipelines = map[string]*resources.Pipeline{} + } + x = &resources.Pipeline{} + r.Pipelines[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "postgres_branches": + x, exists := r.PostgresBranches[name] + if x == nil { + if r.PostgresBranches == nil { + r.PostgresBranches = map[string]*resources.PostgresBranch{} + } + x = &resources.PostgresBranch{} + r.PostgresBranches[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "postgres_catalogs": + x, exists := r.PostgresCatalogs[name] + if x == nil { + if r.PostgresCatalogs == nil { + r.PostgresCatalogs = map[string]*resources.PostgresCatalog{} + } + x = &resources.PostgresCatalog{} + r.PostgresCatalogs[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "postgres_databases": + x, exists := r.PostgresDatabases[name] + if x == nil { + if r.PostgresDatabases == nil { + r.PostgresDatabases = map[string]*resources.PostgresDatabase{} + } + x = &resources.PostgresDatabase{} + r.PostgresDatabases[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "postgres_endpoints": + x, exists := r.PostgresEndpoints[name] + if x == nil { + if r.PostgresEndpoints == nil { + r.PostgresEndpoints = map[string]*resources.PostgresEndpoint{} + } + x = &resources.PostgresEndpoint{} + r.PostgresEndpoints[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "postgres_projects": + x, exists := r.PostgresProjects[name] + if x == nil { + if r.PostgresProjects == nil { + r.PostgresProjects = map[string]*resources.PostgresProject{} + } + x = &resources.PostgresProject{} + r.PostgresProjects[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "postgres_roles": + x, exists := r.PostgresRoles[name] + if x == nil { + if r.PostgresRoles == nil { + r.PostgresRoles = map[string]*resources.PostgresRole{} + } + x = &resources.PostgresRole{} + r.PostgresRoles[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "postgres_snapshot_schedules": + x, exists := r.PostgresSnapshotSchedules[name] + if x == nil { + if r.PostgresSnapshotSchedules == nil { + r.PostgresSnapshotSchedules = map[string]*resources.PostgresSnapshotSchedule{} + } + x = &resources.PostgresSnapshotSchedule{} + r.PostgresSnapshotSchedules[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "postgres_synced_tables": + x, exists := r.PostgresSyncedTables[name] + if x == nil { + if r.PostgresSyncedTables == nil { + r.PostgresSyncedTables = map[string]*resources.PostgresSyncedTable{} + } + x = &resources.PostgresSyncedTable{} + r.PostgresSyncedTables[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "quality_monitors": + x, exists := r.QualityMonitors[name] + if x == nil { + if r.QualityMonitors == nil { + r.QualityMonitors = map[string]*resources.QualityMonitor{} + } + x = &resources.QualityMonitor{} + r.QualityMonitors[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "registered_models": + x, exists := r.RegisteredModels[name] + if x == nil { + if r.RegisteredModels == nil { + r.RegisteredModels = map[string]*resources.RegisteredModel{} + } + x = &resources.RegisteredModel{} + r.RegisteredModels[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "schemas": + x, exists := r.Schemas[name] + if x == nil { + if r.Schemas == nil { + r.Schemas = map[string]*resources.Schema{} + } + x = &resources.Schema{} + r.Schemas[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "secret_scopes": + x, exists := r.SecretScopes[name] + if x == nil { + if r.SecretScopes == nil { + r.SecretScopes = map[string]*resources.SecretScope{} + } + x = &resources.SecretScope{} + r.SecretScopes[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "secrets": + x, exists := r.Secrets[name] + if x == nil { + if r.Secrets == nil { + r.Secrets = map[string]*resources.Secret{} + } + x = &resources.Secret{} + r.Secrets[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "sql_warehouses": + x, exists := r.SqlWarehouses[name] + if x == nil { + if r.SqlWarehouses == nil { + r.SqlWarehouses = map[string]*resources.SqlWarehouse{} + } + x = &resources.SqlWarehouse{} + r.SqlWarehouses[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "synced_database_tables": + x, exists := r.SyncedDatabaseTables[name] + if x == nil { + if r.SyncedDatabaseTables == nil { + r.SyncedDatabaseTables = map[string]*resources.SyncedDatabaseTable{} + } + x = &resources.SyncedDatabaseTable{} + r.SyncedDatabaseTables[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "vector_search_endpoints": + x, exists := r.VectorSearchEndpoints[name] + if x == nil { + if r.VectorSearchEndpoints == nil { + r.VectorSearchEndpoints = map[string]*resources.VectorSearchEndpoint{} + } + x = &resources.VectorSearchEndpoint{} + r.VectorSearchEndpoints[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "vector_search_indexes": + x, exists := r.VectorSearchIndexes[name] + if x == nil { + if r.VectorSearchIndexes == nil { + r.VectorSearchIndexes = map[string]*resources.VectorSearchIndex{} + } + x = &resources.VectorSearchIndex{} + r.VectorSearchIndexes[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + case "volumes": + x, exists := r.Volumes[name] + if x == nil { + if r.Volumes == nil { + r.Volumes = map[string]*resources.Volume{} + } + x = &resources.Volume{} + r.Volumes[name] = x + } + return &x.ID, &x.ModifiedStatus, !exists, true + default: + return nil, nil, false, false + } +} + +// IterateAllGrants calls fn for the grants of every resource that can have them, in +// the order of IterateAllResources, until fn returns false. +func (r *Resources) IterateAllGrants(fn func(typ, name string, grants *[]catalog.PrivilegeAssignment) bool) { + for _, name := range sortedKeys(r.Catalogs) { + if x := r.Catalogs[name]; x != nil && !fn("catalogs", name, &x.Grants) { + return + } + } + for _, name := range sortedKeys(r.ExternalLocations) { + if x := r.ExternalLocations[name]; x != nil && !fn("external_locations", name, &x.Grants) { + return + } + } + for _, name := range sortedKeys(r.McpServices) { + if x := r.McpServices[name]; x != nil && !fn("mcp_services", name, &x.Grants) { + return + } + } + for _, name := range sortedKeys(r.ModelProviderServices) { + if x := r.ModelProviderServices[name]; x != nil && !fn("model_provider_services", name, &x.Grants) { + return + } + } + for _, name := range sortedKeys(r.ModelServices) { + if x := r.ModelServices[name]; x != nil && !fn("model_services", name, &x.Grants) { + return + } + } + for _, name := range sortedKeys(r.RegisteredModels) { + if x := r.RegisteredModels[name]; x != nil && !fn("registered_models", name, &x.Grants) { + return + } + } + for _, name := range sortedKeys(r.Schemas) { + if x := r.Schemas[name]; x != nil && !fn("schemas", name, &x.Grants) { + return + } + } + for _, name := range sortedKeys(r.Secrets) { + if x := r.Secrets[name]; x != nil && !fn("secrets", name, &x.Grants) { + return + } + } + for _, name := range sortedKeys(r.VectorSearchIndexes) { + if x := r.VectorSearchIndexes[name]; x != nil && !fn("vector_search_indexes", name, &x.Grants) { + return + } + } + for _, name := range sortedKeys(r.Volumes) { + if x := r.Volumes[name]; x != nil && !fn("volumes", name, &x.Grants) { + return + } + } +} diff --git a/bundle/config/resources_permissions.go b/bundle/config/resources_permissions.go new file mode 100644 index 00000000000..0b4bddb8fd9 --- /dev/null +++ b/bundle/config/resources_permissions.go @@ -0,0 +1,96 @@ +package config + +import ( + "maps" + "slices" + + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go/service/catalog" + "github.com/databricks/databricks-sdk-go/service/iam" +) + +// PermissionList is the permissions of a resource. Their types differ between +// resources, but all have the fields of [resources.PermissionFields]. +type PermissionList interface { + // IsNil reports whether the permissions are not set. + IsNil() bool + Len() int + // At returns permission i. + At(i int) resources.Permission + // Append appends p. + Append(p resources.Permission) + // Set replaces the permissions with ps. + Set(ps []resources.Permission) +} + +// permissionList is the [PermissionList] of a resource's permissions of type []P. +type permissionList[P ~resources.PermissionFields[L], L ~string] struct { + p *[]P +} + +func (l permissionList[P, L]) IsNil() bool { + return *l.p == nil +} + +func (l permissionList[P, L]) Len() int { + return len(*l.p) +} + +func (l permissionList[P, L]) At(i int) resources.Permission { + f := resources.PermissionFields[L]((*l.p)[i]) + return resources.Permission{ + Level: iam.PermissionLevel(f.Level), + UserName: f.UserName, + ServicePrincipalName: f.ServicePrincipalName, + GroupName: f.GroupName, + } +} + +func (l permissionList[P, L]) Set(ps []resources.Permission) { + *l.p = make([]P, 0, len(ps)) + for _, p := range ps { + l.Append(p) + } +} + +func (l permissionList[P, L]) Append(p resources.Permission) { + *l.p = append(*l.p, P(resources.PermissionFields[L]{ + Level: L(p.Level), + UserName: p.UserName, + ServicePrincipalName: p.ServicePrincipalName, + GroupName: p.GroupName, + })) +} + +// sortedKeys returns the keys of a resource map in iteration order. +func sortedKeys[V any](m map[string]V) []string { + return slices.Sorted(maps.Keys(m)) +} + +// ResourceNodes returns the keys of all resources and of their permissions and grants +// where those are present ("resources.jobs.foo", "resources.jobs.foo.permissions"): +// first all resources, then all permissions, then all grants. Permissions and grants +// are present if they are set or hold a pure reference. +func (r *Root) ResourceNodes() []string { + var nodes []string + r.Resources.IterateAllResources(func(typ, name string, _ ConfigResource) bool { + nodes = append(nodes, structpath.NewPath(nil, "resources", typ, name).String()) + return true + }) + r.Resources.IterateAllPermissions(func(typ, name string, perms PermissionList) bool { + p := structpath.NewPath(nil, "resources", typ, name, "permissions") + if !perms.IsNil() || r.IsReferenceAt(p) { + nodes = append(nodes, p.String()) + } + return true + }) + r.Resources.IterateAllGrants(func(typ, name string, grants *[]catalog.PrivilegeAssignment) bool { + p := structpath.NewPath(nil, "resources", typ, name, "grants") + if *grants != nil || r.IsReferenceAt(p) { + nodes = append(nodes, p.String()) + } + return true + }) + return nodes +} diff --git a/bundle/config/resources_types_test.go b/bundle/config/resources_types_test.go index 7a698604612..d2aadd7f530 100644 --- a/bundle/config/resources_types_test.go +++ b/bundle/config/resources_types_test.go @@ -3,6 +3,7 @@ package config import ( "encoding/json" "fmt" + "maps" "reflect" "slices" "strings" @@ -12,10 +13,9 @@ import ( "github.com/stretchr/testify/require" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/structs/structtag" + "github.com/databricks/cli/libs/structs/structvar" "github.com/databricks/cli/libs/structs/structwalk" ) @@ -32,14 +32,14 @@ func TestResourcesTypesMap(t *testing.T) { } // TestResourceTypesZeroValueFieldsSerialize guards against the ForceSendFields -// routing bug fixed in libs/dyn/convert: a field declared in a struct embedded +// routing bug: a field declared in a struct embedded // more than one level deep (e.g. PostgresProject -> PostgresProjectConfig -> // ProjectSpec) had its zero value recorded in the wrong struct's ForceSendFields, // which the SDK marshaler rejects with "field X cannot be found in struct Y". // The direct engine hits this path when it serializes planned state to JSON. // // For every registered resource type it sets every omitempty scalar field (at any -// depth) to its zero value, converts via ToTyped, and marshals - the same round +// depth) to its zero value, decodes it into the typed value, and marshals - the same round // trip the direct engine performs. Any newly added resource whose wrapper embeds // an SDK spec is covered automatically. func TestResourceTypesZeroValueFieldsSerialize(t *testing.T) { @@ -53,41 +53,38 @@ func TestResourceTypesZeroValueFieldsSerialize(t *testing.T) { t.Run(name, func(t *testing.T) { typ := ResourcesTypes[name] zeros := zeroValueScalars(typ, 0, map[reflect.Type]bool{}) - if zeros.Kind() != dyn.KindMap { + if zeros == nil { return } ptr := reflect.New(typ) - require.NoError(t, convert.ToTyped(ptr.Interface(), zeros)) + _, err := (&structvar.StructVar{Value: ptr.Interface()}).Assign(nil, structvar.NewView(&zeros, nil, nil)) + require.NoError(t, err) - _, err := json.Marshal(ptr.Interface()) + _, err = json.Marshal(ptr.Interface()) require.NoError(t, err) }) } } -// zeroValueScalars builds a [dyn.Value] map that sets every omitempty scalar field +// zeroValueScalars builds a map that sets every omitempty scalar field // reachable through embedded anonymous structs to its zero value. Those are exactly // the fields the convert layer records in ForceSendFields, so they exercise the // routing logic. depth and seen bound recursion against deep or recursive types. -func zeroValueScalars(t reflect.Type, depth int, seen map[reflect.Type]bool) dyn.Value { +func zeroValueScalars(t reflect.Type, depth int, seen map[reflect.Type]bool) map[string]any { for t.Kind() == reflect.Pointer { t = t.Elem() } if t.Kind() != reflect.Struct || depth > 6 || seen[t] { - return dyn.NilValue + return nil } seen[t] = true defer delete(seen, t) - m := dyn.NewMapping() + m := map[string]any{} for f := range t.Fields() { if f.Anonymous { - if sub := zeroValueScalars(f.Type, depth+1, seen); sub.Kind() == dyn.KindMap { - for _, p := range sub.MustMap().Pairs() { - m.SetLoc(p.Key.MustString(), nil, p.Value) - } - } + maps.Copy(m, zeroValueScalars(f.Type, depth+1, seen)) continue } @@ -99,18 +96,18 @@ func zeroValueScalars(t reflect.Type, depth int, seen map[reflect.Type]bool) dyn switch f.Type.Kind() { case reflect.Bool: - m.SetLoc(name, nil, dyn.V(false)) + m[name] = false case reflect.String: - m.SetLoc(name, nil, dyn.V("")) + m[name] = "" case reflect.Int, reflect.Int32, reflect.Int64: - m.SetLoc(name, nil, dyn.V(int64(0))) + m[name] = int64(0) case reflect.Float32, reflect.Float64: - m.SetLoc(name, nil, dyn.V(float64(0))) + m[name] = float64(0) default: // Only basic types are eligible for ForceSendFields; skip the rest. } } - return dyn.V(m) + return m } // TestNoSameDepthJSONShadows uses structwalk.WalkType — the dumb walker that diff --git a/bundle/config/root.go b/bundle/config/root.go index a64b742630e..9ee7489f988 100644 --- a/bundle/config/root.go +++ b/bundle/config/root.go @@ -2,24 +2,23 @@ package config import ( "bytes" - "context" "errors" "fmt" + "io" + "iter" "os" "reflect" + "slices" "strings" + "github.com/databricks/cli/bundle/config/loctable" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/config/variable" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/dynloc" - "github.com/databricks/cli/libs/dyn/merge" - "github.com/databricks/cli/libs/dyn/yamlloader" - "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" "github.com/databricks/databricks-sdk-go/service/jobs" + "go.yaml.in/yaml/v3" ) type Script struct { @@ -35,8 +34,12 @@ type Script struct { } type Root struct { //nolint:recvcheck // value receivers for read-only accessors, pointer for mutators - value dyn.Value - depth int + // refs holds the pure variable references in fields that cannot hold a string + // (e.g. `max_retries: ${var.n}`, where the typed field is zero), keyed by path. + refs map[string]string + + // locations holds the source locations of the values. + locations *structvar.Locations // Contains user defined variables Variables map[string]*variable.Variable `json:"variables,omitempty"` @@ -88,7 +91,7 @@ type Root struct { //nolint:recvcheck // value receivers for read-only accessors // Locations is an output-only field that holds configuration location // information for every path in the configuration tree. - Locations *dynloc.Locations `json:"__locations,omitempty" bundle:"internal"` + Locations *loctable.Locations `json:"__locations,omitempty" bundle:"internal"` Scripts map[string]Script `json:"scripts,omitempty"` @@ -110,161 +113,55 @@ func Load(path string) (*Root, diag.Diagnostics) { } func LoadFromBytes(path string, raw []byte) (*Root, diag.Diagnostics) { - r := Root{} + node, err := structvar.ParseYAML(bytes.NewReader(raw)) + if err != nil { + return nil, diag.Errorf("failed to load %s: %v", path, err) + } - // Load configuration tree from YAML. - v, err := yamlloader.LoadYAML(path, bytes.NewBuffer(raw)) + // Rewrite configuration tree where necessary. + rewriteShorthands(node) + + var r Root + sv, diags, err := structvar.DecodeYAMLNode(path, node, &r, nil) if err != nil { - if le, ok := errors.AsType[*yamlloader.LocationError](err); ok { + if le, ok := errors.AsType[*structvar.LocationError](err); ok { return nil, diag.Diagnostics{{ Severity: diag.Error, Summary: le.Summary, Locations: []diag.Location{le.Loc}, }} } - return nil, diag.Errorf("failed to load %s: %v", path, err) - } - - // Rewrite configuration tree where necessary. - v, err = rewriteShorthands(v) - if err != nil { - return nil, diag.Errorf("failed to rewrite %s: %v", path, err) - } - - // Normalize dynamic configuration tree according to configuration type. - v, diags := convert.Normalize(r, v) - - // Convert normalized configuration tree to typed configuration. - err = r.updateWithDynamicValue(v) - if err != nil { - diags = diags.Extend(diag.Errorf("failed to load %s: %v", path, err)) - return nil, diags + return nil, diags.Extend(diag.Errorf("failed to load %s: %v", path, err)) } + r.store(sv) return &r, diags } -func (r *Root) initializeDynamicValue() error { - // Many test cases initialize a config as a Go struct literal. - // The value will be invalid and we need to populate it from the typed configuration. - if r.value.IsValid() { - return nil - } - - nv, err := convert.FromTyped(r, dyn.NilValue) +// LoadFromReader decodes the configuration in r, recording the locations mapLocations +// returns (if not nil) instead of the locations in the file. +func LoadFromReader(path string, r io.Reader, mapLocations structvar.LocationMapper) (*Root, diag.Diagnostics, error) { + node, err := structvar.ParseYAML(r) if err != nil { - return err + return nil, nil, err } - - r.value = nv - return nil -} - -func (r *Root) updateWithDynamicValue(nv dyn.Value) error { - // Hack: restore state; it may be cleared by [ToTyped] if - // the configuration equals nil (happens in tests). - depth := r.depth - - defer func() { - r.depth = depth - }() - - // Convert normalized configuration tree to typed configuration. - err := convert.ToTyped(r, nv) + var root Root + sv, diags, err := structvar.DecodeYAMLNode(path, node, &root, mapLocations) if err != nil { - return err + return nil, diags, err } - - // Assign the normalized configuration tree. - r.value = nv - return nil + root.store(sv) + return &root, diags, nil } -// Mutate applies a transformation to the dynamic configuration value of a Root object. -// -// Parameters: -// - fn: A function that mutates a dyn.Value object -// -// Example usage, setting bundle.deployment.lock.enabled to false: -// -// err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { -// return dyn.Map(v, "bundle.deployment.lock", func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { -// return dyn.Set(v, "enabled", dyn.V(false)) -// }) -// }) -func (r *Root) Mutate(fn func(dyn.Value) (dyn.Value, error)) error { - err := r.initializeDynamicValue() - if err != nil { - return err - } - nv, err := fn(r.value) - if err != nil { - return err - } - err = r.updateWithDynamicValue(nv) - if err != nil { - return err - } - return nil +// vars returns the configuration as a [structvar.StructVar]; use [Root.store] to +// keep changes made to its references and locations. +func (r *Root) vars() *structvar.StructVar { + return &structvar.StructVar{Value: r, Refs: r.refs, Locations: r.locations} } -func (r *Root) MarkMutatorEntry(ctx context.Context) error { - err := r.initializeDynamicValue() - if err != nil { - return err - } - - r.depth++ - - // If we are entering a mutator at depth 1, we need to convert - // the dynamic configuration tree to typed configuration. - if r.depth == 1 { - // Always run ToTyped upon entering a mutator. - // Convert normalized configuration tree to typed configuration. - err := r.updateWithDynamicValue(r.value) - if err != nil { - log.Warnf(ctx, "unable to convert dynamic configuration to typed configuration: %v", err) - return err - } - - } else { - nv, err := convert.FromTyped(r, r.value) - if err != nil { - log.Warnf(ctx, "unable to convert typed configuration to dynamic configuration: %v", err) - return err - } - - // Re-run ToTyped to ensure that no state is piggybacked - err = r.updateWithDynamicValue(nv) - if err != nil { - log.Warnf(ctx, "unable to convert dynamic configuration to typed configuration: %v", err) - return err - } - } - - return nil -} - -func (r *Root) MarkMutatorExit(ctx context.Context) error { - r.depth-- - - // If we are exiting a mutator at depth 0, we need to convert - // the typed configuration to a dynamic configuration tree. - if r.depth == 0 { - nv, err := convert.FromTyped(r, r.value) - if err != nil { - log.Warnf(ctx, "unable to convert typed configuration to dynamic configuration: %v", err) - return err - } - - // Re-run ToTyped to ensure that no state is piggybacked - err = r.updateWithDynamicValue(nv) - if err != nil { - log.Warnf(ctx, "unable to convert dynamic configuration to typed configuration: %v", err) - return err - } - } - - return nil +func (r *Root) store(sv *structvar.StructVar) { + r.refs = sv.Refs + r.locations = sv.Locations } // Initializes variables using values passed from the command line flag @@ -295,52 +192,34 @@ func (r *Root) InitializeVariables(vars []string) error { return nil } -func (r *Root) Merge(other *Root) error { - // Merge dynamic configuration values. - return r.Mutate(func(root dyn.Value) (dyn.Value, error) { - return merge.Merge(root, other.value) - }) -} - -func mergeField(rv, ov dyn.Value, name string) (dyn.Value, error) { - path := dyn.NewPath(dyn.Key(name)) - reference, _ := dyn.GetByPath(rv, path) - override, _ := dyn.GetByPath(ov, path) - - // Merge the override into the reference. - var out dyn.Value - var err error - if reference.IsValid() && override.IsValid() { - out, err = merge.Merge(reference, override) - if err != nil { - return dyn.InvalidValue, err +// Merge merges the other configurations into this one, in order. +func (r *Root) Merge(others ...*Root) error { + for _, other := range others { + if err := r.MergeAt(nil, other.View()); err != nil { + return err } - } else if reference.IsValid() { - out = reference - } else if override.IsValid() { - out = override - } else { - return rv, nil } - - return dyn.SetByPath(rv, path, out) + return nil } +var bundleGitPath = structpath.NewPath(nil, "bundle", "git") + func (r *Root) MergeTargetOverrides(name string) error { - root := r.value - target, err := dyn.GetByPath(root, dyn.NewPath(dyn.Key("targets"), dyn.Key(name))) - if err != nil { - return err + targetPath := structpath.NewPath(nil, "targets", name) + target := r.View().Lookup(targetPath) + if !target.IsValid() { + return fmt.Errorf("target %s not found", name) } // Confirm validity of variable overrides. - err = validateVariableOverrides(root, target) + err := validateVariableOverrides(r.Variables, r.Targets[name]) if err != nil { return err } - // Merge fields that can be merged 1:1. - for _, f := range []string{ + // Merge fields that can be merged 1:1. Check all of them first so that a failed + // merge leaves the configuration unchanged. + fields := []string{ "bundle", "workspace", "artifacts", @@ -348,106 +227,73 @@ func (r *Root) MergeTargetOverrides(name string) error { "sync", "permissions", "presets", - } { - if root, err = mergeField(root, target, f); err != nil { + } + for _, f := range fields { + if err := structvar.CheckMerge(r.View().Get(f), target.Get(f)); err != nil { + return fmt.Errorf("failed to merge target=%s field=%s: %w", name, f, err) + } + } + if err := structvar.CheckMerge(r.View().Lookup(bundleGitPath), target.Get("git")); err != nil { + return err + } + for _, f := range fields { + if err := r.MergeAt(structpath.NewStringKey(nil, f), target.Get(f)); err != nil { return fmt.Errorf("failed to merge target=%s field=%s: %w", name, f, err) } } // Merge `variables`. This field must be overwritten if set, not merged. - if v := target.Get("variables"); v.Kind() != dyn.KindInvalid { - _, err = dyn.Map(v, ".", dyn.Foreach(func(p dyn.Path, variable dyn.Value) (dyn.Value, error) { - varPath := dyn.MustPathFromString("variables").Append(p...) - - vDefault := variable.Get("default") - if vDefault.Kind() != dyn.KindInvalid { - defaultPath := varPath.Append(dyn.Key("default")) - root, err = dyn.SetByPath(root, defaultPath, vDefault) - if err != nil { - return root, err - } + for varName, variable := range target.Get("variables").MapItems() { + varPath := structpath.NewPath(nil, "variables", varName) - // If the target explicitly sets a default value, drop any lookup from the - // root variable definition so SetVariables can assign this default. - lookupPath := varPath.Append(dyn.Key("lookup")) - root, err = dyn.SetByPath(root, lookupPath, dyn.NilValue) + if vDefault := variable.Get("default"); vDefault.IsValid() { + if err := r.Assign(structpath.NewStringKey(varPath, "default"), vDefault); err != nil { + return err } - vLookup := variable.Get("lookup") - if vLookup.Kind() != dyn.KindInvalid { - lookupPath := varPath.Append(dyn.Key("lookup")) - root, err = dyn.SetByPath(root, lookupPath, vLookup) - if err != nil { - return root, err - } + // If the target explicitly sets a default value, drop any lookup from the + // root variable definition so SetVariables can assign this default. + if err := r.Delete(structpath.NewStringKey(varPath, "lookup")); err != nil { + return err + } + } - // If the target explicitly sets a lookup, drop any default value from the - // root variable definition so lookup resolution remains authoritative. - defaultPath := varPath.Append(dyn.Key("default")) - root, err = dyn.SetByPath(root, defaultPath, dyn.NilValue) + if vLookup := variable.Get("lookup"); vLookup.IsValid() { + if err := r.Assign(structpath.NewStringKey(varPath, "lookup"), vLookup); err != nil { + return err } - return root, err - })) - if err != nil { - return err + // If the target explicitly sets a lookup, drop any default value from the + // root variable definition so lookup resolution remains authoritative. + if err := r.Delete(structpath.NewStringKey(varPath, "default")); err != nil { + return err + } } } // Merge `run_as`. This field must be overwritten if set, not merged. - if v := target.Get("run_as"); v.Kind() != dyn.KindInvalid { - root, err = dyn.Set(root, "run_as", v) - if err != nil { - return err - } - } - - // Below, we're setting fields on the bundle key, so make sure it exists. - if root.Get("bundle").Kind() == dyn.KindInvalid { - root, err = dyn.Set(root, "bundle", dyn.V(map[string]dyn.Value{})) - if err != nil { + if v := target.Get("run_as"); v.IsValid() { + if err := r.Assign(structpath.NewStringKey(nil, "run_as"), v); err != nil { return err } } // Merge `mode`. This field must be overwritten if set, not merged. - if v := target.Get("mode"); v.Kind() != dyn.KindInvalid { - root, err = dyn.SetByPath(root, dyn.NewPath(dyn.Key("bundle"), dyn.Key("mode")), v) - if err != nil { + if v := target.Get("mode"); v.IsValid() { + if err := r.Assign(structpath.NewPath(nil, "bundle", "mode"), v); err != nil { return err } } // Merge `cluster_id`. This field must be overwritten if set, not merged. - if v := target.Get("cluster_id"); v.Kind() != dyn.KindInvalid { - root, err = dyn.SetByPath(root, dyn.NewPath(dyn.Key("bundle"), dyn.Key("cluster_id")), v) - if err != nil { + if v := target.Get("cluster_id"); v.IsValid() { + if err := r.Assign(structpath.NewPath(nil, "bundle", "cluster_id"), v); err != nil { return err } } // Merge `git`. - if v := target.Get("git"); v.Kind() != dyn.KindInvalid { - ref, err := dyn.GetByPath(root, dyn.NewPath(dyn.Key("bundle"), dyn.Key("git"))) - if err != nil { - ref = dyn.V(map[string]dyn.Value{}) - } - - // Merge the override into the reference. - out, err := merge.Merge(ref, v) - if err != nil { - return err - } - - // Set the merged value. - root, err = dyn.SetByPath(root, dyn.NewPath(dyn.Key("bundle"), dyn.Key("git")), out) - if err != nil { - return err - } - } - - // Convert normalized configuration tree to typed configuration. - return r.updateWithDynamicValue(root) + return r.MergeAt(bundleGitPath, target.Get("git")) } var allowedVariableDefinitions = []([]string){ @@ -459,30 +305,25 @@ var allowedVariableDefinitions = []([]string){ {"lookup"}, } -// isFullVariableOverrideDef checks if the given value is a full syntax variable override. -// A full syntax variable override is a map with either 1 of 2 keys. +// isFullVariableOverrideDef checks if a mapping with the given keys is a full syntax +// variable override. A full syntax variable override is a map with either 1 of 2 keys. // If it's 2 keys, the keys should be "default" and "type". // If it's 1 key, the key should be one of the following keys: "default", "lookup". -func isFullVariableOverrideDef(v dyn.Value) bool { - mv, ok := v.AsMap() - if !ok { - return false - } - +func isFullVariableOverrideDef(keys []string) bool { // If the map has more than 3 keys, it is not a full variable override. - if mv.Len() > 3 { + if len(keys) > 3 { return false } - for _, keys := range allowedVariableDefinitions { - if len(keys) != mv.Len() { + for _, allowed := range allowedVariableDefinitions { + if len(allowed) != len(keys) { continue } // Check if the keys are the same. match := true - for _, key := range keys { - if _, ok := mv.GetByString(key); !ok { + for _, key := range allowed { + if !slices.Contains(keys, key) { match = false break } @@ -496,141 +337,290 @@ func isFullVariableOverrideDef(v dyn.Value) bool { return false } +// dealias returns the node an alias refers to, or node itself. +func dealias(node *yaml.Node) *yaml.Node { + for node != nil && node.Kind == yaml.AliasNode { + node = node.Alias + } + return node +} + +// mappingValue returns the value of key in the YAML mapping node (following aliases). +func mappingValue(node *yaml.Node, key string) *yaml.Node { + node = dealias(node) + if node == nil || node.Kind != yaml.MappingNode { + return nil + } + for i := 0; i+1 < len(node.Content); i += 2 { + if node.Content[i].Value == key { + return dealias(node.Content[i+1]) + } + } + return nil +} + +// expandMergeKeys returns a copy of the mapping node with "<<" merge keys replaced +// by the pairs they merge in, so the pairs can be rewritten individually. +func expandMergeKeys(node *yaml.Node) *yaml.Node { + var merged, explicit []*yaml.Node + for i := 0; i+1 < len(node.Content); i += 2 { + if node.Content[i].Value != "<<" || node.Content[i].ShortTag() != "!!merge" { + explicit = append(explicit, node.Content[i], node.Content[i+1]) + continue + } + sources := []*yaml.Node{dealias(node.Content[i+1])} + if sources[0].Kind == yaml.SequenceNode { + sources = sources[0].Content + } + for _, src := range sources { + if src = dealias(src); src.Kind == yaml.MappingNode { + merged = append(merged, expandMergeKeys(src).Content...) + } + } + } + if merged == nil { + return node + } + // Like the YAML decoder, a later merged map takes precedence over an earlier one and + // keys set explicitly take precedence over all merged maps. + out := *node + out.Content = nil + for _, kvs := range [][]*yaml.Node{merged, explicit} { + for i := 0; i+1 < len(kvs); i += 2 { + setMappingValue(&out, kvs[i], kvs[i+1]) + } + } + return &out +} + +// setMappingValue replaces the value of key in the mapping node, or appends the pair. +func setMappingValue(node, key, value *yaml.Node) { + for i := 0; i+1 < len(node.Content); i += 2 { + if node.Content[i].Value == key.Value { + node.Content[i+1] = value + return + } + } + node.Content = append(node.Content, key, value) +} + +func mappingKeys(node *yaml.Node) []string { + var keys []string + for i := 0; i+1 < len(node.Content); i += 2 { + keys = append(keys, node.Content[i].Value) + } + return keys +} + +// mappingNode returns a mapping node with the given keys and values, at the location of at. +func mappingNode(at *yaml.Node, kvs ...any) *yaml.Node { + out := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Line: at.Line, Column: at.Column} + for i := 0; i < len(kvs); i += 2 { + out.Content = append(out.Content, &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: kvs[i].(string)}, kvs[i+1].(*yaml.Node)) + } + return out +} + // rewriteShorthands performs lightweight rewriting of the configuration // tree where we allow users to write a shorthand and must rewrite to the full form. -func rewriteShorthands(v dyn.Value) (dyn.Value, error) { - if v.Kind() != dyn.KindMap { - return v, nil +func rewriteShorthands(root *yaml.Node) { + targets := mappingValue(root, "targets") + if targets == nil || targets.Kind != yaml.MappingNode { + return } // For each target, rewrite the variables block. - return dyn.Map(v, "targets", dyn.Foreach(func(_ dyn.Path, target dyn.Value) (dyn.Value, error) { - // Confirm it has a variables block. - if target.Get("variables").Kind() == dyn.KindInvalid { - return target, nil + for i := 1; i < len(targets.Content); i += 2 { + variables := mappingValue(targets.Content[i], "variables") + if variables == nil || variables.Kind != yaml.MappingNode { + continue } + *variables = *expandMergeKeys(variables) - // For each variable, normalize its contents if it is a single string. - return dyn.Map(target, "variables", dyn.Foreach(func(p dyn.Path, variable dyn.Value) (dyn.Value, error) { - switch variable.Kind() { - - case dyn.KindString, dyn.KindBool, dyn.KindFloat, dyn.KindInt: + for j := 0; j+1 < len(variables.Content); j += 2 { + name := variables.Content[j].Value + variable := dealias(variables.Content[j+1]) + switch { + case variable.Kind == yaml.ScalarNode && variable.ShortTag() != "!!null": // Rewrite the variable to a map with a single key called "default". // This conforms to the variable type. Normalization back to the typed // configuration will convert this to a string if necessary. - return dyn.NewValue(map[string]dyn.Value{ - "default": variable, - }, variable.Locations()), nil + variables.Content[j+1] = mappingNode(variable, "default", variable) - case dyn.KindMap, dyn.KindSequence: + case variable.Kind == yaml.MappingNode || variable.Kind == yaml.SequenceNode: // If it's a full variable definition, leave it as is. - if isFullVariableOverrideDef(variable) { - return variable, nil + if variable.Kind == yaml.MappingNode && isFullVariableOverrideDef(mappingKeys(expandMergeKeys(variable))) { + continue } // Check if the original definition of variable has a type field. // If it has a type field, it means the shorthand is a value of a complex type. // Type might not be found if the variable overridden in a separate file // and configuration is not merged yet. - typeV, err := dyn.GetByPath(v, p.Append(dyn.Key("type"))) - if err == nil && typeV.MustString() == "complex" { - return dyn.NewValue(map[string]dyn.Value{ - "type": typeV, - "default": variable, - }, variable.Locations()), nil + typeV := mappingValue(mappingValue(mappingValue(root, "variables"), name), "type") + if typeV != nil && typeV.Value == "complex" { + variables.Content[j+1] = mappingNode(variable, "type", typeV, "default", variable) + continue } // If it's a shorthand, rewrite it to a full variable definition. - return dyn.NewValue(map[string]dyn.Value{ - "default": variable, - }, variable.Locations()), nil - + variables.Content[j+1] = mappingNode(variable, "default", variable) default: - return variable, nil } - })) - })) + } + } } // validateVariableOverrides checks that all variables specified // in the target override are also defined in the root. -func validateVariableOverrides(root, target dyn.Value) (err error) { - var rv map[string]variable.Variable - var tv map[string]variable.Variable - - // Collect variables from the root. - if v := root.Get("variables"); v.Kind() != dyn.KindInvalid { - err = convert.ToTyped(&rv, v) - if err != nil { - return fmt.Errorf("unable to collect variables from root: %w", err) - } - } - - // Collect variables from the target. - if v := target.Get("variables"); v.Kind() != dyn.KindInvalid { - err = convert.ToTyped(&tv, v) - if err != nil { - return fmt.Errorf("unable to collect variables from target: %w", err) - } +func validateVariableOverrides(root map[string]*variable.Variable, target *Target) error { + if target == nil { + return nil } - - // Check that all variables in the target exist in the root. - for k := range tv { - if _, ok := rv[k]; !ok { + for k := range target.Variables { + if _, ok := root[k]; !ok { return fmt.Errorf("variable %s is not defined but is assigned a value", k) } } - return nil } -// Best effort to get the location of configuration value at the specified path. -// This function is useful to annotate error messages with the location, because -// we don't want to fail with a different error message if we cannot retrieve the location. +// Set sets value at path (see [structvar.StructVar.Set]). +func (r *Root) Set(path *structpath.PathNode, value any) error { + sv := r.vars() + defer r.store(sv) + return sv.Set(path, value) +} + +// SetReference records the pure reference ref at path, a field that cannot hold a string. +func (r *Root) SetReference(path *structpath.PathNode, ref string) error { + sv := r.vars() + defer r.store(sv) + return sv.SetReference(path, ref) +} + +// Assign sets the value described by v at path, with its locations and references. +func (r *Root) Assign(path *structpath.PathNode, v structvar.View) error { + _, err := r.Decode(path, v) + return err +} + +// Decode sets the value described by v at path, converting it to the type at path; +// the diagnostics explain values that could not be converted and were dropped. +func (r *Root) Decode(path *structpath.PathNode, v structvar.View) (diag.Diagnostics, error) { + sv := r.vars() + defer r.store(sv) + return sv.Assign(path, v) +} + +// Delete removes the value at path (see [structvar.StructVar.Delete]). +func (r *Root) Delete(path *structpath.PathNode) error { + sv := r.vars() + defer r.store(sv) + return sv.Delete(path) +} + +// MergeAt merges the value described by v into the value at path (see [structvar.StructVar.Merge]). +func (r *Root) MergeAt(path *structpath.PathNode, v structvar.View) error { + sv := r.vars() + defer r.store(sv) + return sv.Merge(path, v) +} + +// MergeElementsByKey merges the elements of the sequence at path that have the same +// key (see [structvar.StructVar.MergeElementsByKey]). +func (r *Root) MergeElementsByKey(path *structpath.PathNode, keyField string, keyFn func(structvar.View) string, sortKeys bool) error { + sv := r.vars() + defer r.store(sv) + return sv.MergeElementsByKey(path, keyField, keyFn, sortKeys) +} + +// SetLocations sets the locations of the value at path and all values below it. +func (r *Root) SetLocations(path *structpath.PathNode, locs []diag.Location) { + sv := r.vars() + sv.SetLocations(path, locs) + r.store(sv) +} + +// CopyMetadata replaces the locations and references of the value at path with those in +// src (see [structvar.StructVar.CopyMetadata]). +func (r *Root) CopyMetadata(src *Root, path *structpath.PathNode) { + sv := r.vars() + sv.CopyMetadata(src.vars(), path) + r.store(sv) +} + +// MoveMetadata moves the locations and references of the value at path from to the +// path to (see [structvar.StructVar.MoveMetadata]). +func (r *Root) MoveMetadata(from, to *structpath.PathNode) { + sv := r.vars() + sv.MoveMetadata(from, to) + r.store(sv) +} + +// CopyMetadataAt copies the locations and references of the value at path from to the +// path to (see [structvar.StructVar.CopyMetadataAt]). +func (r *Root) CopyMetadataAt(from, to *structpath.PathNode) { + sv := r.vars() + sv.CopyMetadataAt(from, to) + r.store(sv) +} + +// UpdateSequence records that the elements of the sequence at path were rebuilt from +// the old ones (see [structvar.StructVar.UpdateSequence]). +func (r *Root) UpdateSequence(path *structpath.PathNode, sources [][]int) { + sv := r.vars() + sv.UpdateSequence(path, sources) + r.store(sv) +} + +// GetLocation is [Root.GetLocationOf] for a path string, like main's. func (r Root) GetLocation(path string) diag.Location { - v, err := dyn.Get(r.value, path) + p, err := structpath.ParsePath(path) if err != nil { return diag.Location{} } - return v.Location() + return r.GetLocationOf(p) } -// 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. +// GetLocations is [Root.GetLocationsOf] for a path string, like main's. func (r Root) GetLocations(path string) []diag.Location { - v, err := dyn.Get(r.value, path) + p, err := structpath.ParsePath(path) if err != nil { return nil } - return v.Locations() + return r.GetLocationsOf(p) } -// GetLocationOf is [Root.GetLocation] for a path node. +// GetLocationsOf returns the locations of the configuration value at path or, if it +// has none (e.g. it was set by a mutator), those of its closest ancestor that has some. +// Use it to point diagnostics at the most specific place there is; use +// [Root.GetLocationsOfExact] to find where a value is written. +func (r Root) GetLocationsOf(path *structpath.PathNode) []diag.Location { + return r.locations.Nearest(path) +} + +// GetLocationOf returns the primary location of [Root.GetLocationsOf]. func (r Root) GetLocationOf(path *structpath.PathNode) diag.Location { - v, ok := r.valueOf(path) - if !ok { - return diag.Location{} - } - return v.Location() + return firstLocation(r.GetLocationsOf(path)) } -// GetLocationsOf is [Root.GetLocations] for a path node. -func (r Root) GetLocationsOf(path *structpath.PathNode) []diag.Location { - v, ok := r.valueOf(path) - if !ok { - return nil - } - return v.Locations() +// GetLocationsOfExact returns the locations the configuration value at path is written at, +// or none if it was set by a mutator. +func (r Root) GetLocationsOfExact(path *structpath.PathNode) []diag.Location { + return r.locations.Exact(path) } -func (r Root) valueOf(path *structpath.PathNode) (dyn.Value, bool) { - p, ok := dyn.FromStructPath(path) - if !ok { - return dyn.InvalidValue, false +// GetLocationOfExact returns the primary location of [Root.GetLocationsOfExact]. +func (r Root) GetLocationOfExact(path *structpath.PathNode) diag.Location { + return firstLocation(r.GetLocationsOfExact(path)) +} + +func firstLocation(locs []diag.Location) diag.Location { + if len(locs) == 0 { + return diag.Location{} } - v, err := dyn.GetByPath(r.value, p) - return v, err == nil + return locs[0] } // GetNodeAndType and returns parent resource node and type of the resource in direct backend. @@ -638,33 +628,33 @@ func (r Root) valueOf(path *structpath.PathNode) (dyn.Value, bool) { // // "resources.jobs.foo.name" -> ("resources.jobs.foo", "jobs") // "resources.jobs.foo.permissions[0].level -> ("resources.jobs.foo.permissions", "jobs.permissions") -func GetNodeAndType(path dyn.Path) (dyn.Path, string) { - if len(path) < 3 { +func GetNodeAndType(path *structpath.PathNode) (*structpath.PathNode, string) { + if path.Len() < 3 { return nil, "" } - if path[0].Key() != "resources" { + if path.KeyAt(0) != "resources" { return nil, "" } - if len(path) >= 4 { - if path[3].Key() == "permissions" || path[3].Key() == "grants" { - return path[:4], path[1].Key() + "." + path[3].Key() + if path.Len() >= 4 { + if k := path.KeyAt(3); k == "permissions" || k == "grants" { + return path.Prefix(4), path.KeyAt(1) + "." + k } } - return path[:3], path[1].Key() + return path.Prefix(3), path.KeyAt(1) } // GetResourceTypeFromKey extracts the resource group from a resource path. // For example, "resources.jobs.foo" returns "jobs". // Returns empty string if the path is not in the expected format. func GetResourceTypeFromKey(path string) string { - dp, err := dyn.NewPathFromString(path) + p, err := structpath.ParsePath(path) if err != nil { return "" } - _, rType := GetNodeAndType(dp) + _, rType := GetNodeAndType(p) return rType } @@ -673,13 +663,13 @@ func GetResourceTypeFromKey(path string) string { // The returned value is a pointer to the concrete struct that represents that resource type. // When the path is invalid or resource is not found, the second return value is false. func (r *Root) GetResourceConfig(path string) (any, error) { - dynPath, err := dyn.NewPathFromString(path) + p, err := structpath.ParsePath(path) if err != nil { return nil, err } // Extract and validate the resource group from the path - node, resourceType := GetNodeAndType(dynPath) + node, resourceType := GetNodeAndType(p) if resourceType == "" { return nil, fmt.Errorf("path does not correspond to resource: %q", path) } @@ -690,18 +680,15 @@ func (r *Root) GetResourceConfig(path string) (any, error) { return nil, fmt.Errorf("no such resource type in the config: %q", resourceType) } - // Fetch the raw value from the dynamic representation of the bundle config. - v, err := dyn.GetByPath(r.Value(), node) - if err != nil { - if dyn.IsNoSuchKeyError(err) { - return nil, nil - } - return nil, fmt.Errorf("cannot fetch config for %s: %w", node, err) + // Copy the value, so that the caller can't change the configuration through it. + v := r.View().Lookup(node) + if !v.IsValid() { + return nil, nil } typedConfigPtr := reflect.New(typ) - err = convert.ToTyped(typedConfigPtr.Interface(), v) + _, err = (&structvar.StructVar{Value: typedConfigPtr.Interface()}).Assign(nil, v) if err != nil { return nil, fmt.Errorf("cannot convert config to %s: %w", typ.String(), err) } @@ -709,8 +696,69 @@ func (r *Root) GetResourceConfig(path string) (any, error) { return typedConfigPtr.Interface(), nil } -// 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 { - return r.value +// IsReferenceAt is [Root.IsReference] for a path node. +func (r Root) IsReferenceAt(path *structpath.PathNode) bool { + _, ok := r.refs[path.String()] + return ok +} + +// ReferenceAt returns the pure variable reference held by the field at path that +// cannot hold a string, or "" if there is none. +func (r Root) ReferenceAt(path *structpath.PathNode) string { + return r.refs[path.String()] +} + +// References returns all pure variable references in fields that cannot hold a string, +// keyed by path. References in string fields are part of their value. +func (r Root) References() iter.Seq2[*structpath.PathNode, string] { + return r.ReferencesUnder(nil) +} + +// ReferencesUnder returns the pure variable references (see [Root.References]) at path +// and below it, in path order. References below a value that a mutator removed (e.g. a +// cleared new_cluster) are skipped: only those in the typed value are returned. +func (r Root) ReferencesUnder(path *structpath.PathNode) iter.Seq2[*structpath.PathNode, string] { + return func(yield func(*structpath.PathNode, string) bool) { + prefix := path.String() + var keys []string + for k := range r.refs { + if prefix == "" || k == prefix || strings.HasPrefix(k, prefix+".") || strings.HasPrefix(k, prefix+"[") { + keys = append(keys, k) + } + } + slices.Sort(keys) + view := r.View() + for _, k := range keys { + p, err := structpath.ParsePath(k) + if err != nil { + continue + } + // The view shows a present reference as a string. A reference below or at a + // nil pointer (even a forced one), a missing map key or an index out of range + // is gone. + if view.Lookup(p).Kind() != structvar.KindString { + continue + } + if !yield(p, r.refs[k]) { + return + } + } + } +} + +// LocationTree returns the tree of the source locations of the configuration values. +func (r *Root) LocationTree() *structvar.Locations { + return r.locations +} + +// View returns the read-only view of the configuration tree. +func (r *Root) View() structvar.View { + return structvar.NewView(r, r.refs, r.locations) +} + +// Override replaces the configuration with the result of plan (see [structvar.PlanOverride]). +func (r *Root) Override(plan *structvar.OverridePlan) error { + sv := r.vars() + defer r.store(sv) + return sv.Override(plan) } diff --git a/bundle/config/root_test.go b/bundle/config/root_test.go index f735c44405e..a0bc32ab031 100644 --- a/bundle/config/root_test.go +++ b/bundle/config/root_test.go @@ -2,11 +2,13 @@ package config import ( "encoding/json" + "maps" "reflect" + "slices" "testing" "github.com/databricks/cli/bundle/config/variable" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -100,7 +102,6 @@ func TestRootMergeTargetOverridesWithMode(t *testing.T) { }, }, } - require.NoError(t, root.initializeDynamicValue()) require.NoError(t, root.MergeTargetOverrides("development")) assert.Equal(t, Development, root.Bundle.Mode) } @@ -156,7 +157,6 @@ func TestRootMergeTargetOverridesWithVariables(t *testing.T) { }, }, } - require.NoError(t, root.initializeDynamicValue()) require.NoError(t, root.MergeTargetOverrides("development")) assert.Equal(t, "bar", root.Variables["foo"].Default) assert.Equal(t, "foo var", root.Variables["foo"].Description) @@ -172,83 +172,138 @@ func TestRootMergeTargetOverridesWithVariables(t *testing.T) { func TestIsFullVariableOverrideDef(t *testing.T) { testCases := []struct { - value dyn.Value + value map[string]any expected bool }{ { - value: dyn.V(map[string]dyn.Value{ - "type": dyn.V("string"), - "default": dyn.V("foo"), - "description": dyn.V("foo var"), - }), + value: map[string]any{ + "type": "string", + "default": "foo", + "description": "foo var", + }, expected: true, }, { - value: dyn.V(map[string]dyn.Value{ - "type": dyn.V("string"), - "lookup": dyn.V("foo"), - "description": dyn.V("foo var"), - }), + value: map[string]any{ + "type": "string", + "lookup": "foo", + "description": "foo var", + }, expected: false, }, { - value: dyn.V(map[string]dyn.Value{ - "type": dyn.V("string"), - "default": dyn.V("foo"), - }), + value: map[string]any{ + "type": "string", + "default": "foo", + }, expected: true, }, { - value: dyn.V(map[string]dyn.Value{ - "type": dyn.V("string"), - "lookup": dyn.V("foo"), - }), + value: map[string]any{ + "type": "string", + "lookup": "foo", + }, expected: false, }, { - value: dyn.V(map[string]dyn.Value{ - "description": dyn.V("string"), - "default": dyn.V("foo"), - }), + value: map[string]any{ + "description": "string", + "default": "foo", + }, expected: true, }, { - value: dyn.V(map[string]dyn.Value{ - "description": dyn.V("string"), - "lookup": dyn.V("foo"), - }), + value: map[string]any{ + "description": "string", + "lookup": "foo", + }, expected: true, }, { - value: dyn.V(map[string]dyn.Value{ - "default": dyn.V("foo"), - }), + value: map[string]any{ + "default": "foo", + }, expected: true, }, { - value: dyn.V(map[string]dyn.Value{ - "lookup": dyn.V("foo"), - }), + value: map[string]any{ + "lookup": "foo", + }, expected: true, }, { - value: dyn.V(map[string]dyn.Value{ - "type": dyn.V("string"), - }), + value: map[string]any{ + "type": "string", + }, expected: false, }, { - value: dyn.V(map[string]dyn.Value{ - "type": dyn.V("string"), - "default": dyn.V("foo"), - "description": dyn.V("foo var"), - "lookup": dyn.V("foo"), - }), + value: map[string]any{ + "type": "string", + "default": "foo", + "description": "foo var", + "lookup": "foo", + }, expected: false, }, } for i, tc := range testCases { - assert.Equal(t, tc.expected, isFullVariableOverrideDef(tc.value), "test case %d", i) + keys := slices.Collect(maps.Keys(tc.value)) + assert.Equal(t, tc.expected, isFullVariableOverrideDef(keys), "test case %d", i) + } +} + +func TestLoadFromBytesNotAMap(t *testing.T) { + for _, content := range []string{"hello\n", "- a\n- b\n"} { + r, diags := LoadFromBytes("databricks.yml", []byte(content)) + assert.Nil(t, r) + assert.ErrorContains(t, diags.Error(), "failed to load databricks.yml") } } + +func TestMergeComplexVariableSequenceDefaults(t *testing.T) { + a, diags := LoadFromBytes("a.yml", []byte("variables:\n v:\n type: complex\n default: [1, 2]\n")) + require.NoError(t, diags.Error()) + b, diags := LoadFromBytes("b.yml", []byte("variables:\n v:\n type: complex\n default: [3]\n")) + require.NoError(t, diags.Error()) + require.NoError(t, a.Merge(b)) + assert.Equal(t, []any{1, 2, 3}, a.Variables["v"].Default) +} + +func TestTargetVariableShorthandThroughAliases(t *testing.T) { + r, diags := LoadFromBytes("a.yml", []byte(` +x: &dev dev_value +y: &common {b: from_anchor, c: from_anchor} +variables: + a: {default: d} + b: {default: d} + c: {default: d} +targets: + dev: + variables: + <<: *common + a: *dev + c: explicit +`)) + require.NoError(t, diags.Error()) + require.NoError(t, r.MergeTargetOverrides("dev")) + assert.Equal(t, "dev_value", r.Variables["a"].Default) + assert.Equal(t, "from_anchor", r.Variables["b"].Default) + assert.Equal(t, "explicit", r.Variables["c"].Default) +} + +func TestNestedYAMLAnchorCycle(t *testing.T) { + _, diags := LoadFromBytes("a.yml", []byte("variables:\n v:\n default: &x {a: [*x]}\n")) + assert.ErrorContains(t, diags.Error(), `cyclic reference to anchor "x"`) +} + +func TestSetReferenceAtPointerField(t *testing.T) { + r, diags := LoadFromBytes("a.yml", []byte("resources:\n jobs:\n j:\n name: j\n")) + require.NoError(t, diags.Error()) + path := structpath.MustParsePath("resources.jobs.j.trigger") + require.NoError(t, r.SetReference(path, "${var.t}")) + s, ok := r.View().Lookup(path).AsString() + require.True(t, ok) + assert.Equal(t, "${var.t}", s) +} diff --git a/bundle/config/validate/all_resources_have_values.go b/bundle/config/validate/all_resources_have_values.go index 713ec0f83af..40158bde431 100644 --- a/bundle/config/validate/all_resources_have_values.go +++ b/bundle/config/validate/all_resources_have_values.go @@ -6,8 +6,9 @@ import ( "strings" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) func AllResourcesHaveValues() bundle.Mutator { @@ -23,34 +24,25 @@ func (m *allResourcesHaveValues) Name() string { func (m *allResourcesHaveValues) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { diags := diag.Diagnostics{} - _, err := dyn.MapByPattern( - b.Config.Value(), - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), - func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - if v.Kind() != dyn.KindNil { - return v, nil - } - - // Type of the resource, stripped of the trailing 's' to make it - // singular. - rType := strings.TrimSuffix(p[1].Key(), "s") - - // Name of the resource. Eg: "foo" in "jobs.foo". - rName := p[2].Key() - - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("%s %s is not defined", rType, rName), - Locations: v.Locations(), - Paths: dyn.ToStructPaths(p), - }) - - return v, nil - }, - ) - if err != nil { - diags = append(diags, diag.FromErr(err)...) - } + b.Config.Resources.IterateAllResources(func(typ, rName string, res config.ConfigResource) bool { + // A resource declared without a body is visited with a nil res. + if res != nil { + return true + } + + // Type of the resource, stripped of the trailing 's' to make it + // singular. + rType := strings.TrimSuffix(typ, "s") + + p := structpath.NewPath(nil, "resources", typ, rName) + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("%s %s is not defined", rType, rName), + Locations: b.Config.GetLocationsOfExact(p), + Paths: []*structpath.PathNode{p}, + }) + return true + }) return diags } diff --git a/bundle/config/validate/enum.go b/bundle/config/validate/enum.go index 8173cc806ec..46364add693 100644 --- a/bundle/config/validate/enum.go +++ b/bundle/config/validate/enum.go @@ -1,10 +1,7 @@ package validate import ( - "cmp" "context" - "fmt" - "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" @@ -20,28 +17,10 @@ func (f *enum) Name() string { return "validate:enum" } -// Apply reports enum fields with invalid values. The walker is generated (enum_walk.go) -// by bundle/internal/validation. +// Apply reports enum fields with invalid values. The walker is generated +// (enum_walk.go) by bundle/internal/validation. func (f *enum) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - w := &walker{diags: diag.Diagnostics{}} - n := rootNode(b.Config.Value()) - enumConfigRoot(w, &n, &b.Config) - diags := w.diags - - // Start from the order of a walk of the configuration tree, so that the sort - // below breaks ties between diagnostics as it did with that walk. - sortByWalkOrder(b.Config.Value(), diags) - - // Sort diagnostics to make them deterministic - slices.SortFunc(diags, func(a, b diag.Diagnostic) int { - // First sort by summary - if n := cmp.Compare(a.Summary, b.Summary); n != 0 { - return n - } - - // Then sort by locations as a tie breaker if summaries are the same. - return cmp.Compare(fmt.Sprintf("%v", a.Locations), fmt.Sprintf("%v", b.Locations)) - }) - + diags := walkConfig(&b.Config, false, enumConfigRoot) + sortDiagnostics(diags) return diags } diff --git a/bundle/config/validate/enum_walk.go b/bundle/config/validate/enum_walk.go index 37add80d338..2ba3d85b21f 100644 --- a/bundle/config/validate/enum_walk.go +++ b/bundle/config/validate/enum_walk.go @@ -1,6 +1,6 @@ // Code generated by bundle/internal/validation. DO NOT EDIT. -// enumConfigRoot reports the enum fields with invalid values. +// enumWalk reports enum fields with invalid values. package validate @@ -8,6 +8,7 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/config/variable" + "github.com/databricks/cli/bundle/config/walk" "github.com/databricks/databricks-sdk-go/service/apps" "github.com/databricks/databricks-sdk-go/service/catalog" "github.com/databricks/databricks-sdk-go/service/compute" @@ -21,2843 +22,3451 @@ import ( "github.com/databricks/databricks-sdk-go/service/vectorsearch" ) -var enumValuesAppsAppDeploymentMode = []string{"AUTO_SYNC", "SNAPSHOT"} +var enumValues0 = []string{"bash", "sh", "cmd"} -var enumValuesAppsAppDeploymentState = []string{"CANCELLED", "FAILED", "IN_PROGRESS", "SUCCEEDED"} +var enumValues1 = []string{"whl", "jar", "tgz"} -var enumValuesAppsAppPermissionLevel = []string{"CAN_MANAGE", "CAN_USE"} +var enumValues10 = []string{"NULL", "PHOTON", "STANDARD"} -var enumValuesAppsAppResourceAppAppPermission = []string{"CAN_USE"} +var enumValues100 = []string{"RATE_LIMIT_RENEWAL_PERIOD_HOUR", "RATE_LIMIT_RENEWAL_PERIOD_MINUTE"} -var enumValuesAppsAppResourceDatabaseDatabasePermission = []string{"CAN_CONNECT_AND_CREATE"} +var enumValues101 = []string{"endpoint", "service_principal", "user", "user_group"} -var enumValuesAppsAppResourceExperimentExperimentPermission = []string{"CAN_EDIT", "CAN_MANAGE", "CAN_READ"} +var enumValues102 = []string{"CPU", "CPU_LARGE", "CPU_MEDIUM", "GPU_LARGE", "GPU_MEDIUM", "GPU_SMALL", "GPU_XLARGE", "GPU_XLARGE_8", "MULTIGPU_MEDIUM"} -var enumValuesAppsAppResourceGenieSpaceGenieSpacePermission = []string{"CAN_EDIT", "CAN_MANAGE", "CAN_RUN", "CAN_VIEW"} +var enumValues103 = []string{"ENHANCED", "LEGACY"} -var enumValuesAppsAppResourceJobJobPermission = []string{"CAN_MANAGE", "CAN_MANAGE_RUN", "CAN_VIEW", "IS_OWNER"} +var enumValues104 = []string{"APPEND_ONLY", "SCD_TYPE_1", "SCD_TYPE_2"} -var enumValuesAppsAppResourcePostgresPostgresPermission = []string{"CAN_CONNECT_AND_CREATE"} +var enumValues105 = []string{"OAUTH", "PAT"} -var enumValuesAppsAppResourceSecretSecretPermission = []string{"MANAGE", "READ", "WRITE"} +var enumValues106 = []string{"DIRECT_QUERY", "DUAL", "IMPORT"} -var enumValuesAppsAppResourceServingEndpointServingEndpointPermission = []string{"CAN_MANAGE", "CAN_QUERY", "CAN_VIEW"} +var enumValues107 = []string{"DESTINATION_TYPE_EXTERNAL_FOUNDATION_MODEL", "DESTINATION_TYPE_PAY_PER_TOKEN_FOUNDATION_MODEL", "DESTINATION_TYPE_PROVISIONED_THROUGHPUT_FOUNDATION_MODEL"} -var enumValuesAppsAppResourceSqlWarehouseSqlWarehousePermission = []string{"CAN_MANAGE", "CAN_USE", "IS_OWNER"} +var enumValues108 = []string{"ai21labs", "amazon-bedrock", "anthropic", "cohere", "custom", "databricks-model-serving", "google-cloud-vertex-ai", "openai", "palm"} -var enumValuesAppsAppResourceUcSecurableUcSecurablePermission = []string{"EXECUTE", "MODIFY", "READ_VOLUME", "SELECT", "USE_CONNECTION", "WRITE_VOLUME"} +var enumValues109 = []string{"PROVISIONING_PHASE_INDEX_SCAN", "PROVISIONING_PHASE_INDEX_SORT", "PROVISIONING_PHASE_MAIN"} -var enumValuesAppsAppResourceUcSecurableUcSecurableType = []string{"CONNECTION", "FUNCTION", "MODEL_SERVICE", "TABLE", "VOLUME"} +var enumValues11 = []string{"ACTIVE", "TRASHED"} -var enumValuesAppsApplicationState = []string{"CRASHED", "DEPLOYING", "RUNNING", "UNAVAILABLE"} +var enumValues110 = []string{"BINARY", "BOOLEAN", "DATE", "DECIMAL", "DOUBLE", "FLOAT", "INTEGER", "LONG", "SHORT", "STRING", "TIMESTAMP"} -var enumValuesAppsComputeSize = []string{"LARGE", "MEDIUM", "XLARGE"} +var enumValues111 = []string{"BLOCK", "MASK", "NONE"} -var enumValuesAppsComputeState = []string{"ACTIVE", "DELETING", "ERROR", "STARTING", "STOPPED", "STOPPING", "UPDATING"} +var enumValues112 = []string{"ai21labs", "amazon", "anthropic", "cohere"} -var enumValuesCatalogModelProviderServiceConfigExternalModelProviderType = []string{"EXTERNAL_MODEL_PROVIDER_TYPE_AMAZON_BEDROCK", "EXTERNAL_MODEL_PROVIDER_TYPE_ANTHROPIC", "EXTERNAL_MODEL_PROVIDER_TYPE_AZURE_OPENAI", "EXTERNAL_MODEL_PROVIDER_TYPE_BEDROCK_MANTLE", "EXTERNAL_MODEL_PROVIDER_TYPE_CUSTOM", "EXTERNAL_MODEL_PROVIDER_TYPE_GEMINI_ENTERPRISE", "EXTERNAL_MODEL_PROVIDER_TYPE_MICROSOFT_FOUNDRY", "EXTERNAL_MODEL_PROVIDER_TYPE_OPENAI"} +var enumValues113 = []string{"FILE_FORMAT_CSV", "FILE_FORMAT_EXCEL", "FILE_FORMAT_TSV"} -var enumValuesCatalogModelServiceConfigDestinationConfigDestinationType = []string{"DESTINATION_TYPE_EXTERNAL_FOUNDATION_MODEL", "DESTINATION_TYPE_PAY_PER_TOKEN_FOUNDATION_MODEL", "DESTINATION_TYPE_PROVISIONED_THROUGHPUT_FOUNDATION_MODEL"} +var enumValues114 = []string{"PAGE_SELECTION_ALL", "PAGE_SELECTION_SUBSET"} -var enumValuesCatalogMonitorCronSchedulePauseStatus = []string{"PAUSED", "UNPAUSED", "UNSPECIFIED"} +var enumValues115 = []string{"FILE", "FILE_METADATA", "PERMISSION"} -var enumValuesCatalogMonitorInferenceLogProblemType = []string{"PROBLEM_TYPE_CLASSIFICATION", "PROBLEM_TYPE_REGRESSION"} +var enumValues116 = []string{"ALL", "INLINE_ONLY", "NONE", "NON_INLINE_ONLY"} -var enumValuesCatalogMonitorMetricType = []string{"CUSTOM_METRIC_TYPE_AGGREGATE", "CUSTOM_METRIC_TYPE_DERIVED", "CUSTOM_METRIC_TYPE_DRIFT"} +var enumValues117 = []string{"TEXT_HTML", "TEXT_PLAIN"} -var enumValuesCatalogPrivilege = []string{"ACCESS", "ALL_PRIVILEGES", "APPLY_TAG", "BROWSE", "CREATE", "CREATE_CATALOG", "CREATE_CLEAN_ROOM", "CREATE_CONNECTION", "CREATE_EXTERNAL_LOCATION", "CREATE_EXTERNAL_TABLE", "CREATE_EXTERNAL_VOLUME", "CREATE_FOREIGN_CATALOG", "CREATE_FOREIGN_SECURABLE", "CREATE_FUNCTION", "CREATE_MANAGED_STORAGE", "CREATE_MATERIALIZED_VIEW", "CREATE_MODEL", "CREATE_PROVIDER", "CREATE_RECIPIENT", "CREATE_SCHEMA", "CREATE_SERVICE_CREDENTIAL", "CREATE_SHARE", "CREATE_STORAGE_CREDENTIAL", "CREATE_TABLE", "CREATE_VIEW", "CREATE_VOLUME", "EXECUTE", "EXECUTE_CLEAN_ROOM_TASK", "EXTERNAL_USE_LOCATION", "EXTERNAL_USE_SCHEMA", "MANAGE", "MANAGE_ALLOWLIST", "MODIFY", "MODIFY_CLEAN_ROOM", "READ_FILES", "READ_METADATA", "READ_PRIVATE_FILES", "READ_VOLUME", "REFRESH", "SELECT", "SET_SHARE_PERMISSION", "USAGE", "USE_CATALOG", "USE_CONNECTION", "USE_MARKETPLACE_ASSETS", "USE_PROVIDER", "USE_RECIPIENT", "USE_SCHEMA", "USE_SHARE", "WRITE_FILES", "WRITE_PRIVATE_FILES", "WRITE_VOLUME"} +var enumValues118 = []string{"FILE", "FILE_METADATA", "LIST", "PERMISSION"} -var enumValuesCatalogRateLimitRateLimitKey = []string{"RATE_LIMIT_KEY_SERVICE", "RATE_LIMIT_KEY_SERVICE_PRINCIPAL", "RATE_LIMIT_KEY_USER", "RATE_LIMIT_KEY_USER_DEFAULT", "RATE_LIMIT_KEY_USER_GROUP"} +var enumValues119 = []string{"AUCTION_AD", "AUCTION_ADGROUP", "AUCTION_ADVERTISER", "AUCTION_CAMPAIGN"} -var enumValuesCatalogRateLimitRateLimitRenewalPeriod = []string{"RATE_LIMIT_RENEWAL_PERIOD_HOUR", "RATE_LIMIT_RENEWAL_PERIOD_MINUTE"} +var enumValues12 = []string{"AVAILABLE", "DELETING", "FAILING_OVER", "STARTING", "STOPPED", "UPDATING"} -var enumValuesCatalogSseEncryptionDetailsAlgorithm = []string{"AWS_SSE_KMS", "AWS_SSE_S3"} +var enumValues120 = []string{"AUDIENCE", "BASIC", "BUSINESS_CENTER", "DSA", "GMV_MAX", "PLAYABLE_AD"} -var enumValuesCatalogVolumeType = []string{"EXTERNAL", "MANAGED"} +var enumValues121 = []string{"AVRO", "JSON", "PROTOBUF", "STRING"} -var enumValuesComputeAwsAvailability = []string{"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"} +var enumValues122 = []string{"AVRO", "BINARYFILE", "CSV", "EXCEL", "JSON", "ORC", "PARQUET", "XML"} -var enumValuesComputeAzureAvailability = []string{"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"} +var enumValues123 = []string{"ADD_NEW_COLUMNS", "ADD_NEW_COLUMNS_WITH_TYPE_WIDENING", "FAIL_ON_NEW_COLUMNS", "NONE", "RESCUE"} -var enumValuesComputeClusterPermissionLevel = []string{"CAN_ATTACH_TO", "CAN_MANAGE", "CAN_RESTART"} +var enumValues124 = []string{"CAMPAIGN", "CAMPAIGN_GROUP", "CREATIVE"} -var enumValuesComputeClusterPolicyPermissionLevel = []string{"CAN_USE"} +var enumValues125 = []string{"ANALYTICS", "ATTRIBUTED_REVENUE_METRICS", "STATISTICS"} -var enumValuesComputeConfidentialComputeType = []string{"CONFIDENTIAL_COMPUTE_TYPE_NONE", "SEV_SNP"} +var enumValues126 = []string{"ALL", "DAILY", "MONTHLY", "YEARLY"} -var enumValuesComputeDataSecurityMode = []string{"DATA_SECURITY_MODE_AUTO", "DATA_SECURITY_MODE_DEDICATED", "DATA_SECURITY_MODE_STANDARD", "LEGACY_PASSTHROUGH", "LEGACY_SINGLE_USER", "LEGACY_SINGLE_USER_STANDARD", "LEGACY_TABLE_ACL", "NONE", "SINGLE_USER", "USER_ISOLATION"} +var enumValues127 = []string{"FAILFAST", "PERMISSIVE"} -var enumValuesComputeDependencyMode = []string{"DEPENDENCY_MODE_AUTO", "DEPENDENCY_MODE_CLUSTER_LIBRARIES", "DEPENDENCY_MODE_ENVIRONMENTS"} +var enumValues13 = []string{"PERFORMANCE_OPTIMIZED", "STANDARD"} -var enumValuesComputeDiskTypeAzureDiskVolumeType = []string{"PREMIUM_LRS", "STANDARD_LRS"} +var enumValues14 = []string{"EDITABLE", "UI_LOCKED"} -var enumValuesComputeDiskTypeEbsVolumeType = []string{"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"} +var enumValues15 = []string{"MULTI_TASK", "SINGLE_TASK"} -var enumValuesComputeEbsVolumeType = []string{"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"} +var enumValues16 = []string{"ENDPOINT_TYPE_READ_ONLY", "ENDPOINT_TYPE_READ_WRITE"} -var enumValuesComputeGcpAvailability = []string{"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"} +var enumValues17 = []string{"LAKEBASE_OAUTH_V1", "NO_LOGIN", "PG_PASSWORD_SCRAM_SHA_256"} -var enumValuesComputeHardwareAcceleratorType = []string{"GPU_1xA10", "GPU_1xH100", "GPU_8xB300", "GPU_8xH100"} +var enumValues18 = []string{"GROUP", "SERVICE_PRINCIPAL", "USER"} -var enumValuesComputeInstancePoolAwsAttributesAvailability = []string{"ON_DEMAND", "SPOT"} +var enumValues19 = []string{"DATABRICKS_SUPERUSER"} -var enumValuesComputeInstancePoolAzureAttributesAvailability = []string{"ON_DEMAND_AZURE", "SPOT_AZURE"} +var enumValues2 = []string{"CAN_ATTACH_TO", "CAN_BIND", "CAN_CREATE", "CAN_CREATE_APP", "CAN_EDIT", "CAN_EDIT_METADATA", "CAN_MANAGE", "CAN_MANAGE_PRODUCTION_VERSIONS", "CAN_MANAGE_RUN", "CAN_MANAGE_STAGING_VERSIONS", "CAN_MONITOR", "CAN_MONITOR_ONLY", "CAN_QUERY", "CAN_READ", "CAN_RESTART", "CAN_RUN", "CAN_USE", "CAN_VIEW", "CAN_VIEW_METADATA", "IS_OWNER"} -var enumValuesComputeInstancePoolPermissionLevel = []string{"CAN_ATTACH_TO", "CAN_MANAGE"} +var enumValues20 = []string{"CONTINUOUS", "SNAPSHOT", "TRIGGERED"} -var enumValuesComputeKind = []string{"CLASSIC_PREVIEW"} +var enumValues21 = []string{"AZURE_KEYVAULT", "DATABRICKS"} -var enumValuesComputeRuntimeEngine = []string{"NULL", "PHOTON", "STANDARD"} +var enumValues22 = []string{"COST_OPTIMIZED", "POLICY_UNSPECIFIED", "RELIABILITY_OPTIMIZED"} -var enumValuesConfigArtifactType = []string{"whl", "jar", "tgz"} +var enumValues23 = []string{"CLASSIC", "PRO", "REALTIME", "TYPE_UNSPECIFIED"} -var enumValuesDashboardsLifecycleState = []string{"ACTIVE", "TRASHED"} +var enumValues24 = []string{"ACTIVE", "DEGRADED", "DELETING", "FAILED", "PROVISIONING", "UPDATING"} -var enumValuesDatabaseDatabaseInstanceState = []string{"AVAILABLE", "DELETING", "FAILING_OVER", "STARTING", "STOPPED", "UPDATING"} +var enumValues25 = []string{"STANDARD", "STORAGE_OPTIMIZED"} -var enumValuesDatabaseProvisioningInfoState = []string{"ACTIVE", "DEGRADED", "DELETING", "FAILED", "PROVISIONING", "UPDATING"} +var enumValues26 = []string{"FULL_TEXT", "HYBRID", "VECTOR"} -var enumValuesDatabaseProvisioningPhase = []string{"PROVISIONING_PHASE_INDEX_SCAN", "PROVISIONING_PHASE_INDEX_SORT", "PROVISIONING_PHASE_MAIN"} +var enumValues27 = []string{"DELTA_SYNC", "DIRECT_ACCESS"} -var enumValuesDatabaseSyncedTableSchedulingPolicy = []string{"CONTINUOUS", "SNAPSHOT", "TRIGGERED"} +var enumValues28 = []string{"EXTERNAL", "MANAGED"} -var enumValuesDatabaseSyncedTableSpecPgSpecificType = []string{"PG_SPECIFIC_TYPE_HALFVEC", "PG_SPECIFIC_TYPE_VARCHAR", "PG_SPECIFIC_TYPE_VECTOR"} +var enumValues29 = []string{"EQUAL", "GREATER_THAN", "GREATER_THAN_OR_EQUAL", "IS_NOT_NULL", "IS_NULL", "LESS_THAN", "LESS_THAN_OR_EQUAL", "NOT_EQUAL"} -var enumValuesDatabaseSyncedTableState = []string{"SYNCED_TABLED_OFFLINE", "SYNCED_TABLE_OFFLINE_FAILED", "SYNCED_TABLE_ONLINE", "SYNCED_TABLE_ONLINE_CONTINUOUS_UPDATE", "SYNCED_TABLE_ONLINE_NO_PENDING_UPDATE", "SYNCED_TABLE_ONLINE_PIPELINE_FAILED", "SYNCED_TABLE_ONLINE_TRIGGERED_UPDATE", "SYNCED_TABLE_ONLINE_UPDATING_PIPELINE_RESOURCES", "SYNCED_TABLE_PROVISIONING", "SYNCED_TABLE_PROVISIONING_INITIAL_SNAPSHOT", "SYNCED_TABLE_PROVISIONING_PIPELINE_RESOURCES"} +var enumValues3 = []string{"complex"} -var enumValuesExecExecutableType = []string{"bash", "sh", "cmd"} +var enumValues30 = []string{"ERROR", "OK", "TRIGGERED", "UNKNOWN"} -var enumValuesIamPermissionLevel = []string{"CAN_ATTACH_TO", "CAN_BIND", "CAN_CREATE", "CAN_CREATE_APP", "CAN_EDIT", "CAN_EDIT_METADATA", "CAN_MANAGE", "CAN_MANAGE_PRODUCTION_VERSIONS", "CAN_MANAGE_RUN", "CAN_MANAGE_STAGING_VERSIONS", "CAN_MONITOR", "CAN_MONITOR_ONLY", "CAN_QUERY", "CAN_READ", "CAN_RESTART", "CAN_RUN", "CAN_USE", "CAN_VIEW", "CAN_VIEW_METADATA", "IS_OWNER"} +var enumValues31 = []string{"PAUSED", "UNPAUSED"} -var enumValuesJobsAiRuntimeTaskPriorityClass = []string{"BEST_EFFORT", "CRITICAL", "NORMAL"} +var enumValues32 = []string{"AUTO_SYNC", "SNAPSHOT"} -var enumValuesJobsAuthenticationMethod = []string{"OAUTH", "PAT"} +var enumValues33 = []string{"CRASHED", "DEPLOYING", "RUNNING", "UNAVAILABLE"} -var enumValuesJobsComputeSpecAcceleratorType = []string{"GPU_1xA10", "GPU_1xH100", "GPU_8xB300", "GPU_8xH100"} +var enumValues34 = []string{"ACTIVE", "DELETING", "ERROR", "STARTING", "STOPPED", "STOPPING", "UPDATING"} -var enumValuesJobsCondition = []string{"ALL_UPDATED", "ANY_UPDATED"} +var enumValues35 = []string{"CAN_MANAGE", "CAN_USE"} -var enumValuesJobsConditionTaskOp = []string{"EQUAL_TO", "GREATER_THAN", "GREATER_THAN_OR_EQUAL", "LESS_THAN", "LESS_THAN_OR_EQUAL", "NOT_EQUAL"} +var enumValues36 = []string{"ACCESS", "ALL_PRIVILEGES", "APPLY_TAG", "BROWSE", "CREATE", "CREATE_CATALOG", "CREATE_CLEAN_ROOM", "CREATE_CONNECTION", "CREATE_EXTERNAL_LOCATION", "CREATE_EXTERNAL_TABLE", "CREATE_EXTERNAL_VOLUME", "CREATE_FOREIGN_CATALOG", "CREATE_FOREIGN_SECURABLE", "CREATE_FUNCTION", "CREATE_MANAGED_STORAGE", "CREATE_MATERIALIZED_VIEW", "CREATE_MODEL", "CREATE_PROVIDER", "CREATE_RECIPIENT", "CREATE_SCHEMA", "CREATE_SERVICE_CREDENTIAL", "CREATE_SHARE", "CREATE_STORAGE_CREDENTIAL", "CREATE_TABLE", "CREATE_VIEW", "CREATE_VOLUME", "EXECUTE", "EXECUTE_CLEAN_ROOM_TASK", "EXTERNAL_USE_LOCATION", "EXTERNAL_USE_SCHEMA", "MANAGE", "MANAGE_ALLOWLIST", "MODIFY", "MODIFY_CLEAN_ROOM", "READ_FILES", "READ_METADATA", "READ_PRIVATE_FILES", "READ_VOLUME", "REFRESH", "SELECT", "SET_SHARE_PERMISSION", "USAGE", "USE_CATALOG", "USE_CONNECTION", "USE_MARKETPLACE_ASSETS", "USE_PROVIDER", "USE_RECIPIENT", "USE_SCHEMA", "USE_SHARE", "WRITE_FILES", "WRITE_PRIVATE_FILES", "WRITE_VOLUME"} -var enumValuesJobsDayOfWeek = []string{"FRIDAY", "MONDAY", "SATURDAY", "SUNDAY", "THURSDAY", "TUESDAY", "WEDNESDAY"} +var enumValues37 = []string{"CAN_USE"} -var enumValuesJobsFormat = []string{"MULTI_TASK", "SINGLE_TASK"} +var enumValues38 = []string{"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"} -var enumValuesJobsGitProvider = []string{"awsCodeCommit", "azureDevOpsServices", "bitbucketCloud", "bitbucketServer", "gitHub", "gitHubEnterprise", "gitLab", "gitLabEnterpriseEdition"} +var enumValues39 = []string{"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"} -var enumValuesJobsJobDeploymentKind = []string{"BUNDLE", "SYSTEM_MANAGED"} +var enumValues4 = []string{"HTML", "MARKDOWN"} -var enumValuesJobsJobEditMode = []string{"EDITABLE", "UI_LOCKED"} +var enumValues40 = []string{"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"} -var enumValuesJobsJobPermissionLevel = []string{"CAN_MANAGE", "CAN_MANAGE_RUN", "CAN_VIEW", "IS_OWNER"} +var enumValues41 = []string{"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"} -var enumValuesJobsJobSourceDirtyState = []string{"DISCONNECTED", "NOT_SYNCED"} +var enumValues42 = []string{"CONFIDENTIAL_COMPUTE_TYPE_NONE", "SEV_SNP"} -var enumValuesJobsJobsHealthMetric = []string{"RUN_DURATION_SECONDS", "STREAMING_BACKLOG_BYTES", "STREAMING_BACKLOG_FILES", "STREAMING_BACKLOG_RECORDS", "STREAMING_BACKLOG_SECONDS"} +var enumValues43 = []string{"CAN_ATTACH_TO", "CAN_MANAGE", "CAN_RESTART"} -var enumValuesJobsJobsHealthOperator = []string{"GREATER_THAN"} +var enumValues44 = []string{"CAN_EDIT", "CAN_MANAGE", "CAN_READ"} -var enumValuesJobsModelTriggerConfigurationCondition = []string{"MODEL_ALIAS_SET", "MODEL_CREATED", "MODEL_VERSION_READY"} +var enumValues45 = []string{"APPROX_COUNT_DISTINCT", "APPROX_PERCENTILE", "AVG", "COUNT", "FIRST", "FUNCTION_TYPE_UNSPECIFIED", "LAST", "MAX", "MIN", "STDDEV_POP", "STDDEV_SAMP", "SUM", "VAR_POP", "VAR_SAMP"} -var enumValuesJobsPauseStatus = []string{"PAUSED", "UNPAUSED"} +var enumValues46 = []string{"ON_DEMAND", "SPOT"} -var enumValuesJobsPerformanceTarget = []string{"PERFORMANCE_OPTIMIZED", "STANDARD"} +var enumValues47 = []string{"ON_DEMAND_AZURE", "SPOT_AZURE"} -var enumValuesJobsPeriodicTriggerConfigurationTimeUnit = []string{"DAYS", "HOURS", "MINUTES", "WEEKS"} +var enumValues48 = []string{"CAN_ATTACH_TO", "CAN_MANAGE"} -var enumValuesJobsRunIf = []string{"ALL_DONE", "ALL_FAILED", "ALL_SUCCESS", "AT_LEAST_ONE_FAILED", "AT_LEAST_ONE_SUCCESS", "NONE_FAILED"} +var enumValues49 = []string{"NEVER", "ON_FAILURE"} -var enumValuesJobsSource = []string{"GIT", "WORKSPACE"} +var enumValues5 = []string{"ACTIVE", "DELETED"} -var enumValuesJobsSqlConditionTriggerMode = []string{"QUERY_RETURNS_ROWS", "RESULT_VALUE_CHANGES"} +var enumValues50 = []string{"BUNDLE", "SYSTEM_MANAGED"} -var enumValuesJobsStorageMode = []string{"DIRECT_QUERY", "DUAL", "IMPORT"} +var enumValues51 = []string{"awsCodeCommit", "azureDevOpsServices", "bitbucketCloud", "bitbucketServer", "gitHub", "gitHubEnterprise", "gitLab", "gitLabEnterpriseEdition"} -var enumValuesJobsSubscriptionAttachmentDataAttachmentFileFormat = []string{"FILE_FORMAT_CSV", "FILE_FORMAT_EXCEL", "FILE_FORMAT_TSV"} +var enumValues52 = []string{"CAN_MANAGE", "CAN_MANAGE_RUN", "CAN_VIEW", "IS_OWNER"} -var enumValuesJobsSubscriptionAttachmentPageAttachmentPageSelection = []string{"PAGE_SELECTION_ALL", "PAGE_SELECTION_SUBSET"} +var enumValues53 = []string{"ALL_DONE", "ALL_FAILED", "ALL_SUCCESS", "AT_LEAST_ONE_FAILED", "AT_LEAST_ONE_SUCCESS", "NONE_FAILED"} -var enumValuesJobsTaskRetryMode = []string{"NEVER", "ON_FAILURE"} +var enumValues54 = []string{"EXTERNAL_MODEL_PROVIDER_TYPE_AMAZON_BEDROCK", "EXTERNAL_MODEL_PROVIDER_TYPE_ANTHROPIC", "EXTERNAL_MODEL_PROVIDER_TYPE_AZURE_OPENAI", "EXTERNAL_MODEL_PROVIDER_TYPE_BEDROCK_MANTLE", "EXTERNAL_MODEL_PROVIDER_TYPE_CUSTOM", "EXTERNAL_MODEL_PROVIDER_TYPE_GEMINI_ENTERPRISE", "EXTERNAL_MODEL_PROVIDER_TYPE_MICROSOFT_FOUNDRY", "EXTERNAL_MODEL_PROVIDER_TYPE_OPENAI"} -var enumValuesMlExperimentPermissionLevel = []string{"CAN_EDIT", "CAN_MANAGE", "CAN_READ"} +var enumValues55 = []string{"CAN_MANAGE", "CAN_QUERY", "CAN_VIEW"} -var enumValuesMlFunctionFunctionType = []string{"APPROX_COUNT_DISTINCT", "APPROX_PERCENTILE", "AVG", "COUNT", "FIRST", "FUNCTION_TYPE_UNSPECIFIED", "LAST", "MAX", "MIN", "STDDEV_POP", "STDDEV_SAMP", "SUM", "VAR_POP", "VAR_SAMP"} +var enumValues56 = []string{"endpoint", "user"} -var enumValuesMlRegisteredModelPermissionLevel = []string{"CAN_EDIT", "CAN_MANAGE", "CAN_MANAGE_PRODUCTION_VERSIONS", "CAN_MANAGE_STAGING_VERSIONS", "CAN_READ"} +var enumValues57 = []string{"minute"} -var enumValuesMlScalarDataType = []string{"BINARY", "BOOLEAN", "DATE", "DECIMAL", "DOUBLE", "FLOAT", "INTEGER", "LONG", "SHORT", "STRING", "TIMESTAMP"} +var enumValues58 = []string{"TELEMETRY_FEATURE_INFERENCE_TABLE", "TELEMETRY_FEATURE_LOGS", "TELEMETRY_FEATURE_METRICS", "TELEMETRY_FEATURE_TRACES"} -var enumValuesMlSdpSourceState = []string{"ERROR", "PENDING", "RESOLVED"} +var enumValues59 = []string{"CAN_EDIT", "CAN_MANAGE", "CAN_MANAGE_PRODUCTION_VERSIONS", "CAN_MANAGE_STAGING_VERSIONS", "CAN_READ"} -var enumValuesPipelinesConnectorType = []string{"CDC", "QUERY_BASED"} +var enumValues6 = []string{"LARGE", "MEDIUM", "XLARGE"} -var enumValuesPipelinesDayOfWeek = []string{"FRIDAY", "MONDAY", "SATURDAY", "SUNDAY", "THURSDAY", "TUESDAY", "WEDNESDAY"} +var enumValues60 = []string{"BUNDLE"} -var enumValuesPipelinesDeploymentKind = []string{"BUNDLE"} +var enumValues61 = []string{"CDC", "QUERY_BASED"} -var enumValuesPipelinesFileIngestionOptionsFileFormat = []string{"AVRO", "BINARYFILE", "CSV", "EXCEL", "JSON", "ORC", "PARQUET", "XML"} +var enumValues62 = []string{"BIGQUERY", "CONFLUENCE", "DYNAMICS365", "FOREIGN_CATALOG", "GA4_RAW_DATA", "GOOGLE_DRIVE", "JIRA", "MANAGED_POSTGRESQL", "META_MARKETING", "MYSQL", "NETSUITE", "ORACLE", "POSTGRESQL", "RABBITMQ", "SALESFORCE", "SERVICENOW", "SHAREPOINT", "SMARTSHEET", "SQLSERVER", "TERADATA", "TIKTOK_ADS", "WORKDAY_RAAS", "ZENDESK"} -var enumValuesPipelinesFileIngestionOptionsSchemaEvolutionMode = []string{"ADD_NEW_COLUMNS", "ADD_NEW_COLUMNS_WITH_TYPE_WIDENING", "FAIL_ON_NEW_COLUMNS", "NONE", "RESCUE"} +var enumValues63 = []string{"CAN_MANAGE", "CAN_RUN", "CAN_VIEW", "IS_OWNER"} -var enumValuesPipelinesGoogleDriveOptionsGoogleDriveEntityType = []string{"FILE", "FILE_METADATA", "PERMISSION"} +var enumValues64 = []string{"FRIDAY", "MONDAY", "SATURDAY", "SUNDAY", "THURSDAY", "TUESDAY", "WEDNESDAY"} -var enumValuesPipelinesIngestionSourceType = []string{"BIGQUERY", "CONFLUENCE", "DYNAMICS365", "FOREIGN_CATALOG", "GA4_RAW_DATA", "GOOGLE_DRIVE", "JIRA", "MANAGED_POSTGRESQL", "META_MARKETING", "MYSQL", "NETSUITE", "ORACLE", "POSTGRESQL", "RABBITMQ", "SALESFORCE", "SERVICENOW", "SHAREPOINT", "SMARTSHEET", "SQLSERVER", "TERADATA", "TIKTOK_ADS", "WORKDAY_RAAS", "ZENDESK"} +var enumValues65 = []string{"STORED_GENERATED"} -var enumValuesPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptionsLinkedInAdsEntityGranularity = []string{"CAMPAIGN", "CAMPAIGN_GROUP", "CREATIVE"} +var enumValues66 = []string{"CURRENT", "PREVIEW"} -var enumValuesPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptionsLinkedInAdsFinder = []string{"ANALYTICS", "ATTRIBUTED_REVENUE_METRICS", "STATISTICS"} +var enumValues67 = []string{"PG_SPECIFIC_TYPE_HALFVEC", "PG_SPECIFIC_TYPE_VARCHAR", "PG_SPECIFIC_TYPE_VECTOR"} -var enumValuesPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptionsLinkedInAdsTimeGranularity = []string{"ALL", "DAILY", "MONTHLY", "YEARLY"} +var enumValues68 = []string{"CUSTOM_METRIC_TYPE_AGGREGATE", "CUSTOM_METRIC_TYPE_DERIVED", "CUSTOM_METRIC_TYPE_DRIFT"} -var enumValuesPipelinesOutlookAttachmentMode = []string{"ALL", "INLINE_ONLY", "NONE", "NON_INLINE_ONLY"} +var enumValues69 = []string{"PROBLEM_TYPE_CLASSIFICATION", "PROBLEM_TYPE_REGRESSION"} -var enumValuesPipelinesOutlookBodyFormat = []string{"TEXT_HTML", "TEXT_PLAIN"} +var enumValues7 = []string{"DATA_SECURITY_MODE_AUTO", "DATA_SECURITY_MODE_DEDICATED", "DATA_SECURITY_MODE_STANDARD", "LEGACY_PASSTHROUGH", "LEGACY_SINGLE_USER", "LEGACY_SINGLE_USER_STANDARD", "LEGACY_TABLE_ACL", "NONE", "SINGLE_USER", "USER_ISOLATION"} -var enumValuesPipelinesParseMode = []string{"FAILFAST", "PERMISSIVE"} +var enumValues70 = []string{"PAUSED", "UNPAUSED", "UNSPECIFIED"} -var enumValuesPipelinesPipelineClusterAutoscaleMode = []string{"ENHANCED", "LEGACY"} +var enumValues71 = []string{"READ", "WRITE", "MANAGE"} -var enumValuesPipelinesPipelinePermissionLevel = []string{"CAN_MANAGE", "CAN_RUN", "CAN_VIEW", "IS_OWNER"} +var enumValues72 = []string{"CHANNEL_NAME_CURRENT", "CHANNEL_NAME_CUSTOM", "CHANNEL_NAME_PREVIEW", "CHANNEL_NAME_PREVIOUS"} -var enumValuesPipelinesSharepointOptionsSharepointEntityType = []string{"FILE", "FILE_METADATA", "LIST", "PERMISSION"} +var enumValues73 = []string{"CAN_MANAGE", "CAN_MONITOR", "CAN_USE", "CAN_VIEW", "IS_OWNER"} -var enumValuesPipelinesTableSpecificConfigScdType = []string{"APPEND_ONLY", "SCD_TYPE_1", "SCD_TYPE_2"} +var enumValues74 = []string{"SYNCED_TABLED_OFFLINE", "SYNCED_TABLE_OFFLINE_FAILED", "SYNCED_TABLE_ONLINE", "SYNCED_TABLE_ONLINE_CONTINUOUS_UPDATE", "SYNCED_TABLE_ONLINE_NO_PENDING_UPDATE", "SYNCED_TABLE_ONLINE_PIPELINE_FAILED", "SYNCED_TABLE_ONLINE_TRIGGERED_UPDATE", "SYNCED_TABLE_ONLINE_UPDATING_PIPELINE_RESOURCES", "SYNCED_TABLE_PROVISIONING", "SYNCED_TABLE_PROVISIONING_INITIAL_SNAPSHOT", "SYNCED_TABLE_PROVISIONING_PIPELINE_RESOURCES"} -var enumValuesPipelinesTikTokAdsOptionsTikTokDataLevel = []string{"AUCTION_AD", "AUCTION_ADGROUP", "AUCTION_ADVERTISER", "AUCTION_CAMPAIGN"} +var enumValues75 = []string{"CAN_CREATE", "CAN_MANAGE", "CAN_USE"} -var enumValuesPipelinesTikTokAdsOptionsTikTokReportType = []string{"AUDIENCE", "BASIC", "BUSINESS_CENTER", "DSA", "GMV_MAX", "PLAYABLE_AD"} +var enumValues76 = []string{"CONTINUOUS", "TRIGGERED"} -var enumValuesPipelinesTransformerFormat = []string{"AVRO", "JSON", "PROTOBUF", "STRING"} +var enumValues77 = []string{"AVG", "COUNT", "COUNT_DISTINCT", "MAX", "MEDIAN", "MIN", "STDDEV", "SUM"} -var enumValuesPostgresDayOfWeek = []string{"FRIDAY", "MONDAY", "SATURDAY", "SUNDAY", "THURSDAY", "TUESDAY", "WEDNESDAY"} +var enumValues78 = []string{"CANCELLED", "FAILED", "IN_PROGRESS", "SUCCEEDED"} -var enumValuesPostgresEndpointType = []string{"ENDPOINT_TYPE_READ_ONLY", "ENDPOINT_TYPE_READ_WRITE"} +var enumValues79 = []string{"CAN_CONNECT_AND_CREATE"} -var enumValuesPostgresNewPipelineSpecPipelineChannel = []string{"CURRENT", "PREVIEW"} +var enumValues8 = []string{"DEPENDENCY_MODE_AUTO", "DEPENDENCY_MODE_CLUSTER_LIBRARIES", "DEPENDENCY_MODE_ENVIRONMENTS"} -var enumValuesPostgresRoleAuthMethod = []string{"LAKEBASE_OAUTH_V1", "NO_LOGIN", "PG_PASSWORD_SCRAM_SHA_256"} +var enumValues80 = []string{"CAN_EDIT", "CAN_MANAGE", "CAN_RUN", "CAN_VIEW"} -var enumValuesPostgresRoleIdentityType = []string{"GROUP", "SERVICE_PRINCIPAL", "USER"} +var enumValues81 = []string{"MANAGE", "READ", "WRITE"} -var enumValuesPostgresRoleMembershipRole = []string{"DATABRICKS_SUPERUSER"} +var enumValues82 = []string{"CAN_MANAGE", "CAN_USE", "IS_OWNER"} -var enumValuesPostgresSyncedTableSyncedTableSpecExtraColumnMaintenance = []string{"STORED_GENERATED"} +var enumValues83 = []string{"EXECUTE", "MODIFY", "READ_VOLUME", "SELECT", "USE_CONNECTION", "WRITE_VOLUME"} -var enumValuesPostgresSyncedTableSyncedTableSpecPgSpecificType = []string{"PG_SPECIFIC_TYPE_HALFVEC", "PG_SPECIFIC_TYPE_VARCHAR", "PG_SPECIFIC_TYPE_VECTOR"} +var enumValues84 = []string{"CONNECTION", "FUNCTION", "MODEL_SERVICE", "TABLE", "VOLUME"} -var enumValuesPostgresSyncedTableSyncedTableSpecSyncedTableSchedulingPolicy = []string{"CONTINUOUS", "SNAPSHOT", "TRIGGERED"} +var enumValues85 = []string{"AWS_SSE_KMS", "AWS_SSE_S3"} -var enumValuesResourcesSecretScopePermissionLevel = []string{"READ", "WRITE", "MANAGE"} +var enumValues86 = []string{"ERROR", "PENDING", "RESOLVED"} -var enumValuesServingAiGatewayGuardrailPiiBehaviorBehavior = []string{"BLOCK", "MASK", "NONE"} +var enumValues87 = []string{"PREMIUM_LRS", "STANDARD_LRS"} -var enumValuesServingAiGatewayRateLimitKey = []string{"endpoint", "service_principal", "user", "user_group"} +var enumValues88 = []string{"DISCONNECTED", "NOT_SYNCED"} -var enumValuesServingAiGatewayRateLimitRenewalPeriod = []string{"minute"} +var enumValues89 = []string{"RUN_DURATION_SECONDS", "STREAMING_BACKLOG_BYTES", "STREAMING_BACKLOG_FILES", "STREAMING_BACKLOG_RECORDS", "STREAMING_BACKLOG_SECONDS"} -var enumValuesServingAmazonBedrockConfigBedrockProvider = []string{"ai21labs", "amazon", "anthropic", "cohere"} +var enumValues9 = []string{"CLASSIC_PREVIEW"} -var enumValuesServingExternalModelProvider = []string{"ai21labs", "amazon-bedrock", "anthropic", "cohere", "custom", "databricks-model-serving", "google-cloud-vertex-ai", "openai", "palm"} +var enumValues90 = []string{"GREATER_THAN"} -var enumValuesServingRateLimitKey = []string{"endpoint", "user"} +var enumValues91 = []string{"QUERY_RETURNS_ROWS", "RESULT_VALUE_CHANGES"} -var enumValuesServingRateLimitRenewalPeriod = []string{"minute"} +var enumValues92 = []string{"BEST_EFFORT", "CRITICAL", "NORMAL"} -var enumValuesServingServedModelInputWorkloadType = []string{"CPU", "CPU_LARGE", "CPU_MEDIUM", "GPU_LARGE", "GPU_MEDIUM", "GPU_SMALL", "GPU_XLARGE", "GPU_XLARGE_8", "MULTIGPU_MEDIUM"} +var enumValues93 = []string{"GPU_1xA10", "GPU_1xH100", "GPU_8xB300", "GPU_8xH100"} -var enumValuesServingServingEndpointPermissionLevel = []string{"CAN_MANAGE", "CAN_QUERY", "CAN_VIEW"} +var enumValues94 = []string{"EQUAL_TO", "GREATER_THAN", "GREATER_THAN_OR_EQUAL", "LESS_THAN", "LESS_THAN_OR_EQUAL", "NOT_EQUAL"} -var enumValuesServingServingModelWorkloadType = []string{"CPU", "CPU_LARGE", "CPU_MEDIUM", "GPU_LARGE", "GPU_MEDIUM", "GPU_SMALL", "GPU_XLARGE", "GPU_XLARGE_8", "MULTIGPU_MEDIUM"} +var enumValues95 = []string{"GIT", "WORKSPACE"} -var enumValuesServingTelemetryFeature = []string{"TELEMETRY_FEATURE_INFERENCE_TABLE", "TELEMETRY_FEATURE_LOGS", "TELEMETRY_FEATURE_METRICS", "TELEMETRY_FEATURE_TRACES"} +var enumValues96 = []string{"MODEL_ALIAS_SET", "MODEL_CREATED", "MODEL_VERSION_READY"} -var enumValuesSqlAggregation = []string{"AVG", "COUNT", "COUNT_DISTINCT", "MAX", "MEDIAN", "MIN", "STDDEV", "SUM"} +var enumValues97 = []string{"DAYS", "HOURS", "MINUTES", "WEEKS"} -var enumValuesSqlAlertEvaluationState = []string{"ERROR", "OK", "TRIGGERED", "UNKNOWN"} +var enumValues98 = []string{"ALL_UPDATED", "ANY_UPDATED"} -var enumValuesSqlAlertLifecycleState = []string{"ACTIVE", "DELETED"} +var enumValues99 = []string{"RATE_LIMIT_KEY_SERVICE", "RATE_LIMIT_KEY_SERVICE_PRINCIPAL", "RATE_LIMIT_KEY_USER", "RATE_LIMIT_KEY_USER_DEFAULT", "RATE_LIMIT_KEY_USER_GROUP"} -var enumValuesSqlChannelName = []string{"CHANNEL_NAME_CURRENT", "CHANNEL_NAME_CUSTOM", "CHANNEL_NAME_PREVIEW", "CHANNEL_NAME_PREVIOUS"} - -var enumValuesSqlComparisonOperator = []string{"EQUAL", "GREATER_THAN", "GREATER_THAN_OR_EQUAL", "IS_NOT_NULL", "IS_NULL", "LESS_THAN", "LESS_THAN_OR_EQUAL", "NOT_EQUAL"} - -var enumValuesSqlCreateWarehouseRequestWarehouseType = []string{"CLASSIC", "PRO", "REALTIME", "TYPE_UNSPECIFIED"} - -var enumValuesSqlCustomTemplateFormat = []string{"HTML", "MARKDOWN"} - -var enumValuesSqlSchedulePauseStatus = []string{"PAUSED", "UNPAUSED"} - -var enumValuesSqlSpotInstancePolicy = []string{"COST_OPTIMIZED", "POLICY_UNSPECIFIED", "RELIABILITY_OPTIMIZED"} - -var enumValuesSqlWarehousePermissionLevel = []string{"CAN_MANAGE", "CAN_MONITOR", "CAN_USE", "CAN_VIEW", "IS_OWNER"} - -var enumValuesVariableVariableType = []string{"complex"} - -var enumValuesVectorsearchEndpointType = []string{"STANDARD", "STORAGE_OPTIMIZED"} - -var enumValuesVectorsearchIndexSubtype = []string{"FULL_TEXT", "HYBRID", "VECTOR"} - -var enumValuesVectorsearchPipelineType = []string{"CONTINUOUS", "TRIGGERED"} - -var enumValuesVectorsearchVectorIndexType = []string{"DELTA_SYNC", "DIRECT_ACCESS"} - -var enumValuesVectorsearchVectorSearchEndpointPermissionLevel = []string{"CAN_CREATE", "CAN_MANAGE", "CAN_USE"} - -var enumValuesWorkspaceScopeBackendType = []string{"AZURE_KEYVAULT", "DATABRICKS"} - -func enumConfigRoot(w *walker, n *node, v *config.Root) { +func enumConfigRoot(w *walker, n *walk.Node, v *config.Root) { if v == nil { - v = &config.Root{} + return } if len(v.Artifacts) > 0 { - c := n.child("artifacts") - for k0, x0 := range v.Artifacts { - if k0 == "" { + c := n.Child("artifacts") + for k, x := range v.Artifacts { + if k == "" { continue } - e := c.child(k0) - enumConfigArtifact(w, &e, x0) + e := c.Child(k) + enumConfigArtifact(w, &e, x) } } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesPermission(w, &e, &v.Permissions[i]) } } - if c := n.child("resources"); c.exists() { - enumConfigResources(w, &c, &v.Resources) + if x := &v.Resources; !walk.IsZeroStruct(x) || n.HasRefs("resources") { + c := n.Child("resources") + enumConfigResources(w, &c, x) } if len(v.Variables) > 0 { - c := n.child("variables") - for k0, x0 := range v.Variables { - if k0 == "" { + c := n.Child("variables") + for k, x := range v.Variables { + if k == "" { continue } - e := c.child(k0) - enumVariableVariable(w, &e, x0) + e := c.Child(k) + enumVariableVariable(w, &e, x) } } } -func enumConfigArtifact(w *walker, n *node, v *config.Artifact) { +func enumConfigArtifact(w *walker, n *walk.Node, v *config.Artifact) { if v == nil { - v = &config.Artifact{} + return + } + if s := string(v.Executable); s != "" { + c := n.Child("executable") + w.enumValue(&c, s, enumValues0) + } + if s := string(v.Type); s != "" { + c := n.Child("type") + w.enumValue(&c, s, enumValues1) } - w.enumField(n, "executable", string(v.Executable), enumValuesExecExecutableType) - w.enumField(n, "type", string(v.Type), enumValuesConfigArtifactType) } -func enumResourcesPermission(w *walker, n *node, v *resources.Permission) { +func enumResourcesPermission(w *walker, n *walk.Node, v *resources.Permission) { if v == nil { - v = &resources.Permission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues2) } - w.enumField(n, "level", string(v.Level), enumValuesIamPermissionLevel) } -func enumConfigResources(w *walker, n *node, v *config.Resources) { +func enumConfigResources(w *walker, n *walk.Node, v *config.Resources) { if v == nil { - v = &config.Resources{} + return } if len(v.Alerts) > 0 { - c := n.child("alerts") - for k0, x0 := range v.Alerts { - if k0 == "" { + c := n.Child("alerts") + for k, x := range v.Alerts { + if k == "" { continue } - e := c.child(k0) - enumResourcesAlert(w, &e, x0) + e := c.Child(k) + enumResourcesAlert(w, &e, x) } } if len(v.Apps) > 0 { - c := n.child("apps") - for k0, x0 := range v.Apps { - if k0 == "" { + c := n.Child("apps") + for k, x := range v.Apps { + if k == "" { continue } - e := c.child(k0) - enumResourcesApp(w, &e, x0) + e := c.Child(k) + enumResourcesApp(w, &e, x) } } if len(v.Catalogs) > 0 { - c := n.child("catalogs") - for k0, x0 := range v.Catalogs { - if k0 == "" { + c := n.Child("catalogs") + for k, x := range v.Catalogs { + if k == "" { continue } - e := c.child(k0) - enumResourcesCatalog(w, &e, x0) + e := c.Child(k) + enumResourcesCatalog(w, &e, x) } } if len(v.ClusterPolicies) > 0 { - c := n.child("cluster_policies") - for k0, x0 := range v.ClusterPolicies { - if k0 == "" { + c := n.Child("cluster_policies") + for k, x := range v.ClusterPolicies { + if k == "" { continue } - e := c.child(k0) - enumResourcesClusterPolicy(w, &e, x0) + e := c.Child(k) + enumResourcesClusterPolicy(w, &e, x) } } if len(v.Clusters) > 0 { - c := n.child("clusters") - for k0, x0 := range v.Clusters { - if k0 == "" { + c := n.Child("clusters") + for k, x := range v.Clusters { + if k == "" { continue } - e := c.child(k0) - enumResourcesCluster(w, &e, x0) + e := c.Child(k) + enumResourcesCluster(w, &e, x) } } if len(v.Dashboards) > 0 { - c := n.child("dashboards") - for k0, x0 := range v.Dashboards { - if k0 == "" { + c := n.Child("dashboards") + for k, x := range v.Dashboards { + if k == "" { continue } - e := c.child(k0) - enumResourcesDashboard(w, &e, x0) + e := c.Child(k) + enumResourcesDashboard(w, &e, x) } } if len(v.DatabaseInstances) > 0 { - c := n.child("database_instances") - for k0, x0 := range v.DatabaseInstances { - if k0 == "" { + c := n.Child("database_instances") + for k, x := range v.DatabaseInstances { + if k == "" { continue } - e := c.child(k0) - enumResourcesDatabaseInstance(w, &e, x0) + e := c.Child(k) + enumResourcesDatabaseInstance(w, &e, x) } } if len(v.Experiments) > 0 { - c := n.child("experiments") - for k0, x0 := range v.Experiments { - if k0 == "" { + c := n.Child("experiments") + for k, x := range v.Experiments { + if k == "" { continue } - e := c.child(k0) - enumResourcesMlflowExperiment(w, &e, x0) + e := c.Child(k) + enumResourcesMlflowExperiment(w, &e, x) } } if len(v.ExternalLocations) > 0 { - c := n.child("external_locations") - for k0, x0 := range v.ExternalLocations { - if k0 == "" { + c := n.Child("external_locations") + for k, x := range v.ExternalLocations { + if k == "" { continue } - e := c.child(k0) - enumResourcesExternalLocation(w, &e, x0) + e := c.Child(k) + enumResourcesExternalLocation(w, &e, x) } } if len(v.Features) > 0 { - c := n.child("features") - for k0, x0 := range v.Features { - if k0 == "" { + c := n.Child("features") + for k, x := range v.Features { + if k == "" { continue } - e := c.child(k0) - enumResourcesFeature(w, &e, x0) + e := c.Child(k) + enumResourcesFeature(w, &e, x) } } if len(v.GenieSpaces) > 0 { - c := n.child("genie_spaces") - for k0, x0 := range v.GenieSpaces { - if k0 == "" { + c := n.Child("genie_spaces") + for k, x := range v.GenieSpaces { + if k == "" { continue } - e := c.child(k0) - enumResourcesGenieSpace(w, &e, x0) + e := c.Child(k) + enumResourcesGenieSpace(w, &e, x) } } if len(v.InstancePools) > 0 { - c := n.child("instance_pools") - for k0, x0 := range v.InstancePools { - if k0 == "" { + c := n.Child("instance_pools") + for k, x := range v.InstancePools { + if k == "" { continue } - e := c.child(k0) - enumResourcesInstancePool(w, &e, x0) + e := c.Child(k) + enumResourcesInstancePool(w, &e, x) } } if len(v.JobRuns) > 0 { - c := n.child("job_runs") - for k0, x0 := range v.JobRuns { - if k0 == "" { + c := n.Child("job_runs") + for k, x := range v.JobRuns { + if k == "" { continue } - e := c.child(k0) - enumResourcesJobRun(w, &e, x0) + e := c.Child(k) + enumResourcesJobRun(w, &e, x) } } if len(v.Jobs) > 0 { - c := n.child("jobs") - for k0, x0 := range v.Jobs { - if k0 == "" { + c := n.Child("jobs") + for k, x := range v.Jobs { + if k == "" { continue } - e := c.child(k0) - enumResourcesJob(w, &e, x0) + e := c.Child(k) + enumResourcesJob(w, &e, x) } } if len(v.McpServices) > 0 { - c := n.child("mcp_services") - for k0, x0 := range v.McpServices { - if k0 == "" { + c := n.Child("mcp_services") + for k, x := range v.McpServices { + if k == "" { continue } - e := c.child(k0) - enumResourcesMcpService(w, &e, x0) + e := c.Child(k) + enumResourcesMcpService(w, &e, x) } } if len(v.ModelProviderServices) > 0 { - c := n.child("model_provider_services") - for k0, x0 := range v.ModelProviderServices { - if k0 == "" { + c := n.Child("model_provider_services") + for k, x := range v.ModelProviderServices { + if k == "" { continue } - e := c.child(k0) - enumResourcesModelProviderService(w, &e, x0) + e := c.Child(k) + enumResourcesModelProviderService(w, &e, x) } } if len(v.ModelServices) > 0 { - c := n.child("model_services") - for k0, x0 := range v.ModelServices { - if k0 == "" { + c := n.Child("model_services") + for k, x := range v.ModelServices { + if k == "" { continue } - e := c.child(k0) - enumResourcesModelService(w, &e, x0) + e := c.Child(k) + enumResourcesModelService(w, &e, x) } } if len(v.ModelServingEndpoints) > 0 { - c := n.child("model_serving_endpoints") - for k0, x0 := range v.ModelServingEndpoints { - if k0 == "" { + c := n.Child("model_serving_endpoints") + for k, x := range v.ModelServingEndpoints { + if k == "" { continue } - e := c.child(k0) - enumResourcesModelServingEndpoint(w, &e, x0) + e := c.Child(k) + enumResourcesModelServingEndpoint(w, &e, x) } } if len(v.Models) > 0 { - c := n.child("models") - for k0, x0 := range v.Models { - if k0 == "" { + c := n.Child("models") + for k, x := range v.Models { + if k == "" { continue } - e := c.child(k0) - enumResourcesMlflowModel(w, &e, x0) + e := c.Child(k) + enumResourcesMlflowModel(w, &e, x) } } if len(v.Pipelines) > 0 { - c := n.child("pipelines") - for k0, x0 := range v.Pipelines { - if k0 == "" { + c := n.Child("pipelines") + for k, x := range v.Pipelines { + if k == "" { continue } - e := c.child(k0) - enumResourcesPipeline(w, &e, x0) + e := c.Child(k) + enumResourcesPipeline(w, &e, x) } } if len(v.PostgresEndpoints) > 0 { - c := n.child("postgres_endpoints") - for k0, x0 := range v.PostgresEndpoints { - if k0 == "" { + c := n.Child("postgres_endpoints") + for k, x := range v.PostgresEndpoints { + if k == "" { continue } - e := c.child(k0) - enumResourcesPostgresEndpoint(w, &e, x0) + e := c.Child(k) + enumResourcesPostgresEndpoint(w, &e, x) } } if len(v.PostgresProjects) > 0 { - c := n.child("postgres_projects") - for k0, x0 := range v.PostgresProjects { - if k0 == "" { + c := n.Child("postgres_projects") + for k, x := range v.PostgresProjects { + if k == "" { continue } - e := c.child(k0) - enumResourcesPostgresProject(w, &e, x0) + e := c.Child(k) + enumResourcesPostgresProject(w, &e, x) } } if len(v.PostgresRoles) > 0 { - c := n.child("postgres_roles") - for k0, x0 := range v.PostgresRoles { - if k0 == "" { + c := n.Child("postgres_roles") + for k, x := range v.PostgresRoles { + if k == "" { continue } - e := c.child(k0) - enumResourcesPostgresRole(w, &e, x0) + e := c.Child(k) + enumResourcesPostgresRole(w, &e, x) } } if len(v.PostgresSnapshotSchedules) > 0 { - c := n.child("postgres_snapshot_schedules") - for k0, x0 := range v.PostgresSnapshotSchedules { - if k0 == "" { + c := n.Child("postgres_snapshot_schedules") + for k, x := range v.PostgresSnapshotSchedules { + if k == "" { continue } - e := c.child(k0) - enumResourcesPostgresSnapshotSchedule(w, &e, x0) + e := c.Child(k) + enumResourcesPostgresSnapshotSchedule(w, &e, x) } } if len(v.PostgresSyncedTables) > 0 { - c := n.child("postgres_synced_tables") - for k0, x0 := range v.PostgresSyncedTables { - if k0 == "" { + c := n.Child("postgres_synced_tables") + for k, x := range v.PostgresSyncedTables { + if k == "" { continue } - e := c.child(k0) - enumResourcesPostgresSyncedTable(w, &e, x0) + e := c.Child(k) + enumResourcesPostgresSyncedTable(w, &e, x) } } if len(v.QualityMonitors) > 0 { - c := n.child("quality_monitors") - for k0, x0 := range v.QualityMonitors { - if k0 == "" { + c := n.Child("quality_monitors") + for k, x := range v.QualityMonitors { + if k == "" { continue } - e := c.child(k0) - enumResourcesQualityMonitor(w, &e, x0) + e := c.Child(k) + enumResourcesQualityMonitor(w, &e, x) } } if len(v.RegisteredModels) > 0 { - c := n.child("registered_models") - for k0, x0 := range v.RegisteredModels { - if k0 == "" { + c := n.Child("registered_models") + for k, x := range v.RegisteredModels { + if k == "" { continue } - e := c.child(k0) - enumResourcesRegisteredModel(w, &e, x0) + e := c.Child(k) + enumResourcesRegisteredModel(w, &e, x) } } if len(v.Schemas) > 0 { - c := n.child("schemas") - for k0, x0 := range v.Schemas { - if k0 == "" { + c := n.Child("schemas") + for k, x := range v.Schemas { + if k == "" { continue } - e := c.child(k0) - enumResourcesSchema(w, &e, x0) + e := c.Child(k) + enumResourcesSchema(w, &e, x) } } if len(v.SecretScopes) > 0 { - c := n.child("secret_scopes") - for k0, x0 := range v.SecretScopes { - if k0 == "" { + c := n.Child("secret_scopes") + for k, x := range v.SecretScopes { + if k == "" { continue } - e := c.child(k0) - enumResourcesSecretScope(w, &e, x0) + e := c.Child(k) + enumResourcesSecretScope(w, &e, x) } } if len(v.Secrets) > 0 { - c := n.child("secrets") - for k0, x0 := range v.Secrets { - if k0 == "" { + c := n.Child("secrets") + for k, x := range v.Secrets { + if k == "" { continue } - e := c.child(k0) - enumResourcesSecret(w, &e, x0) + e := c.Child(k) + enumResourcesSecret(w, &e, x) } } if len(v.SqlWarehouses) > 0 { - c := n.child("sql_warehouses") - for k0, x0 := range v.SqlWarehouses { - if k0 == "" { + c := n.Child("sql_warehouses") + for k, x := range v.SqlWarehouses { + if k == "" { continue } - e := c.child(k0) - enumResourcesSqlWarehouse(w, &e, x0) + e := c.Child(k) + enumResourcesSqlWarehouse(w, &e, x) } } if len(v.SyncedDatabaseTables) > 0 { - c := n.child("synced_database_tables") - for k0, x0 := range v.SyncedDatabaseTables { - if k0 == "" { + c := n.Child("synced_database_tables") + for k, x := range v.SyncedDatabaseTables { + if k == "" { continue } - e := c.child(k0) - enumResourcesSyncedDatabaseTable(w, &e, x0) + e := c.Child(k) + enumResourcesSyncedDatabaseTable(w, &e, x) } } if len(v.VectorSearchEndpoints) > 0 { - c := n.child("vector_search_endpoints") - for k0, x0 := range v.VectorSearchEndpoints { - if k0 == "" { + c := n.Child("vector_search_endpoints") + for k, x := range v.VectorSearchEndpoints { + if k == "" { continue } - e := c.child(k0) - enumResourcesVectorSearchEndpoint(w, &e, x0) + e := c.Child(k) + enumResourcesVectorSearchEndpoint(w, &e, x) } } if len(v.VectorSearchIndexes) > 0 { - c := n.child("vector_search_indexes") - for k0, x0 := range v.VectorSearchIndexes { - if k0 == "" { + c := n.Child("vector_search_indexes") + for k, x := range v.VectorSearchIndexes { + if k == "" { continue } - e := c.child(k0) - enumResourcesVectorSearchIndex(w, &e, x0) + e := c.Child(k) + enumResourcesVectorSearchIndex(w, &e, x) } } if len(v.Volumes) > 0 { - c := n.child("volumes") - for k0, x0 := range v.Volumes { - if k0 == "" { + c := n.Child("volumes") + for k, x := range v.Volumes { + if k == "" { continue } - e := c.child(k0) - enumResourcesVolume(w, &e, x0) + e := c.Child(k) + enumResourcesVolume(w, &e, x) } } } -func enumVariableVariable(w *walker, n *node, v *variable.Variable) { +func enumVariableVariable(w *walker, n *walk.Node, v *variable.Variable) { if v == nil { - v = &variable.Variable{} + return + } + if s := string(v.Type); s != "" { + c := n.Child("type") + w.enumValue(&c, s, enumValues3) } - w.enumField(n, "type", string(v.Type), enumValuesVariableVariableType) } -func enumResourcesAlert(w *walker, n *node, v *resources.Alert) { +func enumResourcesAlert(w *walker, n *walk.Node, v *resources.Alert) { if v == nil { - v = &resources.Alert{} + return + } + if s := string(v.AlertV2.CustomTemplateFormat); s != "" || walk.Forced(v.AlertV2.ForceSendFields, "CustomTemplateFormat") { + c := n.Child("custom_template_format") + w.enumValue(&c, s, enumValues4) + } + if x := &v.AlertV2.Evaluation; !walk.IsZeroStruct(x) || walk.Forced(v.AlertV2.ForceSendFields, "Evaluation") || n.HasRefs("evaluation") { + c := n.Child("evaluation") + enumSqlAlertV2Evaluation(w, &c, x) } - w.enumField(n, "custom_template_format", string(v.AlertV2.CustomTemplateFormat), enumValuesSqlCustomTemplateFormat) - if c := n.child("evaluation"); c.exists() { - enumSqlAlertV2Evaluation(w, &c, &v.AlertV2.Evaluation) + if s := string(v.AlertV2.LifecycleState); s != "" || walk.Forced(v.AlertV2.ForceSendFields, "LifecycleState") { + c := n.Child("lifecycle_state") + w.enumValue(&c, s, enumValues5) } - w.enumField(n, "lifecycle_state", string(v.AlertV2.LifecycleState), enumValuesSqlAlertLifecycleState) if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesPermission(w, &e, &v.Permissions[i]) } } - if c := n.child("schedule"); c.exists() { - enumSqlCronSchedule(w, &c, &v.AlertV2.Schedule) + if x := &v.AlertV2.Schedule; !walk.IsZeroStruct(x) || walk.Forced(v.AlertV2.ForceSendFields, "Schedule") || n.HasRefs("schedule") { + c := n.Child("schedule") + enumSqlCronSchedule(w, &c, x) } } -func enumResourcesApp(w *walker, n *node, v *resources.App) { +func enumResourcesApp(w *walker, n *walk.Node, v *resources.App) { if v == nil { - v = &resources.App{} + return } - if c := n.child("active_deployment"); c.exists() { - enumAppsAppDeployment(w, &c, v.App.ActiveDeployment) + if x := v.App.ActiveDeployment; x != nil { + c := n.Child("active_deployment") + enumAppsAppDeployment(w, &c, x) } - if c := n.child("app_status"); c.exists() { - enumAppsApplicationStatus(w, &c, v.App.AppStatus) + if x := v.App.AppStatus; x != nil { + c := n.Child("app_status") + enumAppsApplicationStatus(w, &c, x) } - w.enumField(n, "compute_size", string(v.App.ComputeSize), enumValuesAppsComputeSize) - if c := n.child("compute_status"); c.exists() { - enumAppsComputeStatus(w, &c, v.App.ComputeStatus) + if s := string(v.App.ComputeSize); s != "" || walk.Forced(v.App.ForceSendFields, "ComputeSize") { + c := n.Child("compute_size") + w.enumValue(&c, s, enumValues6) } - if c := n.child("pending_deployment"); c.exists() { - enumAppsAppDeployment(w, &c, v.App.PendingDeployment) + if x := v.App.ComputeStatus; x != nil { + c := n.Child("compute_status") + enumAppsComputeStatus(w, &c, x) + } + if x := v.App.PendingDeployment; x != nil { + c := n.Child("pending_deployment") + enumAppsAppDeployment(w, &c, x) } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesAppPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesAppPermission(w, &e, &v.Permissions[i]) } } if len(v.App.Resources) > 0 { - c := n.child("resources") - for i0 := range v.App.Resources { - e := c.elem(i0) - enumAppsAppResource(w, &e, &v.App.Resources[i0]) + c := n.Child("resources") + for i := range v.App.Resources { + e := c.Elem(i) + enumAppsAppResource(w, &e, &v.App.Resources[i]) } } } -func enumResourcesCatalog(w *walker, n *node, v *resources.Catalog) { +func enumResourcesCatalog(w *walker, n *walk.Node, v *resources.Catalog) { if v == nil { - v = &resources.Catalog{} + return } if len(v.Grants) > 0 { - c := n.child("grants") - for i0 := range v.Grants { - e := c.elem(i0) - enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i0]) + c := n.Child("grants") + for i := range v.Grants { + e := c.Elem(i) + enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i]) } } } -func enumResourcesClusterPolicy(w *walker, n *node, v *resources.ClusterPolicy) { +func enumResourcesClusterPolicy(w *walker, n *walk.Node, v *resources.ClusterPolicy) { if v == nil { - v = &resources.ClusterPolicy{} + return } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesClusterPolicyPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesClusterPolicyPermission(w, &e, &v.Permissions[i]) } } } -func enumResourcesCluster(w *walker, n *node, v *resources.Cluster) { +func enumResourcesCluster(w *walker, n *walk.Node, v *resources.Cluster) { if v == nil { - v = &resources.Cluster{} + return + } + if x := v.ClusterSpec.AwsAttributes; x != nil { + c := n.Child("aws_attributes") + enumComputeAwsAttributes(w, &c, x) } - if c := n.child("aws_attributes"); c.exists() { - enumComputeAwsAttributes(w, &c, v.ClusterSpec.AwsAttributes) + if x := v.ClusterSpec.AzureAttributes; x != nil { + c := n.Child("azure_attributes") + enumComputeAzureAttributes(w, &c, x) } - if c := n.child("azure_attributes"); c.exists() { - enumComputeAzureAttributes(w, &c, v.ClusterSpec.AzureAttributes) + if s := string(v.ClusterSpec.DataSecurityMode); s != "" || walk.Forced(v.ClusterSpec.ForceSendFields, "DataSecurityMode") { + c := n.Child("data_security_mode") + w.enumValue(&c, s, enumValues7) } - w.enumField(n, "data_security_mode", string(v.ClusterSpec.DataSecurityMode), enumValuesComputeDataSecurityMode) - w.enumField(n, "dependency_mode", string(v.ClusterSpec.DependencyMode), enumValuesComputeDependencyMode) - if c := n.child("gcp_attributes"); c.exists() { - enumComputeGcpAttributes(w, &c, v.ClusterSpec.GcpAttributes) + if s := string(v.ClusterSpec.DependencyMode); s != "" || walk.Forced(v.ClusterSpec.ForceSendFields, "DependencyMode") { + c := n.Child("dependency_mode") + w.enumValue(&c, s, enumValues8) + } + if x := v.ClusterSpec.GcpAttributes; x != nil { + c := n.Child("gcp_attributes") + enumComputeGcpAttributes(w, &c, x) + } + if s := string(v.ClusterSpec.Kind); s != "" || walk.Forced(v.ClusterSpec.ForceSendFields, "Kind") { + c := n.Child("kind") + w.enumValue(&c, s, enumValues9) } - w.enumField(n, "kind", string(v.ClusterSpec.Kind), enumValuesComputeKind) if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesClusterPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesClusterPermission(w, &e, &v.Permissions[i]) } } - w.enumField(n, "runtime_engine", string(v.ClusterSpec.RuntimeEngine), enumValuesComputeRuntimeEngine) + if s := string(v.ClusterSpec.RuntimeEngine); s != "" || walk.Forced(v.ClusterSpec.ForceSendFields, "RuntimeEngine") { + c := n.Child("runtime_engine") + w.enumValue(&c, s, enumValues10) + } } -func enumResourcesDashboard(w *walker, n *node, v *resources.Dashboard) { +func enumResourcesDashboard(w *walker, n *walk.Node, v *resources.Dashboard) { if v == nil { - v = &resources.Dashboard{} + return + } + if s := string(v.DashboardConfig.LifecycleState); s != "" || walk.Forced(v.DashboardConfig.ForceSendFields, "LifecycleState") { + c := n.Child("lifecycle_state") + w.enumValue(&c, s, enumValues11) } - w.enumField(n, "lifecycle_state", string(v.DashboardConfig.LifecycleState), enumValuesDashboardsLifecycleState) if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesPermission(w, &e, &v.Permissions[i]) } } } -func enumResourcesDatabaseInstance(w *walker, n *node, v *resources.DatabaseInstance) { +func enumResourcesDatabaseInstance(w *walker, n *walk.Node, v *resources.DatabaseInstance) { if v == nil { - v = &resources.DatabaseInstance{} + return } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesPermission(w, &e, &v.Permissions[i]) } } - w.enumField(n, "state", string(v.DatabaseInstance.State), enumValuesDatabaseDatabaseInstanceState) + if s := string(v.DatabaseInstance.State); s != "" || walk.Forced(v.DatabaseInstance.ForceSendFields, "State") { + c := n.Child("state") + w.enumValue(&c, s, enumValues12) + } } -func enumResourcesMlflowExperiment(w *walker, n *node, v *resources.MlflowExperiment) { +func enumResourcesMlflowExperiment(w *walker, n *walk.Node, v *resources.MlflowExperiment) { if v == nil { - v = &resources.MlflowExperiment{} + return } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesMlflowExperimentPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesMlflowExperimentPermission(w, &e, &v.Permissions[i]) } } } -func enumResourcesExternalLocation(w *walker, n *node, v *resources.ExternalLocation) { +func enumResourcesExternalLocation(w *walker, n *walk.Node, v *resources.ExternalLocation) { if v == nil { - v = &resources.ExternalLocation{} + return } - if c := n.child("encryption_details"); c.exists() { - enumCatalogEncryptionDetails(w, &c, v.CreateExternalLocation.EncryptionDetails) + if x := v.CreateExternalLocation.EncryptionDetails; x != nil { + c := n.Child("encryption_details") + enumCatalogEncryptionDetails(w, &c, x) } if len(v.Grants) > 0 { - c := n.child("grants") - for i0 := range v.Grants { - e := c.elem(i0) - enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i0]) + c := n.Child("grants") + for i := range v.Grants { + e := c.Elem(i) + enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i]) } } } -func enumResourcesFeature(w *walker, n *node, v *resources.Feature) { +func enumResourcesFeature(w *walker, n *walk.Node, v *resources.Feature) { if v == nil { - v = &resources.Feature{} + return } - if c := n.child("function"); c.exists() { - enumMlFunction(w, &c, &v.Feature.Function) + if x := &v.Feature.Function; !walk.IsZeroStruct(x) || walk.Forced(v.Feature.ForceSendFields, "Function") || n.HasRefs("function") { + c := n.Child("function") + enumMlFunction(w, &c, x) } - if c := n.child("source"); c.exists() { - enumMlDataSource(w, &c, &v.Feature.Source) + if x := &v.Feature.Source; !walk.IsZeroStruct(x) || walk.Forced(v.Feature.ForceSendFields, "Source") || n.HasRefs("source") { + c := n.Child("source") + enumMlDataSource(w, &c, x) } } -func enumResourcesGenieSpace(w *walker, n *node, v *resources.GenieSpace) { +func enumResourcesGenieSpace(w *walker, n *walk.Node, v *resources.GenieSpace) { if v == nil { - v = &resources.GenieSpace{} + return } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesPermission(w, &e, &v.Permissions[i]) } } } -func enumResourcesInstancePool(w *walker, n *node, v *resources.InstancePool) { +func enumResourcesInstancePool(w *walker, n *walk.Node, v *resources.InstancePool) { if v == nil { - v = &resources.InstancePool{} + return } - if c := n.child("aws_attributes"); c.exists() { - enumComputeInstancePoolAwsAttributes(w, &c, v.CreateInstancePool.AwsAttributes) + if x := v.CreateInstancePool.AwsAttributes; x != nil { + c := n.Child("aws_attributes") + enumComputeInstancePoolAwsAttributes(w, &c, x) } - if c := n.child("azure_attributes"); c.exists() { - enumComputeInstancePoolAzureAttributes(w, &c, v.CreateInstancePool.AzureAttributes) + if x := v.CreateInstancePool.AzureAttributes; x != nil { + c := n.Child("azure_attributes") + enumComputeInstancePoolAzureAttributes(w, &c, x) } - if c := n.child("disk_spec"); c.exists() { - enumComputeDiskSpec(w, &c, v.CreateInstancePool.DiskSpec) + if x := v.CreateInstancePool.DiskSpec; x != nil { + c := n.Child("disk_spec") + enumComputeDiskSpec(w, &c, x) } - if c := n.child("gcp_attributes"); c.exists() { - enumComputeInstancePoolGcpAttributes(w, &c, v.CreateInstancePool.GcpAttributes) + if x := v.CreateInstancePool.GcpAttributes; x != nil { + c := n.Child("gcp_attributes") + enumComputeInstancePoolGcpAttributes(w, &c, x) } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesInstancePoolPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesInstancePoolPermission(w, &e, &v.Permissions[i]) } } } -func enumResourcesJobRun(w *walker, n *node, v *resources.JobRun) { +func enumResourcesJobRun(w *walker, n *walk.Node, v *resources.JobRun) { if v == nil { - v = &resources.JobRun{} + return + } + if s := string(v.RunNow.PerformanceTarget); s != "" || walk.Forced(v.RunNow.ForceSendFields, "PerformanceTarget") { + c := n.Child("performance_target") + w.enumValue(&c, s, enumValues13) } - w.enumField(n, "performance_target", string(v.RunNow.PerformanceTarget), enumValuesJobsPerformanceTarget) } -func enumResourcesJob(w *walker, n *node, v *resources.Job) { +func enumResourcesJob(w *walker, n *walk.Node, v *resources.Job) { if v == nil { - v = &resources.Job{} + return } - if c := n.child("continuous"); c.exists() { - enumJobsContinuous(w, &c, v.JobSettings.Continuous) + if x := v.JobSettings.Continuous; x != nil { + c := n.Child("continuous") + enumJobsContinuous(w, &c, x) } - if c := n.child("deployment"); c.exists() { - enumJobsJobDeployment(w, &c, v.JobSettings.Deployment) + if x := v.JobSettings.Deployment; x != nil { + c := n.Child("deployment") + enumJobsJobDeployment(w, &c, x) } - w.enumField(n, "edit_mode", string(v.JobSettings.EditMode), enumValuesJobsJobEditMode) - w.enumField(n, "format", string(v.JobSettings.Format), enumValuesJobsFormat) - if c := n.child("git_source"); c.exists() { - enumJobsGitSource(w, &c, v.JobSettings.GitSource) + if s := string(v.JobSettings.EditMode); s != "" || walk.Forced(v.JobSettings.ForceSendFields, "EditMode") { + c := n.Child("edit_mode") + w.enumValue(&c, s, enumValues14) } - if c := n.child("health"); c.exists() { - enumJobsJobsHealthRules(w, &c, v.JobSettings.Health) + if s := string(v.JobSettings.Format); s != "" || walk.Forced(v.JobSettings.ForceSendFields, "Format") { + c := n.Child("format") + w.enumValue(&c, s, enumValues15) + } + if x := v.JobSettings.GitSource; x != nil { + c := n.Child("git_source") + enumJobsGitSource(w, &c, x) + } + if x := v.JobSettings.Health; x != nil { + c := n.Child("health") + enumJobsJobsHealthRules(w, &c, x) } if len(v.JobSettings.JobClusters) > 0 { - c := n.child("job_clusters") - for i0 := range v.JobSettings.JobClusters { - e := c.elem(i0) - enumJobsJobCluster(w, &e, &v.JobSettings.JobClusters[i0]) + c := n.Child("job_clusters") + for i := range v.JobSettings.JobClusters { + e := c.Elem(i) + enumJobsJobCluster(w, &e, &v.JobSettings.JobClusters[i]) } } - w.enumField(n, "performance_target", string(v.JobSettings.PerformanceTarget), enumValuesJobsPerformanceTarget) + if s := string(v.JobSettings.PerformanceTarget); s != "" || walk.Forced(v.JobSettings.ForceSendFields, "PerformanceTarget") { + c := n.Child("performance_target") + w.enumValue(&c, s, enumValues13) + } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesJobPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesJobPermission(w, &e, &v.Permissions[i]) } } - if c := n.child("schedule"); c.exists() { - enumJobsCronSchedule(w, &c, v.JobSettings.Schedule) + if x := v.JobSettings.Schedule; x != nil { + c := n.Child("schedule") + enumJobsCronSchedule(w, &c, x) } if len(v.JobSettings.Tasks) > 0 { - c := n.child("tasks") - for i0 := range v.JobSettings.Tasks { - e := c.elem(i0) - enumJobsTask(w, &e, &v.JobSettings.Tasks[i0]) + c := n.Child("tasks") + for i := range v.JobSettings.Tasks { + e := c.Elem(i) + enumJobsTask(w, &e, &v.JobSettings.Tasks[i]) } } - if c := n.child("trigger"); c.exists() { - enumJobsTriggerSettings(w, &c, v.JobSettings.Trigger) + if x := v.JobSettings.Trigger; x != nil { + c := n.Child("trigger") + enumJobsTriggerSettings(w, &c, x) } if len(v.JobSettings.Triggers) > 0 { - c := n.child("triggers") - for i0 := range v.JobSettings.Triggers { - e := c.elem(i0) - enumJobsTriggerConfiguration(w, &e, &v.JobSettings.Triggers[i0]) + c := n.Child("triggers") + for i := range v.JobSettings.Triggers { + e := c.Elem(i) + enumJobsTriggerConfiguration(w, &e, &v.JobSettings.Triggers[i]) } } } -func enumResourcesMcpService(w *walker, n *node, v *resources.McpService) { +func enumResourcesMcpService(w *walker, n *walk.Node, v *resources.McpService) { if v == nil { - v = &resources.McpService{} + return } - if c := n.child("config"); c.exists() { - enumCatalogMcpServiceConfig(w, &c, v.McpServiceConfig.Config) + if x := v.McpServiceConfig.Config; x != nil { + c := n.Child("config") + enumCatalogMcpServiceConfig(w, &c, x) } if len(v.Grants) > 0 { - c := n.child("grants") - for i0 := range v.Grants { - e := c.elem(i0) - enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i0]) + c := n.Child("grants") + for i := range v.Grants { + e := c.Elem(i) + enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i]) } } } -func enumResourcesModelProviderService(w *walker, n *node, v *resources.ModelProviderService) { +func enumResourcesModelProviderService(w *walker, n *walk.Node, v *resources.ModelProviderService) { if v == nil { - v = &resources.ModelProviderService{} + return } - if c := n.child("config"); c.exists() { - enumCatalogModelProviderServiceConfig(w, &c, v.ModelProviderServiceConfig.Config) + if x := v.ModelProviderServiceConfig.Config; x != nil { + c := n.Child("config") + enumCatalogModelProviderServiceConfig(w, &c, x) } if len(v.Grants) > 0 { - c := n.child("grants") - for i0 := range v.Grants { - e := c.elem(i0) - enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i0]) + c := n.Child("grants") + for i := range v.Grants { + e := c.Elem(i) + enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i]) } } } -func enumResourcesModelService(w *walker, n *node, v *resources.ModelService) { +func enumResourcesModelService(w *walker, n *walk.Node, v *resources.ModelService) { if v == nil { - v = &resources.ModelService{} + return } - if c := n.child("config"); c.exists() { - enumCatalogModelServiceConfig(w, &c, v.ModelServiceConfig.Config) + if x := v.ModelServiceConfig.Config; x != nil { + c := n.Child("config") + enumCatalogModelServiceConfig(w, &c, x) } if len(v.Grants) > 0 { - c := n.child("grants") - for i0 := range v.Grants { - e := c.elem(i0) - enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i0]) + c := n.Child("grants") + for i := range v.Grants { + e := c.Elem(i) + enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i]) } } } -func enumResourcesModelServingEndpoint(w *walker, n *node, v *resources.ModelServingEndpoint) { +func enumResourcesModelServingEndpoint(w *walker, n *walk.Node, v *resources.ModelServingEndpoint) { if v == nil { - v = &resources.ModelServingEndpoint{} + return } - if c := n.child("ai_gateway"); c.exists() { - enumServingAiGatewayConfig(w, &c, v.CreateServingEndpoint.AiGateway) + if x := v.CreateServingEndpoint.AiGateway; x != nil { + c := n.Child("ai_gateway") + enumServingAiGatewayConfig(w, &c, x) } - if c := n.child("config"); c.exists() { - enumServingEndpointCoreConfigInput(w, &c, v.CreateServingEndpoint.Config) + if x := v.CreateServingEndpoint.Config; x != nil { + c := n.Child("config") + enumServingEndpointCoreConfigInput(w, &c, x) } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesModelServingEndpointPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesModelServingEndpointPermission(w, &e, &v.Permissions[i]) } } if len(v.CreateServingEndpoint.RateLimits) > 0 { - c := n.child("rate_limits") - for i0 := range v.CreateServingEndpoint.RateLimits { - e := c.elem(i0) - enumServingRateLimit(w, &e, &v.CreateServingEndpoint.RateLimits[i0]) + c := n.Child("rate_limits") + for i := range v.CreateServingEndpoint.RateLimits { + e := c.Elem(i) + enumServingRateLimit(w, &e, &v.CreateServingEndpoint.RateLimits[i]) } } - if c := n.child("telemetry_config"); c.exists() { - enumServingTelemetryConfig(w, &c, v.CreateServingEndpoint.TelemetryConfig) + if x := v.CreateServingEndpoint.TelemetryConfig; x != nil { + c := n.Child("telemetry_config") + enumServingTelemetryConfig(w, &c, x) } } -func enumResourcesMlflowModel(w *walker, n *node, v *resources.MlflowModel) { +func enumResourcesMlflowModel(w *walker, n *walk.Node, v *resources.MlflowModel) { if v == nil { - v = &resources.MlflowModel{} + return } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesMlflowModelPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesMlflowModelPermission(w, &e, &v.Permissions[i]) } } } -func enumResourcesPipeline(w *walker, n *node, v *resources.Pipeline) { +func enumResourcesPipeline(w *walker, n *walk.Node, v *resources.Pipeline) { if v == nil { - v = &resources.Pipeline{} + return } if len(v.CreatePipeline.Clusters) > 0 { - c := n.child("clusters") - for i0 := range v.CreatePipeline.Clusters { - e := c.elem(i0) - enumPipelinesPipelineCluster(w, &e, &v.CreatePipeline.Clusters[i0]) + c := n.Child("clusters") + for i := range v.CreatePipeline.Clusters { + e := c.Elem(i) + enumPipelinesPipelineCluster(w, &e, &v.CreatePipeline.Clusters[i]) } } - if c := n.child("deployment"); c.exists() { - enumPipelinesPipelineDeployment(w, &c, v.CreatePipeline.Deployment) + if x := v.CreatePipeline.Deployment; x != nil { + c := n.Child("deployment") + enumPipelinesPipelineDeployment(w, &c, x) } - if c := n.child("ingestion_definition"); c.exists() { - enumPipelinesIngestionPipelineDefinition(w, &c, v.CreatePipeline.IngestionDefinition) + if x := v.CreatePipeline.IngestionDefinition; x != nil { + c := n.Child("ingestion_definition") + enumPipelinesIngestionPipelineDefinition(w, &c, x) } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesPipelinePermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesPipelinePermission(w, &e, &v.Permissions[i]) } } - if c := n.child("restart_window"); c.exists() { - enumPipelinesRestartWindow(w, &c, v.CreatePipeline.RestartWindow) + if x := v.CreatePipeline.RestartWindow; x != nil { + c := n.Child("restart_window") + enumPipelinesRestartWindow(w, &c, x) } } -func enumResourcesPostgresEndpoint(w *walker, n *node, v *resources.PostgresEndpoint) { +func enumResourcesPostgresEndpoint(w *walker, n *walk.Node, v *resources.PostgresEndpoint) { if v == nil { - v = &resources.PostgresEndpoint{} + return + } + if s := string(v.PostgresEndpointConfig.EndpointSpec.EndpointType); s != "" || walk.Forced(v.PostgresEndpointConfig.EndpointSpec.ForceSendFields, "EndpointType") { + c := n.Child("endpoint_type") + w.enumValue(&c, s, enumValues16) } - w.enumField(n, "endpoint_type", string(v.PostgresEndpointConfig.EndpointSpec.EndpointType), enumValuesPostgresEndpointType) } -func enumResourcesPostgresProject(w *walker, n *node, v *resources.PostgresProject) { +func enumResourcesPostgresProject(w *walker, n *walk.Node, v *resources.PostgresProject) { if v == nil { - v = &resources.PostgresProject{} + return } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesPermission(w, &e, &v.Permissions[i]) } } } -func enumResourcesPostgresRole(w *walker, n *node, v *resources.PostgresRole) { +func enumResourcesPostgresRole(w *walker, n *walk.Node, v *resources.PostgresRole) { if v == nil { - v = &resources.PostgresRole{} + return + } + if s := string(v.PostgresRoleConfig.RoleRoleSpec.AuthMethod); s != "" || walk.Forced(v.PostgresRoleConfig.RoleRoleSpec.ForceSendFields, "AuthMethod") { + c := n.Child("auth_method") + w.enumValue(&c, s, enumValues17) + } + if s := string(v.PostgresRoleConfig.RoleRoleSpec.IdentityType); s != "" || walk.Forced(v.PostgresRoleConfig.RoleRoleSpec.ForceSendFields, "IdentityType") { + c := n.Child("identity_type") + w.enumValue(&c, s, enumValues18) } - w.enumField(n, "auth_method", string(v.PostgresRoleConfig.RoleRoleSpec.AuthMethod), enumValuesPostgresRoleAuthMethod) - w.enumField(n, "identity_type", string(v.PostgresRoleConfig.RoleRoleSpec.IdentityType), enumValuesPostgresRoleIdentityType) if len(v.PostgresRoleConfig.RoleRoleSpec.MembershipRoles) > 0 { - c := n.child("membership_roles") - for i0 := range v.PostgresRoleConfig.RoleRoleSpec.MembershipRoles { - e := c.elem(i0) - w.enumElem(&e, string(v.PostgresRoleConfig.RoleRoleSpec.MembershipRoles[i0]), enumValuesPostgresRoleMembershipRole) + c := n.Child("membership_roles") + for i := range v.PostgresRoleConfig.RoleRoleSpec.MembershipRoles { + e := c.Elem(i) + w.enumValue(&e, string(v.PostgresRoleConfig.RoleRoleSpec.MembershipRoles[i]), enumValues19) } } } -func enumResourcesPostgresSnapshotSchedule(w *walker, n *node, v *resources.PostgresSnapshotSchedule) { +func enumResourcesPostgresSnapshotSchedule(w *walker, n *walk.Node, v *resources.PostgresSnapshotSchedule) { if v == nil { - v = &resources.PostgresSnapshotSchedule{} + return } if len(v.PostgresSnapshotScheduleConfig.Schedule) > 0 { - c := n.child("schedule") - for i0 := range v.PostgresSnapshotScheduleConfig.Schedule { - e := c.elem(i0) - enumPostgresScheduleCadence(w, &e, &v.PostgresSnapshotScheduleConfig.Schedule[i0]) + c := n.Child("schedule") + for i := range v.PostgresSnapshotScheduleConfig.Schedule { + e := c.Elem(i) + enumPostgresScheduleCadence(w, &e, &v.PostgresSnapshotScheduleConfig.Schedule[i]) } } } -func enumResourcesPostgresSyncedTable(w *walker, n *node, v *resources.PostgresSyncedTable) { +func enumResourcesPostgresSyncedTable(w *walker, n *walk.Node, v *resources.PostgresSyncedTable) { if v == nil { - v = &resources.PostgresSyncedTable{} + return } if len(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns) > 0 { - c := n.child("extra_columns") - for i0 := range v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns { - e := c.elem(i0) - enumPostgresSyncedTableSyncedTableSpecExtraColumn(w, &e, &v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns[i0]) + c := n.Child("extra_columns") + for i := range v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns { + e := c.Elem(i) + enumPostgresSyncedTableSyncedTableSpecExtraColumn(w, &e, &v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns[i]) } } - if c := n.child("new_pipeline_spec"); c.exists() { - enumPostgresNewPipelineSpec(w, &c, v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.NewPipelineSpec) + if x := v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.NewPipelineSpec; x != nil { + c := n.Child("new_pipeline_spec") + enumPostgresNewPipelineSpec(w, &c, x) + } + if s := string(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.SchedulingPolicy); s != "" || walk.Forced(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ForceSendFields, "SchedulingPolicy") { + c := n.Child("scheduling_policy") + w.enumValue(&c, s, enumValues20) } - w.enumField(n, "scheduling_policy", string(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.SchedulingPolicy), enumValuesPostgresSyncedTableSyncedTableSpecSyncedTableSchedulingPolicy) if len(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides) > 0 { - c := n.child("type_overrides") - for i0 := range v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides { - e := c.elem(i0) - enumPostgresSyncedTableSyncedTableSpecTypeOverride(w, &e, &v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides[i0]) + c := n.Child("type_overrides") + for i := range v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides { + e := c.Elem(i) + enumPostgresSyncedTableSyncedTableSpecTypeOverride(w, &e, &v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides[i]) } } } -func enumResourcesQualityMonitor(w *walker, n *node, v *resources.QualityMonitor) { +func enumResourcesQualityMonitor(w *walker, n *walk.Node, v *resources.QualityMonitor) { if v == nil { - v = &resources.QualityMonitor{} + return } if len(v.CreateMonitor.CustomMetrics) > 0 { - c := n.child("custom_metrics") - for i0 := range v.CreateMonitor.CustomMetrics { - e := c.elem(i0) - enumCatalogMonitorMetric(w, &e, &v.CreateMonitor.CustomMetrics[i0]) + c := n.Child("custom_metrics") + for i := range v.CreateMonitor.CustomMetrics { + e := c.Elem(i) + enumCatalogMonitorMetric(w, &e, &v.CreateMonitor.CustomMetrics[i]) } } - if c := n.child("inference_log"); c.exists() { - enumCatalogMonitorInferenceLog(w, &c, v.CreateMonitor.InferenceLog) + if x := v.CreateMonitor.InferenceLog; x != nil { + c := n.Child("inference_log") + enumCatalogMonitorInferenceLog(w, &c, x) } - if c := n.child("schedule"); c.exists() { - enumCatalogMonitorCronSchedule(w, &c, v.CreateMonitor.Schedule) + if x := v.CreateMonitor.Schedule; x != nil { + c := n.Child("schedule") + enumCatalogMonitorCronSchedule(w, &c, x) } } -func enumResourcesRegisteredModel(w *walker, n *node, v *resources.RegisteredModel) { +func enumResourcesRegisteredModel(w *walker, n *walk.Node, v *resources.RegisteredModel) { if v == nil { - v = &resources.RegisteredModel{} + return } if len(v.Grants) > 0 { - c := n.child("grants") - for i0 := range v.Grants { - e := c.elem(i0) - enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i0]) + c := n.Child("grants") + for i := range v.Grants { + e := c.Elem(i) + enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i]) } } } -func enumResourcesSchema(w *walker, n *node, v *resources.Schema) { +func enumResourcesSchema(w *walker, n *walk.Node, v *resources.Schema) { if v == nil { - v = &resources.Schema{} + return } if len(v.Grants) > 0 { - c := n.child("grants") - for i0 := range v.Grants { - e := c.elem(i0) - enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i0]) + c := n.Child("grants") + for i := range v.Grants { + e := c.Elem(i) + enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i]) } } } -func enumResourcesSecretScope(w *walker, n *node, v *resources.SecretScope) { +func enumResourcesSecretScope(w *walker, n *walk.Node, v *resources.SecretScope) { if v == nil { - v = &resources.SecretScope{} + return + } + if s := string(v.BackendType); s != "" { + c := n.Child("backend_type") + w.enumValue(&c, s, enumValues21) } - w.enumField(n, "backend_type", string(v.BackendType), enumValuesWorkspaceScopeBackendType) if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesSecretScopePermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesSecretScopePermission(w, &e, &v.Permissions[i]) } } } -func enumResourcesSecret(w *walker, n *node, v *resources.Secret) { +func enumResourcesSecret(w *walker, n *walk.Node, v *resources.Secret) { if v == nil { - v = &resources.Secret{} + return } if len(v.Grants) > 0 { - c := n.child("grants") - for i0 := range v.Grants { - e := c.elem(i0) - enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i0]) + c := n.Child("grants") + for i := range v.Grants { + e := c.Elem(i) + enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i]) } } } -func enumResourcesSqlWarehouse(w *walker, n *node, v *resources.SqlWarehouse) { +func enumResourcesSqlWarehouse(w *walker, n *walk.Node, v *resources.SqlWarehouse) { if v == nil { - v = &resources.SqlWarehouse{} + return } - if c := n.child("channel"); c.exists() { - enumSqlChannel(w, &c, v.CreateWarehouseRequest.Channel) + if x := v.CreateWarehouseRequest.Channel; x != nil { + c := n.Child("channel") + enumSqlChannel(w, &c, x) } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesSqlWarehousePermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesSqlWarehousePermission(w, &e, &v.Permissions[i]) } } - w.enumField(n, "spot_instance_policy", string(v.CreateWarehouseRequest.SpotInstancePolicy), enumValuesSqlSpotInstancePolicy) - w.enumField(n, "warehouse_type", string(v.CreateWarehouseRequest.WarehouseType), enumValuesSqlCreateWarehouseRequestWarehouseType) + if s := string(v.CreateWarehouseRequest.SpotInstancePolicy); s != "" || walk.Forced(v.CreateWarehouseRequest.ForceSendFields, "SpotInstancePolicy") { + c := n.Child("spot_instance_policy") + w.enumValue(&c, s, enumValues22) + } + if s := string(v.CreateWarehouseRequest.WarehouseType); s != "" || walk.Forced(v.CreateWarehouseRequest.ForceSendFields, "WarehouseType") { + c := n.Child("warehouse_type") + w.enumValue(&c, s, enumValues23) + } } -func enumResourcesSyncedDatabaseTable(w *walker, n *node, v *resources.SyncedDatabaseTable) { +func enumResourcesSyncedDatabaseTable(w *walker, n *walk.Node, v *resources.SyncedDatabaseTable) { if v == nil { - v = &resources.SyncedDatabaseTable{} + return + } + if x := v.SyncedDatabaseTable.DataSynchronizationStatus; x != nil { + c := n.Child("data_synchronization_status") + enumDatabaseSyncedTableStatus(w, &c, x) } - if c := n.child("data_synchronization_status"); c.exists() { - enumDatabaseSyncedTableStatus(w, &c, v.SyncedDatabaseTable.DataSynchronizationStatus) + if x := v.SyncedDatabaseTable.Spec; x != nil { + c := n.Child("spec") + enumDatabaseSyncedTableSpec(w, &c, x) } - if c := n.child("spec"); c.exists() { - enumDatabaseSyncedTableSpec(w, &c, v.SyncedDatabaseTable.Spec) + if s := string(v.SyncedDatabaseTable.UnityCatalogProvisioningState); s != "" || walk.Forced(v.SyncedDatabaseTable.ForceSendFields, "UnityCatalogProvisioningState") { + c := n.Child("unity_catalog_provisioning_state") + w.enumValue(&c, s, enumValues24) } - w.enumField(n, "unity_catalog_provisioning_state", string(v.SyncedDatabaseTable.UnityCatalogProvisioningState), enumValuesDatabaseProvisioningInfoState) } -func enumResourcesVectorSearchEndpoint(w *walker, n *node, v *resources.VectorSearchEndpoint) { +func enumResourcesVectorSearchEndpoint(w *walker, n *walk.Node, v *resources.VectorSearchEndpoint) { if v == nil { - v = &resources.VectorSearchEndpoint{} + return + } + if s := string(v.CreateEndpoint.EndpointType); s != "" || walk.Forced(v.CreateEndpoint.ForceSendFields, "EndpointType") { + c := n.Child("endpoint_type") + w.enumValue(&c, s, enumValues25) } - w.enumField(n, "endpoint_type", string(v.CreateEndpoint.EndpointType), enumValuesVectorsearchEndpointType) if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - enumResourcesVectorSearchEndpointPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + enumResourcesVectorSearchEndpointPermission(w, &e, &v.Permissions[i]) } } } -func enumResourcesVectorSearchIndex(w *walker, n *node, v *resources.VectorSearchIndex) { +func enumResourcesVectorSearchIndex(w *walker, n *walk.Node, v *resources.VectorSearchIndex) { if v == nil { - v = &resources.VectorSearchIndex{} + return } - if c := n.child("delta_sync_index_spec"); c.exists() { - enumVectorsearchDeltaSyncVectorIndexSpecRequest(w, &c, v.CreateVectorIndexRequest.DeltaSyncIndexSpec) + if x := v.CreateVectorIndexRequest.DeltaSyncIndexSpec; x != nil { + c := n.Child("delta_sync_index_spec") + enumVectorsearchDeltaSyncVectorIndexSpecRequest(w, &c, x) } if len(v.Grants) > 0 { - c := n.child("grants") - for i0 := range v.Grants { - e := c.elem(i0) - enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i0]) + c := n.Child("grants") + for i := range v.Grants { + e := c.Elem(i) + enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i]) } } - w.enumField(n, "index_subtype", string(v.CreateVectorIndexRequest.IndexSubtype), enumValuesVectorsearchIndexSubtype) - w.enumField(n, "index_type", string(v.CreateVectorIndexRequest.IndexType), enumValuesVectorsearchVectorIndexType) + if s := string(v.CreateVectorIndexRequest.IndexSubtype); s != "" { + c := n.Child("index_subtype") + w.enumValue(&c, s, enumValues26) + } + if s := string(v.CreateVectorIndexRequest.IndexType); s != "" { + c := n.Child("index_type") + w.enumValue(&c, s, enumValues27) + } } -func enumResourcesVolume(w *walker, n *node, v *resources.Volume) { +func enumResourcesVolume(w *walker, n *walk.Node, v *resources.Volume) { if v == nil { - v = &resources.Volume{} + return } if len(v.Grants) > 0 { - c := n.child("grants") - for i0 := range v.Grants { - e := c.elem(i0) - enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i0]) + c := n.Child("grants") + for i := range v.Grants { + e := c.Elem(i) + enumCatalogPrivilegeAssignment(w, &e, &v.Grants[i]) } } - w.enumField(n, "volume_type", string(v.CreateVolumeRequestContent.VolumeType), enumValuesCatalogVolumeType) + if s := string(v.CreateVolumeRequestContent.VolumeType); s != "" || walk.Forced(v.CreateVolumeRequestContent.ForceSendFields, "VolumeType") { + c := n.Child("volume_type") + w.enumValue(&c, s, enumValues28) + } } -func enumSqlAlertV2Evaluation(w *walker, n *node, v *sql.AlertV2Evaluation) { +func enumSqlAlertV2Evaluation(w *walker, n *walk.Node, v *sql.AlertV2Evaluation) { if v == nil { - v = &sql.AlertV2Evaluation{} + return + } + if s := string(v.ComparisonOperator); s != "" || walk.Forced(v.ForceSendFields, "ComparisonOperator") { + c := n.Child("comparison_operator") + w.enumValue(&c, s, enumValues29) } - w.enumField(n, "comparison_operator", string(v.ComparisonOperator), enumValuesSqlComparisonOperator) - w.enumField(n, "empty_result_state", string(v.EmptyResultState), enumValuesSqlAlertEvaluationState) - if c := n.child("source"); c.exists() { - enumSqlAlertV2OperandColumn(w, &c, &v.Source) + if s := string(v.EmptyResultState); s != "" || walk.Forced(v.ForceSendFields, "EmptyResultState") { + c := n.Child("empty_result_state") + w.enumValue(&c, s, enumValues30) } - w.enumField(n, "state", string(v.State), enumValuesSqlAlertEvaluationState) - if c := n.child("threshold"); c.exists() { - enumSqlAlertV2Operand(w, &c, v.Threshold) + if x := &v.Source; !walk.IsZeroStruct(x) || walk.Forced(v.ForceSendFields, "Source") || n.HasRefs("source") { + c := n.Child("source") + enumSqlAlertV2OperandColumn(w, &c, x) + } + if s := string(v.State); s != "" || walk.Forced(v.ForceSendFields, "State") { + c := n.Child("state") + w.enumValue(&c, s, enumValues30) + } + if x := v.Threshold; x != nil { + c := n.Child("threshold") + enumSqlAlertV2Operand(w, &c, x) } } -func enumSqlCronSchedule(w *walker, n *node, v *sql.CronSchedule) { +func enumSqlCronSchedule(w *walker, n *walk.Node, v *sql.CronSchedule) { if v == nil { - v = &sql.CronSchedule{} + return + } + if s := string(v.PauseStatus); s != "" { + c := n.Child("pause_status") + w.enumValue(&c, s, enumValues31) } - w.enumField(n, "pause_status", string(v.PauseStatus), enumValuesSqlSchedulePauseStatus) } -func enumAppsAppDeployment(w *walker, n *node, v *apps.AppDeployment) { +func enumAppsAppDeployment(w *walker, n *walk.Node, v *apps.AppDeployment) { if v == nil { - v = &apps.AppDeployment{} + return } - w.enumField(n, "mode", string(v.Mode), enumValuesAppsAppDeploymentMode) - if c := n.child("status"); c.exists() { - enumAppsAppDeploymentStatus(w, &c, v.Status) + if s := string(v.Mode); s != "" || walk.Forced(v.ForceSendFields, "Mode") { + c := n.Child("mode") + w.enumValue(&c, s, enumValues32) + } + if x := v.Status; x != nil { + c := n.Child("status") + enumAppsAppDeploymentStatus(w, &c, x) } } -func enumAppsApplicationStatus(w *walker, n *node, v *apps.ApplicationStatus) { +func enumAppsApplicationStatus(w *walker, n *walk.Node, v *apps.ApplicationStatus) { if v == nil { - v = &apps.ApplicationStatus{} + return + } + if s := string(v.State); s != "" || walk.Forced(v.ForceSendFields, "State") { + c := n.Child("state") + w.enumValue(&c, s, enumValues33) } - w.enumField(n, "state", string(v.State), enumValuesAppsApplicationState) } -func enumAppsComputeStatus(w *walker, n *node, v *apps.ComputeStatus) { +func enumAppsComputeStatus(w *walker, n *walk.Node, v *apps.ComputeStatus) { if v == nil { - v = &apps.ComputeStatus{} + return + } + if s := string(v.State); s != "" || walk.Forced(v.ForceSendFields, "State") { + c := n.Child("state") + w.enumValue(&c, s, enumValues34) } - w.enumField(n, "state", string(v.State), enumValuesAppsComputeState) } -func enumResourcesAppPermission(w *walker, n *node, v *resources.AppPermission) { +func enumResourcesAppPermission(w *walker, n *walk.Node, v *resources.AppPermission) { if v == nil { - v = &resources.AppPermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues35) } - w.enumField(n, "level", string(v.Level), enumValuesAppsAppPermissionLevel) } -func enumAppsAppResource(w *walker, n *node, v *apps.AppResource) { +func enumAppsAppResource(w *walker, n *walk.Node, v *apps.AppResource) { if v == nil { - v = &apps.AppResource{} + return } - if c := n.child("app"); c.exists() { - enumAppsAppResourceApp(w, &c, v.App) + if x := v.App; x != nil { + c := n.Child("app") + enumAppsAppResourceApp(w, &c, x) } - if c := n.child("database"); c.exists() { - enumAppsAppResourceDatabase(w, &c, v.Database) + if x := v.Database; x != nil { + c := n.Child("database") + enumAppsAppResourceDatabase(w, &c, x) } - if c := n.child("experiment"); c.exists() { - enumAppsAppResourceExperiment(w, &c, v.Experiment) + if x := v.Experiment; x != nil { + c := n.Child("experiment") + enumAppsAppResourceExperiment(w, &c, x) } - if c := n.child("genie_space"); c.exists() { - enumAppsAppResourceGenieSpace(w, &c, v.GenieSpace) + if x := v.GenieSpace; x != nil { + c := n.Child("genie_space") + enumAppsAppResourceGenieSpace(w, &c, x) } - if c := n.child("job"); c.exists() { - enumAppsAppResourceJob(w, &c, v.Job) + if x := v.Job; x != nil { + c := n.Child("job") + enumAppsAppResourceJob(w, &c, x) } - if c := n.child("postgres"); c.exists() { - enumAppsAppResourcePostgres(w, &c, v.Postgres) + if x := v.Postgres; x != nil { + c := n.Child("postgres") + enumAppsAppResourcePostgres(w, &c, x) } - if c := n.child("secret"); c.exists() { - enumAppsAppResourceSecret(w, &c, v.Secret) + if x := v.Secret; x != nil { + c := n.Child("secret") + enumAppsAppResourceSecret(w, &c, x) } - if c := n.child("serving_endpoint"); c.exists() { - enumAppsAppResourceServingEndpoint(w, &c, v.ServingEndpoint) + if x := v.ServingEndpoint; x != nil { + c := n.Child("serving_endpoint") + enumAppsAppResourceServingEndpoint(w, &c, x) } - if c := n.child("sql_warehouse"); c.exists() { - enumAppsAppResourceSqlWarehouse(w, &c, v.SqlWarehouse) + if x := v.SqlWarehouse; x != nil { + c := n.Child("sql_warehouse") + enumAppsAppResourceSqlWarehouse(w, &c, x) } - if c := n.child("uc_securable"); c.exists() { - enumAppsAppResourceUcSecurable(w, &c, v.UcSecurable) + if x := v.UcSecurable; x != nil { + c := n.Child("uc_securable") + enumAppsAppResourceUcSecurable(w, &c, x) } } -func enumCatalogPrivilegeAssignment(w *walker, n *node, v *catalog.PrivilegeAssignment) { +func enumCatalogPrivilegeAssignment(w *walker, n *walk.Node, v *catalog.PrivilegeAssignment) { if v == nil { - v = &catalog.PrivilegeAssignment{} + return } if len(v.Privileges) > 0 { - c := n.child("privileges") - for i0 := range v.Privileges { - e := c.elem(i0) - w.enumElem(&e, string(v.Privileges[i0]), enumValuesCatalogPrivilege) + c := n.Child("privileges") + for i := range v.Privileges { + e := c.Elem(i) + w.enumValue(&e, string(v.Privileges[i]), enumValues36) } } } -func enumResourcesClusterPolicyPermission(w *walker, n *node, v *resources.ClusterPolicyPermission) { +func enumResourcesClusterPolicyPermission(w *walker, n *walk.Node, v *resources.ClusterPolicyPermission) { if v == nil { - v = &resources.ClusterPolicyPermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues37) } - w.enumField(n, "level", string(v.Level), enumValuesComputeClusterPolicyPermissionLevel) } -func enumComputeAwsAttributes(w *walker, n *node, v *compute.AwsAttributes) { +func enumComputeAwsAttributes(w *walker, n *walk.Node, v *compute.AwsAttributes) { if v == nil { - v = &compute.AwsAttributes{} + return + } + if s := string(v.Availability); s != "" || walk.Forced(v.ForceSendFields, "Availability") { + c := n.Child("availability") + w.enumValue(&c, s, enumValues38) + } + if s := string(v.EbsVolumeType); s != "" || walk.Forced(v.ForceSendFields, "EbsVolumeType") { + c := n.Child("ebs_volume_type") + w.enumValue(&c, s, enumValues39) } - w.enumField(n, "availability", string(v.Availability), enumValuesComputeAwsAvailability) - w.enumField(n, "ebs_volume_type", string(v.EbsVolumeType), enumValuesComputeEbsVolumeType) } -func enumComputeAzureAttributes(w *walker, n *node, v *compute.AzureAttributes) { +func enumComputeAzureAttributes(w *walker, n *walk.Node, v *compute.AzureAttributes) { if v == nil { - v = &compute.AzureAttributes{} + return + } + if s := string(v.Availability); s != "" || walk.Forced(v.ForceSendFields, "Availability") { + c := n.Child("availability") + w.enumValue(&c, s, enumValues40) } - w.enumField(n, "availability", string(v.Availability), enumValuesComputeAzureAvailability) } -func enumComputeGcpAttributes(w *walker, n *node, v *compute.GcpAttributes) { +func enumComputeGcpAttributes(w *walker, n *walk.Node, v *compute.GcpAttributes) { if v == nil { - v = &compute.GcpAttributes{} + return + } + if s := string(v.Availability); s != "" || walk.Forced(v.ForceSendFields, "Availability") { + c := n.Child("availability") + w.enumValue(&c, s, enumValues41) + } + if s := string(v.ConfidentialComputeType); s != "" || walk.Forced(v.ForceSendFields, "ConfidentialComputeType") { + c := n.Child("confidential_compute_type") + w.enumValue(&c, s, enumValues42) } - w.enumField(n, "availability", string(v.Availability), enumValuesComputeGcpAvailability) - w.enumField(n, "confidential_compute_type", string(v.ConfidentialComputeType), enumValuesComputeConfidentialComputeType) } -func enumResourcesClusterPermission(w *walker, n *node, v *resources.ClusterPermission) { +func enumResourcesClusterPermission(w *walker, n *walk.Node, v *resources.ClusterPermission) { if v == nil { - v = &resources.ClusterPermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues43) } - w.enumField(n, "level", string(v.Level), enumValuesComputeClusterPermissionLevel) } -func enumResourcesMlflowExperimentPermission(w *walker, n *node, v *resources.MlflowExperimentPermission) { +func enumResourcesMlflowExperimentPermission(w *walker, n *walk.Node, v *resources.MlflowExperimentPermission) { if v == nil { - v = &resources.MlflowExperimentPermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues44) } - w.enumField(n, "level", string(v.Level), enumValuesMlExperimentPermissionLevel) } -func enumCatalogEncryptionDetails(w *walker, n *node, v *catalog.EncryptionDetails) { +func enumCatalogEncryptionDetails(w *walker, n *walk.Node, v *catalog.EncryptionDetails) { if v == nil { - v = &catalog.EncryptionDetails{} + return } - if c := n.child("sse_encryption_details"); c.exists() { - enumCatalogSseEncryptionDetails(w, &c, v.SseEncryptionDetails) + if x := v.SseEncryptionDetails; x != nil { + c := n.Child("sse_encryption_details") + enumCatalogSseEncryptionDetails(w, &c, x) } } -func enumMlFunction(w *walker, n *node, v *ml.Function) { +func enumMlFunction(w *walker, n *walk.Node, v *ml.Function) { if v == nil { - v = &ml.Function{} + return + } + if s := string(v.FunctionType); s != "" { + c := n.Child("function_type") + w.enumValue(&c, s, enumValues45) } - w.enumField(n, "function_type", string(v.FunctionType), enumValuesMlFunctionFunctionType) } -func enumMlDataSource(w *walker, n *node, v *ml.DataSource) { +func enumMlDataSource(w *walker, n *walk.Node, v *ml.DataSource) { if v == nil { - v = &ml.DataSource{} + return } - if c := n.child("request_source"); c.exists() { - enumMlRequestSource(w, &c, v.RequestSource) + if x := v.RequestSource; x != nil { + c := n.Child("request_source") + enumMlRequestSource(w, &c, x) } - if c := n.child("sdp_source"); c.exists() { - enumMlSdpSource(w, &c, v.SdpSource) + if x := v.SdpSource; x != nil { + c := n.Child("sdp_source") + enumMlSdpSource(w, &c, x) } } -func enumComputeInstancePoolAwsAttributes(w *walker, n *node, v *compute.InstancePoolAwsAttributes) { +func enumComputeInstancePoolAwsAttributes(w *walker, n *walk.Node, v *compute.InstancePoolAwsAttributes) { if v == nil { - v = &compute.InstancePoolAwsAttributes{} + return + } + if s := string(v.Availability); s != "" || walk.Forced(v.ForceSendFields, "Availability") { + c := n.Child("availability") + w.enumValue(&c, s, enumValues46) } - w.enumField(n, "availability", string(v.Availability), enumValuesComputeInstancePoolAwsAttributesAvailability) } -func enumComputeInstancePoolAzureAttributes(w *walker, n *node, v *compute.InstancePoolAzureAttributes) { +func enumComputeInstancePoolAzureAttributes(w *walker, n *walk.Node, v *compute.InstancePoolAzureAttributes) { if v == nil { - v = &compute.InstancePoolAzureAttributes{} + return + } + if s := string(v.Availability); s != "" || walk.Forced(v.ForceSendFields, "Availability") { + c := n.Child("availability") + w.enumValue(&c, s, enumValues47) } - w.enumField(n, "availability", string(v.Availability), enumValuesComputeInstancePoolAzureAttributesAvailability) } -func enumComputeDiskSpec(w *walker, n *node, v *compute.DiskSpec) { +func enumComputeDiskSpec(w *walker, n *walk.Node, v *compute.DiskSpec) { if v == nil { - v = &compute.DiskSpec{} + return } - if c := n.child("disk_type"); c.exists() { - enumComputeDiskType(w, &c, v.DiskType) + if x := v.DiskType; x != nil { + c := n.Child("disk_type") + enumComputeDiskType(w, &c, x) } } -func enumComputeInstancePoolGcpAttributes(w *walker, n *node, v *compute.InstancePoolGcpAttributes) { +func enumComputeInstancePoolGcpAttributes(w *walker, n *walk.Node, v *compute.InstancePoolGcpAttributes) { if v == nil { - v = &compute.InstancePoolGcpAttributes{} + return + } + if s := string(v.GcpAvailability); s != "" || walk.Forced(v.ForceSendFields, "GcpAvailability") { + c := n.Child("gcp_availability") + w.enumValue(&c, s, enumValues41) } - w.enumField(n, "gcp_availability", string(v.GcpAvailability), enumValuesComputeGcpAvailability) } -func enumResourcesInstancePoolPermission(w *walker, n *node, v *resources.InstancePoolPermission) { +func enumResourcesInstancePoolPermission(w *walker, n *walk.Node, v *resources.InstancePoolPermission) { if v == nil { - v = &resources.InstancePoolPermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues48) } - w.enumField(n, "level", string(v.Level), enumValuesComputeInstancePoolPermissionLevel) } -func enumJobsContinuous(w *walker, n *node, v *jobs.Continuous) { +func enumJobsContinuous(w *walker, n *walk.Node, v *jobs.Continuous) { if v == nil { - v = &jobs.Continuous{} + return + } + if x := v.MaintenanceWindow; x != nil { + c := n.Child("maintenance_window") + enumJobsMaintenanceWindow(w, &c, x) + } + if s := string(v.PauseStatus); s != "" { + c := n.Child("pause_status") + w.enumValue(&c, s, enumValues31) } - if c := n.child("maintenance_window"); c.exists() { - enumJobsMaintenanceWindow(w, &c, v.MaintenanceWindow) + if s := string(v.TaskRetryMode); s != "" { + c := n.Child("task_retry_mode") + w.enumValue(&c, s, enumValues49) } - w.enumField(n, "pause_status", string(v.PauseStatus), enumValuesJobsPauseStatus) - w.enumField(n, "task_retry_mode", string(v.TaskRetryMode), enumValuesJobsTaskRetryMode) } -func enumJobsJobDeployment(w *walker, n *node, v *jobs.JobDeployment) { +func enumJobsJobDeployment(w *walker, n *walk.Node, v *jobs.JobDeployment) { if v == nil { - v = &jobs.JobDeployment{} + return + } + if s := string(v.Kind); s != "" || walk.Forced(v.ForceSendFields, "Kind") { + c := n.Child("kind") + w.enumValue(&c, s, enumValues50) } - w.enumField(n, "kind", string(v.Kind), enumValuesJobsJobDeploymentKind) } -func enumJobsGitSource(w *walker, n *node, v *jobs.GitSource) { +func enumJobsGitSource(w *walker, n *walk.Node, v *jobs.GitSource) { if v == nil { - v = &jobs.GitSource{} + return + } + if s := string(v.GitProvider); s != "" || walk.Forced(v.ForceSendFields, "GitProvider") { + c := n.Child("git_provider") + w.enumValue(&c, s, enumValues51) } - w.enumField(n, "git_provider", string(v.GitProvider), enumValuesJobsGitProvider) - if c := n.child("job_source"); c.exists() { - enumJobsJobSource(w, &c, v.JobSource) + if x := v.JobSource; x != nil { + c := n.Child("job_source") + enumJobsJobSource(w, &c, x) } } -func enumJobsJobsHealthRules(w *walker, n *node, v *jobs.JobsHealthRules) { +func enumJobsJobsHealthRules(w *walker, n *walk.Node, v *jobs.JobsHealthRules) { if v == nil { - v = &jobs.JobsHealthRules{} + return } if len(v.Rules) > 0 { - c := n.child("rules") - for i0 := range v.Rules { - e := c.elem(i0) - enumJobsJobsHealthRule(w, &e, &v.Rules[i0]) + c := n.Child("rules") + for i := range v.Rules { + e := c.Elem(i) + enumJobsJobsHealthRule(w, &e, &v.Rules[i]) } } } -func enumJobsJobCluster(w *walker, n *node, v *jobs.JobCluster) { +func enumJobsJobCluster(w *walker, n *walk.Node, v *jobs.JobCluster) { if v == nil { - v = &jobs.JobCluster{} + return } - if c := n.child("new_cluster"); c.exists() { - enumComputeClusterSpec(w, &c, v.NewCluster) + if x := v.NewCluster; x != nil { + c := n.Child("new_cluster") + enumComputeClusterSpec(w, &c, x) } } -func enumResourcesJobPermission(w *walker, n *node, v *resources.JobPermission) { +func enumResourcesJobPermission(w *walker, n *walk.Node, v *resources.JobPermission) { if v == nil { - v = &resources.JobPermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues52) } - w.enumField(n, "level", string(v.Level), enumValuesJobsJobPermissionLevel) } -func enumJobsCronSchedule(w *walker, n *node, v *jobs.CronSchedule) { +func enumJobsCronSchedule(w *walker, n *walk.Node, v *jobs.CronSchedule) { if v == nil { - v = &jobs.CronSchedule{} + return } - w.enumField(n, "pause_status", string(v.PauseStatus), enumValuesJobsPauseStatus) - if c := n.child("sql_condition"); c.exists() { - enumJobsSqlConditionConfiguration(w, &c, v.SqlCondition) + if s := string(v.PauseStatus); s != "" { + c := n.Child("pause_status") + w.enumValue(&c, s, enumValues31) + } + if x := v.SqlCondition; x != nil { + c := n.Child("sql_condition") + enumJobsSqlConditionConfiguration(w, &c, x) } } -func enumJobsTask(w *walker, n *node, v *jobs.Task) { +func enumJobsTask(w *walker, n *walk.Node, v *jobs.Task) { if v == nil { - v = &jobs.Task{} + return + } + if x := v.AiRuntimeTask; x != nil { + c := n.Child("ai_runtime_task") + enumJobsAiRuntimeTask(w, &c, x) } - if c := n.child("ai_runtime_task"); c.exists() { - enumJobsAiRuntimeTask(w, &c, v.AiRuntimeTask) + if x := v.Compute; x != nil { + c := n.Child("compute") + enumJobsCompute(w, &c, x) } - if c := n.child("compute"); c.exists() { - enumJobsCompute(w, &c, v.Compute) + if x := v.ConditionTask; x != nil { + c := n.Child("condition_task") + enumJobsConditionTask(w, &c, x) } - if c := n.child("condition_task"); c.exists() { - enumJobsConditionTask(w, &c, v.ConditionTask) + if x := v.DashboardTask; x != nil { + c := n.Child("dashboard_task") + enumJobsDashboardTask(w, &c, x) } - if c := n.child("dashboard_task"); c.exists() { - enumJobsDashboardTask(w, &c, v.DashboardTask) + if x := v.DbtTask; x != nil { + c := n.Child("dbt_task") + enumJobsDbtTask(w, &c, x) } - if c := n.child("dbt_task"); c.exists() { - enumJobsDbtTask(w, &c, v.DbtTask) + if x := v.ForEachTask; x != nil { + c := n.Child("for_each_task") + enumJobsForEachTask(w, &c, x) } - if c := n.child("for_each_task"); c.exists() { - enumJobsForEachTask(w, &c, v.ForEachTask) + if x := v.GenAiComputeTask; x != nil { + c := n.Child("gen_ai_compute_task") + enumJobsGenAiComputeTask(w, &c, x) } - if c := n.child("gen_ai_compute_task"); c.exists() { - enumJobsGenAiComputeTask(w, &c, v.GenAiComputeTask) + if x := v.Health; x != nil { + c := n.Child("health") + enumJobsJobsHealthRules(w, &c, x) } - if c := n.child("health"); c.exists() { - enumJobsJobsHealthRules(w, &c, v.Health) + if x := v.NewCluster; x != nil { + c := n.Child("new_cluster") + enumComputeClusterSpec(w, &c, x) } - if c := n.child("new_cluster"); c.exists() { - enumComputeClusterSpec(w, &c, v.NewCluster) + if x := v.NotebookTask; x != nil { + c := n.Child("notebook_task") + enumJobsNotebookTask(w, &c, x) } - if c := n.child("notebook_task"); c.exists() { - enumJobsNotebookTask(w, &c, v.NotebookTask) + if x := v.PowerBiTask; x != nil { + c := n.Child("power_bi_task") + enumJobsPowerBiTask(w, &c, x) } - if c := n.child("power_bi_task"); c.exists() { - enumJobsPowerBiTask(w, &c, v.PowerBiTask) + if s := string(v.RunIf); s != "" || walk.Forced(v.ForceSendFields, "RunIf") { + c := n.Child("run_if") + w.enumValue(&c, s, enumValues53) } - w.enumField(n, "run_if", string(v.RunIf), enumValuesJobsRunIf) - if c := n.child("spark_python_task"); c.exists() { - enumJobsSparkPythonTask(w, &c, v.SparkPythonTask) + if x := v.SparkPythonTask; x != nil { + c := n.Child("spark_python_task") + enumJobsSparkPythonTask(w, &c, x) } - if c := n.child("sql_task"); c.exists() { - enumJobsSqlTask(w, &c, v.SqlTask) + if x := v.SqlTask; x != nil { + c := n.Child("sql_task") + enumJobsSqlTask(w, &c, x) } } -func enumJobsTriggerSettings(w *walker, n *node, v *jobs.TriggerSettings) { +func enumJobsTriggerSettings(w *walker, n *walk.Node, v *jobs.TriggerSettings) { if v == nil { - v = &jobs.TriggerSettings{} + return + } + if x := v.Model; x != nil { + c := n.Child("model") + enumJobsModelTriggerConfiguration(w, &c, x) } - if c := n.child("model"); c.exists() { - enumJobsModelTriggerConfiguration(w, &c, v.Model) + if s := string(v.PauseStatus); s != "" { + c := n.Child("pause_status") + w.enumValue(&c, s, enumValues31) } - w.enumField(n, "pause_status", string(v.PauseStatus), enumValuesJobsPauseStatus) - if c := n.child("periodic"); c.exists() { - enumJobsPeriodicTriggerConfiguration(w, &c, v.Periodic) + if x := v.Periodic; x != nil { + c := n.Child("periodic") + enumJobsPeriodicTriggerConfiguration(w, &c, x) } - if c := n.child("sql_condition"); c.exists() { - enumJobsSqlConditionConfiguration(w, &c, v.SqlCondition) + if x := v.SqlCondition; x != nil { + c := n.Child("sql_condition") + enumJobsSqlConditionConfiguration(w, &c, x) } - if c := n.child("table_update"); c.exists() { - enumJobsTableUpdateTriggerConfiguration(w, &c, v.TableUpdate) + if x := v.TableUpdate; x != nil { + c := n.Child("table_update") + enumJobsTableUpdateTriggerConfiguration(w, &c, x) } } -func enumJobsTriggerConfiguration(w *walker, n *node, v *jobs.TriggerConfiguration) { +func enumJobsTriggerConfiguration(w *walker, n *walk.Node, v *jobs.TriggerConfiguration) { if v == nil { - v = &jobs.TriggerConfiguration{} + return } - if c := n.child("continuous"); c.exists() { - enumJobsContinuousTriggerConfiguration(w, &c, v.Continuous) + if x := v.Continuous; x != nil { + c := n.Child("continuous") + enumJobsContinuousTriggerConfiguration(w, &c, x) } - if c := n.child("model"); c.exists() { - enumJobsModelTriggerConfiguration(w, &c, v.Model) + if x := v.Model; x != nil { + c := n.Child("model") + enumJobsModelTriggerConfiguration(w, &c, x) } - w.enumField(n, "pause_status", string(v.PauseStatus), enumValuesJobsPauseStatus) - if c := n.child("periodic"); c.exists() { - enumJobsPeriodicTriggerConfiguration(w, &c, v.Periodic) + if s := string(v.PauseStatus); s != "" { + c := n.Child("pause_status") + w.enumValue(&c, s, enumValues31) } - if c := n.child("sql_condition"); c.exists() { - enumJobsSqlConditionConfiguration(w, &c, v.SqlCondition) + if x := v.Periodic; x != nil { + c := n.Child("periodic") + enumJobsPeriodicTriggerConfiguration(w, &c, x) } - if c := n.child("table_update"); c.exists() { - enumJobsTableUpdateTriggerConfiguration(w, &c, v.TableUpdate) + if x := v.SqlCondition; x != nil { + c := n.Child("sql_condition") + enumJobsSqlConditionConfiguration(w, &c, x) + } + if x := v.TableUpdate; x != nil { + c := n.Child("table_update") + enumJobsTableUpdateTriggerConfiguration(w, &c, x) } } -func enumCatalogMcpServiceConfig(w *walker, n *node, v *catalog.McpServiceConfig) { +func enumCatalogMcpServiceConfig(w *walker, n *walk.Node, v *catalog.McpServiceConfig) { if v == nil { - v = &catalog.McpServiceConfig{} + return } if len(v.RateLimits) > 0 { - c := n.child("rate_limits") - for i0 := range v.RateLimits { - e := c.elem(i0) - enumCatalogRateLimit(w, &e, &v.RateLimits[i0]) + c := n.Child("rate_limits") + for i := range v.RateLimits { + e := c.Elem(i) + enumCatalogRateLimit(w, &e, &v.RateLimits[i]) } } } -func enumCatalogModelProviderServiceConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfig) { +func enumCatalogModelProviderServiceConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfig{} + return + } + if s := string(v.ProviderType); s != "" || walk.Forced(v.ForceSendFields, "ProviderType") { + c := n.Child("provider_type") + w.enumValue(&c, s, enumValues54) } - w.enumField(n, "provider_type", string(v.ProviderType), enumValuesCatalogModelProviderServiceConfigExternalModelProviderType) if len(v.RateLimits) > 0 { - c := n.child("rate_limits") - for i0 := range v.RateLimits { - e := c.elem(i0) - enumCatalogRateLimit(w, &e, &v.RateLimits[i0]) + c := n.Child("rate_limits") + for i := range v.RateLimits { + e := c.Elem(i) + enumCatalogRateLimit(w, &e, &v.RateLimits[i]) } } } -func enumCatalogModelServiceConfig(w *walker, n *node, v *catalog.ModelServiceConfig) { +func enumCatalogModelServiceConfig(w *walker, n *walk.Node, v *catalog.ModelServiceConfig) { if v == nil { - v = &catalog.ModelServiceConfig{} + return } if len(v.RateLimits) > 0 { - c := n.child("rate_limits") - for i0 := range v.RateLimits { - e := c.elem(i0) - enumCatalogRateLimit(w, &e, &v.RateLimits[i0]) + c := n.Child("rate_limits") + for i := range v.RateLimits { + e := c.Elem(i) + enumCatalogRateLimit(w, &e, &v.RateLimits[i]) } } - if c := n.child("routing"); c.exists() { - enumCatalogModelServiceConfigRoutingConfig(w, &c, v.Routing) + if x := v.Routing; x != nil { + c := n.Child("routing") + enumCatalogModelServiceConfigRoutingConfig(w, &c, x) } } -func enumServingAiGatewayConfig(w *walker, n *node, v *serving.AiGatewayConfig) { +func enumServingAiGatewayConfig(w *walker, n *walk.Node, v *serving.AiGatewayConfig) { if v == nil { - v = &serving.AiGatewayConfig{} + return } - if c := n.child("guardrails"); c.exists() { - enumServingAiGatewayGuardrails(w, &c, v.Guardrails) + if x := v.Guardrails; x != nil { + c := n.Child("guardrails") + enumServingAiGatewayGuardrails(w, &c, x) } if len(v.RateLimits) > 0 { - c := n.child("rate_limits") - for i0 := range v.RateLimits { - e := c.elem(i0) - enumServingAiGatewayRateLimit(w, &e, &v.RateLimits[i0]) + c := n.Child("rate_limits") + for i := range v.RateLimits { + e := c.Elem(i) + enumServingAiGatewayRateLimit(w, &e, &v.RateLimits[i]) } } } -func enumServingEndpointCoreConfigInput(w *walker, n *node, v *serving.EndpointCoreConfigInput) { +func enumServingEndpointCoreConfigInput(w *walker, n *walk.Node, v *serving.EndpointCoreConfigInput) { if v == nil { - v = &serving.EndpointCoreConfigInput{} + return } if len(v.ServedEntities) > 0 { - c := n.child("served_entities") - for i0 := range v.ServedEntities { - e := c.elem(i0) - enumServingServedEntityInput(w, &e, &v.ServedEntities[i0]) + c := n.Child("served_entities") + for i := range v.ServedEntities { + e := c.Elem(i) + enumServingServedEntityInput(w, &e, &v.ServedEntities[i]) } } if len(v.ServedModels) > 0 { - c := n.child("served_models") - for i0 := range v.ServedModels { - e := c.elem(i0) - enumServingServedModelInput(w, &e, &v.ServedModels[i0]) + c := n.Child("served_models") + for i := range v.ServedModels { + e := c.Elem(i) + enumServingServedModelInput(w, &e, &v.ServedModels[i]) } } } -func enumResourcesModelServingEndpointPermission(w *walker, n *node, v *resources.ModelServingEndpointPermission) { +func enumResourcesModelServingEndpointPermission(w *walker, n *walk.Node, v *resources.ModelServingEndpointPermission) { if v == nil { - v = &resources.ModelServingEndpointPermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues55) } - w.enumField(n, "level", string(v.Level), enumValuesServingServingEndpointPermissionLevel) } -func enumServingRateLimit(w *walker, n *node, v *serving.RateLimit) { +func enumServingRateLimit(w *walker, n *walk.Node, v *serving.RateLimit) { if v == nil { - v = &serving.RateLimit{} + return + } + if s := string(v.Key); s != "" { + c := n.Child("key") + w.enumValue(&c, s, enumValues56) + } + if s := string(v.RenewalPeriod); s != "" { + c := n.Child("renewal_period") + w.enumValue(&c, s, enumValues57) } - w.enumField(n, "key", string(v.Key), enumValuesServingRateLimitKey) - w.enumField(n, "renewal_period", string(v.RenewalPeriod), enumValuesServingRateLimitRenewalPeriod) } -func enumServingTelemetryConfig(w *walker, n *node, v *serving.TelemetryConfig) { +func enumServingTelemetryConfig(w *walker, n *walk.Node, v *serving.TelemetryConfig) { if v == nil { - v = &serving.TelemetryConfig{} + return } if len(v.EnabledTelemetryFeatures) > 0 { - c := n.child("enabled_telemetry_features") - for i0 := range v.EnabledTelemetryFeatures { - e := c.elem(i0) - w.enumElem(&e, string(v.EnabledTelemetryFeatures[i0]), enumValuesServingTelemetryFeature) + c := n.Child("enabled_telemetry_features") + for i := range v.EnabledTelemetryFeatures { + e := c.Elem(i) + w.enumValue(&e, string(v.EnabledTelemetryFeatures[i]), enumValues58) } } } -func enumResourcesMlflowModelPermission(w *walker, n *node, v *resources.MlflowModelPermission) { +func enumResourcesMlflowModelPermission(w *walker, n *walk.Node, v *resources.MlflowModelPermission) { if v == nil { - v = &resources.MlflowModelPermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues59) } - w.enumField(n, "level", string(v.Level), enumValuesMlRegisteredModelPermissionLevel) } -func enumPipelinesPipelineCluster(w *walker, n *node, v *pipelines.PipelineCluster) { +func enumPipelinesPipelineCluster(w *walker, n *walk.Node, v *pipelines.PipelineCluster) { if v == nil { - v = &pipelines.PipelineCluster{} + return } - if c := n.child("autoscale"); c.exists() { - enumPipelinesPipelineClusterAutoscale(w, &c, v.Autoscale) + if x := v.Autoscale; x != nil { + c := n.Child("autoscale") + enumPipelinesPipelineClusterAutoscale(w, &c, x) } - if c := n.child("aws_attributes"); c.exists() { - enumComputeAwsAttributes(w, &c, v.AwsAttributes) + if x := v.AwsAttributes; x != nil { + c := n.Child("aws_attributes") + enumComputeAwsAttributes(w, &c, x) } - if c := n.child("azure_attributes"); c.exists() { - enumComputeAzureAttributes(w, &c, v.AzureAttributes) + if x := v.AzureAttributes; x != nil { + c := n.Child("azure_attributes") + enumComputeAzureAttributes(w, &c, x) } - if c := n.child("gcp_attributes"); c.exists() { - enumComputeGcpAttributes(w, &c, v.GcpAttributes) + if x := v.GcpAttributes; x != nil { + c := n.Child("gcp_attributes") + enumComputeGcpAttributes(w, &c, x) } } -func enumPipelinesPipelineDeployment(w *walker, n *node, v *pipelines.PipelineDeployment) { +func enumPipelinesPipelineDeployment(w *walker, n *walk.Node, v *pipelines.PipelineDeployment) { if v == nil { - v = &pipelines.PipelineDeployment{} + return + } + if s := string(v.Kind); s != "" || walk.Forced(v.ForceSendFields, "Kind") { + c := n.Child("kind") + w.enumValue(&c, s, enumValues60) } - w.enumField(n, "kind", string(v.Kind), enumValuesPipelinesDeploymentKind) } -func enumPipelinesIngestionPipelineDefinition(w *walker, n *node, v *pipelines.IngestionPipelineDefinition) { +func enumPipelinesIngestionPipelineDefinition(w *walker, n *walk.Node, v *pipelines.IngestionPipelineDefinition) { if v == nil { - v = &pipelines.IngestionPipelineDefinition{} + return + } + if s := string(v.ConnectorType); s != "" || walk.Forced(v.ForceSendFields, "ConnectorType") { + c := n.Child("connector_type") + w.enumValue(&c, s, enumValues61) } - w.enumField(n, "connector_type", string(v.ConnectorType), enumValuesPipelinesConnectorType) - if c := n.child("full_refresh_window"); c.exists() { - enumPipelinesOperationTimeWindow(w, &c, v.FullRefreshWindow) + if x := v.FullRefreshWindow; x != nil { + c := n.Child("full_refresh_window") + enumPipelinesOperationTimeWindow(w, &c, x) } if len(v.Objects) > 0 { - c := n.child("objects") - for i0 := range v.Objects { - e := c.elem(i0) - enumPipelinesIngestionConfig(w, &e, &v.Objects[i0]) + c := n.Child("objects") + for i := range v.Objects { + e := c.Elem(i) + enumPipelinesIngestionConfig(w, &e, &v.Objects[i]) } } - w.enumField(n, "source_type", string(v.SourceType), enumValuesPipelinesIngestionSourceType) - if c := n.child("table_configuration"); c.exists() { - enumPipelinesTableSpecificConfig(w, &c, v.TableConfiguration) + if s := string(v.SourceType); s != "" || walk.Forced(v.ForceSendFields, "SourceType") { + c := n.Child("source_type") + w.enumValue(&c, s, enumValues62) + } + if x := v.TableConfiguration; x != nil { + c := n.Child("table_configuration") + enumPipelinesTableSpecificConfig(w, &c, x) } } -func enumResourcesPipelinePermission(w *walker, n *node, v *resources.PipelinePermission) { +func enumResourcesPipelinePermission(w *walker, n *walk.Node, v *resources.PipelinePermission) { if v == nil { - v = &resources.PipelinePermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues63) } - w.enumField(n, "level", string(v.Level), enumValuesPipelinesPipelinePermissionLevel) } -func enumPipelinesRestartWindow(w *walker, n *node, v *pipelines.RestartWindow) { +func enumPipelinesRestartWindow(w *walker, n *walk.Node, v *pipelines.RestartWindow) { if v == nil { - v = &pipelines.RestartWindow{} + return } if len(v.DaysOfWeek) > 0 { - c := n.child("days_of_week") - for i0 := range v.DaysOfWeek { - e := c.elem(i0) - w.enumElem(&e, string(v.DaysOfWeek[i0]), enumValuesPipelinesDayOfWeek) + c := n.Child("days_of_week") + for i := range v.DaysOfWeek { + e := c.Elem(i) + w.enumValue(&e, string(v.DaysOfWeek[i]), enumValues64) } } } -func enumPostgresScheduleCadence(w *walker, n *node, v *postgres.ScheduleCadence) { +func enumPostgresScheduleCadence(w *walker, n *walk.Node, v *postgres.ScheduleCadence) { if v == nil { - v = &postgres.ScheduleCadence{} + return } - if c := n.child("weekly_schedule"); c.exists() { - enumPostgresWeeklySchedule(w, &c, v.WeeklySchedule) + if x := v.WeeklySchedule; x != nil { + c := n.Child("weekly_schedule") + enumPostgresWeeklySchedule(w, &c, x) } } -func enumPostgresSyncedTableSyncedTableSpecExtraColumn(w *walker, n *node, v *postgres.SyncedTableSyncedTableSpecExtraColumn) { +func enumPostgresSyncedTableSyncedTableSpecExtraColumn(w *walker, n *walk.Node, v *postgres.SyncedTableSyncedTableSpecExtraColumn) { if v == nil { - v = &postgres.SyncedTableSyncedTableSpecExtraColumn{} + return + } + if s := string(v.Maintenance); s != "" || walk.Forced(v.ForceSendFields, "Maintenance") { + c := n.Child("maintenance") + w.enumValue(&c, s, enumValues65) } - w.enumField(n, "maintenance", string(v.Maintenance), enumValuesPostgresSyncedTableSyncedTableSpecExtraColumnMaintenance) } -func enumPostgresNewPipelineSpec(w *walker, n *node, v *postgres.NewPipelineSpec) { +func enumPostgresNewPipelineSpec(w *walker, n *walk.Node, v *postgres.NewPipelineSpec) { if v == nil { - v = &postgres.NewPipelineSpec{} + return + } + if s := string(v.PipelineChannel); s != "" || walk.Forced(v.ForceSendFields, "PipelineChannel") { + c := n.Child("pipeline_channel") + w.enumValue(&c, s, enumValues66) } - w.enumField(n, "pipeline_channel", string(v.PipelineChannel), enumValuesPostgresNewPipelineSpecPipelineChannel) } -func enumPostgresSyncedTableSyncedTableSpecTypeOverride(w *walker, n *node, v *postgres.SyncedTableSyncedTableSpecTypeOverride) { +func enumPostgresSyncedTableSyncedTableSpecTypeOverride(w *walker, n *walk.Node, v *postgres.SyncedTableSyncedTableSpecTypeOverride) { if v == nil { - v = &postgres.SyncedTableSyncedTableSpecTypeOverride{} + return + } + if s := string(v.PgType); s != "" || walk.Forced(v.ForceSendFields, "PgType") { + c := n.Child("pg_type") + w.enumValue(&c, s, enumValues67) } - w.enumField(n, "pg_type", string(v.PgType), enumValuesPostgresSyncedTableSyncedTableSpecPgSpecificType) } -func enumCatalogMonitorMetric(w *walker, n *node, v *catalog.MonitorMetric) { +func enumCatalogMonitorMetric(w *walker, n *walk.Node, v *catalog.MonitorMetric) { if v == nil { - v = &catalog.MonitorMetric{} + return + } + if s := string(v.Type); s != "" { + c := n.Child("type") + w.enumValue(&c, s, enumValues68) } - w.enumField(n, "type", string(v.Type), enumValuesCatalogMonitorMetricType) } -func enumCatalogMonitorInferenceLog(w *walker, n *node, v *catalog.MonitorInferenceLog) { +func enumCatalogMonitorInferenceLog(w *walker, n *walk.Node, v *catalog.MonitorInferenceLog) { if v == nil { - v = &catalog.MonitorInferenceLog{} + return + } + if s := string(v.ProblemType); s != "" || walk.Forced(v.ForceSendFields, "ProblemType") { + c := n.Child("problem_type") + w.enumValue(&c, s, enumValues69) } - w.enumField(n, "problem_type", string(v.ProblemType), enumValuesCatalogMonitorInferenceLogProblemType) } -func enumCatalogMonitorCronSchedule(w *walker, n *node, v *catalog.MonitorCronSchedule) { +func enumCatalogMonitorCronSchedule(w *walker, n *walk.Node, v *catalog.MonitorCronSchedule) { if v == nil { - v = &catalog.MonitorCronSchedule{} + return + } + if s := string(v.PauseStatus); s != "" { + c := n.Child("pause_status") + w.enumValue(&c, s, enumValues70) } - w.enumField(n, "pause_status", string(v.PauseStatus), enumValuesCatalogMonitorCronSchedulePauseStatus) } -func enumResourcesSecretScopePermission(w *walker, n *node, v *resources.SecretScopePermission) { +func enumResourcesSecretScopePermission(w *walker, n *walk.Node, v *resources.SecretScopePermission) { if v == nil { - v = &resources.SecretScopePermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues71) } - w.enumField(n, "level", string(v.Level), enumValuesResourcesSecretScopePermissionLevel) } -func enumSqlChannel(w *walker, n *node, v *sql.Channel) { +func enumSqlChannel(w *walker, n *walk.Node, v *sql.Channel) { if v == nil { - v = &sql.Channel{} + return + } + if s := string(v.Name); s != "" || walk.Forced(v.ForceSendFields, "Name") { + c := n.Child("name") + w.enumValue(&c, s, enumValues72) } - w.enumField(n, "name", string(v.Name), enumValuesSqlChannelName) } -func enumResourcesSqlWarehousePermission(w *walker, n *node, v *resources.SqlWarehousePermission) { +func enumResourcesSqlWarehousePermission(w *walker, n *walk.Node, v *resources.SqlWarehousePermission) { if v == nil { - v = &resources.SqlWarehousePermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues73) } - w.enumField(n, "level", string(v.Level), enumValuesSqlWarehousePermissionLevel) } -func enumDatabaseSyncedTableStatus(w *walker, n *node, v *database.SyncedTableStatus) { +func enumDatabaseSyncedTableStatus(w *walker, n *walk.Node, v *database.SyncedTableStatus) { if v == nil { - v = &database.SyncedTableStatus{} + return + } + if x := v.ContinuousUpdateStatus; x != nil { + c := n.Child("continuous_update_status") + enumDatabaseSyncedTableContinuousUpdateStatus(w, &c, x) } - if c := n.child("continuous_update_status"); c.exists() { - enumDatabaseSyncedTableContinuousUpdateStatus(w, &c, v.ContinuousUpdateStatus) + if s := string(v.DetailedState); s != "" || walk.Forced(v.ForceSendFields, "DetailedState") { + c := n.Child("detailed_state") + w.enumValue(&c, s, enumValues74) } - w.enumField(n, "detailed_state", string(v.DetailedState), enumValuesDatabaseSyncedTableState) - if c := n.child("provisioning_status"); c.exists() { - enumDatabaseSyncedTableProvisioningStatus(w, &c, v.ProvisioningStatus) + if x := v.ProvisioningStatus; x != nil { + c := n.Child("provisioning_status") + enumDatabaseSyncedTableProvisioningStatus(w, &c, x) } - if c := n.child("triggered_update_status"); c.exists() { - enumDatabaseSyncedTableTriggeredUpdateStatus(w, &c, v.TriggeredUpdateStatus) + if x := v.TriggeredUpdateStatus; x != nil { + c := n.Child("triggered_update_status") + enumDatabaseSyncedTableTriggeredUpdateStatus(w, &c, x) } } -func enumDatabaseSyncedTableSpec(w *walker, n *node, v *database.SyncedTableSpec) { +func enumDatabaseSyncedTableSpec(w *walker, n *walk.Node, v *database.SyncedTableSpec) { if v == nil { - v = &database.SyncedTableSpec{} + return + } + if s := string(v.SchedulingPolicy); s != "" || walk.Forced(v.ForceSendFields, "SchedulingPolicy") { + c := n.Child("scheduling_policy") + w.enumValue(&c, s, enumValues20) } - w.enumField(n, "scheduling_policy", string(v.SchedulingPolicy), enumValuesDatabaseSyncedTableSchedulingPolicy) if len(v.TypeOverrides) > 0 { - c := n.child("type_overrides") - for i0 := range v.TypeOverrides { - e := c.elem(i0) - enumDatabaseSyncedTableSpecTypeOverride(w, &e, &v.TypeOverrides[i0]) + c := n.Child("type_overrides") + for i := range v.TypeOverrides { + e := c.Elem(i) + enumDatabaseSyncedTableSpecTypeOverride(w, &e, &v.TypeOverrides[i]) } } } -func enumResourcesVectorSearchEndpointPermission(w *walker, n *node, v *resources.VectorSearchEndpointPermission) { +func enumResourcesVectorSearchEndpointPermission(w *walker, n *walk.Node, v *resources.VectorSearchEndpointPermission) { if v == nil { - v = &resources.VectorSearchEndpointPermission{} + return + } + if s := string(v.Level); s != "" { + c := n.Child("level") + w.enumValue(&c, s, enumValues75) } - w.enumField(n, "level", string(v.Level), enumValuesVectorsearchVectorSearchEndpointPermissionLevel) } -func enumVectorsearchDeltaSyncVectorIndexSpecRequest(w *walker, n *node, v *vectorsearch.DeltaSyncVectorIndexSpecRequest) { +func enumVectorsearchDeltaSyncVectorIndexSpecRequest(w *walker, n *walk.Node, v *vectorsearch.DeltaSyncVectorIndexSpecRequest) { if v == nil { - v = &vectorsearch.DeltaSyncVectorIndexSpecRequest{} + return + } + if s := string(v.PipelineType); s != "" || walk.Forced(v.ForceSendFields, "PipelineType") { + c := n.Child("pipeline_type") + w.enumValue(&c, s, enumValues76) } - w.enumField(n, "pipeline_type", string(v.PipelineType), enumValuesVectorsearchPipelineType) } -func enumSqlAlertV2OperandColumn(w *walker, n *node, v *sql.AlertV2OperandColumn) { +func enumSqlAlertV2OperandColumn(w *walker, n *walk.Node, v *sql.AlertV2OperandColumn) { if v == nil { - v = &sql.AlertV2OperandColumn{} + return + } + if s := string(v.Aggregation); s != "" || walk.Forced(v.ForceSendFields, "Aggregation") { + c := n.Child("aggregation") + w.enumValue(&c, s, enumValues77) } - w.enumField(n, "aggregation", string(v.Aggregation), enumValuesSqlAggregation) } -func enumSqlAlertV2Operand(w *walker, n *node, v *sql.AlertV2Operand) { +func enumSqlAlertV2Operand(w *walker, n *walk.Node, v *sql.AlertV2Operand) { if v == nil { - v = &sql.AlertV2Operand{} + return } - if c := n.child("column"); c.exists() { - enumSqlAlertV2OperandColumn2(w, &c, v.Column) + if x := v.Column; x != nil { + c := n.Child("column") + enumSqlAlertV2OperandColumn2(w, &c, x) } } -func enumAppsAppDeploymentStatus(w *walker, n *node, v *apps.AppDeploymentStatus) { +func enumAppsAppDeploymentStatus(w *walker, n *walk.Node, v *apps.AppDeploymentStatus) { if v == nil { - v = &apps.AppDeploymentStatus{} + return + } + if s := string(v.State); s != "" || walk.Forced(v.ForceSendFields, "State") { + c := n.Child("state") + w.enumValue(&c, s, enumValues78) } - w.enumField(n, "state", string(v.State), enumValuesAppsAppDeploymentState) } -func enumAppsAppResourceApp(w *walker, n *node, v *apps.AppResourceApp) { +func enumAppsAppResourceApp(w *walker, n *walk.Node, v *apps.AppResourceApp) { if v == nil { - v = &apps.AppResourceApp{} + return + } + if s := string(v.Permission); s != "" || walk.Forced(v.ForceSendFields, "Permission") { + c := n.Child("permission") + w.enumValue(&c, s, enumValues37) } - w.enumField(n, "permission", string(v.Permission), enumValuesAppsAppResourceAppAppPermission) } -func enumAppsAppResourceDatabase(w *walker, n *node, v *apps.AppResourceDatabase) { +func enumAppsAppResourceDatabase(w *walker, n *walk.Node, v *apps.AppResourceDatabase) { if v == nil { - v = &apps.AppResourceDatabase{} + return + } + if s := string(v.Permission); s != "" { + c := n.Child("permission") + w.enumValue(&c, s, enumValues79) } - w.enumField(n, "permission", string(v.Permission), enumValuesAppsAppResourceDatabaseDatabasePermission) } -func enumAppsAppResourceExperiment(w *walker, n *node, v *apps.AppResourceExperiment) { +func enumAppsAppResourceExperiment(w *walker, n *walk.Node, v *apps.AppResourceExperiment) { if v == nil { - v = &apps.AppResourceExperiment{} + return + } + if s := string(v.Permission); s != "" { + c := n.Child("permission") + w.enumValue(&c, s, enumValues44) } - w.enumField(n, "permission", string(v.Permission), enumValuesAppsAppResourceExperimentExperimentPermission) } -func enumAppsAppResourceGenieSpace(w *walker, n *node, v *apps.AppResourceGenieSpace) { +func enumAppsAppResourceGenieSpace(w *walker, n *walk.Node, v *apps.AppResourceGenieSpace) { if v == nil { - v = &apps.AppResourceGenieSpace{} + return + } + if s := string(v.Permission); s != "" { + c := n.Child("permission") + w.enumValue(&c, s, enumValues80) } - w.enumField(n, "permission", string(v.Permission), enumValuesAppsAppResourceGenieSpaceGenieSpacePermission) } -func enumAppsAppResourceJob(w *walker, n *node, v *apps.AppResourceJob) { +func enumAppsAppResourceJob(w *walker, n *walk.Node, v *apps.AppResourceJob) { if v == nil { - v = &apps.AppResourceJob{} + return + } + if s := string(v.Permission); s != "" { + c := n.Child("permission") + w.enumValue(&c, s, enumValues52) } - w.enumField(n, "permission", string(v.Permission), enumValuesAppsAppResourceJobJobPermission) } -func enumAppsAppResourcePostgres(w *walker, n *node, v *apps.AppResourcePostgres) { +func enumAppsAppResourcePostgres(w *walker, n *walk.Node, v *apps.AppResourcePostgres) { if v == nil { - v = &apps.AppResourcePostgres{} + return + } + if s := string(v.Permission); s != "" || walk.Forced(v.ForceSendFields, "Permission") { + c := n.Child("permission") + w.enumValue(&c, s, enumValues79) } - w.enumField(n, "permission", string(v.Permission), enumValuesAppsAppResourcePostgresPostgresPermission) } -func enumAppsAppResourceSecret(w *walker, n *node, v *apps.AppResourceSecret) { +func enumAppsAppResourceSecret(w *walker, n *walk.Node, v *apps.AppResourceSecret) { if v == nil { - v = &apps.AppResourceSecret{} + return + } + if s := string(v.Permission); s != "" { + c := n.Child("permission") + w.enumValue(&c, s, enumValues81) } - w.enumField(n, "permission", string(v.Permission), enumValuesAppsAppResourceSecretSecretPermission) } -func enumAppsAppResourceServingEndpoint(w *walker, n *node, v *apps.AppResourceServingEndpoint) { +func enumAppsAppResourceServingEndpoint(w *walker, n *walk.Node, v *apps.AppResourceServingEndpoint) { if v == nil { - v = &apps.AppResourceServingEndpoint{} + return + } + if s := string(v.Permission); s != "" { + c := n.Child("permission") + w.enumValue(&c, s, enumValues55) } - w.enumField(n, "permission", string(v.Permission), enumValuesAppsAppResourceServingEndpointServingEndpointPermission) } -func enumAppsAppResourceSqlWarehouse(w *walker, n *node, v *apps.AppResourceSqlWarehouse) { +func enumAppsAppResourceSqlWarehouse(w *walker, n *walk.Node, v *apps.AppResourceSqlWarehouse) { if v == nil { - v = &apps.AppResourceSqlWarehouse{} + return + } + if s := string(v.Permission); s != "" { + c := n.Child("permission") + w.enumValue(&c, s, enumValues82) } - w.enumField(n, "permission", string(v.Permission), enumValuesAppsAppResourceSqlWarehouseSqlWarehousePermission) } -func enumAppsAppResourceUcSecurable(w *walker, n *node, v *apps.AppResourceUcSecurable) { +func enumAppsAppResourceUcSecurable(w *walker, n *walk.Node, v *apps.AppResourceUcSecurable) { if v == nil { - v = &apps.AppResourceUcSecurable{} + return + } + if s := string(v.Permission); s != "" || walk.Forced(v.ForceSendFields, "Permission") { + c := n.Child("permission") + w.enumValue(&c, s, enumValues83) + } + if s := string(v.SecurableType); s != "" || walk.Forced(v.ForceSendFields, "SecurableType") { + c := n.Child("securable_type") + w.enumValue(&c, s, enumValues84) } - w.enumField(n, "permission", string(v.Permission), enumValuesAppsAppResourceUcSecurableUcSecurablePermission) - w.enumField(n, "securable_type", string(v.SecurableType), enumValuesAppsAppResourceUcSecurableUcSecurableType) } -func enumCatalogSseEncryptionDetails(w *walker, n *node, v *catalog.SseEncryptionDetails) { +func enumCatalogSseEncryptionDetails(w *walker, n *walk.Node, v *catalog.SseEncryptionDetails) { if v == nil { - v = &catalog.SseEncryptionDetails{} + return + } + if s := string(v.Algorithm); s != "" || walk.Forced(v.ForceSendFields, "Algorithm") { + c := n.Child("algorithm") + w.enumValue(&c, s, enumValues85) } - w.enumField(n, "algorithm", string(v.Algorithm), enumValuesCatalogSseEncryptionDetailsAlgorithm) } -func enumMlRequestSource(w *walker, n *node, v *ml.RequestSource) { +func enumMlRequestSource(w *walker, n *walk.Node, v *ml.RequestSource) { if v == nil { - v = &ml.RequestSource{} + return } - if c := n.child("flat_schema"); c.exists() { - enumMlFlatSchema(w, &c, v.FlatSchema) + if x := v.FlatSchema; x != nil { + c := n.Child("flat_schema") + enumMlFlatSchema(w, &c, x) } } -func enumMlSdpSource(w *walker, n *node, v *ml.SdpSource) { +func enumMlSdpSource(w *walker, n *walk.Node, v *ml.SdpSource) { if v == nil { - v = &ml.SdpSource{} + return + } + if s := string(v.PipelineState); s != "" || walk.Forced(v.ForceSendFields, "PipelineState") { + c := n.Child("pipeline_state") + w.enumValue(&c, s, enumValues86) } - w.enumField(n, "pipeline_state", string(v.PipelineState), enumValuesMlSdpSourceState) } -func enumComputeDiskType(w *walker, n *node, v *compute.DiskType) { +func enumComputeDiskType(w *walker, n *walk.Node, v *compute.DiskType) { if v == nil { - v = &compute.DiskType{} + return + } + if s := string(v.AzureDiskVolumeType); s != "" { + c := n.Child("azure_disk_volume_type") + w.enumValue(&c, s, enumValues87) + } + if s := string(v.EbsVolumeType); s != "" { + c := n.Child("ebs_volume_type") + w.enumValue(&c, s, enumValues39) } - w.enumField(n, "azure_disk_volume_type", string(v.AzureDiskVolumeType), enumValuesComputeDiskTypeAzureDiskVolumeType) - w.enumField(n, "ebs_volume_type", string(v.EbsVolumeType), enumValuesComputeDiskTypeEbsVolumeType) } -func enumJobsMaintenanceWindow(w *walker, n *node, v *jobs.MaintenanceWindow) { +func enumJobsMaintenanceWindow(w *walker, n *walk.Node, v *jobs.MaintenanceWindow) { if v == nil { - v = &jobs.MaintenanceWindow{} + return + } + if s := string(v.DayOfWeek); s != "" { + c := n.Child("day_of_week") + w.enumValue(&c, s, enumValues64) } - w.enumField(n, "day_of_week", string(v.DayOfWeek), enumValuesJobsDayOfWeek) } -func enumJobsJobSource(w *walker, n *node, v *jobs.JobSource) { +func enumJobsJobSource(w *walker, n *walk.Node, v *jobs.JobSource) { if v == nil { - v = &jobs.JobSource{} + return + } + if s := string(v.DirtyState); s != "" { + c := n.Child("dirty_state") + w.enumValue(&c, s, enumValues88) } - w.enumField(n, "dirty_state", string(v.DirtyState), enumValuesJobsJobSourceDirtyState) } -func enumJobsJobsHealthRule(w *walker, n *node, v *jobs.JobsHealthRule) { +func enumJobsJobsHealthRule(w *walker, n *walk.Node, v *jobs.JobsHealthRule) { if v == nil { - v = &jobs.JobsHealthRule{} + return + } + if s := string(v.Metric); s != "" { + c := n.Child("metric") + w.enumValue(&c, s, enumValues89) + } + if s := string(v.Op); s != "" { + c := n.Child("op") + w.enumValue(&c, s, enumValues90) } - w.enumField(n, "metric", string(v.Metric), enumValuesJobsJobsHealthMetric) - w.enumField(n, "op", string(v.Op), enumValuesJobsJobsHealthOperator) } -func enumComputeClusterSpec(w *walker, n *node, v *compute.ClusterSpec) { +func enumComputeClusterSpec(w *walker, n *walk.Node, v *compute.ClusterSpec) { if v == nil { - v = &compute.ClusterSpec{} + return + } + if x := v.AwsAttributes; x != nil { + c := n.Child("aws_attributes") + enumComputeAwsAttributes(w, &c, x) + } + if x := v.AzureAttributes; x != nil { + c := n.Child("azure_attributes") + enumComputeAzureAttributes(w, &c, x) + } + if s := string(v.DataSecurityMode); s != "" || walk.Forced(v.ForceSendFields, "DataSecurityMode") { + c := n.Child("data_security_mode") + w.enumValue(&c, s, enumValues7) + } + if s := string(v.DependencyMode); s != "" || walk.Forced(v.ForceSendFields, "DependencyMode") { + c := n.Child("dependency_mode") + w.enumValue(&c, s, enumValues8) } - if c := n.child("aws_attributes"); c.exists() { - enumComputeAwsAttributes(w, &c, v.AwsAttributes) + if x := v.GcpAttributes; x != nil { + c := n.Child("gcp_attributes") + enumComputeGcpAttributes(w, &c, x) } - if c := n.child("azure_attributes"); c.exists() { - enumComputeAzureAttributes(w, &c, v.AzureAttributes) + if s := string(v.Kind); s != "" || walk.Forced(v.ForceSendFields, "Kind") { + c := n.Child("kind") + w.enumValue(&c, s, enumValues9) } - w.enumField(n, "data_security_mode", string(v.DataSecurityMode), enumValuesComputeDataSecurityMode) - w.enumField(n, "dependency_mode", string(v.DependencyMode), enumValuesComputeDependencyMode) - if c := n.child("gcp_attributes"); c.exists() { - enumComputeGcpAttributes(w, &c, v.GcpAttributes) + if s := string(v.RuntimeEngine); s != "" || walk.Forced(v.ForceSendFields, "RuntimeEngine") { + c := n.Child("runtime_engine") + w.enumValue(&c, s, enumValues10) } - w.enumField(n, "kind", string(v.Kind), enumValuesComputeKind) - w.enumField(n, "runtime_engine", string(v.RuntimeEngine), enumValuesComputeRuntimeEngine) } -func enumJobsSqlConditionConfiguration(w *walker, n *node, v *jobs.SqlConditionConfiguration) { +func enumJobsSqlConditionConfiguration(w *walker, n *walk.Node, v *jobs.SqlConditionConfiguration) { if v == nil { - v = &jobs.SqlConditionConfiguration{} + return + } + if s := string(v.TriggerMode); s != "" { + c := n.Child("trigger_mode") + w.enumValue(&c, s, enumValues91) } - w.enumField(n, "trigger_mode", string(v.TriggerMode), enumValuesJobsSqlConditionTriggerMode) } -func enumJobsAiRuntimeTask(w *walker, n *node, v *jobs.AiRuntimeTask) { +func enumJobsAiRuntimeTask(w *walker, n *walk.Node, v *jobs.AiRuntimeTask) { if v == nil { - v = &jobs.AiRuntimeTask{} + return } if len(v.Deployments) > 0 { - c := n.child("deployments") - for i0 := range v.Deployments { - e := c.elem(i0) - enumJobsDeploymentSpec(w, &e, &v.Deployments[i0]) + c := n.Child("deployments") + for i := range v.Deployments { + e := c.Elem(i) + enumJobsDeploymentSpec(w, &e, &v.Deployments[i]) } } - w.enumField(n, "priority_class", string(v.PriorityClass), enumValuesJobsAiRuntimeTaskPriorityClass) + if s := string(v.PriorityClass); s != "" || walk.Forced(v.ForceSendFields, "PriorityClass") { + c := n.Child("priority_class") + w.enumValue(&c, s, enumValues92) + } } -func enumJobsCompute(w *walker, n *node, v *jobs.Compute) { +func enumJobsCompute(w *walker, n *walk.Node, v *jobs.Compute) { if v == nil { - v = &jobs.Compute{} + return + } + if s := string(v.HardwareAccelerator); s != "" { + c := n.Child("hardware_accelerator") + w.enumValue(&c, s, enumValues93) } - w.enumField(n, "hardware_accelerator", string(v.HardwareAccelerator), enumValuesComputeHardwareAcceleratorType) } -func enumJobsConditionTask(w *walker, n *node, v *jobs.ConditionTask) { +func enumJobsConditionTask(w *walker, n *walk.Node, v *jobs.ConditionTask) { if v == nil { - v = &jobs.ConditionTask{} + return + } + if s := string(v.Op); s != "" { + c := n.Child("op") + w.enumValue(&c, s, enumValues94) } - w.enumField(n, "op", string(v.Op), enumValuesJobsConditionTaskOp) } -func enumJobsDashboardTask(w *walker, n *node, v *jobs.DashboardTask) { +func enumJobsDashboardTask(w *walker, n *walk.Node, v *jobs.DashboardTask) { if v == nil { - v = &jobs.DashboardTask{} + return } - if c := n.child("subscription"); c.exists() { - enumJobsSubscription(w, &c, v.Subscription) + if x := v.Subscription; x != nil { + c := n.Child("subscription") + enumJobsSubscription(w, &c, x) } } -func enumJobsDbtTask(w *walker, n *node, v *jobs.DbtTask) { +func enumJobsDbtTask(w *walker, n *walk.Node, v *jobs.DbtTask) { if v == nil { - v = &jobs.DbtTask{} + return + } + if s := string(v.Source); s != "" || walk.Forced(v.ForceSendFields, "Source") { + c := n.Child("source") + w.enumValue(&c, s, enumValues95) } - w.enumField(n, "source", string(v.Source), enumValuesJobsSource) } -func enumJobsForEachTask(w *walker, n *node, v *jobs.ForEachTask) { +func enumJobsForEachTask(w *walker, n *walk.Node, v *jobs.ForEachTask) { if v == nil { - v = &jobs.ForEachTask{} + return } - if c := n.child("task"); c.exists() { - enumJobsTask2(w, &c, &v.Task) + if x := &v.Task; !walk.IsZeroStruct(x) || walk.Forced(v.ForceSendFields, "Task") || n.HasRefs("task") { + c := n.Child("task") + enumJobsTask2(w, &c, x) } } -func enumJobsGenAiComputeTask(w *walker, n *node, v *jobs.GenAiComputeTask) { +func enumJobsGenAiComputeTask(w *walker, n *walk.Node, v *jobs.GenAiComputeTask) { if v == nil { - v = &jobs.GenAiComputeTask{} + return + } + if s := string(v.Source); s != "" || walk.Forced(v.ForceSendFields, "Source") { + c := n.Child("source") + w.enumValue(&c, s, enumValues95) } - w.enumField(n, "source", string(v.Source), enumValuesJobsSource) } -func enumJobsNotebookTask(w *walker, n *node, v *jobs.NotebookTask) { +func enumJobsNotebookTask(w *walker, n *walk.Node, v *jobs.NotebookTask) { if v == nil { - v = &jobs.NotebookTask{} + return + } + if s := string(v.Source); s != "" || walk.Forced(v.ForceSendFields, "Source") { + c := n.Child("source") + w.enumValue(&c, s, enumValues95) } - w.enumField(n, "source", string(v.Source), enumValuesJobsSource) } -func enumJobsPowerBiTask(w *walker, n *node, v *jobs.PowerBiTask) { +func enumJobsPowerBiTask(w *walker, n *walk.Node, v *jobs.PowerBiTask) { if v == nil { - v = &jobs.PowerBiTask{} + return } - if c := n.child("power_bi_model"); c.exists() { - enumJobsPowerBiModel(w, &c, v.PowerBiModel) + if x := v.PowerBiModel; x != nil { + c := n.Child("power_bi_model") + enumJobsPowerBiModel(w, &c, x) } if len(v.Tables) > 0 { - c := n.child("tables") - for i0 := range v.Tables { - e := c.elem(i0) - enumJobsPowerBiTable(w, &e, &v.Tables[i0]) + c := n.Child("tables") + for i := range v.Tables { + e := c.Elem(i) + enumJobsPowerBiTable(w, &e, &v.Tables[i]) } } } -func enumJobsSparkPythonTask(w *walker, n *node, v *jobs.SparkPythonTask) { +func enumJobsSparkPythonTask(w *walker, n *walk.Node, v *jobs.SparkPythonTask) { if v == nil { - v = &jobs.SparkPythonTask{} + return + } + if s := string(v.Source); s != "" { + c := n.Child("source") + w.enumValue(&c, s, enumValues95) } - w.enumField(n, "source", string(v.Source), enumValuesJobsSource) } -func enumJobsSqlTask(w *walker, n *node, v *jobs.SqlTask) { +func enumJobsSqlTask(w *walker, n *walk.Node, v *jobs.SqlTask) { if v == nil { - v = &jobs.SqlTask{} + return } - if c := n.child("file"); c.exists() { - enumJobsSqlTaskFile(w, &c, v.File) + if x := v.File; x != nil { + c := n.Child("file") + enumJobsSqlTaskFile(w, &c, x) } } -func enumJobsModelTriggerConfiguration(w *walker, n *node, v *jobs.ModelTriggerConfiguration) { +func enumJobsModelTriggerConfiguration(w *walker, n *walk.Node, v *jobs.ModelTriggerConfiguration) { if v == nil { - v = &jobs.ModelTriggerConfiguration{} + return + } + if s := string(v.Condition); s != "" || walk.Forced(v.ForceSendFields, "Condition") { + c := n.Child("condition") + w.enumValue(&c, s, enumValues96) } - w.enumField(n, "condition", string(v.Condition), enumValuesJobsModelTriggerConfigurationCondition) } -func enumJobsPeriodicTriggerConfiguration(w *walker, n *node, v *jobs.PeriodicTriggerConfiguration) { +func enumJobsPeriodicTriggerConfiguration(w *walker, n *walk.Node, v *jobs.PeriodicTriggerConfiguration) { if v == nil { - v = &jobs.PeriodicTriggerConfiguration{} + return + } + if s := string(v.Unit); s != "" { + c := n.Child("unit") + w.enumValue(&c, s, enumValues97) } - w.enumField(n, "unit", string(v.Unit), enumValuesJobsPeriodicTriggerConfigurationTimeUnit) } -func enumJobsTableUpdateTriggerConfiguration(w *walker, n *node, v *jobs.TableUpdateTriggerConfiguration) { +func enumJobsTableUpdateTriggerConfiguration(w *walker, n *walk.Node, v *jobs.TableUpdateTriggerConfiguration) { if v == nil { - v = &jobs.TableUpdateTriggerConfiguration{} + return + } + if s := string(v.Condition); s != "" || walk.Forced(v.ForceSendFields, "Condition") { + c := n.Child("condition") + w.enumValue(&c, s, enumValues98) } - w.enumField(n, "condition", string(v.Condition), enumValuesJobsCondition) } -func enumJobsContinuousTriggerConfiguration(w *walker, n *node, v *jobs.ContinuousTriggerConfiguration) { +func enumJobsContinuousTriggerConfiguration(w *walker, n *walk.Node, v *jobs.ContinuousTriggerConfiguration) { if v == nil { - v = &jobs.ContinuousTriggerConfiguration{} + return } - if c := n.child("maintenance_window"); c.exists() { - enumJobsMaintenanceWindow(w, &c, v.MaintenanceWindow) + if x := v.MaintenanceWindow; x != nil { + c := n.Child("maintenance_window") + enumJobsMaintenanceWindow(w, &c, x) + } + if s := string(v.TaskRetryMode); s != "" { + c := n.Child("task_retry_mode") + w.enumValue(&c, s, enumValues49) } - w.enumField(n, "task_retry_mode", string(v.TaskRetryMode), enumValuesJobsTaskRetryMode) } -func enumCatalogRateLimit(w *walker, n *node, v *catalog.RateLimit) { +func enumCatalogRateLimit(w *walker, n *walk.Node, v *catalog.RateLimit) { if v == nil { - v = &catalog.RateLimit{} + return + } + if s := string(v.Key); s != "" || walk.Forced(v.ForceSendFields, "Key") { + c := n.Child("key") + w.enumValue(&c, s, enumValues99) + } + if s := string(v.RenewalPeriod); s != "" || walk.Forced(v.ForceSendFields, "RenewalPeriod") { + c := n.Child("renewal_period") + w.enumValue(&c, s, enumValues100) } - w.enumField(n, "key", string(v.Key), enumValuesCatalogRateLimitRateLimitKey) - w.enumField(n, "renewal_period", string(v.RenewalPeriod), enumValuesCatalogRateLimitRateLimitRenewalPeriod) } -func enumCatalogModelServiceConfigRoutingConfig(w *walker, n *node, v *catalog.ModelServiceConfigRoutingConfig) { +func enumCatalogModelServiceConfigRoutingConfig(w *walker, n *walk.Node, v *catalog.ModelServiceConfigRoutingConfig) { if v == nil { - v = &catalog.ModelServiceConfigRoutingConfig{} + return } if len(v.Destinations) > 0 { - c := n.child("destinations") - for i0 := range v.Destinations { - e := c.elem(i0) - enumCatalogModelServiceConfigDestinationConfig(w, &e, &v.Destinations[i0]) + c := n.Child("destinations") + for i := range v.Destinations { + e := c.Elem(i) + enumCatalogModelServiceConfigDestinationConfig(w, &e, &v.Destinations[i]) } } - if c := n.child("fallback"); c.exists() { - enumCatalogModelServiceConfigFallbackConfig(w, &c, v.Fallback) + if x := v.Fallback; x != nil { + c := n.Child("fallback") + enumCatalogModelServiceConfigFallbackConfig(w, &c, x) } } -func enumServingAiGatewayGuardrails(w *walker, n *node, v *serving.AiGatewayGuardrails) { +func enumServingAiGatewayGuardrails(w *walker, n *walk.Node, v *serving.AiGatewayGuardrails) { if v == nil { - v = &serving.AiGatewayGuardrails{} + return } - if c := n.child("input"); c.exists() { - enumServingAiGatewayGuardrailParameters(w, &c, v.Input) + if x := v.Input; x != nil { + c := n.Child("input") + enumServingAiGatewayGuardrailParameters(w, &c, x) } - if c := n.child("output"); c.exists() { - enumServingAiGatewayGuardrailParameters(w, &c, v.Output) + if x := v.Output; x != nil { + c := n.Child("output") + enumServingAiGatewayGuardrailParameters(w, &c, x) } } -func enumServingAiGatewayRateLimit(w *walker, n *node, v *serving.AiGatewayRateLimit) { +func enumServingAiGatewayRateLimit(w *walker, n *walk.Node, v *serving.AiGatewayRateLimit) { if v == nil { - v = &serving.AiGatewayRateLimit{} + return + } + if s := string(v.Key); s != "" || walk.Forced(v.ForceSendFields, "Key") { + c := n.Child("key") + w.enumValue(&c, s, enumValues101) + } + if s := string(v.RenewalPeriod); s != "" || walk.Forced(v.ForceSendFields, "RenewalPeriod") { + c := n.Child("renewal_period") + w.enumValue(&c, s, enumValues57) } - w.enumField(n, "key", string(v.Key), enumValuesServingAiGatewayRateLimitKey) - w.enumField(n, "renewal_period", string(v.RenewalPeriod), enumValuesServingAiGatewayRateLimitRenewalPeriod) } -func enumServingServedEntityInput(w *walker, n *node, v *serving.ServedEntityInput) { +func enumServingServedEntityInput(w *walker, n *walk.Node, v *serving.ServedEntityInput) { if v == nil { - v = &serving.ServedEntityInput{} + return + } + if x := v.ExternalModel; x != nil { + c := n.Child("external_model") + enumServingExternalModel(w, &c, x) } - if c := n.child("external_model"); c.exists() { - enumServingExternalModel(w, &c, v.ExternalModel) + if s := string(v.WorkloadType); s != "" || walk.Forced(v.ForceSendFields, "WorkloadType") { + c := n.Child("workload_type") + w.enumValue(&c, s, enumValues102) } - w.enumField(n, "workload_type", string(v.WorkloadType), enumValuesServingServingModelWorkloadType) } -func enumServingServedModelInput(w *walker, n *node, v *serving.ServedModelInput) { +func enumServingServedModelInput(w *walker, n *walk.Node, v *serving.ServedModelInput) { if v == nil { - v = &serving.ServedModelInput{} + return + } + if s := string(v.WorkloadType); s != "" || walk.Forced(v.ForceSendFields, "WorkloadType") { + c := n.Child("workload_type") + w.enumValue(&c, s, enumValues102) } - w.enumField(n, "workload_type", string(v.WorkloadType), enumValuesServingServedModelInputWorkloadType) } -func enumPipelinesPipelineClusterAutoscale(w *walker, n *node, v *pipelines.PipelineClusterAutoscale) { +func enumPipelinesPipelineClusterAutoscale(w *walker, n *walk.Node, v *pipelines.PipelineClusterAutoscale) { if v == nil { - v = &pipelines.PipelineClusterAutoscale{} + return + } + if s := string(v.Mode); s != "" { + c := n.Child("mode") + w.enumValue(&c, s, enumValues103) } - w.enumField(n, "mode", string(v.Mode), enumValuesPipelinesPipelineClusterAutoscaleMode) } -func enumPipelinesOperationTimeWindow(w *walker, n *node, v *pipelines.OperationTimeWindow) { +func enumPipelinesOperationTimeWindow(w *walker, n *walk.Node, v *pipelines.OperationTimeWindow) { if v == nil { - v = &pipelines.OperationTimeWindow{} + return } if len(v.DaysOfWeek) > 0 { - c := n.child("days_of_week") - for i0 := range v.DaysOfWeek { - e := c.elem(i0) - w.enumElem(&e, string(v.DaysOfWeek[i0]), enumValuesPipelinesDayOfWeek) + c := n.Child("days_of_week") + for i := range v.DaysOfWeek { + e := c.Elem(i) + w.enumValue(&e, string(v.DaysOfWeek[i]), enumValues64) } } } -func enumPipelinesIngestionConfig(w *walker, n *node, v *pipelines.IngestionConfig) { +func enumPipelinesIngestionConfig(w *walker, n *walk.Node, v *pipelines.IngestionConfig) { if v == nil { - v = &pipelines.IngestionConfig{} + return } - if c := n.child("report"); c.exists() { - enumPipelinesReportSpec(w, &c, v.Report) + if x := v.Report; x != nil { + c := n.Child("report") + enumPipelinesReportSpec(w, &c, x) } - if c := n.child("schema"); c.exists() { - enumPipelinesSchemaSpec(w, &c, v.Schema) + if x := v.Schema; x != nil { + c := n.Child("schema") + enumPipelinesSchemaSpec(w, &c, x) } - if c := n.child("table"); c.exists() { - enumPipelinesTableSpec(w, &c, v.Table) + if x := v.Table; x != nil { + c := n.Child("table") + enumPipelinesTableSpec(w, &c, x) } } -func enumPipelinesTableSpecificConfig(w *walker, n *node, v *pipelines.TableSpecificConfig) { +func enumPipelinesTableSpecificConfig(w *walker, n *walk.Node, v *pipelines.TableSpecificConfig) { if v == nil { - v = &pipelines.TableSpecificConfig{} + return + } + if s := string(v.ScdType); s != "" || walk.Forced(v.ForceSendFields, "ScdType") { + c := n.Child("scd_type") + w.enumValue(&c, s, enumValues104) } - w.enumField(n, "scd_type", string(v.ScdType), enumValuesPipelinesTableSpecificConfigScdType) } -func enumPostgresWeeklySchedule(w *walker, n *node, v *postgres.WeeklySchedule) { +func enumPostgresWeeklySchedule(w *walker, n *walk.Node, v *postgres.WeeklySchedule) { if v == nil { - v = &postgres.WeeklySchedule{} + return + } + if s := string(v.DayOfWeek); s != "" || walk.Forced(v.ForceSendFields, "DayOfWeek") { + c := n.Child("day_of_week") + w.enumValue(&c, s, enumValues64) } - w.enumField(n, "day_of_week", string(v.DayOfWeek), enumValuesPostgresDayOfWeek) } -func enumDatabaseSyncedTableContinuousUpdateStatus(w *walker, n *node, v *database.SyncedTableContinuousUpdateStatus) { +func enumDatabaseSyncedTableContinuousUpdateStatus(w *walker, n *walk.Node, v *database.SyncedTableContinuousUpdateStatus) { if v == nil { - v = &database.SyncedTableContinuousUpdateStatus{} + return } - if c := n.child("initial_pipeline_sync_progress"); c.exists() { - enumDatabaseSyncedTablePipelineProgress(w, &c, v.InitialPipelineSyncProgress) + if x := v.InitialPipelineSyncProgress; x != nil { + c := n.Child("initial_pipeline_sync_progress") + enumDatabaseSyncedTablePipelineProgress(w, &c, x) } } -func enumDatabaseSyncedTableProvisioningStatus(w *walker, n *node, v *database.SyncedTableProvisioningStatus) { +func enumDatabaseSyncedTableProvisioningStatus(w *walker, n *walk.Node, v *database.SyncedTableProvisioningStatus) { if v == nil { - v = &database.SyncedTableProvisioningStatus{} + return } - if c := n.child("initial_pipeline_sync_progress"); c.exists() { - enumDatabaseSyncedTablePipelineProgress(w, &c, v.InitialPipelineSyncProgress) + if x := v.InitialPipelineSyncProgress; x != nil { + c := n.Child("initial_pipeline_sync_progress") + enumDatabaseSyncedTablePipelineProgress(w, &c, x) } } -func enumDatabaseSyncedTableTriggeredUpdateStatus(w *walker, n *node, v *database.SyncedTableTriggeredUpdateStatus) { +func enumDatabaseSyncedTableTriggeredUpdateStatus(w *walker, n *walk.Node, v *database.SyncedTableTriggeredUpdateStatus) { if v == nil { - v = &database.SyncedTableTriggeredUpdateStatus{} + return } - if c := n.child("triggered_update_progress"); c.exists() { - enumDatabaseSyncedTablePipelineProgress(w, &c, v.TriggeredUpdateProgress) + if x := v.TriggeredUpdateProgress; x != nil { + c := n.Child("triggered_update_progress") + enumDatabaseSyncedTablePipelineProgress(w, &c, x) } } -func enumDatabaseSyncedTableSpecTypeOverride(w *walker, n *node, v *database.SyncedTableSpecTypeOverride) { +func enumDatabaseSyncedTableSpecTypeOverride(w *walker, n *walk.Node, v *database.SyncedTableSpecTypeOverride) { if v == nil { - v = &database.SyncedTableSpecTypeOverride{} + return + } + if s := string(v.PgType); s != "" || walk.Forced(v.ForceSendFields, "PgType") { + c := n.Child("pg_type") + w.enumValue(&c, s, enumValues67) } - w.enumField(n, "pg_type", string(v.PgType), enumValuesDatabaseSyncedTableSpecPgSpecificType) } -func enumSqlAlertV2OperandColumn2(w *walker, n *node, v *sql.AlertV2OperandColumn) { +func enumSqlAlertV2OperandColumn2(w *walker, n *walk.Node, v *sql.AlertV2OperandColumn) { if v == nil { - v = &sql.AlertV2OperandColumn{} + return + } + if s := string(v.Aggregation); s != "" || walk.Forced(v.ForceSendFields, "Aggregation") { + c := n.Child("aggregation") + w.enumValue(&c, s, enumValues77) } - w.enumField(n, "aggregation", string(v.Aggregation), enumValuesSqlAggregation) } -func enumMlFlatSchema(w *walker, n *node, v *ml.FlatSchema) { +func enumMlFlatSchema(w *walker, n *walk.Node, v *ml.FlatSchema) { if v == nil { - v = &ml.FlatSchema{} + return } if len(v.Fields) > 0 { - c := n.child("fields") - for i0 := range v.Fields { - e := c.elem(i0) - enumMlFieldDefinition(w, &e, &v.Fields[i0]) + c := n.Child("fields") + for i := range v.Fields { + e := c.Elem(i) + enumMlFieldDefinition(w, &e, &v.Fields[i]) } } } -func enumJobsDeploymentSpec(w *walker, n *node, v *jobs.DeploymentSpec) { +func enumJobsDeploymentSpec(w *walker, n *walk.Node, v *jobs.DeploymentSpec) { if v == nil { - v = &jobs.DeploymentSpec{} + return } - if c := n.child("compute"); c.exists() { - enumJobsComputeSpec(w, &c, &v.Compute) + if x := &v.Compute; !walk.IsZeroStruct(x) || walk.Forced(v.ForceSendFields, "Compute") || n.HasRefs("compute") { + c := n.Child("compute") + enumJobsComputeSpec(w, &c, x) } } -func enumJobsSubscription(w *walker, n *node, v *jobs.Subscription) { +func enumJobsSubscription(w *walker, n *walk.Node, v *jobs.Subscription) { if v == nil { - v = &jobs.Subscription{} + return } - if c := n.child("attachment"); c.exists() { - enumJobsSubscriptionAttachment(w, &c, v.Attachment) + if x := v.Attachment; x != nil { + c := n.Child("attachment") + enumJobsSubscriptionAttachment(w, &c, x) } } -func enumJobsTask2(w *walker, n *node, v *jobs.Task) { +func enumJobsTask2(w *walker, n *walk.Node, v *jobs.Task) { if v == nil { - v = &jobs.Task{} + return + } + if x := v.AiRuntimeTask; x != nil { + c := n.Child("ai_runtime_task") + enumJobsAiRuntimeTask(w, &c, x) } - if c := n.child("ai_runtime_task"); c.exists() { - enumJobsAiRuntimeTask(w, &c, v.AiRuntimeTask) + if x := v.Compute; x != nil { + c := n.Child("compute") + enumJobsCompute(w, &c, x) } - if c := n.child("compute"); c.exists() { - enumJobsCompute(w, &c, v.Compute) + if x := v.ConditionTask; x != nil { + c := n.Child("condition_task") + enumJobsConditionTask(w, &c, x) } - if c := n.child("condition_task"); c.exists() { - enumJobsConditionTask(w, &c, v.ConditionTask) + if x := v.DashboardTask; x != nil { + c := n.Child("dashboard_task") + enumJobsDashboardTask(w, &c, x) } - if c := n.child("dashboard_task"); c.exists() { - enumJobsDashboardTask(w, &c, v.DashboardTask) + if x := v.DbtTask; x != nil { + c := n.Child("dbt_task") + enumJobsDbtTask(w, &c, x) } - if c := n.child("dbt_task"); c.exists() { - enumJobsDbtTask(w, &c, v.DbtTask) + if x := v.GenAiComputeTask; x != nil { + c := n.Child("gen_ai_compute_task") + enumJobsGenAiComputeTask(w, &c, x) } - if c := n.child("gen_ai_compute_task"); c.exists() { - enumJobsGenAiComputeTask(w, &c, v.GenAiComputeTask) + if x := v.Health; x != nil { + c := n.Child("health") + enumJobsJobsHealthRules(w, &c, x) } - if c := n.child("health"); c.exists() { - enumJobsJobsHealthRules(w, &c, v.Health) + if x := v.NewCluster; x != nil { + c := n.Child("new_cluster") + enumComputeClusterSpec(w, &c, x) } - if c := n.child("new_cluster"); c.exists() { - enumComputeClusterSpec(w, &c, v.NewCluster) + if x := v.NotebookTask; x != nil { + c := n.Child("notebook_task") + enumJobsNotebookTask(w, &c, x) } - if c := n.child("notebook_task"); c.exists() { - enumJobsNotebookTask(w, &c, v.NotebookTask) + if x := v.PowerBiTask; x != nil { + c := n.Child("power_bi_task") + enumJobsPowerBiTask(w, &c, x) } - if c := n.child("power_bi_task"); c.exists() { - enumJobsPowerBiTask(w, &c, v.PowerBiTask) + if s := string(v.RunIf); s != "" || walk.Forced(v.ForceSendFields, "RunIf") { + c := n.Child("run_if") + w.enumValue(&c, s, enumValues53) } - w.enumField(n, "run_if", string(v.RunIf), enumValuesJobsRunIf) - if c := n.child("spark_python_task"); c.exists() { - enumJobsSparkPythonTask(w, &c, v.SparkPythonTask) + if x := v.SparkPythonTask; x != nil { + c := n.Child("spark_python_task") + enumJobsSparkPythonTask(w, &c, x) } - if c := n.child("sql_task"); c.exists() { - enumJobsSqlTask(w, &c, v.SqlTask) + if x := v.SqlTask; x != nil { + c := n.Child("sql_task") + enumJobsSqlTask(w, &c, x) } } -func enumJobsPowerBiModel(w *walker, n *node, v *jobs.PowerBiModel) { +func enumJobsPowerBiModel(w *walker, n *walk.Node, v *jobs.PowerBiModel) { if v == nil { - v = &jobs.PowerBiModel{} + return + } + if s := string(v.AuthenticationMethod); s != "" || walk.Forced(v.ForceSendFields, "AuthenticationMethod") { + c := n.Child("authentication_method") + w.enumValue(&c, s, enumValues105) + } + if s := string(v.StorageMode); s != "" || walk.Forced(v.ForceSendFields, "StorageMode") { + c := n.Child("storage_mode") + w.enumValue(&c, s, enumValues106) } - w.enumField(n, "authentication_method", string(v.AuthenticationMethod), enumValuesJobsAuthenticationMethod) - w.enumField(n, "storage_mode", string(v.StorageMode), enumValuesJobsStorageMode) } -func enumJobsPowerBiTable(w *walker, n *node, v *jobs.PowerBiTable) { +func enumJobsPowerBiTable(w *walker, n *walk.Node, v *jobs.PowerBiTable) { if v == nil { - v = &jobs.PowerBiTable{} + return + } + if s := string(v.StorageMode); s != "" || walk.Forced(v.ForceSendFields, "StorageMode") { + c := n.Child("storage_mode") + w.enumValue(&c, s, enumValues106) } - w.enumField(n, "storage_mode", string(v.StorageMode), enumValuesJobsStorageMode) } -func enumJobsSqlTaskFile(w *walker, n *node, v *jobs.SqlTaskFile) { +func enumJobsSqlTaskFile(w *walker, n *walk.Node, v *jobs.SqlTaskFile) { if v == nil { - v = &jobs.SqlTaskFile{} + return + } + if s := string(v.Source); s != "" { + c := n.Child("source") + w.enumValue(&c, s, enumValues95) } - w.enumField(n, "source", string(v.Source), enumValuesJobsSource) } -func enumCatalogModelServiceConfigDestinationConfig(w *walker, n *node, v *catalog.ModelServiceConfigDestinationConfig) { +func enumCatalogModelServiceConfigDestinationConfig(w *walker, n *walk.Node, v *catalog.ModelServiceConfigDestinationConfig) { if v == nil { - v = &catalog.ModelServiceConfigDestinationConfig{} + return + } + if s := string(v.DestinationType); s != "" || walk.Forced(v.ForceSendFields, "DestinationType") { + c := n.Child("destination_type") + w.enumValue(&c, s, enumValues107) } - w.enumField(n, "destination_type", string(v.DestinationType), enumValuesCatalogModelServiceConfigDestinationConfigDestinationType) } -func enumCatalogModelServiceConfigFallbackConfig(w *walker, n *node, v *catalog.ModelServiceConfigFallbackConfig) { +func enumCatalogModelServiceConfigFallbackConfig(w *walker, n *walk.Node, v *catalog.ModelServiceConfigFallbackConfig) { if v == nil { - v = &catalog.ModelServiceConfigFallbackConfig{} + return } if len(v.Destinations) > 0 { - c := n.child("destinations") - for i0 := range v.Destinations { - e := c.elem(i0) - enumCatalogModelServiceConfigDestinationConfig(w, &e, &v.Destinations[i0]) + c := n.Child("destinations") + for i := range v.Destinations { + e := c.Elem(i) + enumCatalogModelServiceConfigDestinationConfig(w, &e, &v.Destinations[i]) } } } -func enumServingAiGatewayGuardrailParameters(w *walker, n *node, v *serving.AiGatewayGuardrailParameters) { +func enumServingAiGatewayGuardrailParameters(w *walker, n *walk.Node, v *serving.AiGatewayGuardrailParameters) { if v == nil { - v = &serving.AiGatewayGuardrailParameters{} + return } - if c := n.child("pii"); c.exists() { - enumServingAiGatewayGuardrailPiiBehavior(w, &c, v.Pii) + if x := v.Pii; x != nil { + c := n.Child("pii") + enumServingAiGatewayGuardrailPiiBehavior(w, &c, x) } } -func enumServingExternalModel(w *walker, n *node, v *serving.ExternalModel) { +func enumServingExternalModel(w *walker, n *walk.Node, v *serving.ExternalModel) { if v == nil { - v = &serving.ExternalModel{} + return + } + if x := v.AmazonBedrockConfig; x != nil { + c := n.Child("amazon_bedrock_config") + enumServingAmazonBedrockConfig(w, &c, x) } - if c := n.child("amazon_bedrock_config"); c.exists() { - enumServingAmazonBedrockConfig(w, &c, v.AmazonBedrockConfig) + if s := string(v.Provider); s != "" { + c := n.Child("provider") + w.enumValue(&c, s, enumValues108) } - w.enumField(n, "provider", string(v.Provider), enumValuesServingExternalModelProvider) } -func enumPipelinesReportSpec(w *walker, n *node, v *pipelines.ReportSpec) { +func enumPipelinesReportSpec(w *walker, n *walk.Node, v *pipelines.ReportSpec) { if v == nil { - v = &pipelines.ReportSpec{} + return } - if c := n.child("table_configuration"); c.exists() { - enumPipelinesTableSpecificConfig(w, &c, v.TableConfiguration) + if x := v.TableConfiguration; x != nil { + c := n.Child("table_configuration") + enumPipelinesTableSpecificConfig(w, &c, x) } } -func enumPipelinesSchemaSpec(w *walker, n *node, v *pipelines.SchemaSpec) { +func enumPipelinesSchemaSpec(w *walker, n *walk.Node, v *pipelines.SchemaSpec) { if v == nil { - v = &pipelines.SchemaSpec{} + return } - if c := n.child("connector_options"); c.exists() { - enumPipelinesConnectorOptions(w, &c, v.ConnectorOptions) + if x := v.ConnectorOptions; x != nil { + c := n.Child("connector_options") + enumPipelinesConnectorOptions(w, &c, x) } - if c := n.child("fanout_options"); c.exists() { - enumPipelinesIngestionPipelineDefinitionFanoutOptions(w, &c, v.FanoutOptions) + if x := v.FanoutOptions; x != nil { + c := n.Child("fanout_options") + enumPipelinesIngestionPipelineDefinitionFanoutOptions(w, &c, x) } - if c := n.child("table_configuration"); c.exists() { - enumPipelinesTableSpecificConfig(w, &c, v.TableConfiguration) + if x := v.TableConfiguration; x != nil { + c := n.Child("table_configuration") + enumPipelinesTableSpecificConfig(w, &c, x) } } -func enumPipelinesTableSpec(w *walker, n *node, v *pipelines.TableSpec) { +func enumPipelinesTableSpec(w *walker, n *walk.Node, v *pipelines.TableSpec) { if v == nil { - v = &pipelines.TableSpec{} + return } - if c := n.child("connector_options"); c.exists() { - enumPipelinesConnectorOptions(w, &c, v.ConnectorOptions) + if x := v.ConnectorOptions; x != nil { + c := n.Child("connector_options") + enumPipelinesConnectorOptions(w, &c, x) } - if c := n.child("table_configuration"); c.exists() { - enumPipelinesTableSpecificConfig(w, &c, v.TableConfiguration) + if x := v.TableConfiguration; x != nil { + c := n.Child("table_configuration") + enumPipelinesTableSpecificConfig(w, &c, x) } } -func enumDatabaseSyncedTablePipelineProgress(w *walker, n *node, v *database.SyncedTablePipelineProgress) { +func enumDatabaseSyncedTablePipelineProgress(w *walker, n *walk.Node, v *database.SyncedTablePipelineProgress) { if v == nil { - v = &database.SyncedTablePipelineProgress{} + return + } + if s := string(v.ProvisioningPhase); s != "" || walk.Forced(v.ForceSendFields, "ProvisioningPhase") { + c := n.Child("provisioning_phase") + w.enumValue(&c, s, enumValues109) } - w.enumField(n, "provisioning_phase", string(v.ProvisioningPhase), enumValuesDatabaseProvisioningPhase) } -func enumMlFieldDefinition(w *walker, n *node, v *ml.FieldDefinition) { +func enumMlFieldDefinition(w *walker, n *walk.Node, v *ml.FieldDefinition) { if v == nil { - v = &ml.FieldDefinition{} + return + } + if s := string(v.DataType); s != "" { + c := n.Child("data_type") + w.enumValue(&c, s, enumValues110) } - w.enumField(n, "data_type", string(v.DataType), enumValuesMlScalarDataType) } -func enumJobsComputeSpec(w *walker, n *node, v *jobs.ComputeSpec) { +func enumJobsComputeSpec(w *walker, n *walk.Node, v *jobs.ComputeSpec) { if v == nil { - v = &jobs.ComputeSpec{} + return + } + if s := string(v.AcceleratorType); s != "" || walk.Forced(v.ForceSendFields, "AcceleratorType") { + c := n.Child("accelerator_type") + w.enumValue(&c, s, enumValues93) } - w.enumField(n, "accelerator_type", string(v.AcceleratorType), enumValuesJobsComputeSpecAcceleratorType) } -func enumJobsSubscriptionAttachment(w *walker, n *node, v *jobs.SubscriptionAttachment) { +func enumJobsSubscriptionAttachment(w *walker, n *walk.Node, v *jobs.SubscriptionAttachment) { if v == nil { - v = &jobs.SubscriptionAttachment{} + return } - if c := n.child("data_attachment"); c.exists() { - enumJobsSubscriptionAttachmentDataAttachment(w, &c, v.DataAttachment) + if x := v.DataAttachment; x != nil { + c := n.Child("data_attachment") + enumJobsSubscriptionAttachmentDataAttachment(w, &c, x) } - if c := n.child("page_attachment"); c.exists() { - enumJobsSubscriptionAttachmentPageAttachment(w, &c, v.PageAttachment) + if x := v.PageAttachment; x != nil { + c := n.Child("page_attachment") + enumJobsSubscriptionAttachmentPageAttachment(w, &c, x) } } -func enumServingAiGatewayGuardrailPiiBehavior(w *walker, n *node, v *serving.AiGatewayGuardrailPiiBehavior) { +func enumServingAiGatewayGuardrailPiiBehavior(w *walker, n *walk.Node, v *serving.AiGatewayGuardrailPiiBehavior) { if v == nil { - v = &serving.AiGatewayGuardrailPiiBehavior{} + return + } + if s := string(v.Behavior); s != "" { + c := n.Child("behavior") + w.enumValue(&c, s, enumValues111) } - w.enumField(n, "behavior", string(v.Behavior), enumValuesServingAiGatewayGuardrailPiiBehaviorBehavior) } -func enumServingAmazonBedrockConfig(w *walker, n *node, v *serving.AmazonBedrockConfig) { +func enumServingAmazonBedrockConfig(w *walker, n *walk.Node, v *serving.AmazonBedrockConfig) { if v == nil { - v = &serving.AmazonBedrockConfig{} + return + } + if s := string(v.BedrockProvider); s != "" || walk.Forced(v.ForceSendFields, "BedrockProvider") { + c := n.Child("bedrock_provider") + w.enumValue(&c, s, enumValues112) } - w.enumField(n, "bedrock_provider", string(v.BedrockProvider), enumValuesServingAmazonBedrockConfigBedrockProvider) } -func enumPipelinesConnectorOptions(w *walker, n *node, v *pipelines.ConnectorOptions) { +func enumPipelinesConnectorOptions(w *walker, n *walk.Node, v *pipelines.ConnectorOptions) { if v == nil { - v = &pipelines.ConnectorOptions{} + return } - if c := n.child("gdrive_options"); c.exists() { - enumPipelinesGoogleDriveOptions(w, &c, v.GdriveOptions) + if x := v.GdriveOptions; x != nil { + c := n.Child("gdrive_options") + enumPipelinesGoogleDriveOptions(w, &c, x) } - if c := n.child("kafka_options"); c.exists() { - enumPipelinesKafkaOptions(w, &c, v.KafkaOptions) + if x := v.KafkaOptions; x != nil { + c := n.Child("kafka_options") + enumPipelinesKafkaOptions(w, &c, x) } - if c := n.child("linkedin_ads_options"); c.exists() { - enumPipelinesLinkedInAdsOptions(w, &c, v.LinkedinAdsOptions) + if x := v.LinkedinAdsOptions; x != nil { + c := n.Child("linkedin_ads_options") + enumPipelinesLinkedInAdsOptions(w, &c, x) } - if c := n.child("outlook_options"); c.exists() { - enumPipelinesOutlookOptions(w, &c, v.OutlookOptions) + if x := v.OutlookOptions; x != nil { + c := n.Child("outlook_options") + enumPipelinesOutlookOptions(w, &c, x) } - if c := n.child("sharepoint_options"); c.exists() { - enumPipelinesSharepointOptions(w, &c, v.SharepointOptions) + if x := v.SharepointOptions; x != nil { + c := n.Child("sharepoint_options") + enumPipelinesSharepointOptions(w, &c, x) } - if c := n.child("tiktok_ads_options"); c.exists() { - enumPipelinesTikTokAdsOptions(w, &c, v.TiktokAdsOptions) + if x := v.TiktokAdsOptions; x != nil { + c := n.Child("tiktok_ads_options") + enumPipelinesTikTokAdsOptions(w, &c, x) } } -func enumPipelinesIngestionPipelineDefinitionFanoutOptions(w *walker, n *node, v *pipelines.IngestionPipelineDefinitionFanoutOptions) { +func enumPipelinesIngestionPipelineDefinitionFanoutOptions(w *walker, n *walk.Node, v *pipelines.IngestionPipelineDefinitionFanoutOptions) { if v == nil { - v = &pipelines.IngestionPipelineDefinitionFanoutOptions{} + return } if len(v.Transforms) > 0 { - c := n.child("transforms") - for i0 := range v.Transforms { - e := c.elem(i0) - enumPipelinesTransformer(w, &e, &v.Transforms[i0]) + c := n.Child("transforms") + for i := range v.Transforms { + e := c.Elem(i) + enumPipelinesTransformer(w, &e, &v.Transforms[i]) } } } -func enumJobsSubscriptionAttachmentDataAttachment(w *walker, n *node, v *jobs.SubscriptionAttachmentDataAttachment) { +func enumJobsSubscriptionAttachmentDataAttachment(w *walker, n *walk.Node, v *jobs.SubscriptionAttachmentDataAttachment) { if v == nil { - v = &jobs.SubscriptionAttachmentDataAttachment{} + return + } + if s := string(v.FileFormat); s != "" || walk.Forced(v.ForceSendFields, "FileFormat") { + c := n.Child("file_format") + w.enumValue(&c, s, enumValues113) } - w.enumField(n, "file_format", string(v.FileFormat), enumValuesJobsSubscriptionAttachmentDataAttachmentFileFormat) } -func enumJobsSubscriptionAttachmentPageAttachment(w *walker, n *node, v *jobs.SubscriptionAttachmentPageAttachment) { +func enumJobsSubscriptionAttachmentPageAttachment(w *walker, n *walk.Node, v *jobs.SubscriptionAttachmentPageAttachment) { if v == nil { - v = &jobs.SubscriptionAttachmentPageAttachment{} + return + } + if s := string(v.PageSelection); s != "" { + c := n.Child("page_selection") + w.enumValue(&c, s, enumValues114) } - w.enumField(n, "page_selection", string(v.PageSelection), enumValuesJobsSubscriptionAttachmentPageAttachmentPageSelection) } -func enumPipelinesGoogleDriveOptions(w *walker, n *node, v *pipelines.GoogleDriveOptions) { +func enumPipelinesGoogleDriveOptions(w *walker, n *walk.Node, v *pipelines.GoogleDriveOptions) { if v == nil { - v = &pipelines.GoogleDriveOptions{} + return + } + if s := string(v.EntityType); s != "" || walk.Forced(v.ForceSendFields, "EntityType") { + c := n.Child("entity_type") + w.enumValue(&c, s, enumValues115) } - w.enumField(n, "entity_type", string(v.EntityType), enumValuesPipelinesGoogleDriveOptionsGoogleDriveEntityType) - if c := n.child("file_ingestion_options"); c.exists() { - enumPipelinesFileIngestionOptions(w, &c, v.FileIngestionOptions) + if x := v.FileIngestionOptions; x != nil { + c := n.Child("file_ingestion_options") + enumPipelinesFileIngestionOptions(w, &c, x) } } -func enumPipelinesKafkaOptions(w *walker, n *node, v *pipelines.KafkaOptions) { +func enumPipelinesKafkaOptions(w *walker, n *walk.Node, v *pipelines.KafkaOptions) { if v == nil { - v = &pipelines.KafkaOptions{} + return } - if c := n.child("key_transformer"); c.exists() { - enumPipelinesTransformer2(w, &c, v.KeyTransformer) + if x := v.KeyTransformer; x != nil { + c := n.Child("key_transformer") + enumPipelinesTransformer2(w, &c, x) } - if c := n.child("value_transformer"); c.exists() { - enumPipelinesTransformer2(w, &c, v.ValueTransformer) + if x := v.ValueTransformer; x != nil { + c := n.Child("value_transformer") + enumPipelinesTransformer2(w, &c, x) } } -func enumPipelinesLinkedInAdsOptions(w *walker, n *node, v *pipelines.LinkedInAdsOptions) { +func enumPipelinesLinkedInAdsOptions(w *walker, n *walk.Node, v *pipelines.LinkedInAdsOptions) { if v == nil { - v = &pipelines.LinkedInAdsOptions{} + return } - if c := n.child("custom_report_options"); c.exists() { - enumPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptions(w, &c, v.CustomReportOptions) + if x := v.CustomReportOptions; x != nil { + c := n.Child("custom_report_options") + enumPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptions(w, &c, x) } } -func enumPipelinesOutlookOptions(w *walker, n *node, v *pipelines.OutlookOptions) { +func enumPipelinesOutlookOptions(w *walker, n *walk.Node, v *pipelines.OutlookOptions) { if v == nil { - v = &pipelines.OutlookOptions{} + return + } + if s := string(v.AttachmentMode); s != "" || walk.Forced(v.ForceSendFields, "AttachmentMode") { + c := n.Child("attachment_mode") + w.enumValue(&c, s, enumValues116) + } + if s := string(v.BodyFormat); s != "" || walk.Forced(v.ForceSendFields, "BodyFormat") { + c := n.Child("body_format") + w.enumValue(&c, s, enumValues117) } - w.enumField(n, "attachment_mode", string(v.AttachmentMode), enumValuesPipelinesOutlookAttachmentMode) - w.enumField(n, "body_format", string(v.BodyFormat), enumValuesPipelinesOutlookBodyFormat) } -func enumPipelinesSharepointOptions(w *walker, n *node, v *pipelines.SharepointOptions) { +func enumPipelinesSharepointOptions(w *walker, n *walk.Node, v *pipelines.SharepointOptions) { if v == nil { - v = &pipelines.SharepointOptions{} + return + } + if s := string(v.EntityType); s != "" || walk.Forced(v.ForceSendFields, "EntityType") { + c := n.Child("entity_type") + w.enumValue(&c, s, enumValues118) } - w.enumField(n, "entity_type", string(v.EntityType), enumValuesPipelinesSharepointOptionsSharepointEntityType) - if c := n.child("file_ingestion_options"); c.exists() { - enumPipelinesFileIngestionOptions(w, &c, v.FileIngestionOptions) + if x := v.FileIngestionOptions; x != nil { + c := n.Child("file_ingestion_options") + enumPipelinesFileIngestionOptions(w, &c, x) } } -func enumPipelinesTikTokAdsOptions(w *walker, n *node, v *pipelines.TikTokAdsOptions) { +func enumPipelinesTikTokAdsOptions(w *walker, n *walk.Node, v *pipelines.TikTokAdsOptions) { if v == nil { - v = &pipelines.TikTokAdsOptions{} + return + } + if x := v.CustomReportOptions; x != nil { + c := n.Child("custom_report_options") + enumPipelinesTikTokAdsOptionsTikTokAdsCustomReportOptions(w, &c, x) } - if c := n.child("custom_report_options"); c.exists() { - enumPipelinesTikTokAdsOptionsTikTokAdsCustomReportOptions(w, &c, v.CustomReportOptions) + if s := string(v.DataLevel); s != "" || walk.Forced(v.ForceSendFields, "DataLevel") { + c := n.Child("data_level") + w.enumValue(&c, s, enumValues119) + } + if s := string(v.ReportType); s != "" || walk.Forced(v.ForceSendFields, "ReportType") { + c := n.Child("report_type") + w.enumValue(&c, s, enumValues120) } - w.enumField(n, "data_level", string(v.DataLevel), enumValuesPipelinesTikTokAdsOptionsTikTokDataLevel) - w.enumField(n, "report_type", string(v.ReportType), enumValuesPipelinesTikTokAdsOptionsTikTokReportType) } -func enumPipelinesTransformer(w *walker, n *node, v *pipelines.Transformer) { +func enumPipelinesTransformer(w *walker, n *walk.Node, v *pipelines.Transformer) { if v == nil { - v = &pipelines.Transformer{} + return + } + if x := v.AvroOptions; x != nil { + c := n.Child("avro_options") + enumPipelinesAvroTransformerOptions(w, &c, x) } - if c := n.child("avro_options"); c.exists() { - enumPipelinesAvroTransformerOptions(w, &c, v.AvroOptions) + if s := string(v.Format); s != "" || walk.Forced(v.ForceSendFields, "Format") { + c := n.Child("format") + w.enumValue(&c, s, enumValues121) } - w.enumField(n, "format", string(v.Format), enumValuesPipelinesTransformerFormat) - if c := n.child("json_options"); c.exists() { - enumPipelinesJsonTransformerOptions(w, &c, v.JsonOptions) + if x := v.JsonOptions; x != nil { + c := n.Child("json_options") + enumPipelinesJsonTransformerOptions(w, &c, x) } - if c := n.child("protobuf_options"); c.exists() { - enumPipelinesProtobufTransformerOptions(w, &c, v.ProtobufOptions) + if x := v.ProtobufOptions; x != nil { + c := n.Child("protobuf_options") + enumPipelinesProtobufTransformerOptions(w, &c, x) } } -func enumPipelinesFileIngestionOptions(w *walker, n *node, v *pipelines.FileIngestionOptions) { +func enumPipelinesFileIngestionOptions(w *walker, n *walk.Node, v *pipelines.FileIngestionOptions) { if v == nil { - v = &pipelines.FileIngestionOptions{} + return + } + if s := string(v.Format); s != "" || walk.Forced(v.ForceSendFields, "Format") { + c := n.Child("format") + w.enumValue(&c, s, enumValues122) + } + if s := string(v.SchemaEvolutionMode); s != "" || walk.Forced(v.ForceSendFields, "SchemaEvolutionMode") { + c := n.Child("schema_evolution_mode") + w.enumValue(&c, s, enumValues123) } - w.enumField(n, "format", string(v.Format), enumValuesPipelinesFileIngestionOptionsFileFormat) - w.enumField(n, "schema_evolution_mode", string(v.SchemaEvolutionMode), enumValuesPipelinesFileIngestionOptionsSchemaEvolutionMode) } -func enumPipelinesTransformer2(w *walker, n *node, v *pipelines.Transformer) { +func enumPipelinesTransformer2(w *walker, n *walk.Node, v *pipelines.Transformer) { if v == nil { - v = &pipelines.Transformer{} + return + } + if x := v.AvroOptions; x != nil { + c := n.Child("avro_options") + enumPipelinesAvroTransformerOptions(w, &c, x) } - if c := n.child("avro_options"); c.exists() { - enumPipelinesAvroTransformerOptions(w, &c, v.AvroOptions) + if s := string(v.Format); s != "" || walk.Forced(v.ForceSendFields, "Format") { + c := n.Child("format") + w.enumValue(&c, s, enumValues121) } - w.enumField(n, "format", string(v.Format), enumValuesPipelinesTransformerFormat) - if c := n.child("json_options"); c.exists() { - enumPipelinesJsonTransformerOptions(w, &c, v.JsonOptions) + if x := v.JsonOptions; x != nil { + c := n.Child("json_options") + enumPipelinesJsonTransformerOptions(w, &c, x) } - if c := n.child("protobuf_options"); c.exists() { - enumPipelinesProtobufTransformerOptions(w, &c, v.ProtobufOptions) + if x := v.ProtobufOptions; x != nil { + c := n.Child("protobuf_options") + enumPipelinesProtobufTransformerOptions(w, &c, x) } } -func enumPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptions(w *walker, n *node, v *pipelines.LinkedInAdsOptionsLinkedInAdsCustomReportOptions) { +func enumPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptions(w *walker, n *walk.Node, v *pipelines.LinkedInAdsOptionsLinkedInAdsCustomReportOptions) { if v == nil { - v = &pipelines.LinkedInAdsOptionsLinkedInAdsCustomReportOptions{} + return } if len(v.EntityGranularity) > 0 { - c := n.child("entity_granularity") - for i0 := range v.EntityGranularity { - e := c.elem(i0) - w.enumElem(&e, string(v.EntityGranularity[i0]), enumValuesPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptionsLinkedInAdsEntityGranularity) + c := n.Child("entity_granularity") + for i := range v.EntityGranularity { + e := c.Elem(i) + w.enumValue(&e, string(v.EntityGranularity[i]), enumValues124) } } - w.enumField(n, "finder", string(v.Finder), enumValuesPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptionsLinkedInAdsFinder) - w.enumField(n, "time_granularity", string(v.TimeGranularity), enumValuesPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptionsLinkedInAdsTimeGranularity) + if s := string(v.Finder); s != "" { + c := n.Child("finder") + w.enumValue(&c, s, enumValues125) + } + if s := string(v.TimeGranularity); s != "" { + c := n.Child("time_granularity") + w.enumValue(&c, s, enumValues126) + } } -func enumPipelinesTikTokAdsOptionsTikTokAdsCustomReportOptions(w *walker, n *node, v *pipelines.TikTokAdsOptionsTikTokAdsCustomReportOptions) { +func enumPipelinesTikTokAdsOptionsTikTokAdsCustomReportOptions(w *walker, n *walk.Node, v *pipelines.TikTokAdsOptionsTikTokAdsCustomReportOptions) { if v == nil { - v = &pipelines.TikTokAdsOptionsTikTokAdsCustomReportOptions{} + return + } + if s := string(v.DataLevel); s != "" || walk.Forced(v.ForceSendFields, "DataLevel") { + c := n.Child("data_level") + w.enumValue(&c, s, enumValues119) + } + if s := string(v.ReportType); s != "" || walk.Forced(v.ForceSendFields, "ReportType") { + c := n.Child("report_type") + w.enumValue(&c, s, enumValues120) } - w.enumField(n, "data_level", string(v.DataLevel), enumValuesPipelinesTikTokAdsOptionsTikTokDataLevel) - w.enumField(n, "report_type", string(v.ReportType), enumValuesPipelinesTikTokAdsOptionsTikTokReportType) } -func enumPipelinesAvroTransformerOptions(w *walker, n *node, v *pipelines.AvroTransformerOptions) { +func enumPipelinesAvroTransformerOptions(w *walker, n *walk.Node, v *pipelines.AvroTransformerOptions) { if v == nil { - v = &pipelines.AvroTransformerOptions{} + return + } + if s := string(v.ParseMode); s != "" || walk.Forced(v.ForceSendFields, "ParseMode") { + c := n.Child("parse_mode") + w.enumValue(&c, s, enumValues127) } - w.enumField(n, "parse_mode", string(v.ParseMode), enumValuesPipelinesParseMode) } -func enumPipelinesJsonTransformerOptions(w *walker, n *node, v *pipelines.JsonTransformerOptions) { +func enumPipelinesJsonTransformerOptions(w *walker, n *walk.Node, v *pipelines.JsonTransformerOptions) { if v == nil { - v = &pipelines.JsonTransformerOptions{} + return + } + if s := string(v.SchemaEvolutionMode); s != "" || walk.Forced(v.ForceSendFields, "SchemaEvolutionMode") { + c := n.Child("schema_evolution_mode") + w.enumValue(&c, s, enumValues123) } - w.enumField(n, "schema_evolution_mode", string(v.SchemaEvolutionMode), enumValuesPipelinesFileIngestionOptionsSchemaEvolutionMode) } -func enumPipelinesProtobufTransformerOptions(w *walker, n *node, v *pipelines.ProtobufTransformerOptions) { +func enumPipelinesProtobufTransformerOptions(w *walker, n *walk.Node, v *pipelines.ProtobufTransformerOptions) { if v == nil { - v = &pipelines.ProtobufTransformerOptions{} + return + } + if s := string(v.ParseMode); s != "" || walk.Forced(v.ForceSendFields, "ParseMode") { + c := n.Child("parse_mode") + w.enumValue(&c, s, enumValues127) } - w.enumField(n, "parse_mode", string(v.ParseMode), enumValuesPipelinesParseMode) } diff --git a/bundle/config/validate/interpolation_in_auth_config.go b/bundle/config/validate/interpolation_in_auth_config.go index f749f9e0613..5b5a5ab22fb 100644 --- a/bundle/config/validate/interpolation_in_auth_config.go +++ b/bundle/config/validate/interpolation_in_auth_config.go @@ -7,8 +7,8 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/auth" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) type noInterpolationInAuthConfig struct{} @@ -22,65 +22,58 @@ func (f *noInterpolationInAuthConfig) Name() string { } func (f *noInterpolationInAuthConfig) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - authFields := []string{ + w := &b.Config.Workspace + authFields := []struct { + name string + value string + }{ // Generic attributes. - "host", - "profile", - "auth_type", - "metadata_service_url", + {"host", w.Host}, + {"profile", w.Profile}, + {"auth_type", w.AuthType}, + {"metadata_service_url", w.MetadataServiceURL}, // OAuth specific attributes. - "client_id", + {"client_id", w.ClientID}, // Google specific attributes. - "google_service_account", + {"google_service_account", w.GoogleServiceAccount}, - // Azure specific attributes. - "azure_resource_id", - "azure_use_msi", - "azure_client_id", - "azure_tenant_id", - "azure_environment", - "azure_login_app_id", + // Azure specific attributes. "azure_resource_id" used to be listed here, but it + // is not a workspace key (the field is azure_workspace_resource_id), so it was + // never checked. + {"azure_use_msi", b.Config.ReferenceAt(structpath.NewPath(nil, "workspace", "azure_use_msi"))}, + {"azure_client_id", w.AzureClientID}, + {"azure_tenant_id", w.AzureTenantID}, + {"azure_environment", w.AzureEnvironment}, + {"azure_login_app_id", w.AzureLoginAppID}, // Unified host specific attributes. - "account_id", - "workspace_id", + {"account_id", w.AccountID}, + {"workspace_id", w.WorkspaceID}, } diags := diag.Diagnostics{} - for _, fieldName := range authFields { - p := dyn.NewPath(dyn.Key("workspace"), dyn.Key(fieldName)) - v, err := dyn.GetByPath(b.Config.Value(), p) - if dyn.IsNoSuchKeyError(err) { + for _, field := range authFields { + // Check if the field contains interpolation. + if !structvar.ContainsVariableReference(field.value) { continue } - if err != nil { - return diag.FromErr(err) - } - - vv, ok := v.AsString() + envVar, ok := auth.GetEnvFor(field.name) if !ok { continue } - // Check if the field contains interpolation. - if dynvar.ContainsVariableReference(vv) { - envVar, ok := auth.GetEnvFor(fieldName) - if !ok { - continue - } - - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Variable interpolation is not supported for fields that configure authentication", - Detail: fmt.Sprintf(`Interpolation is not supported for the field %s. Please set + p := structpath.NewPath(nil, "workspace", field.name) + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Variable interpolation is not supported for fields that configure authentication", + Detail: fmt.Sprintf(`Interpolation is not supported for the field %s. Please set the %s environment variable if you wish to configure this field at runtime.`, p.String(), envVar), - Locations: v.Locations(), - Paths: dyn.ToStructPaths(p), - }) - } + Locations: b.Config.GetLocationsOfExact(p), + Paths: []*structpath.PathNode{p}, + }) } return diags diff --git a/bundle/config/validate/job_task_cluster_spec.go b/bundle/config/validate/job_task_cluster_spec.go index e387d83f6fb..71bce7173d5 100644 --- a/bundle/config/validate/job_task_cluster_spec.go +++ b/bundle/config/validate/job_task_cluster_spec.go @@ -7,7 +7,6 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/jobs" ) @@ -27,13 +26,13 @@ func (v *jobTaskClusterSpec) Name() string { func (v *jobTaskClusterSpec) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { diags := diag.Diagnostics{} - jobsPath := dyn.NewPath(dyn.Key("resources"), dyn.Key("jobs")) + jobsPath := structpath.NewPath(nil, "resources", "jobs") for resourceName, job := range b.Config.Resources.Jobs { - resourcePath := jobsPath.Append(dyn.Key(resourceName)) + resourcePath := structpath.NewPath(jobsPath, resourceName) for taskIndex, task := range job.Tasks { - taskPath := resourcePath.Append(dyn.Key("tasks"), dyn.Index(taskIndex)) + taskPath := structpath.NewPath(resourcePath, "tasks", taskIndex) diags = diags.Extend(validateJobTask(b, task, taskPath)) } @@ -42,7 +41,7 @@ func (v *jobTaskClusterSpec) Apply(ctx context.Context, b *bundle.Bundle) diag.D return diags } -func validateJobTask(b *bundle.Bundle, task jobs.Task, taskPath dyn.Path) diag.Diagnostics { +func validateJobTask(b *bundle.Bundle, task jobs.Task, taskPath *structpath.PathNode) diag.Diagnostics { diags := diag.Diagnostics{} var specified []string @@ -73,7 +72,7 @@ func validateJobTask(b *bundle.Bundle, task jobs.Task, taskPath dyn.Path) diag.D } if task.ForEachTask != nil { - forEachTaskPath := taskPath.Append(dyn.Key("for_each_task"), dyn.Key("task")) + forEachTaskPath := structpath.NewPath(taskPath, "for_each_task", "task") diags = diags.Extend(validateJobTask(b, task.ForEachTask.Task, forEachTaskPath)) } @@ -89,13 +88,12 @@ func validateJobTask(b *bundle.Bundle, task jobs.Task, taskPath dyn.Path) diag.D strings.Join(unspecified, ", "), ) - taskNode := dyn.ToStructPath(taskPath) diags = diags.Append(diag.Diagnostic{ Severity: diag.Error, Summary: "Missing required cluster or environment settings", Detail: detail, - Locations: b.Config.GetLocationsOf(taskNode), - Paths: []*structpath.PathNode{taskNode}, + Locations: b.Config.GetLocationsOf(taskPath), + Paths: []*structpath.PathNode{taskPath}, }) } } diff --git a/bundle/config/validate/no_interpolation_in_bundle_name.go b/bundle/config/validate/no_interpolation_in_bundle_name.go index 8fc547b174e..0850f27ac15 100644 --- a/bundle/config/validate/no_interpolation_in_bundle_name.go +++ b/bundle/config/validate/no_interpolation_in_bundle_name.go @@ -5,9 +5,9 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/logdiag" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) type noInterpolationInBundleName struct{} @@ -21,7 +21,7 @@ func (m *noInterpolationInBundleName) Name() string { } func (m *noInterpolationInBundleName) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - if dynvar.ContainsVariableReference(b.Config.Bundle.Name) { + if structvar.ContainsVariableReference(b.Config.Bundle.Name) { logdiag.LogDiag(ctx, diag.Diagnostic{ Severity: diag.Warning, Summary: `Please do not use variable interpolation in the name of your bundle. The name of your bundle diff --git a/bundle/config/validate/no_variable_reference_in_resource_key.go b/bundle/config/validate/no_variable_reference_in_resource_key.go index 75b8361ce97..7c6b4f1e8f1 100644 --- a/bundle/config/validate/no_variable_reference_in_resource_key.go +++ b/bundle/config/validate/no_variable_reference_in_resource_key.go @@ -3,11 +3,14 @@ package validate import ( "context" "fmt" + "maps" + "slices" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) type noVariableReferenceInResourceKey struct{} @@ -25,30 +28,27 @@ func (m *noVariableReferenceInResourceKey) Name() string { func (m *noVariableReferenceInResourceKey) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { var diags diag.Diagnostics - patterns := []dyn.Pattern{ - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), - dyn.NewPattern(dyn.Key("targets"), dyn.AnyKey(), dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), + check := func(prefix *structpath.PathNode, r *config.Resources) { + for _, group := range r.AllResources() { + for _, key := range slices.Sorted(maps.Keys(group.Resources)) { + if !structvar.ContainsVariableReference(key) { + continue + } + p := structpath.NewPath(prefix, group.Description.PluralName, key) + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("resource key %q must not contain variable references", key), + Locations: b.Config.GetLocationsOfExact(p), + Paths: []*structpath.PathNode{p}, + }) + } + } } - for _, pattern := range patterns { - _, err := dyn.MapByPattern( - b.Config.Value(), - pattern, - func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - key := p[len(p)-1].Key() - if dynvar.ContainsVariableReference(key) { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("resource key %q must not contain variable references", key), - Locations: v.Locations(), - Paths: dyn.ToStructPaths(p), - }) - } - return v, nil - }, - ) - if err != nil { - diags = append(diags, diag.FromErr(err)...) + check(structpath.NewPath(nil, "resources"), &b.Config.Resources) + for _, name := range slices.Sorted(maps.Keys(b.Config.Targets)) { + if t := b.Config.Targets[name]; t != nil && t.Resources != nil { + check(structpath.NewPath(nil, "targets", name, "resources"), t.Resources) } } diff --git a/bundle/config/validate/pipeline_development_deprecated.go b/bundle/config/validate/pipeline_development_deprecated.go index 3eb3c77c5f0..cb7f93a5688 100644 --- a/bundle/config/validate/pipeline_development_deprecated.go +++ b/bundle/config/validate/pipeline_development_deprecated.go @@ -2,11 +2,13 @@ package validate import ( "context" + "maps" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) const ( @@ -32,35 +34,45 @@ func (v *pipelineDevelopmentDeprecated) Apply(_ context.Context, b *bundle.Bundl presetEnabled := config.IsExplicitlyEnabled(b.Config.Presets.PipelinesDevelopment) - pattern := dyn.NewPattern(dyn.Key("resources"), dyn.Key("pipelines"), dyn.AnyKey(), dyn.Key("development")) - _, err := dyn.MapByPattern(b.Config.Value(), pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - // Only user-written values have a location; the value set by "mode: development" does not. - if len(v.Locations()) == 0 { - return v, nil + pipelines := b.Config.Resources.Pipelines + for _, key := range slices.Sorted(maps.Keys(pipelines)) { + pipeline := pipelines[key] + if pipeline == nil { + continue } + p := structpath.NewPath(nil, "resources", "pipelines", key, "development") + + // The property is set if it is forced (an explicit value: decoding records a + // written false, and the preset keeps the record when it sets true), a + // reference, or true without the preset. A true from the preset + // ("mode: development") is not reported; a "development: true" written in the + // configuration is then indistinguishable from it. + development := pipeline.Development //nolint:staticcheck // SA1019: see apply_presets.go + forced := slices.Contains(pipeline.ForceSendFields, "Development") + if !forced && !b.Config.IsReferenceAt(p) && (!development || presetEnabled) { + continue + } + locs := b.Config.GetLocationsOfExact(p) + diags = append(diags, diag.Diagnostic{ Severity: diag.Warning, Summary: pipelineDevelopmentDeprecatedSummary, - Locations: v.Locations(), - Paths: dyn.ToStructPaths(p), + Locations: locs, + Paths: []*structpath.PathNode{p}, }) // The preset overwrites YAML values with true, so a false here was set by a Python // mutator after the preset ran. "bundle run" still sends development: true. - if development, ok := v.AsBool(); ok && presetEnabled && !development { + if presetEnabled && !pipeline.Development { //nolint:staticcheck // SA1019: see above diags = append(diags, diag.Diagnostic{ Severity: diag.Warning, Summary: pipelineDevelopmentIgnoredSummary, Detail: pipelineDevelopmentIgnoredDetail, - Locations: v.Locations(), - Paths: dyn.ToStructPaths(p), + Locations: locs, + Paths: []*structpath.PathNode{p}, }) } - return v, nil - }) - if err != nil { - return diag.FromErr(err) } return diags diff --git a/bundle/config/validate/required.go b/bundle/config/validate/required.go index d8892de4f6b..251293e8697 100644 --- a/bundle/config/validate/required.go +++ b/bundle/config/validate/required.go @@ -9,8 +9,8 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go/service/catalog" ) type required struct{} @@ -26,13 +26,9 @@ func (f *required) Name() string { // Warn for missing fields, based on annotations in the Go SDK / OpenAPI spec. // The walker is generated (required_walk.go) by bundle/internal/validation. func warnForMissingFields(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - w := &walker{diags: diag.Diagnostics{}} - n := rootNode(b.Config.Value()) - requiredConfigRoot(w, &n, &b.Config) - - sortDiagnostics(w.diags) - - return w.diags + diags := walkConfig(&b.Config, true, requiredConfigRoot) + sortDiagnostics(diags) + return diags } // sortDiagnostics orders diagnostics deterministically, since they are collected @@ -64,19 +60,18 @@ func errorForMissingFields(ctx context.Context, b *bundle.Bundle) diag.Diagnosti diags := diag.Diagnostics{} for key, dashboard := range b.Config.Resources.Dashboards { + path := structpath.NewPath(nil, "resources", "dashboards", key) if dashboard.DisplayName == "" { - path := structpath.NewPath(nil, "resources", "dashboards", key) nameLocations = append(nameLocations, b.Config.GetLocationsOf(path)...) namePaths = append(namePaths, path) } if dashboard.WarehouseId == "" { - path := structpath.NewPath(nil, "resources", "dashboards", key) warehouseIdLocations = append(warehouseIdLocations, b.Config.GetLocationsOf(path)...) warehouseIdPaths = append(warehouseIdPaths, path) } } - if len(nameLocations) > 0 { + if len(namePaths) > 0 { diags = diags.Append(diag.Diagnostic{ Severity: diag.Error, Summary: "dashboard display_name is required", @@ -84,7 +79,7 @@ func errorForMissingFields(ctx context.Context, b *bundle.Bundle) diag.Diagnosti Paths: namePaths, }) } - if len(warehouseIdLocations) > 0 { + if len(warehouseIdPaths) > 0 { diags = diags.Append(diag.Diagnostic{ Severity: diag.Error, Summary: "dashboard warehouse_id is required", @@ -120,32 +115,29 @@ func errorForMissingFields(ctx context.Context, b *bundle.Bundle) diag.Diagnosti func errorForInvalidGrants(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { diags := diag.Diagnostics{} - _, err := dyn.MapByPattern( - b.Config.Value(), - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey(), dyn.Key("grants"), dyn.AnyIndex()), - func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - if isMissingOrEmptyString(v.Get("principal")) { + b.Config.Resources.IterateAllGrants(func(typ, name string, grants *[]catalog.PrivilegeAssignment) bool { + for i, g := range *grants { + np := structpath.NewPath(nil, "resources", typ, name, "grants", i) + if g.Principal == "" { diags = diags.Append(diag.Diagnostic{ Severity: diag.Error, Summary: "grant principal is required", - Locations: v.Locations(), - Paths: dyn.ToStructPaths(p), + Locations: b.Config.GetLocationsOfExact(np), + Paths: []*structpath.PathNode{np}, }) } - if isMissingOrEmptySequence(v.Get("privileges")) { + // A pure reference stands for the privileges until it is resolved. + if len(g.Privileges) == 0 && !b.Config.IsReferenceAt(structpath.NewPath(np, "privileges")) { diags = diags.Append(diag.Diagnostic{ Severity: diag.Error, Summary: "grant privileges is required", - Locations: v.Locations(), - Paths: dyn.ToStructPaths(p), + Locations: b.Config.GetLocationsOfExact(np), + Paths: []*structpath.PathNode{np}, }) } - return v, nil - }, - ) - if err != nil { - return diag.FromErr(err) - } + } + return true + }) sortDiagnostics(diags) @@ -162,14 +154,16 @@ func errorForInvalidSecretScopePermissions(ctx context.Context, b *bundle.Bundle if perm.UserName != "" || perm.GroupName != "" || perm.ServicePrincipalName != "" { continue } + scopePath := structpath.NewPath(nil, "resources", "secret_scopes", key) + path := structpath.NewPath(scopePath, "permissions", i) // ApplyBundlePermissions rebuilds permissions via convert.FromTyped and drops // per-entry locations, so point at the scope. diags = diags.Append(diag.Diagnostic{ Severity: diag.Error, Summary: "secret scope permission principal is required", Detail: "Set one of user_name, group_name or service_principal_name", - Locations: b.Config.GetLocationsOf(structpath.NewPath(nil, "resources", "secret_scopes", key)), - Paths: structpath.NewPathSlice("resources", "secret_scopes", key, "permissions", i), + Locations: b.Config.GetLocationsOf(scopePath), + Paths: []*structpath.PathNode{path}, }) } } @@ -179,30 +173,6 @@ func errorForInvalidSecretScopePermissions(ctx context.Context, b *bundle.Bundle return diags } -// isMissingOrEmptyString reports whether v is unset, null, or an empty string. -func isMissingOrEmptyString(v dyn.Value) bool { - switch v.Kind() { - case dyn.KindInvalid, dyn.KindNil: - return true - case dyn.KindString: - return v.MustString() == "" - default: - return false - } -} - -// isMissingOrEmptySequence reports whether v is unset, null, or an empty sequence. -func isMissingOrEmptySequence(v dyn.Value) bool { - switch v.Kind() { - case dyn.KindInvalid, dyn.KindNil: - return true - case dyn.KindSequence: - return len(v.MustSequence()) == 0 - default: - return false - } -} - func (f *required) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { diags := errorForMissingFields(ctx, b) diags = diags.Extend(errorForInvalidGrants(ctx, b)) diff --git a/bundle/config/validate/required_walk.go b/bundle/config/validate/required_walk.go index db2bf0a5430..e0542637f1c 100644 --- a/bundle/config/validate/required_walk.go +++ b/bundle/config/validate/required_walk.go @@ -1,12 +1,13 @@ // Code generated by bundle/internal/validation. DO NOT EDIT. -// requiredConfigRoot reports the required fields that are not set. +// requiredWalk reports required fields that are not set. package validate import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/config/walk" "github.com/databricks/databricks-sdk-go/service/apps" "github.com/databricks/databricks-sdk-go/service/catalog" "github.com/databricks/databricks-sdk-go/service/compute" @@ -20,4211 +21,5434 @@ import ( "github.com/databricks/databricks-sdk-go/service/workspace" ) -func requiredConfigRoot(w *walker, n *node, v *config.Root) { +func requiredConfigRoot(w *walker, n *walk.Node, v *config.Root) { if v == nil { - v = &config.Root{} + return } if len(v.Artifacts) > 0 { - c := n.child("artifacts") - for k0, x0 := range v.Artifacts { - if k0 == "" { + c := n.Child("artifacts") + for k, x := range v.Artifacts { + if k == "" { continue } - e := c.child(k0) - requiredConfigArtifact(w, &e, x0) + e := c.Child(k) + requiredConfigArtifact(w, &e, x) } } - if c := n.child("bundle"); c.exists() { - requiredConfigBundle(w, &c, &v.Bundle) + if x := &v.Bundle; !walk.IsZeroStruct(x) || n.HasRefs("bundle") { + c := n.Child("bundle") + requiredConfigBundle(w, &c, x) } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesPermission(w, &e, &v.Permissions[i]) } } - if c := n.child("resources"); c.exists() { - requiredConfigResources(w, &c, &v.Resources) + if x := &v.Resources; !walk.IsZeroStruct(x) || n.HasRefs("resources") { + c := n.Child("resources") + requiredConfigResources(w, &c, x) } if len(v.Scripts) > 0 { - c := n.child("scripts") - for k0, x0 := range v.Scripts { - if k0 == "" { + c := n.Child("scripts") + for k, x := range v.Scripts { + if k == "" { continue } - e := c.child(k0) - requiredConfigScript(w, &e, &x0) + e := c.Child(k) + requiredConfigScript(w, &e, &x) } } } -func requiredConfigArtifact(w *walker, n *node, v *config.Artifact) { +func requiredConfigArtifact(w *walker, n *walk.Node, v *config.Artifact) { if v == nil { - v = &config.Artifact{} + return } if len(v.Files) > 0 { - c := n.child("files") - for i0 := range v.Files { - e := c.elem(i0) - requiredConfigArtifactFile(w, &e, &v.Files[i0]) + c := n.Child("files") + for i := range v.Files { + e := c.Elem(i) + requiredConfigArtifactFile(w, &e, &v.Files[i]) } } } -func requiredConfigBundle(w *walker, n *node, v *config.Bundle) { +func requiredConfigBundle(w *walker, n *walk.Node, v *config.Bundle) { if v == nil { - v = &config.Bundle{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } } -func requiredResourcesPermission(w *walker, n *node, v *resources.Permission) { +func requiredResourcesPermission(w *walker, n *walk.Node, v *resources.Permission) { if v == nil { - v = &resources.Permission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredConfigResources(w *walker, n *node, v *config.Resources) { +func requiredConfigResources(w *walker, n *walk.Node, v *config.Resources) { if v == nil { - v = &config.Resources{} + return } if len(v.Alerts) > 0 { - c := n.child("alerts") - for k0, x0 := range v.Alerts { - if k0 == "" { + c := n.Child("alerts") + for k, x := range v.Alerts { + if k == "" { continue } - e := c.child(k0) - requiredResourcesAlert(w, &e, x0) + e := c.Child(k) + requiredResourcesAlert(w, &e, x) } } if len(v.Apps) > 0 { - c := n.child("apps") - for k0, x0 := range v.Apps { - if k0 == "" { + c := n.Child("apps") + for k, x := range v.Apps { + if k == "" { continue } - e := c.child(k0) - requiredResourcesApp(w, &e, x0) + e := c.Child(k) + requiredResourcesApp(w, &e, x) } } if len(v.Catalogs) > 0 { - c := n.child("catalogs") - for k0, x0 := range v.Catalogs { - if k0 == "" { + c := n.Child("catalogs") + for k, x := range v.Catalogs { + if k == "" { continue } - e := c.child(k0) - requiredResourcesCatalog(w, &e, x0) + e := c.Child(k) + requiredResourcesCatalog(w, &e, x) } } if len(v.ClusterPolicies) > 0 { - c := n.child("cluster_policies") - for k0, x0 := range v.ClusterPolicies { - if k0 == "" { + c := n.Child("cluster_policies") + for k, x := range v.ClusterPolicies { + if k == "" { continue } - e := c.child(k0) - requiredResourcesClusterPolicy(w, &e, x0) + e := c.Child(k) + requiredResourcesClusterPolicy(w, &e, x) } } if len(v.Clusters) > 0 { - c := n.child("clusters") - for k0, x0 := range v.Clusters { - if k0 == "" { + c := n.Child("clusters") + for k, x := range v.Clusters { + if k == "" { continue } - e := c.child(k0) - requiredResourcesCluster(w, &e, x0) + e := c.Child(k) + requiredResourcesCluster(w, &e, x) } } if len(v.Dashboards) > 0 { - c := n.child("dashboards") - for k0, x0 := range v.Dashboards { - if k0 == "" { + c := n.Child("dashboards") + for k, x := range v.Dashboards { + if k == "" { continue } - e := c.child(k0) - requiredResourcesDashboard(w, &e, x0) + e := c.Child(k) + requiredResourcesDashboard(w, &e, x) } } if len(v.DatabaseCatalogs) > 0 { - c := n.child("database_catalogs") - for k0, x0 := range v.DatabaseCatalogs { - if k0 == "" { + c := n.Child("database_catalogs") + for k, x := range v.DatabaseCatalogs { + if k == "" { continue } - e := c.child(k0) - requiredResourcesDatabaseCatalog(w, &e, x0) + e := c.Child(k) + requiredResourcesDatabaseCatalog(w, &e, x) } } if len(v.DatabaseInstances) > 0 { - c := n.child("database_instances") - for k0, x0 := range v.DatabaseInstances { - if k0 == "" { + c := n.Child("database_instances") + for k, x := range v.DatabaseInstances { + if k == "" { continue } - e := c.child(k0) - requiredResourcesDatabaseInstance(w, &e, x0) + e := c.Child(k) + requiredResourcesDatabaseInstance(w, &e, x) } } if len(v.Experiments) > 0 { - c := n.child("experiments") - for k0, x0 := range v.Experiments { - if k0 == "" { + c := n.Child("experiments") + for k, x := range v.Experiments { + if k == "" { continue } - e := c.child(k0) - requiredResourcesMlflowExperiment(w, &e, x0) + e := c.Child(k) + requiredResourcesMlflowExperiment(w, &e, x) } } if len(v.ExternalLocations) > 0 { - c := n.child("external_locations") - for k0, x0 := range v.ExternalLocations { - if k0 == "" { + c := n.Child("external_locations") + for k, x := range v.ExternalLocations { + if k == "" { continue } - e := c.child(k0) - requiredResourcesExternalLocation(w, &e, x0) + e := c.Child(k) + requiredResourcesExternalLocation(w, &e, x) } } if len(v.Features) > 0 { - c := n.child("features") - for k0, x0 := range v.Features { - if k0 == "" { + c := n.Child("features") + for k, x := range v.Features { + if k == "" { continue } - e := c.child(k0) - requiredResourcesFeature(w, &e, x0) + e := c.Child(k) + requiredResourcesFeature(w, &e, x) } } if len(v.GenieSpaces) > 0 { - c := n.child("genie_spaces") - for k0, x0 := range v.GenieSpaces { - if k0 == "" { + c := n.Child("genie_spaces") + for k, x := range v.GenieSpaces { + if k == "" { continue } - e := c.child(k0) - requiredResourcesGenieSpace(w, &e, x0) + e := c.Child(k) + requiredResourcesGenieSpace(w, &e, x) } } if len(v.InstancePools) > 0 { - c := n.child("instance_pools") - for k0, x0 := range v.InstancePools { - if k0 == "" { + c := n.Child("instance_pools") + for k, x := range v.InstancePools { + if k == "" { continue } - e := c.child(k0) - requiredResourcesInstancePool(w, &e, x0) + e := c.Child(k) + requiredResourcesInstancePool(w, &e, x) } } if len(v.JobRuns) > 0 { - c := n.child("job_runs") - for k0, x0 := range v.JobRuns { - if k0 == "" { + c := n.Child("job_runs") + for k, x := range v.JobRuns { + if k == "" { continue } - e := c.child(k0) - requiredResourcesJobRun(w, &e, x0) + e := c.Child(k) + requiredResourcesJobRun(w, &e, x) } } if len(v.Jobs) > 0 { - c := n.child("jobs") - for k0, x0 := range v.Jobs { - if k0 == "" { + c := n.Child("jobs") + for k, x := range v.Jobs { + if k == "" { continue } - e := c.child(k0) - requiredResourcesJob(w, &e, x0) + e := c.Child(k) + requiredResourcesJob(w, &e, x) } } if len(v.McpServices) > 0 { - c := n.child("mcp_services") - for k0, x0 := range v.McpServices { - if k0 == "" { + c := n.Child("mcp_services") + for k, x := range v.McpServices { + if k == "" { continue } - e := c.child(k0) - requiredResourcesMcpService(w, &e, x0) + e := c.Child(k) + requiredResourcesMcpService(w, &e, x) } } if len(v.ModelProviderServices) > 0 { - c := n.child("model_provider_services") - for k0, x0 := range v.ModelProviderServices { - if k0 == "" { + c := n.Child("model_provider_services") + for k, x := range v.ModelProviderServices { + if k == "" { continue } - e := c.child(k0) - requiredResourcesModelProviderService(w, &e, x0) + e := c.Child(k) + requiredResourcesModelProviderService(w, &e, x) } } if len(v.ModelServices) > 0 { - c := n.child("model_services") - for k0, x0 := range v.ModelServices { - if k0 == "" { + c := n.Child("model_services") + for k, x := range v.ModelServices { + if k == "" { continue } - e := c.child(k0) - requiredResourcesModelService(w, &e, x0) + e := c.Child(k) + requiredResourcesModelService(w, &e, x) } } if len(v.ModelServingEndpoints) > 0 { - c := n.child("model_serving_endpoints") - for k0, x0 := range v.ModelServingEndpoints { - if k0 == "" { + c := n.Child("model_serving_endpoints") + for k, x := range v.ModelServingEndpoints { + if k == "" { continue } - e := c.child(k0) - requiredResourcesModelServingEndpoint(w, &e, x0) + e := c.Child(k) + requiredResourcesModelServingEndpoint(w, &e, x) } } if len(v.Models) > 0 { - c := n.child("models") - for k0, x0 := range v.Models { - if k0 == "" { + c := n.Child("models") + for k, x := range v.Models { + if k == "" { continue } - e := c.child(k0) - requiredResourcesMlflowModel(w, &e, x0) + e := c.Child(k) + requiredResourcesMlflowModel(w, &e, x) } } if len(v.Pipelines) > 0 { - c := n.child("pipelines") - for k0, x0 := range v.Pipelines { - if k0 == "" { + c := n.Child("pipelines") + for k, x := range v.Pipelines { + if k == "" { continue } - e := c.child(k0) - requiredResourcesPipeline(w, &e, x0) + e := c.Child(k) + requiredResourcesPipeline(w, &e, x) } } if len(v.PostgresBranches) > 0 { - c := n.child("postgres_branches") - for k0, x0 := range v.PostgresBranches { - if k0 == "" { + c := n.Child("postgres_branches") + for k, x := range v.PostgresBranches { + if k == "" { continue } - e := c.child(k0) - requiredResourcesPostgresBranch(w, &e, x0) + e := c.Child(k) + requiredResourcesPostgresBranch(w, &e, x) } } if len(v.PostgresCatalogs) > 0 { - c := n.child("postgres_catalogs") - for k0, x0 := range v.PostgresCatalogs { - if k0 == "" { + c := n.Child("postgres_catalogs") + for k, x := range v.PostgresCatalogs { + if k == "" { continue } - e := c.child(k0) - requiredResourcesPostgresCatalog(w, &e, x0) + e := c.Child(k) + requiredResourcesPostgresCatalog(w, &e, x) } } if len(v.PostgresDatabases) > 0 { - c := n.child("postgres_databases") - for k0, x0 := range v.PostgresDatabases { - if k0 == "" { + c := n.Child("postgres_databases") + for k, x := range v.PostgresDatabases { + if k == "" { continue } - e := c.child(k0) - requiredResourcesPostgresDatabase(w, &e, x0) + e := c.Child(k) + requiredResourcesPostgresDatabase(w, &e, x) } } if len(v.PostgresEndpoints) > 0 { - c := n.child("postgres_endpoints") - for k0, x0 := range v.PostgresEndpoints { - if k0 == "" { + c := n.Child("postgres_endpoints") + for k, x := range v.PostgresEndpoints { + if k == "" { continue } - e := c.child(k0) - requiredResourcesPostgresEndpoint(w, &e, x0) + e := c.Child(k) + requiredResourcesPostgresEndpoint(w, &e, x) } } if len(v.PostgresProjects) > 0 { - c := n.child("postgres_projects") - for k0, x0 := range v.PostgresProjects { - if k0 == "" { + c := n.Child("postgres_projects") + for k, x := range v.PostgresProjects { + if k == "" { continue } - e := c.child(k0) - requiredResourcesPostgresProject(w, &e, x0) + e := c.Child(k) + requiredResourcesPostgresProject(w, &e, x) } } if len(v.PostgresRoles) > 0 { - c := n.child("postgres_roles") - for k0, x0 := range v.PostgresRoles { - if k0 == "" { + c := n.Child("postgres_roles") + for k, x := range v.PostgresRoles { + if k == "" { continue } - e := c.child(k0) - requiredResourcesPostgresRole(w, &e, x0) + e := c.Child(k) + requiredResourcesPostgresRole(w, &e, x) } } if len(v.PostgresSnapshotSchedules) > 0 { - c := n.child("postgres_snapshot_schedules") - for k0, x0 := range v.PostgresSnapshotSchedules { - if k0 == "" { + c := n.Child("postgres_snapshot_schedules") + for k, x := range v.PostgresSnapshotSchedules { + if k == "" { continue } - e := c.child(k0) - requiredResourcesPostgresSnapshotSchedule(w, &e, x0) + e := c.Child(k) + requiredResourcesPostgresSnapshotSchedule(w, &e, x) } } if len(v.PostgresSyncedTables) > 0 { - c := n.child("postgres_synced_tables") - for k0, x0 := range v.PostgresSyncedTables { - if k0 == "" { + c := n.Child("postgres_synced_tables") + for k, x := range v.PostgresSyncedTables { + if k == "" { continue } - e := c.child(k0) - requiredResourcesPostgresSyncedTable(w, &e, x0) + e := c.Child(k) + requiredResourcesPostgresSyncedTable(w, &e, x) } } if len(v.QualityMonitors) > 0 { - c := n.child("quality_monitors") - for k0, x0 := range v.QualityMonitors { - if k0 == "" { + c := n.Child("quality_monitors") + for k, x := range v.QualityMonitors { + if k == "" { continue } - e := c.child(k0) - requiredResourcesQualityMonitor(w, &e, x0) + e := c.Child(k) + requiredResourcesQualityMonitor(w, &e, x) } } if len(v.Schemas) > 0 { - c := n.child("schemas") - for k0, x0 := range v.Schemas { - if k0 == "" { + c := n.Child("schemas") + for k, x := range v.Schemas { + if k == "" { continue } - e := c.child(k0) - requiredResourcesSchema(w, &e, x0) + e := c.Child(k) + requiredResourcesSchema(w, &e, x) } } if len(v.SecretScopes) > 0 { - c := n.child("secret_scopes") - for k0, x0 := range v.SecretScopes { - if k0 == "" { + c := n.Child("secret_scopes") + for k, x := range v.SecretScopes { + if k == "" { continue } - e := c.child(k0) - requiredResourcesSecretScope(w, &e, x0) + e := c.Child(k) + requiredResourcesSecretScope(w, &e, x) } } if len(v.Secrets) > 0 { - c := n.child("secrets") - for k0, x0 := range v.Secrets { - if k0 == "" { + c := n.Child("secrets") + for k, x := range v.Secrets { + if k == "" { continue } - e := c.child(k0) - requiredResourcesSecret(w, &e, x0) + e := c.Child(k) + requiredResourcesSecret(w, &e, x) } } if len(v.SqlWarehouses) > 0 { - c := n.child("sql_warehouses") - for k0, x0 := range v.SqlWarehouses { - if k0 == "" { + c := n.Child("sql_warehouses") + for k, x := range v.SqlWarehouses { + if k == "" { continue } - e := c.child(k0) - requiredResourcesSqlWarehouse(w, &e, x0) + e := c.Child(k) + requiredResourcesSqlWarehouse(w, &e, x) } } if len(v.SyncedDatabaseTables) > 0 { - c := n.child("synced_database_tables") - for k0, x0 := range v.SyncedDatabaseTables { - if k0 == "" { + c := n.Child("synced_database_tables") + for k, x := range v.SyncedDatabaseTables { + if k == "" { continue } - e := c.child(k0) - requiredResourcesSyncedDatabaseTable(w, &e, x0) + e := c.Child(k) + requiredResourcesSyncedDatabaseTable(w, &e, x) } } if len(v.VectorSearchEndpoints) > 0 { - c := n.child("vector_search_endpoints") - for k0, x0 := range v.VectorSearchEndpoints { - if k0 == "" { + c := n.Child("vector_search_endpoints") + for k, x := range v.VectorSearchEndpoints { + if k == "" { continue } - e := c.child(k0) - requiredResourcesVectorSearchEndpoint(w, &e, x0) + e := c.Child(k) + requiredResourcesVectorSearchEndpoint(w, &e, x) } } if len(v.VectorSearchIndexes) > 0 { - c := n.child("vector_search_indexes") - for k0, x0 := range v.VectorSearchIndexes { - if k0 == "" { + c := n.Child("vector_search_indexes") + for k, x := range v.VectorSearchIndexes { + if k == "" { continue } - e := c.child(k0) - requiredResourcesVectorSearchIndex(w, &e, x0) + e := c.Child(k) + requiredResourcesVectorSearchIndex(w, &e, x) } } if len(v.Volumes) > 0 { - c := n.child("volumes") - for k0, x0 := range v.Volumes { - if k0 == "" { + c := n.Child("volumes") + for k, x := range v.Volumes { + if k == "" { continue } - e := c.child(k0) - requiredResourcesVolume(w, &e, x0) + e := c.Child(k) + requiredResourcesVolume(w, &e, x) } } } -func requiredConfigScript(w *walker, n *node, v *config.Script) { +func requiredConfigScript(w *walker, n *walk.Node, v *config.Script) { if v == nil { - v = &config.Script{} + w.missing(n, "content") + return } - if !(v.Content != "") && !n.has("content") { + if !(v.Content != "") { w.missing(n, "content") } } -func requiredConfigArtifactFile(w *walker, n *node, v *config.ArtifactFile) { +func requiredConfigArtifactFile(w *walker, n *walk.Node, v *config.ArtifactFile) { if v == nil { - v = &config.ArtifactFile{} + w.missing(n, "source") + return } - if !(v.Source != "") && !n.has("source") { + if !(v.Source != "") { w.missing(n, "source") } } -func requiredResourcesAlert(w *walker, n *node, v *resources.Alert) { +func requiredResourcesAlert(w *walker, n *walk.Node, v *resources.Alert) { if v == nil { - v = &resources.Alert{} + w.missing(n, "display_name") + w.missing(n, "evaluation") + w.missing(n, "query_text") + w.missing(n, "schedule") + w.missing(n, "warehouse_id") + return } - if !(v.AlertV2.DisplayName != "") && !n.has("display_name") { + if !(v.AlertV2.DisplayName != "" || walk.Forced(v.AlertV2.ForceSendFields, "DisplayName")) { w.missing(n, "display_name") } - if !n.has("evaluation") { + if !(!walk.IsZeroStruct(&v.AlertV2.Evaluation) || walk.Forced(v.AlertV2.ForceSendFields, "Evaluation") || n.HasRefs("evaluation")) { w.missing(n, "evaluation") } - if !(v.AlertV2.QueryText != "") && !n.has("query_text") { + if !(v.AlertV2.QueryText != "" || walk.Forced(v.AlertV2.ForceSendFields, "QueryText")) { w.missing(n, "query_text") } - if !n.has("schedule") { + if !(!walk.IsZeroStruct(&v.AlertV2.Schedule) || walk.Forced(v.AlertV2.ForceSendFields, "Schedule") || n.HasRefs("schedule")) { w.missing(n, "schedule") } - if !(v.AlertV2.WarehouseId != "") && !n.has("warehouse_id") { + if !(v.AlertV2.WarehouseId != "" || walk.Forced(v.AlertV2.ForceSendFields, "WarehouseId")) { w.missing(n, "warehouse_id") } - if c := n.child("evaluation"); c.exists() { - requiredSqlAlertV2Evaluation(w, &c, &v.AlertV2.Evaluation) + if x := &v.AlertV2.Evaluation; !walk.IsZeroStruct(x) || walk.Forced(v.AlertV2.ForceSendFields, "Evaluation") || n.HasRefs("evaluation") { + c := n.Child("evaluation") + requiredSqlAlertV2Evaluation(w, &c, x) } if len(v.AlertV2.Parameters) > 0 { - c := n.child("parameters") - for i0 := range v.AlertV2.Parameters { - e := c.elem(i0) - requiredSqlAlertStatementParameter(w, &e, &v.AlertV2.Parameters[i0]) + c := n.Child("parameters") + for i := range v.AlertV2.Parameters { + e := c.Elem(i) + requiredSqlAlertStatementParameter(w, &e, &v.AlertV2.Parameters[i]) } } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesPermission(w, &e, &v.Permissions[i]) } } - if c := n.child("schedule"); c.exists() { - requiredSqlCronSchedule(w, &c, &v.AlertV2.Schedule) + if x := &v.AlertV2.Schedule; !walk.IsZeroStruct(x) || walk.Forced(v.AlertV2.ForceSendFields, "Schedule") || n.HasRefs("schedule") { + c := n.Child("schedule") + requiredSqlCronSchedule(w, &c, x) } } -func requiredResourcesApp(w *walker, n *node, v *resources.App) { +func requiredResourcesApp(w *walker, n *walk.Node, v *resources.App) { if v == nil { - v = &resources.App{} - } - if !(v.App.Name != "") && !n.has("name") { w.missing(n, "name") + return } - if c := n.child("active_deployment"); c.exists() { - requiredAppsAppDeployment(w, &c, v.App.ActiveDeployment) - } - if c := n.child("config"); c.exists() { - requiredResourcesAppConfig(w, &c, v.Config) - } - if c := n.child("default_git_source"); c.exists() { - requiredAppsGitSource(w, &c, v.App.DefaultGitSource) - } - if c := n.child("git_repository"); c.exists() { - requiredAppsGitRepository(w, &c, v.App.GitRepository) - } - if c := n.child("git_source"); c.exists() { - requiredAppsGitSource(w, &c, v.App.GitSource) + if !(v.App.Name != "" || walk.Forced(v.App.ForceSendFields, "Name")) { + w.missing(n, "name") } - if c := n.child("pending_deployment"); c.exists() { - requiredAppsAppDeployment(w, &c, v.App.PendingDeployment) + if x := v.App.ActiveDeployment; x != nil { + c := n.Child("active_deployment") + requiredAppsAppDeployment(w, &c, x) + } else if walk.Forced(v.App.ForceSendFields, "ActiveDeployment") || n.IsRef("active_deployment") { + c := n.Child("active_deployment") + requiredAppsAppDeployment(w, &c, nil) + } + if x := v.Config; x != nil { + c := n.Child("config") + requiredResourcesAppConfig(w, &c, x) + } else if n.IsRef("config") { + c := n.Child("config") + requiredResourcesAppConfig(w, &c, nil) + } + if x := v.App.DefaultGitSource; x != nil { + c := n.Child("default_git_source") + requiredAppsGitSource(w, &c, x) + } else if walk.Forced(v.App.ForceSendFields, "DefaultGitSource") || n.IsRef("default_git_source") { + c := n.Child("default_git_source") + requiredAppsGitSource(w, &c, nil) + } + if x := v.App.GitRepository; x != nil { + c := n.Child("git_repository") + requiredAppsGitRepository(w, &c, x) + } else if walk.Forced(v.App.ForceSendFields, "GitRepository") || n.IsRef("git_repository") { + c := n.Child("git_repository") + requiredAppsGitRepository(w, &c, nil) + } + if x := v.App.GitSource; x != nil { + c := n.Child("git_source") + requiredAppsGitSource(w, &c, x) + } else if walk.Forced(v.App.ForceSendFields, "GitSource") || n.IsRef("git_source") { + c := n.Child("git_source") + requiredAppsGitSource(w, &c, nil) + } + if x := v.App.PendingDeployment; x != nil { + c := n.Child("pending_deployment") + requiredAppsAppDeployment(w, &c, x) + } else if walk.Forced(v.App.ForceSendFields, "PendingDeployment") || n.IsRef("pending_deployment") { + c := n.Child("pending_deployment") + requiredAppsAppDeployment(w, &c, nil) } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesAppPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesAppPermission(w, &e, &v.Permissions[i]) } } if len(v.App.Resources) > 0 { - c := n.child("resources") - for i0 := range v.App.Resources { - e := c.elem(i0) - requiredAppsAppResource(w, &e, &v.App.Resources[i0]) + c := n.Child("resources") + for i := range v.App.Resources { + e := c.Elem(i) + requiredAppsAppResource(w, &e, &v.App.Resources[i]) } } if len(v.App.TelemetryExportDestinations) > 0 { - c := n.child("telemetry_export_destinations") - for i0 := range v.App.TelemetryExportDestinations { - e := c.elem(i0) - requiredAppsTelemetryExportDestination(w, &e, &v.App.TelemetryExportDestinations[i0]) + c := n.Child("telemetry_export_destinations") + for i := range v.App.TelemetryExportDestinations { + e := c.Elem(i) + requiredAppsTelemetryExportDestination(w, &e, &v.App.TelemetryExportDestinations[i]) } } } -func requiredResourcesCatalog(w *walker, n *node, v *resources.Catalog) { +func requiredResourcesCatalog(w *walker, n *walk.Node, v *resources.Catalog) { if v == nil { - v = &resources.Catalog{} + w.missing(n, "name") + return } - if !(v.CreateCatalog.Name != "") && !n.has("name") { + if !(v.CreateCatalog.Name != "" || walk.Forced(v.CreateCatalog.ForceSendFields, "Name")) { w.missing(n, "name") } - if c := n.child("managed_encryption_settings"); c.exists() { - requiredCatalogEncryptionSettings(w, &c, v.CreateCatalog.ManagedEncryptionSettings) + if x := v.CreateCatalog.ManagedEncryptionSettings; x != nil { + c := n.Child("managed_encryption_settings") + requiredCatalogEncryptionSettings(w, &c, x) + } else if walk.Forced(v.CreateCatalog.ForceSendFields, "ManagedEncryptionSettings") || n.IsRef("managed_encryption_settings") { + c := n.Child("managed_encryption_settings") + requiredCatalogEncryptionSettings(w, &c, nil) } } -func requiredResourcesClusterPolicy(w *walker, n *node, v *resources.ClusterPolicy) { +func requiredResourcesClusterPolicy(w *walker, n *walk.Node, v *resources.ClusterPolicy) { if v == nil { - v = &resources.ClusterPolicy{} + return } if len(v.CreatePolicy.Libraries) > 0 { - c := n.child("libraries") - for i0 := range v.CreatePolicy.Libraries { - e := c.elem(i0) - requiredComputeLibrary(w, &e, &v.CreatePolicy.Libraries[i0]) + c := n.Child("libraries") + for i := range v.CreatePolicy.Libraries { + e := c.Elem(i) + requiredComputeLibrary(w, &e, &v.CreatePolicy.Libraries[i]) } } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesClusterPolicyPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesClusterPolicyPermission(w, &e, &v.Permissions[i]) } } } -func requiredResourcesCluster(w *walker, n *node, v *resources.Cluster) { +func requiredResourcesCluster(w *walker, n *walk.Node, v *resources.Cluster) { if v == nil { - v = &resources.Cluster{} + return } - if c := n.child("cluster_log_conf"); c.exists() { - requiredComputeClusterLogConf(w, &c, v.ClusterSpec.ClusterLogConf) + if x := v.ClusterSpec.ClusterLogConf; x != nil { + c := n.Child("cluster_log_conf") + requiredComputeClusterLogConf(w, &c, x) + } else if walk.Forced(v.ClusterSpec.ForceSendFields, "ClusterLogConf") || n.IsRef("cluster_log_conf") { + c := n.Child("cluster_log_conf") + requiredComputeClusterLogConf(w, &c, nil) } if len(v.ClusterSpec.InitScripts) > 0 { - c := n.child("init_scripts") - for i0 := range v.ClusterSpec.InitScripts { - e := c.elem(i0) - requiredComputeInitScriptInfo(w, &e, &v.ClusterSpec.InitScripts[i0]) + c := n.Child("init_scripts") + for i := range v.ClusterSpec.InitScripts { + e := c.Elem(i) + requiredComputeInitScriptInfo(w, &e, &v.ClusterSpec.InitScripts[i]) } } if len(v.Libraries) > 0 { - c := n.child("libraries") - for i0 := range v.Libraries { - e := c.elem(i0) - requiredComputeLibrary(w, &e, &v.Libraries[i0]) + c := n.Child("libraries") + for i := range v.Libraries { + e := c.Elem(i) + requiredComputeLibrary(w, &e, &v.Libraries[i]) } } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesClusterPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesClusterPermission(w, &e, &v.Permissions[i]) } } - if c := n.child("workload_type"); c.exists() { - requiredComputeWorkloadType(w, &c, v.ClusterSpec.WorkloadType) + if x := v.ClusterSpec.WorkloadType; x != nil { + c := n.Child("workload_type") + requiredComputeWorkloadType(w, &c, x) + } else if walk.Forced(v.ClusterSpec.ForceSendFields, "WorkloadType") || n.IsRef("workload_type") { + c := n.Child("workload_type") + requiredComputeWorkloadType(w, &c, nil) } } -func requiredResourcesDashboard(w *walker, n *node, v *resources.Dashboard) { +func requiredResourcesDashboard(w *walker, n *walk.Node, v *resources.Dashboard) { if v == nil { - v = &resources.Dashboard{} + return } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesPermission(w, &e, &v.Permissions[i]) } } } -func requiredResourcesDatabaseCatalog(w *walker, n *node, v *resources.DatabaseCatalog) { +func requiredResourcesDatabaseCatalog(w *walker, n *walk.Node, v *resources.DatabaseCatalog) { if v == nil { - v = &resources.DatabaseCatalog{} + w.missing(n, "database_instance_name") + w.missing(n, "database_name") + w.missing(n, "name") + return } - if !(v.DatabaseCatalog.DatabaseInstanceName != "") && !n.has("database_instance_name") { + if !(v.DatabaseCatalog.DatabaseInstanceName != "" || walk.Forced(v.DatabaseCatalog.ForceSendFields, "DatabaseInstanceName")) { w.missing(n, "database_instance_name") } - if !(v.DatabaseCatalog.DatabaseName != "") && !n.has("database_name") { + if !(v.DatabaseCatalog.DatabaseName != "" || walk.Forced(v.DatabaseCatalog.ForceSendFields, "DatabaseName")) { w.missing(n, "database_name") } - if !(v.DatabaseCatalog.Name != "") && !n.has("name") { + if !(v.DatabaseCatalog.Name != "" || walk.Forced(v.DatabaseCatalog.ForceSendFields, "Name")) { w.missing(n, "name") } } -func requiredResourcesDatabaseInstance(w *walker, n *node, v *resources.DatabaseInstance) { +func requiredResourcesDatabaseInstance(w *walker, n *walk.Node, v *resources.DatabaseInstance) { if v == nil { - v = &resources.DatabaseInstance{} + w.missing(n, "name") + return } - if !(v.DatabaseInstance.Name != "") && !n.has("name") { + if !(v.DatabaseInstance.Name != "" || walk.Forced(v.DatabaseInstance.ForceSendFields, "Name")) { w.missing(n, "name") } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesPermission(w, &e, &v.Permissions[i]) } } } -func requiredResourcesMlflowExperiment(w *walker, n *node, v *resources.MlflowExperiment) { +func requiredResourcesMlflowExperiment(w *walker, n *walk.Node, v *resources.MlflowExperiment) { if v == nil { - v = &resources.MlflowExperiment{} + w.missing(n, "name") + return } - if !(v.CreateExperiment.Name != "") && !n.has("name") { + if !(v.CreateExperiment.Name != "" || walk.Forced(v.CreateExperiment.ForceSendFields, "Name")) { w.missing(n, "name") } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesMlflowExperimentPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesMlflowExperimentPermission(w, &e, &v.Permissions[i]) } } - if c := n.child("trace_location"); c.exists() { - requiredMlExperimentTraceLocation(w, &c, v.CreateExperiment.TraceLocation) + if x := v.CreateExperiment.TraceLocation; x != nil { + c := n.Child("trace_location") + requiredMlExperimentTraceLocation(w, &c, x) + } else if walk.Forced(v.CreateExperiment.ForceSendFields, "TraceLocation") || n.IsRef("trace_location") { + c := n.Child("trace_location") + requiredMlExperimentTraceLocation(w, &c, nil) } } -func requiredResourcesExternalLocation(w *walker, n *node, v *resources.ExternalLocation) { +func requiredResourcesExternalLocation(w *walker, n *walk.Node, v *resources.ExternalLocation) { if v == nil { - v = &resources.ExternalLocation{} + w.missing(n, "credential_name") + w.missing(n, "name") + w.missing(n, "url") + return } - if !(v.CreateExternalLocation.CredentialName != "") && !n.has("credential_name") { + if !(v.CreateExternalLocation.CredentialName != "" || walk.Forced(v.CreateExternalLocation.ForceSendFields, "CredentialName")) { w.missing(n, "credential_name") } - if !(v.CreateExternalLocation.Name != "") && !n.has("name") { + if !(v.CreateExternalLocation.Name != "" || walk.Forced(v.CreateExternalLocation.ForceSendFields, "Name")) { w.missing(n, "name") } - if !(v.CreateExternalLocation.Url != "") && !n.has("url") { + if !(v.CreateExternalLocation.Url != "" || walk.Forced(v.CreateExternalLocation.ForceSendFields, "Url")) { w.missing(n, "url") } } -func requiredResourcesFeature(w *walker, n *node, v *resources.Feature) { +func requiredResourcesFeature(w *walker, n *walk.Node, v *resources.Feature) { if v == nil { - v = &resources.Feature{} + w.missing(n, "full_name") + w.missing(n, "function") + w.missing(n, "source") + return } - if !(v.Feature.FullName != "") && !n.has("full_name") { + if !(v.Feature.FullName != "" || walk.Forced(v.Feature.ForceSendFields, "FullName")) { w.missing(n, "full_name") } - if !n.has("function") { + if !(!walk.IsZeroStruct(&v.Feature.Function) || walk.Forced(v.Feature.ForceSendFields, "Function") || n.HasRefs("function")) { w.missing(n, "function") } - if !n.has("source") { + if !(!walk.IsZeroStruct(&v.Feature.Source) || walk.Forced(v.Feature.ForceSendFields, "Source") || n.HasRefs("source")) { w.missing(n, "source") } if len(v.Feature.Entities) > 0 { - c := n.child("entities") - for i0 := range v.Feature.Entities { - e := c.elem(i0) - requiredMlEntityColumn(w, &e, &v.Feature.Entities[i0]) + c := n.Child("entities") + for i := range v.Feature.Entities { + e := c.Elem(i) + requiredMlEntityColumn(w, &e, &v.Feature.Entities[i]) } } - if c := n.child("function"); c.exists() { - requiredMlFunction(w, &c, &v.Feature.Function) + if x := &v.Feature.Function; !walk.IsZeroStruct(x) || walk.Forced(v.Feature.ForceSendFields, "Function") || n.HasRefs("function") { + c := n.Child("function") + requiredMlFunction(w, &c, x) } - if c := n.child("source"); c.exists() { - requiredMlDataSource(w, &c, &v.Feature.Source) + if x := &v.Feature.Source; !walk.IsZeroStruct(x) || walk.Forced(v.Feature.ForceSendFields, "Source") || n.HasRefs("source") { + c := n.Child("source") + requiredMlDataSource(w, &c, x) } - if c := n.child("time_window"); c.exists() { - requiredMlTimeWindow(w, &c, v.Feature.TimeWindow) + if x := v.Feature.TimeWindow; x != nil { + c := n.Child("time_window") + requiredMlTimeWindow(w, &c, x) + } else if walk.Forced(v.Feature.ForceSendFields, "TimeWindow") || n.IsRef("time_window") { + c := n.Child("time_window") + requiredMlTimeWindow(w, &c, nil) } - if c := n.child("timeseries_column"); c.exists() { - requiredMlTimeseriesColumn(w, &c, v.Feature.TimeseriesColumn) + if x := v.Feature.TimeseriesColumn; x != nil { + c := n.Child("timeseries_column") + requiredMlTimeseriesColumn(w, &c, x) + } else if walk.Forced(v.Feature.ForceSendFields, "TimeseriesColumn") || n.IsRef("timeseries_column") { + c := n.Child("timeseries_column") + requiredMlTimeseriesColumn(w, &c, nil) } } -func requiredResourcesGenieSpace(w *walker, n *node, v *resources.GenieSpace) { +func requiredResourcesGenieSpace(w *walker, n *walk.Node, v *resources.GenieSpace) { if v == nil { - v = &resources.GenieSpace{} + return } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesPermission(w, &e, &v.Permissions[i]) } } } -func requiredResourcesInstancePool(w *walker, n *node, v *resources.InstancePool) { +func requiredResourcesInstancePool(w *walker, n *walk.Node, v *resources.InstancePool) { if v == nil { - v = &resources.InstancePool{} + w.missing(n, "instance_pool_name") + w.missing(n, "node_type_id") + return } - if !(v.CreateInstancePool.InstancePoolName != "") && !n.has("instance_pool_name") { + if !(v.CreateInstancePool.InstancePoolName != "" || walk.Forced(v.CreateInstancePool.ForceSendFields, "InstancePoolName")) { w.missing(n, "instance_pool_name") } - if !(v.CreateInstancePool.NodeTypeId != "") && !n.has("node_type_id") { + if !(v.CreateInstancePool.NodeTypeId != "" || walk.Forced(v.CreateInstancePool.ForceSendFields, "NodeTypeId")) { w.missing(n, "node_type_id") } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesInstancePoolPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesInstancePoolPermission(w, &e, &v.Permissions[i]) } } } -func requiredResourcesJobRun(w *walker, n *node, v *resources.JobRun) { +func requiredResourcesJobRun(w *walker, n *walk.Node, v *resources.JobRun) { if v == nil { - v = &resources.JobRun{} + w.missing(n, "job_id") + return } - if !(v.RunNow.JobId != 0) && !n.has("job_id") { + if !(v.RunNow.JobId != 0 || walk.Forced(v.RunNow.ForceSendFields, "JobId") || n.IsRef("job_id")) { w.missing(n, "job_id") } - if c := n.child("queue"); c.exists() { - requiredJobsQueueSettings(w, &c, v.RunNow.Queue) + if x := v.RunNow.Queue; x != nil { + c := n.Child("queue") + requiredJobsQueueSettings(w, &c, x) + } else if walk.Forced(v.RunNow.ForceSendFields, "Queue") || n.IsRef("queue") { + c := n.Child("queue") + requiredJobsQueueSettings(w, &c, nil) } } -func requiredResourcesJob(w *walker, n *node, v *resources.Job) { +func requiredResourcesJob(w *walker, n *walk.Node, v *resources.Job) { if v == nil { - v = &resources.Job{} + return } - if c := n.child("continuous"); c.exists() { - requiredJobsContinuous(w, &c, v.JobSettings.Continuous) + if x := v.JobSettings.Continuous; x != nil { + c := n.Child("continuous") + requiredJobsContinuous(w, &c, x) + } else if walk.Forced(v.JobSettings.ForceSendFields, "Continuous") || n.IsRef("continuous") { + c := n.Child("continuous") + requiredJobsContinuous(w, &c, nil) } - if c := n.child("deployment"); c.exists() { - requiredJobsJobDeployment(w, &c, v.JobSettings.Deployment) + if x := v.JobSettings.Deployment; x != nil { + c := n.Child("deployment") + requiredJobsJobDeployment(w, &c, x) + } else if walk.Forced(v.JobSettings.ForceSendFields, "Deployment") || n.IsRef("deployment") { + c := n.Child("deployment") + requiredJobsJobDeployment(w, &c, nil) } if len(v.JobSettings.EnvironmentVariables) > 0 { - c := n.child("environment_variables") - for i0 := range v.JobSettings.EnvironmentVariables { - e := c.elem(i0) - requiredJobsJobEnvironmentVariables(w, &e, &v.JobSettings.EnvironmentVariables[i0]) + c := n.Child("environment_variables") + for i := range v.JobSettings.EnvironmentVariables { + e := c.Elem(i) + requiredJobsJobEnvironmentVariables(w, &e, &v.JobSettings.EnvironmentVariables[i]) } } if len(v.JobSettings.Environments) > 0 { - c := n.child("environments") - for i0 := range v.JobSettings.Environments { - e := c.elem(i0) - requiredJobsJobEnvironment(w, &e, &v.JobSettings.Environments[i0]) + c := n.Child("environments") + for i := range v.JobSettings.Environments { + e := c.Elem(i) + requiredJobsJobEnvironment(w, &e, &v.JobSettings.Environments[i]) } } - if c := n.child("git_source"); c.exists() { - requiredJobsGitSource(w, &c, v.JobSettings.GitSource) + if x := v.JobSettings.GitSource; x != nil { + c := n.Child("git_source") + requiredJobsGitSource(w, &c, x) + } else if walk.Forced(v.JobSettings.ForceSendFields, "GitSource") || n.IsRef("git_source") { + c := n.Child("git_source") + requiredJobsGitSource(w, &c, nil) } - if c := n.child("health"); c.exists() { - requiredJobsJobsHealthRules(w, &c, v.JobSettings.Health) + if x := v.JobSettings.Health; x != nil { + c := n.Child("health") + requiredJobsJobsHealthRules(w, &c, x) + } else if walk.Forced(v.JobSettings.ForceSendFields, "Health") || n.IsRef("health") { + c := n.Child("health") + requiredJobsJobsHealthRules(w, &c, nil) } if len(v.JobSettings.JobClusters) > 0 { - c := n.child("job_clusters") - for i0 := range v.JobSettings.JobClusters { - e := c.elem(i0) - requiredJobsJobCluster(w, &e, &v.JobSettings.JobClusters[i0]) + c := n.Child("job_clusters") + for i := range v.JobSettings.JobClusters { + e := c.Elem(i) + requiredJobsJobCluster(w, &e, &v.JobSettings.JobClusters[i]) } } if len(v.JobSettings.Parameters) > 0 { - c := n.child("parameters") - for i0 := range v.JobSettings.Parameters { - e := c.elem(i0) - requiredJobsJobParameterDefinition(w, &e, &v.JobSettings.Parameters[i0]) + c := n.Child("parameters") + for i := range v.JobSettings.Parameters { + e := c.Elem(i) + requiredJobsJobParameterDefinition(w, &e, &v.JobSettings.Parameters[i]) } } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesJobPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesJobPermission(w, &e, &v.Permissions[i]) } } - if c := n.child("queue"); c.exists() { - requiredJobsQueueSettings(w, &c, v.JobSettings.Queue) + if x := v.JobSettings.Queue; x != nil { + c := n.Child("queue") + requiredJobsQueueSettings(w, &c, x) + } else if walk.Forced(v.JobSettings.ForceSendFields, "Queue") || n.IsRef("queue") { + c := n.Child("queue") + requiredJobsQueueSettings(w, &c, nil) } - if c := n.child("schedule"); c.exists() { - requiredJobsCronSchedule(w, &c, v.JobSettings.Schedule) + if x := v.JobSettings.Schedule; x != nil { + c := n.Child("schedule") + requiredJobsCronSchedule(w, &c, x) + } else if walk.Forced(v.JobSettings.ForceSendFields, "Schedule") || n.IsRef("schedule") { + c := n.Child("schedule") + requiredJobsCronSchedule(w, &c, nil) } if len(v.JobSettings.Tasks) > 0 { - c := n.child("tasks") - for i0 := range v.JobSettings.Tasks { - e := c.elem(i0) - requiredJobsTask(w, &e, &v.JobSettings.Tasks[i0]) + c := n.Child("tasks") + for i := range v.JobSettings.Tasks { + e := c.Elem(i) + requiredJobsTask(w, &e, &v.JobSettings.Tasks[i]) } } - if c := n.child("trigger"); c.exists() { - requiredJobsTriggerSettings(w, &c, v.JobSettings.Trigger) + if x := v.JobSettings.Trigger; x != nil { + c := n.Child("trigger") + requiredJobsTriggerSettings(w, &c, x) + } else if walk.Forced(v.JobSettings.ForceSendFields, "Trigger") || n.IsRef("trigger") { + c := n.Child("trigger") + requiredJobsTriggerSettings(w, &c, nil) } if len(v.JobSettings.Triggers) > 0 { - c := n.child("triggers") - for i0 := range v.JobSettings.Triggers { - e := c.elem(i0) - requiredJobsTriggerConfiguration(w, &e, &v.JobSettings.Triggers[i0]) + c := n.Child("triggers") + for i := range v.JobSettings.Triggers { + e := c.Elem(i) + requiredJobsTriggerConfiguration(w, &e, &v.JobSettings.Triggers[i]) } } - if c := n.child("webhook_notifications"); c.exists() { - requiredJobsWebhookNotifications(w, &c, v.JobSettings.WebhookNotifications) + if x := v.JobSettings.WebhookNotifications; x != nil { + c := n.Child("webhook_notifications") + requiredJobsWebhookNotifications(w, &c, x) + } else if walk.Forced(v.JobSettings.ForceSendFields, "WebhookNotifications") || n.IsRef("webhook_notifications") { + c := n.Child("webhook_notifications") + requiredJobsWebhookNotifications(w, &c, nil) } } -func requiredResourcesMcpService(w *walker, n *node, v *resources.McpService) { +func requiredResourcesMcpService(w *walker, n *walk.Node, v *resources.McpService) { if v == nil { - v = &resources.McpService{} + w.missing(n, "parent") + w.missing(n, "mcp_service_id") + return } - if !(v.McpServiceConfig.Parent != "") && !n.has("parent") { + if !(v.McpServiceConfig.Parent != "" || walk.Forced(v.McpServiceConfig.ForceSendFields, "Parent")) { w.missing(n, "parent") } - if !(v.McpServiceConfig.McpServiceId != "") && !n.has("mcp_service_id") { + if !(v.McpServiceConfig.McpServiceId != "" || walk.Forced(v.McpServiceConfig.ForceSendFields, "McpServiceId")) { w.missing(n, "mcp_service_id") } - if c := n.child("config"); c.exists() { - requiredCatalogMcpServiceConfig(w, &c, v.McpServiceConfig.Config) + if x := v.McpServiceConfig.Config; x != nil { + c := n.Child("config") + requiredCatalogMcpServiceConfig(w, &c, x) + } else if walk.Forced(v.McpServiceConfig.ForceSendFields, "Config") || n.IsRef("config") { + c := n.Child("config") + requiredCatalogMcpServiceConfig(w, &c, nil) } } -func requiredResourcesModelProviderService(w *walker, n *node, v *resources.ModelProviderService) { +func requiredResourcesModelProviderService(w *walker, n *walk.Node, v *resources.ModelProviderService) { if v == nil { - v = &resources.ModelProviderService{} + w.missing(n, "parent") + w.missing(n, "model_provider_service_id") + return } - if !(v.ModelProviderServiceConfig.Parent != "") && !n.has("parent") { + if !(v.ModelProviderServiceConfig.Parent != "" || walk.Forced(v.ModelProviderServiceConfig.ForceSendFields, "Parent")) { w.missing(n, "parent") } - if !(v.ModelProviderServiceConfig.ModelProviderServiceId != "") && !n.has("model_provider_service_id") { + if !(v.ModelProviderServiceConfig.ModelProviderServiceId != "" || walk.Forced(v.ModelProviderServiceConfig.ForceSendFields, "ModelProviderServiceId")) { w.missing(n, "model_provider_service_id") } - if c := n.child("config"); c.exists() { - requiredCatalogModelProviderServiceConfig(w, &c, v.ModelProviderServiceConfig.Config) + if x := v.ModelProviderServiceConfig.Config; x != nil { + c := n.Child("config") + requiredCatalogModelProviderServiceConfig(w, &c, x) + } else if walk.Forced(v.ModelProviderServiceConfig.ForceSendFields, "Config") || n.IsRef("config") { + c := n.Child("config") + requiredCatalogModelProviderServiceConfig(w, &c, nil) } } -func requiredResourcesModelService(w *walker, n *node, v *resources.ModelService) { +func requiredResourcesModelService(w *walker, n *walk.Node, v *resources.ModelService) { if v == nil { - v = &resources.ModelService{} + w.missing(n, "parent") + w.missing(n, "model_service_id") + return } - if !(v.ModelServiceConfig.Parent != "") && !n.has("parent") { + if !(v.ModelServiceConfig.Parent != "" || walk.Forced(v.ModelServiceConfig.ForceSendFields, "Parent")) { w.missing(n, "parent") } - if !(v.ModelServiceConfig.ModelServiceId != "") && !n.has("model_service_id") { + if !(v.ModelServiceConfig.ModelServiceId != "" || walk.Forced(v.ModelServiceConfig.ForceSendFields, "ModelServiceId")) { w.missing(n, "model_service_id") } - if c := n.child("config"); c.exists() { - requiredCatalogModelServiceConfig(w, &c, v.ModelServiceConfig.Config) + if x := v.ModelServiceConfig.Config; x != nil { + c := n.Child("config") + requiredCatalogModelServiceConfig(w, &c, x) + } else if walk.Forced(v.ModelServiceConfig.ForceSendFields, "Config") || n.IsRef("config") { + c := n.Child("config") + requiredCatalogModelServiceConfig(w, &c, nil) } } -func requiredResourcesModelServingEndpoint(w *walker, n *node, v *resources.ModelServingEndpoint) { +func requiredResourcesModelServingEndpoint(w *walker, n *walk.Node, v *resources.ModelServingEndpoint) { if v == nil { - v = &resources.ModelServingEndpoint{} + w.missing(n, "name") + return } - if !(v.CreateServingEndpoint.Name != "") && !n.has("name") { + if !(v.CreateServingEndpoint.Name != "" || walk.Forced(v.CreateServingEndpoint.ForceSendFields, "Name")) { w.missing(n, "name") } - if c := n.child("ai_gateway"); c.exists() { - requiredServingAiGatewayConfig(w, &c, v.CreateServingEndpoint.AiGateway) + if x := v.CreateServingEndpoint.AiGateway; x != nil { + c := n.Child("ai_gateway") + requiredServingAiGatewayConfig(w, &c, x) + } else if walk.Forced(v.CreateServingEndpoint.ForceSendFields, "AiGateway") || n.IsRef("ai_gateway") { + c := n.Child("ai_gateway") + requiredServingAiGatewayConfig(w, &c, nil) } - if c := n.child("config"); c.exists() { - requiredServingEndpointCoreConfigInput(w, &c, v.CreateServingEndpoint.Config) + if x := v.CreateServingEndpoint.Config; x != nil { + c := n.Child("config") + requiredServingEndpointCoreConfigInput(w, &c, x) + } else if walk.Forced(v.CreateServingEndpoint.ForceSendFields, "Config") || n.IsRef("config") { + c := n.Child("config") + requiredServingEndpointCoreConfigInput(w, &c, nil) } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesModelServingEndpointPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesModelServingEndpointPermission(w, &e, &v.Permissions[i]) } } if len(v.CreateServingEndpoint.RateLimits) > 0 { - c := n.child("rate_limits") - for i0 := range v.CreateServingEndpoint.RateLimits { - e := c.elem(i0) - requiredServingRateLimit(w, &e, &v.CreateServingEndpoint.RateLimits[i0]) + c := n.Child("rate_limits") + for i := range v.CreateServingEndpoint.RateLimits { + e := c.Elem(i) + requiredServingRateLimit(w, &e, &v.CreateServingEndpoint.RateLimits[i]) } } if len(v.CreateServingEndpoint.Tags) > 0 { - c := n.child("tags") - for i0 := range v.CreateServingEndpoint.Tags { - e := c.elem(i0) - requiredServingEndpointTag(w, &e, &v.CreateServingEndpoint.Tags[i0]) + c := n.Child("tags") + for i := range v.CreateServingEndpoint.Tags { + e := c.Elem(i) + requiredServingEndpointTag(w, &e, &v.CreateServingEndpoint.Tags[i]) } } } -func requiredResourcesMlflowModel(w *walker, n *node, v *resources.MlflowModel) { +func requiredResourcesMlflowModel(w *walker, n *walk.Node, v *resources.MlflowModel) { if v == nil { - v = &resources.MlflowModel{} + w.missing(n, "name") + return } - if !(v.CreateModelRequest.Name != "") && !n.has("name") { + if !(v.CreateModelRequest.Name != "" || walk.Forced(v.CreateModelRequest.ForceSendFields, "Name")) { w.missing(n, "name") } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesMlflowModelPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesMlflowModelPermission(w, &e, &v.Permissions[i]) } } } -func requiredResourcesPipeline(w *walker, n *node, v *resources.Pipeline) { +func requiredResourcesPipeline(w *walker, n *walk.Node, v *resources.Pipeline) { if v == nil { - v = &resources.Pipeline{} + return } if len(v.CreatePipeline.Clusters) > 0 { - c := n.child("clusters") - for i0 := range v.CreatePipeline.Clusters { - e := c.elem(i0) - requiredPipelinesPipelineCluster(w, &e, &v.CreatePipeline.Clusters[i0]) + c := n.Child("clusters") + for i := range v.CreatePipeline.Clusters { + e := c.Elem(i) + requiredPipelinesPipelineCluster(w, &e, &v.CreatePipeline.Clusters[i]) } } - if c := n.child("deployment"); c.exists() { - requiredPipelinesPipelineDeployment(w, &c, v.CreatePipeline.Deployment) - } - if c := n.child("gateway_definition"); c.exists() { - requiredPipelinesIngestionGatewayPipelineDefinition(w, &c, v.CreatePipeline.GatewayDefinition) - } - if c := n.child("ingestion_definition"); c.exists() { - requiredPipelinesIngestionPipelineDefinition(w, &c, v.CreatePipeline.IngestionDefinition) + if x := v.CreatePipeline.Deployment; x != nil { + c := n.Child("deployment") + requiredPipelinesPipelineDeployment(w, &c, x) + } else if walk.Forced(v.CreatePipeline.ForceSendFields, "Deployment") || n.IsRef("deployment") { + c := n.Child("deployment") + requiredPipelinesPipelineDeployment(w, &c, nil) + } + if x := v.CreatePipeline.GatewayDefinition; x != nil { + c := n.Child("gateway_definition") + requiredPipelinesIngestionGatewayPipelineDefinition(w, &c, x) + } else if walk.Forced(v.CreatePipeline.ForceSendFields, "GatewayDefinition") || n.IsRef("gateway_definition") { + c := n.Child("gateway_definition") + requiredPipelinesIngestionGatewayPipelineDefinition(w, &c, nil) + } + if x := v.CreatePipeline.IngestionDefinition; x != nil { + c := n.Child("ingestion_definition") + requiredPipelinesIngestionPipelineDefinition(w, &c, x) + } else if walk.Forced(v.CreatePipeline.ForceSendFields, "IngestionDefinition") || n.IsRef("ingestion_definition") { + c := n.Child("ingestion_definition") + requiredPipelinesIngestionPipelineDefinition(w, &c, nil) } if len(v.CreatePipeline.Libraries) > 0 { - c := n.child("libraries") - for i0 := range v.CreatePipeline.Libraries { - e := c.elem(i0) - requiredPipelinesPipelineLibrary(w, &e, &v.CreatePipeline.Libraries[i0]) + c := n.Child("libraries") + for i := range v.CreatePipeline.Libraries { + e := c.Elem(i) + requiredPipelinesPipelineLibrary(w, &e, &v.CreatePipeline.Libraries[i]) } } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesPipelinePermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesPipelinePermission(w, &e, &v.Permissions[i]) } } - if c := n.child("restart_window"); c.exists() { - requiredPipelinesRestartWindow(w, &c, v.CreatePipeline.RestartWindow) + if x := v.CreatePipeline.RestartWindow; x != nil { + c := n.Child("restart_window") + requiredPipelinesRestartWindow(w, &c, x) + } else if walk.Forced(v.CreatePipeline.ForceSendFields, "RestartWindow") || n.IsRef("restart_window") { + c := n.Child("restart_window") + requiredPipelinesRestartWindow(w, &c, nil) } } -func requiredResourcesPostgresBranch(w *walker, n *node, v *resources.PostgresBranch) { +func requiredResourcesPostgresBranch(w *walker, n *walk.Node, v *resources.PostgresBranch) { if v == nil { - v = &resources.PostgresBranch{} + w.missing(n, "branch_id") + w.missing(n, "parent") + return } - if !(v.PostgresBranchConfig.BranchId != "") && !n.has("branch_id") { + if !(v.PostgresBranchConfig.BranchId != "" || walk.Forced(v.PostgresBranchConfig.ForceSendFields, "BranchId")) { w.missing(n, "branch_id") } - if !(v.PostgresBranchConfig.Parent != "") && !n.has("parent") { + if !(v.PostgresBranchConfig.Parent != "" || walk.Forced(v.PostgresBranchConfig.ForceSendFields, "Parent")) { w.missing(n, "parent") } } -func requiredResourcesPostgresCatalog(w *walker, n *node, v *resources.PostgresCatalog) { +func requiredResourcesPostgresCatalog(w *walker, n *walk.Node, v *resources.PostgresCatalog) { if v == nil { - v = &resources.PostgresCatalog{} + w.missing(n, "postgres_database") + w.missing(n, "catalog_id") + return } - if !(v.PostgresCatalogConfig.CatalogCatalogSpec.PostgresDatabase != "") && !n.has("postgres_database") { + if !(v.PostgresCatalogConfig.CatalogCatalogSpec.PostgresDatabase != "" || walk.Forced(v.PostgresCatalogConfig.CatalogCatalogSpec.ForceSendFields, "PostgresDatabase")) { w.missing(n, "postgres_database") } - if !(v.PostgresCatalogConfig.CatalogId != "") && !n.has("catalog_id") { + if !(v.PostgresCatalogConfig.CatalogId != "") { w.missing(n, "catalog_id") } } -func requiredResourcesPostgresDatabase(w *walker, n *node, v *resources.PostgresDatabase) { +func requiredResourcesPostgresDatabase(w *walker, n *walk.Node, v *resources.PostgresDatabase) { if v == nil { - v = &resources.PostgresDatabase{} + w.missing(n, "role") + w.missing(n, "database_id") + w.missing(n, "parent") + return } - if !(v.PostgresDatabaseConfig.DatabaseDatabaseSpec.Role != "") && !n.has("role") { + if !(v.PostgresDatabaseConfig.DatabaseDatabaseSpec.Role != "" || walk.Forced(v.PostgresDatabaseConfig.DatabaseDatabaseSpec.ForceSendFields, "Role")) { w.missing(n, "role") } - if !(v.PostgresDatabaseConfig.DatabaseId != "") && !n.has("database_id") { + if !(v.PostgresDatabaseConfig.DatabaseId != "") { w.missing(n, "database_id") } - if !(v.PostgresDatabaseConfig.Parent != "") && !n.has("parent") { + if !(v.PostgresDatabaseConfig.Parent != "") { w.missing(n, "parent") } } -func requiredResourcesPostgresEndpoint(w *walker, n *node, v *resources.PostgresEndpoint) { +func requiredResourcesPostgresEndpoint(w *walker, n *walk.Node, v *resources.PostgresEndpoint) { if v == nil { - v = &resources.PostgresEndpoint{} + w.missing(n, "endpoint_type") + w.missing(n, "endpoint_id") + w.missing(n, "parent") + return } - if !(v.PostgresEndpointConfig.EndpointSpec.EndpointType != "") && !n.has("endpoint_type") { + if !(v.PostgresEndpointConfig.EndpointSpec.EndpointType != "" || walk.Forced(v.PostgresEndpointConfig.EndpointSpec.ForceSendFields, "EndpointType")) { w.missing(n, "endpoint_type") } - if !(v.PostgresEndpointConfig.EndpointId != "") && !n.has("endpoint_id") { + if !(v.PostgresEndpointConfig.EndpointId != "") { w.missing(n, "endpoint_id") } - if !(v.PostgresEndpointConfig.Parent != "") && !n.has("parent") { + if !(v.PostgresEndpointConfig.Parent != "") { w.missing(n, "parent") } - if c := n.child("group"); c.exists() { - requiredPostgresEndpointGroupSpec(w, &c, v.PostgresEndpointConfig.EndpointSpec.Group) + if x := v.PostgresEndpointConfig.EndpointSpec.Group; x != nil { + c := n.Child("group") + requiredPostgresEndpointGroupSpec(w, &c, x) + } else if walk.Forced(v.PostgresEndpointConfig.EndpointSpec.ForceSendFields, "Group") || n.IsRef("group") { + c := n.Child("group") + requiredPostgresEndpointGroupSpec(w, &c, nil) } } -func requiredResourcesPostgresProject(w *walker, n *node, v *resources.PostgresProject) { +func requiredResourcesPostgresProject(w *walker, n *walk.Node, v *resources.PostgresProject) { if v == nil { - v = &resources.PostgresProject{} + w.missing(n, "project_id") + return } - if !(v.PostgresProjectConfig.ProjectId != "") && !n.has("project_id") { + if !(v.PostgresProjectConfig.ProjectId != "" || walk.Forced(v.PostgresProjectConfig.ForceSendFields, "ProjectId")) { w.missing(n, "project_id") } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesPermission(w, &e, &v.Permissions[i]) } } } -func requiredResourcesPostgresRole(w *walker, n *node, v *resources.PostgresRole) { +func requiredResourcesPostgresRole(w *walker, n *walk.Node, v *resources.PostgresRole) { if v == nil { - v = &resources.PostgresRole{} + w.missing(n, "role_id") + w.missing(n, "parent") + return } - if !(v.PostgresRoleConfig.RoleId != "") && !n.has("role_id") { + if !(v.PostgresRoleConfig.RoleId != "") { w.missing(n, "role_id") } - if !(v.PostgresRoleConfig.Parent != "") && !n.has("parent") { + if !(v.PostgresRoleConfig.Parent != "") { w.missing(n, "parent") } } -func requiredResourcesPostgresSnapshotSchedule(w *walker, n *node, v *resources.PostgresSnapshotSchedule) { +func requiredResourcesPostgresSnapshotSchedule(w *walker, n *walk.Node, v *resources.PostgresSnapshotSchedule) { if v == nil { - v = &resources.PostgresSnapshotSchedule{} + w.missing(n, "branch") + return } - if !(v.PostgresSnapshotScheduleConfig.Branch != "") && !n.has("branch") { + if !(v.PostgresSnapshotScheduleConfig.Branch != "" || walk.Forced(v.PostgresSnapshotScheduleConfig.ForceSendFields, "Branch")) { w.missing(n, "branch") } if len(v.PostgresSnapshotScheduleConfig.Schedule) > 0 { - c := n.child("schedule") - for i0 := range v.PostgresSnapshotScheduleConfig.Schedule { - e := c.elem(i0) - requiredPostgresScheduleCadence(w, &e, &v.PostgresSnapshotScheduleConfig.Schedule[i0]) + c := n.Child("schedule") + for i := range v.PostgresSnapshotScheduleConfig.Schedule { + e := c.Elem(i) + requiredPostgresScheduleCadence(w, &e, &v.PostgresSnapshotScheduleConfig.Schedule[i]) } } } -func requiredResourcesPostgresSyncedTable(w *walker, n *node, v *resources.PostgresSyncedTable) { +func requiredResourcesPostgresSyncedTable(w *walker, n *walk.Node, v *resources.PostgresSyncedTable) { if v == nil { - v = &resources.PostgresSyncedTable{} + w.missing(n, "synced_table_id") + return } - if !(v.PostgresSyncedTableConfig.SyncedTableId != "") && !n.has("synced_table_id") { + if !(v.PostgresSyncedTableConfig.SyncedTableId != "") { w.missing(n, "synced_table_id") } if len(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns) > 0 { - c := n.child("extra_columns") - for i0 := range v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns { - e := c.elem(i0) - requiredPostgresSyncedTableSyncedTableSpecExtraColumn(w, &e, &v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns[i0]) + c := n.Child("extra_columns") + for i := range v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns { + e := c.Elem(i) + requiredPostgresSyncedTableSyncedTableSpecExtraColumn(w, &e, &v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns[i]) } } if len(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides) > 0 { - c := n.child("type_overrides") - for i0 := range v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides { - e := c.elem(i0) - requiredPostgresSyncedTableSyncedTableSpecTypeOverride(w, &e, &v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides[i0]) + c := n.Child("type_overrides") + for i := range v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides { + e := c.Elem(i) + requiredPostgresSyncedTableSyncedTableSpecTypeOverride(w, &e, &v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides[i]) } } } -func requiredResourcesQualityMonitor(w *walker, n *node, v *resources.QualityMonitor) { +func requiredResourcesQualityMonitor(w *walker, n *walk.Node, v *resources.QualityMonitor) { if v == nil { - v = &resources.QualityMonitor{} + w.missing(n, "assets_dir") + w.missing(n, "output_schema_name") + w.missing(n, "table_name") + return } - if !(v.CreateMonitor.AssetsDir != "") && !n.has("assets_dir") { + if !(v.CreateMonitor.AssetsDir != "" || walk.Forced(v.CreateMonitor.ForceSendFields, "AssetsDir")) { w.missing(n, "assets_dir") } - if !(v.CreateMonitor.OutputSchemaName != "") && !n.has("output_schema_name") { + if !(v.CreateMonitor.OutputSchemaName != "" || walk.Forced(v.CreateMonitor.ForceSendFields, "OutputSchemaName")) { w.missing(n, "output_schema_name") } - if !(v.TableName != "") && !n.has("table_name") { + if !(v.TableName != "") { w.missing(n, "table_name") } if len(v.CreateMonitor.CustomMetrics) > 0 { - c := n.child("custom_metrics") - for i0 := range v.CreateMonitor.CustomMetrics { - e := c.elem(i0) - requiredCatalogMonitorMetric(w, &e, &v.CreateMonitor.CustomMetrics[i0]) + c := n.Child("custom_metrics") + for i := range v.CreateMonitor.CustomMetrics { + e := c.Elem(i) + requiredCatalogMonitorMetric(w, &e, &v.CreateMonitor.CustomMetrics[i]) } } - if c := n.child("inference_log"); c.exists() { - requiredCatalogMonitorInferenceLog(w, &c, v.CreateMonitor.InferenceLog) + if x := v.CreateMonitor.InferenceLog; x != nil { + c := n.Child("inference_log") + requiredCatalogMonitorInferenceLog(w, &c, x) + } else if walk.Forced(v.CreateMonitor.ForceSendFields, "InferenceLog") || n.IsRef("inference_log") { + c := n.Child("inference_log") + requiredCatalogMonitorInferenceLog(w, &c, nil) } - if c := n.child("schedule"); c.exists() { - requiredCatalogMonitorCronSchedule(w, &c, v.CreateMonitor.Schedule) + if x := v.CreateMonitor.Schedule; x != nil { + c := n.Child("schedule") + requiredCatalogMonitorCronSchedule(w, &c, x) + } else if walk.Forced(v.CreateMonitor.ForceSendFields, "Schedule") || n.IsRef("schedule") { + c := n.Child("schedule") + requiredCatalogMonitorCronSchedule(w, &c, nil) } - if c := n.child("time_series"); c.exists() { - requiredCatalogMonitorTimeSeries(w, &c, v.CreateMonitor.TimeSeries) + if x := v.CreateMonitor.TimeSeries; x != nil { + c := n.Child("time_series") + requiredCatalogMonitorTimeSeries(w, &c, x) + } else if walk.Forced(v.CreateMonitor.ForceSendFields, "TimeSeries") || n.IsRef("time_series") { + c := n.Child("time_series") + requiredCatalogMonitorTimeSeries(w, &c, nil) } } -func requiredResourcesSchema(w *walker, n *node, v *resources.Schema) { +func requiredResourcesSchema(w *walker, n *walk.Node, v *resources.Schema) { if v == nil { - v = &resources.Schema{} + w.missing(n, "catalog_name") + w.missing(n, "name") + return } - if !(v.CreateSchema.CatalogName != "") && !n.has("catalog_name") { + if !(v.CreateSchema.CatalogName != "" || walk.Forced(v.CreateSchema.ForceSendFields, "CatalogName")) { w.missing(n, "catalog_name") } - if !(v.CreateSchema.Name != "") && !n.has("name") { + if !(v.CreateSchema.Name != "" || walk.Forced(v.CreateSchema.ForceSendFields, "Name")) { w.missing(n, "name") } } -func requiredResourcesSecretScope(w *walker, n *node, v *resources.SecretScope) { +func requiredResourcesSecretScope(w *walker, n *walk.Node, v *resources.SecretScope) { if v == nil { - v = &resources.SecretScope{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } - if c := n.child("keyvault_metadata"); c.exists() { - requiredWorkspaceAzureKeyVaultSecretScopeMetadata(w, &c, v.KeyvaultMetadata) + if x := v.KeyvaultMetadata; x != nil { + c := n.Child("keyvault_metadata") + requiredWorkspaceAzureKeyVaultSecretScopeMetadata(w, &c, x) + } else if n.IsRef("keyvault_metadata") { + c := n.Child("keyvault_metadata") + requiredWorkspaceAzureKeyVaultSecretScopeMetadata(w, &c, nil) } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesSecretScopePermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesSecretScopePermission(w, &e, &v.Permissions[i]) } } } -func requiredResourcesSecret(w *walker, n *node, v *resources.Secret) { +func requiredResourcesSecret(w *walker, n *walk.Node, v *resources.Secret) { if v == nil { - v = &resources.Secret{} + w.missing(n, "catalog_name") + w.missing(n, "name") + w.missing(n, "schema_name") + w.missing(n, "value") + return } - if !(v.Secret.CatalogName != "") && !n.has("catalog_name") { + if !(v.Secret.CatalogName != "" || walk.Forced(v.Secret.ForceSendFields, "CatalogName")) { w.missing(n, "catalog_name") } - if !(v.Secret.Name != "") && !n.has("name") { + if !(v.Secret.Name != "" || walk.Forced(v.Secret.ForceSendFields, "Name")) { w.missing(n, "name") } - if !(v.Secret.SchemaName != "") && !n.has("schema_name") { + if !(v.Secret.SchemaName != "" || walk.Forced(v.Secret.ForceSendFields, "SchemaName")) { w.missing(n, "schema_name") } - if !(v.Secret.Value != "") && !n.has("value") { + if !(v.Secret.Value != "" || walk.Forced(v.Secret.ForceSendFields, "Value")) { w.missing(n, "value") } } -func requiredResourcesSqlWarehouse(w *walker, n *node, v *resources.SqlWarehouse) { +func requiredResourcesSqlWarehouse(w *walker, n *walk.Node, v *resources.SqlWarehouse) { if v == nil { - v = &resources.SqlWarehouse{} + return } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesSqlWarehousePermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesSqlWarehousePermission(w, &e, &v.Permissions[i]) } } } -func requiredResourcesSyncedDatabaseTable(w *walker, n *node, v *resources.SyncedDatabaseTable) { +func requiredResourcesSyncedDatabaseTable(w *walker, n *walk.Node, v *resources.SyncedDatabaseTable) { if v == nil { - v = &resources.SyncedDatabaseTable{} + w.missing(n, "name") + return } - if !(v.SyncedDatabaseTable.Name != "") && !n.has("name") { + if !(v.SyncedDatabaseTable.Name != "" || walk.Forced(v.SyncedDatabaseTable.ForceSendFields, "Name")) { w.missing(n, "name") } - if c := n.child("spec"); c.exists() { - requiredDatabaseSyncedTableSpec(w, &c, v.SyncedDatabaseTable.Spec) + if x := v.SyncedDatabaseTable.Spec; x != nil { + c := n.Child("spec") + requiredDatabaseSyncedTableSpec(w, &c, x) + } else if walk.Forced(v.SyncedDatabaseTable.ForceSendFields, "Spec") || n.IsRef("spec") { + c := n.Child("spec") + requiredDatabaseSyncedTableSpec(w, &c, nil) } } -func requiredResourcesVectorSearchEndpoint(w *walker, n *node, v *resources.VectorSearchEndpoint) { +func requiredResourcesVectorSearchEndpoint(w *walker, n *walk.Node, v *resources.VectorSearchEndpoint) { if v == nil { - v = &resources.VectorSearchEndpoint{} + w.missing(n, "endpoint_type") + w.missing(n, "name") + return } - if !(v.CreateEndpoint.EndpointType != "") && !n.has("endpoint_type") { + if !(v.CreateEndpoint.EndpointType != "" || walk.Forced(v.CreateEndpoint.ForceSendFields, "EndpointType")) { w.missing(n, "endpoint_type") } - if !(v.CreateEndpoint.Name != "") && !n.has("name") { + if !(v.CreateEndpoint.Name != "" || walk.Forced(v.CreateEndpoint.ForceSendFields, "Name")) { w.missing(n, "name") } if len(v.Permissions) > 0 { - c := n.child("permissions") - for i0 := range v.Permissions { - e := c.elem(i0) - requiredResourcesVectorSearchEndpointPermission(w, &e, &v.Permissions[i0]) + c := n.Child("permissions") + for i := range v.Permissions { + e := c.Elem(i) + requiredResourcesVectorSearchEndpointPermission(w, &e, &v.Permissions[i]) } } } -func requiredResourcesVectorSearchIndex(w *walker, n *node, v *resources.VectorSearchIndex) { +func requiredResourcesVectorSearchIndex(w *walker, n *walk.Node, v *resources.VectorSearchIndex) { if v == nil { - v = &resources.VectorSearchIndex{} + w.missing(n, "endpoint_name") + w.missing(n, "index_type") + w.missing(n, "name") + w.missing(n, "primary_key") + return } - if !(v.CreateVectorIndexRequest.EndpointName != "") && !n.has("endpoint_name") { + if !(v.CreateVectorIndexRequest.EndpointName != "") { w.missing(n, "endpoint_name") } - if !(v.CreateVectorIndexRequest.IndexType != "") && !n.has("index_type") { + if !(v.CreateVectorIndexRequest.IndexType != "") { w.missing(n, "index_type") } - if !(v.CreateVectorIndexRequest.Name != "") && !n.has("name") { + if !(v.CreateVectorIndexRequest.Name != "") { w.missing(n, "name") } - if !(v.CreateVectorIndexRequest.PrimaryKey != "") && !n.has("primary_key") { + if !(v.CreateVectorIndexRequest.PrimaryKey != "") { w.missing(n, "primary_key") } } -func requiredResourcesVolume(w *walker, n *node, v *resources.Volume) { +func requiredResourcesVolume(w *walker, n *walk.Node, v *resources.Volume) { if v == nil { - v = &resources.Volume{} + w.missing(n, "catalog_name") + w.missing(n, "name") + w.missing(n, "schema_name") + w.missing(n, "volume_type") + return } - if !(v.CreateVolumeRequestContent.CatalogName != "") && !n.has("catalog_name") { + if !(v.CreateVolumeRequestContent.CatalogName != "" || walk.Forced(v.CreateVolumeRequestContent.ForceSendFields, "CatalogName")) { w.missing(n, "catalog_name") } - if !(v.CreateVolumeRequestContent.Name != "") && !n.has("name") { + if !(v.CreateVolumeRequestContent.Name != "" || walk.Forced(v.CreateVolumeRequestContent.ForceSendFields, "Name")) { w.missing(n, "name") } - if !(v.CreateVolumeRequestContent.SchemaName != "") && !n.has("schema_name") { + if !(v.CreateVolumeRequestContent.SchemaName != "" || walk.Forced(v.CreateVolumeRequestContent.ForceSendFields, "SchemaName")) { w.missing(n, "schema_name") } - if !(v.CreateVolumeRequestContent.VolumeType != "") && !n.has("volume_type") { + if !(v.CreateVolumeRequestContent.VolumeType != "" || walk.Forced(v.CreateVolumeRequestContent.ForceSendFields, "VolumeType")) { w.missing(n, "volume_type") } } -func requiredSqlAlertV2Evaluation(w *walker, n *node, v *sql.AlertV2Evaluation) { +func requiredSqlAlertV2Evaluation(w *walker, n *walk.Node, v *sql.AlertV2Evaluation) { if v == nil { - v = &sql.AlertV2Evaluation{} + w.missing(n, "comparison_operator") + w.missing(n, "source") + return } - if !(v.ComparisonOperator != "") && !n.has("comparison_operator") { + if !(v.ComparisonOperator != "" || walk.Forced(v.ForceSendFields, "ComparisonOperator")) { w.missing(n, "comparison_operator") } - if !n.has("source") { + if !(!walk.IsZeroStruct(&v.Source) || walk.Forced(v.ForceSendFields, "Source") || n.HasRefs("source")) { w.missing(n, "source") } - if c := n.child("source"); c.exists() { - requiredSqlAlertV2OperandColumn(w, &c, &v.Source) + if x := &v.Source; !walk.IsZeroStruct(x) || walk.Forced(v.ForceSendFields, "Source") || n.HasRefs("source") { + c := n.Child("source") + requiredSqlAlertV2OperandColumn(w, &c, x) } - if c := n.child("threshold"); c.exists() { - requiredSqlAlertV2Operand(w, &c, v.Threshold) + if x := v.Threshold; x != nil { + c := n.Child("threshold") + requiredSqlAlertV2Operand(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Threshold") || n.IsRef("threshold") { + c := n.Child("threshold") + requiredSqlAlertV2Operand(w, &c, nil) } } -func requiredSqlAlertStatementParameter(w *walker, n *node, v *sql.AlertStatementParameter) { +func requiredSqlAlertStatementParameter(w *walker, n *walk.Node, v *sql.AlertStatementParameter) { if v == nil { - v = &sql.AlertStatementParameter{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "" || walk.Forced(v.ForceSendFields, "Name")) { w.missing(n, "name") } } -func requiredSqlCronSchedule(w *walker, n *node, v *sql.CronSchedule) { +func requiredSqlCronSchedule(w *walker, n *walk.Node, v *sql.CronSchedule) { if v == nil { - v = &sql.CronSchedule{} + w.missing(n, "quartz_cron_schedule") + w.missing(n, "timezone_id") + return } - if !(v.QuartzCronSchedule != "") && !n.has("quartz_cron_schedule") { + if !(v.QuartzCronSchedule != "") { w.missing(n, "quartz_cron_schedule") } - if !(v.TimezoneId != "") && !n.has("timezone_id") { + if !(v.TimezoneId != "") { w.missing(n, "timezone_id") } } -func requiredAppsAppDeployment(w *walker, n *node, v *apps.AppDeployment) { +func requiredAppsAppDeployment(w *walker, n *walk.Node, v *apps.AppDeployment) { if v == nil { - v = &apps.AppDeployment{} + return } - if c := n.child("git_source"); c.exists() { - requiredAppsGitSource(w, &c, v.GitSource) + if x := v.GitSource; x != nil { + c := n.Child("git_source") + requiredAppsGitSource(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "GitSource") || n.IsRef("git_source") { + c := n.Child("git_source") + requiredAppsGitSource(w, &c, nil) } } -func requiredResourcesAppConfig(w *walker, n *node, v *resources.AppConfig) { +func requiredResourcesAppConfig(w *walker, n *walk.Node, v *resources.AppConfig) { if v == nil { - v = &resources.AppConfig{} + return } if len(v.Env) > 0 { - c := n.child("env") - for i0 := range v.Env { - e := c.elem(i0) - requiredResourcesAppEnvVar(w, &e, &v.Env[i0]) + c := n.Child("env") + for i := range v.Env { + e := c.Elem(i) + requiredResourcesAppEnvVar(w, &e, &v.Env[i]) } } } -func requiredAppsGitSource(w *walker, n *node, v *apps.GitSource) { +func requiredAppsGitSource(w *walker, n *walk.Node, v *apps.GitSource) { if v == nil { - v = &apps.GitSource{} + return } - if c := n.child("git_repository"); c.exists() { - requiredAppsGitRepository(w, &c, v.GitRepository) + if x := v.GitRepository; x != nil { + c := n.Child("git_repository") + requiredAppsGitRepository(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "GitRepository") || n.IsRef("git_repository") { + c := n.Child("git_repository") + requiredAppsGitRepository(w, &c, nil) } } -func requiredAppsGitRepository(w *walker, n *node, v *apps.GitRepository) { +func requiredAppsGitRepository(w *walker, n *walk.Node, v *apps.GitRepository) { if v == nil { - v = &apps.GitRepository{} + w.missing(n, "provider") + w.missing(n, "url") + return } - if !(v.Provider != "") && !n.has("provider") { + if !(v.Provider != "" || walk.Forced(v.ForceSendFields, "Provider")) { w.missing(n, "provider") } - if !(v.Url != "") && !n.has("url") { + if !(v.Url != "" || walk.Forced(v.ForceSendFields, "Url")) { w.missing(n, "url") } } -func requiredResourcesAppPermission(w *walker, n *node, v *resources.AppPermission) { +func requiredResourcesAppPermission(w *walker, n *walk.Node, v *resources.AppPermission) { if v == nil { - v = &resources.AppPermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredAppsAppResource(w *walker, n *node, v *apps.AppResource) { +func requiredAppsAppResource(w *walker, n *walk.Node, v *apps.AppResource) { if v == nil { - v = &apps.AppResource{} - } - if !(v.Name != "") && !n.has("name") { w.missing(n, "name") + return } - if c := n.child("database"); c.exists() { - requiredAppsAppResourceDatabase(w, &c, v.Database) + if !(v.Name != "" || walk.Forced(v.ForceSendFields, "Name")) { + w.missing(n, "name") } - if c := n.child("experiment"); c.exists() { - requiredAppsAppResourceExperiment(w, &c, v.Experiment) + if x := v.Database; x != nil { + c := n.Child("database") + requiredAppsAppResourceDatabase(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Database") || n.IsRef("database") { + c := n.Child("database") + requiredAppsAppResourceDatabase(w, &c, nil) + } + if x := v.Experiment; x != nil { + c := n.Child("experiment") + requiredAppsAppResourceExperiment(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Experiment") || n.IsRef("experiment") { + c := n.Child("experiment") + requiredAppsAppResourceExperiment(w, &c, nil) } - if c := n.child("genie_space"); c.exists() { - requiredAppsAppResourceGenieSpace(w, &c, v.GenieSpace) + if x := v.GenieSpace; x != nil { + c := n.Child("genie_space") + requiredAppsAppResourceGenieSpace(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "GenieSpace") || n.IsRef("genie_space") { + c := n.Child("genie_space") + requiredAppsAppResourceGenieSpace(w, &c, nil) } - if c := n.child("job"); c.exists() { - requiredAppsAppResourceJob(w, &c, v.Job) + if x := v.Job; x != nil { + c := n.Child("job") + requiredAppsAppResourceJob(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Job") || n.IsRef("job") { + c := n.Child("job") + requiredAppsAppResourceJob(w, &c, nil) } - if c := n.child("secret"); c.exists() { - requiredAppsAppResourceSecret(w, &c, v.Secret) + if x := v.Secret; x != nil { + c := n.Child("secret") + requiredAppsAppResourceSecret(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Secret") || n.IsRef("secret") { + c := n.Child("secret") + requiredAppsAppResourceSecret(w, &c, nil) } - if c := n.child("serving_endpoint"); c.exists() { - requiredAppsAppResourceServingEndpoint(w, &c, v.ServingEndpoint) + if x := v.ServingEndpoint; x != nil { + c := n.Child("serving_endpoint") + requiredAppsAppResourceServingEndpoint(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ServingEndpoint") || n.IsRef("serving_endpoint") { + c := n.Child("serving_endpoint") + requiredAppsAppResourceServingEndpoint(w, &c, nil) } - if c := n.child("sql_warehouse"); c.exists() { - requiredAppsAppResourceSqlWarehouse(w, &c, v.SqlWarehouse) + if x := v.SqlWarehouse; x != nil { + c := n.Child("sql_warehouse") + requiredAppsAppResourceSqlWarehouse(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "SqlWarehouse") || n.IsRef("sql_warehouse") { + c := n.Child("sql_warehouse") + requiredAppsAppResourceSqlWarehouse(w, &c, nil) } - if c := n.child("uc_securable"); c.exists() { - requiredAppsAppResourceUcSecurable(w, &c, v.UcSecurable) + if x := v.UcSecurable; x != nil { + c := n.Child("uc_securable") + requiredAppsAppResourceUcSecurable(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "UcSecurable") || n.IsRef("uc_securable") { + c := n.Child("uc_securable") + requiredAppsAppResourceUcSecurable(w, &c, nil) } } -func requiredAppsTelemetryExportDestination(w *walker, n *node, v *apps.TelemetryExportDestination) { +func requiredAppsTelemetryExportDestination(w *walker, n *walk.Node, v *apps.TelemetryExportDestination) { if v == nil { - v = &apps.TelemetryExportDestination{} + return } - if c := n.child("unity_catalog"); c.exists() { - requiredAppsUnityCatalog(w, &c, v.UnityCatalog) + if x := v.UnityCatalog; x != nil { + c := n.Child("unity_catalog") + requiredAppsUnityCatalog(w, &c, x) + } else if n.IsRef("unity_catalog") { + c := n.Child("unity_catalog") + requiredAppsUnityCatalog(w, &c, nil) } } -func requiredCatalogEncryptionSettings(w *walker, n *node, v *catalog.EncryptionSettings) { +func requiredCatalogEncryptionSettings(w *walker, n *walk.Node, v *catalog.EncryptionSettings) { if v == nil { - v = &catalog.EncryptionSettings{} + return } - if c := n.child("azure_encryption_settings"); c.exists() { - requiredCatalogAzureEncryptionSettings(w, &c, v.AzureEncryptionSettings) + if x := v.AzureEncryptionSettings; x != nil { + c := n.Child("azure_encryption_settings") + requiredCatalogAzureEncryptionSettings(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "AzureEncryptionSettings") || n.IsRef("azure_encryption_settings") { + c := n.Child("azure_encryption_settings") + requiredCatalogAzureEncryptionSettings(w, &c, nil) } } -func requiredComputeLibrary(w *walker, n *node, v *compute.Library) { +func requiredComputeLibrary(w *walker, n *walk.Node, v *compute.Library) { if v == nil { - v = &compute.Library{} + return } - if c := n.child("cran"); c.exists() { - requiredComputeRCranLibrary(w, &c, v.Cran) + if x := v.Cran; x != nil { + c := n.Child("cran") + requiredComputeRCranLibrary(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Cran") || n.IsRef("cran") { + c := n.Child("cran") + requiredComputeRCranLibrary(w, &c, nil) } - if c := n.child("maven"); c.exists() { - requiredComputeMavenLibrary(w, &c, v.Maven) + if x := v.Maven; x != nil { + c := n.Child("maven") + requiredComputeMavenLibrary(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Maven") || n.IsRef("maven") { + c := n.Child("maven") + requiredComputeMavenLibrary(w, &c, nil) } - if c := n.child("pypi"); c.exists() { - requiredComputePythonPyPiLibrary(w, &c, v.Pypi) + if x := v.Pypi; x != nil { + c := n.Child("pypi") + requiredComputePythonPyPiLibrary(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Pypi") || n.IsRef("pypi") { + c := n.Child("pypi") + requiredComputePythonPyPiLibrary(w, &c, nil) } } -func requiredResourcesClusterPolicyPermission(w *walker, n *node, v *resources.ClusterPolicyPermission) { +func requiredResourcesClusterPolicyPermission(w *walker, n *walk.Node, v *resources.ClusterPolicyPermission) { if v == nil { - v = &resources.ClusterPolicyPermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredComputeClusterLogConf(w *walker, n *node, v *compute.ClusterLogConf) { +func requiredComputeClusterLogConf(w *walker, n *walk.Node, v *compute.ClusterLogConf) { if v == nil { - v = &compute.ClusterLogConf{} + return } - if c := n.child("dbfs"); c.exists() { - requiredComputeDbfsStorageInfo(w, &c, v.Dbfs) + if x := v.Dbfs; x != nil { + c := n.Child("dbfs") + requiredComputeDbfsStorageInfo(w, &c, x) + } else if n.IsRef("dbfs") { + c := n.Child("dbfs") + requiredComputeDbfsStorageInfo(w, &c, nil) } - if c := n.child("s3"); c.exists() { - requiredComputeS3StorageInfo(w, &c, v.S3) + if x := v.S3; x != nil { + c := n.Child("s3") + requiredComputeS3StorageInfo(w, &c, x) + } else if n.IsRef("s3") { + c := n.Child("s3") + requiredComputeS3StorageInfo(w, &c, nil) } - if c := n.child("volumes"); c.exists() { - requiredComputeVolumesStorageInfo(w, &c, v.Volumes) + if x := v.Volumes; x != nil { + c := n.Child("volumes") + requiredComputeVolumesStorageInfo(w, &c, x) + } else if n.IsRef("volumes") { + c := n.Child("volumes") + requiredComputeVolumesStorageInfo(w, &c, nil) } } -func requiredComputeInitScriptInfo(w *walker, n *node, v *compute.InitScriptInfo) { +func requiredComputeInitScriptInfo(w *walker, n *walk.Node, v *compute.InitScriptInfo) { if v == nil { - v = &compute.InitScriptInfo{} + return } - if c := n.child("abfss"); c.exists() { - requiredComputeAdlsgen2Info(w, &c, v.Abfss) + if x := v.Abfss; x != nil { + c := n.Child("abfss") + requiredComputeAdlsgen2Info(w, &c, x) + } else if n.IsRef("abfss") { + c := n.Child("abfss") + requiredComputeAdlsgen2Info(w, &c, nil) } - if c := n.child("dbfs"); c.exists() { - requiredComputeDbfsStorageInfo(w, &c, v.Dbfs) + if x := v.Dbfs; x != nil { + c := n.Child("dbfs") + requiredComputeDbfsStorageInfo(w, &c, x) + } else if n.IsRef("dbfs") { + c := n.Child("dbfs") + requiredComputeDbfsStorageInfo(w, &c, nil) } - if c := n.child("file"); c.exists() { - requiredComputeLocalFileInfo(w, &c, v.File) + if x := v.File; x != nil { + c := n.Child("file") + requiredComputeLocalFileInfo(w, &c, x) + } else if n.IsRef("file") { + c := n.Child("file") + requiredComputeLocalFileInfo(w, &c, nil) } - if c := n.child("gcs"); c.exists() { - requiredComputeGcsStorageInfo(w, &c, v.Gcs) + if x := v.Gcs; x != nil { + c := n.Child("gcs") + requiredComputeGcsStorageInfo(w, &c, x) + } else if n.IsRef("gcs") { + c := n.Child("gcs") + requiredComputeGcsStorageInfo(w, &c, nil) } - if c := n.child("s3"); c.exists() { - requiredComputeS3StorageInfo(w, &c, v.S3) + if x := v.S3; x != nil { + c := n.Child("s3") + requiredComputeS3StorageInfo(w, &c, x) + } else if n.IsRef("s3") { + c := n.Child("s3") + requiredComputeS3StorageInfo(w, &c, nil) } - if c := n.child("volumes"); c.exists() { - requiredComputeVolumesStorageInfo(w, &c, v.Volumes) + if x := v.Volumes; x != nil { + c := n.Child("volumes") + requiredComputeVolumesStorageInfo(w, &c, x) + } else if n.IsRef("volumes") { + c := n.Child("volumes") + requiredComputeVolumesStorageInfo(w, &c, nil) } - if c := n.child("workspace"); c.exists() { - requiredComputeWorkspaceStorageInfo(w, &c, v.Workspace) + if x := v.Workspace; x != nil { + c := n.Child("workspace") + requiredComputeWorkspaceStorageInfo(w, &c, x) + } else if n.IsRef("workspace") { + c := n.Child("workspace") + requiredComputeWorkspaceStorageInfo(w, &c, nil) } } -func requiredResourcesClusterPermission(w *walker, n *node, v *resources.ClusterPermission) { +func requiredResourcesClusterPermission(w *walker, n *walk.Node, v *resources.ClusterPermission) { if v == nil { - v = &resources.ClusterPermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredComputeWorkloadType(w *walker, n *node, v *compute.WorkloadType) { +func requiredComputeWorkloadType(w *walker, n *walk.Node, v *compute.WorkloadType) { if v == nil { - v = &compute.WorkloadType{} + w.missing(n, "clients") + return } - if !n.has("clients") { + if !(!walk.IsZeroStruct(&v.Clients) || n.HasRefs("clients")) { w.missing(n, "clients") } } -func requiredResourcesMlflowExperimentPermission(w *walker, n *node, v *resources.MlflowExperimentPermission) { +func requiredResourcesMlflowExperimentPermission(w *walker, n *walk.Node, v *resources.MlflowExperimentPermission) { if v == nil { - v = &resources.MlflowExperimentPermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredMlExperimentTraceLocation(w *walker, n *node, v *ml.ExperimentTraceLocation) { +func requiredMlExperimentTraceLocation(w *walker, n *walk.Node, v *ml.ExperimentTraceLocation) { if v == nil { - v = &ml.ExperimentTraceLocation{} + return } - if c := n.child("uc_trace_location"); c.exists() { - requiredMlUcTraceLocation(w, &c, v.UcTraceLocation) + if x := v.UcTraceLocation; x != nil { + c := n.Child("uc_trace_location") + requiredMlUcTraceLocation(w, &c, x) + } else if n.IsRef("uc_trace_location") { + c := n.Child("uc_trace_location") + requiredMlUcTraceLocation(w, &c, nil) } } -func requiredMlEntityColumn(w *walker, n *node, v *ml.EntityColumn) { +func requiredMlEntityColumn(w *walker, n *walk.Node, v *ml.EntityColumn) { if v == nil { - v = &ml.EntityColumn{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } } -func requiredMlFunction(w *walker, n *node, v *ml.Function) { +func requiredMlFunction(w *walker, n *walk.Node, v *ml.Function) { if v == nil { - v = &ml.Function{} + return } - if c := n.child("aggregation_function"); c.exists() { - requiredMlAggregationFunction(w, &c, v.AggregationFunction) + if x := v.AggregationFunction; x != nil { + c := n.Child("aggregation_function") + requiredMlAggregationFunction(w, &c, x) + } else if n.IsRef("aggregation_function") { + c := n.Child("aggregation_function") + requiredMlAggregationFunction(w, &c, nil) } - if c := n.child("column_selection"); c.exists() { - requiredMlColumnSelection(w, &c, v.ColumnSelection) + if x := v.ColumnSelection; x != nil { + c := n.Child("column_selection") + requiredMlColumnSelection(w, &c, x) + } else if n.IsRef("column_selection") { + c := n.Child("column_selection") + requiredMlColumnSelection(w, &c, nil) } - if c := n.child("custom_udf"); c.exists() { - requiredMlCustomUdf(w, &c, v.CustomUdf) + if x := v.CustomUdf; x != nil { + c := n.Child("custom_udf") + requiredMlCustomUdf(w, &c, x) + } else if n.IsRef("custom_udf") { + c := n.Child("custom_udf") + requiredMlCustomUdf(w, &c, nil) } if len(v.ExtraParameters) > 0 { - c := n.child("extra_parameters") - for i0 := range v.ExtraParameters { - e := c.elem(i0) - requiredMlFunctionExtraParameter(w, &e, &v.ExtraParameters[i0]) + c := n.Child("extra_parameters") + for i := range v.ExtraParameters { + e := c.Elem(i) + requiredMlFunctionExtraParameter(w, &e, &v.ExtraParameters[i]) } } } -func requiredMlDataSource(w *walker, n *node, v *ml.DataSource) { +func requiredMlDataSource(w *walker, n *walk.Node, v *ml.DataSource) { if v == nil { - v = &ml.DataSource{} + return } - if c := n.child("delta_table_source"); c.exists() { - requiredMlDeltaTableSource(w, &c, v.DeltaTableSource) + if x := v.DeltaTableSource; x != nil { + c := n.Child("delta_table_source") + requiredMlDeltaTableSource(w, &c, x) + } else if n.IsRef("delta_table_source") { + c := n.Child("delta_table_source") + requiredMlDeltaTableSource(w, &c, nil) } - if c := n.child("feature_view_source"); c.exists() { - requiredMlFeatureViewSource(w, &c, v.FeatureViewSource) + if x := v.FeatureViewSource; x != nil { + c := n.Child("feature_view_source") + requiredMlFeatureViewSource(w, &c, x) + } else if n.IsRef("feature_view_source") { + c := n.Child("feature_view_source") + requiredMlFeatureViewSource(w, &c, nil) } - if c := n.child("kafka_source"); c.exists() { - requiredMlKafkaSource(w, &c, v.KafkaSource) + if x := v.KafkaSource; x != nil { + c := n.Child("kafka_source") + requiredMlKafkaSource(w, &c, x) + } else if n.IsRef("kafka_source") { + c := n.Child("kafka_source") + requiredMlKafkaSource(w, &c, nil) } - if c := n.child("request_source"); c.exists() { - requiredMlRequestSource(w, &c, v.RequestSource) + if x := v.RequestSource; x != nil { + c := n.Child("request_source") + requiredMlRequestSource(w, &c, x) + } else if n.IsRef("request_source") { + c := n.Child("request_source") + requiredMlRequestSource(w, &c, nil) } - if c := n.child("stream_source"); c.exists() { - requiredMlStreamSource(w, &c, v.StreamSource) + if x := v.StreamSource; x != nil { + c := n.Child("stream_source") + requiredMlStreamSource(w, &c, x) + } else if n.IsRef("stream_source") { + c := n.Child("stream_source") + requiredMlStreamSource(w, &c, nil) } } -func requiredMlTimeWindow(w *walker, n *node, v *ml.TimeWindow) { +func requiredMlTimeWindow(w *walker, n *walk.Node, v *ml.TimeWindow) { if v == nil { - v = &ml.TimeWindow{} + return } - if c := n.child("continuous"); c.exists() { - requiredMlContinuousWindow(w, &c, v.Continuous) + if x := v.Continuous; x != nil { + c := n.Child("continuous") + requiredMlContinuousWindow(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Continuous") || n.IsRef("continuous") { + c := n.Child("continuous") + requiredMlContinuousWindow(w, &c, nil) } - if c := n.child("sliding"); c.exists() { - requiredMlSlidingWindow(w, &c, v.Sliding) + if x := v.Sliding; x != nil { + c := n.Child("sliding") + requiredMlSlidingWindow(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Sliding") || n.IsRef("sliding") { + c := n.Child("sliding") + requiredMlSlidingWindow(w, &c, nil) } - if c := n.child("tumbling"); c.exists() { - requiredMlTumblingWindow(w, &c, v.Tumbling) + if x := v.Tumbling; x != nil { + c := n.Child("tumbling") + requiredMlTumblingWindow(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Tumbling") || n.IsRef("tumbling") { + c := n.Child("tumbling") + requiredMlTumblingWindow(w, &c, nil) } } -func requiredMlTimeseriesColumn(w *walker, n *node, v *ml.TimeseriesColumn) { +func requiredMlTimeseriesColumn(w *walker, n *walk.Node, v *ml.TimeseriesColumn) { if v == nil { - v = &ml.TimeseriesColumn{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } } -func requiredResourcesInstancePoolPermission(w *walker, n *node, v *resources.InstancePoolPermission) { +func requiredResourcesInstancePoolPermission(w *walker, n *walk.Node, v *resources.InstancePoolPermission) { if v == nil { - v = &resources.InstancePoolPermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredJobsQueueSettings(w *walker, n *node, v *jobs.QueueSettings) { +func requiredJobsQueueSettings(w *walker, n *walk.Node, v *jobs.QueueSettings) { if v == nil { - v = &jobs.QueueSettings{} + w.missing(n, "enabled") + return } - if !(v.Enabled) && !n.has("enabled") { + if !(v.Enabled || n.IsRef("enabled")) { w.missing(n, "enabled") } } -func requiredJobsContinuous(w *walker, n *node, v *jobs.Continuous) { +func requiredJobsContinuous(w *walker, n *walk.Node, v *jobs.Continuous) { if v == nil { - v = &jobs.Continuous{} + return } - if c := n.child("maintenance_window"); c.exists() { - requiredJobsMaintenanceWindow(w, &c, v.MaintenanceWindow) + if x := v.MaintenanceWindow; x != nil { + c := n.Child("maintenance_window") + requiredJobsMaintenanceWindow(w, &c, x) + } else if n.IsRef("maintenance_window") { + c := n.Child("maintenance_window") + requiredJobsMaintenanceWindow(w, &c, nil) } } -func requiredJobsJobDeployment(w *walker, n *node, v *jobs.JobDeployment) { +func requiredJobsJobDeployment(w *walker, n *walk.Node, v *jobs.JobDeployment) { if v == nil { - v = &jobs.JobDeployment{} + w.missing(n, "kind") + return } - if !(v.Kind != "") && !n.has("kind") { + if !(v.Kind != "" || walk.Forced(v.ForceSendFields, "Kind")) { w.missing(n, "kind") } } -func requiredJobsJobEnvironmentVariables(w *walker, n *node, v *jobs.JobEnvironmentVariables) { +func requiredJobsJobEnvironmentVariables(w *walker, n *walk.Node, v *jobs.JobEnvironmentVariables) { if v == nil { - v = &jobs.JobEnvironmentVariables{} + w.missing(n, "environment_variables_key") + return } - if !(v.EnvironmentVariablesKey != "") && !n.has("environment_variables_key") { + if !(v.EnvironmentVariablesKey != "") { w.missing(n, "environment_variables_key") } } -func requiredJobsJobEnvironment(w *walker, n *node, v *jobs.JobEnvironment) { +func requiredJobsJobEnvironment(w *walker, n *walk.Node, v *jobs.JobEnvironment) { if v == nil { - v = &jobs.JobEnvironment{} + w.missing(n, "environment_key") + return } - if !(v.EnvironmentKey != "") && !n.has("environment_key") { + if !(v.EnvironmentKey != "") { w.missing(n, "environment_key") } } -func requiredJobsGitSource(w *walker, n *node, v *jobs.GitSource) { +func requiredJobsGitSource(w *walker, n *walk.Node, v *jobs.GitSource) { if v == nil { - v = &jobs.GitSource{} + w.missing(n, "git_provider") + w.missing(n, "git_url") + return } - if !(v.GitProvider != "") && !n.has("git_provider") { + if !(v.GitProvider != "" || walk.Forced(v.ForceSendFields, "GitProvider")) { w.missing(n, "git_provider") } - if !(v.GitUrl != "") && !n.has("git_url") { + if !(v.GitUrl != "" || walk.Forced(v.ForceSendFields, "GitUrl")) { w.missing(n, "git_url") } - if c := n.child("job_source"); c.exists() { - requiredJobsJobSource(w, &c, v.JobSource) + if x := v.JobSource; x != nil { + c := n.Child("job_source") + requiredJobsJobSource(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "JobSource") || n.IsRef("job_source") { + c := n.Child("job_source") + requiredJobsJobSource(w, &c, nil) } } -func requiredJobsJobsHealthRules(w *walker, n *node, v *jobs.JobsHealthRules) { +func requiredJobsJobsHealthRules(w *walker, n *walk.Node, v *jobs.JobsHealthRules) { if v == nil { - v = &jobs.JobsHealthRules{} + return } if len(v.Rules) > 0 { - c := n.child("rules") - for i0 := range v.Rules { - e := c.elem(i0) - requiredJobsJobsHealthRule(w, &e, &v.Rules[i0]) + c := n.Child("rules") + for i := range v.Rules { + e := c.Elem(i) + requiredJobsJobsHealthRule(w, &e, &v.Rules[i]) } } } -func requiredJobsJobCluster(w *walker, n *node, v *jobs.JobCluster) { +func requiredJobsJobCluster(w *walker, n *walk.Node, v *jobs.JobCluster) { if v == nil { - v = &jobs.JobCluster{} + w.missing(n, "job_cluster_key") + return } - if !(v.JobClusterKey != "") && !n.has("job_cluster_key") { + if !(v.JobClusterKey != "" || walk.Forced(v.ForceSendFields, "JobClusterKey")) { w.missing(n, "job_cluster_key") } - if c := n.child("new_cluster"); c.exists() { - requiredComputeClusterSpec(w, &c, v.NewCluster) + if x := v.NewCluster; x != nil { + c := n.Child("new_cluster") + requiredComputeClusterSpec(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "NewCluster") || n.IsRef("new_cluster") { + c := n.Child("new_cluster") + requiredComputeClusterSpec(w, &c, nil) } } -func requiredJobsJobParameterDefinition(w *walker, n *node, v *jobs.JobParameterDefinition) { +func requiredJobsJobParameterDefinition(w *walker, n *walk.Node, v *jobs.JobParameterDefinition) { if v == nil { - v = &jobs.JobParameterDefinition{} - } - if !(v.Default != "") && !n.has("default") { w.missing(n, "default") + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } } -func requiredResourcesJobPermission(w *walker, n *node, v *resources.JobPermission) { +func requiredResourcesJobPermission(w *walker, n *walk.Node, v *resources.JobPermission) { if v == nil { - v = &resources.JobPermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredJobsCronSchedule(w *walker, n *node, v *jobs.CronSchedule) { +func requiredJobsCronSchedule(w *walker, n *walk.Node, v *jobs.CronSchedule) { if v == nil { - v = &jobs.CronSchedule{} + w.missing(n, "quartz_cron_expression") + w.missing(n, "timezone_id") + return } - if !(v.QuartzCronExpression != "") && !n.has("quartz_cron_expression") { + if !(v.QuartzCronExpression != "") { w.missing(n, "quartz_cron_expression") } - if !(v.TimezoneId != "") && !n.has("timezone_id") { + if !(v.TimezoneId != "") { w.missing(n, "timezone_id") } - if c := n.child("sql_condition"); c.exists() { - requiredJobsSqlConditionConfiguration(w, &c, v.SqlCondition) + if x := v.SqlCondition; x != nil { + c := n.Child("sql_condition") + requiredJobsSqlConditionConfiguration(w, &c, x) + } else if n.IsRef("sql_condition") { + c := n.Child("sql_condition") + requiredJobsSqlConditionConfiguration(w, &c, nil) } } -func requiredJobsTask(w *walker, n *node, v *jobs.Task) { +func requiredJobsTask(w *walker, n *walk.Node, v *jobs.Task) { if v == nil { - v = &jobs.Task{} - } - if !(v.TaskKey != "") && !n.has("task_key") { w.missing(n, "task_key") + return } - if c := n.child("ai_runtime_task"); c.exists() { - requiredJobsAiRuntimeTask(w, &c, v.AiRuntimeTask) - } - if c := n.child("clean_rooms_notebook_task"); c.exists() { - requiredJobsCleanRoomsNotebookTask(w, &c, v.CleanRoomsNotebookTask) - } - if c := n.child("condition_task"); c.exists() { - requiredJobsConditionTask(w, &c, v.ConditionTask) - } - if c := n.child("dashboard_task"); c.exists() { - requiredJobsDashboardTask(w, &c, v.DashboardTask) + if !(v.TaskKey != "" || walk.Forced(v.ForceSendFields, "TaskKey")) { + w.missing(n, "task_key") } - if c := n.child("dbt_task"); c.exists() { - requiredJobsDbtTask(w, &c, v.DbtTask) + if x := v.AiRuntimeTask; x != nil { + c := n.Child("ai_runtime_task") + requiredJobsAiRuntimeTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "AiRuntimeTask") || n.IsRef("ai_runtime_task") { + c := n.Child("ai_runtime_task") + requiredJobsAiRuntimeTask(w, &c, nil) + } + if x := v.CleanRoomsNotebookTask; x != nil { + c := n.Child("clean_rooms_notebook_task") + requiredJobsCleanRoomsNotebookTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "CleanRoomsNotebookTask") || n.IsRef("clean_rooms_notebook_task") { + c := n.Child("clean_rooms_notebook_task") + requiredJobsCleanRoomsNotebookTask(w, &c, nil) + } + if x := v.ConditionTask; x != nil { + c := n.Child("condition_task") + requiredJobsConditionTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ConditionTask") || n.IsRef("condition_task") { + c := n.Child("condition_task") + requiredJobsConditionTask(w, &c, nil) + } + if x := v.DashboardTask; x != nil { + c := n.Child("dashboard_task") + requiredJobsDashboardTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "DashboardTask") || n.IsRef("dashboard_task") { + c := n.Child("dashboard_task") + requiredJobsDashboardTask(w, &c, nil) + } + if x := v.DbtTask; x != nil { + c := n.Child("dbt_task") + requiredJobsDbtTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "DbtTask") || n.IsRef("dbt_task") { + c := n.Child("dbt_task") + requiredJobsDbtTask(w, &c, nil) } if len(v.DependsOn) > 0 { - c := n.child("depends_on") - for i0 := range v.DependsOn { - e := c.elem(i0) - requiredJobsTaskDependency(w, &e, &v.DependsOn[i0]) + c := n.Child("depends_on") + for i := range v.DependsOn { + e := c.Elem(i) + requiredJobsTaskDependency(w, &e, &v.DependsOn[i]) } } - if c := n.child("for_each_task"); c.exists() { - requiredJobsForEachTask(w, &c, v.ForEachTask) - } - if c := n.child("gen_ai_compute_task"); c.exists() { - requiredJobsGenAiComputeTask(w, &c, v.GenAiComputeTask) - } - if c := n.child("health"); c.exists() { - requiredJobsJobsHealthRules(w, &c, v.Health) + if x := v.ForEachTask; x != nil { + c := n.Child("for_each_task") + requiredJobsForEachTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ForEachTask") || n.IsRef("for_each_task") { + c := n.Child("for_each_task") + requiredJobsForEachTask(w, &c, nil) + } + if x := v.GenAiComputeTask; x != nil { + c := n.Child("gen_ai_compute_task") + requiredJobsGenAiComputeTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "GenAiComputeTask") || n.IsRef("gen_ai_compute_task") { + c := n.Child("gen_ai_compute_task") + requiredJobsGenAiComputeTask(w, &c, nil) + } + if x := v.Health; x != nil { + c := n.Child("health") + requiredJobsJobsHealthRules(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Health") || n.IsRef("health") { + c := n.Child("health") + requiredJobsJobsHealthRules(w, &c, nil) } if len(v.Libraries) > 0 { - c := n.child("libraries") - for i0 := range v.Libraries { - e := c.elem(i0) - requiredComputeLibrary(w, &e, &v.Libraries[i0]) + c := n.Child("libraries") + for i := range v.Libraries { + e := c.Elem(i) + requiredComputeLibrary(w, &e, &v.Libraries[i]) } } - if c := n.child("new_cluster"); c.exists() { - requiredComputeClusterSpec(w, &c, v.NewCluster) - } - if c := n.child("notebook_task"); c.exists() { - requiredJobsNotebookTask(w, &c, v.NotebookTask) - } - if c := n.child("pipeline_task"); c.exists() { - requiredJobsPipelineTask(w, &c, v.PipelineTask) - } - if c := n.child("python_wheel_task"); c.exists() { - requiredJobsPythonWheelTask(w, &c, v.PythonWheelTask) - } - if c := n.child("run_job_task"); c.exists() { - requiredJobsRunJobTask(w, &c, v.RunJobTask) - } - if c := n.child("spark_python_task"); c.exists() { - requiredJobsSparkPythonTask(w, &c, v.SparkPythonTask) - } - if c := n.child("sql_task"); c.exists() { - requiredJobsSqlTask(w, &c, v.SqlTask) - } - if c := n.child("webhook_notifications"); c.exists() { - requiredJobsWebhookNotifications(w, &c, v.WebhookNotifications) - } -} - -func requiredJobsTriggerSettings(w *walker, n *node, v *jobs.TriggerSettings) { - if v == nil { - v = &jobs.TriggerSettings{} - } - if c := n.child("file_arrival"); c.exists() { - requiredJobsFileArrivalTriggerConfiguration(w, &c, v.FileArrival) - } - if c := n.child("model"); c.exists() { - requiredJobsModelTriggerConfiguration(w, &c, v.Model) - } - if c := n.child("periodic"); c.exists() { - requiredJobsPeriodicTriggerConfiguration(w, &c, v.Periodic) - } - if c := n.child("sql_condition"); c.exists() { - requiredJobsSqlConditionConfiguration(w, &c, v.SqlCondition) - } - if c := n.child("table_update"); c.exists() { - requiredJobsTableUpdateTriggerConfiguration(w, &c, v.TableUpdate) - } -} - -func requiredJobsTriggerConfiguration(w *walker, n *node, v *jobs.TriggerConfiguration) { - if v == nil { - v = &jobs.TriggerConfiguration{} - } - if c := n.child("continuous"); c.exists() { - requiredJobsContinuousTriggerConfiguration(w, &c, v.Continuous) - } - if c := n.child("file_arrival"); c.exists() { - requiredJobsFileArrivalTriggerConfiguration(w, &c, v.FileArrival) - } - if c := n.child("model"); c.exists() { - requiredJobsModelTriggerConfiguration(w, &c, v.Model) - } - if c := n.child("periodic"); c.exists() { - requiredJobsPeriodicTriggerConfiguration(w, &c, v.Periodic) - } - if c := n.child("schedule"); c.exists() { - requiredJobsCronTriggerConfiguration(w, &c, v.Schedule) - } - if c := n.child("sql_condition"); c.exists() { - requiredJobsSqlConditionConfiguration(w, &c, v.SqlCondition) - } - if c := n.child("table_update"); c.exists() { - requiredJobsTableUpdateTriggerConfiguration(w, &c, v.TableUpdate) - } -} - -func requiredJobsWebhookNotifications(w *walker, n *node, v *jobs.WebhookNotifications) { + if x := v.NewCluster; x != nil { + c := n.Child("new_cluster") + requiredComputeClusterSpec(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "NewCluster") || n.IsRef("new_cluster") { + c := n.Child("new_cluster") + requiredComputeClusterSpec(w, &c, nil) + } + if x := v.NotebookTask; x != nil { + c := n.Child("notebook_task") + requiredJobsNotebookTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "NotebookTask") || n.IsRef("notebook_task") { + c := n.Child("notebook_task") + requiredJobsNotebookTask(w, &c, nil) + } + if x := v.PipelineTask; x != nil { + c := n.Child("pipeline_task") + requiredJobsPipelineTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "PipelineTask") || n.IsRef("pipeline_task") { + c := n.Child("pipeline_task") + requiredJobsPipelineTask(w, &c, nil) + } + if x := v.PythonWheelTask; x != nil { + c := n.Child("python_wheel_task") + requiredJobsPythonWheelTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "PythonWheelTask") || n.IsRef("python_wheel_task") { + c := n.Child("python_wheel_task") + requiredJobsPythonWheelTask(w, &c, nil) + } + if x := v.RunJobTask; x != nil { + c := n.Child("run_job_task") + requiredJobsRunJobTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "RunJobTask") || n.IsRef("run_job_task") { + c := n.Child("run_job_task") + requiredJobsRunJobTask(w, &c, nil) + } + if x := v.SparkPythonTask; x != nil { + c := n.Child("spark_python_task") + requiredJobsSparkPythonTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "SparkPythonTask") || n.IsRef("spark_python_task") { + c := n.Child("spark_python_task") + requiredJobsSparkPythonTask(w, &c, nil) + } + if x := v.SqlTask; x != nil { + c := n.Child("sql_task") + requiredJobsSqlTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "SqlTask") || n.IsRef("sql_task") { + c := n.Child("sql_task") + requiredJobsSqlTask(w, &c, nil) + } + if x := v.WebhookNotifications; x != nil { + c := n.Child("webhook_notifications") + requiredJobsWebhookNotifications(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "WebhookNotifications") || n.IsRef("webhook_notifications") { + c := n.Child("webhook_notifications") + requiredJobsWebhookNotifications(w, &c, nil) + } +} + +func requiredJobsTriggerSettings(w *walker, n *walk.Node, v *jobs.TriggerSettings) { + if v == nil { + return + } + if x := v.FileArrival; x != nil { + c := n.Child("file_arrival") + requiredJobsFileArrivalTriggerConfiguration(w, &c, x) + } else if n.IsRef("file_arrival") { + c := n.Child("file_arrival") + requiredJobsFileArrivalTriggerConfiguration(w, &c, nil) + } + if x := v.Model; x != nil { + c := n.Child("model") + requiredJobsModelTriggerConfiguration(w, &c, x) + } else if n.IsRef("model") { + c := n.Child("model") + requiredJobsModelTriggerConfiguration(w, &c, nil) + } + if x := v.Periodic; x != nil { + c := n.Child("periodic") + requiredJobsPeriodicTriggerConfiguration(w, &c, x) + } else if n.IsRef("periodic") { + c := n.Child("periodic") + requiredJobsPeriodicTriggerConfiguration(w, &c, nil) + } + if x := v.SqlCondition; x != nil { + c := n.Child("sql_condition") + requiredJobsSqlConditionConfiguration(w, &c, x) + } else if n.IsRef("sql_condition") { + c := n.Child("sql_condition") + requiredJobsSqlConditionConfiguration(w, &c, nil) + } + if x := v.TableUpdate; x != nil { + c := n.Child("table_update") + requiredJobsTableUpdateTriggerConfiguration(w, &c, x) + } else if n.IsRef("table_update") { + c := n.Child("table_update") + requiredJobsTableUpdateTriggerConfiguration(w, &c, nil) + } +} + +func requiredJobsTriggerConfiguration(w *walker, n *walk.Node, v *jobs.TriggerConfiguration) { + if v == nil { + return + } + if x := v.Continuous; x != nil { + c := n.Child("continuous") + requiredJobsContinuousTriggerConfiguration(w, &c, x) + } else if n.IsRef("continuous") { + c := n.Child("continuous") + requiredJobsContinuousTriggerConfiguration(w, &c, nil) + } + if x := v.FileArrival; x != nil { + c := n.Child("file_arrival") + requiredJobsFileArrivalTriggerConfiguration(w, &c, x) + } else if n.IsRef("file_arrival") { + c := n.Child("file_arrival") + requiredJobsFileArrivalTriggerConfiguration(w, &c, nil) + } + if x := v.Model; x != nil { + c := n.Child("model") + requiredJobsModelTriggerConfiguration(w, &c, x) + } else if n.IsRef("model") { + c := n.Child("model") + requiredJobsModelTriggerConfiguration(w, &c, nil) + } + if x := v.Periodic; x != nil { + c := n.Child("periodic") + requiredJobsPeriodicTriggerConfiguration(w, &c, x) + } else if n.IsRef("periodic") { + c := n.Child("periodic") + requiredJobsPeriodicTriggerConfiguration(w, &c, nil) + } + if x := v.Schedule; x != nil { + c := n.Child("schedule") + requiredJobsCronTriggerConfiguration(w, &c, x) + } else if n.IsRef("schedule") { + c := n.Child("schedule") + requiredJobsCronTriggerConfiguration(w, &c, nil) + } + if x := v.SqlCondition; x != nil { + c := n.Child("sql_condition") + requiredJobsSqlConditionConfiguration(w, &c, x) + } else if n.IsRef("sql_condition") { + c := n.Child("sql_condition") + requiredJobsSqlConditionConfiguration(w, &c, nil) + } + if x := v.TableUpdate; x != nil { + c := n.Child("table_update") + requiredJobsTableUpdateTriggerConfiguration(w, &c, x) + } else if n.IsRef("table_update") { + c := n.Child("table_update") + requiredJobsTableUpdateTriggerConfiguration(w, &c, nil) + } +} + +func requiredJobsWebhookNotifications(w *walker, n *walk.Node, v *jobs.WebhookNotifications) { if v == nil { - v = &jobs.WebhookNotifications{} + return } if len(v.OnDurationWarningThresholdExceeded) > 0 { - c := n.child("on_duration_warning_threshold_exceeded") - for i0 := range v.OnDurationWarningThresholdExceeded { - e := c.elem(i0) - requiredJobsWebhook(w, &e, &v.OnDurationWarningThresholdExceeded[i0]) + c := n.Child("on_duration_warning_threshold_exceeded") + for i := range v.OnDurationWarningThresholdExceeded { + e := c.Elem(i) + requiredJobsWebhook(w, &e, &v.OnDurationWarningThresholdExceeded[i]) } } if len(v.OnFailure) > 0 { - c := n.child("on_failure") - for i0 := range v.OnFailure { - e := c.elem(i0) - requiredJobsWebhook(w, &e, &v.OnFailure[i0]) + c := n.Child("on_failure") + for i := range v.OnFailure { + e := c.Elem(i) + requiredJobsWebhook(w, &e, &v.OnFailure[i]) } } if len(v.OnMaintenanceComplete) > 0 { - c := n.child("on_maintenance_complete") - for i0 := range v.OnMaintenanceComplete { - e := c.elem(i0) - requiredJobsWebhook(w, &e, &v.OnMaintenanceComplete[i0]) + c := n.Child("on_maintenance_complete") + for i := range v.OnMaintenanceComplete { + e := c.Elem(i) + requiredJobsWebhook(w, &e, &v.OnMaintenanceComplete[i]) } } if len(v.OnMaintenanceStart) > 0 { - c := n.child("on_maintenance_start") - for i0 := range v.OnMaintenanceStart { - e := c.elem(i0) - requiredJobsWebhook(w, &e, &v.OnMaintenanceStart[i0]) + c := n.Child("on_maintenance_start") + for i := range v.OnMaintenanceStart { + e := c.Elem(i) + requiredJobsWebhook(w, &e, &v.OnMaintenanceStart[i]) } } if len(v.OnStart) > 0 { - c := n.child("on_start") - for i0 := range v.OnStart { - e := c.elem(i0) - requiredJobsWebhook(w, &e, &v.OnStart[i0]) + c := n.Child("on_start") + for i := range v.OnStart { + e := c.Elem(i) + requiredJobsWebhook(w, &e, &v.OnStart[i]) } } if len(v.OnStreamingBacklogExceeded) > 0 { - c := n.child("on_streaming_backlog_exceeded") - for i0 := range v.OnStreamingBacklogExceeded { - e := c.elem(i0) - requiredJobsWebhook(w, &e, &v.OnStreamingBacklogExceeded[i0]) + c := n.Child("on_streaming_backlog_exceeded") + for i := range v.OnStreamingBacklogExceeded { + e := c.Elem(i) + requiredJobsWebhook(w, &e, &v.OnStreamingBacklogExceeded[i]) } } if len(v.OnSuccess) > 0 { - c := n.child("on_success") - for i0 := range v.OnSuccess { - e := c.elem(i0) - requiredJobsWebhook(w, &e, &v.OnSuccess[i0]) + c := n.Child("on_success") + for i := range v.OnSuccess { + e := c.Elem(i) + requiredJobsWebhook(w, &e, &v.OnSuccess[i]) } } } -func requiredCatalogMcpServiceConfig(w *walker, n *node, v *catalog.McpServiceConfig) { +func requiredCatalogMcpServiceConfig(w *walker, n *walk.Node, v *catalog.McpServiceConfig) { if v == nil { - v = &catalog.McpServiceConfig{} + return } if len(v.RateLimits) > 0 { - c := n.child("rate_limits") - for i0 := range v.RateLimits { - e := c.elem(i0) - requiredCatalogRateLimit(w, &e, &v.RateLimits[i0]) + c := n.Child("rate_limits") + for i := range v.RateLimits { + e := c.Elem(i) + requiredCatalogRateLimit(w, &e, &v.RateLimits[i]) } } - if c := n.child("source_connection"); c.exists() { - requiredCatalogMcpServiceConfigSourceConnection(w, &c, v.SourceConnection) - } -} - -func requiredCatalogModelProviderServiceConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfig) { - if v == nil { - v = &catalog.ModelProviderServiceConfig{} - } - if c := n.child("amazon_bedrock"); c.exists() { - requiredCatalogModelProviderServiceConfigAmazonBedrockProviderConfig(w, &c, v.AmazonBedrock) - } - if c := n.child("anthropic"); c.exists() { - requiredCatalogModelProviderServiceConfigAnthropicProviderConfig(w, &c, v.Anthropic) - } - if c := n.child("azure_openai"); c.exists() { - requiredCatalogModelProviderServiceConfigAzureOpenAiProviderConfig(w, &c, v.AzureOpenai) - } - if c := n.child("bedrock_mantle"); c.exists() { - requiredCatalogModelProviderServiceConfigAmazonBedrockProviderConfig(w, &c, v.BedrockMantle) - } - if c := n.child("custom"); c.exists() { - requiredCatalogModelProviderServiceConfigCustomProviderConfig(w, &c, v.Custom) - } - if c := n.child("gemini_enterprise"); c.exists() { - requiredCatalogModelProviderServiceConfigGeminiEnterpriseProviderConfig(w, &c, v.GeminiEnterprise) - } - if c := n.child("inference_table"); c.exists() { - requiredCatalogInferenceTableConfig(w, &c, v.InferenceTable) - } - if c := n.child("microsoft_foundry"); c.exists() { - requiredCatalogModelProviderServiceConfigMicrosoftFoundryProviderConfig(w, &c, v.MicrosoftFoundry) - } - if c := n.child("openai"); c.exists() { - requiredCatalogModelProviderServiceConfigOpenAiProviderConfig(w, &c, v.Openai) + if x := v.SourceConnection; x != nil { + c := n.Child("source_connection") + requiredCatalogMcpServiceConfigSourceConnection(w, &c, x) + } else if n.IsRef("source_connection") { + c := n.Child("source_connection") + requiredCatalogMcpServiceConfigSourceConnection(w, &c, nil) + } +} + +func requiredCatalogModelProviderServiceConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfig) { + if v == nil { + return + } + if x := v.AmazonBedrock; x != nil { + c := n.Child("amazon_bedrock") + requiredCatalogModelProviderServiceConfigAmazonBedrockProviderConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "AmazonBedrock") || n.IsRef("amazon_bedrock") { + c := n.Child("amazon_bedrock") + requiredCatalogModelProviderServiceConfigAmazonBedrockProviderConfig(w, &c, nil) + } + if x := v.Anthropic; x != nil { + c := n.Child("anthropic") + requiredCatalogModelProviderServiceConfigAnthropicProviderConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Anthropic") || n.IsRef("anthropic") { + c := n.Child("anthropic") + requiredCatalogModelProviderServiceConfigAnthropicProviderConfig(w, &c, nil) + } + if x := v.AzureOpenai; x != nil { + c := n.Child("azure_openai") + requiredCatalogModelProviderServiceConfigAzureOpenAiProviderConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "AzureOpenai") || n.IsRef("azure_openai") { + c := n.Child("azure_openai") + requiredCatalogModelProviderServiceConfigAzureOpenAiProviderConfig(w, &c, nil) + } + if x := v.BedrockMantle; x != nil { + c := n.Child("bedrock_mantle") + requiredCatalogModelProviderServiceConfigAmazonBedrockProviderConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "BedrockMantle") || n.IsRef("bedrock_mantle") { + c := n.Child("bedrock_mantle") + requiredCatalogModelProviderServiceConfigAmazonBedrockProviderConfig(w, &c, nil) + } + if x := v.Custom; x != nil { + c := n.Child("custom") + requiredCatalogModelProviderServiceConfigCustomProviderConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Custom") || n.IsRef("custom") { + c := n.Child("custom") + requiredCatalogModelProviderServiceConfigCustomProviderConfig(w, &c, nil) + } + if x := v.GeminiEnterprise; x != nil { + c := n.Child("gemini_enterprise") + requiredCatalogModelProviderServiceConfigGeminiEnterpriseProviderConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "GeminiEnterprise") || n.IsRef("gemini_enterprise") { + c := n.Child("gemini_enterprise") + requiredCatalogModelProviderServiceConfigGeminiEnterpriseProviderConfig(w, &c, nil) + } + if x := v.InferenceTable; x != nil { + c := n.Child("inference_table") + requiredCatalogInferenceTableConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "InferenceTable") || n.IsRef("inference_table") { + c := n.Child("inference_table") + requiredCatalogInferenceTableConfig(w, &c, nil) + } + if x := v.MicrosoftFoundry; x != nil { + c := n.Child("microsoft_foundry") + requiredCatalogModelProviderServiceConfigMicrosoftFoundryProviderConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "MicrosoftFoundry") || n.IsRef("microsoft_foundry") { + c := n.Child("microsoft_foundry") + requiredCatalogModelProviderServiceConfigMicrosoftFoundryProviderConfig(w, &c, nil) + } + if x := v.Openai; x != nil { + c := n.Child("openai") + requiredCatalogModelProviderServiceConfigOpenAiProviderConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Openai") || n.IsRef("openai") { + c := n.Child("openai") + requiredCatalogModelProviderServiceConfigOpenAiProviderConfig(w, &c, nil) } if len(v.RateLimits) > 0 { - c := n.child("rate_limits") - for i0 := range v.RateLimits { - e := c.elem(i0) - requiredCatalogRateLimit(w, &e, &v.RateLimits[i0]) + c := n.Child("rate_limits") + for i := range v.RateLimits { + e := c.Elem(i) + requiredCatalogRateLimit(w, &e, &v.RateLimits[i]) } } if len(v.Targets) > 0 { - c := n.child("targets") - for i0 := range v.Targets { - e := c.elem(i0) - requiredCatalogModelProviderServiceConfigModelTargetConfig(w, &e, &v.Targets[i0]) + c := n.Child("targets") + for i := range v.Targets { + e := c.Elem(i) + requiredCatalogModelProviderServiceConfigModelTargetConfig(w, &e, &v.Targets[i]) } } } -func requiredCatalogModelServiceConfig(w *walker, n *node, v *catalog.ModelServiceConfig) { +func requiredCatalogModelServiceConfig(w *walker, n *walk.Node, v *catalog.ModelServiceConfig) { if v == nil { - v = &catalog.ModelServiceConfig{} + return } - if c := n.child("inference_table"); c.exists() { - requiredCatalogInferenceTableConfig(w, &c, v.InferenceTable) + if x := v.InferenceTable; x != nil { + c := n.Child("inference_table") + requiredCatalogInferenceTableConfig(w, &c, x) + } else if n.IsRef("inference_table") { + c := n.Child("inference_table") + requiredCatalogInferenceTableConfig(w, &c, nil) } if len(v.RateLimits) > 0 { - c := n.child("rate_limits") - for i0 := range v.RateLimits { - e := c.elem(i0) - requiredCatalogRateLimit(w, &e, &v.RateLimits[i0]) + c := n.Child("rate_limits") + for i := range v.RateLimits { + e := c.Elem(i) + requiredCatalogRateLimit(w, &e, &v.RateLimits[i]) } } - if c := n.child("routing"); c.exists() { - requiredCatalogModelServiceConfigRoutingConfig(w, &c, v.Routing) + if x := v.Routing; x != nil { + c := n.Child("routing") + requiredCatalogModelServiceConfigRoutingConfig(w, &c, x) + } else if n.IsRef("routing") { + c := n.Child("routing") + requiredCatalogModelServiceConfigRoutingConfig(w, &c, nil) } } -func requiredServingAiGatewayConfig(w *walker, n *node, v *serving.AiGatewayConfig) { +func requiredServingAiGatewayConfig(w *walker, n *walk.Node, v *serving.AiGatewayConfig) { if v == nil { - v = &serving.AiGatewayConfig{} + return } - if c := n.child("fallback_config"); c.exists() { - requiredServingFallbackConfig(w, &c, v.FallbackConfig) + if x := v.FallbackConfig; x != nil { + c := n.Child("fallback_config") + requiredServingFallbackConfig(w, &c, x) + } else if n.IsRef("fallback_config") { + c := n.Child("fallback_config") + requiredServingFallbackConfig(w, &c, nil) } if len(v.RateLimits) > 0 { - c := n.child("rate_limits") - for i0 := range v.RateLimits { - e := c.elem(i0) - requiredServingAiGatewayRateLimit(w, &e, &v.RateLimits[i0]) + c := n.Child("rate_limits") + for i := range v.RateLimits { + e := c.Elem(i) + requiredServingAiGatewayRateLimit(w, &e, &v.RateLimits[i]) } } } -func requiredServingEndpointCoreConfigInput(w *walker, n *node, v *serving.EndpointCoreConfigInput) { +func requiredServingEndpointCoreConfigInput(w *walker, n *walk.Node, v *serving.EndpointCoreConfigInput) { if v == nil { - v = &serving.EndpointCoreConfigInput{} + return } if len(v.ServedEntities) > 0 { - c := n.child("served_entities") - for i0 := range v.ServedEntities { - e := c.elem(i0) - requiredServingServedEntityInput(w, &e, &v.ServedEntities[i0]) + c := n.Child("served_entities") + for i := range v.ServedEntities { + e := c.Elem(i) + requiredServingServedEntityInput(w, &e, &v.ServedEntities[i]) } } if len(v.ServedModels) > 0 { - c := n.child("served_models") - for i0 := range v.ServedModels { - e := c.elem(i0) - requiredServingServedModelInput(w, &e, &v.ServedModels[i0]) + c := n.Child("served_models") + for i := range v.ServedModels { + e := c.Elem(i) + requiredServingServedModelInput(w, &e, &v.ServedModels[i]) } } - if c := n.child("traffic_config"); c.exists() { - requiredServingTrafficConfig(w, &c, v.TrafficConfig) + if x := v.TrafficConfig; x != nil { + c := n.Child("traffic_config") + requiredServingTrafficConfig(w, &c, x) + } else if n.IsRef("traffic_config") { + c := n.Child("traffic_config") + requiredServingTrafficConfig(w, &c, nil) } } -func requiredResourcesModelServingEndpointPermission(w *walker, n *node, v *resources.ModelServingEndpointPermission) { +func requiredResourcesModelServingEndpointPermission(w *walker, n *walk.Node, v *resources.ModelServingEndpointPermission) { if v == nil { - v = &resources.ModelServingEndpointPermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredServingRateLimit(w *walker, n *node, v *serving.RateLimit) { +func requiredServingRateLimit(w *walker, n *walk.Node, v *serving.RateLimit) { if v == nil { - v = &serving.RateLimit{} + w.missing(n, "calls") + w.missing(n, "renewal_period") + return } - if !(v.Calls != 0) && !n.has("calls") { + if !(v.Calls != 0 || n.IsRef("calls")) { w.missing(n, "calls") } - if !(v.RenewalPeriod != "") && !n.has("renewal_period") { + if !(v.RenewalPeriod != "") { w.missing(n, "renewal_period") } } -func requiredServingEndpointTag(w *walker, n *node, v *serving.EndpointTag) { +func requiredServingEndpointTag(w *walker, n *walk.Node, v *serving.EndpointTag) { if v == nil { - v = &serving.EndpointTag{} + w.missing(n, "key") + return } - if !(v.Key != "") && !n.has("key") { + if !(v.Key != "" || walk.Forced(v.ForceSendFields, "Key")) { w.missing(n, "key") } } -func requiredResourcesMlflowModelPermission(w *walker, n *node, v *resources.MlflowModelPermission) { +func requiredResourcesMlflowModelPermission(w *walker, n *walk.Node, v *resources.MlflowModelPermission) { if v == nil { - v = &resources.MlflowModelPermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredPipelinesPipelineCluster(w *walker, n *node, v *pipelines.PipelineCluster) { +func requiredPipelinesPipelineCluster(w *walker, n *walk.Node, v *pipelines.PipelineCluster) { if v == nil { - v = &pipelines.PipelineCluster{} + return } - if c := n.child("autoscale"); c.exists() { - requiredPipelinesPipelineClusterAutoscale(w, &c, v.Autoscale) + if x := v.Autoscale; x != nil { + c := n.Child("autoscale") + requiredPipelinesPipelineClusterAutoscale(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Autoscale") || n.IsRef("autoscale") { + c := n.Child("autoscale") + requiredPipelinesPipelineClusterAutoscale(w, &c, nil) } - if c := n.child("cluster_log_conf"); c.exists() { - requiredComputeClusterLogConf(w, &c, v.ClusterLogConf) + if x := v.ClusterLogConf; x != nil { + c := n.Child("cluster_log_conf") + requiredComputeClusterLogConf(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ClusterLogConf") || n.IsRef("cluster_log_conf") { + c := n.Child("cluster_log_conf") + requiredComputeClusterLogConf(w, &c, nil) } if len(v.InitScripts) > 0 { - c := n.child("init_scripts") - for i0 := range v.InitScripts { - e := c.elem(i0) - requiredComputeInitScriptInfo(w, &e, &v.InitScripts[i0]) + c := n.Child("init_scripts") + for i := range v.InitScripts { + e := c.Elem(i) + requiredComputeInitScriptInfo(w, &e, &v.InitScripts[i]) } } } -func requiredPipelinesPipelineDeployment(w *walker, n *node, v *pipelines.PipelineDeployment) { +func requiredPipelinesPipelineDeployment(w *walker, n *walk.Node, v *pipelines.PipelineDeployment) { if v == nil { - v = &pipelines.PipelineDeployment{} + w.missing(n, "kind") + return } - if !(v.Kind != "") && !n.has("kind") { + if !(v.Kind != "" || walk.Forced(v.ForceSendFields, "Kind")) { w.missing(n, "kind") } } -func requiredPipelinesIngestionGatewayPipelineDefinition(w *walker, n *node, v *pipelines.IngestionGatewayPipelineDefinition) { +func requiredPipelinesIngestionGatewayPipelineDefinition(w *walker, n *walk.Node, v *pipelines.IngestionGatewayPipelineDefinition) { if v == nil { - v = &pipelines.IngestionGatewayPipelineDefinition{} + w.missing(n, "connection_name") + w.missing(n, "gateway_storage_catalog") + w.missing(n, "gateway_storage_schema") + return } - if !(v.ConnectionName != "") && !n.has("connection_name") { + if !(v.ConnectionName != "" || walk.Forced(v.ForceSendFields, "ConnectionName")) { w.missing(n, "connection_name") } - if !(v.GatewayStorageCatalog != "") && !n.has("gateway_storage_catalog") { + if !(v.GatewayStorageCatalog != "" || walk.Forced(v.ForceSendFields, "GatewayStorageCatalog")) { w.missing(n, "gateway_storage_catalog") } - if !(v.GatewayStorageSchema != "") && !n.has("gateway_storage_schema") { + if !(v.GatewayStorageSchema != "" || walk.Forced(v.ForceSendFields, "GatewayStorageSchema")) { w.missing(n, "gateway_storage_schema") } } -func requiredPipelinesIngestionPipelineDefinition(w *walker, n *node, v *pipelines.IngestionPipelineDefinition) { +func requiredPipelinesIngestionPipelineDefinition(w *walker, n *walk.Node, v *pipelines.IngestionPipelineDefinition) { if v == nil { - v = &pipelines.IngestionPipelineDefinition{} + return } - if c := n.child("data_staging_options"); c.exists() { - requiredPipelinesDataStagingOptions(w, &c, v.DataStagingOptions) + if x := v.DataStagingOptions; x != nil { + c := n.Child("data_staging_options") + requiredPipelinesDataStagingOptions(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "DataStagingOptions") || n.IsRef("data_staging_options") { + c := n.Child("data_staging_options") + requiredPipelinesDataStagingOptions(w, &c, nil) } - if c := n.child("full_refresh_window"); c.exists() { - requiredPipelinesOperationTimeWindow(w, &c, v.FullRefreshWindow) + if x := v.FullRefreshWindow; x != nil { + c := n.Child("full_refresh_window") + requiredPipelinesOperationTimeWindow(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "FullRefreshWindow") || n.IsRef("full_refresh_window") { + c := n.Child("full_refresh_window") + requiredPipelinesOperationTimeWindow(w, &c, nil) } if len(v.Objects) > 0 { - c := n.child("objects") - for i0 := range v.Objects { - e := c.elem(i0) - requiredPipelinesIngestionConfig(w, &e, &v.Objects[i0]) + c := n.Child("objects") + for i := range v.Objects { + e := c.Elem(i) + requiredPipelinesIngestionConfig(w, &e, &v.Objects[i]) } } - if c := n.child("table_configuration"); c.exists() { - requiredPipelinesTableSpecificConfig(w, &c, v.TableConfiguration) + if x := v.TableConfiguration; x != nil { + c := n.Child("table_configuration") + requiredPipelinesTableSpecificConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "TableConfiguration") || n.IsRef("table_configuration") { + c := n.Child("table_configuration") + requiredPipelinesTableSpecificConfig(w, &c, nil) } } -func requiredPipelinesPipelineLibrary(w *walker, n *node, v *pipelines.PipelineLibrary) { +func requiredPipelinesPipelineLibrary(w *walker, n *walk.Node, v *pipelines.PipelineLibrary) { if v == nil { - v = &pipelines.PipelineLibrary{} + return } - if c := n.child("maven"); c.exists() { - requiredComputeMavenLibrary(w, &c, v.Maven) + if x := v.Maven; x != nil { + c := n.Child("maven") + requiredComputeMavenLibrary(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Maven") || n.IsRef("maven") { + c := n.Child("maven") + requiredComputeMavenLibrary(w, &c, nil) } } -func requiredResourcesPipelinePermission(w *walker, n *node, v *resources.PipelinePermission) { +func requiredResourcesPipelinePermission(w *walker, n *walk.Node, v *resources.PipelinePermission) { if v == nil { - v = &resources.PipelinePermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredPipelinesRestartWindow(w *walker, n *node, v *pipelines.RestartWindow) { +func requiredPipelinesRestartWindow(w *walker, n *walk.Node, v *pipelines.RestartWindow) { if v == nil { - v = &pipelines.RestartWindow{} + w.missing(n, "start_hour") + return } - if !(v.StartHour != 0) && !n.has("start_hour") { + if !(v.StartHour != 0 || walk.Forced(v.ForceSendFields, "StartHour") || n.IsRef("start_hour")) { w.missing(n, "start_hour") } } -func requiredPostgresEndpointGroupSpec(w *walker, n *node, v *postgres.EndpointGroupSpec) { +func requiredPostgresEndpointGroupSpec(w *walker, n *walk.Node, v *postgres.EndpointGroupSpec) { if v == nil { - v = &postgres.EndpointGroupSpec{} + w.missing(n, "max") + w.missing(n, "min") + return } - if !(v.Max != 0) && !n.has("max") { + if !(v.Max != 0 || walk.Forced(v.ForceSendFields, "Max") || n.IsRef("max")) { w.missing(n, "max") } - if !(v.Min != 0) && !n.has("min") { + if !(v.Min != 0 || walk.Forced(v.ForceSendFields, "Min") || n.IsRef("min")) { w.missing(n, "min") } } -func requiredPostgresScheduleCadence(w *walker, n *node, v *postgres.ScheduleCadence) { +func requiredPostgresScheduleCadence(w *walker, n *walk.Node, v *postgres.ScheduleCadence) { if v == nil { - v = &postgres.ScheduleCadence{} + w.missing(n, "retention") + return } - if !n.has("retention") { + if !(!walk.IsZeroStruct(&v.Retention) || n.HasRefs("retention")) { w.missing(n, "retention") } - if c := n.child("monthly_schedule"); c.exists() { - requiredPostgresMonthlySchedule(w, &c, v.MonthlySchedule) + if x := v.MonthlySchedule; x != nil { + c := n.Child("monthly_schedule") + requiredPostgresMonthlySchedule(w, &c, x) + } else if n.IsRef("monthly_schedule") { + c := n.Child("monthly_schedule") + requiredPostgresMonthlySchedule(w, &c, nil) } - if c := n.child("weekly_schedule"); c.exists() { - requiredPostgresWeeklySchedule(w, &c, v.WeeklySchedule) + if x := v.WeeklySchedule; x != nil { + c := n.Child("weekly_schedule") + requiredPostgresWeeklySchedule(w, &c, x) + } else if n.IsRef("weekly_schedule") { + c := n.Child("weekly_schedule") + requiredPostgresWeeklySchedule(w, &c, nil) } } -func requiredPostgresSyncedTableSyncedTableSpecExtraColumn(w *walker, n *node, v *postgres.SyncedTableSyncedTableSpecExtraColumn) { +func requiredPostgresSyncedTableSyncedTableSpecExtraColumn(w *walker, n *walk.Node, v *postgres.SyncedTableSyncedTableSpecExtraColumn) { if v == nil { - v = &postgres.SyncedTableSyncedTableSpecExtraColumn{} + w.missing(n, "column_name") + w.missing(n, "column_type") + return } - if !(v.ColumnName != "") && !n.has("column_name") { + if !(v.ColumnName != "" || walk.Forced(v.ForceSendFields, "ColumnName")) { w.missing(n, "column_name") } - if !(v.ColumnType != "") && !n.has("column_type") { + if !(v.ColumnType != "" || walk.Forced(v.ForceSendFields, "ColumnType")) { w.missing(n, "column_type") } } -func requiredPostgresSyncedTableSyncedTableSpecTypeOverride(w *walker, n *node, v *postgres.SyncedTableSyncedTableSpecTypeOverride) { +func requiredPostgresSyncedTableSyncedTableSpecTypeOverride(w *walker, n *walk.Node, v *postgres.SyncedTableSyncedTableSpecTypeOverride) { if v == nil { - v = &postgres.SyncedTableSyncedTableSpecTypeOverride{} + w.missing(n, "column_name") + w.missing(n, "pg_type") + return } - if !(v.ColumnName != "") && !n.has("column_name") { + if !(v.ColumnName != "" || walk.Forced(v.ForceSendFields, "ColumnName")) { w.missing(n, "column_name") } - if !(v.PgType != "") && !n.has("pg_type") { + if !(v.PgType != "" || walk.Forced(v.ForceSendFields, "PgType")) { w.missing(n, "pg_type") } } -func requiredCatalogMonitorMetric(w *walker, n *node, v *catalog.MonitorMetric) { +func requiredCatalogMonitorMetric(w *walker, n *walk.Node, v *catalog.MonitorMetric) { if v == nil { - v = &catalog.MonitorMetric{} + w.missing(n, "definition") + w.missing(n, "input_columns") + w.missing(n, "name") + w.missing(n, "output_data_type") + w.missing(n, "type") + return } - if !(v.Definition != "") && !n.has("definition") { + if !(v.Definition != "") { w.missing(n, "definition") } - if !(v.InputColumns != nil) && !n.has("input_columns") { + if !(v.InputColumns != nil || n.IsRef("input_columns")) { w.missing(n, "input_columns") } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } - if !(v.OutputDataType != "") && !n.has("output_data_type") { + if !(v.OutputDataType != "") { w.missing(n, "output_data_type") } - if !(v.Type != "") && !n.has("type") { + if !(v.Type != "") { w.missing(n, "type") } } -func requiredCatalogMonitorInferenceLog(w *walker, n *node, v *catalog.MonitorInferenceLog) { +func requiredCatalogMonitorInferenceLog(w *walker, n *walk.Node, v *catalog.MonitorInferenceLog) { if v == nil { - v = &catalog.MonitorInferenceLog{} + w.missing(n, "granularities") + w.missing(n, "model_id_col") + w.missing(n, "prediction_col") + w.missing(n, "problem_type") + w.missing(n, "timestamp_col") + return } - if !(v.Granularities != nil) && !n.has("granularities") { + if !(v.Granularities != nil || n.IsRef("granularities")) { w.missing(n, "granularities") } - if !(v.ModelIdCol != "") && !n.has("model_id_col") { + if !(v.ModelIdCol != "" || walk.Forced(v.ForceSendFields, "ModelIdCol")) { w.missing(n, "model_id_col") } - if !(v.PredictionCol != "") && !n.has("prediction_col") { + if !(v.PredictionCol != "" || walk.Forced(v.ForceSendFields, "PredictionCol")) { w.missing(n, "prediction_col") } - if !(v.ProblemType != "") && !n.has("problem_type") { + if !(v.ProblemType != "" || walk.Forced(v.ForceSendFields, "ProblemType")) { w.missing(n, "problem_type") } - if !(v.TimestampCol != "") && !n.has("timestamp_col") { + if !(v.TimestampCol != "" || walk.Forced(v.ForceSendFields, "TimestampCol")) { w.missing(n, "timestamp_col") } } -func requiredCatalogMonitorCronSchedule(w *walker, n *node, v *catalog.MonitorCronSchedule) { +func requiredCatalogMonitorCronSchedule(w *walker, n *walk.Node, v *catalog.MonitorCronSchedule) { if v == nil { - v = &catalog.MonitorCronSchedule{} + w.missing(n, "quartz_cron_expression") + w.missing(n, "timezone_id") + return } - if !(v.QuartzCronExpression != "") && !n.has("quartz_cron_expression") { + if !(v.QuartzCronExpression != "") { w.missing(n, "quartz_cron_expression") } - if !(v.TimezoneId != "") && !n.has("timezone_id") { + if !(v.TimezoneId != "") { w.missing(n, "timezone_id") } } -func requiredCatalogMonitorTimeSeries(w *walker, n *node, v *catalog.MonitorTimeSeries) { +func requiredCatalogMonitorTimeSeries(w *walker, n *walk.Node, v *catalog.MonitorTimeSeries) { if v == nil { - v = &catalog.MonitorTimeSeries{} + w.missing(n, "granularities") + w.missing(n, "timestamp_col") + return } - if !(v.Granularities != nil) && !n.has("granularities") { + if !(v.Granularities != nil || n.IsRef("granularities")) { w.missing(n, "granularities") } - if !(v.TimestampCol != "") && !n.has("timestamp_col") { + if !(v.TimestampCol != "") { w.missing(n, "timestamp_col") } } -func requiredWorkspaceAzureKeyVaultSecretScopeMetadata(w *walker, n *node, v *workspace.AzureKeyVaultSecretScopeMetadata) { +func requiredWorkspaceAzureKeyVaultSecretScopeMetadata(w *walker, n *walk.Node, v *workspace.AzureKeyVaultSecretScopeMetadata) { if v == nil { - v = &workspace.AzureKeyVaultSecretScopeMetadata{} + w.missing(n, "dns_name") + w.missing(n, "resource_id") + return } - if !(v.DnsName != "") && !n.has("dns_name") { + if !(v.DnsName != "") { w.missing(n, "dns_name") } - if !(v.ResourceId != "") && !n.has("resource_id") { + if !(v.ResourceId != "") { w.missing(n, "resource_id") } } -func requiredResourcesSecretScopePermission(w *walker, n *node, v *resources.SecretScopePermission) { +func requiredResourcesSecretScopePermission(w *walker, n *walk.Node, v *resources.SecretScopePermission) { if v == nil { - v = &resources.SecretScopePermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredResourcesSqlWarehousePermission(w *walker, n *node, v *resources.SqlWarehousePermission) { +func requiredResourcesSqlWarehousePermission(w *walker, n *walk.Node, v *resources.SqlWarehousePermission) { if v == nil { - v = &resources.SqlWarehousePermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredDatabaseSyncedTableSpec(w *walker, n *node, v *database.SyncedTableSpec) { +func requiredDatabaseSyncedTableSpec(w *walker, n *walk.Node, v *database.SyncedTableSpec) { if v == nil { - v = &database.SyncedTableSpec{} + return } if len(v.TypeOverrides) > 0 { - c := n.child("type_overrides") - for i0 := range v.TypeOverrides { - e := c.elem(i0) - requiredDatabaseSyncedTableSpecTypeOverride(w, &e, &v.TypeOverrides[i0]) + c := n.Child("type_overrides") + for i := range v.TypeOverrides { + e := c.Elem(i) + requiredDatabaseSyncedTableSpecTypeOverride(w, &e, &v.TypeOverrides[i]) } } } -func requiredResourcesVectorSearchEndpointPermission(w *walker, n *node, v *resources.VectorSearchEndpointPermission) { +func requiredResourcesVectorSearchEndpointPermission(w *walker, n *walk.Node, v *resources.VectorSearchEndpointPermission) { if v == nil { - v = &resources.VectorSearchEndpointPermission{} + w.missing(n, "level") + return } - if !(v.Level != "") && !n.has("level") { + if !(v.Level != "") { w.missing(n, "level") } } -func requiredSqlAlertV2OperandColumn(w *walker, n *node, v *sql.AlertV2OperandColumn) { +func requiredSqlAlertV2OperandColumn(w *walker, n *walk.Node, v *sql.AlertV2OperandColumn) { if v == nil { - v = &sql.AlertV2OperandColumn{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "" || walk.Forced(v.ForceSendFields, "Name")) { w.missing(n, "name") } } -func requiredSqlAlertV2Operand(w *walker, n *node, v *sql.AlertV2Operand) { +func requiredSqlAlertV2Operand(w *walker, n *walk.Node, v *sql.AlertV2Operand) { if v == nil { - v = &sql.AlertV2Operand{} + return } - if c := n.child("column"); c.exists() { - requiredSqlAlertV2OperandColumn2(w, &c, v.Column) + if x := v.Column; x != nil { + c := n.Child("column") + requiredSqlAlertV2OperandColumn2(w, &c, x) + } else if n.IsRef("column") { + c := n.Child("column") + requiredSqlAlertV2OperandColumn2(w, &c, nil) } } -func requiredResourcesAppEnvVar(w *walker, n *node, v *resources.AppEnvVar) { +func requiredResourcesAppEnvVar(w *walker, n *walk.Node, v *resources.AppEnvVar) { if v == nil { - v = &resources.AppEnvVar{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } } -func requiredAppsAppResourceDatabase(w *walker, n *node, v *apps.AppResourceDatabase) { +func requiredAppsAppResourceDatabase(w *walker, n *walk.Node, v *apps.AppResourceDatabase) { if v == nil { - v = &apps.AppResourceDatabase{} + w.missing(n, "database_name") + w.missing(n, "instance_name") + w.missing(n, "permission") + return } - if !(v.DatabaseName != "") && !n.has("database_name") { + if !(v.DatabaseName != "") { w.missing(n, "database_name") } - if !(v.InstanceName != "") && !n.has("instance_name") { + if !(v.InstanceName != "") { w.missing(n, "instance_name") } - if !(v.Permission != "") && !n.has("permission") { + if !(v.Permission != "") { w.missing(n, "permission") } } -func requiredAppsAppResourceExperiment(w *walker, n *node, v *apps.AppResourceExperiment) { +func requiredAppsAppResourceExperiment(w *walker, n *walk.Node, v *apps.AppResourceExperiment) { if v == nil { - v = &apps.AppResourceExperiment{} + w.missing(n, "experiment_id") + w.missing(n, "permission") + return } - if !(v.ExperimentId != "") && !n.has("experiment_id") { + if !(v.ExperimentId != "") { w.missing(n, "experiment_id") } - if !(v.Permission != "") && !n.has("permission") { + if !(v.Permission != "") { w.missing(n, "permission") } } -func requiredAppsAppResourceGenieSpace(w *walker, n *node, v *apps.AppResourceGenieSpace) { +func requiredAppsAppResourceGenieSpace(w *walker, n *walk.Node, v *apps.AppResourceGenieSpace) { if v == nil { - v = &apps.AppResourceGenieSpace{} + w.missing(n, "name") + w.missing(n, "permission") + w.missing(n, "space_id") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } - if !(v.Permission != "") && !n.has("permission") { + if !(v.Permission != "") { w.missing(n, "permission") } - if !(v.SpaceId != "") && !n.has("space_id") { + if !(v.SpaceId != "") { w.missing(n, "space_id") } } -func requiredAppsAppResourceJob(w *walker, n *node, v *apps.AppResourceJob) { +func requiredAppsAppResourceJob(w *walker, n *walk.Node, v *apps.AppResourceJob) { if v == nil { - v = &apps.AppResourceJob{} + w.missing(n, "id") + w.missing(n, "permission") + return } - if !(v.Id != "") && !n.has("id") { + if !(v.Id != "") { w.missing(n, "id") } - if !(v.Permission != "") && !n.has("permission") { + if !(v.Permission != "") { w.missing(n, "permission") } } -func requiredAppsAppResourceSecret(w *walker, n *node, v *apps.AppResourceSecret) { +func requiredAppsAppResourceSecret(w *walker, n *walk.Node, v *apps.AppResourceSecret) { if v == nil { - v = &apps.AppResourceSecret{} + w.missing(n, "key") + w.missing(n, "permission") + w.missing(n, "scope") + return } - if !(v.Key != "") && !n.has("key") { + if !(v.Key != "") { w.missing(n, "key") } - if !(v.Permission != "") && !n.has("permission") { + if !(v.Permission != "") { w.missing(n, "permission") } - if !(v.Scope != "") && !n.has("scope") { + if !(v.Scope != "") { w.missing(n, "scope") } } -func requiredAppsAppResourceServingEndpoint(w *walker, n *node, v *apps.AppResourceServingEndpoint) { +func requiredAppsAppResourceServingEndpoint(w *walker, n *walk.Node, v *apps.AppResourceServingEndpoint) { if v == nil { - v = &apps.AppResourceServingEndpoint{} + w.missing(n, "name") + w.missing(n, "permission") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } - if !(v.Permission != "") && !n.has("permission") { + if !(v.Permission != "") { w.missing(n, "permission") } } -func requiredAppsAppResourceSqlWarehouse(w *walker, n *node, v *apps.AppResourceSqlWarehouse) { +func requiredAppsAppResourceSqlWarehouse(w *walker, n *walk.Node, v *apps.AppResourceSqlWarehouse) { if v == nil { - v = &apps.AppResourceSqlWarehouse{} + w.missing(n, "id") + w.missing(n, "permission") + return } - if !(v.Id != "") && !n.has("id") { + if !(v.Id != "") { w.missing(n, "id") } - if !(v.Permission != "") && !n.has("permission") { + if !(v.Permission != "") { w.missing(n, "permission") } } -func requiredAppsAppResourceUcSecurable(w *walker, n *node, v *apps.AppResourceUcSecurable) { +func requiredAppsAppResourceUcSecurable(w *walker, n *walk.Node, v *apps.AppResourceUcSecurable) { if v == nil { - v = &apps.AppResourceUcSecurable{} + w.missing(n, "permission") + w.missing(n, "securable_full_name") + w.missing(n, "securable_type") + return } - if !(v.Permission != "") && !n.has("permission") { + if !(v.Permission != "" || walk.Forced(v.ForceSendFields, "Permission")) { w.missing(n, "permission") } - if !(v.SecurableFullName != "") && !n.has("securable_full_name") { + if !(v.SecurableFullName != "" || walk.Forced(v.ForceSendFields, "SecurableFullName")) { w.missing(n, "securable_full_name") } - if !(v.SecurableType != "") && !n.has("securable_type") { + if !(v.SecurableType != "" || walk.Forced(v.ForceSendFields, "SecurableType")) { w.missing(n, "securable_type") } } -func requiredAppsUnityCatalog(w *walker, n *node, v *apps.UnityCatalog) { +func requiredAppsUnityCatalog(w *walker, n *walk.Node, v *apps.UnityCatalog) { if v == nil { - v = &apps.UnityCatalog{} + w.missing(n, "logs_table") + w.missing(n, "metrics_table") + w.missing(n, "traces_table") + return } - if !(v.LogsTable != "") && !n.has("logs_table") { + if !(v.LogsTable != "") { w.missing(n, "logs_table") } - if !(v.MetricsTable != "") && !n.has("metrics_table") { + if !(v.MetricsTable != "") { w.missing(n, "metrics_table") } - if !(v.TracesTable != "") && !n.has("traces_table") { + if !(v.TracesTable != "") { w.missing(n, "traces_table") } } -func requiredCatalogAzureEncryptionSettings(w *walker, n *node, v *catalog.AzureEncryptionSettings) { +func requiredCatalogAzureEncryptionSettings(w *walker, n *walk.Node, v *catalog.AzureEncryptionSettings) { if v == nil { - v = &catalog.AzureEncryptionSettings{} + w.missing(n, "azure_tenant_id") + return } - if !(v.AzureTenantId != "") && !n.has("azure_tenant_id") { + if !(v.AzureTenantId != "" || walk.Forced(v.ForceSendFields, "AzureTenantId")) { w.missing(n, "azure_tenant_id") } } -func requiredComputeRCranLibrary(w *walker, n *node, v *compute.RCranLibrary) { +func requiredComputeRCranLibrary(w *walker, n *walk.Node, v *compute.RCranLibrary) { if v == nil { - v = &compute.RCranLibrary{} + w.missing(n, "package") + return } - if !(v.Package != "") && !n.has("package") { + if !(v.Package != "" || walk.Forced(v.ForceSendFields, "Package")) { w.missing(n, "package") } } -func requiredComputeMavenLibrary(w *walker, n *node, v *compute.MavenLibrary) { +func requiredComputeMavenLibrary(w *walker, n *walk.Node, v *compute.MavenLibrary) { if v == nil { - v = &compute.MavenLibrary{} + w.missing(n, "coordinates") + return } - if !(v.Coordinates != "") && !n.has("coordinates") { + if !(v.Coordinates != "" || walk.Forced(v.ForceSendFields, "Coordinates")) { w.missing(n, "coordinates") } } -func requiredComputePythonPyPiLibrary(w *walker, n *node, v *compute.PythonPyPiLibrary) { +func requiredComputePythonPyPiLibrary(w *walker, n *walk.Node, v *compute.PythonPyPiLibrary) { if v == nil { - v = &compute.PythonPyPiLibrary{} + w.missing(n, "package") + return } - if !(v.Package != "") && !n.has("package") { + if !(v.Package != "" || walk.Forced(v.ForceSendFields, "Package")) { w.missing(n, "package") } } -func requiredComputeDbfsStorageInfo(w *walker, n *node, v *compute.DbfsStorageInfo) { +func requiredComputeDbfsStorageInfo(w *walker, n *walk.Node, v *compute.DbfsStorageInfo) { if v == nil { - v = &compute.DbfsStorageInfo{} + w.missing(n, "destination") + return } - if !(v.Destination != "") && !n.has("destination") { + if !(v.Destination != "") { w.missing(n, "destination") } } -func requiredComputeS3StorageInfo(w *walker, n *node, v *compute.S3StorageInfo) { +func requiredComputeS3StorageInfo(w *walker, n *walk.Node, v *compute.S3StorageInfo) { if v == nil { - v = &compute.S3StorageInfo{} + w.missing(n, "destination") + return } - if !(v.Destination != "") && !n.has("destination") { + if !(v.Destination != "" || walk.Forced(v.ForceSendFields, "Destination")) { w.missing(n, "destination") } } -func requiredComputeVolumesStorageInfo(w *walker, n *node, v *compute.VolumesStorageInfo) { +func requiredComputeVolumesStorageInfo(w *walker, n *walk.Node, v *compute.VolumesStorageInfo) { if v == nil { - v = &compute.VolumesStorageInfo{} + w.missing(n, "destination") + return } - if !(v.Destination != "") && !n.has("destination") { + if !(v.Destination != "") { w.missing(n, "destination") } } -func requiredComputeAdlsgen2Info(w *walker, n *node, v *compute.Adlsgen2Info) { +func requiredComputeAdlsgen2Info(w *walker, n *walk.Node, v *compute.Adlsgen2Info) { if v == nil { - v = &compute.Adlsgen2Info{} + w.missing(n, "destination") + return } - if !(v.Destination != "") && !n.has("destination") { + if !(v.Destination != "") { w.missing(n, "destination") } } -func requiredComputeLocalFileInfo(w *walker, n *node, v *compute.LocalFileInfo) { +func requiredComputeLocalFileInfo(w *walker, n *walk.Node, v *compute.LocalFileInfo) { if v == nil { - v = &compute.LocalFileInfo{} + w.missing(n, "destination") + return } - if !(v.Destination != "") && !n.has("destination") { + if !(v.Destination != "") { w.missing(n, "destination") } } -func requiredComputeGcsStorageInfo(w *walker, n *node, v *compute.GcsStorageInfo) { +func requiredComputeGcsStorageInfo(w *walker, n *walk.Node, v *compute.GcsStorageInfo) { if v == nil { - v = &compute.GcsStorageInfo{} + w.missing(n, "destination") + return } - if !(v.Destination != "") && !n.has("destination") { + if !(v.Destination != "") { w.missing(n, "destination") } } -func requiredComputeWorkspaceStorageInfo(w *walker, n *node, v *compute.WorkspaceStorageInfo) { +func requiredComputeWorkspaceStorageInfo(w *walker, n *walk.Node, v *compute.WorkspaceStorageInfo) { if v == nil { - v = &compute.WorkspaceStorageInfo{} + w.missing(n, "destination") + return } - if !(v.Destination != "") && !n.has("destination") { + if !(v.Destination != "") { w.missing(n, "destination") } } -func requiredMlUcTraceLocation(w *walker, n *node, v *ml.UcTraceLocation) { +func requiredMlUcTraceLocation(w *walker, n *walk.Node, v *ml.UcTraceLocation) { if v == nil { - v = &ml.UcTraceLocation{} - } - if !(v.Catalog != "") && !n.has("catalog") { w.missing(n, "catalog") - } - if !(v.Schema != "") && !n.has("schema") { w.missing(n, "schema") + return } -} - -func requiredMlAggregationFunction(w *walker, n *node, v *ml.AggregationFunction) { - if v == nil { - v = &ml.AggregationFunction{} - } - if c := n.child("approx_count_distinct"); c.exists() { - requiredMlApproxCountDistinctFunction(w, &c, v.ApproxCountDistinct) - } - if c := n.child("approx_percentile"); c.exists() { - requiredMlApproxPercentileFunction(w, &c, v.ApproxPercentile) - } - if c := n.child("avg"); c.exists() { - requiredMlAvgFunction(w, &c, v.Avg) - } - if c := n.child("count_function"); c.exists() { - requiredMlCountFunction(w, &c, v.CountFunction) - } - if c := n.child("first"); c.exists() { - requiredMlFirstFunction(w, &c, v.First) - } - if c := n.child("first_distinct"); c.exists() { - requiredMlFirstDistinctFunction(w, &c, v.FirstDistinct) - } - if c := n.child("first_n"); c.exists() { - requiredMlFirstNFunction(w, &c, v.FirstN) - } - if c := n.child("last"); c.exists() { - requiredMlLastFunction(w, &c, v.Last) - } - if c := n.child("last_distinct"); c.exists() { - requiredMlLastDistinctFunction(w, &c, v.LastDistinct) - } - if c := n.child("last_n"); c.exists() { - requiredMlLastNFunction(w, &c, v.LastN) - } - if c := n.child("max"); c.exists() { - requiredMlMaxFunction(w, &c, v.Max) - } - if c := n.child("min"); c.exists() { - requiredMlMinFunction(w, &c, v.Min) - } - if c := n.child("stddev_pop"); c.exists() { - requiredMlStddevPopFunction(w, &c, v.StddevPop) - } - if c := n.child("stddev_samp"); c.exists() { - requiredMlStddevSampFunction(w, &c, v.StddevSamp) - } - if c := n.child("sum"); c.exists() { - requiredMlSumFunction(w, &c, v.Sum) - } - if c := n.child("time_window"); c.exists() { - requiredMlTimeWindow(w, &c, v.TimeWindow) - } - if c := n.child("var_pop"); c.exists() { - requiredMlVarPopFunction(w, &c, v.VarPop) + if !(v.Catalog != "" || walk.Forced(v.ForceSendFields, "Catalog")) { + w.missing(n, "catalog") } - if c := n.child("var_samp"); c.exists() { - requiredMlVarSampFunction(w, &c, v.VarSamp) + if !(v.Schema != "" || walk.Forced(v.ForceSendFields, "Schema")) { + w.missing(n, "schema") } } -func requiredMlColumnSelection(w *walker, n *node, v *ml.ColumnSelection) { +func requiredMlAggregationFunction(w *walker, n *walk.Node, v *ml.AggregationFunction) { + if v == nil { + return + } + if x := v.ApproxCountDistinct; x != nil { + c := n.Child("approx_count_distinct") + requiredMlApproxCountDistinctFunction(w, &c, x) + } else if n.IsRef("approx_count_distinct") { + c := n.Child("approx_count_distinct") + requiredMlApproxCountDistinctFunction(w, &c, nil) + } + if x := v.ApproxPercentile; x != nil { + c := n.Child("approx_percentile") + requiredMlApproxPercentileFunction(w, &c, x) + } else if n.IsRef("approx_percentile") { + c := n.Child("approx_percentile") + requiredMlApproxPercentileFunction(w, &c, nil) + } + if x := v.Avg; x != nil { + c := n.Child("avg") + requiredMlAvgFunction(w, &c, x) + } else if n.IsRef("avg") { + c := n.Child("avg") + requiredMlAvgFunction(w, &c, nil) + } + if x := v.CountFunction; x != nil { + c := n.Child("count_function") + requiredMlCountFunction(w, &c, x) + } else if n.IsRef("count_function") { + c := n.Child("count_function") + requiredMlCountFunction(w, &c, nil) + } + if x := v.First; x != nil { + c := n.Child("first") + requiredMlFirstFunction(w, &c, x) + } else if n.IsRef("first") { + c := n.Child("first") + requiredMlFirstFunction(w, &c, nil) + } + if x := v.FirstDistinct; x != nil { + c := n.Child("first_distinct") + requiredMlFirstDistinctFunction(w, &c, x) + } else if n.IsRef("first_distinct") { + c := n.Child("first_distinct") + requiredMlFirstDistinctFunction(w, &c, nil) + } + if x := v.FirstN; x != nil { + c := n.Child("first_n") + requiredMlFirstNFunction(w, &c, x) + } else if n.IsRef("first_n") { + c := n.Child("first_n") + requiredMlFirstNFunction(w, &c, nil) + } + if x := v.Last; x != nil { + c := n.Child("last") + requiredMlLastFunction(w, &c, x) + } else if n.IsRef("last") { + c := n.Child("last") + requiredMlLastFunction(w, &c, nil) + } + if x := v.LastDistinct; x != nil { + c := n.Child("last_distinct") + requiredMlLastDistinctFunction(w, &c, x) + } else if n.IsRef("last_distinct") { + c := n.Child("last_distinct") + requiredMlLastDistinctFunction(w, &c, nil) + } + if x := v.LastN; x != nil { + c := n.Child("last_n") + requiredMlLastNFunction(w, &c, x) + } else if n.IsRef("last_n") { + c := n.Child("last_n") + requiredMlLastNFunction(w, &c, nil) + } + if x := v.Max; x != nil { + c := n.Child("max") + requiredMlMaxFunction(w, &c, x) + } else if n.IsRef("max") { + c := n.Child("max") + requiredMlMaxFunction(w, &c, nil) + } + if x := v.Min; x != nil { + c := n.Child("min") + requiredMlMinFunction(w, &c, x) + } else if n.IsRef("min") { + c := n.Child("min") + requiredMlMinFunction(w, &c, nil) + } + if x := v.StddevPop; x != nil { + c := n.Child("stddev_pop") + requiredMlStddevPopFunction(w, &c, x) + } else if n.IsRef("stddev_pop") { + c := n.Child("stddev_pop") + requiredMlStddevPopFunction(w, &c, nil) + } + if x := v.StddevSamp; x != nil { + c := n.Child("stddev_samp") + requiredMlStddevSampFunction(w, &c, x) + } else if n.IsRef("stddev_samp") { + c := n.Child("stddev_samp") + requiredMlStddevSampFunction(w, &c, nil) + } + if x := v.Sum; x != nil { + c := n.Child("sum") + requiredMlSumFunction(w, &c, x) + } else if n.IsRef("sum") { + c := n.Child("sum") + requiredMlSumFunction(w, &c, nil) + } + if x := v.TimeWindow; x != nil { + c := n.Child("time_window") + requiredMlTimeWindow(w, &c, x) + } else if n.IsRef("time_window") { + c := n.Child("time_window") + requiredMlTimeWindow(w, &c, nil) + } + if x := v.VarPop; x != nil { + c := n.Child("var_pop") + requiredMlVarPopFunction(w, &c, x) + } else if n.IsRef("var_pop") { + c := n.Child("var_pop") + requiredMlVarPopFunction(w, &c, nil) + } + if x := v.VarSamp; x != nil { + c := n.Child("var_samp") + requiredMlVarSampFunction(w, &c, x) + } else if n.IsRef("var_samp") { + c := n.Child("var_samp") + requiredMlVarSampFunction(w, &c, nil) + } +} + +func requiredMlColumnSelection(w *walker, n *walk.Node, v *ml.ColumnSelection) { if v == nil { - v = &ml.ColumnSelection{} + w.missing(n, "column") + return } - if !(v.Column != "") && !n.has("column") { + if !(v.Column != "") { w.missing(n, "column") } } -func requiredMlCustomUdf(w *walker, n *node, v *ml.CustomUdf) { +func requiredMlCustomUdf(w *walker, n *walk.Node, v *ml.CustomUdf) { if v == nil { - v = &ml.CustomUdf{} + w.missing(n, "function_path") + return } - if !(v.FunctionPath != "") && !n.has("function_path") { + if !(v.FunctionPath != "") { w.missing(n, "function_path") } if len(v.InputBindings) > 0 { - c := n.child("input_bindings") - for i0 := range v.InputBindings { - e := c.elem(i0) - requiredMlInputBinding(w, &e, &v.InputBindings[i0]) + c := n.Child("input_bindings") + for i := range v.InputBindings { + e := c.Elem(i) + requiredMlInputBinding(w, &e, &v.InputBindings[i]) } } } -func requiredMlFunctionExtraParameter(w *walker, n *node, v *ml.FunctionExtraParameter) { +func requiredMlFunctionExtraParameter(w *walker, n *walk.Node, v *ml.FunctionExtraParameter) { if v == nil { - v = &ml.FunctionExtraParameter{} + w.missing(n, "key") + w.missing(n, "value") + return } - if !(v.Key != "") && !n.has("key") { + if !(v.Key != "") { w.missing(n, "key") } - if !(v.Value != "") && !n.has("value") { + if !(v.Value != "") { w.missing(n, "value") } } -func requiredMlDeltaTableSource(w *walker, n *node, v *ml.DeltaTableSource) { +func requiredMlDeltaTableSource(w *walker, n *walk.Node, v *ml.DeltaTableSource) { if v == nil { - v = &ml.DeltaTableSource{} + w.missing(n, "full_name") + return } - if !(v.FullName != "") && !n.has("full_name") { + if !(v.FullName != "" || walk.Forced(v.ForceSendFields, "FullName")) { w.missing(n, "full_name") } } -func requiredMlFeatureViewSource(w *walker, n *node, v *ml.FeatureViewSource) { +func requiredMlFeatureViewSource(w *walker, n *walk.Node, v *ml.FeatureViewSource) { if v == nil { - v = &ml.FeatureViewSource{} + return } if len(v.FeatureReferences) > 0 { - c := n.child("feature_references") - for i0 := range v.FeatureReferences { - e := c.elem(i0) - requiredMlFeatureReference(w, &e, &v.FeatureReferences[i0]) + c := n.Child("feature_references") + for i := range v.FeatureReferences { + e := c.Elem(i) + requiredMlFeatureReference(w, &e, &v.FeatureReferences[i]) } } } -func requiredMlKafkaSource(w *walker, n *node, v *ml.KafkaSource) { +func requiredMlKafkaSource(w *walker, n *walk.Node, v *ml.KafkaSource) { if v == nil { - v = &ml.KafkaSource{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "" || walk.Forced(v.ForceSendFields, "Name")) { w.missing(n, "name") } if len(v.EntityColumnIdentifiers) > 0 { - c := n.child("entity_column_identifiers") - for i0 := range v.EntityColumnIdentifiers { - e := c.elem(i0) - requiredMlColumnIdentifier(w, &e, &v.EntityColumnIdentifiers[i0]) + c := n.Child("entity_column_identifiers") + for i := range v.EntityColumnIdentifiers { + e := c.Elem(i) + requiredMlColumnIdentifier(w, &e, &v.EntityColumnIdentifiers[i]) } } - if c := n.child("timeseries_column_identifier"); c.exists() { - requiredMlColumnIdentifier2(w, &c, v.TimeseriesColumnIdentifier) + if x := v.TimeseriesColumnIdentifier; x != nil { + c := n.Child("timeseries_column_identifier") + requiredMlColumnIdentifier2(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "TimeseriesColumnIdentifier") || n.IsRef("timeseries_column_identifier") { + c := n.Child("timeseries_column_identifier") + requiredMlColumnIdentifier2(w, &c, nil) } } -func requiredMlRequestSource(w *walker, n *node, v *ml.RequestSource) { +func requiredMlRequestSource(w *walker, n *walk.Node, v *ml.RequestSource) { if v == nil { - v = &ml.RequestSource{} + return } - if c := n.child("flat_schema"); c.exists() { - requiredMlFlatSchema(w, &c, v.FlatSchema) + if x := v.FlatSchema; x != nil { + c := n.Child("flat_schema") + requiredMlFlatSchema(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "FlatSchema") || n.IsRef("flat_schema") { + c := n.Child("flat_schema") + requiredMlFlatSchema(w, &c, nil) } } -func requiredMlStreamSource(w *walker, n *node, v *ml.StreamSource) { +func requiredMlStreamSource(w *walker, n *walk.Node, v *ml.StreamSource) { if v == nil { - v = &ml.StreamSource{} + w.missing(n, "full_name") + return } - if !(v.FullName != "") && !n.has("full_name") { + if !(v.FullName != "" || walk.Forced(v.ForceSendFields, "FullName")) { w.missing(n, "full_name") } } -func requiredMlContinuousWindow(w *walker, n *node, v *ml.ContinuousWindow) { +func requiredMlContinuousWindow(w *walker, n *walk.Node, v *ml.ContinuousWindow) { if v == nil { - v = &ml.ContinuousWindow{} + w.missing(n, "window_duration") + return } - if !(v.WindowDuration != "") && !n.has("window_duration") { + if !(v.WindowDuration != "" || walk.Forced(v.ForceSendFields, "WindowDuration")) { w.missing(n, "window_duration") } } -func requiredMlSlidingWindow(w *walker, n *node, v *ml.SlidingWindow) { +func requiredMlSlidingWindow(w *walker, n *walk.Node, v *ml.SlidingWindow) { if v == nil { - v = &ml.SlidingWindow{} + w.missing(n, "slide_duration") + return } - if !(v.SlideDuration != "") && !n.has("slide_duration") { + if !(v.SlideDuration != "" || walk.Forced(v.ForceSendFields, "SlideDuration")) { w.missing(n, "slide_duration") } } -func requiredMlTumblingWindow(w *walker, n *node, v *ml.TumblingWindow) { +func requiredMlTumblingWindow(w *walker, n *walk.Node, v *ml.TumblingWindow) { if v == nil { - v = &ml.TumblingWindow{} + w.missing(n, "window_duration") + return } - if !(v.WindowDuration != "") && !n.has("window_duration") { + if !(v.WindowDuration != "" || walk.Forced(v.ForceSendFields, "WindowDuration")) { w.missing(n, "window_duration") } } -func requiredJobsMaintenanceWindow(w *walker, n *node, v *jobs.MaintenanceWindow) { +func requiredJobsMaintenanceWindow(w *walker, n *walk.Node, v *jobs.MaintenanceWindow) { if v == nil { - v = &jobs.MaintenanceWindow{} + w.missing(n, "day_of_week") + w.missing(n, "start_hour") + w.missing(n, "timezone_id") + return } - if !(v.DayOfWeek != "") && !n.has("day_of_week") { + if !(v.DayOfWeek != "") { w.missing(n, "day_of_week") } - if !(v.StartHour != 0) && !n.has("start_hour") { + if !(v.StartHour != 0 || n.IsRef("start_hour")) { w.missing(n, "start_hour") } - if !(v.TimezoneId != "") && !n.has("timezone_id") { + if !(v.TimezoneId != "") { w.missing(n, "timezone_id") } } -func requiredJobsJobSource(w *walker, n *node, v *jobs.JobSource) { +func requiredJobsJobSource(w *walker, n *walk.Node, v *jobs.JobSource) { if v == nil { - v = &jobs.JobSource{} + w.missing(n, "import_from_git_branch") + w.missing(n, "job_config_path") + return } - if !(v.ImportFromGitBranch != "") && !n.has("import_from_git_branch") { + if !(v.ImportFromGitBranch != "") { w.missing(n, "import_from_git_branch") } - if !(v.JobConfigPath != "") && !n.has("job_config_path") { + if !(v.JobConfigPath != "") { w.missing(n, "job_config_path") } } -func requiredJobsJobsHealthRule(w *walker, n *node, v *jobs.JobsHealthRule) { +func requiredJobsJobsHealthRule(w *walker, n *walk.Node, v *jobs.JobsHealthRule) { if v == nil { - v = &jobs.JobsHealthRule{} + w.missing(n, "metric") + w.missing(n, "op") + w.missing(n, "value") + return } - if !(v.Metric != "") && !n.has("metric") { + if !(v.Metric != "") { w.missing(n, "metric") } - if !(v.Op != "") && !n.has("op") { + if !(v.Op != "") { w.missing(n, "op") } - if !(v.Value != 0) && !n.has("value") { + if !(v.Value != 0 || n.IsRef("value")) { w.missing(n, "value") } } -func requiredComputeClusterSpec(w *walker, n *node, v *compute.ClusterSpec) { +func requiredComputeClusterSpec(w *walker, n *walk.Node, v *compute.ClusterSpec) { if v == nil { - v = &compute.ClusterSpec{} + return } - if c := n.child("cluster_log_conf"); c.exists() { - requiredComputeClusterLogConf(w, &c, v.ClusterLogConf) + if x := v.ClusterLogConf; x != nil { + c := n.Child("cluster_log_conf") + requiredComputeClusterLogConf(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ClusterLogConf") || n.IsRef("cluster_log_conf") { + c := n.Child("cluster_log_conf") + requiredComputeClusterLogConf(w, &c, nil) } if len(v.InitScripts) > 0 { - c := n.child("init_scripts") - for i0 := range v.InitScripts { - e := c.elem(i0) - requiredComputeInitScriptInfo(w, &e, &v.InitScripts[i0]) + c := n.Child("init_scripts") + for i := range v.InitScripts { + e := c.Elem(i) + requiredComputeInitScriptInfo(w, &e, &v.InitScripts[i]) } } - if c := n.child("workload_type"); c.exists() { - requiredComputeWorkloadType(w, &c, v.WorkloadType) + if x := v.WorkloadType; x != nil { + c := n.Child("workload_type") + requiredComputeWorkloadType(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "WorkloadType") || n.IsRef("workload_type") { + c := n.Child("workload_type") + requiredComputeWorkloadType(w, &c, nil) } } -func requiredJobsSqlConditionConfiguration(w *walker, n *node, v *jobs.SqlConditionConfiguration) { +func requiredJobsSqlConditionConfiguration(w *walker, n *walk.Node, v *jobs.SqlConditionConfiguration) { if v == nil { - v = &jobs.SqlConditionConfiguration{} + w.missing(n, "sql_query_id") + w.missing(n, "warehouse_id") + return } - if !(v.SqlQueryId != "") && !n.has("sql_query_id") { + if !(v.SqlQueryId != "") { w.missing(n, "sql_query_id") } - if !(v.WarehouseId != "") && !n.has("warehouse_id") { + if !(v.WarehouseId != "") { w.missing(n, "warehouse_id") } } -func requiredJobsAiRuntimeTask(w *walker, n *node, v *jobs.AiRuntimeTask) { +func requiredJobsAiRuntimeTask(w *walker, n *walk.Node, v *jobs.AiRuntimeTask) { if v == nil { - v = &jobs.AiRuntimeTask{} + w.missing(n, "deployments") + w.missing(n, "experiment") + return } - if !(v.Deployments != nil) && !n.has("deployments") { + if !(v.Deployments != nil || n.IsRef("deployments")) { w.missing(n, "deployments") } - if !(v.Experiment != "") && !n.has("experiment") { + if !(v.Experiment != "" || walk.Forced(v.ForceSendFields, "Experiment")) { w.missing(n, "experiment") } if len(v.Deployments) > 0 { - c := n.child("deployments") - for i0 := range v.Deployments { - e := c.elem(i0) - requiredJobsDeploymentSpec(w, &e, &v.Deployments[i0]) + c := n.Child("deployments") + for i := range v.Deployments { + e := c.Elem(i) + requiredJobsDeploymentSpec(w, &e, &v.Deployments[i]) } } } -func requiredJobsCleanRoomsNotebookTask(w *walker, n *node, v *jobs.CleanRoomsNotebookTask) { +func requiredJobsCleanRoomsNotebookTask(w *walker, n *walk.Node, v *jobs.CleanRoomsNotebookTask) { if v == nil { - v = &jobs.CleanRoomsNotebookTask{} + w.missing(n, "clean_room_name") + w.missing(n, "notebook_name") + return } - if !(v.CleanRoomName != "") && !n.has("clean_room_name") { + if !(v.CleanRoomName != "" || walk.Forced(v.ForceSendFields, "CleanRoomName")) { w.missing(n, "clean_room_name") } - if !(v.NotebookName != "") && !n.has("notebook_name") { + if !(v.NotebookName != "" || walk.Forced(v.ForceSendFields, "NotebookName")) { w.missing(n, "notebook_name") } } -func requiredJobsConditionTask(w *walker, n *node, v *jobs.ConditionTask) { +func requiredJobsConditionTask(w *walker, n *walk.Node, v *jobs.ConditionTask) { if v == nil { - v = &jobs.ConditionTask{} + w.missing(n, "left") + w.missing(n, "op") + w.missing(n, "right") + return } - if !(v.Left != "") && !n.has("left") { + if !(v.Left != "") { w.missing(n, "left") } - if !(v.Op != "") && !n.has("op") { + if !(v.Op != "") { w.missing(n, "op") } - if !(v.Right != "") && !n.has("right") { + if !(v.Right != "") { w.missing(n, "right") } } -func requiredJobsDashboardTask(w *walker, n *node, v *jobs.DashboardTask) { +func requiredJobsDashboardTask(w *walker, n *walk.Node, v *jobs.DashboardTask) { if v == nil { - v = &jobs.DashboardTask{} + return } - if c := n.child("subscription"); c.exists() { - requiredJobsSubscription(w, &c, v.Subscription) + if x := v.Subscription; x != nil { + c := n.Child("subscription") + requiredJobsSubscription(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Subscription") || n.IsRef("subscription") { + c := n.Child("subscription") + requiredJobsSubscription(w, &c, nil) } } -func requiredJobsDbtTask(w *walker, n *node, v *jobs.DbtTask) { +func requiredJobsDbtTask(w *walker, n *walk.Node, v *jobs.DbtTask) { if v == nil { - v = &jobs.DbtTask{} + w.missing(n, "commands") + return } - if !(v.Commands != nil) && !n.has("commands") { + if !(v.Commands != nil || n.IsRef("commands")) { w.missing(n, "commands") } } -func requiredJobsTaskDependency(w *walker, n *node, v *jobs.TaskDependency) { +func requiredJobsTaskDependency(w *walker, n *walk.Node, v *jobs.TaskDependency) { if v == nil { - v = &jobs.TaskDependency{} + w.missing(n, "task_key") + return } - if !(v.TaskKey != "") && !n.has("task_key") { + if !(v.TaskKey != "" || walk.Forced(v.ForceSendFields, "TaskKey")) { w.missing(n, "task_key") } } -func requiredJobsForEachTask(w *walker, n *node, v *jobs.ForEachTask) { +func requiredJobsForEachTask(w *walker, n *walk.Node, v *jobs.ForEachTask) { if v == nil { - v = &jobs.ForEachTask{} + w.missing(n, "inputs") + w.missing(n, "task") + return } - if !(v.Inputs != "") && !n.has("inputs") { + if !(v.Inputs != "" || walk.Forced(v.ForceSendFields, "Inputs")) { w.missing(n, "inputs") } - if !n.has("task") { + if !(!walk.IsZeroStruct(&v.Task) || walk.Forced(v.ForceSendFields, "Task") || n.HasRefs("task")) { w.missing(n, "task") } - if c := n.child("task"); c.exists() { - requiredJobsTask2(w, &c, &v.Task) + if x := &v.Task; !walk.IsZeroStruct(x) || walk.Forced(v.ForceSendFields, "Task") || n.HasRefs("task") { + c := n.Child("task") + requiredJobsTask2(w, &c, x) } } -func requiredJobsGenAiComputeTask(w *walker, n *node, v *jobs.GenAiComputeTask) { +func requiredJobsGenAiComputeTask(w *walker, n *walk.Node, v *jobs.GenAiComputeTask) { if v == nil { - v = &jobs.GenAiComputeTask{} + w.missing(n, "dl_runtime_image") + return } - if !(v.DlRuntimeImage != "") && !n.has("dl_runtime_image") { + if !(v.DlRuntimeImage != "" || walk.Forced(v.ForceSendFields, "DlRuntimeImage")) { w.missing(n, "dl_runtime_image") } - if c := n.child("compute"); c.exists() { - requiredJobsComputeConfig(w, &c, v.Compute) + if x := v.Compute; x != nil { + c := n.Child("compute") + requiredJobsComputeConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Compute") || n.IsRef("compute") { + c := n.Child("compute") + requiredJobsComputeConfig(w, &c, nil) } } -func requiredJobsNotebookTask(w *walker, n *node, v *jobs.NotebookTask) { +func requiredJobsNotebookTask(w *walker, n *walk.Node, v *jobs.NotebookTask) { if v == nil { - v = &jobs.NotebookTask{} + w.missing(n, "notebook_path") + return } - if !(v.NotebookPath != "") && !n.has("notebook_path") { + if !(v.NotebookPath != "" || walk.Forced(v.ForceSendFields, "NotebookPath")) { w.missing(n, "notebook_path") } } -func requiredJobsPipelineTask(w *walker, n *node, v *jobs.PipelineTask) { +func requiredJobsPipelineTask(w *walker, n *walk.Node, v *jobs.PipelineTask) { if v == nil { - v = &jobs.PipelineTask{} + w.missing(n, "pipeline_id") + return } - if !(v.PipelineId != "") && !n.has("pipeline_id") { + if !(v.PipelineId != "" || walk.Forced(v.ForceSendFields, "PipelineId")) { w.missing(n, "pipeline_id") } } -func requiredJobsPythonWheelTask(w *walker, n *node, v *jobs.PythonWheelTask) { +func requiredJobsPythonWheelTask(w *walker, n *walk.Node, v *jobs.PythonWheelTask) { if v == nil { - v = &jobs.PythonWheelTask{} + w.missing(n, "entry_point") + w.missing(n, "package_name") + return } - if !(v.EntryPoint != "") && !n.has("entry_point") { + if !(v.EntryPoint != "") { w.missing(n, "entry_point") } - if !(v.PackageName != "") && !n.has("package_name") { + if !(v.PackageName != "") { w.missing(n, "package_name") } } -func requiredJobsRunJobTask(w *walker, n *node, v *jobs.RunJobTask) { +func requiredJobsRunJobTask(w *walker, n *walk.Node, v *jobs.RunJobTask) { if v == nil { - v = &jobs.RunJobTask{} + w.missing(n, "job_id") + return } - if !(v.JobId != 0) && !n.has("job_id") { + if !(v.JobId != 0 || n.IsRef("job_id")) { w.missing(n, "job_id") } } -func requiredJobsSparkPythonTask(w *walker, n *node, v *jobs.SparkPythonTask) { +func requiredJobsSparkPythonTask(w *walker, n *walk.Node, v *jobs.SparkPythonTask) { if v == nil { - v = &jobs.SparkPythonTask{} + w.missing(n, "python_file") + return } - if !(v.PythonFile != "") && !n.has("python_file") { + if !(v.PythonFile != "") { w.missing(n, "python_file") } } -func requiredJobsSqlTask(w *walker, n *node, v *jobs.SqlTask) { +func requiredJobsSqlTask(w *walker, n *walk.Node, v *jobs.SqlTask) { if v == nil { - v = &jobs.SqlTask{} + w.missing(n, "warehouse_id") + return } - if !(v.WarehouseId != "") && !n.has("warehouse_id") { + if !(v.WarehouseId != "") { w.missing(n, "warehouse_id") } - if c := n.child("alert"); c.exists() { - requiredJobsSqlTaskAlert(w, &c, v.Alert) + if x := v.Alert; x != nil { + c := n.Child("alert") + requiredJobsSqlTaskAlert(w, &c, x) + } else if n.IsRef("alert") { + c := n.Child("alert") + requiredJobsSqlTaskAlert(w, &c, nil) } - if c := n.child("dashboard"); c.exists() { - requiredJobsSqlTaskDashboard(w, &c, v.Dashboard) + if x := v.Dashboard; x != nil { + c := n.Child("dashboard") + requiredJobsSqlTaskDashboard(w, &c, x) + } else if n.IsRef("dashboard") { + c := n.Child("dashboard") + requiredJobsSqlTaskDashboard(w, &c, nil) } - if c := n.child("file"); c.exists() { - requiredJobsSqlTaskFile(w, &c, v.File) + if x := v.File; x != nil { + c := n.Child("file") + requiredJobsSqlTaskFile(w, &c, x) + } else if n.IsRef("file") { + c := n.Child("file") + requiredJobsSqlTaskFile(w, &c, nil) } - if c := n.child("query"); c.exists() { - requiredJobsSqlTaskQuery(w, &c, v.Query) + if x := v.Query; x != nil { + c := n.Child("query") + requiredJobsSqlTaskQuery(w, &c, x) + } else if n.IsRef("query") { + c := n.Child("query") + requiredJobsSqlTaskQuery(w, &c, nil) } } -func requiredJobsFileArrivalTriggerConfiguration(w *walker, n *node, v *jobs.FileArrivalTriggerConfiguration) { +func requiredJobsFileArrivalTriggerConfiguration(w *walker, n *walk.Node, v *jobs.FileArrivalTriggerConfiguration) { if v == nil { - v = &jobs.FileArrivalTriggerConfiguration{} + w.missing(n, "url") + return } - if !(v.Url != "") && !n.has("url") { + if !(v.Url != "" || walk.Forced(v.ForceSendFields, "Url")) { w.missing(n, "url") } } -func requiredJobsModelTriggerConfiguration(w *walker, n *node, v *jobs.ModelTriggerConfiguration) { +func requiredJobsModelTriggerConfiguration(w *walker, n *walk.Node, v *jobs.ModelTriggerConfiguration) { if v == nil { - v = &jobs.ModelTriggerConfiguration{} + w.missing(n, "condition") + return } - if !(v.Condition != "") && !n.has("condition") { + if !(v.Condition != "" || walk.Forced(v.ForceSendFields, "Condition")) { w.missing(n, "condition") } } -func requiredJobsPeriodicTriggerConfiguration(w *walker, n *node, v *jobs.PeriodicTriggerConfiguration) { +func requiredJobsPeriodicTriggerConfiguration(w *walker, n *walk.Node, v *jobs.PeriodicTriggerConfiguration) { if v == nil { - v = &jobs.PeriodicTriggerConfiguration{} + w.missing(n, "interval") + w.missing(n, "unit") + return } - if !(v.Interval != 0) && !n.has("interval") { + if !(v.Interval != 0 || n.IsRef("interval")) { w.missing(n, "interval") } - if !(v.Unit != "") && !n.has("unit") { + if !(v.Unit != "") { w.missing(n, "unit") } } -func requiredJobsTableUpdateTriggerConfiguration(w *walker, n *node, v *jobs.TableUpdateTriggerConfiguration) { +func requiredJobsTableUpdateTriggerConfiguration(w *walker, n *walk.Node, v *jobs.TableUpdateTriggerConfiguration) { if v == nil { - v = &jobs.TableUpdateTriggerConfiguration{} + w.missing(n, "table_names") + return } - if !(v.TableNames != nil) && !n.has("table_names") { + if !(v.TableNames != nil || n.IsRef("table_names")) { w.missing(n, "table_names") } } -func requiredJobsContinuousTriggerConfiguration(w *walker, n *node, v *jobs.ContinuousTriggerConfiguration) { +func requiredJobsContinuousTriggerConfiguration(w *walker, n *walk.Node, v *jobs.ContinuousTriggerConfiguration) { if v == nil { - v = &jobs.ContinuousTriggerConfiguration{} + return } - if c := n.child("maintenance_window"); c.exists() { - requiredJobsMaintenanceWindow(w, &c, v.MaintenanceWindow) + if x := v.MaintenanceWindow; x != nil { + c := n.Child("maintenance_window") + requiredJobsMaintenanceWindow(w, &c, x) + } else if n.IsRef("maintenance_window") { + c := n.Child("maintenance_window") + requiredJobsMaintenanceWindow(w, &c, nil) } } -func requiredJobsCronTriggerConfiguration(w *walker, n *node, v *jobs.CronTriggerConfiguration) { +func requiredJobsCronTriggerConfiguration(w *walker, n *walk.Node, v *jobs.CronTriggerConfiguration) { if v == nil { - v = &jobs.CronTriggerConfiguration{} + w.missing(n, "quartz_cron_expression") + w.missing(n, "timezone_id") + return } - if !(v.QuartzCronExpression != "") && !n.has("quartz_cron_expression") { + if !(v.QuartzCronExpression != "") { w.missing(n, "quartz_cron_expression") } - if !(v.TimezoneId != "") && !n.has("timezone_id") { + if !(v.TimezoneId != "") { w.missing(n, "timezone_id") } } -func requiredJobsWebhook(w *walker, n *node, v *jobs.Webhook) { +func requiredJobsWebhook(w *walker, n *walk.Node, v *jobs.Webhook) { if v == nil { - v = &jobs.Webhook{} + w.missing(n, "id") + return } - if !(v.Id != "") && !n.has("id") { + if !(v.Id != "") { w.missing(n, "id") } } -func requiredCatalogRateLimit(w *walker, n *node, v *catalog.RateLimit) { +func requiredCatalogRateLimit(w *walker, n *walk.Node, v *catalog.RateLimit) { if v == nil { - v = &catalog.RateLimit{} + w.missing(n, "key") + w.missing(n, "renewal_period") + return } - if !(v.Key != "") && !n.has("key") { + if !(v.Key != "" || walk.Forced(v.ForceSendFields, "Key")) { w.missing(n, "key") } - if !(v.RenewalPeriod != "") && !n.has("renewal_period") { + if !(v.RenewalPeriod != "" || walk.Forced(v.ForceSendFields, "RenewalPeriod")) { w.missing(n, "renewal_period") } } -func requiredCatalogMcpServiceConfigSourceConnection(w *walker, n *node, v *catalog.McpServiceConfigSourceConnection) { +func requiredCatalogMcpServiceConfigSourceConnection(w *walker, n *walk.Node, v *catalog.McpServiceConfigSourceConnection) { if v == nil { - v = &catalog.McpServiceConfigSourceConnection{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "" || walk.Forced(v.ForceSendFields, "Name")) { w.missing(n, "name") } } -func requiredCatalogModelProviderServiceConfigAmazonBedrockProviderConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigAmazonBedrockProviderConfig) { +func requiredCatalogModelProviderServiceConfigAmazonBedrockProviderConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigAmazonBedrockProviderConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigAmazonBedrockProviderConfig{} + return } - if c := n.child("direct"); c.exists() { - requiredCatalogModelProviderServiceConfigAmazonBedrockProviderDirectConfig(w, &c, v.Direct) + if x := v.Direct; x != nil { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigAmazonBedrockProviderDirectConfig(w, &c, x) + } else if n.IsRef("direct") { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigAmazonBedrockProviderDirectConfig(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigAnthropicProviderConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigAnthropicProviderConfig) { +func requiredCatalogModelProviderServiceConfigAnthropicProviderConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigAnthropicProviderConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigAnthropicProviderConfig{} + return } - if c := n.child("direct"); c.exists() { - requiredCatalogModelProviderServiceConfigAnthropicProviderDirectConfig(w, &c, v.Direct) + if x := v.Direct; x != nil { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigAnthropicProviderDirectConfig(w, &c, x) + } else if n.IsRef("direct") { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigAnthropicProviderDirectConfig(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigAzureOpenAiProviderConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigAzureOpenAiProviderConfig) { +func requiredCatalogModelProviderServiceConfigAzureOpenAiProviderConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigAzureOpenAiProviderConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigAzureOpenAiProviderConfig{} + return } - if c := n.child("direct"); c.exists() { - requiredCatalogModelProviderServiceConfigAzureOpenAiProviderDirectConfig(w, &c, v.Direct) + if x := v.Direct; x != nil { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigAzureOpenAiProviderDirectConfig(w, &c, x) + } else if n.IsRef("direct") { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigAzureOpenAiProviderDirectConfig(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigCustomProviderConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigCustomProviderConfig) { +func requiredCatalogModelProviderServiceConfigCustomProviderConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigCustomProviderConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigCustomProviderConfig{} + return } - if c := n.child("direct"); c.exists() { - requiredCatalogModelProviderServiceConfigCustomProviderDirectConfig(w, &c, v.Direct) + if x := v.Direct; x != nil { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigCustomProviderDirectConfig(w, &c, x) + } else if n.IsRef("direct") { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigCustomProviderDirectConfig(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigGeminiEnterpriseProviderConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigGeminiEnterpriseProviderConfig) { +func requiredCatalogModelProviderServiceConfigGeminiEnterpriseProviderConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigGeminiEnterpriseProviderConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigGeminiEnterpriseProviderConfig{} + return } - if c := n.child("direct"); c.exists() { - requiredCatalogModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig(w, &c, v.Direct) + if x := v.Direct; x != nil { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig(w, &c, x) + } else if n.IsRef("direct") { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig(w, &c, nil) } } -func requiredCatalogInferenceTableConfig(w *walker, n *node, v *catalog.InferenceTableConfig) { +func requiredCatalogInferenceTableConfig(w *walker, n *walk.Node, v *catalog.InferenceTableConfig) { if v == nil { - v = &catalog.InferenceTableConfig{} + w.missing(n, "parent") + return } - if !(v.Parent != "") && !n.has("parent") { + if !(v.Parent != "" || walk.Forced(v.ForceSendFields, "Parent")) { w.missing(n, "parent") } } -func requiredCatalogModelProviderServiceConfigMicrosoftFoundryProviderConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigMicrosoftFoundryProviderConfig) { +func requiredCatalogModelProviderServiceConfigMicrosoftFoundryProviderConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigMicrosoftFoundryProviderConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigMicrosoftFoundryProviderConfig{} + return } - if c := n.child("direct"); c.exists() { - requiredCatalogModelProviderServiceConfigMicrosoftFoundryProviderDirectConfig(w, &c, v.Direct) + if x := v.Direct; x != nil { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigMicrosoftFoundryProviderDirectConfig(w, &c, x) + } else if n.IsRef("direct") { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigMicrosoftFoundryProviderDirectConfig(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigOpenAiProviderConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigOpenAiProviderConfig) { +func requiredCatalogModelProviderServiceConfigOpenAiProviderConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigOpenAiProviderConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigOpenAiProviderConfig{} + return } - if c := n.child("direct"); c.exists() { - requiredCatalogModelProviderServiceConfigOpenAiProviderDirectConfig(w, &c, v.Direct) + if x := v.Direct; x != nil { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigOpenAiProviderDirectConfig(w, &c, x) + } else if n.IsRef("direct") { + c := n.Child("direct") + requiredCatalogModelProviderServiceConfigOpenAiProviderDirectConfig(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigModelTargetConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigModelTargetConfig) { +func requiredCatalogModelProviderServiceConfigModelTargetConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigModelTargetConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigModelTargetConfig{} + w.missing(n, "model") + return } - if !(v.Model != "") && !n.has("model") { + if !(v.Model != "" || walk.Forced(v.ForceSendFields, "Model")) { w.missing(n, "model") } } -func requiredCatalogModelServiceConfigRoutingConfig(w *walker, n *node, v *catalog.ModelServiceConfigRoutingConfig) { +func requiredCatalogModelServiceConfigRoutingConfig(w *walker, n *walk.Node, v *catalog.ModelServiceConfigRoutingConfig) { if v == nil { - v = &catalog.ModelServiceConfigRoutingConfig{} + return } if len(v.Destinations) > 0 { - c := n.child("destinations") - for i0 := range v.Destinations { - e := c.elem(i0) - requiredCatalogModelServiceConfigDestinationConfig(w, &e, &v.Destinations[i0]) + c := n.Child("destinations") + for i := range v.Destinations { + e := c.Elem(i) + requiredCatalogModelServiceConfigDestinationConfig(w, &e, &v.Destinations[i]) } } - if c := n.child("fallback"); c.exists() { - requiredCatalogModelServiceConfigFallbackConfig(w, &c, v.Fallback) + if x := v.Fallback; x != nil { + c := n.Child("fallback") + requiredCatalogModelServiceConfigFallbackConfig(w, &c, x) + } else if n.IsRef("fallback") { + c := n.Child("fallback") + requiredCatalogModelServiceConfigFallbackConfig(w, &c, nil) } } -func requiredServingFallbackConfig(w *walker, n *node, v *serving.FallbackConfig) { +func requiredServingFallbackConfig(w *walker, n *walk.Node, v *serving.FallbackConfig) { if v == nil { - v = &serving.FallbackConfig{} + w.missing(n, "enabled") + return } - if !(v.Enabled) && !n.has("enabled") { + if !(v.Enabled || n.IsRef("enabled")) { w.missing(n, "enabled") } } -func requiredServingAiGatewayRateLimit(w *walker, n *node, v *serving.AiGatewayRateLimit) { +func requiredServingAiGatewayRateLimit(w *walker, n *walk.Node, v *serving.AiGatewayRateLimit) { if v == nil { - v = &serving.AiGatewayRateLimit{} + w.missing(n, "renewal_period") + return } - if !(v.RenewalPeriod != "") && !n.has("renewal_period") { + if !(v.RenewalPeriod != "" || walk.Forced(v.ForceSendFields, "RenewalPeriod")) { w.missing(n, "renewal_period") } } -func requiredServingServedEntityInput(w *walker, n *node, v *serving.ServedEntityInput) { +func requiredServingServedEntityInput(w *walker, n *walk.Node, v *serving.ServedEntityInput) { if v == nil { - v = &serving.ServedEntityInput{} + return } - if c := n.child("external_model"); c.exists() { - requiredServingExternalModel(w, &c, v.ExternalModel) + if x := v.ExternalModel; x != nil { + c := n.Child("external_model") + requiredServingExternalModel(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ExternalModel") || n.IsRef("external_model") { + c := n.Child("external_model") + requiredServingExternalModel(w, &c, nil) } } -func requiredServingServedModelInput(w *walker, n *node, v *serving.ServedModelInput) { +func requiredServingServedModelInput(w *walker, n *walk.Node, v *serving.ServedModelInput) { if v == nil { - v = &serving.ServedModelInput{} + w.missing(n, "model_name") + w.missing(n, "model_version") + w.missing(n, "scale_to_zero_enabled") + return } - if !(v.ModelName != "") && !n.has("model_name") { + if !(v.ModelName != "" || walk.Forced(v.ForceSendFields, "ModelName")) { w.missing(n, "model_name") } - if !(v.ModelVersion != "") && !n.has("model_version") { + if !(v.ModelVersion != "" || walk.Forced(v.ForceSendFields, "ModelVersion")) { w.missing(n, "model_version") } - if !(v.ScaleToZeroEnabled) && !n.has("scale_to_zero_enabled") { + if !(v.ScaleToZeroEnabled || walk.Forced(v.ForceSendFields, "ScaleToZeroEnabled") || n.IsRef("scale_to_zero_enabled")) { w.missing(n, "scale_to_zero_enabled") } } -func requiredServingTrafficConfig(w *walker, n *node, v *serving.TrafficConfig) { +func requiredServingTrafficConfig(w *walker, n *walk.Node, v *serving.TrafficConfig) { if v == nil { - v = &serving.TrafficConfig{} + return } if len(v.Routes) > 0 { - c := n.child("routes") - for i0 := range v.Routes { - e := c.elem(i0) - requiredServingRoute(w, &e, &v.Routes[i0]) + c := n.Child("routes") + for i := range v.Routes { + e := c.Elem(i) + requiredServingRoute(w, &e, &v.Routes[i]) } } } -func requiredPipelinesPipelineClusterAutoscale(w *walker, n *node, v *pipelines.PipelineClusterAutoscale) { +func requiredPipelinesPipelineClusterAutoscale(w *walker, n *walk.Node, v *pipelines.PipelineClusterAutoscale) { if v == nil { - v = &pipelines.PipelineClusterAutoscale{} + w.missing(n, "max_workers") + w.missing(n, "min_workers") + return } - if !(v.MaxWorkers != 0) && !n.has("max_workers") { + if !(v.MaxWorkers != 0 || n.IsRef("max_workers")) { w.missing(n, "max_workers") } - if !(v.MinWorkers != 0) && !n.has("min_workers") { + if !(v.MinWorkers != 0 || n.IsRef("min_workers")) { w.missing(n, "min_workers") } } -func requiredPipelinesDataStagingOptions(w *walker, n *node, v *pipelines.DataStagingOptions) { +func requiredPipelinesDataStagingOptions(w *walker, n *walk.Node, v *pipelines.DataStagingOptions) { if v == nil { - v = &pipelines.DataStagingOptions{} + w.missing(n, "catalog_name") + w.missing(n, "schema_name") + return } - if !(v.CatalogName != "") && !n.has("catalog_name") { + if !(v.CatalogName != "" || walk.Forced(v.ForceSendFields, "CatalogName")) { w.missing(n, "catalog_name") } - if !(v.SchemaName != "") && !n.has("schema_name") { + if !(v.SchemaName != "" || walk.Forced(v.ForceSendFields, "SchemaName")) { w.missing(n, "schema_name") } } -func requiredPipelinesOperationTimeWindow(w *walker, n *node, v *pipelines.OperationTimeWindow) { +func requiredPipelinesOperationTimeWindow(w *walker, n *walk.Node, v *pipelines.OperationTimeWindow) { if v == nil { - v = &pipelines.OperationTimeWindow{} + w.missing(n, "start_hour") + return } - if !(v.StartHour != 0) && !n.has("start_hour") { + if !(v.StartHour != 0 || walk.Forced(v.ForceSendFields, "StartHour") || n.IsRef("start_hour")) { w.missing(n, "start_hour") } } -func requiredPipelinesIngestionConfig(w *walker, n *node, v *pipelines.IngestionConfig) { +func requiredPipelinesIngestionConfig(w *walker, n *walk.Node, v *pipelines.IngestionConfig) { if v == nil { - v = &pipelines.IngestionConfig{} + return } - if c := n.child("report"); c.exists() { - requiredPipelinesReportSpec(w, &c, v.Report) + if x := v.Report; x != nil { + c := n.Child("report") + requiredPipelinesReportSpec(w, &c, x) + } else if n.IsRef("report") { + c := n.Child("report") + requiredPipelinesReportSpec(w, &c, nil) } - if c := n.child("schema"); c.exists() { - requiredPipelinesSchemaSpec(w, &c, v.Schema) + if x := v.Schema; x != nil { + c := n.Child("schema") + requiredPipelinesSchemaSpec(w, &c, x) + } else if n.IsRef("schema") { + c := n.Child("schema") + requiredPipelinesSchemaSpec(w, &c, nil) } - if c := n.child("table"); c.exists() { - requiredPipelinesTableSpec(w, &c, v.Table) + if x := v.Table; x != nil { + c := n.Child("table") + requiredPipelinesTableSpec(w, &c, x) + } else if n.IsRef("table") { + c := n.Child("table") + requiredPipelinesTableSpec(w, &c, nil) } } -func requiredPipelinesTableSpecificConfig(w *walker, n *node, v *pipelines.TableSpecificConfig) { +func requiredPipelinesTableSpecificConfig(w *walker, n *walk.Node, v *pipelines.TableSpecificConfig) { if v == nil { - v = &pipelines.TableSpecificConfig{} + return } - if c := n.child("auto_full_refresh_policy"); c.exists() { - requiredPipelinesAutoFullRefreshPolicy(w, &c, v.AutoFullRefreshPolicy) + if x := v.AutoFullRefreshPolicy; x != nil { + c := n.Child("auto_full_refresh_policy") + requiredPipelinesAutoFullRefreshPolicy(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "AutoFullRefreshPolicy") || n.IsRef("auto_full_refresh_policy") { + c := n.Child("auto_full_refresh_policy") + requiredPipelinesAutoFullRefreshPolicy(w, &c, nil) } } -func requiredPostgresMonthlySchedule(w *walker, n *node, v *postgres.MonthlySchedule) { +func requiredPostgresMonthlySchedule(w *walker, n *walk.Node, v *postgres.MonthlySchedule) { if v == nil { - v = &postgres.MonthlySchedule{} + w.missing(n, "day") + return } - if !(v.Day != 0) && !n.has("day") { + if !(v.Day != 0 || walk.Forced(v.ForceSendFields, "Day") || n.IsRef("day")) { w.missing(n, "day") } } -func requiredPostgresWeeklySchedule(w *walker, n *node, v *postgres.WeeklySchedule) { +func requiredPostgresWeeklySchedule(w *walker, n *walk.Node, v *postgres.WeeklySchedule) { if v == nil { - v = &postgres.WeeklySchedule{} + w.missing(n, "day_of_week") + return } - if !(v.DayOfWeek != "") && !n.has("day_of_week") { + if !(v.DayOfWeek != "" || walk.Forced(v.ForceSendFields, "DayOfWeek")) { w.missing(n, "day_of_week") } } -func requiredDatabaseSyncedTableSpecTypeOverride(w *walker, n *node, v *database.SyncedTableSpecTypeOverride) { +func requiredDatabaseSyncedTableSpecTypeOverride(w *walker, n *walk.Node, v *database.SyncedTableSpecTypeOverride) { if v == nil { - v = &database.SyncedTableSpecTypeOverride{} + w.missing(n, "column_name") + w.missing(n, "pg_type") + return } - if !(v.ColumnName != "") && !n.has("column_name") { + if !(v.ColumnName != "" || walk.Forced(v.ForceSendFields, "ColumnName")) { w.missing(n, "column_name") } - if !(v.PgType != "") && !n.has("pg_type") { + if !(v.PgType != "" || walk.Forced(v.ForceSendFields, "PgType")) { w.missing(n, "pg_type") } } -func requiredSqlAlertV2OperandColumn2(w *walker, n *node, v *sql.AlertV2OperandColumn) { +func requiredSqlAlertV2OperandColumn2(w *walker, n *walk.Node, v *sql.AlertV2OperandColumn) { if v == nil { - v = &sql.AlertV2OperandColumn{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "" || walk.Forced(v.ForceSendFields, "Name")) { w.missing(n, "name") } } -func requiredMlApproxCountDistinctFunction(w *walker, n *node, v *ml.ApproxCountDistinctFunction) { +func requiredMlApproxCountDistinctFunction(w *walker, n *walk.Node, v *ml.ApproxCountDistinctFunction) { if v == nil { - v = &ml.ApproxCountDistinctFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "" || walk.Forced(v.ForceSendFields, "Input")) { w.missing(n, "input") } } -func requiredMlApproxPercentileFunction(w *walker, n *node, v *ml.ApproxPercentileFunction) { +func requiredMlApproxPercentileFunction(w *walker, n *walk.Node, v *ml.ApproxPercentileFunction) { if v == nil { - v = &ml.ApproxPercentileFunction{} + w.missing(n, "input") + w.missing(n, "percentile") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "" || walk.Forced(v.ForceSendFields, "Input")) { w.missing(n, "input") } - if !(v.Percentile != 0) && !n.has("percentile") { + if !(v.Percentile != 0 || walk.Forced(v.ForceSendFields, "Percentile") || n.IsRef("percentile")) { w.missing(n, "percentile") } } -func requiredMlAvgFunction(w *walker, n *node, v *ml.AvgFunction) { +func requiredMlAvgFunction(w *walker, n *walk.Node, v *ml.AvgFunction) { if v == nil { - v = &ml.AvgFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } } -func requiredMlCountFunction(w *walker, n *node, v *ml.CountFunction) { +func requiredMlCountFunction(w *walker, n *walk.Node, v *ml.CountFunction) { if v == nil { - v = &ml.CountFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } } -func requiredMlFirstFunction(w *walker, n *node, v *ml.FirstFunction) { +func requiredMlFirstFunction(w *walker, n *walk.Node, v *ml.FirstFunction) { if v == nil { - v = &ml.FirstFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } } -func requiredMlFirstDistinctFunction(w *walker, n *node, v *ml.FirstDistinctFunction) { +func requiredMlFirstDistinctFunction(w *walker, n *walk.Node, v *ml.FirstDistinctFunction) { if v == nil { - v = &ml.FirstDistinctFunction{} + w.missing(n, "input") + w.missing(n, "n") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } - if !(v.N != 0) && !n.has("n") { + if !(v.N != 0 || n.IsRef("n")) { w.missing(n, "n") } } -func requiredMlFirstNFunction(w *walker, n *node, v *ml.FirstNFunction) { +func requiredMlFirstNFunction(w *walker, n *walk.Node, v *ml.FirstNFunction) { if v == nil { - v = &ml.FirstNFunction{} + w.missing(n, "input") + w.missing(n, "n") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } - if !(v.N != 0) && !n.has("n") { + if !(v.N != 0 || n.IsRef("n")) { w.missing(n, "n") } } -func requiredMlLastFunction(w *walker, n *node, v *ml.LastFunction) { +func requiredMlLastFunction(w *walker, n *walk.Node, v *ml.LastFunction) { if v == nil { - v = &ml.LastFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } } -func requiredMlLastDistinctFunction(w *walker, n *node, v *ml.LastDistinctFunction) { +func requiredMlLastDistinctFunction(w *walker, n *walk.Node, v *ml.LastDistinctFunction) { if v == nil { - v = &ml.LastDistinctFunction{} + w.missing(n, "input") + w.missing(n, "n") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } - if !(v.N != 0) && !n.has("n") { + if !(v.N != 0 || n.IsRef("n")) { w.missing(n, "n") } } -func requiredMlLastNFunction(w *walker, n *node, v *ml.LastNFunction) { +func requiredMlLastNFunction(w *walker, n *walk.Node, v *ml.LastNFunction) { if v == nil { - v = &ml.LastNFunction{} + w.missing(n, "input") + w.missing(n, "n") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } - if !(v.N != 0) && !n.has("n") { + if !(v.N != 0 || n.IsRef("n")) { w.missing(n, "n") } } -func requiredMlMaxFunction(w *walker, n *node, v *ml.MaxFunction) { +func requiredMlMaxFunction(w *walker, n *walk.Node, v *ml.MaxFunction) { if v == nil { - v = &ml.MaxFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } } -func requiredMlMinFunction(w *walker, n *node, v *ml.MinFunction) { +func requiredMlMinFunction(w *walker, n *walk.Node, v *ml.MinFunction) { if v == nil { - v = &ml.MinFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } } -func requiredMlStddevPopFunction(w *walker, n *node, v *ml.StddevPopFunction) { +func requiredMlStddevPopFunction(w *walker, n *walk.Node, v *ml.StddevPopFunction) { if v == nil { - v = &ml.StddevPopFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } } -func requiredMlStddevSampFunction(w *walker, n *node, v *ml.StddevSampFunction) { +func requiredMlStddevSampFunction(w *walker, n *walk.Node, v *ml.StddevSampFunction) { if v == nil { - v = &ml.StddevSampFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } } -func requiredMlSumFunction(w *walker, n *node, v *ml.SumFunction) { +func requiredMlSumFunction(w *walker, n *walk.Node, v *ml.SumFunction) { if v == nil { - v = &ml.SumFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } } -func requiredMlVarPopFunction(w *walker, n *node, v *ml.VarPopFunction) { +func requiredMlVarPopFunction(w *walker, n *walk.Node, v *ml.VarPopFunction) { if v == nil { - v = &ml.VarPopFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } } -func requiredMlVarSampFunction(w *walker, n *node, v *ml.VarSampFunction) { +func requiredMlVarSampFunction(w *walker, n *walk.Node, v *ml.VarSampFunction) { if v == nil { - v = &ml.VarSampFunction{} + w.missing(n, "input") + return } - if !(v.Input != "") && !n.has("input") { + if !(v.Input != "") { w.missing(n, "input") } } -func requiredMlInputBinding(w *walker, n *node, v *ml.InputBinding) { +func requiredMlInputBinding(w *walker, n *walk.Node, v *ml.InputBinding) { if v == nil { - v = &ml.InputBinding{} + w.missing(n, "column") + w.missing(n, "parameter") + return } - if !(v.Column != "") && !n.has("column") { + if !(v.Column != "") { w.missing(n, "column") } - if !(v.Parameter != "") && !n.has("parameter") { + if !(v.Parameter != "") { w.missing(n, "parameter") } } -func requiredMlFeatureReference(w *walker, n *node, v *ml.FeatureReference) { +func requiredMlFeatureReference(w *walker, n *walk.Node, v *ml.FeatureReference) { if v == nil { - v = &ml.FeatureReference{} + w.missing(n, "feature") + return } - if !(v.Feature != "") && !n.has("feature") { + if !(v.Feature != "") { w.missing(n, "feature") } } -func requiredMlColumnIdentifier(w *walker, n *node, v *ml.ColumnIdentifier) { +func requiredMlColumnIdentifier(w *walker, n *walk.Node, v *ml.ColumnIdentifier) { if v == nil { - v = &ml.ColumnIdentifier{} + w.missing(n, "variant_expr_path") + return } - if !(v.VariantExprPath != "") && !n.has("variant_expr_path") { + if !(v.VariantExprPath != "") { w.missing(n, "variant_expr_path") } } -func requiredMlColumnIdentifier2(w *walker, n *node, v *ml.ColumnIdentifier) { +func requiredMlColumnIdentifier2(w *walker, n *walk.Node, v *ml.ColumnIdentifier) { if v == nil { - v = &ml.ColumnIdentifier{} + w.missing(n, "variant_expr_path") + return } - if !(v.VariantExprPath != "") && !n.has("variant_expr_path") { + if !(v.VariantExprPath != "") { w.missing(n, "variant_expr_path") } } -func requiredMlFlatSchema(w *walker, n *node, v *ml.FlatSchema) { +func requiredMlFlatSchema(w *walker, n *walk.Node, v *ml.FlatSchema) { if v == nil { - v = &ml.FlatSchema{} + w.missing(n, "fields") + return } - if !(v.Fields != nil) && !n.has("fields") { + if !(v.Fields != nil || n.IsRef("fields")) { w.missing(n, "fields") } if len(v.Fields) > 0 { - c := n.child("fields") - for i0 := range v.Fields { - e := c.elem(i0) - requiredMlFieldDefinition(w, &e, &v.Fields[i0]) + c := n.Child("fields") + for i := range v.Fields { + e := c.Elem(i) + requiredMlFieldDefinition(w, &e, &v.Fields[i]) } } } -func requiredJobsDeploymentSpec(w *walker, n *node, v *jobs.DeploymentSpec) { +func requiredJobsDeploymentSpec(w *walker, n *walk.Node, v *jobs.DeploymentSpec) { if v == nil { - v = &jobs.DeploymentSpec{} + w.missing(n, "compute") + return } - if !n.has("compute") { + if !(!walk.IsZeroStruct(&v.Compute) || walk.Forced(v.ForceSendFields, "Compute") || n.HasRefs("compute")) { w.missing(n, "compute") } - if c := n.child("compute"); c.exists() { - requiredJobsComputeSpec(w, &c, &v.Compute) + if x := &v.Compute; !walk.IsZeroStruct(x) || walk.Forced(v.ForceSendFields, "Compute") || n.HasRefs("compute") { + c := n.Child("compute") + requiredJobsComputeSpec(w, &c, x) } } -func requiredJobsSubscription(w *walker, n *node, v *jobs.Subscription) { +func requiredJobsSubscription(w *walker, n *walk.Node, v *jobs.Subscription) { if v == nil { - v = &jobs.Subscription{} + return } - if c := n.child("attachment"); c.exists() { - requiredJobsSubscriptionAttachment(w, &c, v.Attachment) + if x := v.Attachment; x != nil { + c := n.Child("attachment") + requiredJobsSubscriptionAttachment(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Attachment") || n.IsRef("attachment") { + c := n.Child("attachment") + requiredJobsSubscriptionAttachment(w, &c, nil) } } -func requiredJobsTask2(w *walker, n *node, v *jobs.Task) { +func requiredJobsTask2(w *walker, n *walk.Node, v *jobs.Task) { if v == nil { - v = &jobs.Task{} - } - if !(v.TaskKey != "") && !n.has("task_key") { w.missing(n, "task_key") + return } - if c := n.child("ai_runtime_task"); c.exists() { - requiredJobsAiRuntimeTask(w, &c, v.AiRuntimeTask) - } - if c := n.child("clean_rooms_notebook_task"); c.exists() { - requiredJobsCleanRoomsNotebookTask(w, &c, v.CleanRoomsNotebookTask) - } - if c := n.child("condition_task"); c.exists() { - requiredJobsConditionTask(w, &c, v.ConditionTask) - } - if c := n.child("dashboard_task"); c.exists() { - requiredJobsDashboardTask(w, &c, v.DashboardTask) + if !(v.TaskKey != "" || walk.Forced(v.ForceSendFields, "TaskKey")) { + w.missing(n, "task_key") } - if c := n.child("dbt_task"); c.exists() { - requiredJobsDbtTask(w, &c, v.DbtTask) + if x := v.AiRuntimeTask; x != nil { + c := n.Child("ai_runtime_task") + requiredJobsAiRuntimeTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "AiRuntimeTask") || n.IsRef("ai_runtime_task") { + c := n.Child("ai_runtime_task") + requiredJobsAiRuntimeTask(w, &c, nil) + } + if x := v.CleanRoomsNotebookTask; x != nil { + c := n.Child("clean_rooms_notebook_task") + requiredJobsCleanRoomsNotebookTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "CleanRoomsNotebookTask") || n.IsRef("clean_rooms_notebook_task") { + c := n.Child("clean_rooms_notebook_task") + requiredJobsCleanRoomsNotebookTask(w, &c, nil) + } + if x := v.ConditionTask; x != nil { + c := n.Child("condition_task") + requiredJobsConditionTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ConditionTask") || n.IsRef("condition_task") { + c := n.Child("condition_task") + requiredJobsConditionTask(w, &c, nil) + } + if x := v.DashboardTask; x != nil { + c := n.Child("dashboard_task") + requiredJobsDashboardTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "DashboardTask") || n.IsRef("dashboard_task") { + c := n.Child("dashboard_task") + requiredJobsDashboardTask(w, &c, nil) + } + if x := v.DbtTask; x != nil { + c := n.Child("dbt_task") + requiredJobsDbtTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "DbtTask") || n.IsRef("dbt_task") { + c := n.Child("dbt_task") + requiredJobsDbtTask(w, &c, nil) } if len(v.DependsOn) > 0 { - c := n.child("depends_on") - for i0 := range v.DependsOn { - e := c.elem(i0) - requiredJobsTaskDependency(w, &e, &v.DependsOn[i0]) + c := n.Child("depends_on") + for i := range v.DependsOn { + e := c.Elem(i) + requiredJobsTaskDependency(w, &e, &v.DependsOn[i]) } } - if c := n.child("for_each_task"); c.exists() { - requiredJobsForEachTask2(w, &c, v.ForEachTask) - } - if c := n.child("gen_ai_compute_task"); c.exists() { - requiredJobsGenAiComputeTask(w, &c, v.GenAiComputeTask) - } - if c := n.child("health"); c.exists() { - requiredJobsJobsHealthRules(w, &c, v.Health) + if x := v.ForEachTask; x != nil { + c := n.Child("for_each_task") + requiredJobsForEachTask2(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ForEachTask") || n.IsRef("for_each_task") { + c := n.Child("for_each_task") + requiredJobsForEachTask2(w, &c, nil) + } + if x := v.GenAiComputeTask; x != nil { + c := n.Child("gen_ai_compute_task") + requiredJobsGenAiComputeTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "GenAiComputeTask") || n.IsRef("gen_ai_compute_task") { + c := n.Child("gen_ai_compute_task") + requiredJobsGenAiComputeTask(w, &c, nil) + } + if x := v.Health; x != nil { + c := n.Child("health") + requiredJobsJobsHealthRules(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "Health") || n.IsRef("health") { + c := n.Child("health") + requiredJobsJobsHealthRules(w, &c, nil) } if len(v.Libraries) > 0 { - c := n.child("libraries") - for i0 := range v.Libraries { - e := c.elem(i0) - requiredComputeLibrary(w, &e, &v.Libraries[i0]) + c := n.Child("libraries") + for i := range v.Libraries { + e := c.Elem(i) + requiredComputeLibrary(w, &e, &v.Libraries[i]) } } - if c := n.child("new_cluster"); c.exists() { - requiredComputeClusterSpec(w, &c, v.NewCluster) - } - if c := n.child("notebook_task"); c.exists() { - requiredJobsNotebookTask(w, &c, v.NotebookTask) - } - if c := n.child("pipeline_task"); c.exists() { - requiredJobsPipelineTask(w, &c, v.PipelineTask) - } - if c := n.child("python_wheel_task"); c.exists() { - requiredJobsPythonWheelTask(w, &c, v.PythonWheelTask) - } - if c := n.child("run_job_task"); c.exists() { - requiredJobsRunJobTask(w, &c, v.RunJobTask) - } - if c := n.child("spark_python_task"); c.exists() { - requiredJobsSparkPythonTask(w, &c, v.SparkPythonTask) - } - if c := n.child("sql_task"); c.exists() { - requiredJobsSqlTask(w, &c, v.SqlTask) - } - if c := n.child("webhook_notifications"); c.exists() { - requiredJobsWebhookNotifications(w, &c, v.WebhookNotifications) - } -} - -func requiredJobsComputeConfig(w *walker, n *node, v *jobs.ComputeConfig) { + if x := v.NewCluster; x != nil { + c := n.Child("new_cluster") + requiredComputeClusterSpec(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "NewCluster") || n.IsRef("new_cluster") { + c := n.Child("new_cluster") + requiredComputeClusterSpec(w, &c, nil) + } + if x := v.NotebookTask; x != nil { + c := n.Child("notebook_task") + requiredJobsNotebookTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "NotebookTask") || n.IsRef("notebook_task") { + c := n.Child("notebook_task") + requiredJobsNotebookTask(w, &c, nil) + } + if x := v.PipelineTask; x != nil { + c := n.Child("pipeline_task") + requiredJobsPipelineTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "PipelineTask") || n.IsRef("pipeline_task") { + c := n.Child("pipeline_task") + requiredJobsPipelineTask(w, &c, nil) + } + if x := v.PythonWheelTask; x != nil { + c := n.Child("python_wheel_task") + requiredJobsPythonWheelTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "PythonWheelTask") || n.IsRef("python_wheel_task") { + c := n.Child("python_wheel_task") + requiredJobsPythonWheelTask(w, &c, nil) + } + if x := v.RunJobTask; x != nil { + c := n.Child("run_job_task") + requiredJobsRunJobTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "RunJobTask") || n.IsRef("run_job_task") { + c := n.Child("run_job_task") + requiredJobsRunJobTask(w, &c, nil) + } + if x := v.SparkPythonTask; x != nil { + c := n.Child("spark_python_task") + requiredJobsSparkPythonTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "SparkPythonTask") || n.IsRef("spark_python_task") { + c := n.Child("spark_python_task") + requiredJobsSparkPythonTask(w, &c, nil) + } + if x := v.SqlTask; x != nil { + c := n.Child("sql_task") + requiredJobsSqlTask(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "SqlTask") || n.IsRef("sql_task") { + c := n.Child("sql_task") + requiredJobsSqlTask(w, &c, nil) + } + if x := v.WebhookNotifications; x != nil { + c := n.Child("webhook_notifications") + requiredJobsWebhookNotifications(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "WebhookNotifications") || n.IsRef("webhook_notifications") { + c := n.Child("webhook_notifications") + requiredJobsWebhookNotifications(w, &c, nil) + } +} + +func requiredJobsComputeConfig(w *walker, n *walk.Node, v *jobs.ComputeConfig) { if v == nil { - v = &jobs.ComputeConfig{} + w.missing(n, "num_gpus") + return } - if !(v.NumGpus != 0) && !n.has("num_gpus") { + if !(v.NumGpus != 0 || walk.Forced(v.ForceSendFields, "NumGpus") || n.IsRef("num_gpus")) { w.missing(n, "num_gpus") } } -func requiredJobsSqlTaskAlert(w *walker, n *node, v *jobs.SqlTaskAlert) { +func requiredJobsSqlTaskAlert(w *walker, n *walk.Node, v *jobs.SqlTaskAlert) { if v == nil { - v = &jobs.SqlTaskAlert{} + w.missing(n, "alert_id") + return } - if !(v.AlertId != "") && !n.has("alert_id") { + if !(v.AlertId != "" || walk.Forced(v.ForceSendFields, "AlertId")) { w.missing(n, "alert_id") } } -func requiredJobsSqlTaskDashboard(w *walker, n *node, v *jobs.SqlTaskDashboard) { +func requiredJobsSqlTaskDashboard(w *walker, n *walk.Node, v *jobs.SqlTaskDashboard) { if v == nil { - v = &jobs.SqlTaskDashboard{} + w.missing(n, "dashboard_id") + return } - if !(v.DashboardId != "") && !n.has("dashboard_id") { + if !(v.DashboardId != "" || walk.Forced(v.ForceSendFields, "DashboardId")) { w.missing(n, "dashboard_id") } } -func requiredJobsSqlTaskFile(w *walker, n *node, v *jobs.SqlTaskFile) { +func requiredJobsSqlTaskFile(w *walker, n *walk.Node, v *jobs.SqlTaskFile) { if v == nil { - v = &jobs.SqlTaskFile{} + w.missing(n, "path") + return } - if !(v.Path != "") && !n.has("path") { + if !(v.Path != "") { w.missing(n, "path") } } -func requiredJobsSqlTaskQuery(w *walker, n *node, v *jobs.SqlTaskQuery) { +func requiredJobsSqlTaskQuery(w *walker, n *walk.Node, v *jobs.SqlTaskQuery) { if v == nil { - v = &jobs.SqlTaskQuery{} + w.missing(n, "query_id") + return } - if !(v.QueryId != "") && !n.has("query_id") { + if !(v.QueryId != "") { w.missing(n, "query_id") } } -func requiredCatalogModelProviderServiceConfigAmazonBedrockProviderDirectConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigAmazonBedrockProviderDirectConfig) { +func requiredCatalogModelProviderServiceConfigAmazonBedrockProviderDirectConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigAmazonBedrockProviderDirectConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigAmazonBedrockProviderDirectConfig{} + return } - if c := n.child("aws_access_key"); c.exists() { - requiredCatalogModelProviderServiceConfigAwsAccessKey(w, &c, v.AwsAccessKey) + if x := v.AwsAccessKey; x != nil { + c := n.Child("aws_access_key") + requiredCatalogModelProviderServiceConfigAwsAccessKey(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "AwsAccessKey") || n.IsRef("aws_access_key") { + c := n.Child("aws_access_key") + requiredCatalogModelProviderServiceConfigAwsAccessKey(w, &c, nil) } - if c := n.child("service_credential"); c.exists() { - requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, v.ServiceCredential) + if x := v.ServiceCredential; x != nil { + c := n.Child("service_credential") + requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ServiceCredential") || n.IsRef("service_credential") { + c := n.Child("service_credential") + requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigAnthropicProviderDirectConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigAnthropicProviderDirectConfig) { +func requiredCatalogModelProviderServiceConfigAnthropicProviderDirectConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigAnthropicProviderDirectConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigAnthropicProviderDirectConfig{} + return } - if c := n.child("api_key"); c.exists() { - requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, v.ApiKey) + if x := v.ApiKey; x != nil { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, x) + } else if n.IsRef("api_key") { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigAzureOpenAiProviderDirectConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigAzureOpenAiProviderDirectConfig) { +func requiredCatalogModelProviderServiceConfigAzureOpenAiProviderDirectConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigAzureOpenAiProviderDirectConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigAzureOpenAiProviderDirectConfig{} + return } - if c := n.child("api_key"); c.exists() { - requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, v.ApiKey) + if x := v.ApiKey; x != nil { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ApiKey") || n.IsRef("api_key") { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, nil) } - if c := n.child("entra_service_principal"); c.exists() { - requiredCatalogModelProviderServiceConfigEntraServicePrincipal(w, &c, v.EntraServicePrincipal) + if x := v.EntraServicePrincipal; x != nil { + c := n.Child("entra_service_principal") + requiredCatalogModelProviderServiceConfigEntraServicePrincipal(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "EntraServicePrincipal") || n.IsRef("entra_service_principal") { + c := n.Child("entra_service_principal") + requiredCatalogModelProviderServiceConfigEntraServicePrincipal(w, &c, nil) } - if c := n.child("service_credential"); c.exists() { - requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, v.ServiceCredential) + if x := v.ServiceCredential; x != nil { + c := n.Child("service_credential") + requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ServiceCredential") || n.IsRef("service_credential") { + c := n.Child("service_credential") + requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigCustomProviderDirectConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigCustomProviderDirectConfig) { +func requiredCatalogModelProviderServiceConfigCustomProviderDirectConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigCustomProviderDirectConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigCustomProviderDirectConfig{} + return } - if c := n.child("api_key"); c.exists() { - requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, v.ApiKey) + if x := v.ApiKey; x != nil { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ApiKey") || n.IsRef("api_key") { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, nil) } - if c := n.child("header_auth"); c.exists() { - requiredCatalogModelProviderServiceConfigCustomProviderApiKeyHeaderAuth(w, &c, v.HeaderAuth) + if x := v.HeaderAuth; x != nil { + c := n.Child("header_auth") + requiredCatalogModelProviderServiceConfigCustomProviderApiKeyHeaderAuth(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "HeaderAuth") || n.IsRef("header_auth") { + c := n.Child("header_auth") + requiredCatalogModelProviderServiceConfigCustomProviderApiKeyHeaderAuth(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig) { +func requiredCatalogModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig{} + return } - if c := n.child("api_key"); c.exists() { - requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, v.ApiKey) + if x := v.ApiKey; x != nil { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ApiKey") || n.IsRef("api_key") { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, nil) } - if c := n.child("service_credential"); c.exists() { - requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, v.ServiceCredential) + if x := v.ServiceCredential; x != nil { + c := n.Child("service_credential") + requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ServiceCredential") || n.IsRef("service_credential") { + c := n.Child("service_credential") + requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigMicrosoftFoundryProviderDirectConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigMicrosoftFoundryProviderDirectConfig) { +func requiredCatalogModelProviderServiceConfigMicrosoftFoundryProviderDirectConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigMicrosoftFoundryProviderDirectConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigMicrosoftFoundryProviderDirectConfig{} + return } - if c := n.child("api_key"); c.exists() { - requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, v.ApiKey) + if x := v.ApiKey; x != nil { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ApiKey") || n.IsRef("api_key") { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, nil) } - if c := n.child("entra_service_principal"); c.exists() { - requiredCatalogModelProviderServiceConfigEntraServicePrincipal(w, &c, v.EntraServicePrincipal) + if x := v.EntraServicePrincipal; x != nil { + c := n.Child("entra_service_principal") + requiredCatalogModelProviderServiceConfigEntraServicePrincipal(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "EntraServicePrincipal") || n.IsRef("entra_service_principal") { + c := n.Child("entra_service_principal") + requiredCatalogModelProviderServiceConfigEntraServicePrincipal(w, &c, nil) } - if c := n.child("service_credential"); c.exists() { - requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, v.ServiceCredential) + if x := v.ServiceCredential; x != nil { + c := n.Child("service_credential") + requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ServiceCredential") || n.IsRef("service_credential") { + c := n.Child("service_credential") + requiredCatalogModelProviderServiceConfigServiceCredential(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigOpenAiProviderDirectConfig(w *walker, n *node, v *catalog.ModelProviderServiceConfigOpenAiProviderDirectConfig) { +func requiredCatalogModelProviderServiceConfigOpenAiProviderDirectConfig(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigOpenAiProviderDirectConfig) { if v == nil { - v = &catalog.ModelProviderServiceConfigOpenAiProviderDirectConfig{} + return } - if c := n.child("api_key"); c.exists() { - requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, v.ApiKey) + if x := v.ApiKey; x != nil { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ApiKey") || n.IsRef("api_key") { + c := n.Child("api_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, nil) } } -func requiredCatalogModelServiceConfigDestinationConfig(w *walker, n *node, v *catalog.ModelServiceConfigDestinationConfig) { +func requiredCatalogModelServiceConfigDestinationConfig(w *walker, n *walk.Node, v *catalog.ModelServiceConfigDestinationConfig) { if v == nil { - v = &catalog.ModelServiceConfigDestinationConfig{} + w.missing(n, "destination_type") + w.missing(n, "name") + return } - if !(v.DestinationType != "") && !n.has("destination_type") { + if !(v.DestinationType != "" || walk.Forced(v.ForceSendFields, "DestinationType")) { w.missing(n, "destination_type") } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "" || walk.Forced(v.ForceSendFields, "Name")) { w.missing(n, "name") } - if c := n.child("external_model_config"); c.exists() { - requiredCatalogModelServiceConfigExternalModelConfig(w, &c, v.ExternalModelConfig) + if x := v.ExternalModelConfig; x != nil { + c := n.Child("external_model_config") + requiredCatalogModelServiceConfigExternalModelConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ExternalModelConfig") || n.IsRef("external_model_config") { + c := n.Child("external_model_config") + requiredCatalogModelServiceConfigExternalModelConfig(w, &c, nil) } - if c := n.child("pay_per_token_config"); c.exists() { - requiredCatalogModelServiceConfigPayPerTokenConfig(w, &c, v.PayPerTokenConfig) + if x := v.PayPerTokenConfig; x != nil { + c := n.Child("pay_per_token_config") + requiredCatalogModelServiceConfigPayPerTokenConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "PayPerTokenConfig") || n.IsRef("pay_per_token_config") { + c := n.Child("pay_per_token_config") + requiredCatalogModelServiceConfigPayPerTokenConfig(w, &c, nil) } - if c := n.child("provisioned_throughput_config"); c.exists() { - requiredCatalogModelServiceConfigProvisionedThroughputConfig(w, &c, v.ProvisionedThroughputConfig) + if x := v.ProvisionedThroughputConfig; x != nil { + c := n.Child("provisioned_throughput_config") + requiredCatalogModelServiceConfigProvisionedThroughputConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ProvisionedThroughputConfig") || n.IsRef("provisioned_throughput_config") { + c := n.Child("provisioned_throughput_config") + requiredCatalogModelServiceConfigProvisionedThroughputConfig(w, &c, nil) } } -func requiredCatalogModelServiceConfigFallbackConfig(w *walker, n *node, v *catalog.ModelServiceConfigFallbackConfig) { +func requiredCatalogModelServiceConfigFallbackConfig(w *walker, n *walk.Node, v *catalog.ModelServiceConfigFallbackConfig) { if v == nil { - v = &catalog.ModelServiceConfigFallbackConfig{} + return } if len(v.Destinations) > 0 { - c := n.child("destinations") - for i0 := range v.Destinations { - e := c.elem(i0) - requiredCatalogModelServiceConfigDestinationConfig(w, &e, &v.Destinations[i0]) + c := n.Child("destinations") + for i := range v.Destinations { + e := c.Elem(i) + requiredCatalogModelServiceConfigDestinationConfig(w, &e, &v.Destinations[i]) } } } -func requiredServingExternalModel(w *walker, n *node, v *serving.ExternalModel) { +func requiredServingExternalModel(w *walker, n *walk.Node, v *serving.ExternalModel) { if v == nil { - v = &serving.ExternalModel{} + w.missing(n, "name") + w.missing(n, "provider") + w.missing(n, "task") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } - if !(v.Provider != "") && !n.has("provider") { + if !(v.Provider != "") { w.missing(n, "provider") } - if !(v.Task != "") && !n.has("task") { + if !(v.Task != "") { w.missing(n, "task") } - if c := n.child("amazon_bedrock_config"); c.exists() { - requiredServingAmazonBedrockConfig(w, &c, v.AmazonBedrockConfig) + if x := v.AmazonBedrockConfig; x != nil { + c := n.Child("amazon_bedrock_config") + requiredServingAmazonBedrockConfig(w, &c, x) + } else if n.IsRef("amazon_bedrock_config") { + c := n.Child("amazon_bedrock_config") + requiredServingAmazonBedrockConfig(w, &c, nil) } - if c := n.child("custom_provider_config"); c.exists() { - requiredServingCustomProviderConfig(w, &c, v.CustomProviderConfig) + if x := v.CustomProviderConfig; x != nil { + c := n.Child("custom_provider_config") + requiredServingCustomProviderConfig(w, &c, x) + } else if n.IsRef("custom_provider_config") { + c := n.Child("custom_provider_config") + requiredServingCustomProviderConfig(w, &c, nil) } - if c := n.child("databricks_model_serving_config"); c.exists() { - requiredServingDatabricksModelServingConfig(w, &c, v.DatabricksModelServingConfig) + if x := v.DatabricksModelServingConfig; x != nil { + c := n.Child("databricks_model_serving_config") + requiredServingDatabricksModelServingConfig(w, &c, x) + } else if n.IsRef("databricks_model_serving_config") { + c := n.Child("databricks_model_serving_config") + requiredServingDatabricksModelServingConfig(w, &c, nil) } - if c := n.child("google_cloud_vertex_ai_config"); c.exists() { - requiredServingGoogleCloudVertexAiConfig(w, &c, v.GoogleCloudVertexAiConfig) + if x := v.GoogleCloudVertexAiConfig; x != nil { + c := n.Child("google_cloud_vertex_ai_config") + requiredServingGoogleCloudVertexAiConfig(w, &c, x) + } else if n.IsRef("google_cloud_vertex_ai_config") { + c := n.Child("google_cloud_vertex_ai_config") + requiredServingGoogleCloudVertexAiConfig(w, &c, nil) } } -func requiredServingRoute(w *walker, n *node, v *serving.Route) { +func requiredServingRoute(w *walker, n *walk.Node, v *serving.Route) { if v == nil { - v = &serving.Route{} + w.missing(n, "traffic_percentage") + return } - if !(v.TrafficPercentage != 0) && !n.has("traffic_percentage") { + if !(v.TrafficPercentage != 0 || walk.Forced(v.ForceSendFields, "TrafficPercentage") || n.IsRef("traffic_percentage")) { w.missing(n, "traffic_percentage") } } -func requiredPipelinesReportSpec(w *walker, n *node, v *pipelines.ReportSpec) { +func requiredPipelinesReportSpec(w *walker, n *walk.Node, v *pipelines.ReportSpec) { if v == nil { - v = &pipelines.ReportSpec{} + w.missing(n, "destination_catalog") + w.missing(n, "destination_schema") + w.missing(n, "source_url") + return } - if !(v.DestinationCatalog != "") && !n.has("destination_catalog") { + if !(v.DestinationCatalog != "" || walk.Forced(v.ForceSendFields, "DestinationCatalog")) { w.missing(n, "destination_catalog") } - if !(v.DestinationSchema != "") && !n.has("destination_schema") { + if !(v.DestinationSchema != "" || walk.Forced(v.ForceSendFields, "DestinationSchema")) { w.missing(n, "destination_schema") } - if !(v.SourceUrl != "") && !n.has("source_url") { + if !(v.SourceUrl != "" || walk.Forced(v.ForceSendFields, "SourceUrl")) { w.missing(n, "source_url") } - if c := n.child("table_configuration"); c.exists() { - requiredPipelinesTableSpecificConfig(w, &c, v.TableConfiguration) + if x := v.TableConfiguration; x != nil { + c := n.Child("table_configuration") + requiredPipelinesTableSpecificConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "TableConfiguration") || n.IsRef("table_configuration") { + c := n.Child("table_configuration") + requiredPipelinesTableSpecificConfig(w, &c, nil) } } -func requiredPipelinesSchemaSpec(w *walker, n *node, v *pipelines.SchemaSpec) { +func requiredPipelinesSchemaSpec(w *walker, n *walk.Node, v *pipelines.SchemaSpec) { if v == nil { - v = &pipelines.SchemaSpec{} + w.missing(n, "destination_catalog") + w.missing(n, "destination_schema") + return } - if !(v.DestinationCatalog != "") && !n.has("destination_catalog") { + if !(v.DestinationCatalog != "" || walk.Forced(v.ForceSendFields, "DestinationCatalog")) { w.missing(n, "destination_catalog") } - if !(v.DestinationSchema != "") && !n.has("destination_schema") { + if !(v.DestinationSchema != "" || walk.Forced(v.ForceSendFields, "DestinationSchema")) { w.missing(n, "destination_schema") } - if c := n.child("connector_options"); c.exists() { - requiredPipelinesConnectorOptions(w, &c, v.ConnectorOptions) + if x := v.ConnectorOptions; x != nil { + c := n.Child("connector_options") + requiredPipelinesConnectorOptions(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ConnectorOptions") || n.IsRef("connector_options") { + c := n.Child("connector_options") + requiredPipelinesConnectorOptions(w, &c, nil) } - if c := n.child("table_configuration"); c.exists() { - requiredPipelinesTableSpecificConfig(w, &c, v.TableConfiguration) + if x := v.TableConfiguration; x != nil { + c := n.Child("table_configuration") + requiredPipelinesTableSpecificConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "TableConfiguration") || n.IsRef("table_configuration") { + c := n.Child("table_configuration") + requiredPipelinesTableSpecificConfig(w, &c, nil) } } -func requiredPipelinesTableSpec(w *walker, n *node, v *pipelines.TableSpec) { +func requiredPipelinesTableSpec(w *walker, n *walk.Node, v *pipelines.TableSpec) { if v == nil { - v = &pipelines.TableSpec{} + w.missing(n, "destination_catalog") + w.missing(n, "destination_schema") + return } - if !(v.DestinationCatalog != "") && !n.has("destination_catalog") { + if !(v.DestinationCatalog != "" || walk.Forced(v.ForceSendFields, "DestinationCatalog")) { w.missing(n, "destination_catalog") } - if !(v.DestinationSchema != "") && !n.has("destination_schema") { + if !(v.DestinationSchema != "" || walk.Forced(v.ForceSendFields, "DestinationSchema")) { w.missing(n, "destination_schema") } - if c := n.child("connector_options"); c.exists() { - requiredPipelinesConnectorOptions(w, &c, v.ConnectorOptions) + if x := v.ConnectorOptions; x != nil { + c := n.Child("connector_options") + requiredPipelinesConnectorOptions(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ConnectorOptions") || n.IsRef("connector_options") { + c := n.Child("connector_options") + requiredPipelinesConnectorOptions(w, &c, nil) } - if c := n.child("table_configuration"); c.exists() { - requiredPipelinesTableSpecificConfig(w, &c, v.TableConfiguration) + if x := v.TableConfiguration; x != nil { + c := n.Child("table_configuration") + requiredPipelinesTableSpecificConfig(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "TableConfiguration") || n.IsRef("table_configuration") { + c := n.Child("table_configuration") + requiredPipelinesTableSpecificConfig(w, &c, nil) } } -func requiredPipelinesAutoFullRefreshPolicy(w *walker, n *node, v *pipelines.AutoFullRefreshPolicy) { +func requiredPipelinesAutoFullRefreshPolicy(w *walker, n *walk.Node, v *pipelines.AutoFullRefreshPolicy) { if v == nil { - v = &pipelines.AutoFullRefreshPolicy{} + w.missing(n, "enabled") + return } - if !(v.Enabled) && !n.has("enabled") { + if !(v.Enabled || walk.Forced(v.ForceSendFields, "Enabled") || n.IsRef("enabled")) { w.missing(n, "enabled") } } -func requiredMlFieldDefinition(w *walker, n *node, v *ml.FieldDefinition) { +func requiredMlFieldDefinition(w *walker, n *walk.Node, v *ml.FieldDefinition) { if v == nil { - v = &ml.FieldDefinition{} + w.missing(n, "data_type") + w.missing(n, "name") + return } - if !(v.DataType != "") && !n.has("data_type") { + if !(v.DataType != "") { w.missing(n, "data_type") } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } } -func requiredJobsComputeSpec(w *walker, n *node, v *jobs.ComputeSpec) { +func requiredJobsComputeSpec(w *walker, n *walk.Node, v *jobs.ComputeSpec) { if v == nil { - v = &jobs.ComputeSpec{} + w.missing(n, "accelerator_count") + w.missing(n, "accelerator_type") + return } - if !(v.AcceleratorCount != 0) && !n.has("accelerator_count") { + if !(v.AcceleratorCount != 0 || walk.Forced(v.ForceSendFields, "AcceleratorCount") || n.IsRef("accelerator_count")) { w.missing(n, "accelerator_count") } - if !(v.AcceleratorType != "") && !n.has("accelerator_type") { + if !(v.AcceleratorType != "" || walk.Forced(v.ForceSendFields, "AcceleratorType")) { w.missing(n, "accelerator_type") } } -func requiredJobsSubscriptionAttachment(w *walker, n *node, v *jobs.SubscriptionAttachment) { +func requiredJobsSubscriptionAttachment(w *walker, n *walk.Node, v *jobs.SubscriptionAttachment) { if v == nil { - v = &jobs.SubscriptionAttachment{} + return } - if c := n.child("data_attachment"); c.exists() { - requiredJobsSubscriptionAttachmentDataAttachment(w, &c, v.DataAttachment) + if x := v.DataAttachment; x != nil { + c := n.Child("data_attachment") + requiredJobsSubscriptionAttachmentDataAttachment(w, &c, x) + } else if n.IsRef("data_attachment") { + c := n.Child("data_attachment") + requiredJobsSubscriptionAttachmentDataAttachment(w, &c, nil) } } -func requiredJobsForEachTask2(w *walker, n *node, v *jobs.ForEachTask) { +func requiredJobsForEachTask2(w *walker, n *walk.Node, v *jobs.ForEachTask) { if v == nil { - v = &jobs.ForEachTask{} + w.missing(n, "inputs") + w.missing(n, "task") + return } - if !(v.Inputs != "") && !n.has("inputs") { + if !(v.Inputs != "" || walk.Forced(v.ForceSendFields, "Inputs")) { w.missing(n, "inputs") } - if !n.has("task") { + if !(!walk.IsZeroStruct(&v.Task) || walk.Forced(v.ForceSendFields, "Task") || n.HasRefs("task")) { w.missing(n, "task") } } -func requiredCatalogModelProviderServiceConfigAwsAccessKey(w *walker, n *node, v *catalog.ModelProviderServiceConfigAwsAccessKey) { +func requiredCatalogModelProviderServiceConfigAwsAccessKey(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigAwsAccessKey) { if v == nil { - v = &catalog.ModelProviderServiceConfigAwsAccessKey{} + return } - if c := n.child("secret_access_key"); c.exists() { - requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, v.SecretAccessKey) + if x := v.SecretAccessKey; x != nil { + c := n.Child("secret_access_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "SecretAccessKey") || n.IsRef("secret_access_key") { + c := n.Child("secret_access_key") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigServiceCredential(w *walker, n *node, v *catalog.ModelProviderServiceConfigServiceCredential) { +func requiredCatalogModelProviderServiceConfigServiceCredential(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigServiceCredential) { if v == nil { - v = &catalog.ModelProviderServiceConfigServiceCredential{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } } -func requiredCatalogModelProviderServiceConfigProviderSecret(w *walker, n *node, v *catalog.ModelProviderServiceConfigProviderSecret) { +func requiredCatalogModelProviderServiceConfigProviderSecret(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigProviderSecret) { if v == nil { - v = &catalog.ModelProviderServiceConfigProviderSecret{} + return } - if c := n.child("secret_reference"); c.exists() { - requiredCatalogModelProviderServiceConfigSecretReference(w, &c, v.SecretReference) + if x := v.SecretReference; x != nil { + c := n.Child("secret_reference") + requiredCatalogModelProviderServiceConfigSecretReference(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "SecretReference") || n.IsRef("secret_reference") { + c := n.Child("secret_reference") + requiredCatalogModelProviderServiceConfigSecretReference(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigEntraServicePrincipal(w *walker, n *node, v *catalog.ModelProviderServiceConfigEntraServicePrincipal) { +func requiredCatalogModelProviderServiceConfigEntraServicePrincipal(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigEntraServicePrincipal) { if v == nil { - v = &catalog.ModelProviderServiceConfigEntraServicePrincipal{} + return } - if c := n.child("client_secret"); c.exists() { - requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, v.ClientSecret) + if x := v.ClientSecret; x != nil { + c := n.Child("client_secret") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ClientSecret") || n.IsRef("client_secret") { + c := n.Child("client_secret") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, nil) } } -func requiredCatalogModelProviderServiceConfigCustomProviderApiKeyHeaderAuth(w *walker, n *node, v *catalog.ModelProviderServiceConfigCustomProviderApiKeyHeaderAuth) { +func requiredCatalogModelProviderServiceConfigCustomProviderApiKeyHeaderAuth(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigCustomProviderApiKeyHeaderAuth) { if v == nil { - v = &catalog.ModelProviderServiceConfigCustomProviderApiKeyHeaderAuth{} + return } - if c := n.child("api_key_value"); c.exists() { - requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, v.ApiKeyValue) + if x := v.ApiKeyValue; x != nil { + c := n.Child("api_key_value") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "ApiKeyValue") || n.IsRef("api_key_value") { + c := n.Child("api_key_value") + requiredCatalogModelProviderServiceConfigProviderSecret(w, &c, nil) } } -func requiredCatalogModelServiceConfigExternalModelConfig(w *walker, n *node, v *catalog.ModelServiceConfigExternalModelConfig) { +func requiredCatalogModelServiceConfigExternalModelConfig(w *walker, n *walk.Node, v *catalog.ModelServiceConfigExternalModelConfig) { if v == nil { - v = &catalog.ModelServiceConfigExternalModelConfig{} + w.missing(n, "model_provider_service") + w.missing(n, "target") + return } - if !(v.ModelProviderService != "") && !n.has("model_provider_service") { + if !(v.ModelProviderService != "") { w.missing(n, "model_provider_service") } - if !n.has("target") { + if !(!walk.IsZeroStruct(&v.Target) || n.HasRefs("target")) { w.missing(n, "target") } - if c := n.child("target"); c.exists() { - requiredCatalogModelProviderServiceConfigModelTargetConfig(w, &c, &v.Target) + if x := &v.Target; !walk.IsZeroStruct(x) || n.HasRefs("target") { + c := n.Child("target") + requiredCatalogModelProviderServiceConfigModelTargetConfig(w, &c, x) } } -func requiredCatalogModelServiceConfigPayPerTokenConfig(w *walker, n *node, v *catalog.ModelServiceConfigPayPerTokenConfig) { +func requiredCatalogModelServiceConfigPayPerTokenConfig(w *walker, n *walk.Node, v *catalog.ModelServiceConfigPayPerTokenConfig) { if v == nil { - v = &catalog.ModelServiceConfigPayPerTokenConfig{} + w.missing(n, "model") + return } - if !(v.Model != "") && !n.has("model") { + if !(v.Model != "") { w.missing(n, "model") } } -func requiredCatalogModelServiceConfigProvisionedThroughputConfig(w *walker, n *node, v *catalog.ModelServiceConfigProvisionedThroughputConfig) { +func requiredCatalogModelServiceConfigProvisionedThroughputConfig(w *walker, n *walk.Node, v *catalog.ModelServiceConfigProvisionedThroughputConfig) { if v == nil { - v = &catalog.ModelServiceConfigProvisionedThroughputConfig{} + w.missing(n, "model_serving_endpoint") + return } - if !(v.ModelServingEndpoint != "") && !n.has("model_serving_endpoint") { + if !(v.ModelServingEndpoint != "" || walk.Forced(v.ForceSendFields, "ModelServingEndpoint")) { w.missing(n, "model_serving_endpoint") } } -func requiredServingAmazonBedrockConfig(w *walker, n *node, v *serving.AmazonBedrockConfig) { +func requiredServingAmazonBedrockConfig(w *walker, n *walk.Node, v *serving.AmazonBedrockConfig) { if v == nil { - v = &serving.AmazonBedrockConfig{} + w.missing(n, "aws_region") + w.missing(n, "bedrock_provider") + return } - if !(v.AwsRegion != "") && !n.has("aws_region") { + if !(v.AwsRegion != "" || walk.Forced(v.ForceSendFields, "AwsRegion")) { w.missing(n, "aws_region") } - if !(v.BedrockProvider != "") && !n.has("bedrock_provider") { + if !(v.BedrockProvider != "" || walk.Forced(v.ForceSendFields, "BedrockProvider")) { w.missing(n, "bedrock_provider") } } -func requiredServingCustomProviderConfig(w *walker, n *node, v *serving.CustomProviderConfig) { +func requiredServingCustomProviderConfig(w *walker, n *walk.Node, v *serving.CustomProviderConfig) { if v == nil { - v = &serving.CustomProviderConfig{} + w.missing(n, "custom_provider_url") + return } - if !(v.CustomProviderUrl != "") && !n.has("custom_provider_url") { + if !(v.CustomProviderUrl != "") { w.missing(n, "custom_provider_url") } - if c := n.child("api_key_auth"); c.exists() { - requiredServingApiKeyAuth(w, &c, v.ApiKeyAuth) + if x := v.ApiKeyAuth; x != nil { + c := n.Child("api_key_auth") + requiredServingApiKeyAuth(w, &c, x) + } else if n.IsRef("api_key_auth") { + c := n.Child("api_key_auth") + requiredServingApiKeyAuth(w, &c, nil) } } -func requiredServingDatabricksModelServingConfig(w *walker, n *node, v *serving.DatabricksModelServingConfig) { +func requiredServingDatabricksModelServingConfig(w *walker, n *walk.Node, v *serving.DatabricksModelServingConfig) { if v == nil { - v = &serving.DatabricksModelServingConfig{} + w.missing(n, "databricks_workspace_url") + return } - if !(v.DatabricksWorkspaceUrl != "") && !n.has("databricks_workspace_url") { + if !(v.DatabricksWorkspaceUrl != "" || walk.Forced(v.ForceSendFields, "DatabricksWorkspaceUrl")) { w.missing(n, "databricks_workspace_url") } } -func requiredServingGoogleCloudVertexAiConfig(w *walker, n *node, v *serving.GoogleCloudVertexAiConfig) { +func requiredServingGoogleCloudVertexAiConfig(w *walker, n *walk.Node, v *serving.GoogleCloudVertexAiConfig) { if v == nil { - v = &serving.GoogleCloudVertexAiConfig{} + w.missing(n, "project_id") + w.missing(n, "region") + return } - if !(v.ProjectId != "") && !n.has("project_id") { + if !(v.ProjectId != "" || walk.Forced(v.ForceSendFields, "ProjectId")) { w.missing(n, "project_id") } - if !(v.Region != "") && !n.has("region") { + if !(v.Region != "" || walk.Forced(v.ForceSendFields, "Region")) { w.missing(n, "region") } } -func requiredPipelinesConnectorOptions(w *walker, n *node, v *pipelines.ConnectorOptions) { +func requiredPipelinesConnectorOptions(w *walker, n *walk.Node, v *pipelines.ConnectorOptions) { if v == nil { - v = &pipelines.ConnectorOptions{} + return } - if c := n.child("google_ads_options"); c.exists() { - requiredPipelinesGoogleAdsOptions(w, &c, v.GoogleAdsOptions) + if x := v.GoogleAdsOptions; x != nil { + c := n.Child("google_ads_options") + requiredPipelinesGoogleAdsOptions(w, &c, x) + } else if n.IsRef("google_ads_options") { + c := n.Child("google_ads_options") + requiredPipelinesGoogleAdsOptions(w, &c, nil) } - if c := n.child("linkedin_ads_options"); c.exists() { - requiredPipelinesLinkedInAdsOptions(w, &c, v.LinkedinAdsOptions) + if x := v.LinkedinAdsOptions; x != nil { + c := n.Child("linkedin_ads_options") + requiredPipelinesLinkedInAdsOptions(w, &c, x) + } else if n.IsRef("linkedin_ads_options") { + c := n.Child("linkedin_ads_options") + requiredPipelinesLinkedInAdsOptions(w, &c, nil) } - if c := n.child("rabbitmq_options"); c.exists() { - requiredPipelinesRabbitmqOptions(w, &c, v.RabbitmqOptions) + if x := v.RabbitmqOptions; x != nil { + c := n.Child("rabbitmq_options") + requiredPipelinesRabbitmqOptions(w, &c, x) + } else if n.IsRef("rabbitmq_options") { + c := n.Child("rabbitmq_options") + requiredPipelinesRabbitmqOptions(w, &c, nil) } } -func requiredJobsSubscriptionAttachmentDataAttachment(w *walker, n *node, v *jobs.SubscriptionAttachmentDataAttachment) { +func requiredJobsSubscriptionAttachmentDataAttachment(w *walker, n *walk.Node, v *jobs.SubscriptionAttachmentDataAttachment) { if v == nil { - v = &jobs.SubscriptionAttachmentDataAttachment{} + w.missing(n, "file_format") + w.missing(n, "widgets") + return } - if !(v.FileFormat != "") && !n.has("file_format") { + if !(v.FileFormat != "" || walk.Forced(v.ForceSendFields, "FileFormat")) { w.missing(n, "file_format") } - if !(v.Widgets != nil) && !n.has("widgets") { + if !(v.Widgets != nil || n.IsRef("widgets")) { w.missing(n, "widgets") } if len(v.Widgets) > 0 { - c := n.child("widgets") - for i0 := range v.Widgets { - e := c.elem(i0) - requiredJobsSubscriptionAttachmentDataAttachmentWidget(w, &e, &v.Widgets[i0]) + c := n.Child("widgets") + for i := range v.Widgets { + e := c.Elem(i) + requiredJobsSubscriptionAttachmentDataAttachmentWidget(w, &e, &v.Widgets[i]) } } } -func requiredCatalogModelProviderServiceConfigSecretReference(w *walker, n *node, v *catalog.ModelProviderServiceConfigSecretReference) { +func requiredCatalogModelProviderServiceConfigSecretReference(w *walker, n *walk.Node, v *catalog.ModelProviderServiceConfigSecretReference) { if v == nil { - v = &catalog.ModelProviderServiceConfigSecretReference{} + w.missing(n, "name") + return } - if !(v.Name != "") && !n.has("name") { + if !(v.Name != "") { w.missing(n, "name") } } -func requiredServingApiKeyAuth(w *walker, n *node, v *serving.ApiKeyAuth) { +func requiredServingApiKeyAuth(w *walker, n *walk.Node, v *serving.ApiKeyAuth) { if v == nil { - v = &serving.ApiKeyAuth{} + w.missing(n, "key") + return } - if !(v.Key != "") && !n.has("key") { + if !(v.Key != "" || walk.Forced(v.ForceSendFields, "Key")) { w.missing(n, "key") } } -func requiredPipelinesGoogleAdsOptions(w *walker, n *node, v *pipelines.GoogleAdsOptions) { +func requiredPipelinesGoogleAdsOptions(w *walker, n *walk.Node, v *pipelines.GoogleAdsOptions) { if v == nil { - v = &pipelines.GoogleAdsOptions{} + w.missing(n, "manager_account_id") + return } - if !(v.ManagerAccountId != "") && !n.has("manager_account_id") { + if !(v.ManagerAccountId != "" || walk.Forced(v.ForceSendFields, "ManagerAccountId")) { w.missing(n, "manager_account_id") } - if c := n.child("custom_report_options"); c.exists() { - requiredPipelinesGoogleAdsCustomReportOptions(w, &c, v.CustomReportOptions) + if x := v.CustomReportOptions; x != nil { + c := n.Child("custom_report_options") + requiredPipelinesGoogleAdsCustomReportOptions(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "CustomReportOptions") || n.IsRef("custom_report_options") { + c := n.Child("custom_report_options") + requiredPipelinesGoogleAdsCustomReportOptions(w, &c, nil) } } -func requiredPipelinesLinkedInAdsOptions(w *walker, n *node, v *pipelines.LinkedInAdsOptions) { +func requiredPipelinesLinkedInAdsOptions(w *walker, n *walk.Node, v *pipelines.LinkedInAdsOptions) { if v == nil { - v = &pipelines.LinkedInAdsOptions{} + return } - if c := n.child("custom_report_options"); c.exists() { - requiredPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptions(w, &c, v.CustomReportOptions) + if x := v.CustomReportOptions; x != nil { + c := n.Child("custom_report_options") + requiredPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptions(w, &c, x) + } else if walk.Forced(v.ForceSendFields, "CustomReportOptions") || n.IsRef("custom_report_options") { + c := n.Child("custom_report_options") + requiredPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptions(w, &c, nil) } } -func requiredPipelinesRabbitmqOptions(w *walker, n *node, v *pipelines.RabbitmqOptions) { +func requiredPipelinesRabbitmqOptions(w *walker, n *walk.Node, v *pipelines.RabbitmqOptions) { if v == nil { - v = &pipelines.RabbitmqOptions{} + w.missing(n, "queue") + return } - if !(v.Queue != "") && !n.has("queue") { + if !(v.Queue != "") { w.missing(n, "queue") } } -func requiredJobsSubscriptionAttachmentDataAttachmentWidget(w *walker, n *node, v *jobs.SubscriptionAttachmentDataAttachmentWidget) { +func requiredJobsSubscriptionAttachmentDataAttachmentWidget(w *walker, n *walk.Node, v *jobs.SubscriptionAttachmentDataAttachmentWidget) { if v == nil { - v = &jobs.SubscriptionAttachmentDataAttachmentWidget{} + w.missing(n, "page_id") + w.missing(n, "widget_id") + return } - if !(v.PageId != "") && !n.has("page_id") { + if !(v.PageId != "") { w.missing(n, "page_id") } - if !(v.WidgetId != "") && !n.has("widget_id") { + if !(v.WidgetId != "") { w.missing(n, "widget_id") } } -func requiredPipelinesGoogleAdsCustomReportOptions(w *walker, n *node, v *pipelines.GoogleAdsCustomReportOptions) { +func requiredPipelinesGoogleAdsCustomReportOptions(w *walker, n *walk.Node, v *pipelines.GoogleAdsCustomReportOptions) { if v == nil { - v = &pipelines.GoogleAdsCustomReportOptions{} + w.missing(n, "resource") + return } - if !(v.Resource != "") && !n.has("resource") { + if !(v.Resource != "") { w.missing(n, "resource") } } -func requiredPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptions(w *walker, n *node, v *pipelines.LinkedInAdsOptionsLinkedInAdsCustomReportOptions) { +func requiredPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptions(w *walker, n *walk.Node, v *pipelines.LinkedInAdsOptionsLinkedInAdsCustomReportOptions) { if v == nil { - v = &pipelines.LinkedInAdsOptionsLinkedInAdsCustomReportOptions{} + w.missing(n, "finder") + return } - if !(v.Finder != "") && !n.has("finder") { + if !(v.Finder != "") { w.missing(n, "finder") } } diff --git a/bundle/config/validate/scripts.go b/bundle/config/validate/scripts.go index 323db769d23..0f9784c1b6b 100644 --- a/bundle/config/validate/scripts.go +++ b/bundle/config/validate/scripts.go @@ -9,8 +9,8 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) type validateScripts struct{} @@ -38,13 +38,13 @@ func (f *validateScripts) Apply(ctx context.Context, b *bundle.Bundle) diag.Diag for _, k := range scriptKeys { script := b.Config.Scripts[k] - contentPath := dyn.NewPath(dyn.Key("scripts"), dyn.Key(k), dyn.Key("content")) + contentPath := structpath.NewPath(nil, "scripts", k, "content") if script.Content == "" { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: fmt.Sprintf("Script %s has no content", k), - Paths: dyn.ToStructPaths(contentPath), + Paths: []*structpath.PathNode{contentPath}, }) continue } @@ -59,14 +59,13 @@ func (f *validateScripts) Apply(ctx context.Context, b *bundle.Bundle) diag.Diag // validateScriptContent rejects any ${...} reference in a script's content. // Content is passed to the shell as-is, so ${...} would be ambiguous with a // bundle reference; reference a declared env entry with $NAME instead. -func validateScriptContent(b *bundle.Bundle, key, content string, p dyn.Path) diag.Diagnostics { - ref, ok := dynvar.NewRef(dyn.V(content)) +func validateScriptContent(b *bundle.Bundle, key, content string, p *structpath.PathNode) diag.Diagnostics { + ref, ok := structvar.NewRef(content) if !ok { return nil } first := ref.Matches[0][0] - v, _ := dyn.GetByPath(b.Config.Value(), p) return diag.Diagnostics{{ Severity: diag.Error, Summary: fmt.Sprintf("Found %s in script %s.content. Interpolation syntax ${...} is not supported in script content", first, key), @@ -79,8 +78,8 @@ from "content" with $NAME: env: MY_VAR: ${var.foo} content: echo "$MY_VAR"`, - Locations: v.Locations(), - Paths: dyn.ToStructPaths(p), + Locations: b.Config.GetLocationsOfExact(p), + Paths: []*structpath.PathNode{p}, }} } @@ -88,13 +87,12 @@ func validateScriptEnv(b *bundle.Bundle, key string, env map[string]string) diag var diags diag.Diagnostics for _, name := range slices.Sorted(maps.Keys(env)) { - ref, ok := dynvar.NewRef(dyn.V(env[name])) + ref, ok := structvar.NewRef(env[name]) if !ok { continue } - envValuePath := dyn.NewPath(dyn.Key("scripts"), dyn.Key(key), dyn.Key("env"), dyn.Key(name)) - v, _ := dyn.GetByPath(b.Config.Value(), envValuePath) + envValuePath := structpath.NewPath(nil, "scripts", key, "env", name) for _, refPath := range ref.References() { prefix, _, _ := strings.Cut(refPath, ".") @@ -104,8 +102,8 @@ func validateScriptEnv(b *bundle.Bundle, key string, env map[string]string) diag diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: fmt.Sprintf("${%s} cannot be used in scripts.%s.env.%s; only ${bundle.*}, ${workspace.*}, and ${var.*} are resolved before scripts execute", refPath, key, name), - Locations: v.Locations(), - Paths: dyn.ToStructPaths(envValuePath), + Locations: b.Config.GetLocationsOfExact(envValuePath), + Paths: []*structpath.PathNode{envValuePath}, }) } } diff --git a/bundle/config/validate/single_node_cluster.go b/bundle/config/validate/single_node_cluster.go index 88b7536f80b..cfdb924a8c3 100644 --- a/bundle/config/validate/single_node_cluster.go +++ b/bundle/config/validate/single_node_cluster.go @@ -2,13 +2,17 @@ package validate import ( "context" + "maps" + "slices" "strings" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go/service/compute" + "github.com/databricks/databricks-sdk-go/service/jobs" + "github.com/databricks/databricks-sdk-go/service/pipelines" ) // Validates that any single node clusters defined in the bundle are correctly configured. @@ -36,43 +40,68 @@ are correctly set in the cluster specification: const singleNodeWarningSummary = `Single node cluster is not correctly configured` -func showSingleNodeClusterWarning(ctx context.Context, v dyn.Value) bool { +// clusterSpec holds the fields of compute.ClusterSpec and pipelines.PipelineCluster +// that the validation reads. +type clusterSpec struct { + numWorkers int + numWorkersSet bool + isSingleNode bool + sparkConf map[string]string + customTags map[string]string + policyId string +} + +func computeClusterSpec(c *compute.ClusterSpec) clusterSpec { + return clusterSpec{ + numWorkers: c.NumWorkers, + numWorkersSet: c.NumWorkers != 0 || slices.Contains(c.ForceSendFields, "NumWorkers"), + isSingleNode: c.IsSingleNode, + sparkConf: c.SparkConf, + customTags: c.CustomTags, + policyId: c.PolicyId, + } +} + +func pipelineClusterSpec(c *pipelines.PipelineCluster) clusterSpec { + return clusterSpec{ + numWorkers: c.NumWorkers, + numWorkersSet: c.NumWorkers != 0 || slices.Contains(c.ForceSendFields, "NumWorkers"), + sparkConf: c.SparkConf, + customTags: c.CustomTags, + policyId: c.PolicyId, + } +} + +// showSingleNodeClusterWarning reports whether the cluster c at p sets num_workers to +// 0 without configuring a single node cluster. +func showSingleNodeClusterWarning(ctx context.Context, b *bundle.Bundle, p *structpath.PathNode, c clusterSpec) bool { // Check if the user has explicitly set the num_workers to 0. Skip the warning // if that's not the case. - numWorkers, ok := v.Get("num_workers").AsInt() - if !ok || numWorkers > 0 { + if !c.numWorkersSet || c.numWorkers > 0 { return false } // If is_single_node is set to true, the cluster is correctly configured automatically. // No need to show the warning. - isSingleNode, ok := v.Get("is_single_node").AsBool() - if ok && isSingleNode { + if c.isSingleNode { return false } - // Convenient type that contains the common fields from compute.ClusterSpec and - // pipelines.PipelineCluster that we are interested in. - type ClusterConf struct { - SparkConf map[string]string `json:"spark_conf"` - CustomTags map[string]string `json:"custom_tags"` - PolicyId string `json:"policy_id"` - } - - conf := &ClusterConf{} - err := convert.ToTyped(conf, v) - if err != nil { - return false + // Values that are pure references are not resolved yet. + for _, k := range []string{"num_workers", "spark_conf", "custom_tags"} { + if b.Config.IsReferenceAt(structpath.NewStringKey(p, k)) { + return false + } } // If the policy id is set, we don't want to show the warning. This is because // the user might have configured `spark_conf` and `custom_tags` correctly // in their cluster policy. - if conf.PolicyId != "" { + if c.policyId != "" { return false } - profile, ok := conf.SparkConf["spark.databricks.cluster.profile"] + profile, ok := c.sparkConf["spark.databricks.cluster.profile"] if !ok { log.Debugf(ctx, "spark_conf spark.databricks.cluster.profile not found in single-node cluster spec") return true @@ -82,7 +111,7 @@ func showSingleNodeClusterWarning(ctx context.Context, v dyn.Value) bool { return true } - master, ok := conf.SparkConf["spark.master"] + master, ok := c.sparkConf["spark.master"] if !ok { log.Debugf(ctx, "spark_conf spark.master not found in single-node cluster spec") return true @@ -92,7 +121,7 @@ func showSingleNodeClusterWarning(ctx context.Context, v dyn.Value) bool { return true } - resourceClass, ok := conf.CustomTags["ResourceClass"] + resourceClass, ok := c.customTags["ResourceClass"] if !ok { log.Debugf(ctx, "custom_tag ResourceClass not found in single-node cluster spec") return true @@ -106,39 +135,59 @@ func showSingleNodeClusterWarning(ctx context.Context, v dyn.Value) bool { } func (m *singleNodeCluster) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - diags := diag.Diagnostics{} - - patterns := []dyn.Pattern{ - // Interactive clusters - dyn.NewPattern(dyn.Key("resources"), dyn.Key("clusters"), dyn.AnyKey()), - // Job clusters - dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey(), dyn.Key("job_clusters"), dyn.AnyIndex(), dyn.Key("new_cluster")), - // Job task clusters - dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey(), dyn.Key("tasks"), dyn.AnyIndex(), dyn.Key("new_cluster")), - // Job for each task clusters - dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey(), dyn.Key("tasks"), dyn.AnyIndex(), dyn.Key("for_each_task"), dyn.Key("task"), dyn.Key("new_cluster")), - // Pipeline clusters - dyn.NewPattern(dyn.Key("resources"), dyn.Key("pipelines"), dyn.AnyKey(), dyn.Key("clusters"), dyn.AnyIndex()), - } - - for _, p := range patterns { - _, err := dyn.MapByPattern(b.Config.Value(), p, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - warning := diag.Diagnostic{ - Severity: diag.Warning, - Summary: singleNodeWarningSummary, - Detail: singleNodeWarningDetail, - Locations: v.Locations(), - Paths: dyn.ToStructPaths(p), - } + c := clusterCheck{b: b, diags: diag.Diagnostics{}} + r := &b.Config.Resources - if showSingleNodeClusterWarning(ctx, v) { - diags = append(diags, warning) + // Interactive clusters + for _, name := range slices.Sorted(maps.Keys(r.Clusters)) { + if cl := r.Clusters[name]; cl != nil { + c.cluster(ctx, structpath.NewPath(nil, "resources", "clusters", name), computeClusterSpec(&cl.ClusterSpec)) + } + } + // Job clusters + for _, name := range slices.Sorted(maps.Keys(r.Jobs)) { + if job := r.Jobs[name]; job != nil { + for i := range job.JobClusters { + if nc := job.JobClusters[i].NewCluster; nc != nil { + c.cluster(ctx, structpath.NewPath(nil, "resources", "jobs", name, "job_clusters", i, "new_cluster"), computeClusterSpec(nc)) + } } - return v, nil + } + } + // Job task clusters, then the clusters of tasks in for_each_task + for _, nested := range []bool{false, true} { + r.IterateJobTasks(nested, func(p *structpath.PathNode, task *jobs.Task) bool { + if task.NewCluster != nil { + c.cluster(ctx, structpath.NewPath(p, "new_cluster"), computeClusterSpec(task.NewCluster)) + } + return true }) - if err != nil { - log.Debugf(ctx, "Error while applying single node cluster validation: %s", err) + } + // Pipeline clusters + for _, name := range slices.Sorted(maps.Keys(r.Pipelines)) { + if pl := r.Pipelines[name]; pl != nil { + for i := range pl.Clusters { + c.cluster(ctx, structpath.NewPath(nil, "resources", "pipelines", name, "clusters", i), pipelineClusterSpec(&pl.Clusters[i])) + } } } - return diags + return c.diags +} + +// clusterCheck collects the single node cluster warnings. +type clusterCheck struct { + b *bundle.Bundle + diags diag.Diagnostics +} + +func (c *clusterCheck) cluster(ctx context.Context, p *structpath.PathNode, spec clusterSpec) { + if showSingleNodeClusterWarning(ctx, c.b, p, spec) { + c.diags = append(c.diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: singleNodeWarningSummary, + Detail: singleNodeWarningDetail, + Locations: c.b.Config.GetLocationsOfExact(p), + Paths: []*structpath.PathNode{p}, + }) + } } diff --git a/bundle/config/validate/single_node_cluster_test.go b/bundle/config/validate/single_node_cluster_test.go index 997d39ea0ac..1c8d79af4f2 100644 --- a/bundle/config/validate/single_node_cluster_test.go +++ b/bundle/config/validate/single_node_cluster_test.go @@ -8,12 +8,12 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/internal/bundletest" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/pipelines" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func failCases() []struct { @@ -112,10 +112,8 @@ func TestValidateSingleNodeClusterFailForInteractiveClusters(t *testing.T) { bundletest.SetLocation(b, "resources.clusters.foo", []diag.Location{{File: "a.yml", Line: 1, Column: 1}}) // We can't set num_workers to 0 explicitly in the typed configuration. - // Do it on the dyn.Value directly. - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.clusters.foo.num_workers", dyn.V(0)) - }) + // Set it explicitly, since zero values are otherwise treated as unset. + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.clusters.foo.num_workers"), 0)) diags := SingleNodeCluster().Apply(ctx, b) assert.Equal(t, diag.Diagnostics{ { @@ -160,10 +158,8 @@ func TestValidateSingleNodeClusterFailForJobClusters(t *testing.T) { bundletest.SetLocation(b, "resources.jobs.foo.job_clusters[0].new_cluster", []diag.Location{{File: "b.yml", Line: 1, Column: 1}}) // We can't set num_workers to 0 explicitly in the typed configuration. - // Do it on the dyn.Value directly. - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.jobs.foo.job_clusters[0].new_cluster.num_workers", dyn.V(0)) - }) + // Set it explicitly, since zero values are otherwise treated as unset. + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.foo.job_clusters[0].new_cluster.num_workers"), 0)) diags := SingleNodeCluster().Apply(ctx, b) assert.Equal(t, diag.Diagnostics{ @@ -209,10 +205,8 @@ func TestValidateSingleNodeClusterFailForJobTaskClusters(t *testing.T) { bundletest.SetLocation(b, "resources.jobs.foo.tasks[0].new_cluster", []diag.Location{{File: "c.yml", Line: 1, Column: 1}}) // We can't set num_workers to 0 explicitly in the typed configuration. - // Do it on the dyn.Value directly. - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.jobs.foo.tasks[0].new_cluster.num_workers", dyn.V(0)) - }) + // Set it explicitly, since zero values are otherwise treated as unset. + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.foo.tasks[0].new_cluster.num_workers"), 0)) diags := bundle.Apply(ctx, b, SingleNodeCluster()) assert.Equal(t, diag.Diagnostics{ @@ -255,10 +249,8 @@ func TestValidateSingleNodeClusterFailForPipelineClusters(t *testing.T) { bundletest.SetLocation(b, "resources.pipelines.foo.clusters[0]", []diag.Location{{File: "d.yml", Line: 1, Column: 1}}) // We can't set num_workers to 0 explicitly in the typed configuration. - // Do it on the dyn.Value directly. - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.pipelines.foo.clusters[0].num_workers", dyn.V(0)) - }) + // Set it explicitly, since zero values are otherwise treated as unset. + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.pipelines.foo.clusters[0].num_workers"), 0)) diags := bundle.Apply(ctx, b, SingleNodeCluster()) assert.Equal(t, diag.Diagnostics{ @@ -308,10 +300,8 @@ func TestValidateSingleNodeClusterFailForJobForEachTaskCluster(t *testing.T) { bundletest.SetLocation(b, "resources.jobs.foo.tasks[0].for_each_task.task.new_cluster", []diag.Location{{File: "e.yml", Line: 1, Column: 1}}) // We can't set num_workers to 0 explicitly in the typed configuration. - // Do it on the dyn.Value directly. - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.jobs.foo.tasks[0].for_each_task.task.new_cluster.num_workers", dyn.V(0)) - }) + // Set it explicitly, since zero values are otherwise treated as unset. + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.foo.tasks[0].for_each_task.task.new_cluster.num_workers"), 0)) diags := bundle.Apply(ctx, b, SingleNodeCluster()) assert.Equal(t, diag.Diagnostics{ @@ -392,9 +382,7 @@ func TestValidateSingleNodeClusterPassInteractiveClusters(t *testing.T) { } if tc.numWorkers != nil { - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.clusters.foo.num_workers", dyn.V(*tc.numWorkers)) - }) + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.clusters.foo.num_workers"), *tc.numWorkers)) } diags := bundle.Apply(ctx, b, SingleNodeCluster()) @@ -432,9 +420,7 @@ func TestValidateSingleNodeClusterPassJobClusters(t *testing.T) { } if tc.numWorkers != nil { - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.jobs.foo.job_clusters[0].new_cluster.num_workers", dyn.V(*tc.numWorkers)) - }) + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.foo.job_clusters[0].new_cluster.num_workers"), *tc.numWorkers)) } diags := bundle.Apply(ctx, b, SingleNodeCluster()) @@ -472,9 +458,7 @@ func TestValidateSingleNodeClusterPassJobTaskClusters(t *testing.T) { } if tc.numWorkers != nil { - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.jobs.foo.tasks[0].new_cluster.num_workers", dyn.V(*tc.numWorkers)) - }) + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.foo.tasks[0].new_cluster.num_workers"), *tc.numWorkers)) } diags := bundle.Apply(ctx, b, SingleNodeCluster()) @@ -509,9 +493,7 @@ func TestValidateSingleNodeClusterPassPipelineClusters(t *testing.T) { } if tc.numWorkers != nil { - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.pipelines.foo.clusters[0].num_workers", dyn.V(*tc.numWorkers)) - }) + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.pipelines.foo.clusters[0].num_workers"), *tc.numWorkers)) } diags := bundle.Apply(ctx, b, SingleNodeCluster()) @@ -553,9 +535,7 @@ func TestValidateSingleNodeClusterPassJobForEachTaskCluster(t *testing.T) { } if tc.numWorkers != nil { - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.jobs.foo.tasks[0].for_each_task.task.new_cluster.num_workers", dyn.V(*tc.numWorkers)) - }) + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.foo.tasks[0].for_each_task.task.new_cluster.num_workers"), *tc.numWorkers)) } diags := bundle.Apply(ctx, b, SingleNodeCluster()) @@ -590,13 +570,8 @@ func TestValidateSingleNodeClusterWithIsSingleNode(t *testing.T) { } // Set num_workers to 0 and is_single_node to true - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - v, err := dyn.Set(v, "resources.jobs.foo.job_clusters[0].new_cluster.num_workers", dyn.V(0)) - if err != nil { - return v, err - } - return dyn.Set(v, "resources.jobs.foo.job_clusters[0].new_cluster.is_single_node", dyn.V(true)) - }) + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.foo.job_clusters[0].new_cluster.num_workers"), 0)) + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.foo.job_clusters[0].new_cluster.is_single_node"), true)) diags := bundle.Apply(ctx, b, SingleNodeCluster()) assert.Empty(t, diags) diff --git a/bundle/config/validate/tf_only_references.go b/bundle/config/validate/tf_only_references.go index 3603b6f4a7f..a0407a74a30 100644 --- a/bundle/config/validate/tf_only_references.go +++ b/bundle/config/validate/tf_only_references.go @@ -6,11 +6,11 @@ import ( "strings" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config/walk" "github.com/databricks/cli/bundle/terraform_dabs_map" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) type tfOnlyReferences struct{} @@ -29,22 +29,15 @@ func (m *tfOnlyReferences) Name() string { func (m *tfOnlyReferences) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { var diags diag.Diagnostics - // Walk the entire config looking for ${resources.*} references. - _ = dyn.WalkReadOnly(b.Config.Value(), func(_ dyn.Path, v dyn.Value) error { - ref, ok := dynvar.NewRef(v) - if !ok { - return nil - } - for _, r := range ref.References() { - if !strings.HasPrefix(r, "resources.") { - continue - } - if d := checkTFOnlyReference(r, v.Location()); d != nil { - diags = append(diags, *d) - } - } - return nil + // Look for ${resources.*} references in strings and in fields that hold a pure + // reference. + walk.Strings(&b.Config, walk.WithReferences, func(n *walk.Node, s string) (string, bool) { + diags = append(diags, checkTFOnlyReferences(s, n.Location())...) + return "", false }) + for p, ref := range b.Config.References() { + diags = append(diags, checkTFOnlyReferences(ref, b.Config.GetLocationOfExact(p))...) + } if len(diags) > 0 { b.Metrics.AddBoolValue("has_tf_only_references", true) @@ -53,29 +46,42 @@ func (m *tfOnlyReferences) Apply(_ context.Context, b *bundle.Bundle) diag.Diagn return diags } +// checkTFOnlyReferences checks the ${resources.*} references in s. +func checkTFOnlyReferences(s string, loc diag.Location) diag.Diagnostics { + ref, ok := structvar.NewRef(s) + if !ok { + return nil + } + var diags diag.Diagnostics + for _, r := range ref.References() { + if !strings.HasPrefix(r, "resources.") { + continue + } + if d := checkTFOnlyReference(r, loc); d != nil { + diags = append(diags, *d) + } + } + return diags +} + // checkTFOnlyReference checks a single reference string like // "resources.jobs.src.always_running" and returns a diagnostic when it refers // to a TF-only field, or nil otherwise. func checkTFOnlyReference(ref string, loc diag.Location) *diag.Diagnostic { - p, err := dyn.NewPathFromString(ref) + p, err := structpath.ParsePath(ref) // Need at least resources... - if err != nil || len(p) < 4 || p[0].Key() != "resources" { + if err != nil || p.Len() < 4 || p.KeyAt(0) != "resources" { return nil } - group := p[1].Key() + group := p.KeyAt(1) tfOnlyFields, ok := terraform_dabs_map.TerraformOnlyFields[group] if !ok || len(tfOnlyFields) == 0 { return nil } // Field path is everything after resources... - fieldNode, err := structpath.ParsePath(p[3:].String()) - if err != nil { - return nil - } - - if !tfOnlyFields.Contains(fieldNode) { + if !tfOnlyFields.Contains(p.SkipPrefix(3)) { return nil } diff --git a/bundle/config/validate/tf_only_references_test.go b/bundle/config/validate/tf_only_references_test.go index 7b994cf864e..a4f5850f8c8 100644 --- a/bundle/config/validate/tf_only_references_test.go +++ b/bundle/config/validate/tf_only_references_test.go @@ -6,9 +6,8 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/bundle/internal/bundletest" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -31,9 +30,7 @@ func makeBundle(t *testing.T) *bundle.Bundle { func TestTFOnlyReferences_Error(t *testing.T) { b := makeBundle(t) - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources.jobs.dst.name", dyn.V("${resources.jobs.src.always_running}")) - }) + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.dst.name"), "${resources.jobs.src.always_running}")) diags := TFOnlyReferences().Apply(t.Context(), b) require.Len(t, diags, 1) @@ -44,10 +41,8 @@ func TestTFOnlyReferences_Error(t *testing.T) { func TestTFOnlyReferences_NormalReference(t *testing.T) { b := makeBundle(t) - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - // "name" is not a TF-only field; no diagnostic expected. - return dyn.Set(v, "resources.jobs.dst.name", dyn.V("${resources.jobs.src.name}")) - }) + // "name" is not a TF-only field; no diagnostic expected. + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.dst.name"), "${resources.jobs.src.name}")) diags := TFOnlyReferences().Apply(t.Context(), b) assert.Empty(t, diags) @@ -55,10 +50,8 @@ func TestTFOnlyReferences_NormalReference(t *testing.T) { func TestTFOnlyReferences_RenamedField(t *testing.T) { b := makeBundle(t) - bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { - // "git_source[0].branch" is a TF rename (not TF-only), should not error. - return dyn.Set(v, "resources.jobs.dst.name", dyn.V("${resources.jobs.src.git_source[0].branch}")) - }) + // "git_source[0].branch" is a TF rename (not TF-only), should not error. + require.NoError(t, b.Config.Set(structpath.MustParsePath("resources.jobs.dst.name"), "${resources.jobs.src.git_source[0].branch}")) diags := TFOnlyReferences().Apply(t.Context(), b) assert.Empty(t, diags) diff --git a/bundle/config/validate/unique_resource_keys.go b/bundle/config/validate/unique_resource_keys.go index 7d2e49569f7..bf51b78fe4a 100644 --- a/bundle/config/validate/unique_resource_keys.go +++ b/bundle/config/validate/unique_resource_keys.go @@ -7,7 +7,6 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" ) @@ -42,7 +41,7 @@ func (m *uniqueResourceKeys) Apply(ctx context.Context, b *bundle.Bundle) diag.D // Maps of key to the paths and locations the resource / script is defined at. resourceAndScriptMetadata := map[string]*metadata{} - addLocationToMetadata := func(k, prefix string, p dyn.Path, v dyn.Value) { + addLocationToMetadata := func(k string, fullPath *structpath.PathNode) { mv, ok := resourceAndScriptMetadata[k] if !ok { mv = &metadata{ @@ -51,46 +50,22 @@ func (m *uniqueResourceKeys) Apply(ctx context.Context, b *bundle.Bundle) diag.D } } - mv.paths = append(mv.paths, dyn.ToStructPath(dyn.NewPath(dyn.Key(prefix)).Append(p...))) - mv.locations = append(mv.locations, v.Locations()...) + mv.paths = append(mv.paths, fullPath) + mv.locations = append(mv.locations, b.Config.GetLocationsOfExact(fullPath)...) resourceAndScriptMetadata[k] = mv } // Gather the paths and locations of all resources - rv := b.Config.Value().Get("resources") - if rv.Kind() != dyn.KindInvalid && rv.Kind() != dyn.KindNil { - _, err := dyn.MapByPattern( - rv, - dyn.NewPattern(dyn.AnyKey(), dyn.AnyKey()), - func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - // The key for the resource. Eg: "my_job" for jobs.my_job. - k := p[1].Key() - addLocationToMetadata(k, "resources", p, v) - return v, nil - }, - ) - if err != nil { - return diag.FromErr(err) + for _, group := range b.Config.Resources.AllResources() { + for k := range group.Resources { + addLocationToMetadata(k, structpath.NewPath(nil, "resources", group.Description.PluralName, k)) } } // track locations for all scripts. - sv := b.Config.Value().Get("scripts") - if sv.Kind() != dyn.KindInvalid && sv.Kind() != dyn.KindNil { - _, err := dyn.MapByPattern( - sv, - dyn.NewPattern(dyn.AnyKey()), - func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - // The key for the script. Eg: "my_script" for scripts.my_script. - k := p[0].Key() - addLocationToMetadata(k, "scripts", p, v) - return v, nil - }, - ) - if err != nil { - return diag.FromErr(err) - } + for k := range b.Config.Scripts { + addLocationToMetadata(k, structpath.NewPath(nil, "scripts", k)) } // If duplicate keys are found, report an error. diff --git a/bundle/config/validate/validate_artifact_path.go b/bundle/config/validate/validate_artifact_path.go index 36494cf7d77..4b91c3641a9 100644 --- a/bundle/config/validate/validate_artifact_path.go +++ b/bundle/config/validate/validate_artifact_path.go @@ -11,9 +11,8 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" "github.com/databricks/databricks-sdk-go/apierr" ) @@ -52,7 +51,7 @@ func extractVolumeFromPath(artifactPath string) (string, string, string, error) return catalogName, schemaName, volumeName, nil } -func findVolumeInBundle(r config.Root, catalogName, schemaName, volumeName string) (dyn.Path, []diag.Location, bool) { +func findVolumeInBundle(r config.Root, catalogName, schemaName, volumeName string) (*structpath.PathNode, []diag.Location, bool) { volumes := r.Resources.Volumes for k, v := range volumes { if v.CatalogName != catalogName || v.Name != volumeName { @@ -64,12 +63,13 @@ func findVolumeInBundle(r config.Root, catalogName, schemaName, volumeName strin // schema name is interpolated. // We only have to check for ${resources.schemas...} references because any // other valid reference (like ${var.foo}) would have been interpolated by this point. - p, ok := dynvar.PureReferenceToPath(v.SchemaName) - isSchemaDefinedInBundle := ok && p.HasPrefix(dyn.Path{dyn.Key("resources"), dyn.Key("schemas")}) + p, ok := structvar.PureReferenceToPath(v.SchemaName) + isSchemaDefinedInBundle := ok && p.HasPrefix(structpath.NewPath(nil, "resources", "schemas")) if v.SchemaName != schemaName && !isSchemaDefinedInBundle { continue } - return dyn.Path{dyn.Key("resources"), dyn.Key("volumes"), dyn.Key(k)}, r.GetLocationsOf(structpath.NewPath(nil, "resources", "volumes", k)), true + volumePath := structpath.NewPath(nil, "resources", "volumes", k) + return volumePath, r.GetLocationsOf(volumePath), true } return nil, nil, false } @@ -118,7 +118,7 @@ this bundle but which has not been deployed yet. Please first deploy the volume using 'bundle deploy' and then switch over to using it in the artifact_path.`, Locations: slices.Concat(b.Config.GetLocations("workspace.artifact_path"), locations), - Paths: append(structpath.NewPathSlice("workspace", "artifact_path"), dyn.ToStructPath(path)), + Paths: []*structpath.PathNode{structpath.NewPath(nil, "workspace", "artifact_path"), path}, }} } diff --git a/bundle/config/validate/validate_artifact_path_test.go b/bundle/config/validate/validate_artifact_path_test.go index 97a08ab6e30..778326430e0 100644 --- a/bundle/config/validate/validate_artifact_path_test.go +++ b/bundle/config/validate/validate_artifact_path_test.go @@ -8,7 +8,6 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/internal/bundletest" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/experimental/mocks" @@ -209,7 +208,7 @@ func TestFindVolumeInBundle(t *testing.T) { Line: 1, Column: 2, }}, locations) - assert.Equal(t, dyn.MustPathFromString("resources.volumes.foo"), path) + assert.Equal(t, structpath.MustParsePath("resources.volumes.foo"), path) // wrong volume name _, _, ok = findVolumeInBundle(b.Config, "main", "my_schema", "doesnotexist") @@ -238,5 +237,5 @@ func TestFindVolumeInBundle(t *testing.T) { Line: 1, Column: 2, }}, locations) - assert.Equal(t, dyn.MustPathFromString("resources.volumes.foo"), path) + assert.Equal(t, structpath.MustParsePath("resources.volumes.foo"), path) } diff --git a/bundle/config/validate/validate_engine.go b/bundle/config/validate/validate_engine.go index f19c2270b26..55a99e7fb7a 100644 --- a/bundle/config/validate/validate_engine.go +++ b/bundle/config/validate/validate_engine.go @@ -7,7 +7,6 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config/engine" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" ) @@ -26,11 +25,12 @@ func (v *validateEngine) Name() string { func (v *validateEngine) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { var diags diag.Diagnostics - if tf := dyn.GetValue(b.Config.Value(), "bundle.terraform"); tf.IsValid() { + // A reference to a variable also allocates the section. + if b.Config.Bundle.Terraform != nil { diags = diags.Append(diag.Diagnostic{ Severity: diag.Warning, Summary: "bundle.terraform is deprecated and has no effect: " + engine.TerraformRemovedSummary, - Locations: tf.Locations(), + Locations: b.Config.GetLocationsOfExact(structpath.NewPath(nil, "bundle", "terraform")), Paths: structpath.NewPathSlice("bundle", "terraform"), }) } @@ -40,7 +40,7 @@ func (v *validateEngine) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnos return diags } - loc := dyn.GetValue(b.Config.Value(), "bundle.engine").Location() + loc := b.Config.GetLocationOf(structpath.NewPath(nil, "bundle", "engine")) parsed, ok := engine.Parse(string(configEngine)) if !ok { diff --git a/bundle/config/validate/validate_volume_path.go b/bundle/config/validate/validate_volume_path.go index c440dfbf06f..802034bbbee 100644 --- a/bundle/config/validate/validate_volume_path.go +++ b/bundle/config/validate/validate_volume_path.go @@ -23,13 +23,12 @@ func (m *validateVolumePath) Apply(ctx context.Context, b *bundle.Bundle) diag.D // Define paths to check and their corresponding config field names pathChecks := []struct { path string - configName string configPath *structpath.PathNode }{ - {b.Config.Workspace.RootPath, "workspace.root_path", structpath.NewPath(nil, "workspace", "root_path")}, - {b.Config.Workspace.FilePath, "workspace.file_path", structpath.NewPath(nil, "workspace", "file_path")}, - {b.Config.Workspace.StatePath, "workspace.state_path", structpath.NewPath(nil, "workspace", "state_path")}, - {b.Config.Workspace.ResourcePath, "workspace.resource_path", structpath.NewPath(nil, "workspace", "resource_path")}, + {b.Config.Workspace.RootPath, structpath.NewPath(nil, "workspace", "root_path")}, + {b.Config.Workspace.FilePath, structpath.NewPath(nil, "workspace", "file_path")}, + {b.Config.Workspace.StatePath, structpath.NewPath(nil, "workspace", "state_path")}, + {b.Config.Workspace.ResourcePath, structpath.NewPath(nil, "workspace", "resource_path")}, } // Check each path @@ -37,14 +36,14 @@ func (m *validateVolumePath) Apply(ctx context.Context, b *bundle.Bundle) diag.D if check.path != "" && strings.HasPrefix(check.path, "/Volumes/") { diags = diags.Append(diag.Diagnostic{ Severity: diag.Error, - Summary: fmt.Sprintf("%s %s starts with /Volumes. /Volumes can only be used with workspace.artifact_path.", check.configName, check.path), + Summary: fmt.Sprintf("%s %s starts with /Volumes. /Volumes can only be used with workspace.artifact_path.", check.configPath, check.path), Detail: "For more information, see https://docs.databricks.com/aws/en/dev-tools/bundles/settings#workspace", Locations: b.Config.GetLocationsOf(check.configPath), Paths: []*structpath.PathNode{check.configPath}, }) // Return early for root path validation - if check.configName == "workspace.root_path" { + if check.configPath.String() == "workspace.root_path" { return diags } } diff --git a/bundle/config/validate/walk_node.go b/bundle/config/validate/walk_node.go index bcf8d75824a..41cff6ebd46 100644 --- a/bundle/config/validate/walk_node.go +++ b/bundle/config/validate/walk_node.go @@ -4,149 +4,51 @@ import ( "fmt" "slices" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/walk" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) -// This file is the bridge between the generated typed walkers (required_walk.go, -// enum_walk.go) and the dynamic configuration. The typed configuration cannot tell an -// unset field from one that is null, set to its zero value or to a pure variable -// reference, and it has no locations, so a node follows the [dyn.Value] at its path -// for those. The typed configuration is derived from the dynamic one after every -// mutation, so the two agree. Replace the node once the configuration no longer uses -// dyn. - -// node is a position in the configuration visited by a generated walker. Nodes live -// on the stack and link to their parent; the path is only built for a diagnostic. -type node struct { - parent *node - key string - index int // -1 for a key, -2 for the root - v dyn.Value -} - -func rootNode(v dyn.Value) node { - return node{index: -2, v: v} -} - -// child returns the node of the key k of n. -func (n *node) child(k string) node { - return node{parent: n, key: k, index: -1, v: n.v.Get(k)} -} - -// elem returns the node of element i of n. -func (n *node) elem(i int) node { - return node{parent: n, index: i, v: n.v.Index(i)} -} - -// exists reports whether the configuration has a value (possibly null) at n. -func (n *node) exists() bool { - return n.v.IsValid() -} - -// has reports whether the key k of n is set to a value that is not null. -func (n *node) has(k string) bool { - kind := n.v.Get(k).Kind() - return kind != dyn.KindInvalid && kind != dyn.KindNil +// walker holds the state of a generated validation walk (required_walk.go, +// enum_walk.go). +type walker struct { + diags diag.Diagnostics } -func (n *node) path() *structpath.PathNode { - switch n.index { - case -2: - return nil - case -1: - return structpath.NewStringKey(n.parent.path(), n.key) - default: - return structpath.NewIndex(n.parent.path(), n.index) +// walkConfig runs the generated walker fn over the configuration of root; refs makes +// the nodes follow the pure references. +func walkConfig(root *config.Root, refs bool, fn func(*walker, *walk.Node, *config.Root)) diag.Diagnostics { + w := &walker{} + n := walk.Root(root) + if refs { + n = walk.RootWithReferences(root) } -} - -// walker holds the diagnostics of a generated walk. -type walker struct { - diags diag.Diagnostics + fn(w, &n, root) + return w.diags } // missing reports that the required field of n is not set. -func (w *walker) missing(n *node, field string) { +func (w *walker) missing(n *walk.Node, field string) { w.diags = append(w.diags, diag.Diagnostic{ Severity: diag.Warning, Summary: fmt.Sprintf("required field %q is not set", field), - Locations: n.v.Locations(), - Paths: []*structpath.PathNode{n.path()}, + Locations: n.Locations().Get(), + Paths: []*structpath.PathNode{n.Path()}, }) } -// enumField checks the enum field k of n, whose typed value is s. -func (w *walker) enumField(n *node, k, s string, valid []string) { - if s != "" && slices.Contains(valid, s) { - return - } - c := n.child(k) - w.enumValue(&c, valid) -} - -// enumElem checks the enum element n, whose typed value is s. -func (w *walker) enumElem(n *node, s string, valid []string) { - if s != "" && slices.Contains(valid, s) { - return - } - w.enumValue(n, valid) -} - -// enumValue reports the value of n if it is a string that is not one of valid. Values -// with variable references are not resolved yet and are skipped. -func (w *walker) enumValue(n *node, valid []string) { - s, ok := n.v.AsString() - if !ok || dynvar.ContainsVariableReference(s) || slices.Contains(valid, s) { +// enumValue reports s, the value of n, if it is not one of valid. Values with variable +// references are not resolved yet and are skipped. +func (w *walker) enumValue(n *walk.Node, s string, valid []string) { + if slices.Contains(valid, s) || structvar.ContainsVariableReference(s) { return } w.diags = append(w.diags, diag.Diagnostic{ Severity: diag.Warning, Summary: fmt.Sprintf("invalid value %q for enum field. Valid values are %v", s, valid), - Locations: n.v.Locations(), - Paths: []*structpath.PathNode{n.path()}, + Locations: n.Locations().Get(), + Paths: []*structpath.PathNode{n.Path()}, }) } - -// sortByWalkOrder orders diagnostics with one path each in the order [dyn.WalkReadOnly] -// visits their paths in root, which the walkers do not follow. -func sortByWalkOrder(root dyn.Value, diags diag.Diagnostics) { - type item struct { - pos []int - d diag.Diagnostic - } - items := make([]item, len(diags)) - for i, d := range diags { - items[i] = item{walkPosition(root, d.Paths[0]), d} - } - slices.SortStableFunc(items, func(a, b item) int { - return slices.Compare(a.pos, b.pos) - }) - for i := range items { - diags[i] = items[i].d - } -} - -// walkPosition returns the position of each component of path p in root: the index of -// a key among the keys of its map, or the index of an element. -func walkPosition(root dyn.Value, p *structpath.PathNode) []int { - v := root - var pos []int - for _, c := range p.AsSlice() { - if i, ok := c.Index(); ok { - pos = append(pos, i) - v = v.Index(i) - continue - } - k, _ := c.StringKey() - m, _ := v.AsMap() - i := slices.IndexFunc(m.Pairs(), func(pair dyn.Pair) bool { - return pair.Key.MustString() == k - }) - pos = append(pos, i) - v = v.Get(k) - } - return pos -} diff --git a/bundle/config/validate/walk_test.go b/bundle/config/validate/walk_test.go index cf0801b0da3..b8e21309ccb 100644 --- a/bundle/config/validate/walk_test.go +++ b/bundle/config/validate/walk_test.go @@ -7,7 +7,6 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -57,7 +56,9 @@ resources: } func TestRequiredWalkSetValues(t *testing.T) { - // A pure reference and an explicit zero value set a field; null does not. + // A pure reference sets a field; null does not. An explicit zero of a field that has no + // ForceSendFields (here value: 0) cannot be told from an unset field in the typed + // configuration, so it is reported (known difference from the dyn implementation). b := loadBundle(t, ` bundle: name: test-bundle @@ -86,6 +87,7 @@ resources: assert.Equal(t, []string{ `required field "commands" is not set at resources.jobs.job.tasks[1].dbt_task (23)`, `required field "job_id" is not set at resources.jobs.job.tasks[0].run_job_task (20)`, + `required field "value" is not set at resources.jobs.job.health.rules[1] (15)`, }, summarize(diags)) } @@ -111,9 +113,8 @@ resources: }, summarize(diags)) } -func TestEnumWalkKeepsWalkOrderForTies(t *testing.T) { - // Values without a location tie in the sort; they keep the order of the - // configuration, not that of the walk over the typed maps. +func TestEnumWalkOrdersTiesByPath(t *testing.T) { + // Diagnostics without a location tie in the sort; the path breaks the tie. b := loadBundle(t, ` bundle: name: test-bundle @@ -132,21 +133,15 @@ resources: - task_key: a run_if: SOMETIMES `) - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.MapByPattern(v, dyn.NewPattern(dyn.Key("resources"), dyn.Key("jobs"), dyn.AnyKey(), dyn.Key("tasks"), dyn.AnyIndex(), dyn.Key("run_if")), func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return dyn.V(v.MustString()), nil - }) - }) - require.NoError(t, err) diags := bundle.Apply(t.Context(), b, Enum()) var paths []string for _, d := range diags { paths = append(paths, d.Paths[0].String()) } assert.Equal(t, []string{ - "resources.jobs.zeta.tasks[0].run_if", "resources.jobs.alpha.tasks[0].run_if", "resources.jobs.mid.tasks[0].run_if", + "resources.jobs.zeta.tasks[0].run_if", }, paths) } diff --git a/bundle/config/walk/.gitattributes b/bundle/config/walk/.gitattributes new file mode 100644 index 00000000000..aa33ca924e6 --- /dev/null +++ b/bundle/config/walk/.gitattributes @@ -0,0 +1 @@ +*_walk.go linguist-generated=true diff --git a/bundle/config/walk/node.go b/bundle/config/walk/node.go new file mode 100644 index 00000000000..f758c33c9e9 --- /dev/null +++ b/bundle/config/walk/node.go @@ -0,0 +1,162 @@ +// Package walk holds the node type of the generated typed walkers over the bundle +// configuration and the walkers that are not specific to one mutator. +package walk + +import ( + "reflect" + "slices" + + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" +) + +// Node is a position in the configuration visited by a generated walker. Nodes live +// on the stack and link to their parent; the path is only built when needed (e.g. for +// a diagnostic). A node also follows the locations tree and the tree of pure +// references, so neither needs a path lookup. +type Node struct { + parent *Node + key string + index int // -1 for a key, -2 for the root + locs *structvar.Locations + refs *refNode +} + +// Root returns the root node of the configuration r. Its nodes do not follow the pure +// references: use [RootWithReferences] for [Node.IsRef]. +func Root(r *config.Root) Node { + return Node{index: -2, locs: r.LocationTree()} +} + +// RootWithReferences is [Root] whose nodes also follow the pure references. +func RootWithReferences(r *config.Root) Node { + return rootWithReferencesUnder(r, nil) +} + +// rootWithReferencesUnder is [RootWithReferences] following only the references at +// path and below it, for a walk that stays there. +func rootWithReferencesUnder(r *config.Root, path *structpath.PathNode) Node { + n := Root(r) + n.refs = newRefTree(r, path) + return n +} + +// Child returns the node of the key k of n. +func (n *Node) Child(k string) Node { + return Node{parent: n, key: k, index: -1, locs: n.locs.Key(k), refs: n.refs.key(k)} +} + +// Elem returns the node of element i of n. +func (n *Node) Elem(i int) Node { + return Node{parent: n, index: i, locs: n.locs.Index(i), refs: n.refs.elem(i)} +} + +// Path returns the path of n. +func (n *Node) Path() *structpath.PathNode { + switch n.index { + case -2: + return nil + case -1: + return structpath.NewStringKey(n.parent.Path(), n.key) + default: + return structpath.NewIndex(n.parent.Path(), n.index) + } +} + +// Locations returns the locations of the value at n. +func (n *Node) Locations() *structvar.Locations { + return n.locs +} + +// Location returns the first location of the value at n. +func (n *Node) Location() diag.Location { + locs := n.locs.Get() + if len(locs) == 0 { + return diag.Location{} + } + return locs[0] +} + +// isRef reports whether n itself holds a pure variable reference. +func (n *Node) isRef() bool { + return n.refs != nil && n.refs.ref +} + +// IsRef reports whether the key k of n holds a pure variable reference. Only fields +// that cannot hold a string can. +func (n *Node) IsRef(k string) bool { + r := n.refs.key(k) + return r != nil && r.ref +} + +// HasRefs reports whether the key k of n holds a pure variable reference or has one +// below it (e.g. a struct whose only set field is a reference). +func (n *Node) HasRefs(k string) bool { + return n.refs.key(k) != nil +} + +// refNode is a tree of the paths that hold pure variable references. +type refNode struct { + ref bool + keys map[string]*refNode + elems map[int]*refNode +} + +func (r *refNode) key(k string) *refNode { + if r == nil { + return nil + } + return r.keys[k] +} + +func (r *refNode) elem(i int) *refNode { + if r == nil { + return nil + } + return r.elems[i] +} + +func newRefTree(root *config.Root, under *structpath.PathNode) *refNode { + var out *refNode + for p := range root.ReferencesUnder(under) { + if out == nil { + out = &refNode{} + } + n := out + for _, c := range p.AsSlice() { + if k, ok := c.StringKey(); ok { + if n.keys == nil { + n.keys = map[string]*refNode{} + } + if n.keys[k] == nil { + n.keys[k] = &refNode{} + } + n = n.keys[k] + } else if i, ok := c.Index(); ok { + if n.elems == nil { + n.elems = map[int]*refNode{} + } + if n.elems[i] == nil { + n.elems[i] = &refNode{} + } + n = n.elems[i] + } + } + n.ref = true + } + return out +} + +// Forced reports whether name is listed in the ForceSendFields fsf. Generated walkers +// use it to decide whether a zero value is set. +func Forced(fsf []string, name string) bool { + return slices.Contains(fsf, name) +} + +// IsZeroStruct reports whether the struct *v is the zero value. Generated walkers only +// need it for the few struct fields that are not pointers. +func IsZeroStruct[T any](v *T) bool { + return reflect.ValueOf(v).Elem().IsZero() +} diff --git a/bundle/config/walk/strings.go b/bundle/config/walk/strings.go new file mode 100644 index 00000000000..692c33953ff --- /dev/null +++ b/bundle/config/walk/strings.go @@ -0,0 +1,188 @@ +package walk + +import ( + "maps" + "reflect" + "slices" + "strings" + + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" +) + +// Mode selects the strings [Strings] visits. +type Mode int + +const ( + // WithReferences visits the strings that contain a variable reference ("${"). + WithReferences Mode = iota + // NonEmpty visits every non-empty string. + NonEmpty + // EmptyFields visits the struct fields with omitempty that hold an empty string + // and are still present: forced, or a pointer or interface holding "". + EmptyFields +) + +// StringFunc is called for a visited string s at n. It returns a replacement and +// whether to store it. +type StringFunc func(n *Node, s string) (string, bool) + +// Strings calls fn for the strings of the configuration selected by mode, with plain +// field access (strings_walk.go is generated by bundle/internal/validation). Pure +// references in fields that cannot hold a string are not strings: see +// [config.Root.References]. +// +// TODO: processors that run back to back (e.g. reference collection and the +// workspace-prefix rewrite) could share one walk: visit every string with its kind +// (empty, plain, containing "${", pure reference) and dispatch to each processor, +// paying for the traversal once. Phase ordering limits which ones can be combined. +func Strings(root *config.Root, mode Mode, fn StringFunc) { + c := &stringsWalk{mode: mode, fn: fn} + n := RootWithReferences(root) + stringsConfigRoot(c, &n, root) +} + +// StringsWithoutResources is [Strings] for the configuration outside "resources". +func StringsWithoutResources(root *config.Root, mode Mode, fn StringFunc) { + c := &stringsWalk{mode: mode, fn: fn, skipResources: true} + n := RootWithReferences(root) + stringsConfigRoot(c, &n, root) +} + +// ResourceStrings is [Strings] for "resources" only. +func ResourceStrings(root *config.Root, mode Mode, fn StringFunc) { + c := &stringsWalk{mode: mode, fn: fn} + n := rootWithReferencesUnder(root, structpath.NewStringKey(nil, "resources")) + ch := n.Child("resources") + stringsConfigResources(c, &ch, &root.Resources) +} + +// ResourceStringsOf is [Strings] for the resource name of type typ. +func ResourceStringsOf(root *config.Root, typ, name string, mode Mode, fn StringFunc) { + c := &stringsWalk{mode: mode, fn: fn} + n := rootWithReferencesUnder(root, structpath.NewPath(nil, "resources", typ, name)) + rn := n.Child("resources") + stringsResource(c, &rn, &root.Resources, typ, name) +} + +// VariableLookupStrings is [Strings] for the lookups of the variables +// ("variables.*.lookup"), in the order of the variable names. +func VariableLookupStrings(root *config.Root, mode Mode, fn StringFunc) { + c := &stringsWalk{mode: mode, fn: fn} + n := rootWithReferencesUnder(root, structpath.NewStringKey(nil, "variables")) + vn := n.Child("variables") + for _, k := range sortedKeys(root.Variables) { + v := root.Variables[k] + if v == nil || v.Lookup == nil { + continue + } + ch := vn.Child(k) + ln := ch.Child("lookup") + stringsVariableLookup(c, &ln, v.Lookup) + } +} + +type stringsWalk struct { + mode Mode + fn StringFunc + skipResources bool +} + +func (c *stringsWalk) match(s string) bool { + switch c.mode { + case WithReferences: + return strings.Contains(s, "${") + case NonEmpty: + return s != "" + default: + return false + } +} + +// any visits the strings of a dynamic value (e.g. a variable value): the maps, slices +// and strings that decoding produces. +func (c *stringsWalk) any(n *Node, v *any) { + switch x := (*v).(type) { + case string: + if c.match(x) { + if r, ok := c.fn(n, x); ok { + *v = r + } + } + case map[string]any: + for _, k := range sortedKeys(x) { + e := x[k] + ch := n.Child(k) + c.any(&ch, &e) + x[k] = e + } + case []any: + for i := range x { + ch := n.Elem(i) + c.any(&ch, &x[i]) + } + case nil, bool, int, int64, float64: + // Scalars decoded into a dynamic value hold no strings. + default: + // Other values set from code (typed maps, slices, structs, named strings). + rv := reflect.ValueOf(v).Elem() + c.value(n, rv) + } +} + +// value visits the strings of the dynamic value v, which is settable. +func (c *stringsWalk) value(n *Node, v reflect.Value) { + switch v.Kind() { + case reflect.Interface, reflect.Pointer: + if v.IsNil() { + return + } + e := v.Elem() + if v.Kind() == reflect.Interface { + // Interface values are not settable: visit a copy and store it back. + cp := reflect.New(e.Type()).Elem() + cp.Set(e) + c.value(n, cp) + v.Set(cp) + return + } + c.value(n, e) + case reflect.String: + if s := v.String(); c.match(s) { + if r, ok := c.fn(n, s); ok { + v.SetString(r) + } + } + case reflect.Slice, reflect.Array: + for i := range v.Len() { + ch := n.Elem(i) + c.value(&ch, v.Index(i)) + } + case reflect.Map: + if v.Type().Key().Kind() != reflect.String { + return + } + keys := v.MapKeys() + slices.SortFunc(keys, func(a, b reflect.Value) int { return strings.Compare(a.String(), b.String()) }) + for _, k := range keys { + e := reflect.New(v.Type().Elem()).Elem() + e.Set(v.MapIndex(k)) + ch := n.Child(k.String()) + c.value(&ch, e) + v.SetMapIndex(k, e) + } + case reflect.Struct: + info := structvar.GetStructInfo(v.Type()) + for _, f := range info.List { + ch := n.Child(f.Key) + c.value(&ch, structvar.FieldByIndex(v, f.Index)) + } + default: + } +} + +// sortedKeys returns the keys of m in the order maps are visited. +func sortedKeys[K ~string, V any](m map[K]V) []K { + return slices.Sorted(maps.Keys(m)) +} diff --git a/bundle/config/walk/strings_walk.go b/bundle/config/walk/strings_walk.go new file mode 100644 index 00000000000..99b3732a5bf --- /dev/null +++ b/bundle/config/walk/strings_walk.go @@ -0,0 +1,18593 @@ +// Code generated by bundle/internal/validation. DO NOT EDIT. + +package walk + +import ( + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/engine" + "github.com/databricks/cli/bundle/config/loctable" + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/config/variable" + "github.com/databricks/cli/libs/exec" + "github.com/databricks/cli/libs/snapshot" + "github.com/databricks/databricks-sdk-go/service/apps" + "github.com/databricks/databricks-sdk-go/service/catalog" + "github.com/databricks/databricks-sdk-go/service/compute" + "github.com/databricks/databricks-sdk-go/service/dashboards" + "github.com/databricks/databricks-sdk-go/service/database" + "github.com/databricks/databricks-sdk-go/service/iam" + "github.com/databricks/databricks-sdk-go/service/jobs" + "github.com/databricks/databricks-sdk-go/service/ml" + "github.com/databricks/databricks-sdk-go/service/pipelines" + "github.com/databricks/databricks-sdk-go/service/postgres" + "github.com/databricks/databricks-sdk-go/service/serving" + "github.com/databricks/databricks-sdk-go/service/sql" + "github.com/databricks/databricks-sdk-go/service/vectorsearch" + "github.com/databricks/databricks-sdk-go/service/workspace" +) + +func stringsConfigRoot(c *stringsWalk, n *Node, v *config.Root) { + if n.isRef() { + return + } + if !n.IsRef("variables") { + if len(v.Variables) > 0 { + cn := n.Child("variables") + for _, k := range sortedKeys(v.Variables) { + if x := v.Variables[k]; x != nil { + ch := cn.Child(string(k)) + stringsVariableVariable(c, &ch, x) + } + } + } + } + if !n.IsRef("bundle") { + { + ch := n.Child("bundle") + stringsConfigBundle(c, &ch, &v.Bundle) + } + } + if !n.IsRef("include") { + if len(v.Include) > 0 { + cn := n.Child("include") + for i := range v.Include { + if s := string(v.Include[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Include[i] = (r) + } + } + } + } + } + if !n.IsRef("workspace") { + { + ch := n.Child("workspace") + stringsConfigWorkspace(c, &ch, &v.Workspace) + } + } + if !n.IsRef("artifacts") { + if len(v.Artifacts) > 0 { + cn := n.Child("artifacts") + for _, k := range sortedKeys(v.Artifacts) { + if x := v.Artifacts[k]; x != nil { + ch := cn.Child(string(k)) + stringsConfigArtifact(c, &ch, x) + } + } + } + } + if !c.skipResources && !n.IsRef("resources") { + { + ch := n.Child("resources") + stringsConfigResources(c, &ch, &v.Resources) + } + } + if !n.IsRef("targets") { + if len(v.Targets) > 0 { + cn := n.Child("targets") + for _, k := range sortedKeys(v.Targets) { + if x := v.Targets[k]; x != nil { + ch := cn.Child(string(k)) + stringsConfigTarget(c, &ch, x) + } + } + } + } + if !n.IsRef("environments") { + if len(v.Environments) > 0 { + cn := n.Child("environments") + for _, k := range sortedKeys(v.Environments) { + if x := v.Environments[k]; x != nil { + ch := cn.Child(string(k)) + stringsConfigTarget(c, &ch, x) + } + } + } + } + if !n.IsRef("sync") { + { + ch := n.Child("sync") + stringsConfigSync(c, &ch, &v.Sync) + } + } + if !n.IsRef("run_as") { + if x := v.RunAs; x != nil { + ch := n.Child("run_as") + stringsJobsJobRunAs(c, &ch, x) + } + } + if !n.IsRef("presets") { + { + ch := n.Child("presets") + stringsConfigPresets(c, &ch, &v.Presets) + } + } + if !n.IsRef("experimental") { + if x := v.Experimental; x != nil { + ch := n.Child("experimental") + stringsConfigExperimental(c, &ch, x) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if !n.IsRef("__locations") { + if x := v.Locations; x != nil { + ch := n.Child("__locations") + stringsLoctableLocations(c, &ch, x) + } + } + if !n.IsRef("scripts") { + if len(v.Scripts) > 0 { + cn := n.Child("scripts") + for _, k := range sortedKeys(v.Scripts) { + x := v.Scripts[k] + { + ch := cn.Child(string(k)) + stringsConfigScript(c, &ch, &x) + } + v.Scripts[k] = x + } + } + } + if !n.IsRef("python") { + { + ch := n.Child("python") + stringsConfigPython(c, &ch, &v.Python) + } + } + if c.mode == EmptyFields && v.Definitions == "" { + ch := n.Child("definitions") + c.fn(&ch, "") + } + if v.Definitions != nil { + ch := n.Child("definitions") + c.any(&ch, &v.Definitions) + } +} + +func stringsVariableVariable(c *stringsWalk, n *Node, v *variable.Variable) { + if n.isRef() { + return + } + if s := string(v.Type); c.match(s) { + ch := n.Child("type") + if r, ok := c.fn(&ch, s); ok { + v.Type = variable.VariableType(r) + } + } + if c.mode == EmptyFields && v.Default == "" { + ch := n.Child("default") + c.fn(&ch, "") + } + if v.Default != nil { + ch := n.Child("default") + c.any(&ch, &v.Default) + } + if s := string(v.Description); c.match(s) { + ch := n.Child("description") + if r, ok := c.fn(&ch, s); ok { + v.Description = (r) + } + } + if c.mode == EmptyFields && v.Value == "" { + ch := n.Child("value") + c.fn(&ch, "") + } + if v.Value != nil { + ch := n.Child("value") + c.any(&ch, &v.Value) + } + if !n.IsRef("lookup") { + if x := v.Lookup; x != nil { + ch := n.Child("lookup") + stringsVariableLookup(c, &ch, x) + } + } +} + +func stringsConfigBundle(c *stringsWalk, n *Node, v *config.Bundle) { + if n.isRef() { + return + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if s := string(v.Target); c.match(s) { + ch := n.Child("target") + if r, ok := c.fn(&ch, s); ok { + v.Target = (r) + } + } + if s := string(v.Environment); c.match(s) { + ch := n.Child("environment") + if r, ok := c.fn(&ch, s); ok { + v.Environment = (r) + } + } + if !n.IsRef("terraform") { + if x := v.Terraform; x != nil { + ch := n.Child("terraform") + stringsConfigTerraform(c, &ch, x) + } + } + if !n.IsRef("git") { + { + ch := n.Child("git") + stringsConfigGit(c, &ch, &v.Git) + } + } + if s := string(v.Mode); c.match(s) { + ch := n.Child("mode") + if r, ok := c.fn(&ch, s); ok { + v.Mode = config.Mode(r) + } + } + if s := string(v.ComputeId); c.match(s) { + ch := n.Child("compute_id") + if r, ok := c.fn(&ch, s); ok { + v.ComputeId = (r) + } + } + if s := string(v.ClusterId); c.match(s) { + ch := n.Child("cluster_id") + if r, ok := c.fn(&ch, s); ok { + v.ClusterId = (r) + } + } + if s := string(v.Engine); c.match(s) { + ch := n.Child("engine") + if r, ok := c.fn(&ch, s); ok { + v.Engine = engine.EngineType(r) + } + } + if !n.IsRef("deployment") { + { + ch := n.Child("deployment") + stringsConfigDeployment(c, &ch, &v.Deployment) + } + } + if s := string(v.DatabricksCliVersion); c.match(s) { + ch := n.Child("databricks_cli_version") + if r, ok := c.fn(&ch, s); ok { + v.DatabricksCliVersion = (r) + } + } + if s := string(v.Uuid); c.match(s) { + ch := n.Child("uuid") + if r, ok := c.fn(&ch, s); ok { + v.Uuid = (r) + } + } +} + +func stringsConfigWorkspace(c *stringsWalk, n *Node, v *config.Workspace) { + if n.isRef() { + return + } + if s := string(v.Host); c.match(s) { + ch := n.Child("host") + if r, ok := c.fn(&ch, s); ok { + v.Host = (r) + } + } + if s := string(v.Profile); c.match(s) { + ch := n.Child("profile") + if r, ok := c.fn(&ch, s); ok { + v.Profile = (r) + } + } + if s := string(v.AuthType); c.match(s) { + ch := n.Child("auth_type") + if r, ok := c.fn(&ch, s); ok { + v.AuthType = (r) + } + } + if s := string(v.MetadataServiceURL); c.match(s) { + ch := n.Child("metadata_service_url") + if r, ok := c.fn(&ch, s); ok { + v.MetadataServiceURL = (r) + } + } + if s := string(v.ClientID); c.match(s) { + ch := n.Child("client_id") + if r, ok := c.fn(&ch, s); ok { + v.ClientID = (r) + } + } + if s := string(v.GoogleServiceAccount); c.match(s) { + ch := n.Child("google_service_account") + if r, ok := c.fn(&ch, s); ok { + v.GoogleServiceAccount = (r) + } + } + if s := string(v.AzureResourceID); c.match(s) { + ch := n.Child("azure_workspace_resource_id") + if r, ok := c.fn(&ch, s); ok { + v.AzureResourceID = (r) + } + } + if s := string(v.AzureClientID); c.match(s) { + ch := n.Child("azure_client_id") + if r, ok := c.fn(&ch, s); ok { + v.AzureClientID = (r) + } + } + if s := string(v.AzureTenantID); c.match(s) { + ch := n.Child("azure_tenant_id") + if r, ok := c.fn(&ch, s); ok { + v.AzureTenantID = (r) + } + } + if s := string(v.AzureEnvironment); c.match(s) { + ch := n.Child("azure_environment") + if r, ok := c.fn(&ch, s); ok { + v.AzureEnvironment = (r) + } + } + if s := string(v.AzureLoginAppID); c.match(s) { + ch := n.Child("azure_login_app_id") + if r, ok := c.fn(&ch, s); ok { + v.AzureLoginAppID = (r) + } + } + if s := string(v.AccountID); c.match(s) { + ch := n.Child("account_id") + if r, ok := c.fn(&ch, s); ok { + v.AccountID = (r) + } + } + if s := string(v.WorkspaceID); c.match(s) { + ch := n.Child("workspace_id") + if r, ok := c.fn(&ch, s); ok { + v.WorkspaceID = (r) + } + } + if !n.IsRef("current_user") { + if x := v.CurrentUser; x != nil { + ch := n.Child("current_user") + stringsConfigUser(c, &ch, x) + } + } + if s := string(v.RootPath); c.match(s) { + ch := n.Child("root_path") + if r, ok := c.fn(&ch, s); ok { + v.RootPath = (r) + } + } + if s := string(v.FilePath); c.match(s) { + ch := n.Child("file_path") + if r, ok := c.fn(&ch, s); ok { + v.FilePath = (r) + } + } + if s := string(v.ResourcePath); c.match(s) { + ch := n.Child("resource_path") + if r, ok := c.fn(&ch, s); ok { + v.ResourcePath = (r) + } + } + if s := string(v.ArtifactPath); c.match(s) { + ch := n.Child("artifact_path") + if r, ok := c.fn(&ch, s); ok { + v.ArtifactPath = (r) + } + } + if s := string(v.StatePath); c.match(s) { + ch := n.Child("state_path") + if r, ok := c.fn(&ch, s); ok { + v.StatePath = (r) + } + } +} + +func stringsConfigArtifact(c *stringsWalk, n *Node, v *config.Artifact) { + if n.isRef() { + return + } + if s := string(v.Type); c.match(s) { + ch := n.Child("type") + if r, ok := c.fn(&ch, s); ok { + v.Type = config.ArtifactType(r) + } + } + if s := string(v.Path); c.match(s) { + ch := n.Child("path") + if r, ok := c.fn(&ch, s); ok { + v.Path = (r) + } + } + if !n.IsRef("files") { + if len(v.Files) > 0 { + cn := n.Child("files") + for i := range v.Files { + { + ch := cn.Elem(i) + stringsConfigArtifactFile(c, &ch, &v.Files[i]) + } + } + } + } + if s := string(v.BuildCommand); c.match(s) { + ch := n.Child("build") + if r, ok := c.fn(&ch, s); ok { + v.BuildCommand = (r) + } + } + if s := string(v.Executable); c.match(s) { + ch := n.Child("executable") + if r, ok := c.fn(&ch, s); ok { + v.Executable = exec.ExecutableType(r) + } + } + if !n.IsRef("include") { + if len(v.Include) > 0 { + cn := n.Child("include") + for i := range v.Include { + if s := string(v.Include[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Include[i] = (r) + } + } + } + } + } + if !n.IsRef("git") { + if x := v.Git; x != nil { + ch := n.Child("git") + stringsConfigArtifactGit(c, &ch, x) + } + } +} + +func stringsConfigResources(c *stringsWalk, n *Node, v *config.Resources) { + if n.isRef() { + return + } + if !n.IsRef("jobs") { + if len(v.Jobs) > 0 { + cn := n.Child("jobs") + for _, k := range sortedKeys(v.Jobs) { + if x := v.Jobs[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesJob(c, &ch, x) + } + } + } + } + if !n.IsRef("job_runs") { + if len(v.JobRuns) > 0 { + cn := n.Child("job_runs") + for _, k := range sortedKeys(v.JobRuns) { + if x := v.JobRuns[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesJobRun(c, &ch, x) + } + } + } + } + if !n.IsRef("pipelines") { + if len(v.Pipelines) > 0 { + cn := n.Child("pipelines") + for _, k := range sortedKeys(v.Pipelines) { + if x := v.Pipelines[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesPipeline(c, &ch, x) + } + } + } + } + if !n.IsRef("features") { + if len(v.Features) > 0 { + cn := n.Child("features") + for _, k := range sortedKeys(v.Features) { + if x := v.Features[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesFeature(c, &ch, x) + } + } + } + } + if !n.IsRef("models") { + if len(v.Models) > 0 { + cn := n.Child("models") + for _, k := range sortedKeys(v.Models) { + if x := v.Models[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesMlflowModel(c, &ch, x) + } + } + } + } + if !n.IsRef("experiments") { + if len(v.Experiments) > 0 { + cn := n.Child("experiments") + for _, k := range sortedKeys(v.Experiments) { + if x := v.Experiments[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesMlflowExperiment(c, &ch, x) + } + } + } + } + if !n.IsRef("model_serving_endpoints") { + if len(v.ModelServingEndpoints) > 0 { + cn := n.Child("model_serving_endpoints") + for _, k := range sortedKeys(v.ModelServingEndpoints) { + if x := v.ModelServingEndpoints[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesModelServingEndpoint(c, &ch, x) + } + } + } + } + if !n.IsRef("model_services") { + if len(v.ModelServices) > 0 { + cn := n.Child("model_services") + for _, k := range sortedKeys(v.ModelServices) { + if x := v.ModelServices[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesModelService(c, &ch, x) + } + } + } + } + if !n.IsRef("mcp_services") { + if len(v.McpServices) > 0 { + cn := n.Child("mcp_services") + for _, k := range sortedKeys(v.McpServices) { + if x := v.McpServices[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesMcpService(c, &ch, x) + } + } + } + } + if !n.IsRef("model_provider_services") { + if len(v.ModelProviderServices) > 0 { + cn := n.Child("model_provider_services") + for _, k := range sortedKeys(v.ModelProviderServices) { + if x := v.ModelProviderServices[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesModelProviderService(c, &ch, x) + } + } + } + } + if !n.IsRef("registered_models") { + if len(v.RegisteredModels) > 0 { + cn := n.Child("registered_models") + for _, k := range sortedKeys(v.RegisteredModels) { + if x := v.RegisteredModels[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesRegisteredModel(c, &ch, x) + } + } + } + } + if !n.IsRef("quality_monitors") { + if len(v.QualityMonitors) > 0 { + cn := n.Child("quality_monitors") + for _, k := range sortedKeys(v.QualityMonitors) { + if x := v.QualityMonitors[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesQualityMonitor(c, &ch, x) + } + } + } + } + if !n.IsRef("catalogs") { + if len(v.Catalogs) > 0 { + cn := n.Child("catalogs") + for _, k := range sortedKeys(v.Catalogs) { + if x := v.Catalogs[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesCatalog(c, &ch, x) + } + } + } + } + if !n.IsRef("schemas") { + if len(v.Schemas) > 0 { + cn := n.Child("schemas") + for _, k := range sortedKeys(v.Schemas) { + if x := v.Schemas[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesSchema(c, &ch, x) + } + } + } + } + if !n.IsRef("volumes") { + if len(v.Volumes) > 0 { + cn := n.Child("volumes") + for _, k := range sortedKeys(v.Volumes) { + if x := v.Volumes[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesVolume(c, &ch, x) + } + } + } + } + if !n.IsRef("external_locations") { + if len(v.ExternalLocations) > 0 { + cn := n.Child("external_locations") + for _, k := range sortedKeys(v.ExternalLocations) { + if x := v.ExternalLocations[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesExternalLocation(c, &ch, x) + } + } + } + } + if !n.IsRef("clusters") { + if len(v.Clusters) > 0 { + cn := n.Child("clusters") + for _, k := range sortedKeys(v.Clusters) { + if x := v.Clusters[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesCluster(c, &ch, x) + } + } + } + } + if !n.IsRef("dashboards") { + if len(v.Dashboards) > 0 { + cn := n.Child("dashboards") + for _, k := range sortedKeys(v.Dashboards) { + if x := v.Dashboards[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesDashboard(c, &ch, x) + } + } + } + } + if !n.IsRef("genie_spaces") { + if len(v.GenieSpaces) > 0 { + cn := n.Child("genie_spaces") + for _, k := range sortedKeys(v.GenieSpaces) { + if x := v.GenieSpaces[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesGenieSpace(c, &ch, x) + } + } + } + } + if !n.IsRef("apps") { + if len(v.Apps) > 0 { + cn := n.Child("apps") + for _, k := range sortedKeys(v.Apps) { + if x := v.Apps[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesApp(c, &ch, x) + } + } + } + } + if !n.IsRef("secret_scopes") { + if len(v.SecretScopes) > 0 { + cn := n.Child("secret_scopes") + for _, k := range sortedKeys(v.SecretScopes) { + if x := v.SecretScopes[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesSecretScope(c, &ch, x) + } + } + } + } + if !n.IsRef("alerts") { + if len(v.Alerts) > 0 { + cn := n.Child("alerts") + for _, k := range sortedKeys(v.Alerts) { + if x := v.Alerts[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesAlert(c, &ch, x) + } + } + } + } + if !n.IsRef("sql_warehouses") { + if len(v.SqlWarehouses) > 0 { + cn := n.Child("sql_warehouses") + for _, k := range sortedKeys(v.SqlWarehouses) { + if x := v.SqlWarehouses[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesSqlWarehouse(c, &ch, x) + } + } + } + } + if !n.IsRef("database_instances") { + if len(v.DatabaseInstances) > 0 { + cn := n.Child("database_instances") + for _, k := range sortedKeys(v.DatabaseInstances) { + if x := v.DatabaseInstances[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesDatabaseInstance(c, &ch, x) + } + } + } + } + if !n.IsRef("database_catalogs") { + if len(v.DatabaseCatalogs) > 0 { + cn := n.Child("database_catalogs") + for _, k := range sortedKeys(v.DatabaseCatalogs) { + if x := v.DatabaseCatalogs[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesDatabaseCatalog(c, &ch, x) + } + } + } + } + if !n.IsRef("synced_database_tables") { + if len(v.SyncedDatabaseTables) > 0 { + cn := n.Child("synced_database_tables") + for _, k := range sortedKeys(v.SyncedDatabaseTables) { + if x := v.SyncedDatabaseTables[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesSyncedDatabaseTable(c, &ch, x) + } + } + } + } + if !n.IsRef("postgres_projects") { + if len(v.PostgresProjects) > 0 { + cn := n.Child("postgres_projects") + for _, k := range sortedKeys(v.PostgresProjects) { + if x := v.PostgresProjects[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesPostgresProject(c, &ch, x) + } + } + } + } + if !n.IsRef("postgres_branches") { + if len(v.PostgresBranches) > 0 { + cn := n.Child("postgres_branches") + for _, k := range sortedKeys(v.PostgresBranches) { + if x := v.PostgresBranches[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesPostgresBranch(c, &ch, x) + } + } + } + } + if !n.IsRef("postgres_endpoints") { + if len(v.PostgresEndpoints) > 0 { + cn := n.Child("postgres_endpoints") + for _, k := range sortedKeys(v.PostgresEndpoints) { + if x := v.PostgresEndpoints[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesPostgresEndpoint(c, &ch, x) + } + } + } + } + if !n.IsRef("postgres_catalogs") { + if len(v.PostgresCatalogs) > 0 { + cn := n.Child("postgres_catalogs") + for _, k := range sortedKeys(v.PostgresCatalogs) { + if x := v.PostgresCatalogs[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesPostgresCatalog(c, &ch, x) + } + } + } + } + if !n.IsRef("postgres_databases") { + if len(v.PostgresDatabases) > 0 { + cn := n.Child("postgres_databases") + for _, k := range sortedKeys(v.PostgresDatabases) { + if x := v.PostgresDatabases[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesPostgresDatabase(c, &ch, x) + } + } + } + } + if !n.IsRef("postgres_roles") { + if len(v.PostgresRoles) > 0 { + cn := n.Child("postgres_roles") + for _, k := range sortedKeys(v.PostgresRoles) { + if x := v.PostgresRoles[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesPostgresRole(c, &ch, x) + } + } + } + } + if !n.IsRef("postgres_synced_tables") { + if len(v.PostgresSyncedTables) > 0 { + cn := n.Child("postgres_synced_tables") + for _, k := range sortedKeys(v.PostgresSyncedTables) { + if x := v.PostgresSyncedTables[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesPostgresSyncedTable(c, &ch, x) + } + } + } + } + if !n.IsRef("postgres_snapshot_schedules") { + if len(v.PostgresSnapshotSchedules) > 0 { + cn := n.Child("postgres_snapshot_schedules") + for _, k := range sortedKeys(v.PostgresSnapshotSchedules) { + if x := v.PostgresSnapshotSchedules[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesPostgresSnapshotSchedule(c, &ch, x) + } + } + } + } + if !n.IsRef("vector_search_endpoints") { + if len(v.VectorSearchEndpoints) > 0 { + cn := n.Child("vector_search_endpoints") + for _, k := range sortedKeys(v.VectorSearchEndpoints) { + if x := v.VectorSearchEndpoints[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesVectorSearchEndpoint(c, &ch, x) + } + } + } + } + if !n.IsRef("vector_search_indexes") { + if len(v.VectorSearchIndexes) > 0 { + cn := n.Child("vector_search_indexes") + for _, k := range sortedKeys(v.VectorSearchIndexes) { + if x := v.VectorSearchIndexes[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesVectorSearchIndex(c, &ch, x) + } + } + } + } + if !n.IsRef("instance_pools") { + if len(v.InstancePools) > 0 { + cn := n.Child("instance_pools") + for _, k := range sortedKeys(v.InstancePools) { + if x := v.InstancePools[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesInstancePool(c, &ch, x) + } + } + } + } + if !n.IsRef("secrets") { + if len(v.Secrets) > 0 { + cn := n.Child("secrets") + for _, k := range sortedKeys(v.Secrets) { + if x := v.Secrets[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesSecret(c, &ch, x) + } + } + } + } + if !n.IsRef("cluster_policies") { + if len(v.ClusterPolicies) > 0 { + cn := n.Child("cluster_policies") + for _, k := range sortedKeys(v.ClusterPolicies) { + if x := v.ClusterPolicies[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesClusterPolicy(c, &ch, x) + } + } + } + } + if !n.IsRef("internal_immutable_snapshots") { + if len(v.Snapshots) > 0 { + cn := n.Child("internal_immutable_snapshots") + for _, k := range sortedKeys(v.Snapshots) { + if x := v.Snapshots[k]; x != nil { + ch := cn.Child(string(k)) + stringsResourcesSnapshot(c, &ch, x) + } + } + } + } +} + +func stringsConfigTarget(c *stringsWalk, n *Node, v *config.Target) { + if n.isRef() { + return + } + if s := string(v.Mode); c.match(s) { + ch := n.Child("mode") + if r, ok := c.fn(&ch, s); ok { + v.Mode = config.Mode(r) + } + } + if !n.IsRef("presets") { + { + ch := n.Child("presets") + stringsConfigPresets(c, &ch, &v.Presets) + } + } + if s := string(v.ComputeId); c.match(s) { + ch := n.Child("compute_id") + if r, ok := c.fn(&ch, s); ok { + v.ComputeId = (r) + } + } + if s := string(v.ClusterId); c.match(s) { + ch := n.Child("cluster_id") + if r, ok := c.fn(&ch, s); ok { + v.ClusterId = (r) + } + } + if !n.IsRef("bundle") { + if x := v.Bundle; x != nil { + ch := n.Child("bundle") + stringsConfigBundle(c, &ch, x) + } + } + if !n.IsRef("workspace") { + if x := v.Workspace; x != nil { + ch := n.Child("workspace") + stringsConfigWorkspace(c, &ch, x) + } + } + if !n.IsRef("artifacts") { + if len(v.Artifacts) > 0 { + cn := n.Child("artifacts") + for _, k := range sortedKeys(v.Artifacts) { + if x := v.Artifacts[k]; x != nil { + ch := cn.Child(string(k)) + stringsConfigArtifact(c, &ch, x) + } + } + } + } + if !n.IsRef("resources") { + if x := v.Resources; x != nil { + ch := n.Child("resources") + stringsConfigResources(c, &ch, x) + } + } + if !n.IsRef("variables") { + if len(v.Variables) > 0 { + cn := n.Child("variables") + for _, k := range sortedKeys(v.Variables) { + if x := v.Variables[k]; x != nil { + ch := cn.Child(string(k)) + stringsVariableTargetVariable(c, &ch, x) + } + } + } + } + if !n.IsRef("git") { + { + ch := n.Child("git") + stringsConfigGit(c, &ch, &v.Git) + } + } + if !n.IsRef("run_as") { + if x := v.RunAs; x != nil { + ch := n.Child("run_as") + stringsJobsJobRunAs(c, &ch, x) + } + } + if !n.IsRef("sync") { + if x := v.Sync; x != nil { + ch := n.Child("sync") + stringsConfigSync(c, &ch, x) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesPermission(c, &ch, &v.Permissions[i]) + } + } + } + } +} + +func stringsConfigSync(c *stringsWalk, n *Node, v *config.Sync) { + if n.isRef() { + return + } + if !n.IsRef("paths") { + if len(v.Paths) > 0 { + cn := n.Child("paths") + for i := range v.Paths { + if s := string(v.Paths[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Paths[i] = (r) + } + } + } + } + } + if !n.IsRef("include") { + if len(v.Include) > 0 { + cn := n.Child("include") + for i := range v.Include { + if s := string(v.Include[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Include[i] = (r) + } + } + } + } + } + if !n.IsRef("exclude") { + if len(v.Exclude) > 0 { + cn := n.Child("exclude") + for i := range v.Exclude { + if s := string(v.Exclude[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Exclude[i] = (r) + } + } + } + } + } +} + +func stringsJobsJobRunAs(c *stringsWalk, n *Node, v *jobs.JobRunAs) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.GroupName == "" && Forced(v.ForceSendFields, "GroupName") { + ch := n.Child("group_name") + c.fn(&ch, "") + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } + if c.mode == EmptyFields && v.ServicePrincipalName == "" && Forced(v.ForceSendFields, "ServicePrincipalName") { + ch := n.Child("service_principal_name") + c.fn(&ch, "") + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if c.mode == EmptyFields && v.UserName == "" && Forced(v.ForceSendFields, "UserName") { + ch := n.Child("user_name") + c.fn(&ch, "") + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } +} + +func stringsConfigPresets(c *stringsWalk, n *Node, v *config.Presets) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.NamePrefix == "" && Forced(v.ForceSendFields, "NamePrefix") { + ch := n.Child("name_prefix") + c.fn(&ch, "") + } + if s := string(v.NamePrefix); c.match(s) { + ch := n.Child("name_prefix") + if r, ok := c.fn(&ch, s); ok { + v.NamePrefix = (r) + } + } + if c.mode == EmptyFields && v.TriggerPauseStatus == "" && Forced(v.ForceSendFields, "TriggerPauseStatus") { + ch := n.Child("trigger_pause_status") + c.fn(&ch, "") + } + if s := string(v.TriggerPauseStatus); c.match(s) { + ch := n.Child("trigger_pause_status") + if r, ok := c.fn(&ch, s); ok { + v.TriggerPauseStatus = (r) + } + } + if !n.IsRef("tags") { + if len(v.Tags) > 0 { + cn := n.Child("tags") + for _, k := range sortedKeys(v.Tags) { + if s := string(v.Tags[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Tags[k] = (r) + } + } + } + } + } +} + +func stringsConfigExperimental(c *stringsWalk, n *Node, v *config.Experimental) { + if n.isRef() { + return + } + if !n.IsRef("scripts") { + if len(v.Scripts) > 0 { + cn := n.Child("scripts") + for _, k := range sortedKeys(v.Scripts) { + if s := string(v.Scripts[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Scripts[k] = config.Command(r) + } + } + } + } + } + if !n.IsRef("python") { + { + ch := n.Child("python") + stringsConfigPython(c, &ch, &v.Python) + } + } +} + +func stringsResourcesPermission(c *stringsWalk, n *Node, v *resources.Permission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = iam.PermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsLoctableLocations(c *stringsWalk, n *Node, v *loctable.Locations) { + if n.isRef() { + return + } + if !n.IsRef("files") { + if len(v.Files) > 0 { + cn := n.Child("files") + for i := range v.Files { + if s := string(v.Files[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Files[i] = (r) + } + } + } + } + } +} + +func stringsConfigScript(c *stringsWalk, n *Node, v *config.Script) { + if n.isRef() { + return + } + if s := string(v.Content); c.match(s) { + ch := n.Child("content") + if r, ok := c.fn(&ch, s); ok { + v.Content = (r) + } + } + if !n.IsRef("env") { + if len(v.Env) > 0 { + cn := n.Child("env") + for _, k := range sortedKeys(v.Env) { + if s := string(v.Env[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Env[k] = (r) + } + } + } + } + } +} + +func stringsConfigPython(c *stringsWalk, n *Node, v *config.Python) { + if n.isRef() { + return + } + if !n.IsRef("resources") { + if len(v.Resources) > 0 { + cn := n.Child("resources") + for i := range v.Resources { + if s := string(v.Resources[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Resources[i] = (r) + } + } + } + } + } + if !n.IsRef("mutators") { + if len(v.Mutators) > 0 { + cn := n.Child("mutators") + for i := range v.Mutators { + if s := string(v.Mutators[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Mutators[i] = (r) + } + } + } + } + } + if s := string(v.VEnvPath); c.match(s) { + ch := n.Child("venv_path") + if r, ok := c.fn(&ch, s); ok { + v.VEnvPath = (r) + } + } +} + +func stringsVariableLookup(c *stringsWalk, n *Node, v *variable.Lookup) { + if n.isRef() { + return + } + if s := string(v.Alert); c.match(s) { + ch := n.Child("alert") + if r, ok := c.fn(&ch, s); ok { + v.Alert = (r) + } + } + if s := string(v.ClusterPolicy); c.match(s) { + ch := n.Child("cluster_policy") + if r, ok := c.fn(&ch, s); ok { + v.ClusterPolicy = (r) + } + } + if s := string(v.Cluster); c.match(s) { + ch := n.Child("cluster") + if r, ok := c.fn(&ch, s); ok { + v.Cluster = (r) + } + } + if s := string(v.Dashboard); c.match(s) { + ch := n.Child("dashboard") + if r, ok := c.fn(&ch, s); ok { + v.Dashboard = (r) + } + } + if s := string(v.InstancePool); c.match(s) { + ch := n.Child("instance_pool") + if r, ok := c.fn(&ch, s); ok { + v.InstancePool = (r) + } + } + if s := string(v.Job); c.match(s) { + ch := n.Child("job") + if r, ok := c.fn(&ch, s); ok { + v.Job = (r) + } + } + if s := string(v.Metastore); c.match(s) { + ch := n.Child("metastore") + if r, ok := c.fn(&ch, s); ok { + v.Metastore = (r) + } + } + if s := string(v.NotificationDestination); c.match(s) { + ch := n.Child("notification_destination") + if r, ok := c.fn(&ch, s); ok { + v.NotificationDestination = (r) + } + } + if s := string(v.Pipeline); c.match(s) { + ch := n.Child("pipeline") + if r, ok := c.fn(&ch, s); ok { + v.Pipeline = (r) + } + } + if s := string(v.Query); c.match(s) { + ch := n.Child("query") + if r, ok := c.fn(&ch, s); ok { + v.Query = (r) + } + } + if s := string(v.ServicePrincipal); c.match(s) { + ch := n.Child("service_principal") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipal = (r) + } + } + if s := string(v.Warehouse); c.match(s) { + ch := n.Child("warehouse") + if r, ok := c.fn(&ch, s); ok { + v.Warehouse = (r) + } + } +} + +func stringsConfigTerraform(c *stringsWalk, n *Node, v *config.Terraform) { + if n.isRef() { + return + } + if s := string(v.ExecPath); c.match(s) { + ch := n.Child("exec_path") + if r, ok := c.fn(&ch, s); ok { + v.ExecPath = (r) + } + } +} + +func stringsConfigGit(c *stringsWalk, n *Node, v *config.Git) { + if n.isRef() { + return + } + if s := string(v.Branch); c.match(s) { + ch := n.Child("branch") + if r, ok := c.fn(&ch, s); ok { + v.Branch = (r) + } + } + if s := string(v.OriginURL); c.match(s) { + ch := n.Child("origin_url") + if r, ok := c.fn(&ch, s); ok { + v.OriginURL = (r) + } + } + if s := string(v.Commit); c.match(s) { + ch := n.Child("commit") + if r, ok := c.fn(&ch, s); ok { + v.Commit = (r) + } + } + if s := string(v.BundleRootPath); c.match(s) { + ch := n.Child("bundle_root_path") + if r, ok := c.fn(&ch, s); ok { + v.BundleRootPath = (r) + } + } + if s := string(v.ActualBranch); c.match(s) { + ch := n.Child("actual_branch") + if r, ok := c.fn(&ch, s); ok { + v.ActualBranch = (r) + } + } +} + +func stringsConfigDeployment(c *stringsWalk, n *Node, v *config.Deployment) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DeploymentID == "" && Forced(v.ForceSendFields, "DeploymentID") { + ch := n.Child("deployment_id") + c.fn(&ch, "") + } + if s := string(v.DeploymentID); c.match(s) { + ch := n.Child("deployment_id") + if r, ok := c.fn(&ch, s); ok { + v.DeploymentID = (r) + } + } +} + +func stringsConfigUser(c *stringsWalk, n *Node, v *config.User) { + if n.isRef() { + return + } + if s := string(v.ShortName); c.match(s) { + ch := n.Child("short_name") + if r, ok := c.fn(&ch, s); ok { + v.ShortName = (r) + } + } + if s := string(v.DomainFriendlyName); c.match(s) { + ch := n.Child("domain_friendly_name") + if r, ok := c.fn(&ch, s); ok { + v.DomainFriendlyName = (r) + } + } + if v.User != nil { + if c.mode == EmptyFields && v.User.DisplayName == "" && Forced(v.User.ForceSendFields, "DisplayName") { + ch := n.Child("displayName") + c.fn(&ch, "") + } + if s := string(v.User.DisplayName); c.match(s) { + ch := n.Child("displayName") + if r, ok := c.fn(&ch, s); ok { + v.User.DisplayName = (r) + } + } + } + if v.User != nil && !n.IsRef("emails") { + if len(v.User.Emails) > 0 { + cn := n.Child("emails") + for i := range v.User.Emails { + { + ch := cn.Elem(i) + stringsIamComplexValue(c, &ch, &v.User.Emails[i]) + } + } + } + } + if v.User != nil && !n.IsRef("entitlements") { + if len(v.User.Entitlements) > 0 { + cn := n.Child("entitlements") + for i := range v.User.Entitlements { + { + ch := cn.Elem(i) + stringsIamComplexValue(c, &ch, &v.User.Entitlements[i]) + } + } + } + } + if v.User != nil { + if c.mode == EmptyFields && v.User.ExternalId == "" && Forced(v.User.ForceSendFields, "ExternalId") { + ch := n.Child("externalId") + c.fn(&ch, "") + } + if s := string(v.User.ExternalId); c.match(s) { + ch := n.Child("externalId") + if r, ok := c.fn(&ch, s); ok { + v.User.ExternalId = (r) + } + } + } + if v.User != nil && !n.IsRef("groups") { + if len(v.User.Groups) > 0 { + cn := n.Child("groups") + for i := range v.User.Groups { + { + ch := cn.Elem(i) + stringsIamComplexValue(c, &ch, &v.User.Groups[i]) + } + } + } + } + if v.User != nil { + if c.mode == EmptyFields && v.User.Id == "" && Forced(v.User.ForceSendFields, "Id") { + ch := n.Child("id") + c.fn(&ch, "") + } + if s := string(v.User.Id); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.User.Id = (r) + } + } + } + if v.User != nil && !n.IsRef("name") { + if x := v.User.Name; x != nil { + ch := n.Child("name") + stringsIamName(c, &ch, x) + } + } + if v.User != nil && !n.IsRef("roles") { + if len(v.User.Roles) > 0 { + cn := n.Child("roles") + for i := range v.User.Roles { + { + ch := cn.Elem(i) + stringsIamComplexValue(c, &ch, &v.User.Roles[i]) + } + } + } + } + if v.User != nil && !n.IsRef("schemas") { + if len(v.User.Schemas) > 0 { + cn := n.Child("schemas") + for i := range v.User.Schemas { + if s := string(v.User.Schemas[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.User.Schemas[i] = iam.UserSchema(r) + } + } + } + } + } + if v.User != nil { + if c.mode == EmptyFields && v.User.UserName == "" && Forced(v.User.ForceSendFields, "UserName") { + ch := n.Child("userName") + c.fn(&ch, "") + } + if s := string(v.User.UserName); c.match(s) { + ch := n.Child("userName") + if r, ok := c.fn(&ch, s); ok { + v.User.UserName = (r) + } + } + } +} + +func stringsConfigArtifactFile(c *stringsWalk, n *Node, v *config.ArtifactFile) { + if n.isRef() { + return + } + if s := string(v.Source); c.match(s) { + ch := n.Child("source") + if r, ok := c.fn(&ch, s); ok { + v.Source = (r) + } + } + if s := string(v.Patched); c.match(s) { + ch := n.Child("patched") + if r, ok := c.fn(&ch, s); ok { + v.Patched = (r) + } + } + if s := string(v.RemotePath); c.match(s) { + ch := n.Child("remote_path") + if r, ok := c.fn(&ch, s); ok { + v.RemotePath = (r) + } + } +} + +func stringsConfigArtifactGit(c *stringsWalk, n *Node, v *config.ArtifactGit) { + if n.isRef() { + return + } + if s := string(v.Branch); c.match(s) { + ch := n.Child("branch") + if r, ok := c.fn(&ch, s); ok { + v.Branch = (r) + } + } + if s := string(v.Commit); c.match(s) { + ch := n.Child("commit") + if r, ok := c.fn(&ch, s); ok { + v.Commit = (r) + } + } +} + +func stringsResourcesJob(c *stringsWalk, n *Node, v *resources.Job) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesJobPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.JobSettings.BudgetPolicyId == "" && Forced(v.JobSettings.ForceSendFields, "BudgetPolicyId") { + ch := n.Child("budget_policy_id") + c.fn(&ch, "") + } + if s := string(v.JobSettings.BudgetPolicyId); c.match(s) { + ch := n.Child("budget_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.JobSettings.BudgetPolicyId = (r) + } + } + if !n.IsRef("continuous") { + if x := v.JobSettings.Continuous; x != nil { + ch := n.Child("continuous") + stringsJobsContinuous(c, &ch, x) + } + } + if !n.IsRef("deployment") { + if x := v.JobSettings.Deployment; x != nil { + ch := n.Child("deployment") + stringsJobsJobDeployment(c, &ch, x) + } + } + if c.mode == EmptyFields && v.JobSettings.Description == "" && Forced(v.JobSettings.ForceSendFields, "Description") { + ch := n.Child("description") + c.fn(&ch, "") + } + if s := string(v.JobSettings.Description); c.match(s) { + ch := n.Child("description") + if r, ok := c.fn(&ch, s); ok { + v.JobSettings.Description = (r) + } + } + if c.mode == EmptyFields && v.JobSettings.DurationTarget == "" && Forced(v.JobSettings.ForceSendFields, "DurationTarget") { + ch := n.Child("duration_target") + c.fn(&ch, "") + } + if s := string(v.JobSettings.DurationTarget); c.match(s) { + ch := n.Child("duration_target") + if r, ok := c.fn(&ch, s); ok { + v.JobSettings.DurationTarget = (r) + } + } + if c.mode == EmptyFields && v.JobSettings.EditMode == "" && Forced(v.JobSettings.ForceSendFields, "EditMode") { + ch := n.Child("edit_mode") + c.fn(&ch, "") + } + if s := string(v.JobSettings.EditMode); c.match(s) { + ch := n.Child("edit_mode") + if r, ok := c.fn(&ch, s); ok { + v.JobSettings.EditMode = jobs.JobEditMode(r) + } + } + if !n.IsRef("email_notifications") { + if x := v.JobSettings.EmailNotifications; x != nil { + ch := n.Child("email_notifications") + stringsJobsJobEmailNotifications(c, &ch, x) + } + } + if !n.IsRef("environment_variables") { + if len(v.JobSettings.EnvironmentVariables) > 0 { + cn := n.Child("environment_variables") + for i := range v.JobSettings.EnvironmentVariables { + { + ch := cn.Elem(i) + stringsJobsJobEnvironmentVariables(c, &ch, &v.JobSettings.EnvironmentVariables[i]) + } + } + } + } + if !n.IsRef("environments") { + if len(v.JobSettings.Environments) > 0 { + cn := n.Child("environments") + for i := range v.JobSettings.Environments { + { + ch := cn.Elem(i) + stringsJobsJobEnvironment(c, &ch, &v.JobSettings.Environments[i]) + } + } + } + } + if c.mode == EmptyFields && v.JobSettings.Format == "" && Forced(v.JobSettings.ForceSendFields, "Format") { + ch := n.Child("format") + c.fn(&ch, "") + } + if s := string(v.JobSettings.Format); c.match(s) { + ch := n.Child("format") + if r, ok := c.fn(&ch, s); ok { + v.JobSettings.Format = jobs.Format(r) + } + } + if !n.IsRef("git_source") { + if x := v.JobSettings.GitSource; x != nil { + ch := n.Child("git_source") + stringsJobsGitSource(c, &ch, x) + } + } + if !n.IsRef("health") { + if x := v.JobSettings.Health; x != nil { + ch := n.Child("health") + stringsJobsJobsHealthRules(c, &ch, x) + } + } + if !n.IsRef("job_clusters") { + if len(v.JobSettings.JobClusters) > 0 { + cn := n.Child("job_clusters") + for i := range v.JobSettings.JobClusters { + { + ch := cn.Elem(i) + stringsJobsJobCluster(c, &ch, &v.JobSettings.JobClusters[i]) + } + } + } + } + if c.mode == EmptyFields && v.JobSettings.Name == "" && Forced(v.JobSettings.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.JobSettings.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.JobSettings.Name = (r) + } + } + if !n.IsRef("parameters") { + if len(v.JobSettings.Parameters) > 0 { + cn := n.Child("parameters") + for i := range v.JobSettings.Parameters { + { + ch := cn.Elem(i) + stringsJobsJobParameterDefinition(c, &ch, &v.JobSettings.Parameters[i]) + } + } + } + } + if c.mode == EmptyFields && v.JobSettings.ParentPath == "" && Forced(v.JobSettings.ForceSendFields, "ParentPath") { + ch := n.Child("parent_path") + c.fn(&ch, "") + } + if s := string(v.JobSettings.ParentPath); c.match(s) { + ch := n.Child("parent_path") + if r, ok := c.fn(&ch, s); ok { + v.JobSettings.ParentPath = (r) + } + } + if c.mode == EmptyFields && v.JobSettings.PerformanceTarget == "" && Forced(v.JobSettings.ForceSendFields, "PerformanceTarget") { + ch := n.Child("performance_target") + c.fn(&ch, "") + } + if s := string(v.JobSettings.PerformanceTarget); c.match(s) { + ch := n.Child("performance_target") + if r, ok := c.fn(&ch, s); ok { + v.JobSettings.PerformanceTarget = jobs.PerformanceTarget(r) + } + } + if !n.IsRef("run_as") { + if x := v.JobSettings.RunAs; x != nil { + ch := n.Child("run_as") + stringsJobsJobRunAs(c, &ch, x) + } + } + if !n.IsRef("schedule") { + if x := v.JobSettings.Schedule; x != nil { + ch := n.Child("schedule") + stringsJobsCronSchedule(c, &ch, x) + } + } + if !n.IsRef("tags") { + if len(v.JobSettings.Tags) > 0 { + cn := n.Child("tags") + for _, k := range sortedKeys(v.JobSettings.Tags) { + if s := string(v.JobSettings.Tags[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.JobSettings.Tags[k] = (r) + } + } + } + } + } + if !n.IsRef("tasks") { + if len(v.JobSettings.Tasks) > 0 { + cn := n.Child("tasks") + for i := range v.JobSettings.Tasks { + { + ch := cn.Elem(i) + stringsJobsTask(c, &ch, &v.JobSettings.Tasks[i]) + } + } + } + } + if !n.IsRef("trigger") { + if x := v.JobSettings.Trigger; x != nil { + ch := n.Child("trigger") + stringsJobsTriggerSettings(c, &ch, x) + } + } + if !n.IsRef("triggers") { + if len(v.JobSettings.Triggers) > 0 { + cn := n.Child("triggers") + for i := range v.JobSettings.Triggers { + { + ch := cn.Elem(i) + stringsJobsTriggerConfiguration(c, &ch, &v.JobSettings.Triggers[i]) + } + } + } + } + if c.mode == EmptyFields && v.JobSettings.UsagePolicyId == "" && Forced(v.JobSettings.ForceSendFields, "UsagePolicyId") { + ch := n.Child("usage_policy_id") + c.fn(&ch, "") + } + if s := string(v.JobSettings.UsagePolicyId); c.match(s) { + ch := n.Child("usage_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.JobSettings.UsagePolicyId = (r) + } + } + if !n.IsRef("webhook_notifications") { + if x := v.JobSettings.WebhookNotifications; x != nil { + ch := n.Child("webhook_notifications") + stringsJobsWebhookNotifications(c, &ch, x) + } + } +} + +func stringsResourcesJobRun(c *stringsWalk, n *Node, v *resources.JobRun) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("lifecycle") { + if x := v.Lifecycle; x != nil { + ch := n.Child("lifecycle") + stringsResourcesJobRunLifecycle(c, &ch, x) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if !n.IsRef("dbt_commands") { + if len(v.RunNow.DbtCommands) > 0 { + cn := n.Child("dbt_commands") + for i := range v.RunNow.DbtCommands { + if s := string(v.RunNow.DbtCommands[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.RunNow.DbtCommands[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.RunNow.DurationTarget == "" && Forced(v.RunNow.ForceSendFields, "DurationTarget") { + ch := n.Child("duration_target") + c.fn(&ch, "") + } + if s := string(v.RunNow.DurationTarget); c.match(s) { + ch := n.Child("duration_target") + if r, ok := c.fn(&ch, s); ok { + v.RunNow.DurationTarget = (r) + } + } + if c.mode == EmptyFields && v.RunNow.IdempotencyToken == "" && Forced(v.RunNow.ForceSendFields, "IdempotencyToken") { + ch := n.Child("idempotency_token") + c.fn(&ch, "") + } + if s := string(v.RunNow.IdempotencyToken); c.match(s) { + ch := n.Child("idempotency_token") + if r, ok := c.fn(&ch, s); ok { + v.RunNow.IdempotencyToken = (r) + } + } + if !n.IsRef("jar_params") { + if len(v.RunNow.JarParams) > 0 { + cn := n.Child("jar_params") + for i := range v.RunNow.JarParams { + if s := string(v.RunNow.JarParams[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.RunNow.JarParams[i] = (r) + } + } + } + } + } + if !n.IsRef("job_parameters") { + if len(v.RunNow.JobParameters) > 0 { + cn := n.Child("job_parameters") + for _, k := range sortedKeys(v.RunNow.JobParameters) { + if s := string(v.RunNow.JobParameters[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.RunNow.JobParameters[k] = (r) + } + } + } + } + } + if !n.IsRef("notebook_params") { + if len(v.RunNow.NotebookParams) > 0 { + cn := n.Child("notebook_params") + for _, k := range sortedKeys(v.RunNow.NotebookParams) { + if s := string(v.RunNow.NotebookParams[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.RunNow.NotebookParams[k] = (r) + } + } + } + } + } + if !n.IsRef("only") { + if len(v.RunNow.Only) > 0 { + cn := n.Child("only") + for i := range v.RunNow.Only { + if s := string(v.RunNow.Only[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.RunNow.Only[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.RunNow.PerformanceTarget == "" && Forced(v.RunNow.ForceSendFields, "PerformanceTarget") { + ch := n.Child("performance_target") + c.fn(&ch, "") + } + if s := string(v.RunNow.PerformanceTarget); c.match(s) { + ch := n.Child("performance_target") + if r, ok := c.fn(&ch, s); ok { + v.RunNow.PerformanceTarget = jobs.PerformanceTarget(r) + } + } + if !n.IsRef("pipeline_params") { + if x := v.RunNow.PipelineParams; x != nil { + ch := n.Child("pipeline_params") + stringsJobsPipelineParams(c, &ch, x) + } + } + if !n.IsRef("python_named_params") { + if len(v.RunNow.PythonNamedParams) > 0 { + cn := n.Child("python_named_params") + for _, k := range sortedKeys(v.RunNow.PythonNamedParams) { + if s := string(v.RunNow.PythonNamedParams[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.RunNow.PythonNamedParams[k] = (r) + } + } + } + } + } + if !n.IsRef("python_params") { + if len(v.RunNow.PythonParams) > 0 { + cn := n.Child("python_params") + for i := range v.RunNow.PythonParams { + if s := string(v.RunNow.PythonParams[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.RunNow.PythonParams[i] = (r) + } + } + } + } + } + if !n.IsRef("spark_submit_params") { + if len(v.RunNow.SparkSubmitParams) > 0 { + cn := n.Child("spark_submit_params") + for i := range v.RunNow.SparkSubmitParams { + if s := string(v.RunNow.SparkSubmitParams[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.RunNow.SparkSubmitParams[i] = (r) + } + } + } + } + } + if !n.IsRef("sql_params") { + if len(v.RunNow.SqlParams) > 0 { + cn := n.Child("sql_params") + for _, k := range sortedKeys(v.RunNow.SqlParams) { + if s := string(v.RunNow.SqlParams[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.RunNow.SqlParams[k] = (r) + } + } + } + } + } +} + +func stringsResourcesPipeline(c *stringsWalk, n *Node, v *resources.Pipeline) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesPipelinePermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.CreatePipeline.BudgetPolicyId == "" && Forced(v.CreatePipeline.ForceSendFields, "BudgetPolicyId") { + ch := n.Child("budget_policy_id") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.BudgetPolicyId); c.match(s) { + ch := n.Child("budget_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.BudgetPolicyId = (r) + } + } + if c.mode == EmptyFields && v.CreatePipeline.Catalog == "" && Forced(v.CreatePipeline.ForceSendFields, "Catalog") { + ch := n.Child("catalog") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.Catalog); c.match(s) { + ch := n.Child("catalog") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.Catalog = (r) + } + } + if c.mode == EmptyFields && v.CreatePipeline.Channel == "" && Forced(v.CreatePipeline.ForceSendFields, "Channel") { + ch := n.Child("channel") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.Channel); c.match(s) { + ch := n.Child("channel") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.Channel = (r) + } + } + if !n.IsRef("clusters") { + if len(v.CreatePipeline.Clusters) > 0 { + cn := n.Child("clusters") + for i := range v.CreatePipeline.Clusters { + { + ch := cn.Elem(i) + stringsPipelinesPipelineCluster(c, &ch, &v.CreatePipeline.Clusters[i]) + } + } + } + } + if !n.IsRef("configuration") { + if len(v.CreatePipeline.Configuration) > 0 { + cn := n.Child("configuration") + for _, k := range sortedKeys(v.CreatePipeline.Configuration) { + if s := string(v.CreatePipeline.Configuration[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.Configuration[k] = (r) + } + } + } + } + } + if !n.IsRef("deployment") { + if x := v.CreatePipeline.Deployment; x != nil { + ch := n.Child("deployment") + stringsPipelinesPipelineDeployment(c, &ch, x) + } + } + if c.mode == EmptyFields && v.CreatePipeline.Edition == "" && Forced(v.CreatePipeline.ForceSendFields, "Edition") { + ch := n.Child("edition") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.Edition); c.match(s) { + ch := n.Child("edition") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.Edition = (r) + } + } + if !n.IsRef("environment") { + if x := v.CreatePipeline.Environment; x != nil { + ch := n.Child("environment") + stringsPipelinesPipelinesEnvironment(c, &ch, x) + } + } + if !n.IsRef("event_log") { + if x := v.CreatePipeline.EventLog; x != nil { + ch := n.Child("event_log") + stringsPipelinesEventLogSpec(c, &ch, x) + } + } + if !n.IsRef("filters") { + if x := v.CreatePipeline.Filters; x != nil { + ch := n.Child("filters") + stringsPipelinesFilters(c, &ch, x) + } + } + if !n.IsRef("gateway_definition") { + if x := v.CreatePipeline.GatewayDefinition; x != nil { + ch := n.Child("gateway_definition") + stringsPipelinesIngestionGatewayPipelineDefinition(c, &ch, x) + } + } + if !n.IsRef("ingestion_definition") { + if x := v.CreatePipeline.IngestionDefinition; x != nil { + ch := n.Child("ingestion_definition") + stringsPipelinesIngestionPipelineDefinition(c, &ch, x) + } + } + if !n.IsRef("libraries") { + if len(v.CreatePipeline.Libraries) > 0 { + cn := n.Child("libraries") + for i := range v.CreatePipeline.Libraries { + { + ch := cn.Elem(i) + stringsPipelinesPipelineLibrary(c, &ch, &v.CreatePipeline.Libraries[i]) + } + } + } + } + if c.mode == EmptyFields && v.CreatePipeline.Name == "" && Forced(v.CreatePipeline.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.Name = (r) + } + } + if !n.IsRef("notifications") { + if len(v.CreatePipeline.Notifications) > 0 { + cn := n.Child("notifications") + for i := range v.CreatePipeline.Notifications { + { + ch := cn.Elem(i) + stringsPipelinesNotifications(c, &ch, &v.CreatePipeline.Notifications[i]) + } + } + } + } + if !n.IsRef("parameters") { + if len(v.CreatePipeline.Parameters) > 0 { + cn := n.Child("parameters") + for _, k := range sortedKeys(v.CreatePipeline.Parameters) { + if s := string(v.CreatePipeline.Parameters[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.Parameters[k] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.CreatePipeline.ParentPath == "" && Forced(v.CreatePipeline.ForceSendFields, "ParentPath") { + ch := n.Child("parent_path") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.ParentPath); c.match(s) { + ch := n.Child("parent_path") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.ParentPath = (r) + } + } + if !n.IsRef("restart_window") { + if x := v.CreatePipeline.RestartWindow; x != nil { + ch := n.Child("restart_window") + stringsPipelinesRestartWindow(c, &ch, x) + } + } + if c.mode == EmptyFields && v.CreatePipeline.RootPath == "" && Forced(v.CreatePipeline.ForceSendFields, "RootPath") { + ch := n.Child("root_path") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.RootPath); c.match(s) { + ch := n.Child("root_path") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.RootPath = (r) + } + } + if !n.IsRef("run_as") { + if x := v.CreatePipeline.RunAs; x != nil { + ch := n.Child("run_as") + stringsPipelinesRunAs(c, &ch, x) + } + } + if c.mode == EmptyFields && v.CreatePipeline.Schema == "" && Forced(v.CreatePipeline.ForceSendFields, "Schema") { + ch := n.Child("schema") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.Schema); c.match(s) { + ch := n.Child("schema") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.Schema = (r) + } + } + if c.mode == EmptyFields && v.CreatePipeline.ServerlessComputeId == "" && Forced(v.CreatePipeline.ForceSendFields, "ServerlessComputeId") { + ch := n.Child("serverless_compute_id") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.ServerlessComputeId); c.match(s) { + ch := n.Child("serverless_compute_id") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.ServerlessComputeId = (r) + } + } + if c.mode == EmptyFields && v.CreatePipeline.Storage == "" && Forced(v.CreatePipeline.ForceSendFields, "Storage") { + ch := n.Child("storage") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.Storage); c.match(s) { + ch := n.Child("storage") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.Storage = (r) + } + } + if !n.IsRef("tags") { + if len(v.CreatePipeline.Tags) > 0 { + cn := n.Child("tags") + for _, k := range sortedKeys(v.CreatePipeline.Tags) { + if s := string(v.CreatePipeline.Tags[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.Tags[k] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.CreatePipeline.Target == "" && Forced(v.CreatePipeline.ForceSendFields, "Target") { + ch := n.Child("target") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.Target); c.match(s) { + ch := n.Child("target") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.Target = (r) + } + } + if !n.IsRef("trigger") { + if x := v.CreatePipeline.Trigger; x != nil { + ch := n.Child("trigger") + stringsPipelinesPipelineTrigger(c, &ch, x) + } + } + if c.mode == EmptyFields && v.CreatePipeline.UsagePolicyId == "" && Forced(v.CreatePipeline.ForceSendFields, "UsagePolicyId") { + ch := n.Child("usage_policy_id") + c.fn(&ch, "") + } + if s := string(v.CreatePipeline.UsagePolicyId); c.match(s) { + ch := n.Child("usage_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.CreatePipeline.UsagePolicyId = (r) + } + } +} + +func stringsResourcesFeature(c *stringsWalk, n *Node, v *resources.Feature) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.Feature.CatalogName == "" && Forced(v.Feature.ForceSendFields, "CatalogName") { + ch := n.Child("catalog_name") + c.fn(&ch, "") + } + if s := string(v.Feature.CatalogName); c.match(s) { + ch := n.Child("catalog_name") + if r, ok := c.fn(&ch, s); ok { + v.Feature.CatalogName = (r) + } + } + if c.mode == EmptyFields && v.Feature.CreatedBy == "" && Forced(v.Feature.ForceSendFields, "CreatedBy") { + ch := n.Child("created_by") + c.fn(&ch, "") + } + if s := string(v.Feature.CreatedBy); c.match(s) { + ch := n.Child("created_by") + if r, ok := c.fn(&ch, s); ok { + v.Feature.CreatedBy = (r) + } + } + if c.mode == EmptyFields && v.Feature.Description == "" && Forced(v.Feature.ForceSendFields, "Description") { + ch := n.Child("description") + c.fn(&ch, "") + } + if s := string(v.Feature.Description); c.match(s) { + ch := n.Child("description") + if r, ok := c.fn(&ch, s); ok { + v.Feature.Description = (r) + } + } + if !n.IsRef("entities") { + if len(v.Feature.Entities) > 0 { + cn := n.Child("entities") + for i := range v.Feature.Entities { + { + ch := cn.Elem(i) + stringsMlEntityColumn(c, &ch, &v.Feature.Entities[i]) + } + } + } + } + if c.mode == EmptyFields && v.Feature.FilterCondition == "" && Forced(v.Feature.ForceSendFields, "FilterCondition") { + ch := n.Child("filter_condition") + c.fn(&ch, "") + } + if s := string(v.Feature.FilterCondition); c.match(s) { + ch := n.Child("filter_condition") + if r, ok := c.fn(&ch, s); ok { + v.Feature.FilterCondition = (r) + } + } + if s := string(v.Feature.FullName); c.match(s) { + ch := n.Child("full_name") + if r, ok := c.fn(&ch, s); ok { + v.Feature.FullName = (r) + } + } + if !n.IsRef("function") { + { + ch := n.Child("function") + stringsMlFunction(c, &ch, &v.Feature.Function) + } + } + if !n.IsRef("inputs") { + if len(v.Feature.Inputs) > 0 { + cn := n.Child("inputs") + for i := range v.Feature.Inputs { + if s := string(v.Feature.Inputs[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Feature.Inputs[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.Feature.Name == "" && Forced(v.Feature.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Feature.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Feature.Name = (r) + } + } + if c.mode == EmptyFields && v.Feature.SchemaName == "" && Forced(v.Feature.ForceSendFields, "SchemaName") { + ch := n.Child("schema_name") + c.fn(&ch, "") + } + if s := string(v.Feature.SchemaName); c.match(s) { + ch := n.Child("schema_name") + if r, ok := c.fn(&ch, s); ok { + v.Feature.SchemaName = (r) + } + } + if !n.IsRef("source") { + { + ch := n.Child("source") + stringsMlDataSource(c, &ch, &v.Feature.Source) + } + } + if !n.IsRef("time_window") { + if x := v.Feature.TimeWindow; x != nil { + ch := n.Child("time_window") + stringsMlTimeWindow(c, &ch, x) + } + } + if !n.IsRef("timeseries_column") { + if x := v.Feature.TimeseriesColumn; x != nil { + ch := n.Child("timeseries_column") + stringsMlTimeseriesColumn(c, &ch, x) + } + } +} + +func stringsResourcesMlflowModel(c *stringsWalk, n *Node, v *resources.MlflowModel) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesMlflowModelPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.CreateModelRequest.Description == "" && Forced(v.CreateModelRequest.ForceSendFields, "Description") { + ch := n.Child("description") + c.fn(&ch, "") + } + if s := string(v.CreateModelRequest.Description); c.match(s) { + ch := n.Child("description") + if r, ok := c.fn(&ch, s); ok { + v.CreateModelRequest.Description = (r) + } + } + if s := string(v.CreateModelRequest.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreateModelRequest.Name = (r) + } + } + if !n.IsRef("tags") { + if len(v.CreateModelRequest.Tags) > 0 { + cn := n.Child("tags") + for i := range v.CreateModelRequest.Tags { + { + ch := cn.Elem(i) + stringsMlModelTag(c, &ch, &v.CreateModelRequest.Tags[i]) + } + } + } + } +} + +func stringsResourcesMlflowExperiment(c *stringsWalk, n *Node, v *resources.MlflowExperiment) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesMlflowExperimentPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.CreateExperiment.ArtifactLocation == "" && Forced(v.CreateExperiment.ForceSendFields, "ArtifactLocation") { + ch := n.Child("artifact_location") + c.fn(&ch, "") + } + if s := string(v.CreateExperiment.ArtifactLocation); c.match(s) { + ch := n.Child("artifact_location") + if r, ok := c.fn(&ch, s); ok { + v.CreateExperiment.ArtifactLocation = (r) + } + } + if s := string(v.CreateExperiment.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreateExperiment.Name = (r) + } + } + if !n.IsRef("tags") { + if len(v.CreateExperiment.Tags) > 0 { + cn := n.Child("tags") + for i := range v.CreateExperiment.Tags { + { + ch := cn.Elem(i) + stringsMlExperimentTag(c, &ch, &v.CreateExperiment.Tags[i]) + } + } + } + } + if !n.IsRef("trace_location") { + if x := v.CreateExperiment.TraceLocation; x != nil { + ch := n.Child("trace_location") + stringsMlExperimentTraceLocation(c, &ch, x) + } + } +} + +func stringsResourcesModelServingEndpoint(c *stringsWalk, n *Node, v *resources.ModelServingEndpoint) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesModelServingEndpointPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if !n.IsRef("ai_gateway") { + if x := v.CreateServingEndpoint.AiGateway; x != nil { + ch := n.Child("ai_gateway") + stringsServingAiGatewayConfig(c, &ch, x) + } + } + if c.mode == EmptyFields && v.CreateServingEndpoint.BudgetPolicyId == "" && Forced(v.CreateServingEndpoint.ForceSendFields, "BudgetPolicyId") { + ch := n.Child("budget_policy_id") + c.fn(&ch, "") + } + if s := string(v.CreateServingEndpoint.BudgetPolicyId); c.match(s) { + ch := n.Child("budget_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.CreateServingEndpoint.BudgetPolicyId = (r) + } + } + if !n.IsRef("config") { + if x := v.CreateServingEndpoint.Config; x != nil { + ch := n.Child("config") + stringsServingEndpointCoreConfigInput(c, &ch, x) + } + } + if c.mode == EmptyFields && v.CreateServingEndpoint.Description == "" && Forced(v.CreateServingEndpoint.ForceSendFields, "Description") { + ch := n.Child("description") + c.fn(&ch, "") + } + if s := string(v.CreateServingEndpoint.Description); c.match(s) { + ch := n.Child("description") + if r, ok := c.fn(&ch, s); ok { + v.CreateServingEndpoint.Description = (r) + } + } + if !n.IsRef("email_notifications") { + if x := v.CreateServingEndpoint.EmailNotifications; x != nil { + ch := n.Child("email_notifications") + stringsServingEmailNotifications(c, &ch, x) + } + } + if s := string(v.CreateServingEndpoint.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreateServingEndpoint.Name = (r) + } + } + if !n.IsRef("rate_limits") { + if len(v.CreateServingEndpoint.RateLimits) > 0 { + cn := n.Child("rate_limits") + for i := range v.CreateServingEndpoint.RateLimits { + { + ch := cn.Elem(i) + stringsServingRateLimit(c, &ch, &v.CreateServingEndpoint.RateLimits[i]) + } + } + } + } + if !n.IsRef("tags") { + if len(v.CreateServingEndpoint.Tags) > 0 { + cn := n.Child("tags") + for i := range v.CreateServingEndpoint.Tags { + { + ch := cn.Elem(i) + stringsServingEndpointTag(c, &ch, &v.CreateServingEndpoint.Tags[i]) + } + } + } + } + if !n.IsRef("telemetry_config") { + if x := v.CreateServingEndpoint.TelemetryConfig; x != nil { + ch := n.Child("telemetry_config") + stringsServingTelemetryConfig(c, &ch, x) + } + } +} + +func stringsResourcesModelService(c *stringsWalk, n *Node, v *resources.ModelService) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("grants") { + if len(v.Grants) > 0 { + cn := n.Child("grants") + for i := range v.Grants { + { + ch := cn.Elem(i) + stringsCatalogPrivilegeAssignment(c, &ch, &v.Grants[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.ModelServiceConfig.Parent); c.match(s) { + ch := n.Child("parent") + if r, ok := c.fn(&ch, s); ok { + v.ModelServiceConfig.Parent = (r) + } + } + if s := string(v.ModelServiceConfig.ModelServiceId); c.match(s) { + ch := n.Child("model_service_id") + if r, ok := c.fn(&ch, s); ok { + v.ModelServiceConfig.ModelServiceId = (r) + } + } + if c.mode == EmptyFields && v.ModelServiceConfig.Comment == "" && Forced(v.ModelServiceConfig.ForceSendFields, "Comment") { + ch := n.Child("comment") + c.fn(&ch, "") + } + if s := string(v.ModelServiceConfig.Comment); c.match(s) { + ch := n.Child("comment") + if r, ok := c.fn(&ch, s); ok { + v.ModelServiceConfig.Comment = (r) + } + } + if !n.IsRef("config") { + if x := v.ModelServiceConfig.Config; x != nil { + ch := n.Child("config") + stringsCatalogModelServiceConfig(c, &ch, x) + } + } +} + +func stringsResourcesMcpService(c *stringsWalk, n *Node, v *resources.McpService) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("grants") { + if len(v.Grants) > 0 { + cn := n.Child("grants") + for i := range v.Grants { + { + ch := cn.Elem(i) + stringsCatalogPrivilegeAssignment(c, &ch, &v.Grants[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.McpServiceConfig.Parent); c.match(s) { + ch := n.Child("parent") + if r, ok := c.fn(&ch, s); ok { + v.McpServiceConfig.Parent = (r) + } + } + if s := string(v.McpServiceConfig.McpServiceId); c.match(s) { + ch := n.Child("mcp_service_id") + if r, ok := c.fn(&ch, s); ok { + v.McpServiceConfig.McpServiceId = (r) + } + } + if c.mode == EmptyFields && v.McpServiceConfig.Comment == "" && Forced(v.McpServiceConfig.ForceSendFields, "Comment") { + ch := n.Child("comment") + c.fn(&ch, "") + } + if s := string(v.McpServiceConfig.Comment); c.match(s) { + ch := n.Child("comment") + if r, ok := c.fn(&ch, s); ok { + v.McpServiceConfig.Comment = (r) + } + } + if !n.IsRef("config") { + if x := v.McpServiceConfig.Config; x != nil { + ch := n.Child("config") + stringsCatalogMcpServiceConfig(c, &ch, x) + } + } +} + +func stringsResourcesModelProviderService(c *stringsWalk, n *Node, v *resources.ModelProviderService) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("grants") { + if len(v.Grants) > 0 { + cn := n.Child("grants") + for i := range v.Grants { + { + ch := cn.Elem(i) + stringsCatalogPrivilegeAssignment(c, &ch, &v.Grants[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.ModelProviderServiceConfig.Parent); c.match(s) { + ch := n.Child("parent") + if r, ok := c.fn(&ch, s); ok { + v.ModelProviderServiceConfig.Parent = (r) + } + } + if s := string(v.ModelProviderServiceConfig.ModelProviderServiceId); c.match(s) { + ch := n.Child("model_provider_service_id") + if r, ok := c.fn(&ch, s); ok { + v.ModelProviderServiceConfig.ModelProviderServiceId = (r) + } + } + if c.mode == EmptyFields && v.ModelProviderServiceConfig.Comment == "" && Forced(v.ModelProviderServiceConfig.ForceSendFields, "Comment") { + ch := n.Child("comment") + c.fn(&ch, "") + } + if s := string(v.ModelProviderServiceConfig.Comment); c.match(s) { + ch := n.Child("comment") + if r, ok := c.fn(&ch, s); ok { + v.ModelProviderServiceConfig.Comment = (r) + } + } + if !n.IsRef("config") { + if x := v.ModelProviderServiceConfig.Config; x != nil { + ch := n.Child("config") + stringsCatalogModelProviderServiceConfig(c, &ch, x) + } + } +} + +func stringsResourcesRegisteredModel(c *stringsWalk, n *Node, v *resources.RegisteredModel) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("grants") { + if len(v.Grants) > 0 { + cn := n.Child("grants") + for i := range v.Grants { + { + ch := cn.Elem(i) + stringsCatalogPrivilegeAssignment(c, &ch, &v.Grants[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if !n.IsRef("aliases") { + if len(v.CreateRegisteredModelRequest.Aliases) > 0 { + cn := n.Child("aliases") + for i := range v.CreateRegisteredModelRequest.Aliases { + { + ch := cn.Elem(i) + stringsCatalogRegisteredModelAlias(c, &ch, &v.CreateRegisteredModelRequest.Aliases[i]) + } + } + } + } + if c.mode == EmptyFields && v.CreateRegisteredModelRequest.CatalogName == "" && Forced(v.CreateRegisteredModelRequest.ForceSendFields, "CatalogName") { + ch := n.Child("catalog_name") + c.fn(&ch, "") + } + if s := string(v.CreateRegisteredModelRequest.CatalogName); c.match(s) { + ch := n.Child("catalog_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateRegisteredModelRequest.CatalogName = (r) + } + } + if c.mode == EmptyFields && v.CreateRegisteredModelRequest.Comment == "" && Forced(v.CreateRegisteredModelRequest.ForceSendFields, "Comment") { + ch := n.Child("comment") + c.fn(&ch, "") + } + if s := string(v.CreateRegisteredModelRequest.Comment); c.match(s) { + ch := n.Child("comment") + if r, ok := c.fn(&ch, s); ok { + v.CreateRegisteredModelRequest.Comment = (r) + } + } + if c.mode == EmptyFields && v.CreateRegisteredModelRequest.CreatedBy == "" && Forced(v.CreateRegisteredModelRequest.ForceSendFields, "CreatedBy") { + ch := n.Child("created_by") + c.fn(&ch, "") + } + if s := string(v.CreateRegisteredModelRequest.CreatedBy); c.match(s) { + ch := n.Child("created_by") + if r, ok := c.fn(&ch, s); ok { + v.CreateRegisteredModelRequest.CreatedBy = (r) + } + } + if c.mode == EmptyFields && v.CreateRegisteredModelRequest.FullName == "" && Forced(v.CreateRegisteredModelRequest.ForceSendFields, "FullName") { + ch := n.Child("full_name") + c.fn(&ch, "") + } + if s := string(v.CreateRegisteredModelRequest.FullName); c.match(s) { + ch := n.Child("full_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateRegisteredModelRequest.FullName = (r) + } + } + if c.mode == EmptyFields && v.CreateRegisteredModelRequest.MetastoreId == "" && Forced(v.CreateRegisteredModelRequest.ForceSendFields, "MetastoreId") { + ch := n.Child("metastore_id") + c.fn(&ch, "") + } + if s := string(v.CreateRegisteredModelRequest.MetastoreId); c.match(s) { + ch := n.Child("metastore_id") + if r, ok := c.fn(&ch, s); ok { + v.CreateRegisteredModelRequest.MetastoreId = (r) + } + } + if c.mode == EmptyFields && v.CreateRegisteredModelRequest.Name == "" && Forced(v.CreateRegisteredModelRequest.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.CreateRegisteredModelRequest.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreateRegisteredModelRequest.Name = (r) + } + } + if c.mode == EmptyFields && v.CreateRegisteredModelRequest.Owner == "" && Forced(v.CreateRegisteredModelRequest.ForceSendFields, "Owner") { + ch := n.Child("owner") + c.fn(&ch, "") + } + if s := string(v.CreateRegisteredModelRequest.Owner); c.match(s) { + ch := n.Child("owner") + if r, ok := c.fn(&ch, s); ok { + v.CreateRegisteredModelRequest.Owner = (r) + } + } + if c.mode == EmptyFields && v.CreateRegisteredModelRequest.SchemaName == "" && Forced(v.CreateRegisteredModelRequest.ForceSendFields, "SchemaName") { + ch := n.Child("schema_name") + c.fn(&ch, "") + } + if s := string(v.CreateRegisteredModelRequest.SchemaName); c.match(s) { + ch := n.Child("schema_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateRegisteredModelRequest.SchemaName = (r) + } + } + if c.mode == EmptyFields && v.CreateRegisteredModelRequest.StorageLocation == "" && Forced(v.CreateRegisteredModelRequest.ForceSendFields, "StorageLocation") { + ch := n.Child("storage_location") + c.fn(&ch, "") + } + if s := string(v.CreateRegisteredModelRequest.StorageLocation); c.match(s) { + ch := n.Child("storage_location") + if r, ok := c.fn(&ch, s); ok { + v.CreateRegisteredModelRequest.StorageLocation = (r) + } + } + if c.mode == EmptyFields && v.CreateRegisteredModelRequest.UpdatedBy == "" && Forced(v.CreateRegisteredModelRequest.ForceSendFields, "UpdatedBy") { + ch := n.Child("updated_by") + c.fn(&ch, "") + } + if s := string(v.CreateRegisteredModelRequest.UpdatedBy); c.match(s) { + ch := n.Child("updated_by") + if r, ok := c.fn(&ch, s); ok { + v.CreateRegisteredModelRequest.UpdatedBy = (r) + } + } +} + +func stringsResourcesQualityMonitor(c *stringsWalk, n *Node, v *resources.QualityMonitor) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.TableName); c.match(s) { + ch := n.Child("table_name") + if r, ok := c.fn(&ch, s); ok { + v.TableName = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.CreateMonitor.AssetsDir); c.match(s) { + ch := n.Child("assets_dir") + if r, ok := c.fn(&ch, s); ok { + v.CreateMonitor.AssetsDir = (r) + } + } + if c.mode == EmptyFields && v.CreateMonitor.BaselineTableName == "" && Forced(v.CreateMonitor.ForceSendFields, "BaselineTableName") { + ch := n.Child("baseline_table_name") + c.fn(&ch, "") + } + if s := string(v.CreateMonitor.BaselineTableName); c.match(s) { + ch := n.Child("baseline_table_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateMonitor.BaselineTableName = (r) + } + } + if !n.IsRef("custom_metrics") { + if len(v.CreateMonitor.CustomMetrics) > 0 { + cn := n.Child("custom_metrics") + for i := range v.CreateMonitor.CustomMetrics { + { + ch := cn.Elem(i) + stringsCatalogMonitorMetric(c, &ch, &v.CreateMonitor.CustomMetrics[i]) + } + } + } + } + if !n.IsRef("inference_log") { + if x := v.CreateMonitor.InferenceLog; x != nil { + ch := n.Child("inference_log") + stringsCatalogMonitorInferenceLog(c, &ch, x) + } + } + if c.mode == EmptyFields && v.CreateMonitor.LatestMonitorFailureMsg == "" && Forced(v.CreateMonitor.ForceSendFields, "LatestMonitorFailureMsg") { + ch := n.Child("latest_monitor_failure_msg") + c.fn(&ch, "") + } + if s := string(v.CreateMonitor.LatestMonitorFailureMsg); c.match(s) { + ch := n.Child("latest_monitor_failure_msg") + if r, ok := c.fn(&ch, s); ok { + v.CreateMonitor.LatestMonitorFailureMsg = (r) + } + } + if !n.IsRef("notifications") { + if x := v.CreateMonitor.Notifications; x != nil { + ch := n.Child("notifications") + stringsCatalogMonitorNotifications(c, &ch, x) + } + } + if s := string(v.CreateMonitor.OutputSchemaName); c.match(s) { + ch := n.Child("output_schema_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateMonitor.OutputSchemaName = (r) + } + } + if !n.IsRef("schedule") { + if x := v.CreateMonitor.Schedule; x != nil { + ch := n.Child("schedule") + stringsCatalogMonitorCronSchedule(c, &ch, x) + } + } + if !n.IsRef("slicing_exprs") { + if len(v.CreateMonitor.SlicingExprs) > 0 { + cn := n.Child("slicing_exprs") + for i := range v.CreateMonitor.SlicingExprs { + if s := string(v.CreateMonitor.SlicingExprs[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.CreateMonitor.SlicingExprs[i] = (r) + } + } + } + } + } + if !n.IsRef("time_series") { + if x := v.CreateMonitor.TimeSeries; x != nil { + ch := n.Child("time_series") + stringsCatalogMonitorTimeSeries(c, &ch, x) + } + } + if c.mode == EmptyFields && v.CreateMonitor.WarehouseId == "" && Forced(v.CreateMonitor.ForceSendFields, "WarehouseId") { + ch := n.Child("warehouse_id") + c.fn(&ch, "") + } + if s := string(v.CreateMonitor.WarehouseId); c.match(s) { + ch := n.Child("warehouse_id") + if r, ok := c.fn(&ch, s); ok { + v.CreateMonitor.WarehouseId = (r) + } + } +} + +func stringsResourcesCatalog(c *stringsWalk, n *Node, v *resources.Catalog) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("grants") { + if len(v.Grants) > 0 { + cn := n.Child("grants") + for i := range v.Grants { + { + ch := cn.Elem(i) + stringsCatalogPrivilegeAssignment(c, &ch, &v.Grants[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.CreateCatalog.Comment == "" && Forced(v.CreateCatalog.ForceSendFields, "Comment") { + ch := n.Child("comment") + c.fn(&ch, "") + } + if s := string(v.CreateCatalog.Comment); c.match(s) { + ch := n.Child("comment") + if r, ok := c.fn(&ch, s); ok { + v.CreateCatalog.Comment = (r) + } + } + if c.mode == EmptyFields && v.CreateCatalog.ConnectionName == "" && Forced(v.CreateCatalog.ForceSendFields, "ConnectionName") { + ch := n.Child("connection_name") + c.fn(&ch, "") + } + if s := string(v.CreateCatalog.ConnectionName); c.match(s) { + ch := n.Child("connection_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateCatalog.ConnectionName = (r) + } + } + if !n.IsRef("managed_encryption_settings") { + if x := v.CreateCatalog.ManagedEncryptionSettings; x != nil { + ch := n.Child("managed_encryption_settings") + stringsCatalogEncryptionSettings(c, &ch, x) + } + } + if s := string(v.CreateCatalog.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreateCatalog.Name = (r) + } + } + if !n.IsRef("options") { + if len(v.CreateCatalog.Options) > 0 { + cn := n.Child("options") + for _, k := range sortedKeys(v.CreateCatalog.Options) { + if s := string(v.CreateCatalog.Options[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.CreateCatalog.Options[k] = (r) + } + } + } + } + } + if !n.IsRef("properties") { + if len(v.CreateCatalog.Properties) > 0 { + cn := n.Child("properties") + for _, k := range sortedKeys(v.CreateCatalog.Properties) { + if s := string(v.CreateCatalog.Properties[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.CreateCatalog.Properties[k] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.CreateCatalog.ProviderName == "" && Forced(v.CreateCatalog.ForceSendFields, "ProviderName") { + ch := n.Child("provider_name") + c.fn(&ch, "") + } + if s := string(v.CreateCatalog.ProviderName); c.match(s) { + ch := n.Child("provider_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateCatalog.ProviderName = (r) + } + } + if c.mode == EmptyFields && v.CreateCatalog.ShareName == "" && Forced(v.CreateCatalog.ForceSendFields, "ShareName") { + ch := n.Child("share_name") + c.fn(&ch, "") + } + if s := string(v.CreateCatalog.ShareName); c.match(s) { + ch := n.Child("share_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateCatalog.ShareName = (r) + } + } + if c.mode == EmptyFields && v.CreateCatalog.StorageRoot == "" && Forced(v.CreateCatalog.ForceSendFields, "StorageRoot") { + ch := n.Child("storage_root") + c.fn(&ch, "") + } + if s := string(v.CreateCatalog.StorageRoot); c.match(s) { + ch := n.Child("storage_root") + if r, ok := c.fn(&ch, s); ok { + v.CreateCatalog.StorageRoot = (r) + } + } +} + +func stringsResourcesSchema(c *stringsWalk, n *Node, v *resources.Schema) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("grants") { + if len(v.Grants) > 0 { + cn := n.Child("grants") + for i := range v.Grants { + { + ch := cn.Elem(i) + stringsCatalogPrivilegeAssignment(c, &ch, &v.Grants[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.CreateSchema.CatalogName); c.match(s) { + ch := n.Child("catalog_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateSchema.CatalogName = (r) + } + } + if c.mode == EmptyFields && v.CreateSchema.Comment == "" && Forced(v.CreateSchema.ForceSendFields, "Comment") { + ch := n.Child("comment") + c.fn(&ch, "") + } + if s := string(v.CreateSchema.Comment); c.match(s) { + ch := n.Child("comment") + if r, ok := c.fn(&ch, s); ok { + v.CreateSchema.Comment = (r) + } + } + if s := string(v.CreateSchema.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreateSchema.Name = (r) + } + } + if !n.IsRef("properties") { + if len(v.CreateSchema.Properties) > 0 { + cn := n.Child("properties") + for _, k := range sortedKeys(v.CreateSchema.Properties) { + if s := string(v.CreateSchema.Properties[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.CreateSchema.Properties[k] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.CreateSchema.StorageRoot == "" && Forced(v.CreateSchema.ForceSendFields, "StorageRoot") { + ch := n.Child("storage_root") + c.fn(&ch, "") + } + if s := string(v.CreateSchema.StorageRoot); c.match(s) { + ch := n.Child("storage_root") + if r, ok := c.fn(&ch, s); ok { + v.CreateSchema.StorageRoot = (r) + } + } +} + +func stringsResourcesVolume(c *stringsWalk, n *Node, v *resources.Volume) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.VolumePath); c.match(s) { + ch := n.Child("volume_path") + if r, ok := c.fn(&ch, s); ok { + v.VolumePath = (r) + } + } + if !n.IsRef("grants") { + if len(v.Grants) > 0 { + cn := n.Child("grants") + for i := range v.Grants { + { + ch := cn.Elem(i) + stringsCatalogPrivilegeAssignment(c, &ch, &v.Grants[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.CreateVolumeRequestContent.CatalogName); c.match(s) { + ch := n.Child("catalog_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateVolumeRequestContent.CatalogName = (r) + } + } + if c.mode == EmptyFields && v.CreateVolumeRequestContent.Comment == "" && Forced(v.CreateVolumeRequestContent.ForceSendFields, "Comment") { + ch := n.Child("comment") + c.fn(&ch, "") + } + if s := string(v.CreateVolumeRequestContent.Comment); c.match(s) { + ch := n.Child("comment") + if r, ok := c.fn(&ch, s); ok { + v.CreateVolumeRequestContent.Comment = (r) + } + } + if s := string(v.CreateVolumeRequestContent.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreateVolumeRequestContent.Name = (r) + } + } + if s := string(v.CreateVolumeRequestContent.SchemaName); c.match(s) { + ch := n.Child("schema_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateVolumeRequestContent.SchemaName = (r) + } + } + if c.mode == EmptyFields && v.CreateVolumeRequestContent.StorageLocation == "" && Forced(v.CreateVolumeRequestContent.ForceSendFields, "StorageLocation") { + ch := n.Child("storage_location") + c.fn(&ch, "") + } + if s := string(v.CreateVolumeRequestContent.StorageLocation); c.match(s) { + ch := n.Child("storage_location") + if r, ok := c.fn(&ch, s); ok { + v.CreateVolumeRequestContent.StorageLocation = (r) + } + } + if s := string(v.CreateVolumeRequestContent.VolumeType); c.match(s) { + ch := n.Child("volume_type") + if r, ok := c.fn(&ch, s); ok { + v.CreateVolumeRequestContent.VolumeType = catalog.VolumeType(r) + } + } +} + +func stringsResourcesExternalLocation(c *stringsWalk, n *Node, v *resources.ExternalLocation) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.ModifiedStatus = (r) + } + } + if !n.IsRef("grants") { + if len(v.Grants) > 0 { + cn := n.Child("grants") + for i := range v.Grants { + { + ch := cn.Elem(i) + stringsCatalogPrivilegeAssignment(c, &ch, &v.Grants[i]) + } + } + } + } + if c.mode == EmptyFields && v.CreateExternalLocation.Comment == "" && Forced(v.CreateExternalLocation.ForceSendFields, "Comment") { + ch := n.Child("comment") + c.fn(&ch, "") + } + if s := string(v.CreateExternalLocation.Comment); c.match(s) { + ch := n.Child("comment") + if r, ok := c.fn(&ch, s); ok { + v.CreateExternalLocation.Comment = (r) + } + } + if s := string(v.CreateExternalLocation.CredentialName); c.match(s) { + ch := n.Child("credential_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateExternalLocation.CredentialName = (r) + } + } + if !n.IsRef("effective_file_event_queue") { + if x := v.CreateExternalLocation.EffectiveFileEventQueue; x != nil { + ch := n.Child("effective_file_event_queue") + stringsCatalogFileEventQueue(c, &ch, x) + } + } + if !n.IsRef("encryption_details") { + if x := v.CreateExternalLocation.EncryptionDetails; x != nil { + ch := n.Child("encryption_details") + stringsCatalogEncryptionDetails(c, &ch, x) + } + } + if !n.IsRef("file_event_queue") { + if x := v.CreateExternalLocation.FileEventQueue; x != nil { + ch := n.Child("file_event_queue") + stringsCatalogFileEventQueue(c, &ch, x) + } + } + if s := string(v.CreateExternalLocation.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreateExternalLocation.Name = (r) + } + } + if s := string(v.CreateExternalLocation.Url); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.CreateExternalLocation.Url = (r) + } + } +} + +func stringsResourcesCluster(c *stringsWalk, n *Node, v *resources.Cluster) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("libraries") { + if len(v.Libraries) > 0 { + cn := n.Child("libraries") + for i := range v.Libraries { + { + ch := cn.Elem(i) + stringsComputeLibrary(c, &ch, &v.Libraries[i]) + } + } + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesClusterPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if !n.IsRef("aws_attributes") { + if x := v.ClusterSpec.AwsAttributes; x != nil { + ch := n.Child("aws_attributes") + stringsComputeAwsAttributes(c, &ch, x) + } + } + if !n.IsRef("azure_attributes") { + if x := v.ClusterSpec.AzureAttributes; x != nil { + ch := n.Child("azure_attributes") + stringsComputeAzureAttributes(c, &ch, x) + } + } + if !n.IsRef("cluster_log_conf") { + if x := v.ClusterSpec.ClusterLogConf; x != nil { + ch := n.Child("cluster_log_conf") + stringsComputeClusterLogConf(c, &ch, x) + } + } + if c.mode == EmptyFields && v.ClusterSpec.ClusterName == "" && Forced(v.ClusterSpec.ForceSendFields, "ClusterName") { + ch := n.Child("cluster_name") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.ClusterName); c.match(s) { + ch := n.Child("cluster_name") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.ClusterName = (r) + } + } + if !n.IsRef("custom_tags") { + if len(v.ClusterSpec.CustomTags) > 0 { + cn := n.Child("custom_tags") + for _, k := range sortedKeys(v.ClusterSpec.CustomTags) { + if s := string(v.ClusterSpec.CustomTags[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.CustomTags[k] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.ClusterSpec.DataSecurityMode == "" && Forced(v.ClusterSpec.ForceSendFields, "DataSecurityMode") { + ch := n.Child("data_security_mode") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.DataSecurityMode); c.match(s) { + ch := n.Child("data_security_mode") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.DataSecurityMode = compute.DataSecurityMode(r) + } + } + if c.mode == EmptyFields && v.ClusterSpec.DependencyMode == "" && Forced(v.ClusterSpec.ForceSendFields, "DependencyMode") { + ch := n.Child("dependency_mode") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.DependencyMode); c.match(s) { + ch := n.Child("dependency_mode") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.DependencyMode = compute.DependencyMode(r) + } + } + if !n.IsRef("docker_image") { + if x := v.ClusterSpec.DockerImage; x != nil { + ch := n.Child("docker_image") + stringsComputeDockerImage(c, &ch, x) + } + } + if c.mode == EmptyFields && v.ClusterSpec.DriverInstancePoolId == "" && Forced(v.ClusterSpec.ForceSendFields, "DriverInstancePoolId") { + ch := n.Child("driver_instance_pool_id") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.DriverInstancePoolId); c.match(s) { + ch := n.Child("driver_instance_pool_id") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.DriverInstancePoolId = (r) + } + } + if !n.IsRef("driver_node_type_flexibility") { + if x := v.ClusterSpec.DriverNodeTypeFlexibility; x != nil { + ch := n.Child("driver_node_type_flexibility") + stringsComputeNodeTypeFlexibility(c, &ch, x) + } + } + if c.mode == EmptyFields && v.ClusterSpec.DriverNodeTypeId == "" && Forced(v.ClusterSpec.ForceSendFields, "DriverNodeTypeId") { + ch := n.Child("driver_node_type_id") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.DriverNodeTypeId); c.match(s) { + ch := n.Child("driver_node_type_id") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.DriverNodeTypeId = (r) + } + } + if !n.IsRef("gcp_attributes") { + if x := v.ClusterSpec.GcpAttributes; x != nil { + ch := n.Child("gcp_attributes") + stringsComputeGcpAttributes(c, &ch, x) + } + } + if !n.IsRef("init_scripts") { + if len(v.ClusterSpec.InitScripts) > 0 { + cn := n.Child("init_scripts") + for i := range v.ClusterSpec.InitScripts { + { + ch := cn.Elem(i) + stringsComputeInitScriptInfo(c, &ch, &v.ClusterSpec.InitScripts[i]) + } + } + } + } + if c.mode == EmptyFields && v.ClusterSpec.InstancePoolId == "" && Forced(v.ClusterSpec.ForceSendFields, "InstancePoolId") { + ch := n.Child("instance_pool_id") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.InstancePoolId); c.match(s) { + ch := n.Child("instance_pool_id") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.InstancePoolId = (r) + } + } + if c.mode == EmptyFields && v.ClusterSpec.Kind == "" && Forced(v.ClusterSpec.ForceSendFields, "Kind") { + ch := n.Child("kind") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.Kind); c.match(s) { + ch := n.Child("kind") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.Kind = compute.Kind(r) + } + } + if c.mode == EmptyFields && v.ClusterSpec.NodeTypeId == "" && Forced(v.ClusterSpec.ForceSendFields, "NodeTypeId") { + ch := n.Child("node_type_id") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.NodeTypeId); c.match(s) { + ch := n.Child("node_type_id") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.NodeTypeId = (r) + } + } + if c.mode == EmptyFields && v.ClusterSpec.PolicyId == "" && Forced(v.ClusterSpec.ForceSendFields, "PolicyId") { + ch := n.Child("policy_id") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.PolicyId); c.match(s) { + ch := n.Child("policy_id") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.PolicyId = (r) + } + } + if c.mode == EmptyFields && v.ClusterSpec.RuntimeEngine == "" && Forced(v.ClusterSpec.ForceSendFields, "RuntimeEngine") { + ch := n.Child("runtime_engine") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.RuntimeEngine); c.match(s) { + ch := n.Child("runtime_engine") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.RuntimeEngine = compute.RuntimeEngine(r) + } + } + if c.mode == EmptyFields && v.ClusterSpec.SingleUserName == "" && Forced(v.ClusterSpec.ForceSendFields, "SingleUserName") { + ch := n.Child("single_user_name") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.SingleUserName); c.match(s) { + ch := n.Child("single_user_name") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.SingleUserName = (r) + } + } + if !n.IsRef("spark_conf") { + if len(v.ClusterSpec.SparkConf) > 0 { + cn := n.Child("spark_conf") + for _, k := range sortedKeys(v.ClusterSpec.SparkConf) { + if s := string(v.ClusterSpec.SparkConf[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.SparkConf[k] = (r) + } + } + } + } + } + if !n.IsRef("spark_env_vars") { + if len(v.ClusterSpec.SparkEnvVars) > 0 { + cn := n.Child("spark_env_vars") + for _, k := range sortedKeys(v.ClusterSpec.SparkEnvVars) { + if s := string(v.ClusterSpec.SparkEnvVars[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.SparkEnvVars[k] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.ClusterSpec.SparkVersion == "" && Forced(v.ClusterSpec.ForceSendFields, "SparkVersion") { + ch := n.Child("spark_version") + c.fn(&ch, "") + } + if s := string(v.ClusterSpec.SparkVersion); c.match(s) { + ch := n.Child("spark_version") + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.SparkVersion = (r) + } + } + if !n.IsRef("ssh_public_keys") { + if len(v.ClusterSpec.SshPublicKeys) > 0 { + cn := n.Child("ssh_public_keys") + for i := range v.ClusterSpec.SshPublicKeys { + if s := string(v.ClusterSpec.SshPublicKeys[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ClusterSpec.SshPublicKeys[i] = (r) + } + } + } + } + } + if !n.IsRef("worker_node_type_flexibility") { + if x := v.ClusterSpec.WorkerNodeTypeFlexibility; x != nil { + ch := n.Child("worker_node_type_flexibility") + stringsComputeNodeTypeFlexibility(c, &ch, x) + } + } +} + +func stringsResourcesDashboard(c *stringsWalk, n *Node, v *resources.Dashboard) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.FilePath); c.match(s) { + ch := n.Child("file_path") + if r, ok := c.fn(&ch, s); ok { + v.FilePath = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.DashboardConfig.CreateTime == "" && Forced(v.DashboardConfig.ForceSendFields, "CreateTime") { + ch := n.Child("create_time") + c.fn(&ch, "") + } + if s := string(v.DashboardConfig.CreateTime); c.match(s) { + ch := n.Child("create_time") + if r, ok := c.fn(&ch, s); ok { + v.DashboardConfig.CreateTime = (r) + } + } + if c.mode == EmptyFields && v.DashboardConfig.DashboardId == "" && Forced(v.DashboardConfig.ForceSendFields, "DashboardId") { + ch := n.Child("dashboard_id") + c.fn(&ch, "") + } + if s := string(v.DashboardConfig.DashboardId); c.match(s) { + ch := n.Child("dashboard_id") + if r, ok := c.fn(&ch, s); ok { + v.DashboardConfig.DashboardId = (r) + } + } + if c.mode == EmptyFields && v.DashboardConfig.DisplayName == "" && Forced(v.DashboardConfig.ForceSendFields, "DisplayName") { + ch := n.Child("display_name") + c.fn(&ch, "") + } + if s := string(v.DashboardConfig.DisplayName); c.match(s) { + ch := n.Child("display_name") + if r, ok := c.fn(&ch, s); ok { + v.DashboardConfig.DisplayName = (r) + } + } + if c.mode == EmptyFields && v.DashboardConfig.Etag == "" && Forced(v.DashboardConfig.ForceSendFields, "Etag") { + ch := n.Child("etag") + c.fn(&ch, "") + } + if s := string(v.DashboardConfig.Etag); c.match(s) { + ch := n.Child("etag") + if r, ok := c.fn(&ch, s); ok { + v.DashboardConfig.Etag = (r) + } + } + if c.mode == EmptyFields && v.DashboardConfig.LifecycleState == "" && Forced(v.DashboardConfig.ForceSendFields, "LifecycleState") { + ch := n.Child("lifecycle_state") + c.fn(&ch, "") + } + if s := string(v.DashboardConfig.LifecycleState); c.match(s) { + ch := n.Child("lifecycle_state") + if r, ok := c.fn(&ch, s); ok { + v.DashboardConfig.LifecycleState = dashboards.LifecycleState(r) + } + } + if c.mode == EmptyFields && v.DashboardConfig.ParentPath == "" && Forced(v.DashboardConfig.ForceSendFields, "ParentPath") { + ch := n.Child("parent_path") + c.fn(&ch, "") + } + if s := string(v.DashboardConfig.ParentPath); c.match(s) { + ch := n.Child("parent_path") + if r, ok := c.fn(&ch, s); ok { + v.DashboardConfig.ParentPath = (r) + } + } + if c.mode == EmptyFields && v.DashboardConfig.Path == "" && Forced(v.DashboardConfig.ForceSendFields, "Path") { + ch := n.Child("path") + c.fn(&ch, "") + } + if s := string(v.DashboardConfig.Path); c.match(s) { + ch := n.Child("path") + if r, ok := c.fn(&ch, s); ok { + v.DashboardConfig.Path = (r) + } + } + if c.mode == EmptyFields && v.DashboardConfig.UpdateTime == "" && Forced(v.DashboardConfig.ForceSendFields, "UpdateTime") { + ch := n.Child("update_time") + c.fn(&ch, "") + } + if s := string(v.DashboardConfig.UpdateTime); c.match(s) { + ch := n.Child("update_time") + if r, ok := c.fn(&ch, s); ok { + v.DashboardConfig.UpdateTime = (r) + } + } + if c.mode == EmptyFields && v.DashboardConfig.WarehouseId == "" && Forced(v.DashboardConfig.ForceSendFields, "WarehouseId") { + ch := n.Child("warehouse_id") + c.fn(&ch, "") + } + if s := string(v.DashboardConfig.WarehouseId); c.match(s) { + ch := n.Child("warehouse_id") + if r, ok := c.fn(&ch, s); ok { + v.DashboardConfig.WarehouseId = (r) + } + } + if c.mode == EmptyFields && v.DashboardConfig.SerializedDashboard == "" { + ch := n.Child("serialized_dashboard") + c.fn(&ch, "") + } + if v.DashboardConfig.SerializedDashboard != nil { + ch := n.Child("serialized_dashboard") + c.any(&ch, &v.DashboardConfig.SerializedDashboard) + } + if c.mode == EmptyFields && v.DashboardConfig.DatasetCatalog == "" && Forced(v.DashboardConfig.ForceSendFields, "DatasetCatalog") { + ch := n.Child("dataset_catalog") + c.fn(&ch, "") + } + if s := string(v.DashboardConfig.DatasetCatalog); c.match(s) { + ch := n.Child("dataset_catalog") + if r, ok := c.fn(&ch, s); ok { + v.DashboardConfig.DatasetCatalog = (r) + } + } + if c.mode == EmptyFields && v.DashboardConfig.DatasetSchema == "" && Forced(v.DashboardConfig.ForceSendFields, "DatasetSchema") { + ch := n.Child("dataset_schema") + c.fn(&ch, "") + } + if s := string(v.DashboardConfig.DatasetSchema); c.match(s) { + ch := n.Child("dataset_schema") + if r, ok := c.fn(&ch, s); ok { + v.DashboardConfig.DatasetSchema = (r) + } + } +} + +func stringsResourcesGenieSpace(c *stringsWalk, n *Node, v *resources.GenieSpace) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.FilePath); c.match(s) { + ch := n.Child("file_path") + if r, ok := c.fn(&ch, s); ok { + v.FilePath = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.GenieSpaceConfig.Description == "" && Forced(v.GenieSpaceConfig.ForceSendFields, "Description") { + ch := n.Child("description") + c.fn(&ch, "") + } + if s := string(v.GenieSpaceConfig.Description); c.match(s) { + ch := n.Child("description") + if r, ok := c.fn(&ch, s); ok { + v.GenieSpaceConfig.Description = (r) + } + } + if c.mode == EmptyFields && v.GenieSpaceConfig.Etag == "" && Forced(v.GenieSpaceConfig.ForceSendFields, "Etag") { + ch := n.Child("etag") + c.fn(&ch, "") + } + if s := string(v.GenieSpaceConfig.Etag); c.match(s) { + ch := n.Child("etag") + if r, ok := c.fn(&ch, s); ok { + v.GenieSpaceConfig.Etag = (r) + } + } + if c.mode == EmptyFields && v.GenieSpaceConfig.Title == "" && Forced(v.GenieSpaceConfig.ForceSendFields, "Title") { + ch := n.Child("title") + c.fn(&ch, "") + } + if s := string(v.GenieSpaceConfig.Title); c.match(s) { + ch := n.Child("title") + if r, ok := c.fn(&ch, s); ok { + v.GenieSpaceConfig.Title = (r) + } + } + if c.mode == EmptyFields && v.GenieSpaceConfig.WarehouseId == "" && Forced(v.GenieSpaceConfig.ForceSendFields, "WarehouseId") { + ch := n.Child("warehouse_id") + c.fn(&ch, "") + } + if s := string(v.GenieSpaceConfig.WarehouseId); c.match(s) { + ch := n.Child("warehouse_id") + if r, ok := c.fn(&ch, s); ok { + v.GenieSpaceConfig.WarehouseId = (r) + } + } + if c.mode == EmptyFields && v.GenieSpaceConfig.ParentPath == "" && Forced(v.GenieSpaceConfig.ForceSendFields, "ParentPath") { + ch := n.Child("parent_path") + c.fn(&ch, "") + } + if s := string(v.GenieSpaceConfig.ParentPath); c.match(s) { + ch := n.Child("parent_path") + if r, ok := c.fn(&ch, s); ok { + v.GenieSpaceConfig.ParentPath = (r) + } + } + if c.mode == EmptyFields && v.GenieSpaceConfig.SerializedSpace == "" { + ch := n.Child("serialized_space") + c.fn(&ch, "") + } + if v.GenieSpaceConfig.SerializedSpace != nil { + ch := n.Child("serialized_space") + c.any(&ch, &v.GenieSpaceConfig.SerializedSpace) + } +} + +func stringsResourcesApp(c *stringsWalk, n *Node, v *resources.App) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.URL = (r) + } + } + if !n.IsRef("config") { + if x := v.Config; x != nil { + ch := n.Child("config") + stringsResourcesAppConfig(c, &ch, x) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesAppPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if !n.IsRef("active_deployment") { + if x := v.App.ActiveDeployment; x != nil { + ch := n.Child("active_deployment") + stringsAppsAppDeployment(c, &ch, x) + } + } + if !n.IsRef("app_status") { + if x := v.App.AppStatus; x != nil { + ch := n.Child("app_status") + stringsAppsApplicationStatus(c, &ch, x) + } + } + if c.mode == EmptyFields && v.App.BudgetPolicyId == "" && Forced(v.App.ForceSendFields, "BudgetPolicyId") { + ch := n.Child("budget_policy_id") + c.fn(&ch, "") + } + if s := string(v.App.BudgetPolicyId); c.match(s) { + ch := n.Child("budget_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.App.BudgetPolicyId = (r) + } + } + if c.mode == EmptyFields && v.App.ComputeSize == "" && Forced(v.App.ForceSendFields, "ComputeSize") { + ch := n.Child("compute_size") + c.fn(&ch, "") + } + if s := string(v.App.ComputeSize); c.match(s) { + ch := n.Child("compute_size") + if r, ok := c.fn(&ch, s); ok { + v.App.ComputeSize = apps.ComputeSize(r) + } + } + if !n.IsRef("compute_status") { + if x := v.App.ComputeStatus; x != nil { + ch := n.Child("compute_status") + stringsAppsComputeStatus(c, &ch, x) + } + } + if c.mode == EmptyFields && v.App.CreateTime == "" && Forced(v.App.ForceSendFields, "CreateTime") { + ch := n.Child("create_time") + c.fn(&ch, "") + } + if s := string(v.App.CreateTime); c.match(s) { + ch := n.Child("create_time") + if r, ok := c.fn(&ch, s); ok { + v.App.CreateTime = (r) + } + } + if c.mode == EmptyFields && v.App.Creator == "" && Forced(v.App.ForceSendFields, "Creator") { + ch := n.Child("creator") + c.fn(&ch, "") + } + if s := string(v.App.Creator); c.match(s) { + ch := n.Child("creator") + if r, ok := c.fn(&ch, s); ok { + v.App.Creator = (r) + } + } + if !n.IsRef("default_git_source") { + if x := v.App.DefaultGitSource; x != nil { + ch := n.Child("default_git_source") + stringsAppsGitSource(c, &ch, x) + } + } + if c.mode == EmptyFields && v.App.DefaultSourceCodePath == "" && Forced(v.App.ForceSendFields, "DefaultSourceCodePath") { + ch := n.Child("default_source_code_path") + c.fn(&ch, "") + } + if s := string(v.App.DefaultSourceCodePath); c.match(s) { + ch := n.Child("default_source_code_path") + if r, ok := c.fn(&ch, s); ok { + v.App.DefaultSourceCodePath = (r) + } + } + if c.mode == EmptyFields && v.App.Description == "" && Forced(v.App.ForceSendFields, "Description") { + ch := n.Child("description") + c.fn(&ch, "") + } + if s := string(v.App.Description); c.match(s) { + ch := n.Child("description") + if r, ok := c.fn(&ch, s); ok { + v.App.Description = (r) + } + } + if c.mode == EmptyFields && v.App.EffectiveBudgetPolicyId == "" && Forced(v.App.ForceSendFields, "EffectiveBudgetPolicyId") { + ch := n.Child("effective_budget_policy_id") + c.fn(&ch, "") + } + if s := string(v.App.EffectiveBudgetPolicyId); c.match(s) { + ch := n.Child("effective_budget_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.App.EffectiveBudgetPolicyId = (r) + } + } + if c.mode == EmptyFields && v.App.EffectiveUsagePolicyId == "" && Forced(v.App.ForceSendFields, "EffectiveUsagePolicyId") { + ch := n.Child("effective_usage_policy_id") + c.fn(&ch, "") + } + if s := string(v.App.EffectiveUsagePolicyId); c.match(s) { + ch := n.Child("effective_usage_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.App.EffectiveUsagePolicyId = (r) + } + } + if !n.IsRef("effective_user_api_scopes") { + if len(v.App.EffectiveUserApiScopes) > 0 { + cn := n.Child("effective_user_api_scopes") + for i := range v.App.EffectiveUserApiScopes { + if s := string(v.App.EffectiveUserApiScopes[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.App.EffectiveUserApiScopes[i] = (r) + } + } + } + } + } + if !n.IsRef("git_repository") { + if x := v.App.GitRepository; x != nil { + ch := n.Child("git_repository") + stringsAppsGitRepository(c, &ch, x) + } + } + if !n.IsRef("git_source") { + if x := v.App.GitSource; x != nil { + ch := n.Child("git_source") + stringsAppsGitSource(c, &ch, x) + } + } + if s := string(v.App.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.App.Name = (r) + } + } + if c.mode == EmptyFields && v.App.Oauth2AppClientId == "" && Forced(v.App.ForceSendFields, "Oauth2AppClientId") { + ch := n.Child("oauth2_app_client_id") + c.fn(&ch, "") + } + if s := string(v.App.Oauth2AppClientId); c.match(s) { + ch := n.Child("oauth2_app_client_id") + if r, ok := c.fn(&ch, s); ok { + v.App.Oauth2AppClientId = (r) + } + } + if c.mode == EmptyFields && v.App.Oauth2AppIntegrationId == "" && Forced(v.App.ForceSendFields, "Oauth2AppIntegrationId") { + ch := n.Child("oauth2_app_integration_id") + c.fn(&ch, "") + } + if s := string(v.App.Oauth2AppIntegrationId); c.match(s) { + ch := n.Child("oauth2_app_integration_id") + if r, ok := c.fn(&ch, s); ok { + v.App.Oauth2AppIntegrationId = (r) + } + } + if !n.IsRef("pending_deployment") { + if x := v.App.PendingDeployment; x != nil { + ch := n.Child("pending_deployment") + stringsAppsAppDeployment(c, &ch, x) + } + } + if !n.IsRef("resources") { + if len(v.App.Resources) > 0 { + cn := n.Child("resources") + for i := range v.App.Resources { + { + ch := cn.Elem(i) + stringsAppsAppResource(c, &ch, &v.App.Resources[i]) + } + } + } + } + if c.mode == EmptyFields && v.App.ServicePrincipalClientId == "" && Forced(v.App.ForceSendFields, "ServicePrincipalClientId") { + ch := n.Child("service_principal_client_id") + c.fn(&ch, "") + } + if s := string(v.App.ServicePrincipalClientId); c.match(s) { + ch := n.Child("service_principal_client_id") + if r, ok := c.fn(&ch, s); ok { + v.App.ServicePrincipalClientId = (r) + } + } + if c.mode == EmptyFields && v.App.ServicePrincipalName == "" && Forced(v.App.ForceSendFields, "ServicePrincipalName") { + ch := n.Child("service_principal_name") + c.fn(&ch, "") + } + if s := string(v.App.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.App.ServicePrincipalName = (r) + } + } + if c.mode == EmptyFields && v.App.SourceCodePath == "" && Forced(v.App.ForceSendFields, "SourceCodePath") { + ch := n.Child("source_code_path") + c.fn(&ch, "") + } + if s := string(v.App.SourceCodePath); c.match(s) { + ch := n.Child("source_code_path") + if r, ok := c.fn(&ch, s); ok { + v.App.SourceCodePath = (r) + } + } + if c.mode == EmptyFields && v.App.Space == "" && Forced(v.App.ForceSendFields, "Space") { + ch := n.Child("space") + c.fn(&ch, "") + } + if s := string(v.App.Space); c.match(s) { + ch := n.Child("space") + if r, ok := c.fn(&ch, s); ok { + v.App.Space = (r) + } + } + if !n.IsRef("telemetry_export_destinations") { + if len(v.App.TelemetryExportDestinations) > 0 { + cn := n.Child("telemetry_export_destinations") + for i := range v.App.TelemetryExportDestinations { + { + ch := cn.Elem(i) + stringsAppsTelemetryExportDestination(c, &ch, &v.App.TelemetryExportDestinations[i]) + } + } + } + } + if c.mode == EmptyFields && v.App.ThumbnailUrl == "" && Forced(v.App.ForceSendFields, "ThumbnailUrl") { + ch := n.Child("thumbnail_url") + c.fn(&ch, "") + } + if s := string(v.App.ThumbnailUrl); c.match(s) { + ch := n.Child("thumbnail_url") + if r, ok := c.fn(&ch, s); ok { + v.App.ThumbnailUrl = (r) + } + } + if c.mode == EmptyFields && v.App.UpdateTime == "" && Forced(v.App.ForceSendFields, "UpdateTime") { + ch := n.Child("update_time") + c.fn(&ch, "") + } + if s := string(v.App.UpdateTime); c.match(s) { + ch := n.Child("update_time") + if r, ok := c.fn(&ch, s); ok { + v.App.UpdateTime = (r) + } + } + if c.mode == EmptyFields && v.App.Updater == "" && Forced(v.App.ForceSendFields, "Updater") { + ch := n.Child("updater") + c.fn(&ch, "") + } + if s := string(v.App.Updater); c.match(s) { + ch := n.Child("updater") + if r, ok := c.fn(&ch, s); ok { + v.App.Updater = (r) + } + } + if c.mode == EmptyFields && v.App.UsagePolicyId == "" && Forced(v.App.ForceSendFields, "UsagePolicyId") { + ch := n.Child("usage_policy_id") + c.fn(&ch, "") + } + if s := string(v.App.UsagePolicyId); c.match(s) { + ch := n.Child("usage_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.App.UsagePolicyId = (r) + } + } + if !n.IsRef("user_api_scopes") { + if len(v.App.UserApiScopes) > 0 { + cn := n.Child("user_api_scopes") + for i := range v.App.UserApiScopes { + if s := string(v.App.UserApiScopes[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.App.UserApiScopes[i] = (r) + } + } + } + } + } +} + +func stringsResourcesSecretScope(c *stringsWalk, n *Node, v *resources.SecretScope) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesSecretScopePermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BackendType); c.match(s) { + ch := n.Child("backend_type") + if r, ok := c.fn(&ch, s); ok { + v.BackendType = workspace.ScopeBackendType(r) + } + } + if !n.IsRef("keyvault_metadata") { + if x := v.KeyvaultMetadata; x != nil { + ch := n.Child("keyvault_metadata") + stringsWorkspaceAzureKeyVaultSecretScopeMetadata(c, &ch, x) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } +} + +func stringsResourcesAlert(c *stringsWalk, n *Node, v *resources.Alert) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.FilePath); c.match(s) { + ch := n.Child("file_path") + if r, ok := c.fn(&ch, s); ok { + v.FilePath = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.AlertV2.CreateTime == "" && Forced(v.AlertV2.ForceSendFields, "CreateTime") { + ch := n.Child("create_time") + c.fn(&ch, "") + } + if s := string(v.AlertV2.CreateTime); c.match(s) { + ch := n.Child("create_time") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.CreateTime = (r) + } + } + if c.mode == EmptyFields && v.AlertV2.CustomDescription == "" && Forced(v.AlertV2.ForceSendFields, "CustomDescription") { + ch := n.Child("custom_description") + c.fn(&ch, "") + } + if s := string(v.AlertV2.CustomDescription); c.match(s) { + ch := n.Child("custom_description") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.CustomDescription = (r) + } + } + if c.mode == EmptyFields && v.AlertV2.CustomSummary == "" && Forced(v.AlertV2.ForceSendFields, "CustomSummary") { + ch := n.Child("custom_summary") + c.fn(&ch, "") + } + if s := string(v.AlertV2.CustomSummary); c.match(s) { + ch := n.Child("custom_summary") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.CustomSummary = (r) + } + } + if c.mode == EmptyFields && v.AlertV2.CustomTemplateFormat == "" && Forced(v.AlertV2.ForceSendFields, "CustomTemplateFormat") { + ch := n.Child("custom_template_format") + c.fn(&ch, "") + } + if s := string(v.AlertV2.CustomTemplateFormat); c.match(s) { + ch := n.Child("custom_template_format") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.CustomTemplateFormat = sql.CustomTemplateFormat(r) + } + } + if s := string(v.AlertV2.DisplayName); c.match(s) { + ch := n.Child("display_name") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.DisplayName = (r) + } + } + if !n.IsRef("effective_run_as") { + if x := v.AlertV2.EffectiveRunAs; x != nil { + ch := n.Child("effective_run_as") + stringsSqlAlertV2RunAs(c, &ch, x) + } + } + if !n.IsRef("evaluation") { + { + ch := n.Child("evaluation") + stringsSqlAlertV2Evaluation(c, &ch, &v.AlertV2.Evaluation) + } + } + if c.mode == EmptyFields && v.AlertV2.LifecycleState == "" && Forced(v.AlertV2.ForceSendFields, "LifecycleState") { + ch := n.Child("lifecycle_state") + c.fn(&ch, "") + } + if s := string(v.AlertV2.LifecycleState); c.match(s) { + ch := n.Child("lifecycle_state") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.LifecycleState = sql.AlertLifecycleState(r) + } + } + if c.mode == EmptyFields && v.AlertV2.OwnerUserName == "" && Forced(v.AlertV2.ForceSendFields, "OwnerUserName") { + ch := n.Child("owner_user_name") + c.fn(&ch, "") + } + if s := string(v.AlertV2.OwnerUserName); c.match(s) { + ch := n.Child("owner_user_name") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.OwnerUserName = (r) + } + } + if !n.IsRef("parameters") { + if len(v.AlertV2.Parameters) > 0 { + cn := n.Child("parameters") + for i := range v.AlertV2.Parameters { + { + ch := cn.Elem(i) + stringsSqlAlertStatementParameter(c, &ch, &v.AlertV2.Parameters[i]) + } + } + } + } + if c.mode == EmptyFields && v.AlertV2.ParentPath == "" && Forced(v.AlertV2.ForceSendFields, "ParentPath") { + ch := n.Child("parent_path") + c.fn(&ch, "") + } + if s := string(v.AlertV2.ParentPath); c.match(s) { + ch := n.Child("parent_path") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.ParentPath = (r) + } + } + if s := string(v.AlertV2.QueryText); c.match(s) { + ch := n.Child("query_text") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.QueryText = (r) + } + } + if !n.IsRef("run_as") { + if x := v.AlertV2.RunAs; x != nil { + ch := n.Child("run_as") + stringsSqlAlertV2RunAs(c, &ch, x) + } + } + if c.mode == EmptyFields && v.AlertV2.RunAsUserName == "" && Forced(v.AlertV2.ForceSendFields, "RunAsUserName") { + ch := n.Child("run_as_user_name") + c.fn(&ch, "") + } + if s := string(v.AlertV2.RunAsUserName); c.match(s) { + ch := n.Child("run_as_user_name") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.RunAsUserName = (r) + } + } + if !n.IsRef("schedule") { + { + ch := n.Child("schedule") + stringsSqlCronSchedule(c, &ch, &v.AlertV2.Schedule) + } + } + if c.mode == EmptyFields && v.AlertV2.UpdateTime == "" && Forced(v.AlertV2.ForceSendFields, "UpdateTime") { + ch := n.Child("update_time") + c.fn(&ch, "") + } + if s := string(v.AlertV2.UpdateTime); c.match(s) { + ch := n.Child("update_time") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.UpdateTime = (r) + } + } + if s := string(v.AlertV2.WarehouseId); c.match(s) { + ch := n.Child("warehouse_id") + if r, ok := c.fn(&ch, s); ok { + v.AlertV2.WarehouseId = (r) + } + } +} + +func stringsResourcesSqlWarehouse(c *stringsWalk, n *Node, v *resources.SqlWarehouse) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesSqlWarehousePermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if !n.IsRef("channel") { + if x := v.CreateWarehouseRequest.Channel; x != nil { + ch := n.Child("channel") + stringsSqlChannel(c, &ch, x) + } + } + if c.mode == EmptyFields && v.CreateWarehouseRequest.ClusterSize == "" && Forced(v.CreateWarehouseRequest.ForceSendFields, "ClusterSize") { + ch := n.Child("cluster_size") + c.fn(&ch, "") + } + if s := string(v.CreateWarehouseRequest.ClusterSize); c.match(s) { + ch := n.Child("cluster_size") + if r, ok := c.fn(&ch, s); ok { + v.CreateWarehouseRequest.ClusterSize = (r) + } + } + if c.mode == EmptyFields && v.CreateWarehouseRequest.CreatorName == "" && Forced(v.CreateWarehouseRequest.ForceSendFields, "CreatorName") { + ch := n.Child("creator_name") + c.fn(&ch, "") + } + if s := string(v.CreateWarehouseRequest.CreatorName); c.match(s) { + ch := n.Child("creator_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateWarehouseRequest.CreatorName = (r) + } + } + if c.mode == EmptyFields && v.CreateWarehouseRequest.CustomerVersionLabel == "" && Forced(v.CreateWarehouseRequest.ForceSendFields, "CustomerVersionLabel") { + ch := n.Child("customer_version_label") + c.fn(&ch, "") + } + if s := string(v.CreateWarehouseRequest.CustomerVersionLabel); c.match(s) { + ch := n.Child("customer_version_label") + if r, ok := c.fn(&ch, s); ok { + v.CreateWarehouseRequest.CustomerVersionLabel = (r) + } + } + if c.mode == EmptyFields && v.CreateWarehouseRequest.InstanceProfileArn == "" && Forced(v.CreateWarehouseRequest.ForceSendFields, "InstanceProfileArn") { + ch := n.Child("instance_profile_arn") + c.fn(&ch, "") + } + if s := string(v.CreateWarehouseRequest.InstanceProfileArn); c.match(s) { + ch := n.Child("instance_profile_arn") + if r, ok := c.fn(&ch, s); ok { + v.CreateWarehouseRequest.InstanceProfileArn = (r) + } + } + if c.mode == EmptyFields && v.CreateWarehouseRequest.Name == "" && Forced(v.CreateWarehouseRequest.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.CreateWarehouseRequest.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreateWarehouseRequest.Name = (r) + } + } + if c.mode == EmptyFields && v.CreateWarehouseRequest.SpotInstancePolicy == "" && Forced(v.CreateWarehouseRequest.ForceSendFields, "SpotInstancePolicy") { + ch := n.Child("spot_instance_policy") + c.fn(&ch, "") + } + if s := string(v.CreateWarehouseRequest.SpotInstancePolicy); c.match(s) { + ch := n.Child("spot_instance_policy") + if r, ok := c.fn(&ch, s); ok { + v.CreateWarehouseRequest.SpotInstancePolicy = sql.SpotInstancePolicy(r) + } + } + if !n.IsRef("tags") { + if x := v.CreateWarehouseRequest.Tags; x != nil { + ch := n.Child("tags") + stringsSqlEndpointTags(c, &ch, x) + } + } + if c.mode == EmptyFields && v.CreateWarehouseRequest.WarehouseType == "" && Forced(v.CreateWarehouseRequest.ForceSendFields, "WarehouseType") { + ch := n.Child("warehouse_type") + c.fn(&ch, "") + } + if s := string(v.CreateWarehouseRequest.WarehouseType); c.match(s) { + ch := n.Child("warehouse_type") + if r, ok := c.fn(&ch, s); ok { + v.CreateWarehouseRequest.WarehouseType = sql.CreateWarehouseRequestWarehouseType(r) + } + } +} + +func stringsResourcesDatabaseInstance(c *stringsWalk, n *Node, v *resources.DatabaseInstance) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.DatabaseInstance.Capacity == "" && Forced(v.DatabaseInstance.ForceSendFields, "Capacity") { + ch := n.Child("capacity") + c.fn(&ch, "") + } + if s := string(v.DatabaseInstance.Capacity); c.match(s) { + ch := n.Child("capacity") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.Capacity = (r) + } + } + if !n.IsRef("child_instance_refs") { + if len(v.DatabaseInstance.ChildInstanceRefs) > 0 { + cn := n.Child("child_instance_refs") + for i := range v.DatabaseInstance.ChildInstanceRefs { + { + ch := cn.Elem(i) + stringsDatabaseDatabaseInstanceRef(c, &ch, &v.DatabaseInstance.ChildInstanceRefs[i]) + } + } + } + } + if c.mode == EmptyFields && v.DatabaseInstance.CreationTime == "" && Forced(v.DatabaseInstance.ForceSendFields, "CreationTime") { + ch := n.Child("creation_time") + c.fn(&ch, "") + } + if s := string(v.DatabaseInstance.CreationTime); c.match(s) { + ch := n.Child("creation_time") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.CreationTime = (r) + } + } + if c.mode == EmptyFields && v.DatabaseInstance.Creator == "" && Forced(v.DatabaseInstance.ForceSendFields, "Creator") { + ch := n.Child("creator") + c.fn(&ch, "") + } + if s := string(v.DatabaseInstance.Creator); c.match(s) { + ch := n.Child("creator") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.Creator = (r) + } + } + if !n.IsRef("custom_tags") { + if len(v.DatabaseInstance.CustomTags) > 0 { + cn := n.Child("custom_tags") + for i := range v.DatabaseInstance.CustomTags { + { + ch := cn.Elem(i) + stringsDatabaseCustomTag(c, &ch, &v.DatabaseInstance.CustomTags[i]) + } + } + } + } + if c.mode == EmptyFields && v.DatabaseInstance.EffectiveCapacity == "" && Forced(v.DatabaseInstance.ForceSendFields, "EffectiveCapacity") { + ch := n.Child("effective_capacity") + c.fn(&ch, "") + } + if s := string(v.DatabaseInstance.EffectiveCapacity); c.match(s) { + ch := n.Child("effective_capacity") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.EffectiveCapacity = (r) + } + } + if !n.IsRef("effective_custom_tags") { + if len(v.DatabaseInstance.EffectiveCustomTags) > 0 { + cn := n.Child("effective_custom_tags") + for i := range v.DatabaseInstance.EffectiveCustomTags { + { + ch := cn.Elem(i) + stringsDatabaseCustomTag(c, &ch, &v.DatabaseInstance.EffectiveCustomTags[i]) + } + } + } + } + if c.mode == EmptyFields && v.DatabaseInstance.EffectiveUsagePolicyId == "" && Forced(v.DatabaseInstance.ForceSendFields, "EffectiveUsagePolicyId") { + ch := n.Child("effective_usage_policy_id") + c.fn(&ch, "") + } + if s := string(v.DatabaseInstance.EffectiveUsagePolicyId); c.match(s) { + ch := n.Child("effective_usage_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.EffectiveUsagePolicyId = (r) + } + } + if s := string(v.DatabaseInstance.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.Name = (r) + } + } + if !n.IsRef("parent_instance_ref") { + if x := v.DatabaseInstance.ParentInstanceRef; x != nil { + ch := n.Child("parent_instance_ref") + stringsDatabaseDatabaseInstanceRef(c, &ch, x) + } + } + if c.mode == EmptyFields && v.DatabaseInstance.PgVersion == "" && Forced(v.DatabaseInstance.ForceSendFields, "PgVersion") { + ch := n.Child("pg_version") + c.fn(&ch, "") + } + if s := string(v.DatabaseInstance.PgVersion); c.match(s) { + ch := n.Child("pg_version") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.PgVersion = (r) + } + } + if c.mode == EmptyFields && v.DatabaseInstance.ReadOnlyDns == "" && Forced(v.DatabaseInstance.ForceSendFields, "ReadOnlyDns") { + ch := n.Child("read_only_dns") + c.fn(&ch, "") + } + if s := string(v.DatabaseInstance.ReadOnlyDns); c.match(s) { + ch := n.Child("read_only_dns") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.ReadOnlyDns = (r) + } + } + if c.mode == EmptyFields && v.DatabaseInstance.ReadWriteDns == "" && Forced(v.DatabaseInstance.ForceSendFields, "ReadWriteDns") { + ch := n.Child("read_write_dns") + c.fn(&ch, "") + } + if s := string(v.DatabaseInstance.ReadWriteDns); c.match(s) { + ch := n.Child("read_write_dns") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.ReadWriteDns = (r) + } + } + if c.mode == EmptyFields && v.DatabaseInstance.State == "" && Forced(v.DatabaseInstance.ForceSendFields, "State") { + ch := n.Child("state") + c.fn(&ch, "") + } + if s := string(v.DatabaseInstance.State); c.match(s) { + ch := n.Child("state") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.State = database.DatabaseInstanceState(r) + } + } + if c.mode == EmptyFields && v.DatabaseInstance.Uid == "" && Forced(v.DatabaseInstance.ForceSendFields, "Uid") { + ch := n.Child("uid") + c.fn(&ch, "") + } + if s := string(v.DatabaseInstance.Uid); c.match(s) { + ch := n.Child("uid") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.Uid = (r) + } + } + if c.mode == EmptyFields && v.DatabaseInstance.UsagePolicyId == "" && Forced(v.DatabaseInstance.ForceSendFields, "UsagePolicyId") { + ch := n.Child("usage_policy_id") + c.fn(&ch, "") + } + if s := string(v.DatabaseInstance.UsagePolicyId); c.match(s) { + ch := n.Child("usage_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseInstance.UsagePolicyId = (r) + } + } +} + +func stringsResourcesDatabaseCatalog(c *stringsWalk, n *Node, v *resources.DatabaseCatalog) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.DatabaseCatalog.DatabaseInstanceName); c.match(s) { + ch := n.Child("database_instance_name") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseCatalog.DatabaseInstanceName = (r) + } + } + if s := string(v.DatabaseCatalog.DatabaseName); c.match(s) { + ch := n.Child("database_name") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseCatalog.DatabaseName = (r) + } + } + if s := string(v.DatabaseCatalog.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseCatalog.Name = (r) + } + } + if c.mode == EmptyFields && v.DatabaseCatalog.Uid == "" && Forced(v.DatabaseCatalog.ForceSendFields, "Uid") { + ch := n.Child("uid") + c.fn(&ch, "") + } + if s := string(v.DatabaseCatalog.Uid); c.match(s) { + ch := n.Child("uid") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseCatalog.Uid = (r) + } + } +} + +func stringsResourcesSyncedDatabaseTable(c *stringsWalk, n *Node, v *resources.SyncedDatabaseTable) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if !n.IsRef("data_synchronization_status") { + if x := v.SyncedDatabaseTable.DataSynchronizationStatus; x != nil { + ch := n.Child("data_synchronization_status") + stringsDatabaseSyncedTableStatus(c, &ch, x) + } + } + if c.mode == EmptyFields && v.SyncedDatabaseTable.DatabaseInstanceName == "" && Forced(v.SyncedDatabaseTable.ForceSendFields, "DatabaseInstanceName") { + ch := n.Child("database_instance_name") + c.fn(&ch, "") + } + if s := string(v.SyncedDatabaseTable.DatabaseInstanceName); c.match(s) { + ch := n.Child("database_instance_name") + if r, ok := c.fn(&ch, s); ok { + v.SyncedDatabaseTable.DatabaseInstanceName = (r) + } + } + if c.mode == EmptyFields && v.SyncedDatabaseTable.EffectiveDatabaseInstanceName == "" && Forced(v.SyncedDatabaseTable.ForceSendFields, "EffectiveDatabaseInstanceName") { + ch := n.Child("effective_database_instance_name") + c.fn(&ch, "") + } + if s := string(v.SyncedDatabaseTable.EffectiveDatabaseInstanceName); c.match(s) { + ch := n.Child("effective_database_instance_name") + if r, ok := c.fn(&ch, s); ok { + v.SyncedDatabaseTable.EffectiveDatabaseInstanceName = (r) + } + } + if c.mode == EmptyFields && v.SyncedDatabaseTable.EffectiveLogicalDatabaseName == "" && Forced(v.SyncedDatabaseTable.ForceSendFields, "EffectiveLogicalDatabaseName") { + ch := n.Child("effective_logical_database_name") + c.fn(&ch, "") + } + if s := string(v.SyncedDatabaseTable.EffectiveLogicalDatabaseName); c.match(s) { + ch := n.Child("effective_logical_database_name") + if r, ok := c.fn(&ch, s); ok { + v.SyncedDatabaseTable.EffectiveLogicalDatabaseName = (r) + } + } + if c.mode == EmptyFields && v.SyncedDatabaseTable.LogicalDatabaseName == "" && Forced(v.SyncedDatabaseTable.ForceSendFields, "LogicalDatabaseName") { + ch := n.Child("logical_database_name") + c.fn(&ch, "") + } + if s := string(v.SyncedDatabaseTable.LogicalDatabaseName); c.match(s) { + ch := n.Child("logical_database_name") + if r, ok := c.fn(&ch, s); ok { + v.SyncedDatabaseTable.LogicalDatabaseName = (r) + } + } + if s := string(v.SyncedDatabaseTable.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.SyncedDatabaseTable.Name = (r) + } + } + if !n.IsRef("spec") { + if x := v.SyncedDatabaseTable.Spec; x != nil { + ch := n.Child("spec") + stringsDatabaseSyncedTableSpec(c, &ch, x) + } + } + if c.mode == EmptyFields && v.SyncedDatabaseTable.UnityCatalogProvisioningState == "" && Forced(v.SyncedDatabaseTable.ForceSendFields, "UnityCatalogProvisioningState") { + ch := n.Child("unity_catalog_provisioning_state") + c.fn(&ch, "") + } + if s := string(v.SyncedDatabaseTable.UnityCatalogProvisioningState); c.match(s) { + ch := n.Child("unity_catalog_provisioning_state") + if r, ok := c.fn(&ch, s); ok { + v.SyncedDatabaseTable.UnityCatalogProvisioningState = database.ProvisioningInfoState(r) + } + } +} + +func stringsResourcesPostgresProject(c *stringsWalk, n *Node, v *resources.PostgresProject) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.PostgresProjectConfig.ProjectId); c.match(s) { + ch := n.Child("project_id") + if r, ok := c.fn(&ch, s); ok { + v.PostgresProjectConfig.ProjectId = (r) + } + } + if c.mode == EmptyFields && v.PostgresProjectConfig.ProjectSpec.BudgetPolicyId == "" && Forced(v.PostgresProjectConfig.ProjectSpec.ForceSendFields, "BudgetPolicyId") { + ch := n.Child("budget_policy_id") + c.fn(&ch, "") + } + if s := string(v.PostgresProjectConfig.ProjectSpec.BudgetPolicyId); c.match(s) { + ch := n.Child("budget_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.PostgresProjectConfig.ProjectSpec.BudgetPolicyId = (r) + } + } + if !n.IsRef("custom_tags") { + if len(v.PostgresProjectConfig.ProjectSpec.CustomTags) > 0 { + cn := n.Child("custom_tags") + for i := range v.PostgresProjectConfig.ProjectSpec.CustomTags { + { + ch := cn.Elem(i) + stringsPostgresProjectCustomTag(c, &ch, &v.PostgresProjectConfig.ProjectSpec.CustomTags[i]) + } + } + } + } + if c.mode == EmptyFields && v.PostgresProjectConfig.ProjectSpec.DefaultBranch == "" && Forced(v.PostgresProjectConfig.ProjectSpec.ForceSendFields, "DefaultBranch") { + ch := n.Child("default_branch") + c.fn(&ch, "") + } + if s := string(v.PostgresProjectConfig.ProjectSpec.DefaultBranch); c.match(s) { + ch := n.Child("default_branch") + if r, ok := c.fn(&ch, s); ok { + v.PostgresProjectConfig.ProjectSpec.DefaultBranch = (r) + } + } + if !n.IsRef("default_endpoint_settings") { + if x := v.PostgresProjectConfig.ProjectSpec.DefaultEndpointSettings; x != nil { + ch := n.Child("default_endpoint_settings") + stringsPostgresProjectDefaultEndpointSettings(c, &ch, x) + } + } + if c.mode == EmptyFields && v.PostgresProjectConfig.ProjectSpec.DisplayName == "" && Forced(v.PostgresProjectConfig.ProjectSpec.ForceSendFields, "DisplayName") { + ch := n.Child("display_name") + c.fn(&ch, "") + } + if s := string(v.PostgresProjectConfig.ProjectSpec.DisplayName); c.match(s) { + ch := n.Child("display_name") + if r, ok := c.fn(&ch, s); ok { + v.PostgresProjectConfig.ProjectSpec.DisplayName = (r) + } + } +} + +func stringsResourcesPostgresBranch(c *stringsWalk, n *Node, v *resources.PostgresBranch) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.PostgresBranchConfig.BranchId); c.match(s) { + ch := n.Child("branch_id") + if r, ok := c.fn(&ch, s); ok { + v.PostgresBranchConfig.BranchId = (r) + } + } + if s := string(v.PostgresBranchConfig.Parent); c.match(s) { + ch := n.Child("parent") + if r, ok := c.fn(&ch, s); ok { + v.PostgresBranchConfig.Parent = (r) + } + } + if c.mode == EmptyFields && v.PostgresBranchConfig.BranchSpec.SourceBranch == "" && Forced(v.PostgresBranchConfig.BranchSpec.ForceSendFields, "SourceBranch") { + ch := n.Child("source_branch") + c.fn(&ch, "") + } + if s := string(v.PostgresBranchConfig.BranchSpec.SourceBranch); c.match(s) { + ch := n.Child("source_branch") + if r, ok := c.fn(&ch, s); ok { + v.PostgresBranchConfig.BranchSpec.SourceBranch = (r) + } + } + if c.mode == EmptyFields && v.PostgresBranchConfig.BranchSpec.SourceBranchLsn == "" && Forced(v.PostgresBranchConfig.BranchSpec.ForceSendFields, "SourceBranchLsn") { + ch := n.Child("source_branch_lsn") + c.fn(&ch, "") + } + if s := string(v.PostgresBranchConfig.BranchSpec.SourceBranchLsn); c.match(s) { + ch := n.Child("source_branch_lsn") + if r, ok := c.fn(&ch, s); ok { + v.PostgresBranchConfig.BranchSpec.SourceBranchLsn = (r) + } + } + if c.mode == EmptyFields && v.PostgresBranchConfig.BranchSpec.SourceSnapshot == "" && Forced(v.PostgresBranchConfig.BranchSpec.ForceSendFields, "SourceSnapshot") { + ch := n.Child("source_snapshot") + c.fn(&ch, "") + } + if s := string(v.PostgresBranchConfig.BranchSpec.SourceSnapshot); c.match(s) { + ch := n.Child("source_snapshot") + if r, ok := c.fn(&ch, s); ok { + v.PostgresBranchConfig.BranchSpec.SourceSnapshot = (r) + } + } +} + +func stringsResourcesPostgresEndpoint(c *stringsWalk, n *Node, v *resources.PostgresEndpoint) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.PostgresEndpointConfig.EndpointId); c.match(s) { + ch := n.Child("endpoint_id") + if r, ok := c.fn(&ch, s); ok { + v.PostgresEndpointConfig.EndpointId = (r) + } + } + if s := string(v.PostgresEndpointConfig.Parent); c.match(s) { + ch := n.Child("parent") + if r, ok := c.fn(&ch, s); ok { + v.PostgresEndpointConfig.Parent = (r) + } + } + if s := string(v.PostgresEndpointConfig.EndpointSpec.EndpointType); c.match(s) { + ch := n.Child("endpoint_type") + if r, ok := c.fn(&ch, s); ok { + v.PostgresEndpointConfig.EndpointSpec.EndpointType = postgres.EndpointType(r) + } + } + if !n.IsRef("settings") { + if x := v.PostgresEndpointConfig.EndpointSpec.Settings; x != nil { + ch := n.Child("settings") + stringsPostgresEndpointSettings(c, &ch, x) + } + } +} + +func stringsResourcesPostgresCatalog(c *stringsWalk, n *Node, v *resources.PostgresCatalog) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.PostgresCatalogConfig.CatalogId); c.match(s) { + ch := n.Child("catalog_id") + if r, ok := c.fn(&ch, s); ok { + v.PostgresCatalogConfig.CatalogId = (r) + } + } + if c.mode == EmptyFields && v.PostgresCatalogConfig.CatalogCatalogSpec.Branch == "" && Forced(v.PostgresCatalogConfig.CatalogCatalogSpec.ForceSendFields, "Branch") { + ch := n.Child("branch") + c.fn(&ch, "") + } + if s := string(v.PostgresCatalogConfig.CatalogCatalogSpec.Branch); c.match(s) { + ch := n.Child("branch") + if r, ok := c.fn(&ch, s); ok { + v.PostgresCatalogConfig.CatalogCatalogSpec.Branch = (r) + } + } + if s := string(v.PostgresCatalogConfig.CatalogCatalogSpec.PostgresDatabase); c.match(s) { + ch := n.Child("postgres_database") + if r, ok := c.fn(&ch, s); ok { + v.PostgresCatalogConfig.CatalogCatalogSpec.PostgresDatabase = (r) + } + } +} + +func stringsResourcesPostgresDatabase(c *stringsWalk, n *Node, v *resources.PostgresDatabase) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.PostgresDatabaseConfig.DatabaseId); c.match(s) { + ch := n.Child("database_id") + if r, ok := c.fn(&ch, s); ok { + v.PostgresDatabaseConfig.DatabaseId = (r) + } + } + if s := string(v.PostgresDatabaseConfig.Parent); c.match(s) { + ch := n.Child("parent") + if r, ok := c.fn(&ch, s); ok { + v.PostgresDatabaseConfig.Parent = (r) + } + } + if c.mode == EmptyFields && v.PostgresDatabaseConfig.DatabaseDatabaseSpec.PostgresDatabase == "" && Forced(v.PostgresDatabaseConfig.DatabaseDatabaseSpec.ForceSendFields, "PostgresDatabase") { + ch := n.Child("postgres_database") + c.fn(&ch, "") + } + if s := string(v.PostgresDatabaseConfig.DatabaseDatabaseSpec.PostgresDatabase); c.match(s) { + ch := n.Child("postgres_database") + if r, ok := c.fn(&ch, s); ok { + v.PostgresDatabaseConfig.DatabaseDatabaseSpec.PostgresDatabase = (r) + } + } + if s := string(v.PostgresDatabaseConfig.DatabaseDatabaseSpec.Role); c.match(s) { + ch := n.Child("role") + if r, ok := c.fn(&ch, s); ok { + v.PostgresDatabaseConfig.DatabaseDatabaseSpec.Role = (r) + } + } +} + +func stringsResourcesPostgresRole(c *stringsWalk, n *Node, v *resources.PostgresRole) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.PostgresRoleConfig.RoleId); c.match(s) { + ch := n.Child("role_id") + if r, ok := c.fn(&ch, s); ok { + v.PostgresRoleConfig.RoleId = (r) + } + } + if s := string(v.PostgresRoleConfig.Parent); c.match(s) { + ch := n.Child("parent") + if r, ok := c.fn(&ch, s); ok { + v.PostgresRoleConfig.Parent = (r) + } + } + if c.mode == EmptyFields && v.PostgresRoleConfig.RoleRoleSpec.AuthMethod == "" && Forced(v.PostgresRoleConfig.RoleRoleSpec.ForceSendFields, "AuthMethod") { + ch := n.Child("auth_method") + c.fn(&ch, "") + } + if s := string(v.PostgresRoleConfig.RoleRoleSpec.AuthMethod); c.match(s) { + ch := n.Child("auth_method") + if r, ok := c.fn(&ch, s); ok { + v.PostgresRoleConfig.RoleRoleSpec.AuthMethod = postgres.RoleAuthMethod(r) + } + } + if c.mode == EmptyFields && v.PostgresRoleConfig.RoleRoleSpec.IdentityType == "" && Forced(v.PostgresRoleConfig.RoleRoleSpec.ForceSendFields, "IdentityType") { + ch := n.Child("identity_type") + c.fn(&ch, "") + } + if s := string(v.PostgresRoleConfig.RoleRoleSpec.IdentityType); c.match(s) { + ch := n.Child("identity_type") + if r, ok := c.fn(&ch, s); ok { + v.PostgresRoleConfig.RoleRoleSpec.IdentityType = postgres.RoleIdentityType(r) + } + } + if !n.IsRef("membership_roles") { + if len(v.PostgresRoleConfig.RoleRoleSpec.MembershipRoles) > 0 { + cn := n.Child("membership_roles") + for i := range v.PostgresRoleConfig.RoleRoleSpec.MembershipRoles { + if s := string(v.PostgresRoleConfig.RoleRoleSpec.MembershipRoles[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.PostgresRoleConfig.RoleRoleSpec.MembershipRoles[i] = postgres.RoleMembershipRole(r) + } + } + } + } + } + if c.mode == EmptyFields && v.PostgresRoleConfig.RoleRoleSpec.PostgresRole == "" && Forced(v.PostgresRoleConfig.RoleRoleSpec.ForceSendFields, "PostgresRole") { + ch := n.Child("postgres_role") + c.fn(&ch, "") + } + if s := string(v.PostgresRoleConfig.RoleRoleSpec.PostgresRole); c.match(s) { + ch := n.Child("postgres_role") + if r, ok := c.fn(&ch, s); ok { + v.PostgresRoleConfig.RoleRoleSpec.PostgresRole = (r) + } + } +} + +func stringsResourcesPostgresSyncedTable(c *stringsWalk, n *Node, v *resources.PostgresSyncedTable) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.PostgresSyncedTableConfig.SyncedTableId); c.match(s) { + ch := n.Child("synced_table_id") + if r, ok := c.fn(&ch, s); ok { + v.PostgresSyncedTableConfig.SyncedTableId = (r) + } + } + if c.mode == EmptyFields && v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.Branch == "" && Forced(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ForceSendFields, "Branch") { + ch := n.Child("branch") + c.fn(&ch, "") + } + if s := string(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.Branch); c.match(s) { + ch := n.Child("branch") + if r, ok := c.fn(&ch, s); ok { + v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.Branch = (r) + } + } + if c.mode == EmptyFields && v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExistingPipelineId == "" && Forced(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ForceSendFields, "ExistingPipelineId") { + ch := n.Child("existing_pipeline_id") + c.fn(&ch, "") + } + if s := string(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExistingPipelineId); c.match(s) { + ch := n.Child("existing_pipeline_id") + if r, ok := c.fn(&ch, s); ok { + v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExistingPipelineId = (r) + } + } + if !n.IsRef("extra_columns") { + if len(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns) > 0 { + cn := n.Child("extra_columns") + for i := range v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns { + { + ch := cn.Elem(i) + stringsPostgresSyncedTableSyncedTableSpecExtraColumn(c, &ch, &v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ExtraColumns[i]) + } + } + } + } + if !n.IsRef("new_pipeline_spec") { + if x := v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.NewPipelineSpec; x != nil { + ch := n.Child("new_pipeline_spec") + stringsPostgresNewPipelineSpec(c, &ch, x) + } + } + if c.mode == EmptyFields && v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.PostgresDatabase == "" && Forced(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ForceSendFields, "PostgresDatabase") { + ch := n.Child("postgres_database") + c.fn(&ch, "") + } + if s := string(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.PostgresDatabase); c.match(s) { + ch := n.Child("postgres_database") + if r, ok := c.fn(&ch, s); ok { + v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.PostgresDatabase = (r) + } + } + if !n.IsRef("primary_key_columns") { + if len(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.PrimaryKeyColumns) > 0 { + cn := n.Child("primary_key_columns") + for i := range v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.PrimaryKeyColumns { + if s := string(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.PrimaryKeyColumns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.PrimaryKeyColumns[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.SchedulingPolicy == "" && Forced(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ForceSendFields, "SchedulingPolicy") { + ch := n.Child("scheduling_policy") + c.fn(&ch, "") + } + if s := string(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.SchedulingPolicy); c.match(s) { + ch := n.Child("scheduling_policy") + if r, ok := c.fn(&ch, s); ok { + v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.SchedulingPolicy = postgres.SyncedTableSyncedTableSpecSyncedTableSchedulingPolicy(r) + } + } + if c.mode == EmptyFields && v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.SourceTableFullName == "" && Forced(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ForceSendFields, "SourceTableFullName") { + ch := n.Child("source_table_full_name") + c.fn(&ch, "") + } + if s := string(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.SourceTableFullName); c.match(s) { + ch := n.Child("source_table_full_name") + if r, ok := c.fn(&ch, s); ok { + v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.SourceTableFullName = (r) + } + } + if c.mode == EmptyFields && v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TimeseriesKey == "" && Forced(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.ForceSendFields, "TimeseriesKey") { + ch := n.Child("timeseries_key") + c.fn(&ch, "") + } + if s := string(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TimeseriesKey); c.match(s) { + ch := n.Child("timeseries_key") + if r, ok := c.fn(&ch, s); ok { + v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TimeseriesKey = (r) + } + } + if !n.IsRef("type_overrides") { + if len(v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides) > 0 { + cn := n.Child("type_overrides") + for i := range v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides { + { + ch := cn.Elem(i) + stringsPostgresSyncedTableSyncedTableSpecTypeOverride(c, &ch, &v.PostgresSyncedTableConfig.SyncedTableSyncedTableSpec.TypeOverrides[i]) + } + } + } + } +} + +func stringsResourcesPostgresSnapshotSchedule(c *stringsWalk, n *Node, v *resources.PostgresSnapshotSchedule) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.PostgresSnapshotScheduleConfig.Branch); c.match(s) { + ch := n.Child("branch") + if r, ok := c.fn(&ch, s); ok { + v.PostgresSnapshotScheduleConfig.Branch = (r) + } + } + if !n.IsRef("schedule") { + if len(v.PostgresSnapshotScheduleConfig.Schedule) > 0 { + cn := n.Child("schedule") + for i := range v.PostgresSnapshotScheduleConfig.Schedule { + { + ch := cn.Elem(i) + stringsPostgresScheduleCadence(c, &ch, &v.PostgresSnapshotScheduleConfig.Schedule[i]) + } + } + } + } +} + +func stringsResourcesVectorSearchEndpoint(c *stringsWalk, n *Node, v *resources.VectorSearchEndpoint) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesVectorSearchEndpointPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.CreateEndpoint.BudgetPolicyId == "" && Forced(v.CreateEndpoint.ForceSendFields, "BudgetPolicyId") { + ch := n.Child("budget_policy_id") + c.fn(&ch, "") + } + if s := string(v.CreateEndpoint.BudgetPolicyId); c.match(s) { + ch := n.Child("budget_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.CreateEndpoint.BudgetPolicyId = (r) + } + } + if s := string(v.CreateEndpoint.EndpointType); c.match(s) { + ch := n.Child("endpoint_type") + if r, ok := c.fn(&ch, s); ok { + v.CreateEndpoint.EndpointType = vectorsearch.EndpointType(r) + } + } + if s := string(v.CreateEndpoint.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreateEndpoint.Name = (r) + } + } + if c.mode == EmptyFields && v.CreateEndpoint.UsagePolicyId == "" && Forced(v.CreateEndpoint.ForceSendFields, "UsagePolicyId") { + ch := n.Child("usage_policy_id") + c.fn(&ch, "") + } + if s := string(v.CreateEndpoint.UsagePolicyId); c.match(s) { + ch := n.Child("usage_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.CreateEndpoint.UsagePolicyId = (r) + } + } +} + +func stringsResourcesVectorSearchIndex(c *stringsWalk, n *Node, v *resources.VectorSearchIndex) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("grants") { + if len(v.Grants) > 0 { + cn := n.Child("grants") + for i := range v.Grants { + { + ch := cn.Elem(i) + stringsCatalogPrivilegeAssignment(c, &ch, &v.Grants[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if !n.IsRef("delta_sync_index_spec") { + if x := v.CreateVectorIndexRequest.DeltaSyncIndexSpec; x != nil { + ch := n.Child("delta_sync_index_spec") + stringsVectorsearchDeltaSyncVectorIndexSpecRequest(c, &ch, x) + } + } + if !n.IsRef("direct_access_index_spec") { + if x := v.CreateVectorIndexRequest.DirectAccessIndexSpec; x != nil { + ch := n.Child("direct_access_index_spec") + stringsVectorsearchDirectAccessVectorIndexSpec(c, &ch, x) + } + } + if s := string(v.CreateVectorIndexRequest.EndpointName); c.match(s) { + ch := n.Child("endpoint_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateVectorIndexRequest.EndpointName = (r) + } + } + if s := string(v.CreateVectorIndexRequest.IndexSubtype); c.match(s) { + ch := n.Child("index_subtype") + if r, ok := c.fn(&ch, s); ok { + v.CreateVectorIndexRequest.IndexSubtype = vectorsearch.IndexSubtype(r) + } + } + if s := string(v.CreateVectorIndexRequest.IndexType); c.match(s) { + ch := n.Child("index_type") + if r, ok := c.fn(&ch, s); ok { + v.CreateVectorIndexRequest.IndexType = vectorsearch.VectorIndexType(r) + } + } + if s := string(v.CreateVectorIndexRequest.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreateVectorIndexRequest.Name = (r) + } + } + if s := string(v.CreateVectorIndexRequest.PrimaryKey); c.match(s) { + ch := n.Child("primary_key") + if r, ok := c.fn(&ch, s); ok { + v.CreateVectorIndexRequest.PrimaryKey = (r) + } + } +} + +func stringsResourcesInstancePool(c *stringsWalk, n *Node, v *resources.InstancePool) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesInstancePoolPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if !n.IsRef("aws_attributes") { + if x := v.CreateInstancePool.AwsAttributes; x != nil { + ch := n.Child("aws_attributes") + stringsComputeInstancePoolAwsAttributes(c, &ch, x) + } + } + if !n.IsRef("azure_attributes") { + if x := v.CreateInstancePool.AzureAttributes; x != nil { + ch := n.Child("azure_attributes") + stringsComputeInstancePoolAzureAttributes(c, &ch, x) + } + } + if !n.IsRef("custom_tags") { + if len(v.CreateInstancePool.CustomTags) > 0 { + cn := n.Child("custom_tags") + for _, k := range sortedKeys(v.CreateInstancePool.CustomTags) { + if s := string(v.CreateInstancePool.CustomTags[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.CreateInstancePool.CustomTags[k] = (r) + } + } + } + } + } + if !n.IsRef("disk_spec") { + if x := v.CreateInstancePool.DiskSpec; x != nil { + ch := n.Child("disk_spec") + stringsComputeDiskSpec(c, &ch, x) + } + } + if !n.IsRef("gcp_attributes") { + if x := v.CreateInstancePool.GcpAttributes; x != nil { + ch := n.Child("gcp_attributes") + stringsComputeInstancePoolGcpAttributes(c, &ch, x) + } + } + if s := string(v.CreateInstancePool.InstancePoolName); c.match(s) { + ch := n.Child("instance_pool_name") + if r, ok := c.fn(&ch, s); ok { + v.CreateInstancePool.InstancePoolName = (r) + } + } + if !n.IsRef("node_type_flexibility") { + if x := v.CreateInstancePool.NodeTypeFlexibility; x != nil { + ch := n.Child("node_type_flexibility") + stringsComputeNodeTypeFlexibility(c, &ch, x) + } + } + if s := string(v.CreateInstancePool.NodeTypeId); c.match(s) { + ch := n.Child("node_type_id") + if r, ok := c.fn(&ch, s); ok { + v.CreateInstancePool.NodeTypeId = (r) + } + } + if !n.IsRef("preloaded_docker_images") { + if len(v.CreateInstancePool.PreloadedDockerImages) > 0 { + cn := n.Child("preloaded_docker_images") + for i := range v.CreateInstancePool.PreloadedDockerImages { + { + ch := cn.Elem(i) + stringsComputeDockerImage(c, &ch, &v.CreateInstancePool.PreloadedDockerImages[i]) + } + } + } + } + if !n.IsRef("preloaded_spark_versions") { + if len(v.CreateInstancePool.PreloadedSparkVersions) > 0 { + cn := n.Child("preloaded_spark_versions") + for i := range v.CreateInstancePool.PreloadedSparkVersions { + if s := string(v.CreateInstancePool.PreloadedSparkVersions[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.CreateInstancePool.PreloadedSparkVersions[i] = (r) + } + } + } + } + } +} + +func stringsResourcesSecret(c *stringsWalk, n *Node, v *resources.Secret) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if !n.IsRef("grants") { + if len(v.Grants) > 0 { + cn := n.Child("grants") + for i := range v.Grants { + { + ch := cn.Elem(i) + stringsCatalogPrivilegeAssignment(c, &ch, &v.Grants[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if s := string(v.Secret.CatalogName); c.match(s) { + ch := n.Child("catalog_name") + if r, ok := c.fn(&ch, s); ok { + v.Secret.CatalogName = (r) + } + } + if c.mode == EmptyFields && v.Secret.Comment == "" && Forced(v.Secret.ForceSendFields, "Comment") { + ch := n.Child("comment") + c.fn(&ch, "") + } + if s := string(v.Secret.Comment); c.match(s) { + ch := n.Child("comment") + if r, ok := c.fn(&ch, s); ok { + v.Secret.Comment = (r) + } + } + if c.mode == EmptyFields && v.Secret.CreatedBy == "" && Forced(v.Secret.ForceSendFields, "CreatedBy") { + ch := n.Child("created_by") + c.fn(&ch, "") + } + if s := string(v.Secret.CreatedBy); c.match(s) { + ch := n.Child("created_by") + if r, ok := c.fn(&ch, s); ok { + v.Secret.CreatedBy = (r) + } + } + if c.mode == EmptyFields && v.Secret.EffectiveOwner == "" && Forced(v.Secret.ForceSendFields, "EffectiveOwner") { + ch := n.Child("effective_owner") + c.fn(&ch, "") + } + if s := string(v.Secret.EffectiveOwner); c.match(s) { + ch := n.Child("effective_owner") + if r, ok := c.fn(&ch, s); ok { + v.Secret.EffectiveOwner = (r) + } + } + if c.mode == EmptyFields && v.Secret.EffectiveValue == "" && Forced(v.Secret.ForceSendFields, "EffectiveValue") { + ch := n.Child("effective_value") + c.fn(&ch, "") + } + if s := string(v.Secret.EffectiveValue); c.match(s) { + ch := n.Child("effective_value") + if r, ok := c.fn(&ch, s); ok { + v.Secret.EffectiveValue = (r) + } + } + if c.mode == EmptyFields && v.Secret.FullName == "" && Forced(v.Secret.ForceSendFields, "FullName") { + ch := n.Child("full_name") + c.fn(&ch, "") + } + if s := string(v.Secret.FullName); c.match(s) { + ch := n.Child("full_name") + if r, ok := c.fn(&ch, s); ok { + v.Secret.FullName = (r) + } + } + if c.mode == EmptyFields && v.Secret.MetastoreId == "" && Forced(v.Secret.ForceSendFields, "MetastoreId") { + ch := n.Child("metastore_id") + c.fn(&ch, "") + } + if s := string(v.Secret.MetastoreId); c.match(s) { + ch := n.Child("metastore_id") + if r, ok := c.fn(&ch, s); ok { + v.Secret.MetastoreId = (r) + } + } + if s := string(v.Secret.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Secret.Name = (r) + } + } + if c.mode == EmptyFields && v.Secret.Owner == "" && Forced(v.Secret.ForceSendFields, "Owner") { + ch := n.Child("owner") + c.fn(&ch, "") + } + if s := string(v.Secret.Owner); c.match(s) { + ch := n.Child("owner") + if r, ok := c.fn(&ch, s); ok { + v.Secret.Owner = (r) + } + } + if s := string(v.Secret.SchemaName); c.match(s) { + ch := n.Child("schema_name") + if r, ok := c.fn(&ch, s); ok { + v.Secret.SchemaName = (r) + } + } + if c.mode == EmptyFields && v.Secret.UpdatedBy == "" && Forced(v.Secret.ForceSendFields, "UpdatedBy") { + ch := n.Child("updated_by") + c.fn(&ch, "") + } + if s := string(v.Secret.UpdatedBy); c.match(s) { + ch := n.Child("updated_by") + if r, ok := c.fn(&ch, s); ok { + v.Secret.UpdatedBy = (r) + } + } + if s := string(v.Secret.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Secret.Value = (r) + } + } +} + +func stringsResourcesClusterPolicy(c *stringsWalk, n *Node, v *resources.ClusterPolicy) { + if n.isRef() { + return + } + if s := string(v.ID); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.ID = (r) + } + } + if c.mode == EmptyFields && v.Definition == "" { + ch := n.Child("definition") + c.fn(&ch, "") + } + if v.Definition != nil { + ch := n.Child("definition") + c.any(&ch, &v.Definition) + } + if c.mode == EmptyFields && v.PolicyFamilyDefinitionOverrides == "" { + ch := n.Child("policy_family_definition_overrides") + c.fn(&ch, "") + } + if v.PolicyFamilyDefinitionOverrides != nil { + ch := n.Child("policy_family_definition_overrides") + c.any(&ch, &v.PolicyFamilyDefinitionOverrides) + } + if !n.IsRef("permissions") { + if len(v.Permissions) > 0 { + cn := n.Child("permissions") + for i := range v.Permissions { + { + ch := cn.Elem(i) + stringsResourcesClusterPolicyPermission(c, &ch, &v.Permissions[i]) + } + } + } + } + if s := string(v.BaseResource.URL); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.URL = (r) + } + } + if s := string(v.BaseResource.ModifiedStatus); c.match(s) { + ch := n.Child("modified_status") + if r, ok := c.fn(&ch, s); ok { + v.BaseResource.ModifiedStatus = (r) + } + } + if c.mode == EmptyFields && v.CreatePolicy.Description == "" && Forced(v.CreatePolicy.ForceSendFields, "Description") { + ch := n.Child("description") + c.fn(&ch, "") + } + if s := string(v.CreatePolicy.Description); c.match(s) { + ch := n.Child("description") + if r, ok := c.fn(&ch, s); ok { + v.CreatePolicy.Description = (r) + } + } + if !n.IsRef("libraries") { + if len(v.CreatePolicy.Libraries) > 0 { + cn := n.Child("libraries") + for i := range v.CreatePolicy.Libraries { + { + ch := cn.Elem(i) + stringsComputeLibrary(c, &ch, &v.CreatePolicy.Libraries[i]) + } + } + } + } + if c.mode == EmptyFields && v.CreatePolicy.Name == "" && Forced(v.CreatePolicy.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.CreatePolicy.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.CreatePolicy.Name = (r) + } + } + if c.mode == EmptyFields && v.CreatePolicy.PolicyFamilyId == "" && Forced(v.CreatePolicy.ForceSendFields, "PolicyFamilyId") { + ch := n.Child("policy_family_id") + c.fn(&ch, "") + } + if s := string(v.CreatePolicy.PolicyFamilyId); c.match(s) { + ch := n.Child("policy_family_id") + if r, ok := c.fn(&ch, s); ok { + v.CreatePolicy.PolicyFamilyId = (r) + } + } +} + +func stringsResourcesSnapshot(c *stringsWalk, n *Node, v *resources.Snapshot) { + if n.isRef() { + return + } + if s := string(v.BundleID); c.match(s) { + ch := n.Child("bundle_id") + if r, ok := c.fn(&ch, s); ok { + v.BundleID = (r) + } + } + if !n.IsRef("acl") { + if len(v.ACL) > 0 { + cn := n.Child("acl") + for i := range v.ACL { + { + ch := cn.Elem(i) + stringsSnapshotACLEntry(c, &ch, &v.ACL[i]) + } + } + } + } + if s := string(v.ZipPath); c.match(s) { + ch := n.Child("zip_path") + if r, ok := c.fn(&ch, s); ok { + v.ZipPath = (r) + } + } + if s := string(v.RemoteRoot); c.match(s) { + ch := n.Child("remote_root") + if r, ok := c.fn(&ch, s); ok { + v.RemoteRoot = (r) + } + } +} + +func stringsVariableTargetVariable(c *stringsWalk, n *Node, v *variable.TargetVariable) { + if n.isRef() { + return + } + if s := string(v.Type); c.match(s) { + ch := n.Child("type") + if r, ok := c.fn(&ch, s); ok { + v.Type = variable.VariableType(r) + } + } + if c.mode == EmptyFields && v.Default == "" { + ch := n.Child("default") + c.fn(&ch, "") + } + if v.Default != nil { + ch := n.Child("default") + c.any(&ch, &v.Default) + } + if s := string(v.Description); c.match(s) { + ch := n.Child("description") + if r, ok := c.fn(&ch, s); ok { + v.Description = (r) + } + } + if c.mode == EmptyFields && v.Value == "" { + ch := n.Child("value") + c.fn(&ch, "") + } + if v.Value != nil { + ch := n.Child("value") + c.any(&ch, &v.Value) + } + if !n.IsRef("lookup") { + if x := v.Lookup; x != nil { + ch := n.Child("lookup") + stringsVariableLookup(c, &ch, x) + } + } +} + +func stringsIamComplexValue(c *stringsWalk, n *Node, v *iam.ComplexValue) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Display == "" && Forced(v.ForceSendFields, "Display") { + ch := n.Child("display") + c.fn(&ch, "") + } + if s := string(v.Display); c.match(s) { + ch := n.Child("display") + if r, ok := c.fn(&ch, s); ok { + v.Display = (r) + } + } + if c.mode == EmptyFields && v.Ref == "" && Forced(v.ForceSendFields, "Ref") { + ch := n.Child("$ref") + c.fn(&ch, "") + } + if s := string(v.Ref); c.match(s) { + ch := n.Child("$ref") + if r, ok := c.fn(&ch, s); ok { + v.Ref = (r) + } + } + if c.mode == EmptyFields && v.Type == "" && Forced(v.ForceSendFields, "Type") { + ch := n.Child("type") + c.fn(&ch, "") + } + if s := string(v.Type); c.match(s) { + ch := n.Child("type") + if r, ok := c.fn(&ch, s); ok { + v.Type = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } +} + +func stringsIamName(c *stringsWalk, n *Node, v *iam.Name) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.FamilyName == "" && Forced(v.ForceSendFields, "FamilyName") { + ch := n.Child("familyName") + c.fn(&ch, "") + } + if s := string(v.FamilyName); c.match(s) { + ch := n.Child("familyName") + if r, ok := c.fn(&ch, s); ok { + v.FamilyName = (r) + } + } + if c.mode == EmptyFields && v.GivenName == "" && Forced(v.ForceSendFields, "GivenName") { + ch := n.Child("givenName") + c.fn(&ch, "") + } + if s := string(v.GivenName); c.match(s) { + ch := n.Child("givenName") + if r, ok := c.fn(&ch, s); ok { + v.GivenName = (r) + } + } +} + +func stringsResourcesJobPermission(c *stringsWalk, n *Node, v *resources.JobPermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = jobs.JobPermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsJobsContinuous(c *stringsWalk, n *Node, v *jobs.Continuous) { + if n.isRef() { + return + } + if !n.IsRef("maintenance_window") { + if x := v.MaintenanceWindow; x != nil { + ch := n.Child("maintenance_window") + stringsJobsMaintenanceWindow(c, &ch, x) + } + } + if s := string(v.PauseStatus); c.match(s) { + ch := n.Child("pause_status") + if r, ok := c.fn(&ch, s); ok { + v.PauseStatus = jobs.PauseStatus(r) + } + } + if s := string(v.TaskRetryMode); c.match(s) { + ch := n.Child("task_retry_mode") + if r, ok := c.fn(&ch, s); ok { + v.TaskRetryMode = jobs.TaskRetryMode(r) + } + } +} + +func stringsJobsJobDeployment(c *stringsWalk, n *Node, v *jobs.JobDeployment) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DeploymentId == "" && Forced(v.ForceSendFields, "DeploymentId") { + ch := n.Child("deployment_id") + c.fn(&ch, "") + } + if s := string(v.DeploymentId); c.match(s) { + ch := n.Child("deployment_id") + if r, ok := c.fn(&ch, s); ok { + v.DeploymentId = (r) + } + } + if s := string(v.Kind); c.match(s) { + ch := n.Child("kind") + if r, ok := c.fn(&ch, s); ok { + v.Kind = jobs.JobDeploymentKind(r) + } + } + if c.mode == EmptyFields && v.MetadataFilePath == "" && Forced(v.ForceSendFields, "MetadataFilePath") { + ch := n.Child("metadata_file_path") + c.fn(&ch, "") + } + if s := string(v.MetadataFilePath); c.match(s) { + ch := n.Child("metadata_file_path") + if r, ok := c.fn(&ch, s); ok { + v.MetadataFilePath = (r) + } + } + if c.mode == EmptyFields && v.VersionId == "" && Forced(v.ForceSendFields, "VersionId") { + ch := n.Child("version_id") + c.fn(&ch, "") + } + if s := string(v.VersionId); c.match(s) { + ch := n.Child("version_id") + if r, ok := c.fn(&ch, s); ok { + v.VersionId = (r) + } + } +} + +func stringsJobsJobEmailNotifications(c *stringsWalk, n *Node, v *jobs.JobEmailNotifications) { + if n.isRef() { + return + } + if !n.IsRef("on_duration_warning_threshold_exceeded") { + if len(v.OnDurationWarningThresholdExceeded) > 0 { + cn := n.Child("on_duration_warning_threshold_exceeded") + for i := range v.OnDurationWarningThresholdExceeded { + if s := string(v.OnDurationWarningThresholdExceeded[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnDurationWarningThresholdExceeded[i] = (r) + } + } + } + } + } + if !n.IsRef("on_failure") { + if len(v.OnFailure) > 0 { + cn := n.Child("on_failure") + for i := range v.OnFailure { + if s := string(v.OnFailure[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnFailure[i] = (r) + } + } + } + } + } + if !n.IsRef("on_maintenance_complete") { + if len(v.OnMaintenanceComplete) > 0 { + cn := n.Child("on_maintenance_complete") + for i := range v.OnMaintenanceComplete { + if s := string(v.OnMaintenanceComplete[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnMaintenanceComplete[i] = (r) + } + } + } + } + } + if !n.IsRef("on_maintenance_start") { + if len(v.OnMaintenanceStart) > 0 { + cn := n.Child("on_maintenance_start") + for i := range v.OnMaintenanceStart { + if s := string(v.OnMaintenanceStart[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnMaintenanceStart[i] = (r) + } + } + } + } + } + if !n.IsRef("on_start") { + if len(v.OnStart) > 0 { + cn := n.Child("on_start") + for i := range v.OnStart { + if s := string(v.OnStart[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnStart[i] = (r) + } + } + } + } + } + if !n.IsRef("on_streaming_backlog_exceeded") { + if len(v.OnStreamingBacklogExceeded) > 0 { + cn := n.Child("on_streaming_backlog_exceeded") + for i := range v.OnStreamingBacklogExceeded { + if s := string(v.OnStreamingBacklogExceeded[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnStreamingBacklogExceeded[i] = (r) + } + } + } + } + } + if !n.IsRef("on_success") { + if len(v.OnSuccess) > 0 { + cn := n.Child("on_success") + for i := range v.OnSuccess { + if s := string(v.OnSuccess[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnSuccess[i] = (r) + } + } + } + } + } +} + +func stringsJobsJobEnvironmentVariables(c *stringsWalk, n *Node, v *jobs.JobEnvironmentVariables) { + if n.isRef() { + return + } + if s := string(v.EnvironmentVariablesKey); c.match(s) { + ch := n.Child("environment_variables_key") + if r, ok := c.fn(&ch, s); ok { + v.EnvironmentVariablesKey = (r) + } + } + if !n.IsRef("spec") { + if x := v.Spec; x != nil { + ch := n.Child("spec") + stringsJobsJobEnvironmentVariablesSpec(c, &ch, x) + } + } +} + +func stringsJobsJobEnvironment(c *stringsWalk, n *Node, v *jobs.JobEnvironment) { + if n.isRef() { + return + } + if s := string(v.EnvironmentKey); c.match(s) { + ch := n.Child("environment_key") + if r, ok := c.fn(&ch, s); ok { + v.EnvironmentKey = (r) + } + } + if !n.IsRef("spec") { + if x := v.Spec; x != nil { + ch := n.Child("spec") + stringsComputeEnvironment(c, &ch, x) + } + } +} + +func stringsJobsGitSource(c *stringsWalk, n *Node, v *jobs.GitSource) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.GitBranch == "" && Forced(v.ForceSendFields, "GitBranch") { + ch := n.Child("git_branch") + c.fn(&ch, "") + } + if s := string(v.GitBranch); c.match(s) { + ch := n.Child("git_branch") + if r, ok := c.fn(&ch, s); ok { + v.GitBranch = (r) + } + } + if c.mode == EmptyFields && v.GitCommit == "" && Forced(v.ForceSendFields, "GitCommit") { + ch := n.Child("git_commit") + c.fn(&ch, "") + } + if s := string(v.GitCommit); c.match(s) { + ch := n.Child("git_commit") + if r, ok := c.fn(&ch, s); ok { + v.GitCommit = (r) + } + } + if s := string(v.GitProvider); c.match(s) { + ch := n.Child("git_provider") + if r, ok := c.fn(&ch, s); ok { + v.GitProvider = jobs.GitProvider(r) + } + } + if !n.IsRef("git_snapshot") { + if x := v.GitSnapshot; x != nil { + ch := n.Child("git_snapshot") + stringsJobsGitSnapshot(c, &ch, x) + } + } + if c.mode == EmptyFields && v.GitTag == "" && Forced(v.ForceSendFields, "GitTag") { + ch := n.Child("git_tag") + c.fn(&ch, "") + } + if s := string(v.GitTag); c.match(s) { + ch := n.Child("git_tag") + if r, ok := c.fn(&ch, s); ok { + v.GitTag = (r) + } + } + if s := string(v.GitUrl); c.match(s) { + ch := n.Child("git_url") + if r, ok := c.fn(&ch, s); ok { + v.GitUrl = (r) + } + } + if !n.IsRef("job_source") { + if x := v.JobSource; x != nil { + ch := n.Child("job_source") + stringsJobsJobSource(c, &ch, x) + } + } + if !n.IsRef("sparse_checkout") { + if x := v.SparseCheckout; x != nil { + ch := n.Child("sparse_checkout") + stringsJobsSparseCheckout(c, &ch, x) + } + } +} + +func stringsJobsJobsHealthRules(c *stringsWalk, n *Node, v *jobs.JobsHealthRules) { + if n.isRef() { + return + } + if !n.IsRef("rules") { + if len(v.Rules) > 0 { + cn := n.Child("rules") + for i := range v.Rules { + { + ch := cn.Elem(i) + stringsJobsJobsHealthRule(c, &ch, &v.Rules[i]) + } + } + } + } +} + +func stringsJobsJobCluster(c *stringsWalk, n *Node, v *jobs.JobCluster) { + if n.isRef() { + return + } + if s := string(v.JobClusterKey); c.match(s) { + ch := n.Child("job_cluster_key") + if r, ok := c.fn(&ch, s); ok { + v.JobClusterKey = (r) + } + } + if !n.IsRef("new_cluster") { + if x := v.NewCluster; x != nil { + ch := n.Child("new_cluster") + stringsComputeClusterSpec(c, &ch, x) + } + } + if c.mode == EmptyFields && v.ServerlessComputeId == "" && Forced(v.ForceSendFields, "ServerlessComputeId") { + ch := n.Child("serverless_compute_id") + c.fn(&ch, "") + } + if s := string(v.ServerlessComputeId); c.match(s) { + ch := n.Child("serverless_compute_id") + if r, ok := c.fn(&ch, s); ok { + v.ServerlessComputeId = (r) + } + } +} + +func stringsJobsJobParameterDefinition(c *stringsWalk, n *Node, v *jobs.JobParameterDefinition) { + if n.isRef() { + return + } + if s := string(v.Default); c.match(s) { + ch := n.Child("default") + if r, ok := c.fn(&ch, s); ok { + v.Default = (r) + } + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } +} + +func stringsJobsCronSchedule(c *stringsWalk, n *Node, v *jobs.CronSchedule) { + if n.isRef() { + return + } + if s := string(v.PauseStatus); c.match(s) { + ch := n.Child("pause_status") + if r, ok := c.fn(&ch, s); ok { + v.PauseStatus = jobs.PauseStatus(r) + } + } + if s := string(v.QuartzCronExpression); c.match(s) { + ch := n.Child("quartz_cron_expression") + if r, ok := c.fn(&ch, s); ok { + v.QuartzCronExpression = (r) + } + } + if !n.IsRef("sql_condition") { + if x := v.SqlCondition; x != nil { + ch := n.Child("sql_condition") + stringsJobsSqlConditionConfiguration(c, &ch, x) + } + } + if s := string(v.TimezoneId); c.match(s) { + ch := n.Child("timezone_id") + if r, ok := c.fn(&ch, s); ok { + v.TimezoneId = (r) + } + } +} + +func stringsJobsTask(c *stringsWalk, n *Node, v *jobs.Task) { + if n.isRef() { + return + } + if !n.IsRef("ai_runtime_task") { + if x := v.AiRuntimeTask; x != nil { + ch := n.Child("ai_runtime_task") + stringsJobsAiRuntimeTask(c, &ch, x) + } + } + if !n.IsRef("alert_task") { + if x := v.AlertTask; x != nil { + ch := n.Child("alert_task") + stringsJobsAlertTask(c, &ch, x) + } + } + if !n.IsRef("clean_rooms_notebook_task") { + if x := v.CleanRoomsNotebookTask; x != nil { + ch := n.Child("clean_rooms_notebook_task") + stringsJobsCleanRoomsNotebookTask(c, &ch, x) + } + } + if !n.IsRef("compute") { + if x := v.Compute; x != nil { + ch := n.Child("compute") + stringsJobsCompute(c, &ch, x) + } + } + if !n.IsRef("condition_task") { + if x := v.ConditionTask; x != nil { + ch := n.Child("condition_task") + stringsJobsConditionTask(c, &ch, x) + } + } + if !n.IsRef("dashboard_task") { + if x := v.DashboardTask; x != nil { + ch := n.Child("dashboard_task") + stringsJobsDashboardTask(c, &ch, x) + } + } + if !n.IsRef("dbt_cloud_task") { + if x := v.DbtCloudTask; x != nil { + ch := n.Child("dbt_cloud_task") + stringsJobsDbtCloudTask(c, &ch, x) + } + } + if !n.IsRef("dbt_platform_task") { + if x := v.DbtPlatformTask; x != nil { + ch := n.Child("dbt_platform_task") + stringsJobsDbtPlatformTask(c, &ch, x) + } + } + if !n.IsRef("dbt_task") { + if x := v.DbtTask; x != nil { + ch := n.Child("dbt_task") + stringsJobsDbtTask(c, &ch, x) + } + } + if !n.IsRef("depends_on") { + if len(v.DependsOn) > 0 { + cn := n.Child("depends_on") + for i := range v.DependsOn { + { + ch := cn.Elem(i) + stringsJobsTaskDependency(c, &ch, &v.DependsOn[i]) + } + } + } + } + if c.mode == EmptyFields && v.Description == "" && Forced(v.ForceSendFields, "Description") { + ch := n.Child("description") + c.fn(&ch, "") + } + if s := string(v.Description); c.match(s) { + ch := n.Child("description") + if r, ok := c.fn(&ch, s); ok { + v.Description = (r) + } + } + if !n.IsRef("email_notifications") { + if x := v.EmailNotifications; x != nil { + ch := n.Child("email_notifications") + stringsJobsTaskEmailNotifications(c, &ch, x) + } + } + if c.mode == EmptyFields && v.EnvironmentKey == "" && Forced(v.ForceSendFields, "EnvironmentKey") { + ch := n.Child("environment_key") + c.fn(&ch, "") + } + if s := string(v.EnvironmentKey); c.match(s) { + ch := n.Child("environment_key") + if r, ok := c.fn(&ch, s); ok { + v.EnvironmentKey = (r) + } + } + if c.mode == EmptyFields && v.EnvironmentVariablesKey == "" && Forced(v.ForceSendFields, "EnvironmentVariablesKey") { + ch := n.Child("environment_variables_key") + c.fn(&ch, "") + } + if s := string(v.EnvironmentVariablesKey); c.match(s) { + ch := n.Child("environment_variables_key") + if r, ok := c.fn(&ch, s); ok { + v.EnvironmentVariablesKey = (r) + } + } + if c.mode == EmptyFields && v.ExistingClusterId == "" && Forced(v.ForceSendFields, "ExistingClusterId") { + ch := n.Child("existing_cluster_id") + c.fn(&ch, "") + } + if s := string(v.ExistingClusterId); c.match(s) { + ch := n.Child("existing_cluster_id") + if r, ok := c.fn(&ch, s); ok { + v.ExistingClusterId = (r) + } + } + if !n.IsRef("for_each_task") { + if x := v.ForEachTask; x != nil { + ch := n.Child("for_each_task") + stringsJobsForEachTask(c, &ch, x) + } + } + if !n.IsRef("gen_ai_compute_task") { + if x := v.GenAiComputeTask; x != nil { + ch := n.Child("gen_ai_compute_task") + stringsJobsGenAiComputeTask(c, &ch, x) + } + } + if !n.IsRef("health") { + if x := v.Health; x != nil { + ch := n.Child("health") + stringsJobsJobsHealthRules(c, &ch, x) + } + } + if c.mode == EmptyFields && v.JobClusterKey == "" && Forced(v.ForceSendFields, "JobClusterKey") { + ch := n.Child("job_cluster_key") + c.fn(&ch, "") + } + if s := string(v.JobClusterKey); c.match(s) { + ch := n.Child("job_cluster_key") + if r, ok := c.fn(&ch, s); ok { + v.JobClusterKey = (r) + } + } + if !n.IsRef("libraries") { + if len(v.Libraries) > 0 { + cn := n.Child("libraries") + for i := range v.Libraries { + { + ch := cn.Elem(i) + stringsComputeLibrary(c, &ch, &v.Libraries[i]) + } + } + } + } + if !n.IsRef("new_cluster") { + if x := v.NewCluster; x != nil { + ch := n.Child("new_cluster") + stringsComputeClusterSpec(c, &ch, x) + } + } + if !n.IsRef("notebook_task") { + if x := v.NotebookTask; x != nil { + ch := n.Child("notebook_task") + stringsJobsNotebookTask(c, &ch, x) + } + } + if !n.IsRef("pipeline_task") { + if x := v.PipelineTask; x != nil { + ch := n.Child("pipeline_task") + stringsJobsPipelineTask(c, &ch, x) + } + } + if !n.IsRef("power_bi_task") { + if x := v.PowerBiTask; x != nil { + ch := n.Child("power_bi_task") + stringsJobsPowerBiTask(c, &ch, x) + } + } + if !n.IsRef("python_operator_task") { + if x := v.PythonOperatorTask; x != nil { + ch := n.Child("python_operator_task") + stringsJobsPythonOperatorTask(c, &ch, x) + } + } + if !n.IsRef("python_wheel_task") { + if x := v.PythonWheelTask; x != nil { + ch := n.Child("python_wheel_task") + stringsJobsPythonWheelTask(c, &ch, x) + } + } + if c.mode == EmptyFields && v.RunIf == "" && Forced(v.ForceSendFields, "RunIf") { + ch := n.Child("run_if") + c.fn(&ch, "") + } + if s := string(v.RunIf); c.match(s) { + ch := n.Child("run_if") + if r, ok := c.fn(&ch, s); ok { + v.RunIf = jobs.RunIf(r) + } + } + if !n.IsRef("run_job_task") { + if x := v.RunJobTask; x != nil { + ch := n.Child("run_job_task") + stringsJobsRunJobTask(c, &ch, x) + } + } + if !n.IsRef("spark_jar_task") { + if x := v.SparkJarTask; x != nil { + ch := n.Child("spark_jar_task") + stringsJobsSparkJarTask(c, &ch, x) + } + } + if !n.IsRef("spark_python_task") { + if x := v.SparkPythonTask; x != nil { + ch := n.Child("spark_python_task") + stringsJobsSparkPythonTask(c, &ch, x) + } + } + if !n.IsRef("spark_submit_task") { + if x := v.SparkSubmitTask; x != nil { + ch := n.Child("spark_submit_task") + stringsJobsSparkSubmitTask(c, &ch, x) + } + } + if !n.IsRef("sql_task") { + if x := v.SqlTask; x != nil { + ch := n.Child("sql_task") + stringsJobsSqlTask(c, &ch, x) + } + } + if s := string(v.TaskKey); c.match(s) { + ch := n.Child("task_key") + if r, ok := c.fn(&ch, s); ok { + v.TaskKey = (r) + } + } + if !n.IsRef("webhook_notifications") { + if x := v.WebhookNotifications; x != nil { + ch := n.Child("webhook_notifications") + stringsJobsWebhookNotifications(c, &ch, x) + } + } +} + +func stringsJobsTriggerSettings(c *stringsWalk, n *Node, v *jobs.TriggerSettings) { + if n.isRef() { + return + } + if !n.IsRef("file_arrival") { + if x := v.FileArrival; x != nil { + ch := n.Child("file_arrival") + stringsJobsFileArrivalTriggerConfiguration(c, &ch, x) + } + } + if !n.IsRef("model") { + if x := v.Model; x != nil { + ch := n.Child("model") + stringsJobsModelTriggerConfiguration(c, &ch, x) + } + } + if s := string(v.PauseStatus); c.match(s) { + ch := n.Child("pause_status") + if r, ok := c.fn(&ch, s); ok { + v.PauseStatus = jobs.PauseStatus(r) + } + } + if !n.IsRef("periodic") { + if x := v.Periodic; x != nil { + ch := n.Child("periodic") + stringsJobsPeriodicTriggerConfiguration(c, &ch, x) + } + } + if !n.IsRef("sql_condition") { + if x := v.SqlCondition; x != nil { + ch := n.Child("sql_condition") + stringsJobsSqlConditionConfiguration(c, &ch, x) + } + } + if !n.IsRef("table_update") { + if x := v.TableUpdate; x != nil { + ch := n.Child("table_update") + stringsJobsTableUpdateTriggerConfiguration(c, &ch, x) + } + } +} + +func stringsJobsTriggerConfiguration(c *stringsWalk, n *Node, v *jobs.TriggerConfiguration) { + if n.isRef() { + return + } + if !n.IsRef("continuous") { + if x := v.Continuous; x != nil { + ch := n.Child("continuous") + stringsJobsContinuousTriggerConfiguration(c, &ch, x) + } + } + if !n.IsRef("file_arrival") { + if x := v.FileArrival; x != nil { + ch := n.Child("file_arrival") + stringsJobsFileArrivalTriggerConfiguration(c, &ch, x) + } + } + if !n.IsRef("model") { + if x := v.Model; x != nil { + ch := n.Child("model") + stringsJobsModelTriggerConfiguration(c, &ch, x) + } + } + if s := string(v.PauseStatus); c.match(s) { + ch := n.Child("pause_status") + if r, ok := c.fn(&ch, s); ok { + v.PauseStatus = jobs.PauseStatus(r) + } + } + if !n.IsRef("periodic") { + if x := v.Periodic; x != nil { + ch := n.Child("periodic") + stringsJobsPeriodicTriggerConfiguration(c, &ch, x) + } + } + if !n.IsRef("schedule") { + if x := v.Schedule; x != nil { + ch := n.Child("schedule") + stringsJobsCronTriggerConfiguration(c, &ch, x) + } + } + if !n.IsRef("sql_condition") { + if x := v.SqlCondition; x != nil { + ch := n.Child("sql_condition") + stringsJobsSqlConditionConfiguration(c, &ch, x) + } + } + if !n.IsRef("table_update") { + if x := v.TableUpdate; x != nil { + ch := n.Child("table_update") + stringsJobsTableUpdateTriggerConfiguration(c, &ch, x) + } + } +} + +func stringsJobsWebhookNotifications(c *stringsWalk, n *Node, v *jobs.WebhookNotifications) { + if n.isRef() { + return + } + if !n.IsRef("on_duration_warning_threshold_exceeded") { + if len(v.OnDurationWarningThresholdExceeded) > 0 { + cn := n.Child("on_duration_warning_threshold_exceeded") + for i := range v.OnDurationWarningThresholdExceeded { + { + ch := cn.Elem(i) + stringsJobsWebhook(c, &ch, &v.OnDurationWarningThresholdExceeded[i]) + } + } + } + } + if !n.IsRef("on_failure") { + if len(v.OnFailure) > 0 { + cn := n.Child("on_failure") + for i := range v.OnFailure { + { + ch := cn.Elem(i) + stringsJobsWebhook(c, &ch, &v.OnFailure[i]) + } + } + } + } + if !n.IsRef("on_maintenance_complete") { + if len(v.OnMaintenanceComplete) > 0 { + cn := n.Child("on_maintenance_complete") + for i := range v.OnMaintenanceComplete { + { + ch := cn.Elem(i) + stringsJobsWebhook(c, &ch, &v.OnMaintenanceComplete[i]) + } + } + } + } + if !n.IsRef("on_maintenance_start") { + if len(v.OnMaintenanceStart) > 0 { + cn := n.Child("on_maintenance_start") + for i := range v.OnMaintenanceStart { + { + ch := cn.Elem(i) + stringsJobsWebhook(c, &ch, &v.OnMaintenanceStart[i]) + } + } + } + } + if !n.IsRef("on_start") { + if len(v.OnStart) > 0 { + cn := n.Child("on_start") + for i := range v.OnStart { + { + ch := cn.Elem(i) + stringsJobsWebhook(c, &ch, &v.OnStart[i]) + } + } + } + } + if !n.IsRef("on_streaming_backlog_exceeded") { + if len(v.OnStreamingBacklogExceeded) > 0 { + cn := n.Child("on_streaming_backlog_exceeded") + for i := range v.OnStreamingBacklogExceeded { + { + ch := cn.Elem(i) + stringsJobsWebhook(c, &ch, &v.OnStreamingBacklogExceeded[i]) + } + } + } + } + if !n.IsRef("on_success") { + if len(v.OnSuccess) > 0 { + cn := n.Child("on_success") + for i := range v.OnSuccess { + { + ch := cn.Elem(i) + stringsJobsWebhook(c, &ch, &v.OnSuccess[i]) + } + } + } + } +} + +func stringsResourcesJobRunLifecycle(c *stringsWalk, n *Node, v *resources.JobRunLifecycle) { + if n.isRef() { + return + } + if !n.IsRef("triggers") { + if len(v.Triggers) > 0 { + cn := n.Child("triggers") + for i := range v.Triggers { + { + ch := cn.Elem(i) + stringsResourcesJobRunTrigger(c, &ch, &v.Triggers[i]) + } + } + } + } + if !n.IsRef("triggers_state") { + if x := v.TriggersState; x != nil { + ch := n.Child("triggers_state") + stringsResourcesJobRunTriggersState(c, &ch, x) + } + } +} + +func stringsJobsPipelineParams(c *stringsWalk, n *Node, v *jobs.PipelineParams) { + if n.isRef() { + return + } + if !n.IsRef("full_refresh_selection") { + if len(v.FullRefreshSelection) > 0 { + cn := n.Child("full_refresh_selection") + for i := range v.FullRefreshSelection { + if s := string(v.FullRefreshSelection[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.FullRefreshSelection[i] = (r) + } + } + } + } + } + if !n.IsRef("refresh_flow_selection") { + if len(v.RefreshFlowSelection) > 0 { + cn := n.Child("refresh_flow_selection") + for i := range v.RefreshFlowSelection { + if s := string(v.RefreshFlowSelection[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.RefreshFlowSelection[i] = (r) + } + } + } + } + } + if !n.IsRef("refresh_selection") { + if len(v.RefreshSelection) > 0 { + cn := n.Child("refresh_selection") + for i := range v.RefreshSelection { + if s := string(v.RefreshSelection[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.RefreshSelection[i] = (r) + } + } + } + } + } + if !n.IsRef("reset_checkpoint_selection") { + if len(v.ResetCheckpointSelection) > 0 { + cn := n.Child("reset_checkpoint_selection") + for i := range v.ResetCheckpointSelection { + if s := string(v.ResetCheckpointSelection[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ResetCheckpointSelection[i] = (r) + } + } + } + } + } +} + +func stringsResourcesPipelinePermission(c *stringsWalk, n *Node, v *resources.PipelinePermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = pipelines.PipelinePermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsPipelinesPipelineCluster(c *stringsWalk, n *Node, v *pipelines.PipelineCluster) { + if n.isRef() { + return + } + if !n.IsRef("autoscale") { + if x := v.Autoscale; x != nil { + ch := n.Child("autoscale") + stringsPipelinesPipelineClusterAutoscale(c, &ch, x) + } + } + if !n.IsRef("aws_attributes") { + if x := v.AwsAttributes; x != nil { + ch := n.Child("aws_attributes") + stringsComputeAwsAttributes(c, &ch, x) + } + } + if !n.IsRef("azure_attributes") { + if x := v.AzureAttributes; x != nil { + ch := n.Child("azure_attributes") + stringsComputeAzureAttributes(c, &ch, x) + } + } + if !n.IsRef("cluster_log_conf") { + if x := v.ClusterLogConf; x != nil { + ch := n.Child("cluster_log_conf") + stringsComputeClusterLogConf(c, &ch, x) + } + } + if !n.IsRef("custom_tags") { + if len(v.CustomTags) > 0 { + cn := n.Child("custom_tags") + for _, k := range sortedKeys(v.CustomTags) { + if s := string(v.CustomTags[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.CustomTags[k] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.DriverInstancePoolId == "" && Forced(v.ForceSendFields, "DriverInstancePoolId") { + ch := n.Child("driver_instance_pool_id") + c.fn(&ch, "") + } + if s := string(v.DriverInstancePoolId); c.match(s) { + ch := n.Child("driver_instance_pool_id") + if r, ok := c.fn(&ch, s); ok { + v.DriverInstancePoolId = (r) + } + } + if c.mode == EmptyFields && v.DriverNodeTypeId == "" && Forced(v.ForceSendFields, "DriverNodeTypeId") { + ch := n.Child("driver_node_type_id") + c.fn(&ch, "") + } + if s := string(v.DriverNodeTypeId); c.match(s) { + ch := n.Child("driver_node_type_id") + if r, ok := c.fn(&ch, s); ok { + v.DriverNodeTypeId = (r) + } + } + if !n.IsRef("gcp_attributes") { + if x := v.GcpAttributes; x != nil { + ch := n.Child("gcp_attributes") + stringsComputeGcpAttributes(c, &ch, x) + } + } + if !n.IsRef("init_scripts") { + if len(v.InitScripts) > 0 { + cn := n.Child("init_scripts") + for i := range v.InitScripts { + { + ch := cn.Elem(i) + stringsComputeInitScriptInfo(c, &ch, &v.InitScripts[i]) + } + } + } + } + if c.mode == EmptyFields && v.InstancePoolId == "" && Forced(v.ForceSendFields, "InstancePoolId") { + ch := n.Child("instance_pool_id") + c.fn(&ch, "") + } + if s := string(v.InstancePoolId); c.match(s) { + ch := n.Child("instance_pool_id") + if r, ok := c.fn(&ch, s); ok { + v.InstancePoolId = (r) + } + } + if c.mode == EmptyFields && v.Label == "" && Forced(v.ForceSendFields, "Label") { + ch := n.Child("label") + c.fn(&ch, "") + } + if s := string(v.Label); c.match(s) { + ch := n.Child("label") + if r, ok := c.fn(&ch, s); ok { + v.Label = (r) + } + } + if c.mode == EmptyFields && v.NodeTypeId == "" && Forced(v.ForceSendFields, "NodeTypeId") { + ch := n.Child("node_type_id") + c.fn(&ch, "") + } + if s := string(v.NodeTypeId); c.match(s) { + ch := n.Child("node_type_id") + if r, ok := c.fn(&ch, s); ok { + v.NodeTypeId = (r) + } + } + if c.mode == EmptyFields && v.PolicyId == "" && Forced(v.ForceSendFields, "PolicyId") { + ch := n.Child("policy_id") + c.fn(&ch, "") + } + if s := string(v.PolicyId); c.match(s) { + ch := n.Child("policy_id") + if r, ok := c.fn(&ch, s); ok { + v.PolicyId = (r) + } + } + if !n.IsRef("spark_conf") { + if len(v.SparkConf) > 0 { + cn := n.Child("spark_conf") + for _, k := range sortedKeys(v.SparkConf) { + if s := string(v.SparkConf[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.SparkConf[k] = (r) + } + } + } + } + } + if !n.IsRef("spark_env_vars") { + if len(v.SparkEnvVars) > 0 { + cn := n.Child("spark_env_vars") + for _, k := range sortedKeys(v.SparkEnvVars) { + if s := string(v.SparkEnvVars[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.SparkEnvVars[k] = (r) + } + } + } + } + } + if !n.IsRef("ssh_public_keys") { + if len(v.SshPublicKeys) > 0 { + cn := n.Child("ssh_public_keys") + for i := range v.SshPublicKeys { + if s := string(v.SshPublicKeys[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.SshPublicKeys[i] = (r) + } + } + } + } + } +} + +func stringsPipelinesPipelineDeployment(c *stringsWalk, n *Node, v *pipelines.PipelineDeployment) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DeploymentId == "" && Forced(v.ForceSendFields, "DeploymentId") { + ch := n.Child("deployment_id") + c.fn(&ch, "") + } + if s := string(v.DeploymentId); c.match(s) { + ch := n.Child("deployment_id") + if r, ok := c.fn(&ch, s); ok { + v.DeploymentId = (r) + } + } + if s := string(v.Kind); c.match(s) { + ch := n.Child("kind") + if r, ok := c.fn(&ch, s); ok { + v.Kind = pipelines.DeploymentKind(r) + } + } + if c.mode == EmptyFields && v.MetadataFilePath == "" && Forced(v.ForceSendFields, "MetadataFilePath") { + ch := n.Child("metadata_file_path") + c.fn(&ch, "") + } + if s := string(v.MetadataFilePath); c.match(s) { + ch := n.Child("metadata_file_path") + if r, ok := c.fn(&ch, s); ok { + v.MetadataFilePath = (r) + } + } + if c.mode == EmptyFields && v.VersionId == "" && Forced(v.ForceSendFields, "VersionId") { + ch := n.Child("version_id") + c.fn(&ch, "") + } + if s := string(v.VersionId); c.match(s) { + ch := n.Child("version_id") + if r, ok := c.fn(&ch, s); ok { + v.VersionId = (r) + } + } +} + +func stringsPipelinesPipelinesEnvironment(c *stringsWalk, n *Node, v *pipelines.PipelinesEnvironment) { + if n.isRef() { + return + } + if !n.IsRef("dependencies") { + if len(v.Dependencies) > 0 { + cn := n.Child("dependencies") + for i := range v.Dependencies { + if s := string(v.Dependencies[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Dependencies[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.EnvironmentVersion == "" && Forced(v.ForceSendFields, "EnvironmentVersion") { + ch := n.Child("environment_version") + c.fn(&ch, "") + } + if s := string(v.EnvironmentVersion); c.match(s) { + ch := n.Child("environment_version") + if r, ok := c.fn(&ch, s); ok { + v.EnvironmentVersion = (r) + } + } +} + +func stringsPipelinesEventLogSpec(c *stringsWalk, n *Node, v *pipelines.EventLogSpec) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Catalog == "" && Forced(v.ForceSendFields, "Catalog") { + ch := n.Child("catalog") + c.fn(&ch, "") + } + if s := string(v.Catalog); c.match(s) { + ch := n.Child("catalog") + if r, ok := c.fn(&ch, s); ok { + v.Catalog = (r) + } + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if c.mode == EmptyFields && v.Schema == "" && Forced(v.ForceSendFields, "Schema") { + ch := n.Child("schema") + c.fn(&ch, "") + } + if s := string(v.Schema); c.match(s) { + ch := n.Child("schema") + if r, ok := c.fn(&ch, s); ok { + v.Schema = (r) + } + } +} + +func stringsPipelinesFilters(c *stringsWalk, n *Node, v *pipelines.Filters) { + if n.isRef() { + return + } + if !n.IsRef("exclude") { + if len(v.Exclude) > 0 { + cn := n.Child("exclude") + for i := range v.Exclude { + if s := string(v.Exclude[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Exclude[i] = (r) + } + } + } + } + } + if !n.IsRef("include") { + if len(v.Include) > 0 { + cn := n.Child("include") + for i := range v.Include { + if s := string(v.Include[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Include[i] = (r) + } + } + } + } + } +} + +func stringsPipelinesIngestionGatewayPipelineDefinition(c *stringsWalk, n *Node, v *pipelines.IngestionGatewayPipelineDefinition) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ConnectionId == "" && Forced(v.ForceSendFields, "ConnectionId") { + ch := n.Child("connection_id") + c.fn(&ch, "") + } + if s := string(v.ConnectionId); c.match(s) { + ch := n.Child("connection_id") + if r, ok := c.fn(&ch, s); ok { + v.ConnectionId = (r) + } + } + if s := string(v.ConnectionName); c.match(s) { + ch := n.Child("connection_name") + if r, ok := c.fn(&ch, s); ok { + v.ConnectionName = (r) + } + } + if !n.IsRef("connection_parameters") { + if x := v.ConnectionParameters; x != nil { + ch := n.Child("connection_parameters") + stringsPipelinesConnectionParameters(c, &ch, x) + } + } + if s := string(v.GatewayStorageCatalog); c.match(s) { + ch := n.Child("gateway_storage_catalog") + if r, ok := c.fn(&ch, s); ok { + v.GatewayStorageCatalog = (r) + } + } + if c.mode == EmptyFields && v.GatewayStorageName == "" && Forced(v.ForceSendFields, "GatewayStorageName") { + ch := n.Child("gateway_storage_name") + c.fn(&ch, "") + } + if s := string(v.GatewayStorageName); c.match(s) { + ch := n.Child("gateway_storage_name") + if r, ok := c.fn(&ch, s); ok { + v.GatewayStorageName = (r) + } + } + if s := string(v.GatewayStorageSchema); c.match(s) { + ch := n.Child("gateway_storage_schema") + if r, ok := c.fn(&ch, s); ok { + v.GatewayStorageSchema = (r) + } + } +} + +func stringsPipelinesIngestionPipelineDefinition(c *stringsWalk, n *Node, v *pipelines.IngestionPipelineDefinition) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ConnectionName == "" && Forced(v.ForceSendFields, "ConnectionName") { + ch := n.Child("connection_name") + c.fn(&ch, "") + } + if s := string(v.ConnectionName); c.match(s) { + ch := n.Child("connection_name") + if r, ok := c.fn(&ch, s); ok { + v.ConnectionName = (r) + } + } + if c.mode == EmptyFields && v.ConnectorType == "" && Forced(v.ForceSendFields, "ConnectorType") { + ch := n.Child("connector_type") + c.fn(&ch, "") + } + if s := string(v.ConnectorType); c.match(s) { + ch := n.Child("connector_type") + if r, ok := c.fn(&ch, s); ok { + v.ConnectorType = pipelines.ConnectorType(r) + } + } + if !n.IsRef("data_staging_options") { + if x := v.DataStagingOptions; x != nil { + ch := n.Child("data_staging_options") + stringsPipelinesDataStagingOptions(c, &ch, x) + } + } + if !n.IsRef("full_refresh_window") { + if x := v.FullRefreshWindow; x != nil { + ch := n.Child("full_refresh_window") + stringsPipelinesOperationTimeWindow(c, &ch, x) + } + } + if c.mode == EmptyFields && v.IngestionGatewayId == "" && Forced(v.ForceSendFields, "IngestionGatewayId") { + ch := n.Child("ingestion_gateway_id") + c.fn(&ch, "") + } + if s := string(v.IngestionGatewayId); c.match(s) { + ch := n.Child("ingestion_gateway_id") + if r, ok := c.fn(&ch, s); ok { + v.IngestionGatewayId = (r) + } + } + if c.mode == EmptyFields && v.NetsuiteJarPath == "" && Forced(v.ForceSendFields, "NetsuiteJarPath") { + ch := n.Child("netsuite_jar_path") + c.fn(&ch, "") + } + if s := string(v.NetsuiteJarPath); c.match(s) { + ch := n.Child("netsuite_jar_path") + if r, ok := c.fn(&ch, s); ok { + v.NetsuiteJarPath = (r) + } + } + if !n.IsRef("objects") { + if len(v.Objects) > 0 { + cn := n.Child("objects") + for i := range v.Objects { + { + ch := cn.Elem(i) + stringsPipelinesIngestionConfig(c, &ch, &v.Objects[i]) + } + } + } + } + if !n.IsRef("source_configurations") { + if len(v.SourceConfigurations) > 0 { + cn := n.Child("source_configurations") + for i := range v.SourceConfigurations { + { + ch := cn.Elem(i) + stringsPipelinesSourceConfig(c, &ch, &v.SourceConfigurations[i]) + } + } + } + } + if c.mode == EmptyFields && v.SourceType == "" && Forced(v.ForceSendFields, "SourceType") { + ch := n.Child("source_type") + c.fn(&ch, "") + } + if s := string(v.SourceType); c.match(s) { + ch := n.Child("source_type") + if r, ok := c.fn(&ch, s); ok { + v.SourceType = pipelines.IngestionSourceType(r) + } + } + if !n.IsRef("table_configuration") { + if x := v.TableConfiguration; x != nil { + ch := n.Child("table_configuration") + stringsPipelinesTableSpecificConfig(c, &ch, x) + } + } +} + +func stringsPipelinesPipelineLibrary(c *stringsWalk, n *Node, v *pipelines.PipelineLibrary) { + if n.isRef() { + return + } + if !n.IsRef("file") { + if x := v.File; x != nil { + ch := n.Child("file") + stringsPipelinesFileLibrary(c, &ch, x) + } + } + if !n.IsRef("glob") { + if x := v.Glob; x != nil { + ch := n.Child("glob") + stringsPipelinesPathPattern(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Jar == "" && Forced(v.ForceSendFields, "Jar") { + ch := n.Child("jar") + c.fn(&ch, "") + } + if s := string(v.Jar); c.match(s) { + ch := n.Child("jar") + if r, ok := c.fn(&ch, s); ok { + v.Jar = (r) + } + } + if !n.IsRef("maven") { + if x := v.Maven; x != nil { + ch := n.Child("maven") + stringsComputeMavenLibrary(c, &ch, x) + } + } + if !n.IsRef("notebook") { + if x := v.Notebook; x != nil { + ch := n.Child("notebook") + stringsPipelinesNotebookLibrary(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Whl == "" && Forced(v.ForceSendFields, "Whl") { + ch := n.Child("whl") + c.fn(&ch, "") + } + if s := string(v.Whl); c.match(s) { + ch := n.Child("whl") + if r, ok := c.fn(&ch, s); ok { + v.Whl = (r) + } + } +} + +func stringsPipelinesNotifications(c *stringsWalk, n *Node, v *pipelines.Notifications) { + if n.isRef() { + return + } + if !n.IsRef("alerts") { + if len(v.Alerts) > 0 { + cn := n.Child("alerts") + for i := range v.Alerts { + if s := string(v.Alerts[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Alerts[i] = (r) + } + } + } + } + } + if !n.IsRef("email_recipients") { + if len(v.EmailRecipients) > 0 { + cn := n.Child("email_recipients") + for i := range v.EmailRecipients { + if s := string(v.EmailRecipients[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.EmailRecipients[i] = (r) + } + } + } + } + } +} + +func stringsPipelinesRestartWindow(c *stringsWalk, n *Node, v *pipelines.RestartWindow) { + if n.isRef() { + return + } + if !n.IsRef("days_of_week") { + if len(v.DaysOfWeek) > 0 { + cn := n.Child("days_of_week") + for i := range v.DaysOfWeek { + if s := string(v.DaysOfWeek[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.DaysOfWeek[i] = pipelines.DayOfWeek(r) + } + } + } + } + } + if c.mode == EmptyFields && v.TimeZoneId == "" && Forced(v.ForceSendFields, "TimeZoneId") { + ch := n.Child("time_zone_id") + c.fn(&ch, "") + } + if s := string(v.TimeZoneId); c.match(s) { + ch := n.Child("time_zone_id") + if r, ok := c.fn(&ch, s); ok { + v.TimeZoneId = (r) + } + } +} + +func stringsPipelinesRunAs(c *stringsWalk, n *Node, v *pipelines.RunAs) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.GroupName == "" && Forced(v.ForceSendFields, "GroupName") { + ch := n.Child("group_name") + c.fn(&ch, "") + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } + if c.mode == EmptyFields && v.ServicePrincipalName == "" && Forced(v.ForceSendFields, "ServicePrincipalName") { + ch := n.Child("service_principal_name") + c.fn(&ch, "") + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if c.mode == EmptyFields && v.UserName == "" && Forced(v.ForceSendFields, "UserName") { + ch := n.Child("user_name") + c.fn(&ch, "") + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } +} + +func stringsPipelinesPipelineTrigger(c *stringsWalk, n *Node, v *pipelines.PipelineTrigger) { + if n.isRef() { + return + } + if !n.IsRef("cron") { + if x := v.Cron; x != nil { + ch := n.Child("cron") + stringsPipelinesCronTrigger(c, &ch, x) + } + } +} + +func stringsMlEntityColumn(c *stringsWalk, n *Node, v *ml.EntityColumn) { + if n.isRef() { + return + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } +} + +func stringsMlFunction(c *stringsWalk, n *Node, v *ml.Function) { + if n.isRef() { + return + } + if !n.IsRef("aggregation_function") { + if x := v.AggregationFunction; x != nil { + ch := n.Child("aggregation_function") + stringsMlAggregationFunction(c, &ch, x) + } + } + if !n.IsRef("column_selection") { + if x := v.ColumnSelection; x != nil { + ch := n.Child("column_selection") + stringsMlColumnSelection(c, &ch, x) + } + } + if !n.IsRef("custom_udf") { + if x := v.CustomUdf; x != nil { + ch := n.Child("custom_udf") + stringsMlCustomUdf(c, &ch, x) + } + } + if !n.IsRef("extra_parameters") { + if len(v.ExtraParameters) > 0 { + cn := n.Child("extra_parameters") + for i := range v.ExtraParameters { + { + ch := cn.Elem(i) + stringsMlFunctionExtraParameter(c, &ch, &v.ExtraParameters[i]) + } + } + } + } + if s := string(v.FunctionType); c.match(s) { + ch := n.Child("function_type") + if r, ok := c.fn(&ch, s); ok { + v.FunctionType = ml.FunctionFunctionType(r) + } + } +} + +func stringsMlDataSource(c *stringsWalk, n *Node, v *ml.DataSource) { + if n.isRef() { + return + } + if !n.IsRef("delta_table_source") { + if x := v.DeltaTableSource; x != nil { + ch := n.Child("delta_table_source") + stringsMlDeltaTableSource(c, &ch, x) + } + } + if !n.IsRef("feature_view_source") { + if x := v.FeatureViewSource; x != nil { + ch := n.Child("feature_view_source") + stringsMlFeatureViewSource(c, &ch, x) + } + } + if !n.IsRef("kafka_source") { + if x := v.KafkaSource; x != nil { + ch := n.Child("kafka_source") + stringsMlKafkaSource(c, &ch, x) + } + } + if !n.IsRef("request_source") { + if x := v.RequestSource; x != nil { + ch := n.Child("request_source") + stringsMlRequestSource(c, &ch, x) + } + } + if !n.IsRef("sdp_source") { + if x := v.SdpSource; x != nil { + ch := n.Child("sdp_source") + stringsMlSdpSource(c, &ch, x) + } + } + if !n.IsRef("stream_source") { + if x := v.StreamSource; x != nil { + ch := n.Child("stream_source") + stringsMlStreamSource(c, &ch, x) + } + } +} + +func stringsMlTimeWindow(c *stringsWalk, n *Node, v *ml.TimeWindow) { + if n.isRef() { + return + } + if !n.IsRef("continuous") { + if x := v.Continuous; x != nil { + ch := n.Child("continuous") + stringsMlContinuousWindow(c, &ch, x) + } + } + if !n.IsRef("sliding") { + if x := v.Sliding; x != nil { + ch := n.Child("sliding") + stringsMlSlidingWindow(c, &ch, x) + } + } + if !n.IsRef("tumbling") { + if x := v.Tumbling; x != nil { + ch := n.Child("tumbling") + stringsMlTumblingWindow(c, &ch, x) + } + } +} + +func stringsMlTimeseriesColumn(c *stringsWalk, n *Node, v *ml.TimeseriesColumn) { + if n.isRef() { + return + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } +} + +func stringsResourcesMlflowModelPermission(c *stringsWalk, n *Node, v *resources.MlflowModelPermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = ml.RegisteredModelPermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsMlModelTag(c *stringsWalk, n *Node, v *ml.ModelTag) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Key == "" && Forced(v.ForceSendFields, "Key") { + ch := n.Child("key") + c.fn(&ch, "") + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } +} + +func stringsResourcesMlflowExperimentPermission(c *stringsWalk, n *Node, v *resources.MlflowExperimentPermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = ml.ExperimentPermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsMlExperimentTag(c *stringsWalk, n *Node, v *ml.ExperimentTag) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Key == "" && Forced(v.ForceSendFields, "Key") { + ch := n.Child("key") + c.fn(&ch, "") + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } +} + +func stringsMlExperimentTraceLocation(c *stringsWalk, n *Node, v *ml.ExperimentTraceLocation) { + if n.isRef() { + return + } + if !n.IsRef("uc_trace_location") { + if x := v.UcTraceLocation; x != nil { + ch := n.Child("uc_trace_location") + stringsMlUcTraceLocation(c, &ch, x) + } + } +} + +func stringsResourcesModelServingEndpointPermission(c *stringsWalk, n *Node, v *resources.ModelServingEndpointPermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = serving.ServingEndpointPermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsServingAiGatewayConfig(c *stringsWalk, n *Node, v *serving.AiGatewayConfig) { + if n.isRef() { + return + } + if !n.IsRef("guardrails") { + if x := v.Guardrails; x != nil { + ch := n.Child("guardrails") + stringsServingAiGatewayGuardrails(c, &ch, x) + } + } + if !n.IsRef("inference_table_config") { + if x := v.InferenceTableConfig; x != nil { + ch := n.Child("inference_table_config") + stringsServingAiGatewayInferenceTableConfig(c, &ch, x) + } + } + if !n.IsRef("rate_limits") { + if len(v.RateLimits) > 0 { + cn := n.Child("rate_limits") + for i := range v.RateLimits { + { + ch := cn.Elem(i) + stringsServingAiGatewayRateLimit(c, &ch, &v.RateLimits[i]) + } + } + } + } +} + +func stringsServingEndpointCoreConfigInput(c *stringsWalk, n *Node, v *serving.EndpointCoreConfigInput) { + if n.isRef() { + return + } + if !n.IsRef("auto_capture_config") { + if x := v.AutoCaptureConfig; x != nil { + ch := n.Child("auto_capture_config") + stringsServingAutoCaptureConfigInput(c, &ch, x) + } + } + if !n.IsRef("served_entities") { + if len(v.ServedEntities) > 0 { + cn := n.Child("served_entities") + for i := range v.ServedEntities { + { + ch := cn.Elem(i) + stringsServingServedEntityInput(c, &ch, &v.ServedEntities[i]) + } + } + } + } + if !n.IsRef("served_models") { + if len(v.ServedModels) > 0 { + cn := n.Child("served_models") + for i := range v.ServedModels { + { + ch := cn.Elem(i) + stringsServingServedModelInput(c, &ch, &v.ServedModels[i]) + } + } + } + } + if !n.IsRef("traffic_config") { + if x := v.TrafficConfig; x != nil { + ch := n.Child("traffic_config") + stringsServingTrafficConfig(c, &ch, x) + } + } +} + +func stringsServingEmailNotifications(c *stringsWalk, n *Node, v *serving.EmailNotifications) { + if n.isRef() { + return + } + if !n.IsRef("on_update_failure") { + if len(v.OnUpdateFailure) > 0 { + cn := n.Child("on_update_failure") + for i := range v.OnUpdateFailure { + if s := string(v.OnUpdateFailure[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnUpdateFailure[i] = (r) + } + } + } + } + } + if !n.IsRef("on_update_success") { + if len(v.OnUpdateSuccess) > 0 { + cn := n.Child("on_update_success") + for i := range v.OnUpdateSuccess { + if s := string(v.OnUpdateSuccess[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnUpdateSuccess[i] = (r) + } + } + } + } + } +} + +func stringsServingRateLimit(c *stringsWalk, n *Node, v *serving.RateLimit) { + if n.isRef() { + return + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = serving.RateLimitKey(r) + } + } + if s := string(v.RenewalPeriod); c.match(s) { + ch := n.Child("renewal_period") + if r, ok := c.fn(&ch, s); ok { + v.RenewalPeriod = serving.RateLimitRenewalPeriod(r) + } + } +} + +func stringsServingEndpointTag(c *stringsWalk, n *Node, v *serving.EndpointTag) { + if n.isRef() { + return + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } +} + +func stringsServingTelemetryConfig(c *stringsWalk, n *Node, v *serving.TelemetryConfig) { + if n.isRef() { + return + } + if !n.IsRef("enabled_telemetry_features") { + if len(v.EnabledTelemetryFeatures) > 0 { + cn := n.Child("enabled_telemetry_features") + for i := range v.EnabledTelemetryFeatures { + if s := string(v.EnabledTelemetryFeatures[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.EnabledTelemetryFeatures[i] = serving.TelemetryFeature(r) + } + } + } + } + } + if !n.IsRef("inference_table_config") { + if x := v.InferenceTableConfig; x != nil { + ch := n.Child("inference_table_config") + stringsServingTelemetryInferenceTableConfig(c, &ch, x) + } + } + if !n.IsRef("table_names") { + if x := v.TableNames; x != nil { + ch := n.Child("table_names") + stringsServingUnityCatalogTableNames(c, &ch, x) + } + } + if c.mode == EmptyFields && v.TelemetryProfileId == "" && Forced(v.ForceSendFields, "TelemetryProfileId") { + ch := n.Child("telemetry_profile_id") + c.fn(&ch, "") + } + if s := string(v.TelemetryProfileId); c.match(s) { + ch := n.Child("telemetry_profile_id") + if r, ok := c.fn(&ch, s); ok { + v.TelemetryProfileId = (r) + } + } +} + +func stringsCatalogPrivilegeAssignment(c *stringsWalk, n *Node, v *catalog.PrivilegeAssignment) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Principal == "" && Forced(v.ForceSendFields, "Principal") { + ch := n.Child("principal") + c.fn(&ch, "") + } + if s := string(v.Principal); c.match(s) { + ch := n.Child("principal") + if r, ok := c.fn(&ch, s); ok { + v.Principal = (r) + } + } + if !n.IsRef("privileges") { + if len(v.Privileges) > 0 { + cn := n.Child("privileges") + for i := range v.Privileges { + if s := string(v.Privileges[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Privileges[i] = catalog.Privilege(r) + } + } + } + } + } +} + +func stringsCatalogModelServiceConfig(c *stringsWalk, n *Node, v *catalog.ModelServiceConfig) { + if n.isRef() { + return + } + if !n.IsRef("inference_table") { + if x := v.InferenceTable; x != nil { + ch := n.Child("inference_table") + stringsCatalogInferenceTableConfig(c, &ch, x) + } + } + if !n.IsRef("rate_limits") { + if len(v.RateLimits) > 0 { + cn := n.Child("rate_limits") + for i := range v.RateLimits { + { + ch := cn.Elem(i) + stringsCatalogRateLimit(c, &ch, &v.RateLimits[i]) + } + } + } + } + if !n.IsRef("routing") { + if x := v.Routing; x != nil { + ch := n.Child("routing") + stringsCatalogModelServiceConfigRoutingConfig(c, &ch, x) + } + } +} + +func stringsCatalogMcpServiceConfig(c *stringsWalk, n *Node, v *catalog.McpServiceConfig) { + if n.isRef() { + return + } + if !n.IsRef("include_tool_selectors") { + if len(v.IncludeToolSelectors) > 0 { + cn := n.Child("include_tool_selectors") + for i := range v.IncludeToolSelectors { + if s := string(v.IncludeToolSelectors[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.IncludeToolSelectors[i] = (r) + } + } + } + } + } + if !n.IsRef("rate_limits") { + if len(v.RateLimits) > 0 { + cn := n.Child("rate_limits") + for i := range v.RateLimits { + { + ch := cn.Elem(i) + stringsCatalogRateLimit(c, &ch, &v.RateLimits[i]) + } + } + } + } + if !n.IsRef("source_connection") { + if x := v.SourceConnection; x != nil { + ch := n.Child("source_connection") + stringsCatalogMcpServiceConfigSourceConnection(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfig) { + if n.isRef() { + return + } + if !n.IsRef("amazon_bedrock") { + if x := v.AmazonBedrock; x != nil { + ch := n.Child("amazon_bedrock") + stringsCatalogModelProviderServiceConfigAmazonBedrockProviderConfig(c, &ch, x) + } + } + if !n.IsRef("anthropic") { + if x := v.Anthropic; x != nil { + ch := n.Child("anthropic") + stringsCatalogModelProviderServiceConfigAnthropicProviderConfig(c, &ch, x) + } + } + if !n.IsRef("azure_openai") { + if x := v.AzureOpenai; x != nil { + ch := n.Child("azure_openai") + stringsCatalogModelProviderServiceConfigAzureOpenAiProviderConfig(c, &ch, x) + } + } + if !n.IsRef("bedrock_mantle") { + if x := v.BedrockMantle; x != nil { + ch := n.Child("bedrock_mantle") + stringsCatalogModelProviderServiceConfigAmazonBedrockProviderConfig(c, &ch, x) + } + } + if !n.IsRef("custom") { + if x := v.Custom; x != nil { + ch := n.Child("custom") + stringsCatalogModelProviderServiceConfigCustomProviderConfig(c, &ch, x) + } + } + if !n.IsRef("gemini_enterprise") { + if x := v.GeminiEnterprise; x != nil { + ch := n.Child("gemini_enterprise") + stringsCatalogModelProviderServiceConfigGeminiEnterpriseProviderConfig(c, &ch, x) + } + } + if !n.IsRef("inference_table") { + if x := v.InferenceTable; x != nil { + ch := n.Child("inference_table") + stringsCatalogInferenceTableConfig(c, &ch, x) + } + } + if !n.IsRef("microsoft_foundry") { + if x := v.MicrosoftFoundry; x != nil { + ch := n.Child("microsoft_foundry") + stringsCatalogModelProviderServiceConfigMicrosoftFoundryProviderConfig(c, &ch, x) + } + } + if !n.IsRef("openai") { + if x := v.Openai; x != nil { + ch := n.Child("openai") + stringsCatalogModelProviderServiceConfigOpenAiProviderConfig(c, &ch, x) + } + } + if c.mode == EmptyFields && v.ProviderType == "" && Forced(v.ForceSendFields, "ProviderType") { + ch := n.Child("provider_type") + c.fn(&ch, "") + } + if s := string(v.ProviderType); c.match(s) { + ch := n.Child("provider_type") + if r, ok := c.fn(&ch, s); ok { + v.ProviderType = catalog.ModelProviderServiceConfigExternalModelProviderType(r) + } + } + if !n.IsRef("rate_limits") { + if len(v.RateLimits) > 0 { + cn := n.Child("rate_limits") + for i := range v.RateLimits { + { + ch := cn.Elem(i) + stringsCatalogRateLimit(c, &ch, &v.RateLimits[i]) + } + } + } + } + if !n.IsRef("targets") { + if len(v.Targets) > 0 { + cn := n.Child("targets") + for i := range v.Targets { + { + ch := cn.Elem(i) + stringsCatalogModelProviderServiceConfigModelTargetConfig(c, &ch, &v.Targets[i]) + } + } + } + } +} + +func stringsCatalogRegisteredModelAlias(c *stringsWalk, n *Node, v *catalog.RegisteredModelAlias) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.AliasName == "" && Forced(v.ForceSendFields, "AliasName") { + ch := n.Child("alias_name") + c.fn(&ch, "") + } + if s := string(v.AliasName); c.match(s) { + ch := n.Child("alias_name") + if r, ok := c.fn(&ch, s); ok { + v.AliasName = (r) + } + } + if c.mode == EmptyFields && v.CatalogName == "" && Forced(v.ForceSendFields, "CatalogName") { + ch := n.Child("catalog_name") + c.fn(&ch, "") + } + if s := string(v.CatalogName); c.match(s) { + ch := n.Child("catalog_name") + if r, ok := c.fn(&ch, s); ok { + v.CatalogName = (r) + } + } + if c.mode == EmptyFields && v.Id == "" && Forced(v.ForceSendFields, "Id") { + ch := n.Child("id") + c.fn(&ch, "") + } + if s := string(v.Id); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.Id = (r) + } + } + if c.mode == EmptyFields && v.ModelName == "" && Forced(v.ForceSendFields, "ModelName") { + ch := n.Child("model_name") + c.fn(&ch, "") + } + if s := string(v.ModelName); c.match(s) { + ch := n.Child("model_name") + if r, ok := c.fn(&ch, s); ok { + v.ModelName = (r) + } + } + if c.mode == EmptyFields && v.SchemaName == "" && Forced(v.ForceSendFields, "SchemaName") { + ch := n.Child("schema_name") + c.fn(&ch, "") + } + if s := string(v.SchemaName); c.match(s) { + ch := n.Child("schema_name") + if r, ok := c.fn(&ch, s); ok { + v.SchemaName = (r) + } + } +} + +func stringsCatalogMonitorMetric(c *stringsWalk, n *Node, v *catalog.MonitorMetric) { + if n.isRef() { + return + } + if s := string(v.Definition); c.match(s) { + ch := n.Child("definition") + if r, ok := c.fn(&ch, s); ok { + v.Definition = (r) + } + } + if !n.IsRef("input_columns") { + if len(v.InputColumns) > 0 { + cn := n.Child("input_columns") + for i := range v.InputColumns { + if s := string(v.InputColumns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.InputColumns[i] = (r) + } + } + } + } + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if s := string(v.OutputDataType); c.match(s) { + ch := n.Child("output_data_type") + if r, ok := c.fn(&ch, s); ok { + v.OutputDataType = (r) + } + } + if s := string(v.Type); c.match(s) { + ch := n.Child("type") + if r, ok := c.fn(&ch, s); ok { + v.Type = catalog.MonitorMetricType(r) + } + } +} + +func stringsCatalogMonitorInferenceLog(c *stringsWalk, n *Node, v *catalog.MonitorInferenceLog) { + if n.isRef() { + return + } + if !n.IsRef("granularities") { + if len(v.Granularities) > 0 { + cn := n.Child("granularities") + for i := range v.Granularities { + if s := string(v.Granularities[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Granularities[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.LabelCol == "" && Forced(v.ForceSendFields, "LabelCol") { + ch := n.Child("label_col") + c.fn(&ch, "") + } + if s := string(v.LabelCol); c.match(s) { + ch := n.Child("label_col") + if r, ok := c.fn(&ch, s); ok { + v.LabelCol = (r) + } + } + if s := string(v.ModelIdCol); c.match(s) { + ch := n.Child("model_id_col") + if r, ok := c.fn(&ch, s); ok { + v.ModelIdCol = (r) + } + } + if s := string(v.PredictionCol); c.match(s) { + ch := n.Child("prediction_col") + if r, ok := c.fn(&ch, s); ok { + v.PredictionCol = (r) + } + } + if c.mode == EmptyFields && v.PredictionProbaCol == "" && Forced(v.ForceSendFields, "PredictionProbaCol") { + ch := n.Child("prediction_proba_col") + c.fn(&ch, "") + } + if s := string(v.PredictionProbaCol); c.match(s) { + ch := n.Child("prediction_proba_col") + if r, ok := c.fn(&ch, s); ok { + v.PredictionProbaCol = (r) + } + } + if s := string(v.ProblemType); c.match(s) { + ch := n.Child("problem_type") + if r, ok := c.fn(&ch, s); ok { + v.ProblemType = catalog.MonitorInferenceLogProblemType(r) + } + } + if s := string(v.TimestampCol); c.match(s) { + ch := n.Child("timestamp_col") + if r, ok := c.fn(&ch, s); ok { + v.TimestampCol = (r) + } + } +} + +func stringsCatalogMonitorNotifications(c *stringsWalk, n *Node, v *catalog.MonitorNotifications) { + if n.isRef() { + return + } + if !n.IsRef("on_failure") { + if x := v.OnFailure; x != nil { + ch := n.Child("on_failure") + stringsCatalogMonitorDestination(c, &ch, x) + } + } + if !n.IsRef("on_new_classification_tag_detected") { + if x := v.OnNewClassificationTagDetected; x != nil { + ch := n.Child("on_new_classification_tag_detected") + stringsCatalogMonitorDestination(c, &ch, x) + } + } +} + +func stringsCatalogMonitorCronSchedule(c *stringsWalk, n *Node, v *catalog.MonitorCronSchedule) { + if n.isRef() { + return + } + if s := string(v.PauseStatus); c.match(s) { + ch := n.Child("pause_status") + if r, ok := c.fn(&ch, s); ok { + v.PauseStatus = catalog.MonitorCronSchedulePauseStatus(r) + } + } + if s := string(v.QuartzCronExpression); c.match(s) { + ch := n.Child("quartz_cron_expression") + if r, ok := c.fn(&ch, s); ok { + v.QuartzCronExpression = (r) + } + } + if s := string(v.TimezoneId); c.match(s) { + ch := n.Child("timezone_id") + if r, ok := c.fn(&ch, s); ok { + v.TimezoneId = (r) + } + } +} + +func stringsCatalogMonitorTimeSeries(c *stringsWalk, n *Node, v *catalog.MonitorTimeSeries) { + if n.isRef() { + return + } + if !n.IsRef("granularities") { + if len(v.Granularities) > 0 { + cn := n.Child("granularities") + for i := range v.Granularities { + if s := string(v.Granularities[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Granularities[i] = (r) + } + } + } + } + } + if s := string(v.TimestampCol); c.match(s) { + ch := n.Child("timestamp_col") + if r, ok := c.fn(&ch, s); ok { + v.TimestampCol = (r) + } + } +} + +func stringsCatalogEncryptionSettings(c *stringsWalk, n *Node, v *catalog.EncryptionSettings) { + if n.isRef() { + return + } + if !n.IsRef("azure_encryption_settings") { + if x := v.AzureEncryptionSettings; x != nil { + ch := n.Child("azure_encryption_settings") + stringsCatalogAzureEncryptionSettings(c, &ch, x) + } + } + if c.mode == EmptyFields && v.AzureKeyVaultKeyId == "" && Forced(v.ForceSendFields, "AzureKeyVaultKeyId") { + ch := n.Child("azure_key_vault_key_id") + c.fn(&ch, "") + } + if s := string(v.AzureKeyVaultKeyId); c.match(s) { + ch := n.Child("azure_key_vault_key_id") + if r, ok := c.fn(&ch, s); ok { + v.AzureKeyVaultKeyId = (r) + } + } + if c.mode == EmptyFields && v.CustomerManagedKeyId == "" && Forced(v.ForceSendFields, "CustomerManagedKeyId") { + ch := n.Child("customer_managed_key_id") + c.fn(&ch, "") + } + if s := string(v.CustomerManagedKeyId); c.match(s) { + ch := n.Child("customer_managed_key_id") + if r, ok := c.fn(&ch, s); ok { + v.CustomerManagedKeyId = (r) + } + } +} + +func stringsCatalogFileEventQueue(c *stringsWalk, n *Node, v *catalog.FileEventQueue) { + if n.isRef() { + return + } + if !n.IsRef("managed_aqs") { + if x := v.ManagedAqs; x != nil { + ch := n.Child("managed_aqs") + stringsCatalogAzureQueueStorage(c, &ch, x) + } + } + if !n.IsRef("managed_pubsub") { + if x := v.ManagedPubsub; x != nil { + ch := n.Child("managed_pubsub") + stringsCatalogGcpPubsub(c, &ch, x) + } + } + if !n.IsRef("managed_sqs") { + if x := v.ManagedSqs; x != nil { + ch := n.Child("managed_sqs") + stringsCatalogAwsSqsQueue(c, &ch, x) + } + } + if !n.IsRef("provided_aqs") { + if x := v.ProvidedAqs; x != nil { + ch := n.Child("provided_aqs") + stringsCatalogAzureQueueStorage(c, &ch, x) + } + } + if !n.IsRef("provided_pubsub") { + if x := v.ProvidedPubsub; x != nil { + ch := n.Child("provided_pubsub") + stringsCatalogGcpPubsub(c, &ch, x) + } + } + if !n.IsRef("provided_sqs") { + if x := v.ProvidedSqs; x != nil { + ch := n.Child("provided_sqs") + stringsCatalogAwsSqsQueue(c, &ch, x) + } + } +} + +func stringsCatalogEncryptionDetails(c *stringsWalk, n *Node, v *catalog.EncryptionDetails) { + if n.isRef() { + return + } + if !n.IsRef("sse_encryption_details") { + if x := v.SseEncryptionDetails; x != nil { + ch := n.Child("sse_encryption_details") + stringsCatalogSseEncryptionDetails(c, &ch, x) + } + } +} + +func stringsComputeLibrary(c *stringsWalk, n *Node, v *compute.Library) { + if n.isRef() { + return + } + if !n.IsRef("cran") { + if x := v.Cran; x != nil { + ch := n.Child("cran") + stringsComputeRCranLibrary(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Egg == "" && Forced(v.ForceSendFields, "Egg") { + ch := n.Child("egg") + c.fn(&ch, "") + } + if s := string(v.Egg); c.match(s) { + ch := n.Child("egg") + if r, ok := c.fn(&ch, s); ok { + v.Egg = (r) + } + } + if c.mode == EmptyFields && v.Jar == "" && Forced(v.ForceSendFields, "Jar") { + ch := n.Child("jar") + c.fn(&ch, "") + } + if s := string(v.Jar); c.match(s) { + ch := n.Child("jar") + if r, ok := c.fn(&ch, s); ok { + v.Jar = (r) + } + } + if !n.IsRef("maven") { + if x := v.Maven; x != nil { + ch := n.Child("maven") + stringsComputeMavenLibrary(c, &ch, x) + } + } + if !n.IsRef("pypi") { + if x := v.Pypi; x != nil { + ch := n.Child("pypi") + stringsComputePythonPyPiLibrary(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Requirements == "" && Forced(v.ForceSendFields, "Requirements") { + ch := n.Child("requirements") + c.fn(&ch, "") + } + if s := string(v.Requirements); c.match(s) { + ch := n.Child("requirements") + if r, ok := c.fn(&ch, s); ok { + v.Requirements = (r) + } + } + if c.mode == EmptyFields && v.Whl == "" && Forced(v.ForceSendFields, "Whl") { + ch := n.Child("whl") + c.fn(&ch, "") + } + if s := string(v.Whl); c.match(s) { + ch := n.Child("whl") + if r, ok := c.fn(&ch, s); ok { + v.Whl = (r) + } + } +} + +func stringsResourcesClusterPermission(c *stringsWalk, n *Node, v *resources.ClusterPermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = compute.ClusterPermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsComputeAwsAttributes(c *stringsWalk, n *Node, v *compute.AwsAttributes) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Availability == "" && Forced(v.ForceSendFields, "Availability") { + ch := n.Child("availability") + c.fn(&ch, "") + } + if s := string(v.Availability); c.match(s) { + ch := n.Child("availability") + if r, ok := c.fn(&ch, s); ok { + v.Availability = compute.AwsAvailability(r) + } + } + if c.mode == EmptyFields && v.EbsVolumeType == "" && Forced(v.ForceSendFields, "EbsVolumeType") { + ch := n.Child("ebs_volume_type") + c.fn(&ch, "") + } + if s := string(v.EbsVolumeType); c.match(s) { + ch := n.Child("ebs_volume_type") + if r, ok := c.fn(&ch, s); ok { + v.EbsVolumeType = compute.EbsVolumeType(r) + } + } + if c.mode == EmptyFields && v.InstanceProfileArn == "" && Forced(v.ForceSendFields, "InstanceProfileArn") { + ch := n.Child("instance_profile_arn") + c.fn(&ch, "") + } + if s := string(v.InstanceProfileArn); c.match(s) { + ch := n.Child("instance_profile_arn") + if r, ok := c.fn(&ch, s); ok { + v.InstanceProfileArn = (r) + } + } + if c.mode == EmptyFields && v.ZoneId == "" && Forced(v.ForceSendFields, "ZoneId") { + ch := n.Child("zone_id") + c.fn(&ch, "") + } + if s := string(v.ZoneId); c.match(s) { + ch := n.Child("zone_id") + if r, ok := c.fn(&ch, s); ok { + v.ZoneId = (r) + } + } +} + +func stringsComputeAzureAttributes(c *stringsWalk, n *Node, v *compute.AzureAttributes) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Availability == "" && Forced(v.ForceSendFields, "Availability") { + ch := n.Child("availability") + c.fn(&ch, "") + } + if s := string(v.Availability); c.match(s) { + ch := n.Child("availability") + if r, ok := c.fn(&ch, s); ok { + v.Availability = compute.AzureAvailability(r) + } + } + if c.mode == EmptyFields && v.CapacityReservationGroup == "" && Forced(v.ForceSendFields, "CapacityReservationGroup") { + ch := n.Child("capacity_reservation_group") + c.fn(&ch, "") + } + if s := string(v.CapacityReservationGroup); c.match(s) { + ch := n.Child("capacity_reservation_group") + if r, ok := c.fn(&ch, s); ok { + v.CapacityReservationGroup = (r) + } + } + if !n.IsRef("log_analytics_info") { + if x := v.LogAnalyticsInfo; x != nil { + ch := n.Child("log_analytics_info") + stringsComputeLogAnalyticsInfo(c, &ch, x) + } + } +} + +func stringsComputeClusterLogConf(c *stringsWalk, n *Node, v *compute.ClusterLogConf) { + if n.isRef() { + return + } + if !n.IsRef("dbfs") { + if x := v.Dbfs; x != nil { + ch := n.Child("dbfs") + stringsComputeDbfsStorageInfo(c, &ch, x) + } + } + if !n.IsRef("s3") { + if x := v.S3; x != nil { + ch := n.Child("s3") + stringsComputeS3StorageInfo(c, &ch, x) + } + } + if !n.IsRef("volumes") { + if x := v.Volumes; x != nil { + ch := n.Child("volumes") + stringsComputeVolumesStorageInfo(c, &ch, x) + } + } +} + +func stringsComputeDockerImage(c *stringsWalk, n *Node, v *compute.DockerImage) { + if n.isRef() { + return + } + if !n.IsRef("basic_auth") { + if x := v.BasicAuth; x != nil { + ch := n.Child("basic_auth") + stringsComputeDockerBasicAuth(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Url == "" && Forced(v.ForceSendFields, "Url") { + ch := n.Child("url") + c.fn(&ch, "") + } + if s := string(v.Url); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.Url = (r) + } + } +} + +func stringsComputeNodeTypeFlexibility(c *stringsWalk, n *Node, v *compute.NodeTypeFlexibility) { + if n.isRef() { + return + } + if !n.IsRef("alternate_node_type_ids") { + if len(v.AlternateNodeTypeIds) > 0 { + cn := n.Child("alternate_node_type_ids") + for i := range v.AlternateNodeTypeIds { + if s := string(v.AlternateNodeTypeIds[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.AlternateNodeTypeIds[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.AwsContextId == "" && Forced(v.ForceSendFields, "AwsContextId") { + ch := n.Child("aws_context_id") + c.fn(&ch, "") + } + if s := string(v.AwsContextId); c.match(s) { + ch := n.Child("aws_context_id") + if r, ok := c.fn(&ch, s); ok { + v.AwsContextId = (r) + } + } +} + +func stringsComputeGcpAttributes(c *stringsWalk, n *Node, v *compute.GcpAttributes) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Availability == "" && Forced(v.ForceSendFields, "Availability") { + ch := n.Child("availability") + c.fn(&ch, "") + } + if s := string(v.Availability); c.match(s) { + ch := n.Child("availability") + if r, ok := c.fn(&ch, s); ok { + v.Availability = compute.GcpAvailability(r) + } + } + if c.mode == EmptyFields && v.ConfidentialComputeType == "" && Forced(v.ForceSendFields, "ConfidentialComputeType") { + ch := n.Child("confidential_compute_type") + c.fn(&ch, "") + } + if s := string(v.ConfidentialComputeType); c.match(s) { + ch := n.Child("confidential_compute_type") + if r, ok := c.fn(&ch, s); ok { + v.ConfidentialComputeType = compute.ConfidentialComputeType(r) + } + } + if c.mode == EmptyFields && v.GoogleServiceAccount == "" && Forced(v.ForceSendFields, "GoogleServiceAccount") { + ch := n.Child("google_service_account") + c.fn(&ch, "") + } + if s := string(v.GoogleServiceAccount); c.match(s) { + ch := n.Child("google_service_account") + if r, ok := c.fn(&ch, s); ok { + v.GoogleServiceAccount = (r) + } + } + if c.mode == EmptyFields && v.ZoneId == "" && Forced(v.ForceSendFields, "ZoneId") { + ch := n.Child("zone_id") + c.fn(&ch, "") + } + if s := string(v.ZoneId); c.match(s) { + ch := n.Child("zone_id") + if r, ok := c.fn(&ch, s); ok { + v.ZoneId = (r) + } + } +} + +func stringsComputeInitScriptInfo(c *stringsWalk, n *Node, v *compute.InitScriptInfo) { + if n.isRef() { + return + } + if !n.IsRef("abfss") { + if x := v.Abfss; x != nil { + ch := n.Child("abfss") + stringsComputeAdlsgen2Info(c, &ch, x) + } + } + if !n.IsRef("dbfs") { + if x := v.Dbfs; x != nil { + ch := n.Child("dbfs") + stringsComputeDbfsStorageInfo(c, &ch, x) + } + } + if !n.IsRef("file") { + if x := v.File; x != nil { + ch := n.Child("file") + stringsComputeLocalFileInfo(c, &ch, x) + } + } + if !n.IsRef("gcs") { + if x := v.Gcs; x != nil { + ch := n.Child("gcs") + stringsComputeGcsStorageInfo(c, &ch, x) + } + } + if !n.IsRef("s3") { + if x := v.S3; x != nil { + ch := n.Child("s3") + stringsComputeS3StorageInfo(c, &ch, x) + } + } + if !n.IsRef("volumes") { + if x := v.Volumes; x != nil { + ch := n.Child("volumes") + stringsComputeVolumesStorageInfo(c, &ch, x) + } + } + if !n.IsRef("workspace") { + if x := v.Workspace; x != nil { + ch := n.Child("workspace") + stringsComputeWorkspaceStorageInfo(c, &ch, x) + } + } +} + +func stringsResourcesAppConfig(c *stringsWalk, n *Node, v *resources.AppConfig) { + if n.isRef() { + return + } + if !n.IsRef("command") { + if len(v.Command) > 0 { + cn := n.Child("command") + for i := range v.Command { + if s := string(v.Command[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Command[i] = (r) + } + } + } + } + } + if !n.IsRef("env") { + if len(v.Env) > 0 { + cn := n.Child("env") + for i := range v.Env { + { + ch := cn.Elem(i) + stringsResourcesAppEnvVar(c, &ch, &v.Env[i]) + } + } + } + } +} + +func stringsResourcesAppPermission(c *stringsWalk, n *Node, v *resources.AppPermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = apps.AppPermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsAppsAppDeployment(c *stringsWalk, n *Node, v *apps.AppDeployment) { + if n.isRef() { + return + } + if !n.IsRef("command") { + if len(v.Command) > 0 { + cn := n.Child("command") + for i := range v.Command { + if s := string(v.Command[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Command[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.CreateTime == "" && Forced(v.ForceSendFields, "CreateTime") { + ch := n.Child("create_time") + c.fn(&ch, "") + } + if s := string(v.CreateTime); c.match(s) { + ch := n.Child("create_time") + if r, ok := c.fn(&ch, s); ok { + v.CreateTime = (r) + } + } + if c.mode == EmptyFields && v.Creator == "" && Forced(v.ForceSendFields, "Creator") { + ch := n.Child("creator") + c.fn(&ch, "") + } + if s := string(v.Creator); c.match(s) { + ch := n.Child("creator") + if r, ok := c.fn(&ch, s); ok { + v.Creator = (r) + } + } + if !n.IsRef("deployment_artifacts") { + if x := v.DeploymentArtifacts; x != nil { + ch := n.Child("deployment_artifacts") + stringsAppsAppDeploymentArtifacts(c, &ch, x) + } + } + if c.mode == EmptyFields && v.DeploymentId == "" && Forced(v.ForceSendFields, "DeploymentId") { + ch := n.Child("deployment_id") + c.fn(&ch, "") + } + if s := string(v.DeploymentId); c.match(s) { + ch := n.Child("deployment_id") + if r, ok := c.fn(&ch, s); ok { + v.DeploymentId = (r) + } + } + if !n.IsRef("env_vars") { + if len(v.EnvVars) > 0 { + cn := n.Child("env_vars") + for i := range v.EnvVars { + { + ch := cn.Elem(i) + stringsAppsEnvVar(c, &ch, &v.EnvVars[i]) + } + } + } + } + if !n.IsRef("git_source") { + if x := v.GitSource; x != nil { + ch := n.Child("git_source") + stringsAppsGitSource(c, &ch, x) + } + } + if !n.IsRef("health_check") { + if x := v.HealthCheck; x != nil { + ch := n.Child("health_check") + stringsAppsAppHealthCheck(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Mode == "" && Forced(v.ForceSendFields, "Mode") { + ch := n.Child("mode") + c.fn(&ch, "") + } + if s := string(v.Mode); c.match(s) { + ch := n.Child("mode") + if r, ok := c.fn(&ch, s); ok { + v.Mode = apps.AppDeploymentMode(r) + } + } + if c.mode == EmptyFields && v.SourceCodePath == "" && Forced(v.ForceSendFields, "SourceCodePath") { + ch := n.Child("source_code_path") + c.fn(&ch, "") + } + if s := string(v.SourceCodePath); c.match(s) { + ch := n.Child("source_code_path") + if r, ok := c.fn(&ch, s); ok { + v.SourceCodePath = (r) + } + } + if !n.IsRef("status") { + if x := v.Status; x != nil { + ch := n.Child("status") + stringsAppsAppDeploymentStatus(c, &ch, x) + } + } + if c.mode == EmptyFields && v.UpdateTime == "" && Forced(v.ForceSendFields, "UpdateTime") { + ch := n.Child("update_time") + c.fn(&ch, "") + } + if s := string(v.UpdateTime); c.match(s) { + ch := n.Child("update_time") + if r, ok := c.fn(&ch, s); ok { + v.UpdateTime = (r) + } + } +} + +func stringsAppsApplicationStatus(c *stringsWalk, n *Node, v *apps.ApplicationStatus) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Message == "" && Forced(v.ForceSendFields, "Message") { + ch := n.Child("message") + c.fn(&ch, "") + } + if s := string(v.Message); c.match(s) { + ch := n.Child("message") + if r, ok := c.fn(&ch, s); ok { + v.Message = (r) + } + } + if c.mode == EmptyFields && v.State == "" && Forced(v.ForceSendFields, "State") { + ch := n.Child("state") + c.fn(&ch, "") + } + if s := string(v.State); c.match(s) { + ch := n.Child("state") + if r, ok := c.fn(&ch, s); ok { + v.State = apps.ApplicationState(r) + } + } +} + +func stringsAppsComputeStatus(c *stringsWalk, n *Node, v *apps.ComputeStatus) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Message == "" && Forced(v.ForceSendFields, "Message") { + ch := n.Child("message") + c.fn(&ch, "") + } + if s := string(v.Message); c.match(s) { + ch := n.Child("message") + if r, ok := c.fn(&ch, s); ok { + v.Message = (r) + } + } + if c.mode == EmptyFields && v.State == "" && Forced(v.ForceSendFields, "State") { + ch := n.Child("state") + c.fn(&ch, "") + } + if s := string(v.State); c.match(s) { + ch := n.Child("state") + if r, ok := c.fn(&ch, s); ok { + v.State = apps.ComputeState(r) + } + } +} + +func stringsAppsGitSource(c *stringsWalk, n *Node, v *apps.GitSource) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Branch == "" && Forced(v.ForceSendFields, "Branch") { + ch := n.Child("branch") + c.fn(&ch, "") + } + if s := string(v.Branch); c.match(s) { + ch := n.Child("branch") + if r, ok := c.fn(&ch, s); ok { + v.Branch = (r) + } + } + if c.mode == EmptyFields && v.Commit == "" && Forced(v.ForceSendFields, "Commit") { + ch := n.Child("commit") + c.fn(&ch, "") + } + if s := string(v.Commit); c.match(s) { + ch := n.Child("commit") + if r, ok := c.fn(&ch, s); ok { + v.Commit = (r) + } + } + if !n.IsRef("git_repository") { + if x := v.GitRepository; x != nil { + ch := n.Child("git_repository") + stringsAppsGitRepository(c, &ch, x) + } + } + if c.mode == EmptyFields && v.ResolvedCommit == "" && Forced(v.ForceSendFields, "ResolvedCommit") { + ch := n.Child("resolved_commit") + c.fn(&ch, "") + } + if s := string(v.ResolvedCommit); c.match(s) { + ch := n.Child("resolved_commit") + if r, ok := c.fn(&ch, s); ok { + v.ResolvedCommit = (r) + } + } + if c.mode == EmptyFields && v.SourceCodePath == "" && Forced(v.ForceSendFields, "SourceCodePath") { + ch := n.Child("source_code_path") + c.fn(&ch, "") + } + if s := string(v.SourceCodePath); c.match(s) { + ch := n.Child("source_code_path") + if r, ok := c.fn(&ch, s); ok { + v.SourceCodePath = (r) + } + } + if c.mode == EmptyFields && v.Tag == "" && Forced(v.ForceSendFields, "Tag") { + ch := n.Child("tag") + c.fn(&ch, "") + } + if s := string(v.Tag); c.match(s) { + ch := n.Child("tag") + if r, ok := c.fn(&ch, s); ok { + v.Tag = (r) + } + } +} + +func stringsAppsGitRepository(c *stringsWalk, n *Node, v *apps.GitRepository) { + if n.isRef() { + return + } + if s := string(v.Provider); c.match(s) { + ch := n.Child("provider") + if r, ok := c.fn(&ch, s); ok { + v.Provider = (r) + } + } + if s := string(v.Url); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.Url = (r) + } + } +} + +func stringsAppsAppResource(c *stringsWalk, n *Node, v *apps.AppResource) { + if n.isRef() { + return + } + if !n.IsRef("app") { + if x := v.App; x != nil { + ch := n.Child("app") + stringsAppsAppResourceApp(c, &ch, x) + } + } + if !n.IsRef("database") { + if x := v.Database; x != nil { + ch := n.Child("database") + stringsAppsAppResourceDatabase(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Description == "" && Forced(v.ForceSendFields, "Description") { + ch := n.Child("description") + c.fn(&ch, "") + } + if s := string(v.Description); c.match(s) { + ch := n.Child("description") + if r, ok := c.fn(&ch, s); ok { + v.Description = (r) + } + } + if !n.IsRef("experiment") { + if x := v.Experiment; x != nil { + ch := n.Child("experiment") + stringsAppsAppResourceExperiment(c, &ch, x) + } + } + if !n.IsRef("genie_space") { + if x := v.GenieSpace; x != nil { + ch := n.Child("genie_space") + stringsAppsAppResourceGenieSpace(c, &ch, x) + } + } + if !n.IsRef("job") { + if x := v.Job; x != nil { + ch := n.Child("job") + stringsAppsAppResourceJob(c, &ch, x) + } + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if !n.IsRef("postgres") { + if x := v.Postgres; x != nil { + ch := n.Child("postgres") + stringsAppsAppResourcePostgres(c, &ch, x) + } + } + if !n.IsRef("secret") { + if x := v.Secret; x != nil { + ch := n.Child("secret") + stringsAppsAppResourceSecret(c, &ch, x) + } + } + if !n.IsRef("serving_endpoint") { + if x := v.ServingEndpoint; x != nil { + ch := n.Child("serving_endpoint") + stringsAppsAppResourceServingEndpoint(c, &ch, x) + } + } + if !n.IsRef("sql_warehouse") { + if x := v.SqlWarehouse; x != nil { + ch := n.Child("sql_warehouse") + stringsAppsAppResourceSqlWarehouse(c, &ch, x) + } + } + if !n.IsRef("uc_securable") { + if x := v.UcSecurable; x != nil { + ch := n.Child("uc_securable") + stringsAppsAppResourceUcSecurable(c, &ch, x) + } + } +} + +func stringsAppsTelemetryExportDestination(c *stringsWalk, n *Node, v *apps.TelemetryExportDestination) { + if n.isRef() { + return + } + if !n.IsRef("unity_catalog") { + if x := v.UnityCatalog; x != nil { + ch := n.Child("unity_catalog") + stringsAppsUnityCatalog(c, &ch, x) + } + } +} + +func stringsResourcesSecretScopePermission(c *stringsWalk, n *Node, v *resources.SecretScopePermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = resources.SecretScopePermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsWorkspaceAzureKeyVaultSecretScopeMetadata(c *stringsWalk, n *Node, v *workspace.AzureKeyVaultSecretScopeMetadata) { + if n.isRef() { + return + } + if s := string(v.DnsName); c.match(s) { + ch := n.Child("dns_name") + if r, ok := c.fn(&ch, s); ok { + v.DnsName = (r) + } + } + if s := string(v.ResourceId); c.match(s) { + ch := n.Child("resource_id") + if r, ok := c.fn(&ch, s); ok { + v.ResourceId = (r) + } + } +} + +func stringsSqlAlertV2RunAs(c *stringsWalk, n *Node, v *sql.AlertV2RunAs) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ServicePrincipalName == "" && Forced(v.ForceSendFields, "ServicePrincipalName") { + ch := n.Child("service_principal_name") + c.fn(&ch, "") + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if c.mode == EmptyFields && v.UserName == "" && Forced(v.ForceSendFields, "UserName") { + ch := n.Child("user_name") + c.fn(&ch, "") + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } +} + +func stringsSqlAlertV2Evaluation(c *stringsWalk, n *Node, v *sql.AlertV2Evaluation) { + if n.isRef() { + return + } + if s := string(v.ComparisonOperator); c.match(s) { + ch := n.Child("comparison_operator") + if r, ok := c.fn(&ch, s); ok { + v.ComparisonOperator = sql.ComparisonOperator(r) + } + } + if c.mode == EmptyFields && v.EmptyResultState == "" && Forced(v.ForceSendFields, "EmptyResultState") { + ch := n.Child("empty_result_state") + c.fn(&ch, "") + } + if s := string(v.EmptyResultState); c.match(s) { + ch := n.Child("empty_result_state") + if r, ok := c.fn(&ch, s); ok { + v.EmptyResultState = sql.AlertEvaluationState(r) + } + } + if c.mode == EmptyFields && v.LastEvaluatedAt == "" && Forced(v.ForceSendFields, "LastEvaluatedAt") { + ch := n.Child("last_evaluated_at") + c.fn(&ch, "") + } + if s := string(v.LastEvaluatedAt); c.match(s) { + ch := n.Child("last_evaluated_at") + if r, ok := c.fn(&ch, s); ok { + v.LastEvaluatedAt = (r) + } + } + if !n.IsRef("notification") { + if x := v.Notification; x != nil { + ch := n.Child("notification") + stringsSqlAlertV2Notification(c, &ch, x) + } + } + if !n.IsRef("source") { + { + ch := n.Child("source") + stringsSqlAlertV2OperandColumn(c, &ch, &v.Source) + } + } + if c.mode == EmptyFields && v.State == "" && Forced(v.ForceSendFields, "State") { + ch := n.Child("state") + c.fn(&ch, "") + } + if s := string(v.State); c.match(s) { + ch := n.Child("state") + if r, ok := c.fn(&ch, s); ok { + v.State = sql.AlertEvaluationState(r) + } + } + if !n.IsRef("threshold") { + if x := v.Threshold; x != nil { + ch := n.Child("threshold") + stringsSqlAlertV2Operand(c, &ch, x) + } + } +} + +func stringsSqlAlertStatementParameter(c *stringsWalk, n *Node, v *sql.AlertStatementParameter) { + if n.isRef() { + return + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if c.mode == EmptyFields && v.Type == "" && Forced(v.ForceSendFields, "Type") { + ch := n.Child("type") + c.fn(&ch, "") + } + if s := string(v.Type); c.match(s) { + ch := n.Child("type") + if r, ok := c.fn(&ch, s); ok { + v.Type = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } +} + +func stringsSqlCronSchedule(c *stringsWalk, n *Node, v *sql.CronSchedule) { + if n.isRef() { + return + } + if s := string(v.PauseStatus); c.match(s) { + ch := n.Child("pause_status") + if r, ok := c.fn(&ch, s); ok { + v.PauseStatus = sql.SchedulePauseStatus(r) + } + } + if s := string(v.QuartzCronSchedule); c.match(s) { + ch := n.Child("quartz_cron_schedule") + if r, ok := c.fn(&ch, s); ok { + v.QuartzCronSchedule = (r) + } + } + if s := string(v.TimezoneId); c.match(s) { + ch := n.Child("timezone_id") + if r, ok := c.fn(&ch, s); ok { + v.TimezoneId = (r) + } + } +} + +func stringsResourcesSqlWarehousePermission(c *stringsWalk, n *Node, v *resources.SqlWarehousePermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = sql.WarehousePermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsSqlChannel(c *stringsWalk, n *Node, v *sql.Channel) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DbsqlVersion == "" && Forced(v.ForceSendFields, "DbsqlVersion") { + ch := n.Child("dbsql_version") + c.fn(&ch, "") + } + if s := string(v.DbsqlVersion); c.match(s) { + ch := n.Child("dbsql_version") + if r, ok := c.fn(&ch, s); ok { + v.DbsqlVersion = (r) + } + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = sql.ChannelName(r) + } + } +} + +func stringsSqlEndpointTags(c *stringsWalk, n *Node, v *sql.EndpointTags) { + if n.isRef() { + return + } + if !n.IsRef("custom_tags") { + if len(v.CustomTags) > 0 { + cn := n.Child("custom_tags") + for i := range v.CustomTags { + { + ch := cn.Elem(i) + stringsSqlEndpointTagPair(c, &ch, &v.CustomTags[i]) + } + } + } + } +} + +func stringsDatabaseDatabaseInstanceRef(c *stringsWalk, n *Node, v *database.DatabaseInstanceRef) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.BranchTime == "" && Forced(v.ForceSendFields, "BranchTime") { + ch := n.Child("branch_time") + c.fn(&ch, "") + } + if s := string(v.BranchTime); c.match(s) { + ch := n.Child("branch_time") + if r, ok := c.fn(&ch, s); ok { + v.BranchTime = (r) + } + } + if c.mode == EmptyFields && v.EffectiveLsn == "" && Forced(v.ForceSendFields, "EffectiveLsn") { + ch := n.Child("effective_lsn") + c.fn(&ch, "") + } + if s := string(v.EffectiveLsn); c.match(s) { + ch := n.Child("effective_lsn") + if r, ok := c.fn(&ch, s); ok { + v.EffectiveLsn = (r) + } + } + if c.mode == EmptyFields && v.Lsn == "" && Forced(v.ForceSendFields, "Lsn") { + ch := n.Child("lsn") + c.fn(&ch, "") + } + if s := string(v.Lsn); c.match(s) { + ch := n.Child("lsn") + if r, ok := c.fn(&ch, s); ok { + v.Lsn = (r) + } + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if c.mode == EmptyFields && v.Uid == "" && Forced(v.ForceSendFields, "Uid") { + ch := n.Child("uid") + c.fn(&ch, "") + } + if s := string(v.Uid); c.match(s) { + ch := n.Child("uid") + if r, ok := c.fn(&ch, s); ok { + v.Uid = (r) + } + } +} + +func stringsDatabaseCustomTag(c *stringsWalk, n *Node, v *database.CustomTag) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Key == "" && Forced(v.ForceSendFields, "Key") { + ch := n.Child("key") + c.fn(&ch, "") + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } +} + +func stringsDatabaseSyncedTableStatus(c *stringsWalk, n *Node, v *database.SyncedTableStatus) { + if n.isRef() { + return + } + if !n.IsRef("continuous_update_status") { + if x := v.ContinuousUpdateStatus; x != nil { + ch := n.Child("continuous_update_status") + stringsDatabaseSyncedTableContinuousUpdateStatus(c, &ch, x) + } + } + if c.mode == EmptyFields && v.DetailedState == "" && Forced(v.ForceSendFields, "DetailedState") { + ch := n.Child("detailed_state") + c.fn(&ch, "") + } + if s := string(v.DetailedState); c.match(s) { + ch := n.Child("detailed_state") + if r, ok := c.fn(&ch, s); ok { + v.DetailedState = database.SyncedTableState(r) + } + } + if !n.IsRef("failed_status") { + if x := v.FailedStatus; x != nil { + ch := n.Child("failed_status") + stringsDatabaseSyncedTableFailedStatus(c, &ch, x) + } + } + if !n.IsRef("last_sync") { + if x := v.LastSync; x != nil { + ch := n.Child("last_sync") + stringsDatabaseSyncedTablePosition(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Message == "" && Forced(v.ForceSendFields, "Message") { + ch := n.Child("message") + c.fn(&ch, "") + } + if s := string(v.Message); c.match(s) { + ch := n.Child("message") + if r, ok := c.fn(&ch, s); ok { + v.Message = (r) + } + } + if c.mode == EmptyFields && v.PipelineId == "" && Forced(v.ForceSendFields, "PipelineId") { + ch := n.Child("pipeline_id") + c.fn(&ch, "") + } + if s := string(v.PipelineId); c.match(s) { + ch := n.Child("pipeline_id") + if r, ok := c.fn(&ch, s); ok { + v.PipelineId = (r) + } + } + if !n.IsRef("provisioning_status") { + if x := v.ProvisioningStatus; x != nil { + ch := n.Child("provisioning_status") + stringsDatabaseSyncedTableProvisioningStatus(c, &ch, x) + } + } + if !n.IsRef("triggered_update_status") { + if x := v.TriggeredUpdateStatus; x != nil { + ch := n.Child("triggered_update_status") + stringsDatabaseSyncedTableTriggeredUpdateStatus(c, &ch, x) + } + } +} + +func stringsDatabaseSyncedTableSpec(c *stringsWalk, n *Node, v *database.SyncedTableSpec) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ExistingPipelineId == "" && Forced(v.ForceSendFields, "ExistingPipelineId") { + ch := n.Child("existing_pipeline_id") + c.fn(&ch, "") + } + if s := string(v.ExistingPipelineId); c.match(s) { + ch := n.Child("existing_pipeline_id") + if r, ok := c.fn(&ch, s); ok { + v.ExistingPipelineId = (r) + } + } + if !n.IsRef("new_pipeline_spec") { + if x := v.NewPipelineSpec; x != nil { + ch := n.Child("new_pipeline_spec") + stringsDatabaseNewPipelineSpec(c, &ch, x) + } + } + if !n.IsRef("primary_key_columns") { + if len(v.PrimaryKeyColumns) > 0 { + cn := n.Child("primary_key_columns") + for i := range v.PrimaryKeyColumns { + if s := string(v.PrimaryKeyColumns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.PrimaryKeyColumns[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.SchedulingPolicy == "" && Forced(v.ForceSendFields, "SchedulingPolicy") { + ch := n.Child("scheduling_policy") + c.fn(&ch, "") + } + if s := string(v.SchedulingPolicy); c.match(s) { + ch := n.Child("scheduling_policy") + if r, ok := c.fn(&ch, s); ok { + v.SchedulingPolicy = database.SyncedTableSchedulingPolicy(r) + } + } + if c.mode == EmptyFields && v.SourceTableFullName == "" && Forced(v.ForceSendFields, "SourceTableFullName") { + ch := n.Child("source_table_full_name") + c.fn(&ch, "") + } + if s := string(v.SourceTableFullName); c.match(s) { + ch := n.Child("source_table_full_name") + if r, ok := c.fn(&ch, s); ok { + v.SourceTableFullName = (r) + } + } + if c.mode == EmptyFields && v.TimeseriesKey == "" && Forced(v.ForceSendFields, "TimeseriesKey") { + ch := n.Child("timeseries_key") + c.fn(&ch, "") + } + if s := string(v.TimeseriesKey); c.match(s) { + ch := n.Child("timeseries_key") + if r, ok := c.fn(&ch, s); ok { + v.TimeseriesKey = (r) + } + } + if !n.IsRef("type_overrides") { + if len(v.TypeOverrides) > 0 { + cn := n.Child("type_overrides") + for i := range v.TypeOverrides { + { + ch := cn.Elem(i) + stringsDatabaseSyncedTableSpecTypeOverride(c, &ch, &v.TypeOverrides[i]) + } + } + } + } +} + +func stringsPostgresProjectCustomTag(c *stringsWalk, n *Node, v *postgres.ProjectCustomTag) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Key == "" && Forced(v.ForceSendFields, "Key") { + ch := n.Child("key") + c.fn(&ch, "") + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } +} + +func stringsPostgresProjectDefaultEndpointSettings(c *stringsWalk, n *Node, v *postgres.ProjectDefaultEndpointSettings) { + if n.isRef() { + return + } + if !n.IsRef("pg_settings") { + if len(v.PgSettings) > 0 { + cn := n.Child("pg_settings") + for _, k := range sortedKeys(v.PgSettings) { + if s := string(v.PgSettings[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.PgSettings[k] = (r) + } + } + } + } + } +} + +func stringsPostgresEndpointSettings(c *stringsWalk, n *Node, v *postgres.EndpointSettings) { + if n.isRef() { + return + } + if !n.IsRef("pg_settings") { + if len(v.PgSettings) > 0 { + cn := n.Child("pg_settings") + for _, k := range sortedKeys(v.PgSettings) { + if s := string(v.PgSettings[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.PgSettings[k] = (r) + } + } + } + } + } +} + +func stringsPostgresSyncedTableSyncedTableSpecExtraColumn(c *stringsWalk, n *Node, v *postgres.SyncedTableSyncedTableSpecExtraColumn) { + if n.isRef() { + return + } + if s := string(v.ColumnName); c.match(s) { + ch := n.Child("column_name") + if r, ok := c.fn(&ch, s); ok { + v.ColumnName = (r) + } + } + if s := string(v.ColumnType); c.match(s) { + ch := n.Child("column_type") + if r, ok := c.fn(&ch, s); ok { + v.ColumnType = (r) + } + } + if c.mode == EmptyFields && v.Compute == "" && Forced(v.ForceSendFields, "Compute") { + ch := n.Child("compute") + c.fn(&ch, "") + } + if s := string(v.Compute); c.match(s) { + ch := n.Child("compute") + if r, ok := c.fn(&ch, s); ok { + v.Compute = (r) + } + } + if c.mode == EmptyFields && v.Maintenance == "" && Forced(v.ForceSendFields, "Maintenance") { + ch := n.Child("maintenance") + c.fn(&ch, "") + } + if s := string(v.Maintenance); c.match(s) { + ch := n.Child("maintenance") + if r, ok := c.fn(&ch, s); ok { + v.Maintenance = postgres.SyncedTableSyncedTableSpecExtraColumnMaintenance(r) + } + } +} + +func stringsPostgresNewPipelineSpec(c *stringsWalk, n *Node, v *postgres.NewPipelineSpec) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.BudgetPolicyId == "" && Forced(v.ForceSendFields, "BudgetPolicyId") { + ch := n.Child("budget_policy_id") + c.fn(&ch, "") + } + if s := string(v.BudgetPolicyId); c.match(s) { + ch := n.Child("budget_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.BudgetPolicyId = (r) + } + } + if c.mode == EmptyFields && v.PipelineChannel == "" && Forced(v.ForceSendFields, "PipelineChannel") { + ch := n.Child("pipeline_channel") + c.fn(&ch, "") + } + if s := string(v.PipelineChannel); c.match(s) { + ch := n.Child("pipeline_channel") + if r, ok := c.fn(&ch, s); ok { + v.PipelineChannel = postgres.NewPipelineSpecPipelineChannel(r) + } + } + if c.mode == EmptyFields && v.StorageCatalog == "" && Forced(v.ForceSendFields, "StorageCatalog") { + ch := n.Child("storage_catalog") + c.fn(&ch, "") + } + if s := string(v.StorageCatalog); c.match(s) { + ch := n.Child("storage_catalog") + if r, ok := c.fn(&ch, s); ok { + v.StorageCatalog = (r) + } + } + if c.mode == EmptyFields && v.StorageSchema == "" && Forced(v.ForceSendFields, "StorageSchema") { + ch := n.Child("storage_schema") + c.fn(&ch, "") + } + if s := string(v.StorageSchema); c.match(s) { + ch := n.Child("storage_schema") + if r, ok := c.fn(&ch, s); ok { + v.StorageSchema = (r) + } + } +} + +func stringsPostgresSyncedTableSyncedTableSpecTypeOverride(c *stringsWalk, n *Node, v *postgres.SyncedTableSyncedTableSpecTypeOverride) { + if n.isRef() { + return + } + if s := string(v.ColumnName); c.match(s) { + ch := n.Child("column_name") + if r, ok := c.fn(&ch, s); ok { + v.ColumnName = (r) + } + } + if s := string(v.PgType); c.match(s) { + ch := n.Child("pg_type") + if r, ok := c.fn(&ch, s); ok { + v.PgType = postgres.SyncedTableSyncedTableSpecPgSpecificType(r) + } + } +} + +func stringsPostgresScheduleCadence(c *stringsWalk, n *Node, v *postgres.ScheduleCadence) { + if n.isRef() { + return + } + if !n.IsRef("weekly_schedule") { + if x := v.WeeklySchedule; x != nil { + ch := n.Child("weekly_schedule") + stringsPostgresWeeklySchedule(c, &ch, x) + } + } +} + +func stringsResourcesVectorSearchEndpointPermission(c *stringsWalk, n *Node, v *resources.VectorSearchEndpointPermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = vectorsearch.VectorSearchEndpointPermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsVectorsearchDeltaSyncVectorIndexSpecRequest(c *stringsWalk, n *Node, v *vectorsearch.DeltaSyncVectorIndexSpecRequest) { + if n.isRef() { + return + } + if !n.IsRef("columns_to_index") { + if len(v.ColumnsToIndex) > 0 { + cn := n.Child("columns_to_index") + for i := range v.ColumnsToIndex { + if s := string(v.ColumnsToIndex[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ColumnsToIndex[i] = (r) + } + } + } + } + } + if !n.IsRef("columns_to_sync") { + if len(v.ColumnsToSync) > 0 { + cn := n.Child("columns_to_sync") + for i := range v.ColumnsToSync { + if s := string(v.ColumnsToSync[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ColumnsToSync[i] = (r) + } + } + } + } + } + if !n.IsRef("embedding_source_columns") { + if len(v.EmbeddingSourceColumns) > 0 { + cn := n.Child("embedding_source_columns") + for i := range v.EmbeddingSourceColumns { + { + ch := cn.Elem(i) + stringsVectorsearchEmbeddingSourceColumn(c, &ch, &v.EmbeddingSourceColumns[i]) + } + } + } + } + if !n.IsRef("embedding_vector_columns") { + if len(v.EmbeddingVectorColumns) > 0 { + cn := n.Child("embedding_vector_columns") + for i := range v.EmbeddingVectorColumns { + { + ch := cn.Elem(i) + stringsVectorsearchEmbeddingVectorColumn(c, &ch, &v.EmbeddingVectorColumns[i]) + } + } + } + } + if c.mode == EmptyFields && v.EmbeddingWritebackTable == "" && Forced(v.ForceSendFields, "EmbeddingWritebackTable") { + ch := n.Child("embedding_writeback_table") + c.fn(&ch, "") + } + if s := string(v.EmbeddingWritebackTable); c.match(s) { + ch := n.Child("embedding_writeback_table") + if r, ok := c.fn(&ch, s); ok { + v.EmbeddingWritebackTable = (r) + } + } + if c.mode == EmptyFields && v.PipelineType == "" && Forced(v.ForceSendFields, "PipelineType") { + ch := n.Child("pipeline_type") + c.fn(&ch, "") + } + if s := string(v.PipelineType); c.match(s) { + ch := n.Child("pipeline_type") + if r, ok := c.fn(&ch, s); ok { + v.PipelineType = vectorsearch.PipelineType(r) + } + } + if c.mode == EmptyFields && v.SourceTable == "" && Forced(v.ForceSendFields, "SourceTable") { + ch := n.Child("source_table") + c.fn(&ch, "") + } + if s := string(v.SourceTable); c.match(s) { + ch := n.Child("source_table") + if r, ok := c.fn(&ch, s); ok { + v.SourceTable = (r) + } + } +} + +func stringsVectorsearchDirectAccessVectorIndexSpec(c *stringsWalk, n *Node, v *vectorsearch.DirectAccessVectorIndexSpec) { + if n.isRef() { + return + } + if !n.IsRef("embedding_source_columns") { + if len(v.EmbeddingSourceColumns) > 0 { + cn := n.Child("embedding_source_columns") + for i := range v.EmbeddingSourceColumns { + { + ch := cn.Elem(i) + stringsVectorsearchEmbeddingSourceColumn(c, &ch, &v.EmbeddingSourceColumns[i]) + } + } + } + } + if !n.IsRef("embedding_vector_columns") { + if len(v.EmbeddingVectorColumns) > 0 { + cn := n.Child("embedding_vector_columns") + for i := range v.EmbeddingVectorColumns { + { + ch := cn.Elem(i) + stringsVectorsearchEmbeddingVectorColumn(c, &ch, &v.EmbeddingVectorColumns[i]) + } + } + } + } + if c.mode == EmptyFields && v.SchemaJson == "" && Forced(v.ForceSendFields, "SchemaJson") { + ch := n.Child("schema_json") + c.fn(&ch, "") + } + if s := string(v.SchemaJson); c.match(s) { + ch := n.Child("schema_json") + if r, ok := c.fn(&ch, s); ok { + v.SchemaJson = (r) + } + } +} + +func stringsResourcesInstancePoolPermission(c *stringsWalk, n *Node, v *resources.InstancePoolPermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = compute.InstancePoolPermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsComputeInstancePoolAwsAttributes(c *stringsWalk, n *Node, v *compute.InstancePoolAwsAttributes) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Availability == "" && Forced(v.ForceSendFields, "Availability") { + ch := n.Child("availability") + c.fn(&ch, "") + } + if s := string(v.Availability); c.match(s) { + ch := n.Child("availability") + if r, ok := c.fn(&ch, s); ok { + v.Availability = compute.InstancePoolAwsAttributesAvailability(r) + } + } + if c.mode == EmptyFields && v.InstanceProfileArn == "" && Forced(v.ForceSendFields, "InstanceProfileArn") { + ch := n.Child("instance_profile_arn") + c.fn(&ch, "") + } + if s := string(v.InstanceProfileArn); c.match(s) { + ch := n.Child("instance_profile_arn") + if r, ok := c.fn(&ch, s); ok { + v.InstanceProfileArn = (r) + } + } + if c.mode == EmptyFields && v.ZoneId == "" && Forced(v.ForceSendFields, "ZoneId") { + ch := n.Child("zone_id") + c.fn(&ch, "") + } + if s := string(v.ZoneId); c.match(s) { + ch := n.Child("zone_id") + if r, ok := c.fn(&ch, s); ok { + v.ZoneId = (r) + } + } +} + +func stringsComputeInstancePoolAzureAttributes(c *stringsWalk, n *Node, v *compute.InstancePoolAzureAttributes) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Availability == "" && Forced(v.ForceSendFields, "Availability") { + ch := n.Child("availability") + c.fn(&ch, "") + } + if s := string(v.Availability); c.match(s) { + ch := n.Child("availability") + if r, ok := c.fn(&ch, s); ok { + v.Availability = compute.InstancePoolAzureAttributesAvailability(r) + } + } + if c.mode == EmptyFields && v.CapacityReservationGroup == "" && Forced(v.ForceSendFields, "CapacityReservationGroup") { + ch := n.Child("capacity_reservation_group") + c.fn(&ch, "") + } + if s := string(v.CapacityReservationGroup); c.match(s) { + ch := n.Child("capacity_reservation_group") + if r, ok := c.fn(&ch, s); ok { + v.CapacityReservationGroup = (r) + } + } +} + +func stringsComputeDiskSpec(c *stringsWalk, n *Node, v *compute.DiskSpec) { + if n.isRef() { + return + } + if !n.IsRef("disk_type") { + if x := v.DiskType; x != nil { + ch := n.Child("disk_type") + stringsComputeDiskType(c, &ch, x) + } + } +} + +func stringsComputeInstancePoolGcpAttributes(c *stringsWalk, n *Node, v *compute.InstancePoolGcpAttributes) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.GcpAvailability == "" && Forced(v.ForceSendFields, "GcpAvailability") { + ch := n.Child("gcp_availability") + c.fn(&ch, "") + } + if s := string(v.GcpAvailability); c.match(s) { + ch := n.Child("gcp_availability") + if r, ok := c.fn(&ch, s); ok { + v.GcpAvailability = compute.GcpAvailability(r) + } + } + if c.mode == EmptyFields && v.ZoneId == "" && Forced(v.ForceSendFields, "ZoneId") { + ch := n.Child("zone_id") + c.fn(&ch, "") + } + if s := string(v.ZoneId); c.match(s) { + ch := n.Child("zone_id") + if r, ok := c.fn(&ch, s); ok { + v.ZoneId = (r) + } + } +} + +func stringsResourcesClusterPolicyPermission(c *stringsWalk, n *Node, v *resources.ClusterPolicyPermission) { + if n.isRef() { + return + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = compute.ClusterPolicyPermissionLevel(r) + } + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } +} + +func stringsSnapshotACLEntry(c *stringsWalk, n *Node, v *snapshot.ACLEntry) { + if n.isRef() { + return + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } + if s := string(v.GroupName); c.match(s) { + ch := n.Child("group_name") + if r, ok := c.fn(&ch, s); ok { + v.GroupName = (r) + } + } + if s := string(v.ServicePrincipalName); c.match(s) { + ch := n.Child("service_principal_name") + if r, ok := c.fn(&ch, s); ok { + v.ServicePrincipalName = (r) + } + } + if s := string(v.PermissionLevel); c.match(s) { + ch := n.Child("permission_level") + if r, ok := c.fn(&ch, s); ok { + v.PermissionLevel = (r) + } + } +} + +func stringsJobsMaintenanceWindow(c *stringsWalk, n *Node, v *jobs.MaintenanceWindow) { + if n.isRef() { + return + } + if s := string(v.DayOfWeek); c.match(s) { + ch := n.Child("day_of_week") + if r, ok := c.fn(&ch, s); ok { + v.DayOfWeek = jobs.DayOfWeek(r) + } + } + if s := string(v.TimezoneId); c.match(s) { + ch := n.Child("timezone_id") + if r, ok := c.fn(&ch, s); ok { + v.TimezoneId = (r) + } + } +} + +func stringsJobsJobEnvironmentVariablesSpec(c *stringsWalk, n *Node, v *jobs.JobEnvironmentVariablesSpec) { + if n.isRef() { + return + } + if !n.IsRef("files") { + if len(v.Files) > 0 { + cn := n.Child("files") + for i := range v.Files { + if s := string(v.Files[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Files[i] = (r) + } + } + } + } + } + if !n.IsRef("variables") { + if len(v.Variables) > 0 { + cn := n.Child("variables") + for _, k := range sortedKeys(v.Variables) { + if s := string(v.Variables[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Variables[k] = (r) + } + } + } + } + } +} + +func stringsComputeEnvironment(c *stringsWalk, n *Node, v *compute.Environment) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.BaseEnvironment == "" && Forced(v.ForceSendFields, "BaseEnvironment") { + ch := n.Child("base_environment") + c.fn(&ch, "") + } + if s := string(v.BaseEnvironment); c.match(s) { + ch := n.Child("base_environment") + if r, ok := c.fn(&ch, s); ok { + v.BaseEnvironment = (r) + } + } + if c.mode == EmptyFields && v.Client == "" && Forced(v.ForceSendFields, "Client") { + ch := n.Child("client") + c.fn(&ch, "") + } + if s := string(v.Client); c.match(s) { + ch := n.Child("client") + if r, ok := c.fn(&ch, s); ok { + v.Client = (r) + } + } + if !n.IsRef("dependencies") { + if len(v.Dependencies) > 0 { + cn := n.Child("dependencies") + for i := range v.Dependencies { + if s := string(v.Dependencies[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Dependencies[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.EnvironmentVersion == "" && Forced(v.ForceSendFields, "EnvironmentVersion") { + ch := n.Child("environment_version") + c.fn(&ch, "") + } + if s := string(v.EnvironmentVersion); c.match(s) { + ch := n.Child("environment_version") + if r, ok := c.fn(&ch, s); ok { + v.EnvironmentVersion = (r) + } + } + if !n.IsRef("java_dependencies") { + if len(v.JavaDependencies) > 0 { + cn := n.Child("java_dependencies") + for i := range v.JavaDependencies { + if s := string(v.JavaDependencies[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.JavaDependencies[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.ProjectEnvironment == "" && Forced(v.ForceSendFields, "ProjectEnvironment") { + ch := n.Child("project_environment") + c.fn(&ch, "") + } + if s := string(v.ProjectEnvironment); c.match(s) { + ch := n.Child("project_environment") + if r, ok := c.fn(&ch, s); ok { + v.ProjectEnvironment = (r) + } + } +} + +func stringsJobsGitSnapshot(c *stringsWalk, n *Node, v *jobs.GitSnapshot) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.UsedCommit == "" && Forced(v.ForceSendFields, "UsedCommit") { + ch := n.Child("used_commit") + c.fn(&ch, "") + } + if s := string(v.UsedCommit); c.match(s) { + ch := n.Child("used_commit") + if r, ok := c.fn(&ch, s); ok { + v.UsedCommit = (r) + } + } +} + +func stringsJobsJobSource(c *stringsWalk, n *Node, v *jobs.JobSource) { + if n.isRef() { + return + } + if s := string(v.DirtyState); c.match(s) { + ch := n.Child("dirty_state") + if r, ok := c.fn(&ch, s); ok { + v.DirtyState = jobs.JobSourceDirtyState(r) + } + } + if s := string(v.ImportFromGitBranch); c.match(s) { + ch := n.Child("import_from_git_branch") + if r, ok := c.fn(&ch, s); ok { + v.ImportFromGitBranch = (r) + } + } + if s := string(v.JobConfigPath); c.match(s) { + ch := n.Child("job_config_path") + if r, ok := c.fn(&ch, s); ok { + v.JobConfigPath = (r) + } + } +} + +func stringsJobsSparseCheckout(c *stringsWalk, n *Node, v *jobs.SparseCheckout) { + if n.isRef() { + return + } + if !n.IsRef("patterns") { + if len(v.Patterns) > 0 { + cn := n.Child("patterns") + for i := range v.Patterns { + if s := string(v.Patterns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Patterns[i] = (r) + } + } + } + } + } +} + +func stringsJobsJobsHealthRule(c *stringsWalk, n *Node, v *jobs.JobsHealthRule) { + if n.isRef() { + return + } + if s := string(v.Metric); c.match(s) { + ch := n.Child("metric") + if r, ok := c.fn(&ch, s); ok { + v.Metric = jobs.JobsHealthMetric(r) + } + } + if s := string(v.Op); c.match(s) { + ch := n.Child("op") + if r, ok := c.fn(&ch, s); ok { + v.Op = jobs.JobsHealthOperator(r) + } + } +} + +func stringsComputeClusterSpec(c *stringsWalk, n *Node, v *compute.ClusterSpec) { + if n.isRef() { + return + } + if !n.IsRef("aws_attributes") { + if x := v.AwsAttributes; x != nil { + ch := n.Child("aws_attributes") + stringsComputeAwsAttributes(c, &ch, x) + } + } + if !n.IsRef("azure_attributes") { + if x := v.AzureAttributes; x != nil { + ch := n.Child("azure_attributes") + stringsComputeAzureAttributes(c, &ch, x) + } + } + if !n.IsRef("cluster_log_conf") { + if x := v.ClusterLogConf; x != nil { + ch := n.Child("cluster_log_conf") + stringsComputeClusterLogConf(c, &ch, x) + } + } + if c.mode == EmptyFields && v.ClusterName == "" && Forced(v.ForceSendFields, "ClusterName") { + ch := n.Child("cluster_name") + c.fn(&ch, "") + } + if s := string(v.ClusterName); c.match(s) { + ch := n.Child("cluster_name") + if r, ok := c.fn(&ch, s); ok { + v.ClusterName = (r) + } + } + if !n.IsRef("custom_tags") { + if len(v.CustomTags) > 0 { + cn := n.Child("custom_tags") + for _, k := range sortedKeys(v.CustomTags) { + if s := string(v.CustomTags[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.CustomTags[k] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.DataSecurityMode == "" && Forced(v.ForceSendFields, "DataSecurityMode") { + ch := n.Child("data_security_mode") + c.fn(&ch, "") + } + if s := string(v.DataSecurityMode); c.match(s) { + ch := n.Child("data_security_mode") + if r, ok := c.fn(&ch, s); ok { + v.DataSecurityMode = compute.DataSecurityMode(r) + } + } + if c.mode == EmptyFields && v.DependencyMode == "" && Forced(v.ForceSendFields, "DependencyMode") { + ch := n.Child("dependency_mode") + c.fn(&ch, "") + } + if s := string(v.DependencyMode); c.match(s) { + ch := n.Child("dependency_mode") + if r, ok := c.fn(&ch, s); ok { + v.DependencyMode = compute.DependencyMode(r) + } + } + if !n.IsRef("docker_image") { + if x := v.DockerImage; x != nil { + ch := n.Child("docker_image") + stringsComputeDockerImage(c, &ch, x) + } + } + if c.mode == EmptyFields && v.DriverInstancePoolId == "" && Forced(v.ForceSendFields, "DriverInstancePoolId") { + ch := n.Child("driver_instance_pool_id") + c.fn(&ch, "") + } + if s := string(v.DriverInstancePoolId); c.match(s) { + ch := n.Child("driver_instance_pool_id") + if r, ok := c.fn(&ch, s); ok { + v.DriverInstancePoolId = (r) + } + } + if !n.IsRef("driver_node_type_flexibility") { + if x := v.DriverNodeTypeFlexibility; x != nil { + ch := n.Child("driver_node_type_flexibility") + stringsComputeNodeTypeFlexibility(c, &ch, x) + } + } + if c.mode == EmptyFields && v.DriverNodeTypeId == "" && Forced(v.ForceSendFields, "DriverNodeTypeId") { + ch := n.Child("driver_node_type_id") + c.fn(&ch, "") + } + if s := string(v.DriverNodeTypeId); c.match(s) { + ch := n.Child("driver_node_type_id") + if r, ok := c.fn(&ch, s); ok { + v.DriverNodeTypeId = (r) + } + } + if !n.IsRef("gcp_attributes") { + if x := v.GcpAttributes; x != nil { + ch := n.Child("gcp_attributes") + stringsComputeGcpAttributes(c, &ch, x) + } + } + if !n.IsRef("init_scripts") { + if len(v.InitScripts) > 0 { + cn := n.Child("init_scripts") + for i := range v.InitScripts { + { + ch := cn.Elem(i) + stringsComputeInitScriptInfo(c, &ch, &v.InitScripts[i]) + } + } + } + } + if c.mode == EmptyFields && v.InstancePoolId == "" && Forced(v.ForceSendFields, "InstancePoolId") { + ch := n.Child("instance_pool_id") + c.fn(&ch, "") + } + if s := string(v.InstancePoolId); c.match(s) { + ch := n.Child("instance_pool_id") + if r, ok := c.fn(&ch, s); ok { + v.InstancePoolId = (r) + } + } + if c.mode == EmptyFields && v.Kind == "" && Forced(v.ForceSendFields, "Kind") { + ch := n.Child("kind") + c.fn(&ch, "") + } + if s := string(v.Kind); c.match(s) { + ch := n.Child("kind") + if r, ok := c.fn(&ch, s); ok { + v.Kind = compute.Kind(r) + } + } + if c.mode == EmptyFields && v.NodeTypeId == "" && Forced(v.ForceSendFields, "NodeTypeId") { + ch := n.Child("node_type_id") + c.fn(&ch, "") + } + if s := string(v.NodeTypeId); c.match(s) { + ch := n.Child("node_type_id") + if r, ok := c.fn(&ch, s); ok { + v.NodeTypeId = (r) + } + } + if c.mode == EmptyFields && v.PolicyId == "" && Forced(v.ForceSendFields, "PolicyId") { + ch := n.Child("policy_id") + c.fn(&ch, "") + } + if s := string(v.PolicyId); c.match(s) { + ch := n.Child("policy_id") + if r, ok := c.fn(&ch, s); ok { + v.PolicyId = (r) + } + } + if c.mode == EmptyFields && v.RuntimeEngine == "" && Forced(v.ForceSendFields, "RuntimeEngine") { + ch := n.Child("runtime_engine") + c.fn(&ch, "") + } + if s := string(v.RuntimeEngine); c.match(s) { + ch := n.Child("runtime_engine") + if r, ok := c.fn(&ch, s); ok { + v.RuntimeEngine = compute.RuntimeEngine(r) + } + } + if c.mode == EmptyFields && v.SingleUserName == "" && Forced(v.ForceSendFields, "SingleUserName") { + ch := n.Child("single_user_name") + c.fn(&ch, "") + } + if s := string(v.SingleUserName); c.match(s) { + ch := n.Child("single_user_name") + if r, ok := c.fn(&ch, s); ok { + v.SingleUserName = (r) + } + } + if !n.IsRef("spark_conf") { + if len(v.SparkConf) > 0 { + cn := n.Child("spark_conf") + for _, k := range sortedKeys(v.SparkConf) { + if s := string(v.SparkConf[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.SparkConf[k] = (r) + } + } + } + } + } + if !n.IsRef("spark_env_vars") { + if len(v.SparkEnvVars) > 0 { + cn := n.Child("spark_env_vars") + for _, k := range sortedKeys(v.SparkEnvVars) { + if s := string(v.SparkEnvVars[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.SparkEnvVars[k] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.SparkVersion == "" && Forced(v.ForceSendFields, "SparkVersion") { + ch := n.Child("spark_version") + c.fn(&ch, "") + } + if s := string(v.SparkVersion); c.match(s) { + ch := n.Child("spark_version") + if r, ok := c.fn(&ch, s); ok { + v.SparkVersion = (r) + } + } + if !n.IsRef("ssh_public_keys") { + if len(v.SshPublicKeys) > 0 { + cn := n.Child("ssh_public_keys") + for i := range v.SshPublicKeys { + if s := string(v.SshPublicKeys[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.SshPublicKeys[i] = (r) + } + } + } + } + } + if !n.IsRef("worker_node_type_flexibility") { + if x := v.WorkerNodeTypeFlexibility; x != nil { + ch := n.Child("worker_node_type_flexibility") + stringsComputeNodeTypeFlexibility(c, &ch, x) + } + } +} + +func stringsJobsSqlConditionConfiguration(c *stringsWalk, n *Node, v *jobs.SqlConditionConfiguration) { + if n.isRef() { + return + } + if s := string(v.SqlQueryId); c.match(s) { + ch := n.Child("sql_query_id") + if r, ok := c.fn(&ch, s); ok { + v.SqlQueryId = (r) + } + } + if s := string(v.TriggerMode); c.match(s) { + ch := n.Child("trigger_mode") + if r, ok := c.fn(&ch, s); ok { + v.TriggerMode = jobs.SqlConditionTriggerMode(r) + } + } + if s := string(v.WarehouseId); c.match(s) { + ch := n.Child("warehouse_id") + if r, ok := c.fn(&ch, s); ok { + v.WarehouseId = (r) + } + } +} + +func stringsJobsAiRuntimeTask(c *stringsWalk, n *Node, v *jobs.AiRuntimeTask) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.CodeSourcePath == "" && Forced(v.ForceSendFields, "CodeSourcePath") { + ch := n.Child("code_source_path") + c.fn(&ch, "") + } + if s := string(v.CodeSourcePath); c.match(s) { + ch := n.Child("code_source_path") + if r, ok := c.fn(&ch, s); ok { + v.CodeSourcePath = (r) + } + } + if !n.IsRef("deployments") { + if len(v.Deployments) > 0 { + cn := n.Child("deployments") + for i := range v.Deployments { + { + ch := cn.Elem(i) + stringsJobsDeploymentSpec(c, &ch, &v.Deployments[i]) + } + } + } + } + if c.mode == EmptyFields && v.DockerImageUrl == "" && Forced(v.ForceSendFields, "DockerImageUrl") { + ch := n.Child("docker_image_url") + c.fn(&ch, "") + } + if s := string(v.DockerImageUrl); c.match(s) { + ch := n.Child("docker_image_url") + if r, ok := c.fn(&ch, s); ok { + v.DockerImageUrl = (r) + } + } + if s := string(v.Experiment); c.match(s) { + ch := n.Child("experiment") + if r, ok := c.fn(&ch, s); ok { + v.Experiment = (r) + } + } + if c.mode == EmptyFields && v.MlflowArtifactLocation == "" && Forced(v.ForceSendFields, "MlflowArtifactLocation") { + ch := n.Child("mlflow_artifact_location") + c.fn(&ch, "") + } + if s := string(v.MlflowArtifactLocation); c.match(s) { + ch := n.Child("mlflow_artifact_location") + if r, ok := c.fn(&ch, s); ok { + v.MlflowArtifactLocation = (r) + } + } + if c.mode == EmptyFields && v.MlflowExperimentDirectory == "" && Forced(v.ForceSendFields, "MlflowExperimentDirectory") { + ch := n.Child("mlflow_experiment_directory") + c.fn(&ch, "") + } + if s := string(v.MlflowExperimentDirectory); c.match(s) { + ch := n.Child("mlflow_experiment_directory") + if r, ok := c.fn(&ch, s); ok { + v.MlflowExperimentDirectory = (r) + } + } + if c.mode == EmptyFields && v.MlflowRun == "" && Forced(v.ForceSendFields, "MlflowRun") { + ch := n.Child("mlflow_run") + c.fn(&ch, "") + } + if s := string(v.MlflowRun); c.match(s) { + ch := n.Child("mlflow_run") + if r, ok := c.fn(&ch, s); ok { + v.MlflowRun = (r) + } + } + if c.mode == EmptyFields && v.PriorityClass == "" && Forced(v.ForceSendFields, "PriorityClass") { + ch := n.Child("priority_class") + c.fn(&ch, "") + } + if s := string(v.PriorityClass); c.match(s) { + ch := n.Child("priority_class") + if r, ok := c.fn(&ch, s); ok { + v.PriorityClass = jobs.AiRuntimeTaskPriorityClass(r) + } + } + if c.mode == EmptyFields && v.UnityCatalogImagePath == "" && Forced(v.ForceSendFields, "UnityCatalogImagePath") { + ch := n.Child("unity_catalog_image_path") + c.fn(&ch, "") + } + if s := string(v.UnityCatalogImagePath); c.match(s) { + ch := n.Child("unity_catalog_image_path") + if r, ok := c.fn(&ch, s); ok { + v.UnityCatalogImagePath = (r) + } + } +} + +func stringsJobsAlertTask(c *stringsWalk, n *Node, v *jobs.AlertTask) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.AlertId == "" && Forced(v.ForceSendFields, "AlertId") { + ch := n.Child("alert_id") + c.fn(&ch, "") + } + if s := string(v.AlertId); c.match(s) { + ch := n.Child("alert_id") + if r, ok := c.fn(&ch, s); ok { + v.AlertId = (r) + } + } + if !n.IsRef("parameters") { + if len(v.Parameters) > 0 { + cn := n.Child("parameters") + for _, k := range sortedKeys(v.Parameters) { + if s := string(v.Parameters[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Parameters[k] = (r) + } + } + } + } + } + if !n.IsRef("subscribers") { + if len(v.Subscribers) > 0 { + cn := n.Child("subscribers") + for i := range v.Subscribers { + { + ch := cn.Elem(i) + stringsJobsAlertTaskSubscriber(c, &ch, &v.Subscribers[i]) + } + } + } + } + if c.mode == EmptyFields && v.WarehouseId == "" && Forced(v.ForceSendFields, "WarehouseId") { + ch := n.Child("warehouse_id") + c.fn(&ch, "") + } + if s := string(v.WarehouseId); c.match(s) { + ch := n.Child("warehouse_id") + if r, ok := c.fn(&ch, s); ok { + v.WarehouseId = (r) + } + } + if c.mode == EmptyFields && v.WorkspacePath == "" && Forced(v.ForceSendFields, "WorkspacePath") { + ch := n.Child("workspace_path") + c.fn(&ch, "") + } + if s := string(v.WorkspacePath); c.match(s) { + ch := n.Child("workspace_path") + if r, ok := c.fn(&ch, s); ok { + v.WorkspacePath = (r) + } + } +} + +func stringsJobsCleanRoomsNotebookTask(c *stringsWalk, n *Node, v *jobs.CleanRoomsNotebookTask) { + if n.isRef() { + return + } + if s := string(v.CleanRoomName); c.match(s) { + ch := n.Child("clean_room_name") + if r, ok := c.fn(&ch, s); ok { + v.CleanRoomName = (r) + } + } + if c.mode == EmptyFields && v.Etag == "" && Forced(v.ForceSendFields, "Etag") { + ch := n.Child("etag") + c.fn(&ch, "") + } + if s := string(v.Etag); c.match(s) { + ch := n.Child("etag") + if r, ok := c.fn(&ch, s); ok { + v.Etag = (r) + } + } + if !n.IsRef("notebook_base_parameters") { + if len(v.NotebookBaseParameters) > 0 { + cn := n.Child("notebook_base_parameters") + for _, k := range sortedKeys(v.NotebookBaseParameters) { + if s := string(v.NotebookBaseParameters[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.NotebookBaseParameters[k] = (r) + } + } + } + } + } + if s := string(v.NotebookName); c.match(s) { + ch := n.Child("notebook_name") + if r, ok := c.fn(&ch, s); ok { + v.NotebookName = (r) + } + } +} + +func stringsJobsCompute(c *stringsWalk, n *Node, v *jobs.Compute) { + if n.isRef() { + return + } + if s := string(v.HardwareAccelerator); c.match(s) { + ch := n.Child("hardware_accelerator") + if r, ok := c.fn(&ch, s); ok { + v.HardwareAccelerator = compute.HardwareAcceleratorType(r) + } + } +} + +func stringsJobsConditionTask(c *stringsWalk, n *Node, v *jobs.ConditionTask) { + if n.isRef() { + return + } + if s := string(v.Left); c.match(s) { + ch := n.Child("left") + if r, ok := c.fn(&ch, s); ok { + v.Left = (r) + } + } + if s := string(v.Op); c.match(s) { + ch := n.Child("op") + if r, ok := c.fn(&ch, s); ok { + v.Op = jobs.ConditionTaskOp(r) + } + } + if s := string(v.Right); c.match(s) { + ch := n.Child("right") + if r, ok := c.fn(&ch, s); ok { + v.Right = (r) + } + } +} + +func stringsJobsDashboardTask(c *stringsWalk, n *Node, v *jobs.DashboardTask) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DashboardId == "" && Forced(v.ForceSendFields, "DashboardId") { + ch := n.Child("dashboard_id") + c.fn(&ch, "") + } + if s := string(v.DashboardId); c.match(s) { + ch := n.Child("dashboard_id") + if r, ok := c.fn(&ch, s); ok { + v.DashboardId = (r) + } + } + if !n.IsRef("filters") { + if len(v.Filters) > 0 { + cn := n.Child("filters") + for _, k := range sortedKeys(v.Filters) { + if s := string(v.Filters[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Filters[k] = (r) + } + } + } + } + } + if !n.IsRef("subscription") { + if x := v.Subscription; x != nil { + ch := n.Child("subscription") + stringsJobsSubscription(c, &ch, x) + } + } + if c.mode == EmptyFields && v.WarehouseId == "" && Forced(v.ForceSendFields, "WarehouseId") { + ch := n.Child("warehouse_id") + c.fn(&ch, "") + } + if s := string(v.WarehouseId); c.match(s) { + ch := n.Child("warehouse_id") + if r, ok := c.fn(&ch, s); ok { + v.WarehouseId = (r) + } + } +} + +func stringsJobsDbtCloudTask(c *stringsWalk, n *Node, v *jobs.DbtCloudTask) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ConnectionResourceName == "" && Forced(v.ForceSendFields, "ConnectionResourceName") { + ch := n.Child("connection_resource_name") + c.fn(&ch, "") + } + if s := string(v.ConnectionResourceName); c.match(s) { + ch := n.Child("connection_resource_name") + if r, ok := c.fn(&ch, s); ok { + v.ConnectionResourceName = (r) + } + } +} + +func stringsJobsDbtPlatformTask(c *stringsWalk, n *Node, v *jobs.DbtPlatformTask) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ConnectionResourceName == "" && Forced(v.ForceSendFields, "ConnectionResourceName") { + ch := n.Child("connection_resource_name") + c.fn(&ch, "") + } + if s := string(v.ConnectionResourceName); c.match(s) { + ch := n.Child("connection_resource_name") + if r, ok := c.fn(&ch, s); ok { + v.ConnectionResourceName = (r) + } + } + if c.mode == EmptyFields && v.DbtPlatformJobId == "" && Forced(v.ForceSendFields, "DbtPlatformJobId") { + ch := n.Child("dbt_platform_job_id") + c.fn(&ch, "") + } + if s := string(v.DbtPlatformJobId); c.match(s) { + ch := n.Child("dbt_platform_job_id") + if r, ok := c.fn(&ch, s); ok { + v.DbtPlatformJobId = (r) + } + } +} + +func stringsJobsDbtTask(c *stringsWalk, n *Node, v *jobs.DbtTask) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Catalog == "" && Forced(v.ForceSendFields, "Catalog") { + ch := n.Child("catalog") + c.fn(&ch, "") + } + if s := string(v.Catalog); c.match(s) { + ch := n.Child("catalog") + if r, ok := c.fn(&ch, s); ok { + v.Catalog = (r) + } + } + if !n.IsRef("commands") { + if len(v.Commands) > 0 { + cn := n.Child("commands") + for i := range v.Commands { + if s := string(v.Commands[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Commands[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.ProfilesDirectory == "" && Forced(v.ForceSendFields, "ProfilesDirectory") { + ch := n.Child("profiles_directory") + c.fn(&ch, "") + } + if s := string(v.ProfilesDirectory); c.match(s) { + ch := n.Child("profiles_directory") + if r, ok := c.fn(&ch, s); ok { + v.ProfilesDirectory = (r) + } + } + if c.mode == EmptyFields && v.ProjectDirectory == "" && Forced(v.ForceSendFields, "ProjectDirectory") { + ch := n.Child("project_directory") + c.fn(&ch, "") + } + if s := string(v.ProjectDirectory); c.match(s) { + ch := n.Child("project_directory") + if r, ok := c.fn(&ch, s); ok { + v.ProjectDirectory = (r) + } + } + if c.mode == EmptyFields && v.Schema == "" && Forced(v.ForceSendFields, "Schema") { + ch := n.Child("schema") + c.fn(&ch, "") + } + if s := string(v.Schema); c.match(s) { + ch := n.Child("schema") + if r, ok := c.fn(&ch, s); ok { + v.Schema = (r) + } + } + if c.mode == EmptyFields && v.Source == "" && Forced(v.ForceSendFields, "Source") { + ch := n.Child("source") + c.fn(&ch, "") + } + if s := string(v.Source); c.match(s) { + ch := n.Child("source") + if r, ok := c.fn(&ch, s); ok { + v.Source = jobs.Source(r) + } + } + if c.mode == EmptyFields && v.WarehouseId == "" && Forced(v.ForceSendFields, "WarehouseId") { + ch := n.Child("warehouse_id") + c.fn(&ch, "") + } + if s := string(v.WarehouseId); c.match(s) { + ch := n.Child("warehouse_id") + if r, ok := c.fn(&ch, s); ok { + v.WarehouseId = (r) + } + } +} + +func stringsJobsTaskDependency(c *stringsWalk, n *Node, v *jobs.TaskDependency) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Outcome == "" && Forced(v.ForceSendFields, "Outcome") { + ch := n.Child("outcome") + c.fn(&ch, "") + } + if s := string(v.Outcome); c.match(s) { + ch := n.Child("outcome") + if r, ok := c.fn(&ch, s); ok { + v.Outcome = (r) + } + } + if s := string(v.TaskKey); c.match(s) { + ch := n.Child("task_key") + if r, ok := c.fn(&ch, s); ok { + v.TaskKey = (r) + } + } +} + +func stringsJobsTaskEmailNotifications(c *stringsWalk, n *Node, v *jobs.TaskEmailNotifications) { + if n.isRef() { + return + } + if !n.IsRef("on_duration_warning_threshold_exceeded") { + if len(v.OnDurationWarningThresholdExceeded) > 0 { + cn := n.Child("on_duration_warning_threshold_exceeded") + for i := range v.OnDurationWarningThresholdExceeded { + if s := string(v.OnDurationWarningThresholdExceeded[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnDurationWarningThresholdExceeded[i] = (r) + } + } + } + } + } + if !n.IsRef("on_failure") { + if len(v.OnFailure) > 0 { + cn := n.Child("on_failure") + for i := range v.OnFailure { + if s := string(v.OnFailure[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnFailure[i] = (r) + } + } + } + } + } + if !n.IsRef("on_maintenance_complete") { + if len(v.OnMaintenanceComplete) > 0 { + cn := n.Child("on_maintenance_complete") + for i := range v.OnMaintenanceComplete { + if s := string(v.OnMaintenanceComplete[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnMaintenanceComplete[i] = (r) + } + } + } + } + } + if !n.IsRef("on_maintenance_start") { + if len(v.OnMaintenanceStart) > 0 { + cn := n.Child("on_maintenance_start") + for i := range v.OnMaintenanceStart { + if s := string(v.OnMaintenanceStart[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnMaintenanceStart[i] = (r) + } + } + } + } + } + if !n.IsRef("on_start") { + if len(v.OnStart) > 0 { + cn := n.Child("on_start") + for i := range v.OnStart { + if s := string(v.OnStart[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnStart[i] = (r) + } + } + } + } + } + if !n.IsRef("on_streaming_backlog_exceeded") { + if len(v.OnStreamingBacklogExceeded) > 0 { + cn := n.Child("on_streaming_backlog_exceeded") + for i := range v.OnStreamingBacklogExceeded { + if s := string(v.OnStreamingBacklogExceeded[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnStreamingBacklogExceeded[i] = (r) + } + } + } + } + } + if !n.IsRef("on_success") { + if len(v.OnSuccess) > 0 { + cn := n.Child("on_success") + for i := range v.OnSuccess { + if s := string(v.OnSuccess[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.OnSuccess[i] = (r) + } + } + } + } + } +} + +func stringsJobsForEachTask(c *stringsWalk, n *Node, v *jobs.ForEachTask) { + if n.isRef() { + return + } + if s := string(v.Inputs); c.match(s) { + ch := n.Child("inputs") + if r, ok := c.fn(&ch, s); ok { + v.Inputs = (r) + } + } + if !n.IsRef("task") { + { + ch := n.Child("task") + stringsJobsTask(c, &ch, &v.Task) + } + } +} + +func stringsJobsGenAiComputeTask(c *stringsWalk, n *Node, v *jobs.GenAiComputeTask) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Command == "" && Forced(v.ForceSendFields, "Command") { + ch := n.Child("command") + c.fn(&ch, "") + } + if s := string(v.Command); c.match(s) { + ch := n.Child("command") + if r, ok := c.fn(&ch, s); ok { + v.Command = (r) + } + } + if !n.IsRef("compute") { + if x := v.Compute; x != nil { + ch := n.Child("compute") + stringsJobsComputeConfig(c, &ch, x) + } + } + if s := string(v.DlRuntimeImage); c.match(s) { + ch := n.Child("dl_runtime_image") + if r, ok := c.fn(&ch, s); ok { + v.DlRuntimeImage = (r) + } + } + if c.mode == EmptyFields && v.MlflowExperimentName == "" && Forced(v.ForceSendFields, "MlflowExperimentName") { + ch := n.Child("mlflow_experiment_name") + c.fn(&ch, "") + } + if s := string(v.MlflowExperimentName); c.match(s) { + ch := n.Child("mlflow_experiment_name") + if r, ok := c.fn(&ch, s); ok { + v.MlflowExperimentName = (r) + } + } + if c.mode == EmptyFields && v.Source == "" && Forced(v.ForceSendFields, "Source") { + ch := n.Child("source") + c.fn(&ch, "") + } + if s := string(v.Source); c.match(s) { + ch := n.Child("source") + if r, ok := c.fn(&ch, s); ok { + v.Source = jobs.Source(r) + } + } + if c.mode == EmptyFields && v.TrainingScriptPath == "" && Forced(v.ForceSendFields, "TrainingScriptPath") { + ch := n.Child("training_script_path") + c.fn(&ch, "") + } + if s := string(v.TrainingScriptPath); c.match(s) { + ch := n.Child("training_script_path") + if r, ok := c.fn(&ch, s); ok { + v.TrainingScriptPath = (r) + } + } + if c.mode == EmptyFields && v.YamlParameters == "" && Forced(v.ForceSendFields, "YamlParameters") { + ch := n.Child("yaml_parameters") + c.fn(&ch, "") + } + if s := string(v.YamlParameters); c.match(s) { + ch := n.Child("yaml_parameters") + if r, ok := c.fn(&ch, s); ok { + v.YamlParameters = (r) + } + } + if c.mode == EmptyFields && v.YamlParametersFilePath == "" && Forced(v.ForceSendFields, "YamlParametersFilePath") { + ch := n.Child("yaml_parameters_file_path") + c.fn(&ch, "") + } + if s := string(v.YamlParametersFilePath); c.match(s) { + ch := n.Child("yaml_parameters_file_path") + if r, ok := c.fn(&ch, s); ok { + v.YamlParametersFilePath = (r) + } + } +} + +func stringsJobsNotebookTask(c *stringsWalk, n *Node, v *jobs.NotebookTask) { + if n.isRef() { + return + } + if !n.IsRef("base_parameters") { + if len(v.BaseParameters) > 0 { + cn := n.Child("base_parameters") + for _, k := range sortedKeys(v.BaseParameters) { + if s := string(v.BaseParameters[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.BaseParameters[k] = (r) + } + } + } + } + } + if s := string(v.NotebookPath); c.match(s) { + ch := n.Child("notebook_path") + if r, ok := c.fn(&ch, s); ok { + v.NotebookPath = (r) + } + } + if c.mode == EmptyFields && v.Source == "" && Forced(v.ForceSendFields, "Source") { + ch := n.Child("source") + c.fn(&ch, "") + } + if s := string(v.Source); c.match(s) { + ch := n.Child("source") + if r, ok := c.fn(&ch, s); ok { + v.Source = jobs.Source(r) + } + } + if c.mode == EmptyFields && v.WarehouseId == "" && Forced(v.ForceSendFields, "WarehouseId") { + ch := n.Child("warehouse_id") + c.fn(&ch, "") + } + if s := string(v.WarehouseId); c.match(s) { + ch := n.Child("warehouse_id") + if r, ok := c.fn(&ch, s); ok { + v.WarehouseId = (r) + } + } +} + +func stringsJobsPipelineTask(c *stringsWalk, n *Node, v *jobs.PipelineTask) { + if n.isRef() { + return + } + if !n.IsRef("full_refresh_selection") { + if len(v.FullRefreshSelection) > 0 { + cn := n.Child("full_refresh_selection") + for i := range v.FullRefreshSelection { + if s := string(v.FullRefreshSelection[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.FullRefreshSelection[i] = (r) + } + } + } + } + } + if !n.IsRef("parameters") { + if len(v.Parameters) > 0 { + cn := n.Child("parameters") + for _, k := range sortedKeys(v.Parameters) { + if s := string(v.Parameters[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Parameters[k] = (r) + } + } + } + } + } + if s := string(v.PipelineId); c.match(s) { + ch := n.Child("pipeline_id") + if r, ok := c.fn(&ch, s); ok { + v.PipelineId = (r) + } + } + if !n.IsRef("refresh_flow_selection") { + if len(v.RefreshFlowSelection) > 0 { + cn := n.Child("refresh_flow_selection") + for i := range v.RefreshFlowSelection { + if s := string(v.RefreshFlowSelection[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.RefreshFlowSelection[i] = (r) + } + } + } + } + } + if !n.IsRef("refresh_selection") { + if len(v.RefreshSelection) > 0 { + cn := n.Child("refresh_selection") + for i := range v.RefreshSelection { + if s := string(v.RefreshSelection[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.RefreshSelection[i] = (r) + } + } + } + } + } + if !n.IsRef("reset_checkpoint_selection") { + if len(v.ResetCheckpointSelection) > 0 { + cn := n.Child("reset_checkpoint_selection") + for i := range v.ResetCheckpointSelection { + if s := string(v.ResetCheckpointSelection[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ResetCheckpointSelection[i] = (r) + } + } + } + } + } +} + +func stringsJobsPowerBiTask(c *stringsWalk, n *Node, v *jobs.PowerBiTask) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ConnectionResourceName == "" && Forced(v.ForceSendFields, "ConnectionResourceName") { + ch := n.Child("connection_resource_name") + c.fn(&ch, "") + } + if s := string(v.ConnectionResourceName); c.match(s) { + ch := n.Child("connection_resource_name") + if r, ok := c.fn(&ch, s); ok { + v.ConnectionResourceName = (r) + } + } + if !n.IsRef("power_bi_model") { + if x := v.PowerBiModel; x != nil { + ch := n.Child("power_bi_model") + stringsJobsPowerBiModel(c, &ch, x) + } + } + if !n.IsRef("tables") { + if len(v.Tables) > 0 { + cn := n.Child("tables") + for i := range v.Tables { + { + ch := cn.Elem(i) + stringsJobsPowerBiTable(c, &ch, &v.Tables[i]) + } + } + } + } + if c.mode == EmptyFields && v.WarehouseId == "" && Forced(v.ForceSendFields, "WarehouseId") { + ch := n.Child("warehouse_id") + c.fn(&ch, "") + } + if s := string(v.WarehouseId); c.match(s) { + ch := n.Child("warehouse_id") + if r, ok := c.fn(&ch, s); ok { + v.WarehouseId = (r) + } + } +} + +func stringsJobsPythonOperatorTask(c *stringsWalk, n *Node, v *jobs.PythonOperatorTask) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Main == "" && Forced(v.ForceSendFields, "Main") { + ch := n.Child("main") + c.fn(&ch, "") + } + if s := string(v.Main); c.match(s) { + ch := n.Child("main") + if r, ok := c.fn(&ch, s); ok { + v.Main = (r) + } + } + if !n.IsRef("parameters") { + if len(v.Parameters) > 0 { + cn := n.Child("parameters") + for i := range v.Parameters { + { + ch := cn.Elem(i) + stringsJobsPythonOperatorTaskParameter(c, &ch, &v.Parameters[i]) + } + } + } + } +} + +func stringsJobsPythonWheelTask(c *stringsWalk, n *Node, v *jobs.PythonWheelTask) { + if n.isRef() { + return + } + if s := string(v.EntryPoint); c.match(s) { + ch := n.Child("entry_point") + if r, ok := c.fn(&ch, s); ok { + v.EntryPoint = (r) + } + } + if !n.IsRef("named_parameters") { + if len(v.NamedParameters) > 0 { + cn := n.Child("named_parameters") + for _, k := range sortedKeys(v.NamedParameters) { + if s := string(v.NamedParameters[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.NamedParameters[k] = (r) + } + } + } + } + } + if s := string(v.PackageName); c.match(s) { + ch := n.Child("package_name") + if r, ok := c.fn(&ch, s); ok { + v.PackageName = (r) + } + } + if !n.IsRef("parameters") { + if len(v.Parameters) > 0 { + cn := n.Child("parameters") + for i := range v.Parameters { + if s := string(v.Parameters[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Parameters[i] = (r) + } + } + } + } + } +} + +func stringsJobsRunJobTask(c *stringsWalk, n *Node, v *jobs.RunJobTask) { + if n.isRef() { + return + } + if !n.IsRef("dbt_commands") { + if len(v.DbtCommands) > 0 { + cn := n.Child("dbt_commands") + for i := range v.DbtCommands { + if s := string(v.DbtCommands[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.DbtCommands[i] = (r) + } + } + } + } + } + if !n.IsRef("jar_params") { + if len(v.JarParams) > 0 { + cn := n.Child("jar_params") + for i := range v.JarParams { + if s := string(v.JarParams[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.JarParams[i] = (r) + } + } + } + } + } + if !n.IsRef("job_parameters") { + if len(v.JobParameters) > 0 { + cn := n.Child("job_parameters") + for _, k := range sortedKeys(v.JobParameters) { + if s := string(v.JobParameters[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.JobParameters[k] = (r) + } + } + } + } + } + if !n.IsRef("notebook_params") { + if len(v.NotebookParams) > 0 { + cn := n.Child("notebook_params") + for _, k := range sortedKeys(v.NotebookParams) { + if s := string(v.NotebookParams[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.NotebookParams[k] = (r) + } + } + } + } + } + if !n.IsRef("pipeline_params") { + if x := v.PipelineParams; x != nil { + ch := n.Child("pipeline_params") + stringsJobsPipelineParams(c, &ch, x) + } + } + if !n.IsRef("python_named_params") { + if len(v.PythonNamedParams) > 0 { + cn := n.Child("python_named_params") + for _, k := range sortedKeys(v.PythonNamedParams) { + if s := string(v.PythonNamedParams[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.PythonNamedParams[k] = (r) + } + } + } + } + } + if !n.IsRef("python_params") { + if len(v.PythonParams) > 0 { + cn := n.Child("python_params") + for i := range v.PythonParams { + if s := string(v.PythonParams[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.PythonParams[i] = (r) + } + } + } + } + } + if !n.IsRef("spark_submit_params") { + if len(v.SparkSubmitParams) > 0 { + cn := n.Child("spark_submit_params") + for i := range v.SparkSubmitParams { + if s := string(v.SparkSubmitParams[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.SparkSubmitParams[i] = (r) + } + } + } + } + } + if !n.IsRef("sql_params") { + if len(v.SqlParams) > 0 { + cn := n.Child("sql_params") + for _, k := range sortedKeys(v.SqlParams) { + if s := string(v.SqlParams[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.SqlParams[k] = (r) + } + } + } + } + } +} + +func stringsJobsSparkJarTask(c *stringsWalk, n *Node, v *jobs.SparkJarTask) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.JarUri == "" && Forced(v.ForceSendFields, "JarUri") { + ch := n.Child("jar_uri") + c.fn(&ch, "") + } + if s := string(v.JarUri); c.match(s) { + ch := n.Child("jar_uri") + if r, ok := c.fn(&ch, s); ok { + v.JarUri = (r) + } + } + if c.mode == EmptyFields && v.MainClassName == "" && Forced(v.ForceSendFields, "MainClassName") { + ch := n.Child("main_class_name") + c.fn(&ch, "") + } + if s := string(v.MainClassName); c.match(s) { + ch := n.Child("main_class_name") + if r, ok := c.fn(&ch, s); ok { + v.MainClassName = (r) + } + } + if !n.IsRef("parameters") { + if len(v.Parameters) > 0 { + cn := n.Child("parameters") + for i := range v.Parameters { + if s := string(v.Parameters[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Parameters[i] = (r) + } + } + } + } + } +} + +func stringsJobsSparkPythonTask(c *stringsWalk, n *Node, v *jobs.SparkPythonTask) { + if n.isRef() { + return + } + if !n.IsRef("parameters") { + if len(v.Parameters) > 0 { + cn := n.Child("parameters") + for i := range v.Parameters { + if s := string(v.Parameters[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Parameters[i] = (r) + } + } + } + } + } + if s := string(v.PythonFile); c.match(s) { + ch := n.Child("python_file") + if r, ok := c.fn(&ch, s); ok { + v.PythonFile = (r) + } + } + if s := string(v.Source); c.match(s) { + ch := n.Child("source") + if r, ok := c.fn(&ch, s); ok { + v.Source = jobs.Source(r) + } + } +} + +func stringsJobsSparkSubmitTask(c *stringsWalk, n *Node, v *jobs.SparkSubmitTask) { + if n.isRef() { + return + } + if !n.IsRef("parameters") { + if len(v.Parameters) > 0 { + cn := n.Child("parameters") + for i := range v.Parameters { + if s := string(v.Parameters[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Parameters[i] = (r) + } + } + } + } + } +} + +func stringsJobsSqlTask(c *stringsWalk, n *Node, v *jobs.SqlTask) { + if n.isRef() { + return + } + if !n.IsRef("alert") { + if x := v.Alert; x != nil { + ch := n.Child("alert") + stringsJobsSqlTaskAlert(c, &ch, x) + } + } + if !n.IsRef("dashboard") { + if x := v.Dashboard; x != nil { + ch := n.Child("dashboard") + stringsJobsSqlTaskDashboard(c, &ch, x) + } + } + if !n.IsRef("file") { + if x := v.File; x != nil { + ch := n.Child("file") + stringsJobsSqlTaskFile(c, &ch, x) + } + } + if !n.IsRef("parameters") { + if len(v.Parameters) > 0 { + cn := n.Child("parameters") + for _, k := range sortedKeys(v.Parameters) { + if s := string(v.Parameters[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Parameters[k] = (r) + } + } + } + } + } + if !n.IsRef("query") { + if x := v.Query; x != nil { + ch := n.Child("query") + stringsJobsSqlTaskQuery(c, &ch, x) + } + } + if s := string(v.WarehouseId); c.match(s) { + ch := n.Child("warehouse_id") + if r, ok := c.fn(&ch, s); ok { + v.WarehouseId = (r) + } + } +} + +func stringsJobsFileArrivalTriggerConfiguration(c *stringsWalk, n *Node, v *jobs.FileArrivalTriggerConfiguration) { + if n.isRef() { + return + } + if s := string(v.Url); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.Url = (r) + } + } +} + +func stringsJobsModelTriggerConfiguration(c *stringsWalk, n *Node, v *jobs.ModelTriggerConfiguration) { + if n.isRef() { + return + } + if !n.IsRef("aliases") { + if len(v.Aliases) > 0 { + cn := n.Child("aliases") + for i := range v.Aliases { + if s := string(v.Aliases[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Aliases[i] = (r) + } + } + } + } + } + if s := string(v.Condition); c.match(s) { + ch := n.Child("condition") + if r, ok := c.fn(&ch, s); ok { + v.Condition = jobs.ModelTriggerConfigurationCondition(r) + } + } + if c.mode == EmptyFields && v.SecurableName == "" && Forced(v.ForceSendFields, "SecurableName") { + ch := n.Child("securable_name") + c.fn(&ch, "") + } + if s := string(v.SecurableName); c.match(s) { + ch := n.Child("securable_name") + if r, ok := c.fn(&ch, s); ok { + v.SecurableName = (r) + } + } +} + +func stringsJobsPeriodicTriggerConfiguration(c *stringsWalk, n *Node, v *jobs.PeriodicTriggerConfiguration) { + if n.isRef() { + return + } + if s := string(v.Unit); c.match(s) { + ch := n.Child("unit") + if r, ok := c.fn(&ch, s); ok { + v.Unit = jobs.PeriodicTriggerConfigurationTimeUnit(r) + } + } +} + +func stringsJobsTableUpdateTriggerConfiguration(c *stringsWalk, n *Node, v *jobs.TableUpdateTriggerConfiguration) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Condition == "" && Forced(v.ForceSendFields, "Condition") { + ch := n.Child("condition") + c.fn(&ch, "") + } + if s := string(v.Condition); c.match(s) { + ch := n.Child("condition") + if r, ok := c.fn(&ch, s); ok { + v.Condition = jobs.Condition(r) + } + } + if !n.IsRef("table_names") { + if len(v.TableNames) > 0 { + cn := n.Child("table_names") + for i := range v.TableNames { + if s := string(v.TableNames[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.TableNames[i] = (r) + } + } + } + } + } +} + +func stringsJobsContinuousTriggerConfiguration(c *stringsWalk, n *Node, v *jobs.ContinuousTriggerConfiguration) { + if n.isRef() { + return + } + if !n.IsRef("maintenance_window") { + if x := v.MaintenanceWindow; x != nil { + ch := n.Child("maintenance_window") + stringsJobsMaintenanceWindow(c, &ch, x) + } + } + if s := string(v.TaskRetryMode); c.match(s) { + ch := n.Child("task_retry_mode") + if r, ok := c.fn(&ch, s); ok { + v.TaskRetryMode = jobs.TaskRetryMode(r) + } + } +} + +func stringsJobsCronTriggerConfiguration(c *stringsWalk, n *Node, v *jobs.CronTriggerConfiguration) { + if n.isRef() { + return + } + if s := string(v.QuartzCronExpression); c.match(s) { + ch := n.Child("quartz_cron_expression") + if r, ok := c.fn(&ch, s); ok { + v.QuartzCronExpression = (r) + } + } + if s := string(v.TimezoneId); c.match(s) { + ch := n.Child("timezone_id") + if r, ok := c.fn(&ch, s); ok { + v.TimezoneId = (r) + } + } +} + +func stringsJobsWebhook(c *stringsWalk, n *Node, v *jobs.Webhook) { + if n.isRef() { + return + } + if s := string(v.Id); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.Id = (r) + } + } +} + +func stringsResourcesJobRunTrigger(c *stringsWalk, n *Node, v *resources.JobRunTrigger) { + if n.isRef() { + return + } + if !n.IsRef("on_file_change") { + if c.mode == EmptyFields && v.OnFileChange != nil && *v.OnFileChange == "" { + ch := n.Child("on_file_change") + c.fn(&ch, "") + } + if x := v.OnFileChange; x != nil { + if s := string(*x); c.match(s) { + ch := n.Child("on_file_change") + if r, ok := c.fn(&ch, s); ok { + *x = (r) + } + } + } + } +} + +func stringsResourcesJobRunTriggersState(c *stringsWalk, n *Node, v *resources.JobRunTriggersState) { + if n.isRef() { + return + } + if s := string(v.OnBundleDeploy); c.match(s) { + ch := n.Child("on_bundle_deploy") + if r, ok := c.fn(&ch, s); ok { + v.OnBundleDeploy = (r) + } + } + if !n.IsRef("on_file_change") { + if len(v.OnFileChange) > 0 { + cn := n.Child("on_file_change") + for _, k := range sortedKeys(v.OnFileChange) { + if s := string(v.OnFileChange[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.OnFileChange[k] = (r) + } + } + } + } + } +} + +func stringsPipelinesPipelineClusterAutoscale(c *stringsWalk, n *Node, v *pipelines.PipelineClusterAutoscale) { + if n.isRef() { + return + } + if s := string(v.Mode); c.match(s) { + ch := n.Child("mode") + if r, ok := c.fn(&ch, s); ok { + v.Mode = pipelines.PipelineClusterAutoscaleMode(r) + } + } +} + +func stringsPipelinesConnectionParameters(c *stringsWalk, n *Node, v *pipelines.ConnectionParameters) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.SourceCatalog == "" && Forced(v.ForceSendFields, "SourceCatalog") { + ch := n.Child("source_catalog") + c.fn(&ch, "") + } + if s := string(v.SourceCatalog); c.match(s) { + ch := n.Child("source_catalog") + if r, ok := c.fn(&ch, s); ok { + v.SourceCatalog = (r) + } + } +} + +func stringsPipelinesDataStagingOptions(c *stringsWalk, n *Node, v *pipelines.DataStagingOptions) { + if n.isRef() { + return + } + if s := string(v.CatalogName); c.match(s) { + ch := n.Child("catalog_name") + if r, ok := c.fn(&ch, s); ok { + v.CatalogName = (r) + } + } + if s := string(v.SchemaName); c.match(s) { + ch := n.Child("schema_name") + if r, ok := c.fn(&ch, s); ok { + v.SchemaName = (r) + } + } + if c.mode == EmptyFields && v.VolumeName == "" && Forced(v.ForceSendFields, "VolumeName") { + ch := n.Child("volume_name") + c.fn(&ch, "") + } + if s := string(v.VolumeName); c.match(s) { + ch := n.Child("volume_name") + if r, ok := c.fn(&ch, s); ok { + v.VolumeName = (r) + } + } +} + +func stringsPipelinesOperationTimeWindow(c *stringsWalk, n *Node, v *pipelines.OperationTimeWindow) { + if n.isRef() { + return + } + if !n.IsRef("days_of_week") { + if len(v.DaysOfWeek) > 0 { + cn := n.Child("days_of_week") + for i := range v.DaysOfWeek { + if s := string(v.DaysOfWeek[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.DaysOfWeek[i] = pipelines.DayOfWeek(r) + } + } + } + } + } + if c.mode == EmptyFields && v.TimeZoneId == "" && Forced(v.ForceSendFields, "TimeZoneId") { + ch := n.Child("time_zone_id") + c.fn(&ch, "") + } + if s := string(v.TimeZoneId); c.match(s) { + ch := n.Child("time_zone_id") + if r, ok := c.fn(&ch, s); ok { + v.TimeZoneId = (r) + } + } +} + +func stringsPipelinesIngestionConfig(c *stringsWalk, n *Node, v *pipelines.IngestionConfig) { + if n.isRef() { + return + } + if !n.IsRef("report") { + if x := v.Report; x != nil { + ch := n.Child("report") + stringsPipelinesReportSpec(c, &ch, x) + } + } + if !n.IsRef("schema") { + if x := v.Schema; x != nil { + ch := n.Child("schema") + stringsPipelinesSchemaSpec(c, &ch, x) + } + } + if !n.IsRef("table") { + if x := v.Table; x != nil { + ch := n.Child("table") + stringsPipelinesTableSpec(c, &ch, x) + } + } +} + +func stringsPipelinesSourceConfig(c *stringsWalk, n *Node, v *pipelines.SourceConfig) { + if n.isRef() { + return + } + if !n.IsRef("api_source_connector_config") { + if x := v.ApiSourceConnectorConfig; x != nil { + ch := n.Child("api_source_connector_config") + stringsPipelinesApiSourceConnectorConfig(c, &ch, x) + } + } + if !n.IsRef("catalog") { + if x := v.Catalog; x != nil { + ch := n.Child("catalog") + stringsPipelinesSourceCatalogConfig(c, &ch, x) + } + } + if !n.IsRef("google_ads_config") { + if x := v.GoogleAdsConfig; x != nil { + ch := n.Child("google_ads_config") + stringsPipelinesGoogleAdsConfig(c, &ch, x) + } + } +} + +func stringsPipelinesTableSpecificConfig(c *stringsWalk, n *Node, v *pipelines.TableSpecificConfig) { + if n.isRef() { + return + } + if !n.IsRef("clustering_columns") { + if len(v.ClusteringColumns) > 0 { + cn := n.Child("clustering_columns") + for i := range v.ClusteringColumns { + if s := string(v.ClusteringColumns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ClusteringColumns[i] = (r) + } + } + } + } + } + if !n.IsRef("exclude_columns") { + if len(v.ExcludeColumns) > 0 { + cn := n.Child("exclude_columns") + for i := range v.ExcludeColumns { + if s := string(v.ExcludeColumns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ExcludeColumns[i] = (r) + } + } + } + } + } + if !n.IsRef("include_columns") { + if len(v.IncludeColumns) > 0 { + cn := n.Child("include_columns") + for i := range v.IncludeColumns { + if s := string(v.IncludeColumns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.IncludeColumns[i] = (r) + } + } + } + } + } + if !n.IsRef("primary_keys") { + if len(v.PrimaryKeys) > 0 { + cn := n.Child("primary_keys") + for i := range v.PrimaryKeys { + if s := string(v.PrimaryKeys[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.PrimaryKeys[i] = (r) + } + } + } + } + } + if !n.IsRef("query_based_connector_config") { + if x := v.QueryBasedConnectorConfig; x != nil { + ch := n.Child("query_based_connector_config") + stringsPipelinesIngestionPipelineDefinitionTableSpecificConfigQueryBasedConnectorConfig(c, &ch, x) + } + } + if c.mode == EmptyFields && v.RowFilter == "" && Forced(v.ForceSendFields, "RowFilter") { + ch := n.Child("row_filter") + c.fn(&ch, "") + } + if s := string(v.RowFilter); c.match(s) { + ch := n.Child("row_filter") + if r, ok := c.fn(&ch, s); ok { + v.RowFilter = (r) + } + } + if c.mode == EmptyFields && v.ScdType == "" && Forced(v.ForceSendFields, "ScdType") { + ch := n.Child("scd_type") + c.fn(&ch, "") + } + if s := string(v.ScdType); c.match(s) { + ch := n.Child("scd_type") + if r, ok := c.fn(&ch, s); ok { + v.ScdType = pipelines.TableSpecificConfigScdType(r) + } + } + if !n.IsRef("sequence_by") { + if len(v.SequenceBy) > 0 { + cn := n.Child("sequence_by") + for i := range v.SequenceBy { + if s := string(v.SequenceBy[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.SequenceBy[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.SourceMetadataColumn == "" && Forced(v.ForceSendFields, "SourceMetadataColumn") { + ch := n.Child("source_metadata_column") + c.fn(&ch, "") + } + if s := string(v.SourceMetadataColumn); c.match(s) { + ch := n.Child("source_metadata_column") + if r, ok := c.fn(&ch, s); ok { + v.SourceMetadataColumn = (r) + } + } + if !n.IsRef("table_properties") { + if len(v.TableProperties) > 0 { + cn := n.Child("table_properties") + for _, k := range sortedKeys(v.TableProperties) { + if s := string(v.TableProperties[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.TableProperties[k] = (r) + } + } + } + } + } + if !n.IsRef("workday_report_parameters") { + if x := v.WorkdayReportParameters; x != nil { + ch := n.Child("workday_report_parameters") + stringsPipelinesIngestionPipelineDefinitionWorkdayReportParameters(c, &ch, x) + } + } +} + +func stringsPipelinesFileLibrary(c *stringsWalk, n *Node, v *pipelines.FileLibrary) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Path == "" && Forced(v.ForceSendFields, "Path") { + ch := n.Child("path") + c.fn(&ch, "") + } + if s := string(v.Path); c.match(s) { + ch := n.Child("path") + if r, ok := c.fn(&ch, s); ok { + v.Path = (r) + } + } +} + +func stringsPipelinesPathPattern(c *stringsWalk, n *Node, v *pipelines.PathPattern) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Include == "" && Forced(v.ForceSendFields, "Include") { + ch := n.Child("include") + c.fn(&ch, "") + } + if s := string(v.Include); c.match(s) { + ch := n.Child("include") + if r, ok := c.fn(&ch, s); ok { + v.Include = (r) + } + } +} + +func stringsComputeMavenLibrary(c *stringsWalk, n *Node, v *compute.MavenLibrary) { + if n.isRef() { + return + } + if s := string(v.Coordinates); c.match(s) { + ch := n.Child("coordinates") + if r, ok := c.fn(&ch, s); ok { + v.Coordinates = (r) + } + } + if !n.IsRef("exclusions") { + if len(v.Exclusions) > 0 { + cn := n.Child("exclusions") + for i := range v.Exclusions { + if s := string(v.Exclusions[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Exclusions[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.Repo == "" && Forced(v.ForceSendFields, "Repo") { + ch := n.Child("repo") + c.fn(&ch, "") + } + if s := string(v.Repo); c.match(s) { + ch := n.Child("repo") + if r, ok := c.fn(&ch, s); ok { + v.Repo = (r) + } + } +} + +func stringsPipelinesNotebookLibrary(c *stringsWalk, n *Node, v *pipelines.NotebookLibrary) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Path == "" && Forced(v.ForceSendFields, "Path") { + ch := n.Child("path") + c.fn(&ch, "") + } + if s := string(v.Path); c.match(s) { + ch := n.Child("path") + if r, ok := c.fn(&ch, s); ok { + v.Path = (r) + } + } +} + +func stringsPipelinesCronTrigger(c *stringsWalk, n *Node, v *pipelines.CronTrigger) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.QuartzCronSchedule == "" && Forced(v.ForceSendFields, "QuartzCronSchedule") { + ch := n.Child("quartz_cron_schedule") + c.fn(&ch, "") + } + if s := string(v.QuartzCronSchedule); c.match(s) { + ch := n.Child("quartz_cron_schedule") + if r, ok := c.fn(&ch, s); ok { + v.QuartzCronSchedule = (r) + } + } + if c.mode == EmptyFields && v.TimezoneId == "" && Forced(v.ForceSendFields, "TimezoneId") { + ch := n.Child("timezone_id") + c.fn(&ch, "") + } + if s := string(v.TimezoneId); c.match(s) { + ch := n.Child("timezone_id") + if r, ok := c.fn(&ch, s); ok { + v.TimezoneId = (r) + } + } +} + +func stringsMlAggregationFunction(c *stringsWalk, n *Node, v *ml.AggregationFunction) { + if n.isRef() { + return + } + if !n.IsRef("approx_count_distinct") { + if x := v.ApproxCountDistinct; x != nil { + ch := n.Child("approx_count_distinct") + stringsMlApproxCountDistinctFunction(c, &ch, x) + } + } + if !n.IsRef("approx_percentile") { + if x := v.ApproxPercentile; x != nil { + ch := n.Child("approx_percentile") + stringsMlApproxPercentileFunction(c, &ch, x) + } + } + if !n.IsRef("avg") { + if x := v.Avg; x != nil { + ch := n.Child("avg") + stringsMlAvgFunction(c, &ch, x) + } + } + if !n.IsRef("count_function") { + if x := v.CountFunction; x != nil { + ch := n.Child("count_function") + stringsMlCountFunction(c, &ch, x) + } + } + if !n.IsRef("first") { + if x := v.First; x != nil { + ch := n.Child("first") + stringsMlFirstFunction(c, &ch, x) + } + } + if !n.IsRef("first_distinct") { + if x := v.FirstDistinct; x != nil { + ch := n.Child("first_distinct") + stringsMlFirstDistinctFunction(c, &ch, x) + } + } + if !n.IsRef("first_n") { + if x := v.FirstN; x != nil { + ch := n.Child("first_n") + stringsMlFirstNFunction(c, &ch, x) + } + } + if !n.IsRef("last") { + if x := v.Last; x != nil { + ch := n.Child("last") + stringsMlLastFunction(c, &ch, x) + } + } + if !n.IsRef("last_distinct") { + if x := v.LastDistinct; x != nil { + ch := n.Child("last_distinct") + stringsMlLastDistinctFunction(c, &ch, x) + } + } + if !n.IsRef("last_n") { + if x := v.LastN; x != nil { + ch := n.Child("last_n") + stringsMlLastNFunction(c, &ch, x) + } + } + if !n.IsRef("max") { + if x := v.Max; x != nil { + ch := n.Child("max") + stringsMlMaxFunction(c, &ch, x) + } + } + if !n.IsRef("min") { + if x := v.Min; x != nil { + ch := n.Child("min") + stringsMlMinFunction(c, &ch, x) + } + } + if !n.IsRef("stddev_pop") { + if x := v.StddevPop; x != nil { + ch := n.Child("stddev_pop") + stringsMlStddevPopFunction(c, &ch, x) + } + } + if !n.IsRef("stddev_samp") { + if x := v.StddevSamp; x != nil { + ch := n.Child("stddev_samp") + stringsMlStddevSampFunction(c, &ch, x) + } + } + if !n.IsRef("sum") { + if x := v.Sum; x != nil { + ch := n.Child("sum") + stringsMlSumFunction(c, &ch, x) + } + } + if !n.IsRef("time_window") { + if x := v.TimeWindow; x != nil { + ch := n.Child("time_window") + stringsMlTimeWindow(c, &ch, x) + } + } + if !n.IsRef("var_pop") { + if x := v.VarPop; x != nil { + ch := n.Child("var_pop") + stringsMlVarPopFunction(c, &ch, x) + } + } + if !n.IsRef("var_samp") { + if x := v.VarSamp; x != nil { + ch := n.Child("var_samp") + stringsMlVarSampFunction(c, &ch, x) + } + } +} + +func stringsMlColumnSelection(c *stringsWalk, n *Node, v *ml.ColumnSelection) { + if n.isRef() { + return + } + if s := string(v.Column); c.match(s) { + ch := n.Child("column") + if r, ok := c.fn(&ch, s); ok { + v.Column = (r) + } + } +} + +func stringsMlCustomUdf(c *stringsWalk, n *Node, v *ml.CustomUdf) { + if n.isRef() { + return + } + if s := string(v.FunctionPath); c.match(s) { + ch := n.Child("function_path") + if r, ok := c.fn(&ch, s); ok { + v.FunctionPath = (r) + } + } + if !n.IsRef("input_bindings") { + if len(v.InputBindings) > 0 { + cn := n.Child("input_bindings") + for i := range v.InputBindings { + { + ch := cn.Elem(i) + stringsMlInputBinding(c, &ch, &v.InputBindings[i]) + } + } + } + } +} + +func stringsMlFunctionExtraParameter(c *stringsWalk, n *Node, v *ml.FunctionExtraParameter) { + if n.isRef() { + return + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = (r) + } + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } +} + +func stringsMlDeltaTableSource(c *stringsWalk, n *Node, v *ml.DeltaTableSource) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DataframeSchema == "" && Forced(v.ForceSendFields, "DataframeSchema") { + ch := n.Child("dataframe_schema") + c.fn(&ch, "") + } + if s := string(v.DataframeSchema); c.match(s) { + ch := n.Child("dataframe_schema") + if r, ok := c.fn(&ch, s); ok { + v.DataframeSchema = (r) + } + } + if !n.IsRef("entity_columns") { + if len(v.EntityColumns) > 0 { + cn := n.Child("entity_columns") + for i := range v.EntityColumns { + if s := string(v.EntityColumns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.EntityColumns[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.FilterCondition == "" && Forced(v.ForceSendFields, "FilterCondition") { + ch := n.Child("filter_condition") + c.fn(&ch, "") + } + if s := string(v.FilterCondition); c.match(s) { + ch := n.Child("filter_condition") + if r, ok := c.fn(&ch, s); ok { + v.FilterCondition = (r) + } + } + if s := string(v.FullName); c.match(s) { + ch := n.Child("full_name") + if r, ok := c.fn(&ch, s); ok { + v.FullName = (r) + } + } + if c.mode == EmptyFields && v.TimeseriesColumn == "" && Forced(v.ForceSendFields, "TimeseriesColumn") { + ch := n.Child("timeseries_column") + c.fn(&ch, "") + } + if s := string(v.TimeseriesColumn); c.match(s) { + ch := n.Child("timeseries_column") + if r, ok := c.fn(&ch, s); ok { + v.TimeseriesColumn = (r) + } + } + if c.mode == EmptyFields && v.TransformationSql == "" && Forced(v.ForceSendFields, "TransformationSql") { + ch := n.Child("transformation_sql") + c.fn(&ch, "") + } + if s := string(v.TransformationSql); c.match(s) { + ch := n.Child("transformation_sql") + if r, ok := c.fn(&ch, s); ok { + v.TransformationSql = (r) + } + } +} + +func stringsMlFeatureViewSource(c *stringsWalk, n *Node, v *ml.FeatureViewSource) { + if n.isRef() { + return + } + if !n.IsRef("feature_references") { + if len(v.FeatureReferences) > 0 { + cn := n.Child("feature_references") + for i := range v.FeatureReferences { + { + ch := cn.Elem(i) + stringsMlFeatureReference(c, &ch, &v.FeatureReferences[i]) + } + } + } + } +} + +func stringsMlKafkaSource(c *stringsWalk, n *Node, v *ml.KafkaSource) { + if n.isRef() { + return + } + if !n.IsRef("entity_column_identifiers") { + if len(v.EntityColumnIdentifiers) > 0 { + cn := n.Child("entity_column_identifiers") + for i := range v.EntityColumnIdentifiers { + { + ch := cn.Elem(i) + stringsMlColumnIdentifier(c, &ch, &v.EntityColumnIdentifiers[i]) + } + } + } + } + if c.mode == EmptyFields && v.FilterCondition == "" && Forced(v.ForceSendFields, "FilterCondition") { + ch := n.Child("filter_condition") + c.fn(&ch, "") + } + if s := string(v.FilterCondition); c.match(s) { + ch := n.Child("filter_condition") + if r, ok := c.fn(&ch, s); ok { + v.FilterCondition = (r) + } + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if !n.IsRef("timeseries_column_identifier") { + if x := v.TimeseriesColumnIdentifier; x != nil { + ch := n.Child("timeseries_column_identifier") + stringsMlColumnIdentifier(c, &ch, x) + } + } +} + +func stringsMlRequestSource(c *stringsWalk, n *Node, v *ml.RequestSource) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DataframeSchema == "" && Forced(v.ForceSendFields, "DataframeSchema") { + ch := n.Child("dataframe_schema") + c.fn(&ch, "") + } + if s := string(v.DataframeSchema); c.match(s) { + ch := n.Child("dataframe_schema") + if r, ok := c.fn(&ch, s); ok { + v.DataframeSchema = (r) + } + } + if !n.IsRef("flat_schema") { + if x := v.FlatSchema; x != nil { + ch := n.Child("flat_schema") + stringsMlFlatSchema(c, &ch, x) + } + } +} + +func stringsMlSdpSource(c *stringsWalk, n *Node, v *ml.SdpSource) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DataframeSchema == "" && Forced(v.ForceSendFields, "DataframeSchema") { + ch := n.Child("dataframe_schema") + c.fn(&ch, "") + } + if s := string(v.DataframeSchema); c.match(s) { + ch := n.Child("dataframe_schema") + if r, ok := c.fn(&ch, s); ok { + v.DataframeSchema = (r) + } + } + if c.mode == EmptyFields && v.FilterCondition == "" && Forced(v.ForceSendFields, "FilterCondition") { + ch := n.Child("filter_condition") + c.fn(&ch, "") + } + if s := string(v.FilterCondition); c.match(s) { + ch := n.Child("filter_condition") + if r, ok := c.fn(&ch, s); ok { + v.FilterCondition = (r) + } + } + if !n.IsRef("input_tables") { + if len(v.InputTables) > 0 { + cn := n.Child("input_tables") + for i := range v.InputTables { + if s := string(v.InputTables[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.InputTables[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.OutputTable == "" && Forced(v.ForceSendFields, "OutputTable") { + ch := n.Child("output_table") + c.fn(&ch, "") + } + if s := string(v.OutputTable); c.match(s) { + ch := n.Child("output_table") + if r, ok := c.fn(&ch, s); ok { + v.OutputTable = (r) + } + } + if c.mode == EmptyFields && v.PipelineId == "" && Forced(v.ForceSendFields, "PipelineId") { + ch := n.Child("pipeline_id") + c.fn(&ch, "") + } + if s := string(v.PipelineId); c.match(s) { + ch := n.Child("pipeline_id") + if r, ok := c.fn(&ch, s); ok { + v.PipelineId = (r) + } + } + if c.mode == EmptyFields && v.PipelineState == "" && Forced(v.ForceSendFields, "PipelineState") { + ch := n.Child("pipeline_state") + c.fn(&ch, "") + } + if s := string(v.PipelineState); c.match(s) { + ch := n.Child("pipeline_state") + if r, ok := c.fn(&ch, s); ok { + v.PipelineState = ml.SdpSourceState(r) + } + } + if c.mode == EmptyFields && v.TransformationSql == "" && Forced(v.ForceSendFields, "TransformationSql") { + ch := n.Child("transformation_sql") + c.fn(&ch, "") + } + if s := string(v.TransformationSql); c.match(s) { + ch := n.Child("transformation_sql") + if r, ok := c.fn(&ch, s); ok { + v.TransformationSql = (r) + } + } +} + +func stringsMlStreamSource(c *stringsWalk, n *Node, v *ml.StreamSource) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DataframeSchema == "" && Forced(v.ForceSendFields, "DataframeSchema") { + ch := n.Child("dataframe_schema") + c.fn(&ch, "") + } + if s := string(v.DataframeSchema); c.match(s) { + ch := n.Child("dataframe_schema") + if r, ok := c.fn(&ch, s); ok { + v.DataframeSchema = (r) + } + } + if c.mode == EmptyFields && v.FilterCondition == "" && Forced(v.ForceSendFields, "FilterCondition") { + ch := n.Child("filter_condition") + c.fn(&ch, "") + } + if s := string(v.FilterCondition); c.match(s) { + ch := n.Child("filter_condition") + if r, ok := c.fn(&ch, s); ok { + v.FilterCondition = (r) + } + } + if s := string(v.FullName); c.match(s) { + ch := n.Child("full_name") + if r, ok := c.fn(&ch, s); ok { + v.FullName = (r) + } + } + if c.mode == EmptyFields && v.TransformationSql == "" && Forced(v.ForceSendFields, "TransformationSql") { + ch := n.Child("transformation_sql") + c.fn(&ch, "") + } + if s := string(v.TransformationSql); c.match(s) { + ch := n.Child("transformation_sql") + if r, ok := c.fn(&ch, s); ok { + v.TransformationSql = (r) + } + } +} + +func stringsMlContinuousWindow(c *stringsWalk, n *Node, v *ml.ContinuousWindow) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Offset == "" && Forced(v.ForceSendFields, "Offset") { + ch := n.Child("offset") + c.fn(&ch, "") + } + if s := string(v.Offset); c.match(s) { + ch := n.Child("offset") + if r, ok := c.fn(&ch, s); ok { + v.Offset = (r) + } + } + if s := string(v.WindowDuration); c.match(s) { + ch := n.Child("window_duration") + if r, ok := c.fn(&ch, s); ok { + v.WindowDuration = (r) + } + } +} + +func stringsMlSlidingWindow(c *stringsWalk, n *Node, v *ml.SlidingWindow) { + if n.isRef() { + return + } + if s := string(v.SlideDuration); c.match(s) { + ch := n.Child("slide_duration") + if r, ok := c.fn(&ch, s); ok { + v.SlideDuration = (r) + } + } + if c.mode == EmptyFields && v.WindowDuration == "" && Forced(v.ForceSendFields, "WindowDuration") { + ch := n.Child("window_duration") + c.fn(&ch, "") + } + if s := string(v.WindowDuration); c.match(s) { + ch := n.Child("window_duration") + if r, ok := c.fn(&ch, s); ok { + v.WindowDuration = (r) + } + } +} + +func stringsMlTumblingWindow(c *stringsWalk, n *Node, v *ml.TumblingWindow) { + if n.isRef() { + return + } + if s := string(v.WindowDuration); c.match(s) { + ch := n.Child("window_duration") + if r, ok := c.fn(&ch, s); ok { + v.WindowDuration = (r) + } + } +} + +func stringsMlUcTraceLocation(c *stringsWalk, n *Node, v *ml.UcTraceLocation) { + if n.isRef() { + return + } + if s := string(v.Catalog); c.match(s) { + ch := n.Child("catalog") + if r, ok := c.fn(&ch, s); ok { + v.Catalog = (r) + } + } + if c.mode == EmptyFields && v.EffectiveTablePrefix == "" && Forced(v.ForceSendFields, "EffectiveTablePrefix") { + ch := n.Child("effective_table_prefix") + c.fn(&ch, "") + } + if s := string(v.EffectiveTablePrefix); c.match(s) { + ch := n.Child("effective_table_prefix") + if r, ok := c.fn(&ch, s); ok { + v.EffectiveTablePrefix = (r) + } + } + if s := string(v.Schema); c.match(s) { + ch := n.Child("schema") + if r, ok := c.fn(&ch, s); ok { + v.Schema = (r) + } + } + if c.mode == EmptyFields && v.TablePrefix == "" && Forced(v.ForceSendFields, "TablePrefix") { + ch := n.Child("table_prefix") + c.fn(&ch, "") + } + if s := string(v.TablePrefix); c.match(s) { + ch := n.Child("table_prefix") + if r, ok := c.fn(&ch, s); ok { + v.TablePrefix = (r) + } + } +} + +func stringsServingAiGatewayGuardrails(c *stringsWalk, n *Node, v *serving.AiGatewayGuardrails) { + if n.isRef() { + return + } + if !n.IsRef("input") { + if x := v.Input; x != nil { + ch := n.Child("input") + stringsServingAiGatewayGuardrailParameters(c, &ch, x) + } + } + if !n.IsRef("output") { + if x := v.Output; x != nil { + ch := n.Child("output") + stringsServingAiGatewayGuardrailParameters(c, &ch, x) + } + } +} + +func stringsServingAiGatewayInferenceTableConfig(c *stringsWalk, n *Node, v *serving.AiGatewayInferenceTableConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.CatalogName == "" && Forced(v.ForceSendFields, "CatalogName") { + ch := n.Child("catalog_name") + c.fn(&ch, "") + } + if s := string(v.CatalogName); c.match(s) { + ch := n.Child("catalog_name") + if r, ok := c.fn(&ch, s); ok { + v.CatalogName = (r) + } + } + if c.mode == EmptyFields && v.SchemaName == "" && Forced(v.ForceSendFields, "SchemaName") { + ch := n.Child("schema_name") + c.fn(&ch, "") + } + if s := string(v.SchemaName); c.match(s) { + ch := n.Child("schema_name") + if r, ok := c.fn(&ch, s); ok { + v.SchemaName = (r) + } + } + if c.mode == EmptyFields && v.TableNamePrefix == "" && Forced(v.ForceSendFields, "TableNamePrefix") { + ch := n.Child("table_name_prefix") + c.fn(&ch, "") + } + if s := string(v.TableNamePrefix); c.match(s) { + ch := n.Child("table_name_prefix") + if r, ok := c.fn(&ch, s); ok { + v.TableNamePrefix = (r) + } + } +} + +func stringsServingAiGatewayRateLimit(c *stringsWalk, n *Node, v *serving.AiGatewayRateLimit) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Key == "" && Forced(v.ForceSendFields, "Key") { + ch := n.Child("key") + c.fn(&ch, "") + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = serving.AiGatewayRateLimitKey(r) + } + } + if c.mode == EmptyFields && v.Principal == "" && Forced(v.ForceSendFields, "Principal") { + ch := n.Child("principal") + c.fn(&ch, "") + } + if s := string(v.Principal); c.match(s) { + ch := n.Child("principal") + if r, ok := c.fn(&ch, s); ok { + v.Principal = (r) + } + } + if s := string(v.RenewalPeriod); c.match(s) { + ch := n.Child("renewal_period") + if r, ok := c.fn(&ch, s); ok { + v.RenewalPeriod = serving.AiGatewayRateLimitRenewalPeriod(r) + } + } +} + +func stringsServingAutoCaptureConfigInput(c *stringsWalk, n *Node, v *serving.AutoCaptureConfigInput) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.CatalogName == "" && Forced(v.ForceSendFields, "CatalogName") { + ch := n.Child("catalog_name") + c.fn(&ch, "") + } + if s := string(v.CatalogName); c.match(s) { + ch := n.Child("catalog_name") + if r, ok := c.fn(&ch, s); ok { + v.CatalogName = (r) + } + } + if c.mode == EmptyFields && v.SchemaName == "" && Forced(v.ForceSendFields, "SchemaName") { + ch := n.Child("schema_name") + c.fn(&ch, "") + } + if s := string(v.SchemaName); c.match(s) { + ch := n.Child("schema_name") + if r, ok := c.fn(&ch, s); ok { + v.SchemaName = (r) + } + } + if c.mode == EmptyFields && v.TableNamePrefix == "" && Forced(v.ForceSendFields, "TableNamePrefix") { + ch := n.Child("table_name_prefix") + c.fn(&ch, "") + } + if s := string(v.TableNamePrefix); c.match(s) { + ch := n.Child("table_name_prefix") + if r, ok := c.fn(&ch, s); ok { + v.TableNamePrefix = (r) + } + } +} + +func stringsServingServedEntityInput(c *stringsWalk, n *Node, v *serving.ServedEntityInput) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.EntityName == "" && Forced(v.ForceSendFields, "EntityName") { + ch := n.Child("entity_name") + c.fn(&ch, "") + } + if s := string(v.EntityName); c.match(s) { + ch := n.Child("entity_name") + if r, ok := c.fn(&ch, s); ok { + v.EntityName = (r) + } + } + if c.mode == EmptyFields && v.EntityVersion == "" && Forced(v.ForceSendFields, "EntityVersion") { + ch := n.Child("entity_version") + c.fn(&ch, "") + } + if s := string(v.EntityVersion); c.match(s) { + ch := n.Child("entity_version") + if r, ok := c.fn(&ch, s); ok { + v.EntityVersion = (r) + } + } + if !n.IsRef("environment_vars") { + if len(v.EnvironmentVars) > 0 { + cn := n.Child("environment_vars") + for _, k := range sortedKeys(v.EnvironmentVars) { + if s := string(v.EnvironmentVars[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.EnvironmentVars[k] = (r) + } + } + } + } + } + if !n.IsRef("external_model") { + if x := v.ExternalModel; x != nil { + ch := n.Child("external_model") + stringsServingExternalModel(c, &ch, x) + } + } + if c.mode == EmptyFields && v.InstanceProfileArn == "" && Forced(v.ForceSendFields, "InstanceProfileArn") { + ch := n.Child("instance_profile_arn") + c.fn(&ch, "") + } + if s := string(v.InstanceProfileArn); c.match(s) { + ch := n.Child("instance_profile_arn") + if r, ok := c.fn(&ch, s); ok { + v.InstanceProfileArn = (r) + } + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if c.mode == EmptyFields && v.WorkloadSize == "" && Forced(v.ForceSendFields, "WorkloadSize") { + ch := n.Child("workload_size") + c.fn(&ch, "") + } + if s := string(v.WorkloadSize); c.match(s) { + ch := n.Child("workload_size") + if r, ok := c.fn(&ch, s); ok { + v.WorkloadSize = (r) + } + } + if c.mode == EmptyFields && v.WorkloadType == "" && Forced(v.ForceSendFields, "WorkloadType") { + ch := n.Child("workload_type") + c.fn(&ch, "") + } + if s := string(v.WorkloadType); c.match(s) { + ch := n.Child("workload_type") + if r, ok := c.fn(&ch, s); ok { + v.WorkloadType = serving.ServingModelWorkloadType(r) + } + } +} + +func stringsServingServedModelInput(c *stringsWalk, n *Node, v *serving.ServedModelInput) { + if n.isRef() { + return + } + if !n.IsRef("environment_vars") { + if len(v.EnvironmentVars) > 0 { + cn := n.Child("environment_vars") + for _, k := range sortedKeys(v.EnvironmentVars) { + if s := string(v.EnvironmentVars[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.EnvironmentVars[k] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.InstanceProfileArn == "" && Forced(v.ForceSendFields, "InstanceProfileArn") { + ch := n.Child("instance_profile_arn") + c.fn(&ch, "") + } + if s := string(v.InstanceProfileArn); c.match(s) { + ch := n.Child("instance_profile_arn") + if r, ok := c.fn(&ch, s); ok { + v.InstanceProfileArn = (r) + } + } + if s := string(v.ModelName); c.match(s) { + ch := n.Child("model_name") + if r, ok := c.fn(&ch, s); ok { + v.ModelName = (r) + } + } + if s := string(v.ModelVersion); c.match(s) { + ch := n.Child("model_version") + if r, ok := c.fn(&ch, s); ok { + v.ModelVersion = (r) + } + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if c.mode == EmptyFields && v.WorkloadSize == "" && Forced(v.ForceSendFields, "WorkloadSize") { + ch := n.Child("workload_size") + c.fn(&ch, "") + } + if s := string(v.WorkloadSize); c.match(s) { + ch := n.Child("workload_size") + if r, ok := c.fn(&ch, s); ok { + v.WorkloadSize = (r) + } + } + if c.mode == EmptyFields && v.WorkloadType == "" && Forced(v.ForceSendFields, "WorkloadType") { + ch := n.Child("workload_type") + c.fn(&ch, "") + } + if s := string(v.WorkloadType); c.match(s) { + ch := n.Child("workload_type") + if r, ok := c.fn(&ch, s); ok { + v.WorkloadType = serving.ServedModelInputWorkloadType(r) + } + } +} + +func stringsServingTrafficConfig(c *stringsWalk, n *Node, v *serving.TrafficConfig) { + if n.isRef() { + return + } + if !n.IsRef("routes") { + if len(v.Routes) > 0 { + cn := n.Child("routes") + for i := range v.Routes { + { + ch := cn.Elem(i) + stringsServingRoute(c, &ch, &v.Routes[i]) + } + } + } + } +} + +func stringsServingTelemetryInferenceTableConfig(c *stringsWalk, n *Node, v *serving.TelemetryInferenceTableConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } +} + +func stringsServingUnityCatalogTableNames(c *stringsWalk, n *Node, v *serving.UnityCatalogTableNames) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.AnnotationsTable == "" && Forced(v.ForceSendFields, "AnnotationsTable") { + ch := n.Child("annotations_table") + c.fn(&ch, "") + } + if s := string(v.AnnotationsTable); c.match(s) { + ch := n.Child("annotations_table") + if r, ok := c.fn(&ch, s); ok { + v.AnnotationsTable = (r) + } + } + if c.mode == EmptyFields && v.LogsTable == "" && Forced(v.ForceSendFields, "LogsTable") { + ch := n.Child("logs_table") + c.fn(&ch, "") + } + if s := string(v.LogsTable); c.match(s) { + ch := n.Child("logs_table") + if r, ok := c.fn(&ch, s); ok { + v.LogsTable = (r) + } + } + if c.mode == EmptyFields && v.MetricsTable == "" && Forced(v.ForceSendFields, "MetricsTable") { + ch := n.Child("metrics_table") + c.fn(&ch, "") + } + if s := string(v.MetricsTable); c.match(s) { + ch := n.Child("metrics_table") + if r, ok := c.fn(&ch, s); ok { + v.MetricsTable = (r) + } + } + if c.mode == EmptyFields && v.TracesTable == "" && Forced(v.ForceSendFields, "TracesTable") { + ch := n.Child("traces_table") + c.fn(&ch, "") + } + if s := string(v.TracesTable); c.match(s) { + ch := n.Child("traces_table") + if r, ok := c.fn(&ch, s); ok { + v.TracesTable = (r) + } + } +} + +func stringsCatalogInferenceTableConfig(c *stringsWalk, n *Node, v *catalog.InferenceTableConfig) { + if n.isRef() { + return + } + if s := string(v.Parent); c.match(s) { + ch := n.Child("parent") + if r, ok := c.fn(&ch, s); ok { + v.Parent = (r) + } + } + if c.mode == EmptyFields && v.Table == "" && Forced(v.ForceSendFields, "Table") { + ch := n.Child("table") + c.fn(&ch, "") + } + if s := string(v.Table); c.match(s) { + ch := n.Child("table") + if r, ok := c.fn(&ch, s); ok { + v.Table = (r) + } + } + if c.mode == EmptyFields && v.TableNamePrefix == "" && Forced(v.ForceSendFields, "TableNamePrefix") { + ch := n.Child("table_name_prefix") + c.fn(&ch, "") + } + if s := string(v.TableNamePrefix); c.match(s) { + ch := n.Child("table_name_prefix") + if r, ok := c.fn(&ch, s); ok { + v.TableNamePrefix = (r) + } + } +} + +func stringsCatalogRateLimit(c *stringsWalk, n *Node, v *catalog.RateLimit) { + if n.isRef() { + return + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = catalog.RateLimitRateLimitKey(r) + } + } + if c.mode == EmptyFields && v.Principal == "" && Forced(v.ForceSendFields, "Principal") { + ch := n.Child("principal") + c.fn(&ch, "") + } + if s := string(v.Principal); c.match(s) { + ch := n.Child("principal") + if r, ok := c.fn(&ch, s); ok { + v.Principal = (r) + } + } + if s := string(v.RenewalPeriod); c.match(s) { + ch := n.Child("renewal_period") + if r, ok := c.fn(&ch, s); ok { + v.RenewalPeriod = catalog.RateLimitRateLimitRenewalPeriod(r) + } + } +} + +func stringsCatalogModelServiceConfigRoutingConfig(c *stringsWalk, n *Node, v *catalog.ModelServiceConfigRoutingConfig) { + if n.isRef() { + return + } + if !n.IsRef("destinations") { + if len(v.Destinations) > 0 { + cn := n.Child("destinations") + for i := range v.Destinations { + { + ch := cn.Elem(i) + stringsCatalogModelServiceConfigDestinationConfig(c, &ch, &v.Destinations[i]) + } + } + } + } + if !n.IsRef("fallback") { + if x := v.Fallback; x != nil { + ch := n.Child("fallback") + stringsCatalogModelServiceConfigFallbackConfig(c, &ch, x) + } + } +} + +func stringsCatalogMcpServiceConfigSourceConnection(c *stringsWalk, n *Node, v *catalog.McpServiceConfigSourceConnection) { + if n.isRef() { + return + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if !n.IsRef("options") { + if len(v.Options) > 0 { + cn := n.Child("options") + for _, k := range sortedKeys(v.Options) { + if s := string(v.Options[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Options[k] = (r) + } + } + } + } + } +} + +func stringsCatalogModelProviderServiceConfigAmazonBedrockProviderConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigAmazonBedrockProviderConfig) { + if n.isRef() { + return + } + if !n.IsRef("direct") { + if x := v.Direct; x != nil { + ch := n.Child("direct") + stringsCatalogModelProviderServiceConfigAmazonBedrockProviderDirectConfig(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigAnthropicProviderConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigAnthropicProviderConfig) { + if n.isRef() { + return + } + if !n.IsRef("direct") { + if x := v.Direct; x != nil { + ch := n.Child("direct") + stringsCatalogModelProviderServiceConfigAnthropicProviderDirectConfig(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigAzureOpenAiProviderConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigAzureOpenAiProviderConfig) { + if n.isRef() { + return + } + if !n.IsRef("direct") { + if x := v.Direct; x != nil { + ch := n.Child("direct") + stringsCatalogModelProviderServiceConfigAzureOpenAiProviderDirectConfig(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigCustomProviderConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigCustomProviderConfig) { + if n.isRef() { + return + } + if !n.IsRef("direct") { + if x := v.Direct; x != nil { + ch := n.Child("direct") + stringsCatalogModelProviderServiceConfigCustomProviderDirectConfig(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigGeminiEnterpriseProviderConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigGeminiEnterpriseProviderConfig) { + if n.isRef() { + return + } + if !n.IsRef("direct") { + if x := v.Direct; x != nil { + ch := n.Child("direct") + stringsCatalogModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigMicrosoftFoundryProviderConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigMicrosoftFoundryProviderConfig) { + if n.isRef() { + return + } + if !n.IsRef("direct") { + if x := v.Direct; x != nil { + ch := n.Child("direct") + stringsCatalogModelProviderServiceConfigMicrosoftFoundryProviderDirectConfig(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigOpenAiProviderConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigOpenAiProviderConfig) { + if n.isRef() { + return + } + if !n.IsRef("direct") { + if x := v.Direct; x != nil { + ch := n.Child("direct") + stringsCatalogModelProviderServiceConfigOpenAiProviderDirectConfig(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigModelTargetConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigModelTargetConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.EndpointRoute == "" && Forced(v.ForceSendFields, "EndpointRoute") { + ch := n.Child("endpoint_route") + c.fn(&ch, "") + } + if s := string(v.EndpointRoute); c.match(s) { + ch := n.Child("endpoint_route") + if r, ok := c.fn(&ch, s); ok { + v.EndpointRoute = (r) + } + } + if s := string(v.Model); c.match(s) { + ch := n.Child("model") + if r, ok := c.fn(&ch, s); ok { + v.Model = (r) + } + } + if !n.IsRef("native_api_types") { + if len(v.NativeApiTypes) > 0 { + cn := n.Child("native_api_types") + for i := range v.NativeApiTypes { + if s := string(v.NativeApiTypes[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.NativeApiTypes[i] = (r) + } + } + } + } + } +} + +func stringsCatalogMonitorDestination(c *stringsWalk, n *Node, v *catalog.MonitorDestination) { + if n.isRef() { + return + } + if !n.IsRef("email_addresses") { + if len(v.EmailAddresses) > 0 { + cn := n.Child("email_addresses") + for i := range v.EmailAddresses { + if s := string(v.EmailAddresses[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.EmailAddresses[i] = (r) + } + } + } + } + } +} + +func stringsCatalogAzureEncryptionSettings(c *stringsWalk, n *Node, v *catalog.AzureEncryptionSettings) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.AzureCmkAccessConnectorId == "" && Forced(v.ForceSendFields, "AzureCmkAccessConnectorId") { + ch := n.Child("azure_cmk_access_connector_id") + c.fn(&ch, "") + } + if s := string(v.AzureCmkAccessConnectorId); c.match(s) { + ch := n.Child("azure_cmk_access_connector_id") + if r, ok := c.fn(&ch, s); ok { + v.AzureCmkAccessConnectorId = (r) + } + } + if c.mode == EmptyFields && v.AzureCmkManagedIdentityId == "" && Forced(v.ForceSendFields, "AzureCmkManagedIdentityId") { + ch := n.Child("azure_cmk_managed_identity_id") + c.fn(&ch, "") + } + if s := string(v.AzureCmkManagedIdentityId); c.match(s) { + ch := n.Child("azure_cmk_managed_identity_id") + if r, ok := c.fn(&ch, s); ok { + v.AzureCmkManagedIdentityId = (r) + } + } + if s := string(v.AzureTenantId); c.match(s) { + ch := n.Child("azure_tenant_id") + if r, ok := c.fn(&ch, s); ok { + v.AzureTenantId = (r) + } + } +} + +func stringsCatalogAzureQueueStorage(c *stringsWalk, n *Node, v *catalog.AzureQueueStorage) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ManagedResourceId == "" && Forced(v.ForceSendFields, "ManagedResourceId") { + ch := n.Child("managed_resource_id") + c.fn(&ch, "") + } + if s := string(v.ManagedResourceId); c.match(s) { + ch := n.Child("managed_resource_id") + if r, ok := c.fn(&ch, s); ok { + v.ManagedResourceId = (r) + } + } + if c.mode == EmptyFields && v.QueueUrl == "" && Forced(v.ForceSendFields, "QueueUrl") { + ch := n.Child("queue_url") + c.fn(&ch, "") + } + if s := string(v.QueueUrl); c.match(s) { + ch := n.Child("queue_url") + if r, ok := c.fn(&ch, s); ok { + v.QueueUrl = (r) + } + } + if c.mode == EmptyFields && v.ResourceGroup == "" && Forced(v.ForceSendFields, "ResourceGroup") { + ch := n.Child("resource_group") + c.fn(&ch, "") + } + if s := string(v.ResourceGroup); c.match(s) { + ch := n.Child("resource_group") + if r, ok := c.fn(&ch, s); ok { + v.ResourceGroup = (r) + } + } + if c.mode == EmptyFields && v.SubscriptionId == "" && Forced(v.ForceSendFields, "SubscriptionId") { + ch := n.Child("subscription_id") + c.fn(&ch, "") + } + if s := string(v.SubscriptionId); c.match(s) { + ch := n.Child("subscription_id") + if r, ok := c.fn(&ch, s); ok { + v.SubscriptionId = (r) + } + } +} + +func stringsCatalogGcpPubsub(c *stringsWalk, n *Node, v *catalog.GcpPubsub) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ManagedResourceId == "" && Forced(v.ForceSendFields, "ManagedResourceId") { + ch := n.Child("managed_resource_id") + c.fn(&ch, "") + } + if s := string(v.ManagedResourceId); c.match(s) { + ch := n.Child("managed_resource_id") + if r, ok := c.fn(&ch, s); ok { + v.ManagedResourceId = (r) + } + } + if c.mode == EmptyFields && v.SubscriptionName == "" && Forced(v.ForceSendFields, "SubscriptionName") { + ch := n.Child("subscription_name") + c.fn(&ch, "") + } + if s := string(v.SubscriptionName); c.match(s) { + ch := n.Child("subscription_name") + if r, ok := c.fn(&ch, s); ok { + v.SubscriptionName = (r) + } + } +} + +func stringsCatalogAwsSqsQueue(c *stringsWalk, n *Node, v *catalog.AwsSqsQueue) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ManagedResourceId == "" && Forced(v.ForceSendFields, "ManagedResourceId") { + ch := n.Child("managed_resource_id") + c.fn(&ch, "") + } + if s := string(v.ManagedResourceId); c.match(s) { + ch := n.Child("managed_resource_id") + if r, ok := c.fn(&ch, s); ok { + v.ManagedResourceId = (r) + } + } + if c.mode == EmptyFields && v.QueueUrl == "" && Forced(v.ForceSendFields, "QueueUrl") { + ch := n.Child("queue_url") + c.fn(&ch, "") + } + if s := string(v.QueueUrl); c.match(s) { + ch := n.Child("queue_url") + if r, ok := c.fn(&ch, s); ok { + v.QueueUrl = (r) + } + } +} + +func stringsCatalogSseEncryptionDetails(c *stringsWalk, n *Node, v *catalog.SseEncryptionDetails) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Algorithm == "" && Forced(v.ForceSendFields, "Algorithm") { + ch := n.Child("algorithm") + c.fn(&ch, "") + } + if s := string(v.Algorithm); c.match(s) { + ch := n.Child("algorithm") + if r, ok := c.fn(&ch, s); ok { + v.Algorithm = catalog.SseEncryptionDetailsAlgorithm(r) + } + } + if c.mode == EmptyFields && v.AwsKmsKeyArn == "" && Forced(v.ForceSendFields, "AwsKmsKeyArn") { + ch := n.Child("aws_kms_key_arn") + c.fn(&ch, "") + } + if s := string(v.AwsKmsKeyArn); c.match(s) { + ch := n.Child("aws_kms_key_arn") + if r, ok := c.fn(&ch, s); ok { + v.AwsKmsKeyArn = (r) + } + } +} + +func stringsComputeRCranLibrary(c *stringsWalk, n *Node, v *compute.RCranLibrary) { + if n.isRef() { + return + } + if s := string(v.Package); c.match(s) { + ch := n.Child("package") + if r, ok := c.fn(&ch, s); ok { + v.Package = (r) + } + } + if c.mode == EmptyFields && v.Repo == "" && Forced(v.ForceSendFields, "Repo") { + ch := n.Child("repo") + c.fn(&ch, "") + } + if s := string(v.Repo); c.match(s) { + ch := n.Child("repo") + if r, ok := c.fn(&ch, s); ok { + v.Repo = (r) + } + } +} + +func stringsComputePythonPyPiLibrary(c *stringsWalk, n *Node, v *compute.PythonPyPiLibrary) { + if n.isRef() { + return + } + if s := string(v.Package); c.match(s) { + ch := n.Child("package") + if r, ok := c.fn(&ch, s); ok { + v.Package = (r) + } + } + if c.mode == EmptyFields && v.Repo == "" && Forced(v.ForceSendFields, "Repo") { + ch := n.Child("repo") + c.fn(&ch, "") + } + if s := string(v.Repo); c.match(s) { + ch := n.Child("repo") + if r, ok := c.fn(&ch, s); ok { + v.Repo = (r) + } + } +} + +func stringsComputeLogAnalyticsInfo(c *stringsWalk, n *Node, v *compute.LogAnalyticsInfo) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.LogAnalyticsPrimaryKey == "" && Forced(v.ForceSendFields, "LogAnalyticsPrimaryKey") { + ch := n.Child("log_analytics_primary_key") + c.fn(&ch, "") + } + if s := string(v.LogAnalyticsPrimaryKey); c.match(s) { + ch := n.Child("log_analytics_primary_key") + if r, ok := c.fn(&ch, s); ok { + v.LogAnalyticsPrimaryKey = (r) + } + } + if c.mode == EmptyFields && v.LogAnalyticsWorkspaceId == "" && Forced(v.ForceSendFields, "LogAnalyticsWorkspaceId") { + ch := n.Child("log_analytics_workspace_id") + c.fn(&ch, "") + } + if s := string(v.LogAnalyticsWorkspaceId); c.match(s) { + ch := n.Child("log_analytics_workspace_id") + if r, ok := c.fn(&ch, s); ok { + v.LogAnalyticsWorkspaceId = (r) + } + } +} + +func stringsComputeDbfsStorageInfo(c *stringsWalk, n *Node, v *compute.DbfsStorageInfo) { + if n.isRef() { + return + } + if s := string(v.Destination); c.match(s) { + ch := n.Child("destination") + if r, ok := c.fn(&ch, s); ok { + v.Destination = (r) + } + } +} + +func stringsComputeS3StorageInfo(c *stringsWalk, n *Node, v *compute.S3StorageInfo) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.CannedAcl == "" && Forced(v.ForceSendFields, "CannedAcl") { + ch := n.Child("canned_acl") + c.fn(&ch, "") + } + if s := string(v.CannedAcl); c.match(s) { + ch := n.Child("canned_acl") + if r, ok := c.fn(&ch, s); ok { + v.CannedAcl = (r) + } + } + if s := string(v.Destination); c.match(s) { + ch := n.Child("destination") + if r, ok := c.fn(&ch, s); ok { + v.Destination = (r) + } + } + if c.mode == EmptyFields && v.EncryptionType == "" && Forced(v.ForceSendFields, "EncryptionType") { + ch := n.Child("encryption_type") + c.fn(&ch, "") + } + if s := string(v.EncryptionType); c.match(s) { + ch := n.Child("encryption_type") + if r, ok := c.fn(&ch, s); ok { + v.EncryptionType = (r) + } + } + if c.mode == EmptyFields && v.Endpoint == "" && Forced(v.ForceSendFields, "Endpoint") { + ch := n.Child("endpoint") + c.fn(&ch, "") + } + if s := string(v.Endpoint); c.match(s) { + ch := n.Child("endpoint") + if r, ok := c.fn(&ch, s); ok { + v.Endpoint = (r) + } + } + if c.mode == EmptyFields && v.KmsKey == "" && Forced(v.ForceSendFields, "KmsKey") { + ch := n.Child("kms_key") + c.fn(&ch, "") + } + if s := string(v.KmsKey); c.match(s) { + ch := n.Child("kms_key") + if r, ok := c.fn(&ch, s); ok { + v.KmsKey = (r) + } + } + if c.mode == EmptyFields && v.Region == "" && Forced(v.ForceSendFields, "Region") { + ch := n.Child("region") + c.fn(&ch, "") + } + if s := string(v.Region); c.match(s) { + ch := n.Child("region") + if r, ok := c.fn(&ch, s); ok { + v.Region = (r) + } + } +} + +func stringsComputeVolumesStorageInfo(c *stringsWalk, n *Node, v *compute.VolumesStorageInfo) { + if n.isRef() { + return + } + if s := string(v.Destination); c.match(s) { + ch := n.Child("destination") + if r, ok := c.fn(&ch, s); ok { + v.Destination = (r) + } + } +} + +func stringsComputeDockerBasicAuth(c *stringsWalk, n *Node, v *compute.DockerBasicAuth) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Password == "" && Forced(v.ForceSendFields, "Password") { + ch := n.Child("password") + c.fn(&ch, "") + } + if s := string(v.Password); c.match(s) { + ch := n.Child("password") + if r, ok := c.fn(&ch, s); ok { + v.Password = (r) + } + } + if c.mode == EmptyFields && v.Username == "" && Forced(v.ForceSendFields, "Username") { + ch := n.Child("username") + c.fn(&ch, "") + } + if s := string(v.Username); c.match(s) { + ch := n.Child("username") + if r, ok := c.fn(&ch, s); ok { + v.Username = (r) + } + } +} + +func stringsComputeAdlsgen2Info(c *stringsWalk, n *Node, v *compute.Adlsgen2Info) { + if n.isRef() { + return + } + if s := string(v.Destination); c.match(s) { + ch := n.Child("destination") + if r, ok := c.fn(&ch, s); ok { + v.Destination = (r) + } + } +} + +func stringsComputeLocalFileInfo(c *stringsWalk, n *Node, v *compute.LocalFileInfo) { + if n.isRef() { + return + } + if s := string(v.Destination); c.match(s) { + ch := n.Child("destination") + if r, ok := c.fn(&ch, s); ok { + v.Destination = (r) + } + } +} + +func stringsComputeGcsStorageInfo(c *stringsWalk, n *Node, v *compute.GcsStorageInfo) { + if n.isRef() { + return + } + if s := string(v.Destination); c.match(s) { + ch := n.Child("destination") + if r, ok := c.fn(&ch, s); ok { + v.Destination = (r) + } + } +} + +func stringsComputeWorkspaceStorageInfo(c *stringsWalk, n *Node, v *compute.WorkspaceStorageInfo) { + if n.isRef() { + return + } + if s := string(v.Destination); c.match(s) { + ch := n.Child("destination") + if r, ok := c.fn(&ch, s); ok { + v.Destination = (r) + } + } +} + +func stringsResourcesAppEnvVar(c *stringsWalk, n *Node, v *resources.AppEnvVar) { + if n.isRef() { + return + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } + if s := string(v.ValueFrom); c.match(s) { + ch := n.Child("value_from") + if r, ok := c.fn(&ch, s); ok { + v.ValueFrom = (r) + } + } +} + +func stringsAppsAppDeploymentArtifacts(c *stringsWalk, n *Node, v *apps.AppDeploymentArtifacts) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.SourceCodePath == "" && Forced(v.ForceSendFields, "SourceCodePath") { + ch := n.Child("source_code_path") + c.fn(&ch, "") + } + if s := string(v.SourceCodePath); c.match(s) { + ch := n.Child("source_code_path") + if r, ok := c.fn(&ch, s); ok { + v.SourceCodePath = (r) + } + } +} + +func stringsAppsEnvVar(c *stringsWalk, n *Node, v *apps.EnvVar) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } + if c.mode == EmptyFields && v.ValueFrom == "" && Forced(v.ForceSendFields, "ValueFrom") { + ch := n.Child("value_from") + c.fn(&ch, "") + } + if s := string(v.ValueFrom); c.match(s) { + ch := n.Child("value_from") + if r, ok := c.fn(&ch, s); ok { + v.ValueFrom = (r) + } + } +} + +func stringsAppsAppHealthCheck(c *stringsWalk, n *Node, v *apps.AppHealthCheck) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Path == "" && Forced(v.ForceSendFields, "Path") { + ch := n.Child("path") + c.fn(&ch, "") + } + if s := string(v.Path); c.match(s) { + ch := n.Child("path") + if r, ok := c.fn(&ch, s); ok { + v.Path = (r) + } + } +} + +func stringsAppsAppDeploymentStatus(c *stringsWalk, n *Node, v *apps.AppDeploymentStatus) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Message == "" && Forced(v.ForceSendFields, "Message") { + ch := n.Child("message") + c.fn(&ch, "") + } + if s := string(v.Message); c.match(s) { + ch := n.Child("message") + if r, ok := c.fn(&ch, s); ok { + v.Message = (r) + } + } + if c.mode == EmptyFields && v.State == "" && Forced(v.ForceSendFields, "State") { + ch := n.Child("state") + c.fn(&ch, "") + } + if s := string(v.State); c.match(s) { + ch := n.Child("state") + if r, ok := c.fn(&ch, s); ok { + v.State = apps.AppDeploymentState(r) + } + } +} + +func stringsAppsAppResourceApp(c *stringsWalk, n *Node, v *apps.AppResourceApp) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if c.mode == EmptyFields && v.Permission == "" && Forced(v.ForceSendFields, "Permission") { + ch := n.Child("permission") + c.fn(&ch, "") + } + if s := string(v.Permission); c.match(s) { + ch := n.Child("permission") + if r, ok := c.fn(&ch, s); ok { + v.Permission = apps.AppResourceAppAppPermission(r) + } + } +} + +func stringsAppsAppResourceDatabase(c *stringsWalk, n *Node, v *apps.AppResourceDatabase) { + if n.isRef() { + return + } + if s := string(v.DatabaseName); c.match(s) { + ch := n.Child("database_name") + if r, ok := c.fn(&ch, s); ok { + v.DatabaseName = (r) + } + } + if s := string(v.InstanceName); c.match(s) { + ch := n.Child("instance_name") + if r, ok := c.fn(&ch, s); ok { + v.InstanceName = (r) + } + } + if s := string(v.Permission); c.match(s) { + ch := n.Child("permission") + if r, ok := c.fn(&ch, s); ok { + v.Permission = apps.AppResourceDatabaseDatabasePermission(r) + } + } +} + +func stringsAppsAppResourceExperiment(c *stringsWalk, n *Node, v *apps.AppResourceExperiment) { + if n.isRef() { + return + } + if s := string(v.ExperimentId); c.match(s) { + ch := n.Child("experiment_id") + if r, ok := c.fn(&ch, s); ok { + v.ExperimentId = (r) + } + } + if s := string(v.Permission); c.match(s) { + ch := n.Child("permission") + if r, ok := c.fn(&ch, s); ok { + v.Permission = apps.AppResourceExperimentExperimentPermission(r) + } + } +} + +func stringsAppsAppResourceGenieSpace(c *stringsWalk, n *Node, v *apps.AppResourceGenieSpace) { + if n.isRef() { + return + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if s := string(v.Permission); c.match(s) { + ch := n.Child("permission") + if r, ok := c.fn(&ch, s); ok { + v.Permission = apps.AppResourceGenieSpaceGenieSpacePermission(r) + } + } + if s := string(v.SpaceId); c.match(s) { + ch := n.Child("space_id") + if r, ok := c.fn(&ch, s); ok { + v.SpaceId = (r) + } + } +} + +func stringsAppsAppResourceJob(c *stringsWalk, n *Node, v *apps.AppResourceJob) { + if n.isRef() { + return + } + if s := string(v.Id); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.Id = (r) + } + } + if s := string(v.Permission); c.match(s) { + ch := n.Child("permission") + if r, ok := c.fn(&ch, s); ok { + v.Permission = apps.AppResourceJobJobPermission(r) + } + } +} + +func stringsAppsAppResourcePostgres(c *stringsWalk, n *Node, v *apps.AppResourcePostgres) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Branch == "" && Forced(v.ForceSendFields, "Branch") { + ch := n.Child("branch") + c.fn(&ch, "") + } + if s := string(v.Branch); c.match(s) { + ch := n.Child("branch") + if r, ok := c.fn(&ch, s); ok { + v.Branch = (r) + } + } + if c.mode == EmptyFields && v.Database == "" && Forced(v.ForceSendFields, "Database") { + ch := n.Child("database") + c.fn(&ch, "") + } + if s := string(v.Database); c.match(s) { + ch := n.Child("database") + if r, ok := c.fn(&ch, s); ok { + v.Database = (r) + } + } + if c.mode == EmptyFields && v.Permission == "" && Forced(v.ForceSendFields, "Permission") { + ch := n.Child("permission") + c.fn(&ch, "") + } + if s := string(v.Permission); c.match(s) { + ch := n.Child("permission") + if r, ok := c.fn(&ch, s); ok { + v.Permission = apps.AppResourcePostgresPostgresPermission(r) + } + } +} + +func stringsAppsAppResourceSecret(c *stringsWalk, n *Node, v *apps.AppResourceSecret) { + if n.isRef() { + return + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = (r) + } + } + if s := string(v.Permission); c.match(s) { + ch := n.Child("permission") + if r, ok := c.fn(&ch, s); ok { + v.Permission = apps.AppResourceSecretSecretPermission(r) + } + } + if s := string(v.Scope); c.match(s) { + ch := n.Child("scope") + if r, ok := c.fn(&ch, s); ok { + v.Scope = (r) + } + } +} + +func stringsAppsAppResourceServingEndpoint(c *stringsWalk, n *Node, v *apps.AppResourceServingEndpoint) { + if n.isRef() { + return + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if s := string(v.Permission); c.match(s) { + ch := n.Child("permission") + if r, ok := c.fn(&ch, s); ok { + v.Permission = apps.AppResourceServingEndpointServingEndpointPermission(r) + } + } +} + +func stringsAppsAppResourceSqlWarehouse(c *stringsWalk, n *Node, v *apps.AppResourceSqlWarehouse) { + if n.isRef() { + return + } + if s := string(v.Id); c.match(s) { + ch := n.Child("id") + if r, ok := c.fn(&ch, s); ok { + v.Id = (r) + } + } + if s := string(v.Permission); c.match(s) { + ch := n.Child("permission") + if r, ok := c.fn(&ch, s); ok { + v.Permission = apps.AppResourceSqlWarehouseSqlWarehousePermission(r) + } + } +} + +func stringsAppsAppResourceUcSecurable(c *stringsWalk, n *Node, v *apps.AppResourceUcSecurable) { + if n.isRef() { + return + } + if s := string(v.Permission); c.match(s) { + ch := n.Child("permission") + if r, ok := c.fn(&ch, s); ok { + v.Permission = apps.AppResourceUcSecurableUcSecurablePermission(r) + } + } + if s := string(v.SecurableFullName); c.match(s) { + ch := n.Child("securable_full_name") + if r, ok := c.fn(&ch, s); ok { + v.SecurableFullName = (r) + } + } + if c.mode == EmptyFields && v.SecurableKind == "" && Forced(v.ForceSendFields, "SecurableKind") { + ch := n.Child("securable_kind") + c.fn(&ch, "") + } + if s := string(v.SecurableKind); c.match(s) { + ch := n.Child("securable_kind") + if r, ok := c.fn(&ch, s); ok { + v.SecurableKind = (r) + } + } + if s := string(v.SecurableType); c.match(s) { + ch := n.Child("securable_type") + if r, ok := c.fn(&ch, s); ok { + v.SecurableType = apps.AppResourceUcSecurableUcSecurableType(r) + } + } +} + +func stringsAppsUnityCatalog(c *stringsWalk, n *Node, v *apps.UnityCatalog) { + if n.isRef() { + return + } + if s := string(v.LogsTable); c.match(s) { + ch := n.Child("logs_table") + if r, ok := c.fn(&ch, s); ok { + v.LogsTable = (r) + } + } + if s := string(v.MetricsTable); c.match(s) { + ch := n.Child("metrics_table") + if r, ok := c.fn(&ch, s); ok { + v.MetricsTable = (r) + } + } + if s := string(v.TracesTable); c.match(s) { + ch := n.Child("traces_table") + if r, ok := c.fn(&ch, s); ok { + v.TracesTable = (r) + } + } +} + +func stringsSqlAlertV2Notification(c *stringsWalk, n *Node, v *sql.AlertV2Notification) { + if n.isRef() { + return + } + if !n.IsRef("subscriptions") { + if len(v.Subscriptions) > 0 { + cn := n.Child("subscriptions") + for i := range v.Subscriptions { + { + ch := cn.Elem(i) + stringsSqlAlertV2Subscription(c, &ch, &v.Subscriptions[i]) + } + } + } + } +} + +func stringsSqlAlertV2OperandColumn(c *stringsWalk, n *Node, v *sql.AlertV2OperandColumn) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Aggregation == "" && Forced(v.ForceSendFields, "Aggregation") { + ch := n.Child("aggregation") + c.fn(&ch, "") + } + if s := string(v.Aggregation); c.match(s) { + ch := n.Child("aggregation") + if r, ok := c.fn(&ch, s); ok { + v.Aggregation = sql.Aggregation(r) + } + } + if c.mode == EmptyFields && v.Display == "" && Forced(v.ForceSendFields, "Display") { + ch := n.Child("display") + c.fn(&ch, "") + } + if s := string(v.Display); c.match(s) { + ch := n.Child("display") + if r, ok := c.fn(&ch, s); ok { + v.Display = (r) + } + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } +} + +func stringsSqlAlertV2Operand(c *stringsWalk, n *Node, v *sql.AlertV2Operand) { + if n.isRef() { + return + } + if !n.IsRef("column") { + if x := v.Column; x != nil { + ch := n.Child("column") + stringsSqlAlertV2OperandColumn(c, &ch, x) + } + } + if !n.IsRef("value") { + if x := v.Value; x != nil { + ch := n.Child("value") + stringsSqlAlertV2OperandValue(c, &ch, x) + } + } +} + +func stringsSqlEndpointTagPair(c *stringsWalk, n *Node, v *sql.EndpointTagPair) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Key == "" && Forced(v.ForceSendFields, "Key") { + ch := n.Child("key") + c.fn(&ch, "") + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } +} + +func stringsDatabaseSyncedTableContinuousUpdateStatus(c *stringsWalk, n *Node, v *database.SyncedTableContinuousUpdateStatus) { + if n.isRef() { + return + } + if !n.IsRef("initial_pipeline_sync_progress") { + if x := v.InitialPipelineSyncProgress; x != nil { + ch := n.Child("initial_pipeline_sync_progress") + stringsDatabaseSyncedTablePipelineProgress(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Timestamp == "" && Forced(v.ForceSendFields, "Timestamp") { + ch := n.Child("timestamp") + c.fn(&ch, "") + } + if s := string(v.Timestamp); c.match(s) { + ch := n.Child("timestamp") + if r, ok := c.fn(&ch, s); ok { + v.Timestamp = (r) + } + } +} + +func stringsDatabaseSyncedTableFailedStatus(c *stringsWalk, n *Node, v *database.SyncedTableFailedStatus) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Timestamp == "" && Forced(v.ForceSendFields, "Timestamp") { + ch := n.Child("timestamp") + c.fn(&ch, "") + } + if s := string(v.Timestamp); c.match(s) { + ch := n.Child("timestamp") + if r, ok := c.fn(&ch, s); ok { + v.Timestamp = (r) + } + } +} + +func stringsDatabaseSyncedTablePosition(c *stringsWalk, n *Node, v *database.SyncedTablePosition) { + if n.isRef() { + return + } + if !n.IsRef("delta_table_sync_info") { + if x := v.DeltaTableSyncInfo; x != nil { + ch := n.Child("delta_table_sync_info") + stringsDatabaseDeltaTableSyncInfo(c, &ch, x) + } + } + if c.mode == EmptyFields && v.SyncEndTimestamp == "" && Forced(v.ForceSendFields, "SyncEndTimestamp") { + ch := n.Child("sync_end_timestamp") + c.fn(&ch, "") + } + if s := string(v.SyncEndTimestamp); c.match(s) { + ch := n.Child("sync_end_timestamp") + if r, ok := c.fn(&ch, s); ok { + v.SyncEndTimestamp = (r) + } + } + if c.mode == EmptyFields && v.SyncStartTimestamp == "" && Forced(v.ForceSendFields, "SyncStartTimestamp") { + ch := n.Child("sync_start_timestamp") + c.fn(&ch, "") + } + if s := string(v.SyncStartTimestamp); c.match(s) { + ch := n.Child("sync_start_timestamp") + if r, ok := c.fn(&ch, s); ok { + v.SyncStartTimestamp = (r) + } + } +} + +func stringsDatabaseSyncedTableProvisioningStatus(c *stringsWalk, n *Node, v *database.SyncedTableProvisioningStatus) { + if n.isRef() { + return + } + if !n.IsRef("initial_pipeline_sync_progress") { + if x := v.InitialPipelineSyncProgress; x != nil { + ch := n.Child("initial_pipeline_sync_progress") + stringsDatabaseSyncedTablePipelineProgress(c, &ch, x) + } + } +} + +func stringsDatabaseSyncedTableTriggeredUpdateStatus(c *stringsWalk, n *Node, v *database.SyncedTableTriggeredUpdateStatus) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Timestamp == "" && Forced(v.ForceSendFields, "Timestamp") { + ch := n.Child("timestamp") + c.fn(&ch, "") + } + if s := string(v.Timestamp); c.match(s) { + ch := n.Child("timestamp") + if r, ok := c.fn(&ch, s); ok { + v.Timestamp = (r) + } + } + if !n.IsRef("triggered_update_progress") { + if x := v.TriggeredUpdateProgress; x != nil { + ch := n.Child("triggered_update_progress") + stringsDatabaseSyncedTablePipelineProgress(c, &ch, x) + } + } +} + +func stringsDatabaseNewPipelineSpec(c *stringsWalk, n *Node, v *database.NewPipelineSpec) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.BudgetPolicyId == "" && Forced(v.ForceSendFields, "BudgetPolicyId") { + ch := n.Child("budget_policy_id") + c.fn(&ch, "") + } + if s := string(v.BudgetPolicyId); c.match(s) { + ch := n.Child("budget_policy_id") + if r, ok := c.fn(&ch, s); ok { + v.BudgetPolicyId = (r) + } + } + if c.mode == EmptyFields && v.StorageCatalog == "" && Forced(v.ForceSendFields, "StorageCatalog") { + ch := n.Child("storage_catalog") + c.fn(&ch, "") + } + if s := string(v.StorageCatalog); c.match(s) { + ch := n.Child("storage_catalog") + if r, ok := c.fn(&ch, s); ok { + v.StorageCatalog = (r) + } + } + if c.mode == EmptyFields && v.StorageSchema == "" && Forced(v.ForceSendFields, "StorageSchema") { + ch := n.Child("storage_schema") + c.fn(&ch, "") + } + if s := string(v.StorageSchema); c.match(s) { + ch := n.Child("storage_schema") + if r, ok := c.fn(&ch, s); ok { + v.StorageSchema = (r) + } + } +} + +func stringsDatabaseSyncedTableSpecTypeOverride(c *stringsWalk, n *Node, v *database.SyncedTableSpecTypeOverride) { + if n.isRef() { + return + } + if s := string(v.ColumnName); c.match(s) { + ch := n.Child("column_name") + if r, ok := c.fn(&ch, s); ok { + v.ColumnName = (r) + } + } + if s := string(v.PgType); c.match(s) { + ch := n.Child("pg_type") + if r, ok := c.fn(&ch, s); ok { + v.PgType = database.SyncedTableSpecPgSpecificType(r) + } + } +} + +func stringsPostgresWeeklySchedule(c *stringsWalk, n *Node, v *postgres.WeeklySchedule) { + if n.isRef() { + return + } + if s := string(v.DayOfWeek); c.match(s) { + ch := n.Child("day_of_week") + if r, ok := c.fn(&ch, s); ok { + v.DayOfWeek = postgres.DayOfWeek(r) + } + } +} + +func stringsVectorsearchEmbeddingSourceColumn(c *stringsWalk, n *Node, v *vectorsearch.EmbeddingSourceColumn) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.EmbeddingModelEndpointName == "" && Forced(v.ForceSendFields, "EmbeddingModelEndpointName") { + ch := n.Child("embedding_model_endpoint_name") + c.fn(&ch, "") + } + if s := string(v.EmbeddingModelEndpointName); c.match(s) { + ch := n.Child("embedding_model_endpoint_name") + if r, ok := c.fn(&ch, s); ok { + v.EmbeddingModelEndpointName = (r) + } + } + if c.mode == EmptyFields && v.ModelEndpointNameForQuery == "" && Forced(v.ForceSendFields, "ModelEndpointNameForQuery") { + ch := n.Child("model_endpoint_name_for_query") + c.fn(&ch, "") + } + if s := string(v.ModelEndpointNameForQuery); c.match(s) { + ch := n.Child("model_endpoint_name_for_query") + if r, ok := c.fn(&ch, s); ok { + v.ModelEndpointNameForQuery = (r) + } + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } +} + +func stringsVectorsearchEmbeddingVectorColumn(c *stringsWalk, n *Node, v *vectorsearch.EmbeddingVectorColumn) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } +} + +func stringsComputeDiskType(c *stringsWalk, n *Node, v *compute.DiskType) { + if n.isRef() { + return + } + if s := string(v.AzureDiskVolumeType); c.match(s) { + ch := n.Child("azure_disk_volume_type") + if r, ok := c.fn(&ch, s); ok { + v.AzureDiskVolumeType = compute.DiskTypeAzureDiskVolumeType(r) + } + } + if s := string(v.EbsVolumeType); c.match(s) { + ch := n.Child("ebs_volume_type") + if r, ok := c.fn(&ch, s); ok { + v.EbsVolumeType = compute.DiskTypeEbsVolumeType(r) + } + } +} + +func stringsJobsDeploymentSpec(c *stringsWalk, n *Node, v *jobs.DeploymentSpec) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.CommandPath == "" && Forced(v.ForceSendFields, "CommandPath") { + ch := n.Child("command_path") + c.fn(&ch, "") + } + if s := string(v.CommandPath); c.match(s) { + ch := n.Child("command_path") + if r, ok := c.fn(&ch, s); ok { + v.CommandPath = (r) + } + } + if !n.IsRef("compute") { + { + ch := n.Child("compute") + stringsJobsComputeSpec(c, &ch, &v.Compute) + } + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } +} + +func stringsJobsAlertTaskSubscriber(c *stringsWalk, n *Node, v *jobs.AlertTaskSubscriber) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DestinationId == "" && Forced(v.ForceSendFields, "DestinationId") { + ch := n.Child("destination_id") + c.fn(&ch, "") + } + if s := string(v.DestinationId); c.match(s) { + ch := n.Child("destination_id") + if r, ok := c.fn(&ch, s); ok { + v.DestinationId = (r) + } + } + if c.mode == EmptyFields && v.UserName == "" && Forced(v.ForceSendFields, "UserName") { + ch := n.Child("user_name") + c.fn(&ch, "") + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } +} + +func stringsJobsSubscription(c *stringsWalk, n *Node, v *jobs.Subscription) { + if n.isRef() { + return + } + if !n.IsRef("attachment") { + if x := v.Attachment; x != nil { + ch := n.Child("attachment") + stringsJobsSubscriptionAttachment(c, &ch, x) + } + } + if c.mode == EmptyFields && v.CustomSubject == "" && Forced(v.ForceSendFields, "CustomSubject") { + ch := n.Child("custom_subject") + c.fn(&ch, "") + } + if s := string(v.CustomSubject); c.match(s) { + ch := n.Child("custom_subject") + if r, ok := c.fn(&ch, s); ok { + v.CustomSubject = (r) + } + } + if !n.IsRef("subscribers") { + if len(v.Subscribers) > 0 { + cn := n.Child("subscribers") + for i := range v.Subscribers { + { + ch := cn.Elem(i) + stringsJobsSubscriptionSubscriber(c, &ch, &v.Subscribers[i]) + } + } + } + } +} + +func stringsJobsComputeConfig(c *stringsWalk, n *Node, v *jobs.ComputeConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.GpuNodePoolId == "" && Forced(v.ForceSendFields, "GpuNodePoolId") { + ch := n.Child("gpu_node_pool_id") + c.fn(&ch, "") + } + if s := string(v.GpuNodePoolId); c.match(s) { + ch := n.Child("gpu_node_pool_id") + if r, ok := c.fn(&ch, s); ok { + v.GpuNodePoolId = (r) + } + } + if c.mode == EmptyFields && v.GpuType == "" && Forced(v.ForceSendFields, "GpuType") { + ch := n.Child("gpu_type") + c.fn(&ch, "") + } + if s := string(v.GpuType); c.match(s) { + ch := n.Child("gpu_type") + if r, ok := c.fn(&ch, s); ok { + v.GpuType = (r) + } + } +} + +func stringsJobsPowerBiModel(c *stringsWalk, n *Node, v *jobs.PowerBiModel) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.AuthenticationMethod == "" && Forced(v.ForceSendFields, "AuthenticationMethod") { + ch := n.Child("authentication_method") + c.fn(&ch, "") + } + if s := string(v.AuthenticationMethod); c.match(s) { + ch := n.Child("authentication_method") + if r, ok := c.fn(&ch, s); ok { + v.AuthenticationMethod = jobs.AuthenticationMethod(r) + } + } + if c.mode == EmptyFields && v.ModelName == "" && Forced(v.ForceSendFields, "ModelName") { + ch := n.Child("model_name") + c.fn(&ch, "") + } + if s := string(v.ModelName); c.match(s) { + ch := n.Child("model_name") + if r, ok := c.fn(&ch, s); ok { + v.ModelName = (r) + } + } + if c.mode == EmptyFields && v.StorageMode == "" && Forced(v.ForceSendFields, "StorageMode") { + ch := n.Child("storage_mode") + c.fn(&ch, "") + } + if s := string(v.StorageMode); c.match(s) { + ch := n.Child("storage_mode") + if r, ok := c.fn(&ch, s); ok { + v.StorageMode = jobs.StorageMode(r) + } + } + if c.mode == EmptyFields && v.WorkspaceName == "" && Forced(v.ForceSendFields, "WorkspaceName") { + ch := n.Child("workspace_name") + c.fn(&ch, "") + } + if s := string(v.WorkspaceName); c.match(s) { + ch := n.Child("workspace_name") + if r, ok := c.fn(&ch, s); ok { + v.WorkspaceName = (r) + } + } +} + +func stringsJobsPowerBiTable(c *stringsWalk, n *Node, v *jobs.PowerBiTable) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Catalog == "" && Forced(v.ForceSendFields, "Catalog") { + ch := n.Child("catalog") + c.fn(&ch, "") + } + if s := string(v.Catalog); c.match(s) { + ch := n.Child("catalog") + if r, ok := c.fn(&ch, s); ok { + v.Catalog = (r) + } + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if c.mode == EmptyFields && v.Schema == "" && Forced(v.ForceSendFields, "Schema") { + ch := n.Child("schema") + c.fn(&ch, "") + } + if s := string(v.Schema); c.match(s) { + ch := n.Child("schema") + if r, ok := c.fn(&ch, s); ok { + v.Schema = (r) + } + } + if c.mode == EmptyFields && v.StorageMode == "" && Forced(v.ForceSendFields, "StorageMode") { + ch := n.Child("storage_mode") + c.fn(&ch, "") + } + if s := string(v.StorageMode); c.match(s) { + ch := n.Child("storage_mode") + if r, ok := c.fn(&ch, s); ok { + v.StorageMode = jobs.StorageMode(r) + } + } +} + +func stringsJobsPythonOperatorTaskParameter(c *stringsWalk, n *Node, v *jobs.PythonOperatorTaskParameter) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Name == "" && Forced(v.ForceSendFields, "Name") { + ch := n.Child("name") + c.fn(&ch, "") + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } +} + +func stringsJobsSqlTaskAlert(c *stringsWalk, n *Node, v *jobs.SqlTaskAlert) { + if n.isRef() { + return + } + if s := string(v.AlertId); c.match(s) { + ch := n.Child("alert_id") + if r, ok := c.fn(&ch, s); ok { + v.AlertId = (r) + } + } + if !n.IsRef("subscriptions") { + if len(v.Subscriptions) > 0 { + cn := n.Child("subscriptions") + for i := range v.Subscriptions { + { + ch := cn.Elem(i) + stringsJobsSqlTaskSubscription(c, &ch, &v.Subscriptions[i]) + } + } + } + } +} + +func stringsJobsSqlTaskDashboard(c *stringsWalk, n *Node, v *jobs.SqlTaskDashboard) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.CustomSubject == "" && Forced(v.ForceSendFields, "CustomSubject") { + ch := n.Child("custom_subject") + c.fn(&ch, "") + } + if s := string(v.CustomSubject); c.match(s) { + ch := n.Child("custom_subject") + if r, ok := c.fn(&ch, s); ok { + v.CustomSubject = (r) + } + } + if s := string(v.DashboardId); c.match(s) { + ch := n.Child("dashboard_id") + if r, ok := c.fn(&ch, s); ok { + v.DashboardId = (r) + } + } + if !n.IsRef("subscriptions") { + if len(v.Subscriptions) > 0 { + cn := n.Child("subscriptions") + for i := range v.Subscriptions { + { + ch := cn.Elem(i) + stringsJobsSqlTaskSubscription(c, &ch, &v.Subscriptions[i]) + } + } + } + } +} + +func stringsJobsSqlTaskFile(c *stringsWalk, n *Node, v *jobs.SqlTaskFile) { + if n.isRef() { + return + } + if s := string(v.Path); c.match(s) { + ch := n.Child("path") + if r, ok := c.fn(&ch, s); ok { + v.Path = (r) + } + } + if s := string(v.Source); c.match(s) { + ch := n.Child("source") + if r, ok := c.fn(&ch, s); ok { + v.Source = jobs.Source(r) + } + } +} + +func stringsJobsSqlTaskQuery(c *stringsWalk, n *Node, v *jobs.SqlTaskQuery) { + if n.isRef() { + return + } + if s := string(v.QueryId); c.match(s) { + ch := n.Child("query_id") + if r, ok := c.fn(&ch, s); ok { + v.QueryId = (r) + } + } +} + +func stringsPipelinesReportSpec(c *stringsWalk, n *Node, v *pipelines.ReportSpec) { + if n.isRef() { + return + } + if s := string(v.DestinationCatalog); c.match(s) { + ch := n.Child("destination_catalog") + if r, ok := c.fn(&ch, s); ok { + v.DestinationCatalog = (r) + } + } + if s := string(v.DestinationSchema); c.match(s) { + ch := n.Child("destination_schema") + if r, ok := c.fn(&ch, s); ok { + v.DestinationSchema = (r) + } + } + if c.mode == EmptyFields && v.DestinationTable == "" && Forced(v.ForceSendFields, "DestinationTable") { + ch := n.Child("destination_table") + c.fn(&ch, "") + } + if s := string(v.DestinationTable); c.match(s) { + ch := n.Child("destination_table") + if r, ok := c.fn(&ch, s); ok { + v.DestinationTable = (r) + } + } + if s := string(v.SourceUrl); c.match(s) { + ch := n.Child("source_url") + if r, ok := c.fn(&ch, s); ok { + v.SourceUrl = (r) + } + } + if !n.IsRef("table_configuration") { + if x := v.TableConfiguration; x != nil { + ch := n.Child("table_configuration") + stringsPipelinesTableSpecificConfig(c, &ch, x) + } + } +} + +func stringsPipelinesSchemaSpec(c *stringsWalk, n *Node, v *pipelines.SchemaSpec) { + if n.isRef() { + return + } + if !n.IsRef("connector_options") { + if x := v.ConnectorOptions; x != nil { + ch := n.Child("connector_options") + stringsPipelinesConnectorOptions(c, &ch, x) + } + } + if s := string(v.DestinationCatalog); c.match(s) { + ch := n.Child("destination_catalog") + if r, ok := c.fn(&ch, s); ok { + v.DestinationCatalog = (r) + } + } + if s := string(v.DestinationSchema); c.match(s) { + ch := n.Child("destination_schema") + if r, ok := c.fn(&ch, s); ok { + v.DestinationSchema = (r) + } + } + if !n.IsRef("fanout_options") { + if x := v.FanoutOptions; x != nil { + ch := n.Child("fanout_options") + stringsPipelinesIngestionPipelineDefinitionFanoutOptions(c, &ch, x) + } + } + if c.mode == EmptyFields && v.SourceCatalog == "" && Forced(v.ForceSendFields, "SourceCatalog") { + ch := n.Child("source_catalog") + c.fn(&ch, "") + } + if s := string(v.SourceCatalog); c.match(s) { + ch := n.Child("source_catalog") + if r, ok := c.fn(&ch, s); ok { + v.SourceCatalog = (r) + } + } + if c.mode == EmptyFields && v.SourceSchema == "" && Forced(v.ForceSendFields, "SourceSchema") { + ch := n.Child("source_schema") + c.fn(&ch, "") + } + if s := string(v.SourceSchema); c.match(s) { + ch := n.Child("source_schema") + if r, ok := c.fn(&ch, s); ok { + v.SourceSchema = (r) + } + } + if !n.IsRef("table_configuration") { + if x := v.TableConfiguration; x != nil { + ch := n.Child("table_configuration") + stringsPipelinesTableSpecificConfig(c, &ch, x) + } + } +} + +func stringsPipelinesTableSpec(c *stringsWalk, n *Node, v *pipelines.TableSpec) { + if n.isRef() { + return + } + if !n.IsRef("connector_options") { + if x := v.ConnectorOptions; x != nil { + ch := n.Child("connector_options") + stringsPipelinesConnectorOptions(c, &ch, x) + } + } + if s := string(v.DestinationCatalog); c.match(s) { + ch := n.Child("destination_catalog") + if r, ok := c.fn(&ch, s); ok { + v.DestinationCatalog = (r) + } + } + if s := string(v.DestinationSchema); c.match(s) { + ch := n.Child("destination_schema") + if r, ok := c.fn(&ch, s); ok { + v.DestinationSchema = (r) + } + } + if c.mode == EmptyFields && v.DestinationTable == "" && Forced(v.ForceSendFields, "DestinationTable") { + ch := n.Child("destination_table") + c.fn(&ch, "") + } + if s := string(v.DestinationTable); c.match(s) { + ch := n.Child("destination_table") + if r, ok := c.fn(&ch, s); ok { + v.DestinationTable = (r) + } + } + if c.mode == EmptyFields && v.SourceCatalog == "" && Forced(v.ForceSendFields, "SourceCatalog") { + ch := n.Child("source_catalog") + c.fn(&ch, "") + } + if s := string(v.SourceCatalog); c.match(s) { + ch := n.Child("source_catalog") + if r, ok := c.fn(&ch, s); ok { + v.SourceCatalog = (r) + } + } + if c.mode == EmptyFields && v.SourceSchema == "" && Forced(v.ForceSendFields, "SourceSchema") { + ch := n.Child("source_schema") + c.fn(&ch, "") + } + if s := string(v.SourceSchema); c.match(s) { + ch := n.Child("source_schema") + if r, ok := c.fn(&ch, s); ok { + v.SourceSchema = (r) + } + } + if c.mode == EmptyFields && v.SourceTable == "" && Forced(v.ForceSendFields, "SourceTable") { + ch := n.Child("source_table") + c.fn(&ch, "") + } + if s := string(v.SourceTable); c.match(s) { + ch := n.Child("source_table") + if r, ok := c.fn(&ch, s); ok { + v.SourceTable = (r) + } + } + if !n.IsRef("table_configuration") { + if x := v.TableConfiguration; x != nil { + ch := n.Child("table_configuration") + stringsPipelinesTableSpecificConfig(c, &ch, x) + } + } +} + +func stringsPipelinesApiSourceConnectorConfig(c *stringsWalk, n *Node, v *pipelines.ApiSourceConnectorConfig) { + if n.isRef() { + return + } + if !n.IsRef("configs") { + if len(v.Configs) > 0 { + cn := n.Child("configs") + for _, k := range sortedKeys(v.Configs) { + if s := string(v.Configs[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Configs[k] = (r) + } + } + } + } + } +} + +func stringsPipelinesSourceCatalogConfig(c *stringsWalk, n *Node, v *pipelines.SourceCatalogConfig) { + if n.isRef() { + return + } + if !n.IsRef("postgres") { + if x := v.Postgres; x != nil { + ch := n.Child("postgres") + stringsPipelinesPostgresCatalogConfig(c, &ch, x) + } + } + if c.mode == EmptyFields && v.SourceCatalog == "" && Forced(v.ForceSendFields, "SourceCatalog") { + ch := n.Child("source_catalog") + c.fn(&ch, "") + } + if s := string(v.SourceCatalog); c.match(s) { + ch := n.Child("source_catalog") + if r, ok := c.fn(&ch, s); ok { + v.SourceCatalog = (r) + } + } +} + +func stringsPipelinesGoogleAdsConfig(c *stringsWalk, n *Node, v *pipelines.GoogleAdsConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ManagerAccountId == "" && Forced(v.ForceSendFields, "ManagerAccountId") { + ch := n.Child("manager_account_id") + c.fn(&ch, "") + } + if s := string(v.ManagerAccountId); c.match(s) { + ch := n.Child("manager_account_id") + if r, ok := c.fn(&ch, s); ok { + v.ManagerAccountId = (r) + } + } +} + +func stringsPipelinesIngestionPipelineDefinitionTableSpecificConfigQueryBasedConnectorConfig(c *stringsWalk, n *Node, v *pipelines.IngestionPipelineDefinitionTableSpecificConfigQueryBasedConnectorConfig) { + if n.isRef() { + return + } + if !n.IsRef("cursor_columns") { + if len(v.CursorColumns) > 0 { + cn := n.Child("cursor_columns") + for i := range v.CursorColumns { + if s := string(v.CursorColumns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.CursorColumns[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.DeletionCondition == "" && Forced(v.ForceSendFields, "DeletionCondition") { + ch := n.Child("deletion_condition") + c.fn(&ch, "") + } + if s := string(v.DeletionCondition); c.match(s) { + ch := n.Child("deletion_condition") + if r, ok := c.fn(&ch, s); ok { + v.DeletionCondition = (r) + } + } +} + +func stringsPipelinesIngestionPipelineDefinitionWorkdayReportParameters(c *stringsWalk, n *Node, v *pipelines.IngestionPipelineDefinitionWorkdayReportParameters) { + if n.isRef() { + return + } + if !n.IsRef("parameters") { + if len(v.Parameters) > 0 { + cn := n.Child("parameters") + for _, k := range sortedKeys(v.Parameters) { + if s := string(v.Parameters[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Parameters[k] = (r) + } + } + } + } + } + if !n.IsRef("report_parameters") { + if len(v.ReportParameters) > 0 { + cn := n.Child("report_parameters") + for i := range v.ReportParameters { + { + ch := cn.Elem(i) + stringsPipelinesIngestionPipelineDefinitionWorkdayReportParametersQueryKeyValue(c, &ch, &v.ReportParameters[i]) + } + } + } + } +} + +func stringsMlApproxCountDistinctFunction(c *stringsWalk, n *Node, v *ml.ApproxCountDistinctFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlApproxPercentileFunction(c *stringsWalk, n *Node, v *ml.ApproxPercentileFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlAvgFunction(c *stringsWalk, n *Node, v *ml.AvgFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlCountFunction(c *stringsWalk, n *Node, v *ml.CountFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlFirstFunction(c *stringsWalk, n *Node, v *ml.FirstFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlFirstDistinctFunction(c *stringsWalk, n *Node, v *ml.FirstDistinctFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlFirstNFunction(c *stringsWalk, n *Node, v *ml.FirstNFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlLastFunction(c *stringsWalk, n *Node, v *ml.LastFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlLastDistinctFunction(c *stringsWalk, n *Node, v *ml.LastDistinctFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlLastNFunction(c *stringsWalk, n *Node, v *ml.LastNFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlMaxFunction(c *stringsWalk, n *Node, v *ml.MaxFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlMinFunction(c *stringsWalk, n *Node, v *ml.MinFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlStddevPopFunction(c *stringsWalk, n *Node, v *ml.StddevPopFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlStddevSampFunction(c *stringsWalk, n *Node, v *ml.StddevSampFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlSumFunction(c *stringsWalk, n *Node, v *ml.SumFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlVarPopFunction(c *stringsWalk, n *Node, v *ml.VarPopFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlVarSampFunction(c *stringsWalk, n *Node, v *ml.VarSampFunction) { + if n.isRef() { + return + } + if s := string(v.Input); c.match(s) { + ch := n.Child("input") + if r, ok := c.fn(&ch, s); ok { + v.Input = (r) + } + } +} + +func stringsMlInputBinding(c *stringsWalk, n *Node, v *ml.InputBinding) { + if n.isRef() { + return + } + if s := string(v.Column); c.match(s) { + ch := n.Child("column") + if r, ok := c.fn(&ch, s); ok { + v.Column = (r) + } + } + if s := string(v.Parameter); c.match(s) { + ch := n.Child("parameter") + if r, ok := c.fn(&ch, s); ok { + v.Parameter = (r) + } + } +} + +func stringsMlFeatureReference(c *stringsWalk, n *Node, v *ml.FeatureReference) { + if n.isRef() { + return + } + if s := string(v.Feature); c.match(s) { + ch := n.Child("feature") + if r, ok := c.fn(&ch, s); ok { + v.Feature = (r) + } + } +} + +func stringsMlColumnIdentifier(c *stringsWalk, n *Node, v *ml.ColumnIdentifier) { + if n.isRef() { + return + } + if s := string(v.VariantExprPath); c.match(s) { + ch := n.Child("variant_expr_path") + if r, ok := c.fn(&ch, s); ok { + v.VariantExprPath = (r) + } + } +} + +func stringsMlFlatSchema(c *stringsWalk, n *Node, v *ml.FlatSchema) { + if n.isRef() { + return + } + if !n.IsRef("fields") { + if len(v.Fields) > 0 { + cn := n.Child("fields") + for i := range v.Fields { + { + ch := cn.Elem(i) + stringsMlFieldDefinition(c, &ch, &v.Fields[i]) + } + } + } + } +} + +func stringsServingAiGatewayGuardrailParameters(c *stringsWalk, n *Node, v *serving.AiGatewayGuardrailParameters) { + if n.isRef() { + return + } + if !n.IsRef("invalid_keywords") { + if len(v.InvalidKeywords) > 0 { + cn := n.Child("invalid_keywords") + for i := range v.InvalidKeywords { + if s := string(v.InvalidKeywords[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.InvalidKeywords[i] = (r) + } + } + } + } + } + if !n.IsRef("pii") { + if x := v.Pii; x != nil { + ch := n.Child("pii") + stringsServingAiGatewayGuardrailPiiBehavior(c, &ch, x) + } + } + if !n.IsRef("valid_topics") { + if len(v.ValidTopics) > 0 { + cn := n.Child("valid_topics") + for i := range v.ValidTopics { + if s := string(v.ValidTopics[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ValidTopics[i] = (r) + } + } + } + } + } +} + +func stringsServingExternalModel(c *stringsWalk, n *Node, v *serving.ExternalModel) { + if n.isRef() { + return + } + if !n.IsRef("ai21labs_config") { + if x := v.Ai21labsConfig; x != nil { + ch := n.Child("ai21labs_config") + stringsServingAi21LabsConfig(c, &ch, x) + } + } + if !n.IsRef("amazon_bedrock_config") { + if x := v.AmazonBedrockConfig; x != nil { + ch := n.Child("amazon_bedrock_config") + stringsServingAmazonBedrockConfig(c, &ch, x) + } + } + if !n.IsRef("anthropic_config") { + if x := v.AnthropicConfig; x != nil { + ch := n.Child("anthropic_config") + stringsServingAnthropicConfig(c, &ch, x) + } + } + if !n.IsRef("cohere_config") { + if x := v.CohereConfig; x != nil { + ch := n.Child("cohere_config") + stringsServingCohereConfig(c, &ch, x) + } + } + if !n.IsRef("custom_provider_config") { + if x := v.CustomProviderConfig; x != nil { + ch := n.Child("custom_provider_config") + stringsServingCustomProviderConfig(c, &ch, x) + } + } + if !n.IsRef("databricks_model_serving_config") { + if x := v.DatabricksModelServingConfig; x != nil { + ch := n.Child("databricks_model_serving_config") + stringsServingDatabricksModelServingConfig(c, &ch, x) + } + } + if !n.IsRef("google_cloud_vertex_ai_config") { + if x := v.GoogleCloudVertexAiConfig; x != nil { + ch := n.Child("google_cloud_vertex_ai_config") + stringsServingGoogleCloudVertexAiConfig(c, &ch, x) + } + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if !n.IsRef("openai_config") { + if x := v.OpenaiConfig; x != nil { + ch := n.Child("openai_config") + stringsServingOpenAiConfig(c, &ch, x) + } + } + if !n.IsRef("palm_config") { + if x := v.PalmConfig; x != nil { + ch := n.Child("palm_config") + stringsServingPaLmConfig(c, &ch, x) + } + } + if s := string(v.Provider); c.match(s) { + ch := n.Child("provider") + if r, ok := c.fn(&ch, s); ok { + v.Provider = serving.ExternalModelProvider(r) + } + } + if s := string(v.Task); c.match(s) { + ch := n.Child("task") + if r, ok := c.fn(&ch, s); ok { + v.Task = (r) + } + } +} + +func stringsServingRoute(c *stringsWalk, n *Node, v *serving.Route) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ServedEntityName == "" && Forced(v.ForceSendFields, "ServedEntityName") { + ch := n.Child("served_entity_name") + c.fn(&ch, "") + } + if s := string(v.ServedEntityName); c.match(s) { + ch := n.Child("served_entity_name") + if r, ok := c.fn(&ch, s); ok { + v.ServedEntityName = (r) + } + } + if c.mode == EmptyFields && v.ServedModelName == "" && Forced(v.ForceSendFields, "ServedModelName") { + ch := n.Child("served_model_name") + c.fn(&ch, "") + } + if s := string(v.ServedModelName); c.match(s) { + ch := n.Child("served_model_name") + if r, ok := c.fn(&ch, s); ok { + v.ServedModelName = (r) + } + } +} + +func stringsCatalogModelServiceConfigDestinationConfig(c *stringsWalk, n *Node, v *catalog.ModelServiceConfigDestinationConfig) { + if n.isRef() { + return + } + if s := string(v.DestinationType); c.match(s) { + ch := n.Child("destination_type") + if r, ok := c.fn(&ch, s); ok { + v.DestinationType = catalog.ModelServiceConfigDestinationConfigDestinationType(r) + } + } + if !n.IsRef("external_model_config") { + if x := v.ExternalModelConfig; x != nil { + ch := n.Child("external_model_config") + stringsCatalogModelServiceConfigExternalModelConfig(c, &ch, x) + } + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } + if !n.IsRef("pay_per_token_config") { + if x := v.PayPerTokenConfig; x != nil { + ch := n.Child("pay_per_token_config") + stringsCatalogModelServiceConfigPayPerTokenConfig(c, &ch, x) + } + } + if !n.IsRef("provisioned_throughput_config") { + if x := v.ProvisionedThroughputConfig; x != nil { + ch := n.Child("provisioned_throughput_config") + stringsCatalogModelServiceConfigProvisionedThroughputConfig(c, &ch, x) + } + } +} + +func stringsCatalogModelServiceConfigFallbackConfig(c *stringsWalk, n *Node, v *catalog.ModelServiceConfigFallbackConfig) { + if n.isRef() { + return + } + if !n.IsRef("destinations") { + if len(v.Destinations) > 0 { + cn := n.Child("destinations") + for i := range v.Destinations { + { + ch := cn.Elem(i) + stringsCatalogModelServiceConfigDestinationConfig(c, &ch, &v.Destinations[i]) + } + } + } + } +} + +func stringsCatalogModelProviderServiceConfigAmazonBedrockProviderDirectConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigAmazonBedrockProviderDirectConfig) { + if n.isRef() { + return + } + if !n.IsRef("aws_access_key") { + if x := v.AwsAccessKey; x != nil { + ch := n.Child("aws_access_key") + stringsCatalogModelProviderServiceConfigAwsAccessKey(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Region == "" && Forced(v.ForceSendFields, "Region") { + ch := n.Child("region") + c.fn(&ch, "") + } + if s := string(v.Region); c.match(s) { + ch := n.Child("region") + if r, ok := c.fn(&ch, s); ok { + v.Region = (r) + } + } + if !n.IsRef("service_credential") { + if x := v.ServiceCredential; x != nil { + ch := n.Child("service_credential") + stringsCatalogModelProviderServiceConfigServiceCredential(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigAnthropicProviderDirectConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigAnthropicProviderDirectConfig) { + if n.isRef() { + return + } + if !n.IsRef("api_key") { + if x := v.ApiKey; x != nil { + ch := n.Child("api_key") + stringsCatalogModelProviderServiceConfigProviderSecret(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigAzureOpenAiProviderDirectConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigAzureOpenAiProviderDirectConfig) { + if n.isRef() { + return + } + if !n.IsRef("api_key") { + if x := v.ApiKey; x != nil { + ch := n.Child("api_key") + stringsCatalogModelProviderServiceConfigProviderSecret(c, &ch, x) + } + } + if c.mode == EmptyFields && v.BaseUrl == "" && Forced(v.ForceSendFields, "BaseUrl") { + ch := n.Child("base_url") + c.fn(&ch, "") + } + if s := string(v.BaseUrl); c.match(s) { + ch := n.Child("base_url") + if r, ok := c.fn(&ch, s); ok { + v.BaseUrl = (r) + } + } + if !n.IsRef("entra_service_principal") { + if x := v.EntraServicePrincipal; x != nil { + ch := n.Child("entra_service_principal") + stringsCatalogModelProviderServiceConfigEntraServicePrincipal(c, &ch, x) + } + } + if !n.IsRef("service_credential") { + if x := v.ServiceCredential; x != nil { + ch := n.Child("service_credential") + stringsCatalogModelProviderServiceConfigServiceCredential(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigCustomProviderDirectConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigCustomProviderDirectConfig) { + if n.isRef() { + return + } + if !n.IsRef("api_key") { + if x := v.ApiKey; x != nil { + ch := n.Child("api_key") + stringsCatalogModelProviderServiceConfigProviderSecret(c, &ch, x) + } + } + if c.mode == EmptyFields && v.BaseUrl == "" && Forced(v.ForceSendFields, "BaseUrl") { + ch := n.Child("base_url") + c.fn(&ch, "") + } + if s := string(v.BaseUrl); c.match(s) { + ch := n.Child("base_url") + if r, ok := c.fn(&ch, s); ok { + v.BaseUrl = (r) + } + } + if !n.IsRef("header_auth") { + if x := v.HeaderAuth; x != nil { + ch := n.Child("header_auth") + stringsCatalogModelProviderServiceConfigCustomProviderApiKeyHeaderAuth(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigGeminiEnterpriseProviderDirectConfig) { + if n.isRef() { + return + } + if !n.IsRef("api_key") { + if x := v.ApiKey; x != nil { + ch := n.Child("api_key") + stringsCatalogModelProviderServiceConfigProviderSecret(c, &ch, x) + } + } + if c.mode == EmptyFields && v.ProjectId == "" && Forced(v.ForceSendFields, "ProjectId") { + ch := n.Child("project_id") + c.fn(&ch, "") + } + if s := string(v.ProjectId); c.match(s) { + ch := n.Child("project_id") + if r, ok := c.fn(&ch, s); ok { + v.ProjectId = (r) + } + } + if c.mode == EmptyFields && v.Region == "" && Forced(v.ForceSendFields, "Region") { + ch := n.Child("region") + c.fn(&ch, "") + } + if s := string(v.Region); c.match(s) { + ch := n.Child("region") + if r, ok := c.fn(&ch, s); ok { + v.Region = (r) + } + } + if !n.IsRef("service_credential") { + if x := v.ServiceCredential; x != nil { + ch := n.Child("service_credential") + stringsCatalogModelProviderServiceConfigServiceCredential(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigMicrosoftFoundryProviderDirectConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigMicrosoftFoundryProviderDirectConfig) { + if n.isRef() { + return + } + if !n.IsRef("api_key") { + if x := v.ApiKey; x != nil { + ch := n.Child("api_key") + stringsCatalogModelProviderServiceConfigProviderSecret(c, &ch, x) + } + } + if c.mode == EmptyFields && v.BaseUrl == "" && Forced(v.ForceSendFields, "BaseUrl") { + ch := n.Child("base_url") + c.fn(&ch, "") + } + if s := string(v.BaseUrl); c.match(s) { + ch := n.Child("base_url") + if r, ok := c.fn(&ch, s); ok { + v.BaseUrl = (r) + } + } + if !n.IsRef("entra_service_principal") { + if x := v.EntraServicePrincipal; x != nil { + ch := n.Child("entra_service_principal") + stringsCatalogModelProviderServiceConfigEntraServicePrincipal(c, &ch, x) + } + } + if !n.IsRef("service_credential") { + if x := v.ServiceCredential; x != nil { + ch := n.Child("service_credential") + stringsCatalogModelProviderServiceConfigServiceCredential(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigOpenAiProviderDirectConfig(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigOpenAiProviderDirectConfig) { + if n.isRef() { + return + } + if !n.IsRef("api_key") { + if x := v.ApiKey; x != nil { + ch := n.Child("api_key") + stringsCatalogModelProviderServiceConfigProviderSecret(c, &ch, x) + } + } + if c.mode == EmptyFields && v.BaseUrl == "" && Forced(v.ForceSendFields, "BaseUrl") { + ch := n.Child("base_url") + c.fn(&ch, "") + } + if s := string(v.BaseUrl); c.match(s) { + ch := n.Child("base_url") + if r, ok := c.fn(&ch, s); ok { + v.BaseUrl = (r) + } + } + if c.mode == EmptyFields && v.Organization == "" && Forced(v.ForceSendFields, "Organization") { + ch := n.Child("organization") + c.fn(&ch, "") + } + if s := string(v.Organization); c.match(s) { + ch := n.Child("organization") + if r, ok := c.fn(&ch, s); ok { + v.Organization = (r) + } + } +} + +func stringsSqlAlertV2Subscription(c *stringsWalk, n *Node, v *sql.AlertV2Subscription) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DestinationId == "" && Forced(v.ForceSendFields, "DestinationId") { + ch := n.Child("destination_id") + c.fn(&ch, "") + } + if s := string(v.DestinationId); c.match(s) { + ch := n.Child("destination_id") + if r, ok := c.fn(&ch, s); ok { + v.DestinationId = (r) + } + } + if c.mode == EmptyFields && v.UserEmail == "" && Forced(v.ForceSendFields, "UserEmail") { + ch := n.Child("user_email") + c.fn(&ch, "") + } + if s := string(v.UserEmail); c.match(s) { + ch := n.Child("user_email") + if r, ok := c.fn(&ch, s); ok { + v.UserEmail = (r) + } + } +} + +func stringsSqlAlertV2OperandValue(c *stringsWalk, n *Node, v *sql.AlertV2OperandValue) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.StringValue == "" && Forced(v.ForceSendFields, "StringValue") { + ch := n.Child("string_value") + c.fn(&ch, "") + } + if s := string(v.StringValue); c.match(s) { + ch := n.Child("string_value") + if r, ok := c.fn(&ch, s); ok { + v.StringValue = (r) + } + } +} + +func stringsDatabaseSyncedTablePipelineProgress(c *stringsWalk, n *Node, v *database.SyncedTablePipelineProgress) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ProvisioningPhase == "" && Forced(v.ForceSendFields, "ProvisioningPhase") { + ch := n.Child("provisioning_phase") + c.fn(&ch, "") + } + if s := string(v.ProvisioningPhase); c.match(s) { + ch := n.Child("provisioning_phase") + if r, ok := c.fn(&ch, s); ok { + v.ProvisioningPhase = database.ProvisioningPhase(r) + } + } +} + +func stringsDatabaseDeltaTableSyncInfo(c *stringsWalk, n *Node, v *database.DeltaTableSyncInfo) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DeltaCommitTimestamp == "" && Forced(v.ForceSendFields, "DeltaCommitTimestamp") { + ch := n.Child("delta_commit_timestamp") + c.fn(&ch, "") + } + if s := string(v.DeltaCommitTimestamp); c.match(s) { + ch := n.Child("delta_commit_timestamp") + if r, ok := c.fn(&ch, s); ok { + v.DeltaCommitTimestamp = (r) + } + } +} + +func stringsJobsComputeSpec(c *stringsWalk, n *Node, v *jobs.ComputeSpec) { + if n.isRef() { + return + } + if s := string(v.AcceleratorType); c.match(s) { + ch := n.Child("accelerator_type") + if r, ok := c.fn(&ch, s); ok { + v.AcceleratorType = jobs.ComputeSpecAcceleratorType(r) + } + } + if c.mode == EmptyFields && v.ProvisionedCapacityId == "" && Forced(v.ForceSendFields, "ProvisionedCapacityId") { + ch := n.Child("provisioned_capacity_id") + c.fn(&ch, "") + } + if s := string(v.ProvisionedCapacityId); c.match(s) { + ch := n.Child("provisioned_capacity_id") + if r, ok := c.fn(&ch, s); ok { + v.ProvisionedCapacityId = (r) + } + } +} + +func stringsJobsSubscriptionAttachment(c *stringsWalk, n *Node, v *jobs.SubscriptionAttachment) { + if n.isRef() { + return + } + if !n.IsRef("data_attachment") { + if x := v.DataAttachment; x != nil { + ch := n.Child("data_attachment") + stringsJobsSubscriptionAttachmentDataAttachment(c, &ch, x) + } + } + if !n.IsRef("page_attachment") { + if x := v.PageAttachment; x != nil { + ch := n.Child("page_attachment") + stringsJobsSubscriptionAttachmentPageAttachment(c, &ch, x) + } + } +} + +func stringsJobsSubscriptionSubscriber(c *stringsWalk, n *Node, v *jobs.SubscriptionSubscriber) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DestinationId == "" && Forced(v.ForceSendFields, "DestinationId") { + ch := n.Child("destination_id") + c.fn(&ch, "") + } + if s := string(v.DestinationId); c.match(s) { + ch := n.Child("destination_id") + if r, ok := c.fn(&ch, s); ok { + v.DestinationId = (r) + } + } + if c.mode == EmptyFields && v.UserName == "" && Forced(v.ForceSendFields, "UserName") { + ch := n.Child("user_name") + c.fn(&ch, "") + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } +} + +func stringsJobsSqlTaskSubscription(c *stringsWalk, n *Node, v *jobs.SqlTaskSubscription) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DestinationId == "" && Forced(v.ForceSendFields, "DestinationId") { + ch := n.Child("destination_id") + c.fn(&ch, "") + } + if s := string(v.DestinationId); c.match(s) { + ch := n.Child("destination_id") + if r, ok := c.fn(&ch, s); ok { + v.DestinationId = (r) + } + } + if c.mode == EmptyFields && v.UserName == "" && Forced(v.ForceSendFields, "UserName") { + ch := n.Child("user_name") + c.fn(&ch, "") + } + if s := string(v.UserName); c.match(s) { + ch := n.Child("user_name") + if r, ok := c.fn(&ch, s); ok { + v.UserName = (r) + } + } +} + +func stringsPipelinesConnectorOptions(c *stringsWalk, n *Node, v *pipelines.ConnectorOptions) { + if n.isRef() { + return + } + if !n.IsRef("api_source_connector_options") { + if x := v.ApiSourceConnectorOptions; x != nil { + ch := n.Child("api_source_connector_options") + stringsPipelinesApiSourceConnectorOptions(c, &ch, x) + } + } + if !n.IsRef("confluence_options") { + if x := v.ConfluenceOptions; x != nil { + ch := n.Child("confluence_options") + stringsPipelinesConfluenceConnectorOptions(c, &ch, x) + } + } + if !n.IsRef("gdrive_options") { + if x := v.GdriveOptions; x != nil { + ch := n.Child("gdrive_options") + stringsPipelinesGoogleDriveOptions(c, &ch, x) + } + } + if !n.IsRef("google_ads_options") { + if x := v.GoogleAdsOptions; x != nil { + ch := n.Child("google_ads_options") + stringsPipelinesGoogleAdsOptions(c, &ch, x) + } + } + if !n.IsRef("jira_options") { + if x := v.JiraOptions; x != nil { + ch := n.Child("jira_options") + stringsPipelinesJiraConnectorOptions(c, &ch, x) + } + } + if !n.IsRef("kafka_options") { + if x := v.KafkaOptions; x != nil { + ch := n.Child("kafka_options") + stringsPipelinesKafkaOptions(c, &ch, x) + } + } + if !n.IsRef("linkedin_ads_options") { + if x := v.LinkedinAdsOptions; x != nil { + ch := n.Child("linkedin_ads_options") + stringsPipelinesLinkedInAdsOptions(c, &ch, x) + } + } + if !n.IsRef("marketo_options") { + if x := v.MarketoOptions; x != nil { + ch := n.Child("marketo_options") + stringsPipelinesMarketoOptions(c, &ch, x) + } + } + if !n.IsRef("meta_ads_options") { + if x := v.MetaAdsOptions; x != nil { + ch := n.Child("meta_ads_options") + stringsPipelinesMetaMarketingOptions(c, &ch, x) + } + } + if !n.IsRef("outlook_options") { + if x := v.OutlookOptions; x != nil { + ch := n.Child("outlook_options") + stringsPipelinesOutlookOptions(c, &ch, x) + } + } + if !n.IsRef("rabbitmq_options") { + if x := v.RabbitmqOptions; x != nil { + ch := n.Child("rabbitmq_options") + stringsPipelinesRabbitmqOptions(c, &ch, x) + } + } + if !n.IsRef("reddit_ads_options") { + if x := v.RedditAdsOptions; x != nil { + ch := n.Child("reddit_ads_options") + stringsPipelinesRedditAdsOptions(c, &ch, x) + } + } + if !n.IsRef("sharepoint_options") { + if x := v.SharepointOptions; x != nil { + ch := n.Child("sharepoint_options") + stringsPipelinesSharepointOptions(c, &ch, x) + } + } + if !n.IsRef("tiktok_ads_options") { + if x := v.TiktokAdsOptions; x != nil { + ch := n.Child("tiktok_ads_options") + stringsPipelinesTikTokAdsOptions(c, &ch, x) + } + } + if !n.IsRef("zendesk_support_options") { + if x := v.ZendeskSupportOptions; x != nil { + ch := n.Child("zendesk_support_options") + stringsPipelinesZendeskSupportOptions(c, &ch, x) + } + } +} + +func stringsPipelinesIngestionPipelineDefinitionFanoutOptions(c *stringsWalk, n *Node, v *pipelines.IngestionPipelineDefinitionFanoutOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.FanoutBy == "" && Forced(v.ForceSendFields, "FanoutBy") { + ch := n.Child("fanout_by") + c.fn(&ch, "") + } + if s := string(v.FanoutBy); c.match(s) { + ch := n.Child("fanout_by") + if r, ok := c.fn(&ch, s); ok { + v.FanoutBy = (r) + } + } + if !n.IsRef("transforms") { + if len(v.Transforms) > 0 { + cn := n.Child("transforms") + for i := range v.Transforms { + { + ch := cn.Elem(i) + stringsPipelinesTransformer(c, &ch, &v.Transforms[i]) + } + } + } + } +} + +func stringsPipelinesPostgresCatalogConfig(c *stringsWalk, n *Node, v *pipelines.PostgresCatalogConfig) { + if n.isRef() { + return + } + if !n.IsRef("slot_config") { + if x := v.SlotConfig; x != nil { + ch := n.Child("slot_config") + stringsPipelinesPostgresSlotConfig(c, &ch, x) + } + } +} + +func stringsPipelinesIngestionPipelineDefinitionWorkdayReportParametersQueryKeyValue(c *stringsWalk, n *Node, v *pipelines.IngestionPipelineDefinitionWorkdayReportParametersQueryKeyValue) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Key == "" && Forced(v.ForceSendFields, "Key") { + ch := n.Child("key") + c.fn(&ch, "") + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } +} + +func stringsMlFieldDefinition(c *stringsWalk, n *Node, v *ml.FieldDefinition) { + if n.isRef() { + return + } + if s := string(v.DataType); c.match(s) { + ch := n.Child("data_type") + if r, ok := c.fn(&ch, s); ok { + v.DataType = ml.ScalarDataType(r) + } + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } +} + +func stringsServingAiGatewayGuardrailPiiBehavior(c *stringsWalk, n *Node, v *serving.AiGatewayGuardrailPiiBehavior) { + if n.isRef() { + return + } + if s := string(v.Behavior); c.match(s) { + ch := n.Child("behavior") + if r, ok := c.fn(&ch, s); ok { + v.Behavior = serving.AiGatewayGuardrailPiiBehaviorBehavior(r) + } + } +} + +func stringsServingAi21LabsConfig(c *stringsWalk, n *Node, v *serving.Ai21LabsConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Ai21labsApiKey == "" && Forced(v.ForceSendFields, "Ai21labsApiKey") { + ch := n.Child("ai21labs_api_key") + c.fn(&ch, "") + } + if s := string(v.Ai21labsApiKey); c.match(s) { + ch := n.Child("ai21labs_api_key") + if r, ok := c.fn(&ch, s); ok { + v.Ai21labsApiKey = (r) + } + } + if c.mode == EmptyFields && v.Ai21labsApiKeyPlaintext == "" && Forced(v.ForceSendFields, "Ai21labsApiKeyPlaintext") { + ch := n.Child("ai21labs_api_key_plaintext") + c.fn(&ch, "") + } + if s := string(v.Ai21labsApiKeyPlaintext); c.match(s) { + ch := n.Child("ai21labs_api_key_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.Ai21labsApiKeyPlaintext = (r) + } + } +} + +func stringsServingAmazonBedrockConfig(c *stringsWalk, n *Node, v *serving.AmazonBedrockConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.AwsAccessKeyId == "" && Forced(v.ForceSendFields, "AwsAccessKeyId") { + ch := n.Child("aws_access_key_id") + c.fn(&ch, "") + } + if s := string(v.AwsAccessKeyId); c.match(s) { + ch := n.Child("aws_access_key_id") + if r, ok := c.fn(&ch, s); ok { + v.AwsAccessKeyId = (r) + } + } + if c.mode == EmptyFields && v.AwsAccessKeyIdPlaintext == "" && Forced(v.ForceSendFields, "AwsAccessKeyIdPlaintext") { + ch := n.Child("aws_access_key_id_plaintext") + c.fn(&ch, "") + } + if s := string(v.AwsAccessKeyIdPlaintext); c.match(s) { + ch := n.Child("aws_access_key_id_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.AwsAccessKeyIdPlaintext = (r) + } + } + if s := string(v.AwsRegion); c.match(s) { + ch := n.Child("aws_region") + if r, ok := c.fn(&ch, s); ok { + v.AwsRegion = (r) + } + } + if c.mode == EmptyFields && v.AwsSecretAccessKey == "" && Forced(v.ForceSendFields, "AwsSecretAccessKey") { + ch := n.Child("aws_secret_access_key") + c.fn(&ch, "") + } + if s := string(v.AwsSecretAccessKey); c.match(s) { + ch := n.Child("aws_secret_access_key") + if r, ok := c.fn(&ch, s); ok { + v.AwsSecretAccessKey = (r) + } + } + if c.mode == EmptyFields && v.AwsSecretAccessKeyPlaintext == "" && Forced(v.ForceSendFields, "AwsSecretAccessKeyPlaintext") { + ch := n.Child("aws_secret_access_key_plaintext") + c.fn(&ch, "") + } + if s := string(v.AwsSecretAccessKeyPlaintext); c.match(s) { + ch := n.Child("aws_secret_access_key_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.AwsSecretAccessKeyPlaintext = (r) + } + } + if s := string(v.BedrockProvider); c.match(s) { + ch := n.Child("bedrock_provider") + if r, ok := c.fn(&ch, s); ok { + v.BedrockProvider = serving.AmazonBedrockConfigBedrockProvider(r) + } + } + if c.mode == EmptyFields && v.InstanceProfileArn == "" && Forced(v.ForceSendFields, "InstanceProfileArn") { + ch := n.Child("instance_profile_arn") + c.fn(&ch, "") + } + if s := string(v.InstanceProfileArn); c.match(s) { + ch := n.Child("instance_profile_arn") + if r, ok := c.fn(&ch, s); ok { + v.InstanceProfileArn = (r) + } + } + if c.mode == EmptyFields && v.UcServiceCredentialName == "" && Forced(v.ForceSendFields, "UcServiceCredentialName") { + ch := n.Child("uc_service_credential_name") + c.fn(&ch, "") + } + if s := string(v.UcServiceCredentialName); c.match(s) { + ch := n.Child("uc_service_credential_name") + if r, ok := c.fn(&ch, s); ok { + v.UcServiceCredentialName = (r) + } + } +} + +func stringsServingAnthropicConfig(c *stringsWalk, n *Node, v *serving.AnthropicConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.AnthropicApiKey == "" && Forced(v.ForceSendFields, "AnthropicApiKey") { + ch := n.Child("anthropic_api_key") + c.fn(&ch, "") + } + if s := string(v.AnthropicApiKey); c.match(s) { + ch := n.Child("anthropic_api_key") + if r, ok := c.fn(&ch, s); ok { + v.AnthropicApiKey = (r) + } + } + if c.mode == EmptyFields && v.AnthropicApiKeyPlaintext == "" && Forced(v.ForceSendFields, "AnthropicApiKeyPlaintext") { + ch := n.Child("anthropic_api_key_plaintext") + c.fn(&ch, "") + } + if s := string(v.AnthropicApiKeyPlaintext); c.match(s) { + ch := n.Child("anthropic_api_key_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.AnthropicApiKeyPlaintext = (r) + } + } +} + +func stringsServingCohereConfig(c *stringsWalk, n *Node, v *serving.CohereConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.CohereApiBase == "" && Forced(v.ForceSendFields, "CohereApiBase") { + ch := n.Child("cohere_api_base") + c.fn(&ch, "") + } + if s := string(v.CohereApiBase); c.match(s) { + ch := n.Child("cohere_api_base") + if r, ok := c.fn(&ch, s); ok { + v.CohereApiBase = (r) + } + } + if c.mode == EmptyFields && v.CohereApiKey == "" && Forced(v.ForceSendFields, "CohereApiKey") { + ch := n.Child("cohere_api_key") + c.fn(&ch, "") + } + if s := string(v.CohereApiKey); c.match(s) { + ch := n.Child("cohere_api_key") + if r, ok := c.fn(&ch, s); ok { + v.CohereApiKey = (r) + } + } + if c.mode == EmptyFields && v.CohereApiKeyPlaintext == "" && Forced(v.ForceSendFields, "CohereApiKeyPlaintext") { + ch := n.Child("cohere_api_key_plaintext") + c.fn(&ch, "") + } + if s := string(v.CohereApiKeyPlaintext); c.match(s) { + ch := n.Child("cohere_api_key_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.CohereApiKeyPlaintext = (r) + } + } +} + +func stringsServingCustomProviderConfig(c *stringsWalk, n *Node, v *serving.CustomProviderConfig) { + if n.isRef() { + return + } + if !n.IsRef("api_key_auth") { + if x := v.ApiKeyAuth; x != nil { + ch := n.Child("api_key_auth") + stringsServingApiKeyAuth(c, &ch, x) + } + } + if !n.IsRef("bearer_token_auth") { + if x := v.BearerTokenAuth; x != nil { + ch := n.Child("bearer_token_auth") + stringsServingBearerTokenAuth(c, &ch, x) + } + } + if s := string(v.CustomProviderUrl); c.match(s) { + ch := n.Child("custom_provider_url") + if r, ok := c.fn(&ch, s); ok { + v.CustomProviderUrl = (r) + } + } +} + +func stringsServingDatabricksModelServingConfig(c *stringsWalk, n *Node, v *serving.DatabricksModelServingConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DatabricksApiToken == "" && Forced(v.ForceSendFields, "DatabricksApiToken") { + ch := n.Child("databricks_api_token") + c.fn(&ch, "") + } + if s := string(v.DatabricksApiToken); c.match(s) { + ch := n.Child("databricks_api_token") + if r, ok := c.fn(&ch, s); ok { + v.DatabricksApiToken = (r) + } + } + if c.mode == EmptyFields && v.DatabricksApiTokenPlaintext == "" && Forced(v.ForceSendFields, "DatabricksApiTokenPlaintext") { + ch := n.Child("databricks_api_token_plaintext") + c.fn(&ch, "") + } + if s := string(v.DatabricksApiTokenPlaintext); c.match(s) { + ch := n.Child("databricks_api_token_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.DatabricksApiTokenPlaintext = (r) + } + } + if s := string(v.DatabricksWorkspaceUrl); c.match(s) { + ch := n.Child("databricks_workspace_url") + if r, ok := c.fn(&ch, s); ok { + v.DatabricksWorkspaceUrl = (r) + } + } +} + +func stringsServingGoogleCloudVertexAiConfig(c *stringsWalk, n *Node, v *serving.GoogleCloudVertexAiConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.PrivateKey == "" && Forced(v.ForceSendFields, "PrivateKey") { + ch := n.Child("private_key") + c.fn(&ch, "") + } + if s := string(v.PrivateKey); c.match(s) { + ch := n.Child("private_key") + if r, ok := c.fn(&ch, s); ok { + v.PrivateKey = (r) + } + } + if c.mode == EmptyFields && v.PrivateKeyPlaintext == "" && Forced(v.ForceSendFields, "PrivateKeyPlaintext") { + ch := n.Child("private_key_plaintext") + c.fn(&ch, "") + } + if s := string(v.PrivateKeyPlaintext); c.match(s) { + ch := n.Child("private_key_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.PrivateKeyPlaintext = (r) + } + } + if s := string(v.ProjectId); c.match(s) { + ch := n.Child("project_id") + if r, ok := c.fn(&ch, s); ok { + v.ProjectId = (r) + } + } + if s := string(v.Region); c.match(s) { + ch := n.Child("region") + if r, ok := c.fn(&ch, s); ok { + v.Region = (r) + } + } +} + +func stringsServingOpenAiConfig(c *stringsWalk, n *Node, v *serving.OpenAiConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.MicrosoftEntraClientId == "" && Forced(v.ForceSendFields, "MicrosoftEntraClientId") { + ch := n.Child("microsoft_entra_client_id") + c.fn(&ch, "") + } + if s := string(v.MicrosoftEntraClientId); c.match(s) { + ch := n.Child("microsoft_entra_client_id") + if r, ok := c.fn(&ch, s); ok { + v.MicrosoftEntraClientId = (r) + } + } + if c.mode == EmptyFields && v.MicrosoftEntraClientSecret == "" && Forced(v.ForceSendFields, "MicrosoftEntraClientSecret") { + ch := n.Child("microsoft_entra_client_secret") + c.fn(&ch, "") + } + if s := string(v.MicrosoftEntraClientSecret); c.match(s) { + ch := n.Child("microsoft_entra_client_secret") + if r, ok := c.fn(&ch, s); ok { + v.MicrosoftEntraClientSecret = (r) + } + } + if c.mode == EmptyFields && v.MicrosoftEntraClientSecretPlaintext == "" && Forced(v.ForceSendFields, "MicrosoftEntraClientSecretPlaintext") { + ch := n.Child("microsoft_entra_client_secret_plaintext") + c.fn(&ch, "") + } + if s := string(v.MicrosoftEntraClientSecretPlaintext); c.match(s) { + ch := n.Child("microsoft_entra_client_secret_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.MicrosoftEntraClientSecretPlaintext = (r) + } + } + if c.mode == EmptyFields && v.MicrosoftEntraTenantId == "" && Forced(v.ForceSendFields, "MicrosoftEntraTenantId") { + ch := n.Child("microsoft_entra_tenant_id") + c.fn(&ch, "") + } + if s := string(v.MicrosoftEntraTenantId); c.match(s) { + ch := n.Child("microsoft_entra_tenant_id") + if r, ok := c.fn(&ch, s); ok { + v.MicrosoftEntraTenantId = (r) + } + } + if c.mode == EmptyFields && v.OpenaiApiBase == "" && Forced(v.ForceSendFields, "OpenaiApiBase") { + ch := n.Child("openai_api_base") + c.fn(&ch, "") + } + if s := string(v.OpenaiApiBase); c.match(s) { + ch := n.Child("openai_api_base") + if r, ok := c.fn(&ch, s); ok { + v.OpenaiApiBase = (r) + } + } + if c.mode == EmptyFields && v.OpenaiApiKey == "" && Forced(v.ForceSendFields, "OpenaiApiKey") { + ch := n.Child("openai_api_key") + c.fn(&ch, "") + } + if s := string(v.OpenaiApiKey); c.match(s) { + ch := n.Child("openai_api_key") + if r, ok := c.fn(&ch, s); ok { + v.OpenaiApiKey = (r) + } + } + if c.mode == EmptyFields && v.OpenaiApiKeyPlaintext == "" && Forced(v.ForceSendFields, "OpenaiApiKeyPlaintext") { + ch := n.Child("openai_api_key_plaintext") + c.fn(&ch, "") + } + if s := string(v.OpenaiApiKeyPlaintext); c.match(s) { + ch := n.Child("openai_api_key_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.OpenaiApiKeyPlaintext = (r) + } + } + if c.mode == EmptyFields && v.OpenaiApiType == "" && Forced(v.ForceSendFields, "OpenaiApiType") { + ch := n.Child("openai_api_type") + c.fn(&ch, "") + } + if s := string(v.OpenaiApiType); c.match(s) { + ch := n.Child("openai_api_type") + if r, ok := c.fn(&ch, s); ok { + v.OpenaiApiType = (r) + } + } + if c.mode == EmptyFields && v.OpenaiApiVersion == "" && Forced(v.ForceSendFields, "OpenaiApiVersion") { + ch := n.Child("openai_api_version") + c.fn(&ch, "") + } + if s := string(v.OpenaiApiVersion); c.match(s) { + ch := n.Child("openai_api_version") + if r, ok := c.fn(&ch, s); ok { + v.OpenaiApiVersion = (r) + } + } + if c.mode == EmptyFields && v.OpenaiDeploymentName == "" && Forced(v.ForceSendFields, "OpenaiDeploymentName") { + ch := n.Child("openai_deployment_name") + c.fn(&ch, "") + } + if s := string(v.OpenaiDeploymentName); c.match(s) { + ch := n.Child("openai_deployment_name") + if r, ok := c.fn(&ch, s); ok { + v.OpenaiDeploymentName = (r) + } + } + if c.mode == EmptyFields && v.OpenaiOrganization == "" && Forced(v.ForceSendFields, "OpenaiOrganization") { + ch := n.Child("openai_organization") + c.fn(&ch, "") + } + if s := string(v.OpenaiOrganization); c.match(s) { + ch := n.Child("openai_organization") + if r, ok := c.fn(&ch, s); ok { + v.OpenaiOrganization = (r) + } + } +} + +func stringsServingPaLmConfig(c *stringsWalk, n *Node, v *serving.PaLmConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.PalmApiKey == "" && Forced(v.ForceSendFields, "PalmApiKey") { + ch := n.Child("palm_api_key") + c.fn(&ch, "") + } + if s := string(v.PalmApiKey); c.match(s) { + ch := n.Child("palm_api_key") + if r, ok := c.fn(&ch, s); ok { + v.PalmApiKey = (r) + } + } + if c.mode == EmptyFields && v.PalmApiKeyPlaintext == "" && Forced(v.ForceSendFields, "PalmApiKeyPlaintext") { + ch := n.Child("palm_api_key_plaintext") + c.fn(&ch, "") + } + if s := string(v.PalmApiKeyPlaintext); c.match(s) { + ch := n.Child("palm_api_key_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.PalmApiKeyPlaintext = (r) + } + } +} + +func stringsCatalogModelServiceConfigExternalModelConfig(c *stringsWalk, n *Node, v *catalog.ModelServiceConfigExternalModelConfig) { + if n.isRef() { + return + } + if s := string(v.ModelProviderService); c.match(s) { + ch := n.Child("model_provider_service") + if r, ok := c.fn(&ch, s); ok { + v.ModelProviderService = (r) + } + } + if !n.IsRef("target") { + { + ch := n.Child("target") + stringsCatalogModelProviderServiceConfigModelTargetConfig(c, &ch, &v.Target) + } + } +} + +func stringsCatalogModelServiceConfigPayPerTokenConfig(c *stringsWalk, n *Node, v *catalog.ModelServiceConfigPayPerTokenConfig) { + if n.isRef() { + return + } + if s := string(v.Model); c.match(s) { + ch := n.Child("model") + if r, ok := c.fn(&ch, s); ok { + v.Model = (r) + } + } +} + +func stringsCatalogModelServiceConfigProvisionedThroughputConfig(c *stringsWalk, n *Node, v *catalog.ModelServiceConfigProvisionedThroughputConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Model == "" && Forced(v.ForceSendFields, "Model") { + ch := n.Child("model") + c.fn(&ch, "") + } + if s := string(v.Model); c.match(s) { + ch := n.Child("model") + if r, ok := c.fn(&ch, s); ok { + v.Model = (r) + } + } + if s := string(v.ModelServingEndpoint); c.match(s) { + ch := n.Child("model_serving_endpoint") + if r, ok := c.fn(&ch, s); ok { + v.ModelServingEndpoint = (r) + } + } +} + +func stringsCatalogModelProviderServiceConfigAwsAccessKey(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigAwsAccessKey) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.AccessKeyId == "" && Forced(v.ForceSendFields, "AccessKeyId") { + ch := n.Child("access_key_id") + c.fn(&ch, "") + } + if s := string(v.AccessKeyId); c.match(s) { + ch := n.Child("access_key_id") + if r, ok := c.fn(&ch, s); ok { + v.AccessKeyId = (r) + } + } + if !n.IsRef("secret_access_key") { + if x := v.SecretAccessKey; x != nil { + ch := n.Child("secret_access_key") + stringsCatalogModelProviderServiceConfigProviderSecret(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigServiceCredential(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigServiceCredential) { + if n.isRef() { + return + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } +} + +func stringsCatalogModelProviderServiceConfigProviderSecret(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigProviderSecret) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Plaintext == "" && Forced(v.ForceSendFields, "Plaintext") { + ch := n.Child("plaintext") + c.fn(&ch, "") + } + if s := string(v.Plaintext); c.match(s) { + ch := n.Child("plaintext") + if r, ok := c.fn(&ch, s); ok { + v.Plaintext = (r) + } + } + if !n.IsRef("secret_reference") { + if x := v.SecretReference; x != nil { + ch := n.Child("secret_reference") + stringsCatalogModelProviderServiceConfigSecretReference(c, &ch, x) + } + } +} + +func stringsCatalogModelProviderServiceConfigEntraServicePrincipal(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigEntraServicePrincipal) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ClientId == "" && Forced(v.ForceSendFields, "ClientId") { + ch := n.Child("client_id") + c.fn(&ch, "") + } + if s := string(v.ClientId); c.match(s) { + ch := n.Child("client_id") + if r, ok := c.fn(&ch, s); ok { + v.ClientId = (r) + } + } + if !n.IsRef("client_secret") { + if x := v.ClientSecret; x != nil { + ch := n.Child("client_secret") + stringsCatalogModelProviderServiceConfigProviderSecret(c, &ch, x) + } + } + if c.mode == EmptyFields && v.TenantId == "" && Forced(v.ForceSendFields, "TenantId") { + ch := n.Child("tenant_id") + c.fn(&ch, "") + } + if s := string(v.TenantId); c.match(s) { + ch := n.Child("tenant_id") + if r, ok := c.fn(&ch, s); ok { + v.TenantId = (r) + } + } +} + +func stringsCatalogModelProviderServiceConfigCustomProviderApiKeyHeaderAuth(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigCustomProviderApiKeyHeaderAuth) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ApiKeyName == "" && Forced(v.ForceSendFields, "ApiKeyName") { + ch := n.Child("api_key_name") + c.fn(&ch, "") + } + if s := string(v.ApiKeyName); c.match(s) { + ch := n.Child("api_key_name") + if r, ok := c.fn(&ch, s); ok { + v.ApiKeyName = (r) + } + } + if !n.IsRef("api_key_value") { + if x := v.ApiKeyValue; x != nil { + ch := n.Child("api_key_value") + stringsCatalogModelProviderServiceConfigProviderSecret(c, &ch, x) + } + } +} + +func stringsJobsSubscriptionAttachmentDataAttachment(c *stringsWalk, n *Node, v *jobs.SubscriptionAttachmentDataAttachment) { + if n.isRef() { + return + } + if s := string(v.FileFormat); c.match(s) { + ch := n.Child("file_format") + if r, ok := c.fn(&ch, s); ok { + v.FileFormat = jobs.SubscriptionAttachmentDataAttachmentFileFormat(r) + } + } + if !n.IsRef("widgets") { + if len(v.Widgets) > 0 { + cn := n.Child("widgets") + for i := range v.Widgets { + { + ch := cn.Elem(i) + stringsJobsSubscriptionAttachmentDataAttachmentWidget(c, &ch, &v.Widgets[i]) + } + } + } + } +} + +func stringsJobsSubscriptionAttachmentPageAttachment(c *stringsWalk, n *Node, v *jobs.SubscriptionAttachmentPageAttachment) { + if n.isRef() { + return + } + if !n.IsRef("page_ids") { + if len(v.PageIds) > 0 { + cn := n.Child("page_ids") + for i := range v.PageIds { + if s := string(v.PageIds[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.PageIds[i] = (r) + } + } + } + } + } + if s := string(v.PageSelection); c.match(s) { + ch := n.Child("page_selection") + if r, ok := c.fn(&ch, s); ok { + v.PageSelection = jobs.SubscriptionAttachmentPageAttachmentPageSelection(r) + } + } +} + +func stringsPipelinesApiSourceConnectorOptions(c *stringsWalk, n *Node, v *pipelines.ApiSourceConnectorOptions) { + if n.isRef() { + return + } + if !n.IsRef("options") { + if len(v.Options) > 0 { + cn := n.Child("options") + for _, k := range sortedKeys(v.Options) { + if s := string(v.Options[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.Options[k] = (r) + } + } + } + } + } +} + +func stringsPipelinesConfluenceConnectorOptions(c *stringsWalk, n *Node, v *pipelines.ConfluenceConnectorOptions) { + if n.isRef() { + return + } + if !n.IsRef("include_confluence_spaces") { + if len(v.IncludeConfluenceSpaces) > 0 { + cn := n.Child("include_confluence_spaces") + for i := range v.IncludeConfluenceSpaces { + if s := string(v.IncludeConfluenceSpaces[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.IncludeConfluenceSpaces[i] = (r) + } + } + } + } + } +} + +func stringsPipelinesGoogleDriveOptions(c *stringsWalk, n *Node, v *pipelines.GoogleDriveOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.EntityType == "" && Forced(v.ForceSendFields, "EntityType") { + ch := n.Child("entity_type") + c.fn(&ch, "") + } + if s := string(v.EntityType); c.match(s) { + ch := n.Child("entity_type") + if r, ok := c.fn(&ch, s); ok { + v.EntityType = pipelines.GoogleDriveOptionsGoogleDriveEntityType(r) + } + } + if !n.IsRef("file_ingestion_options") { + if x := v.FileIngestionOptions; x != nil { + ch := n.Child("file_ingestion_options") + stringsPipelinesFileIngestionOptions(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Url == "" && Forced(v.ForceSendFields, "Url") { + ch := n.Child("url") + c.fn(&ch, "") + } + if s := string(v.Url); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.Url = (r) + } + } +} + +func stringsPipelinesGoogleAdsOptions(c *stringsWalk, n *Node, v *pipelines.GoogleAdsOptions) { + if n.isRef() { + return + } + if !n.IsRef("custom_report_options") { + if x := v.CustomReportOptions; x != nil { + ch := n.Child("custom_report_options") + stringsPipelinesGoogleAdsCustomReportOptions(c, &ch, x) + } + } + if s := string(v.ManagerAccountId); c.match(s) { + ch := n.Child("manager_account_id") + if r, ok := c.fn(&ch, s); ok { + v.ManagerAccountId = (r) + } + } + if c.mode == EmptyFields && v.SyncStartDate == "" && Forced(v.ForceSendFields, "SyncStartDate") { + ch := n.Child("sync_start_date") + c.fn(&ch, "") + } + if s := string(v.SyncStartDate); c.match(s) { + ch := n.Child("sync_start_date") + if r, ok := c.fn(&ch, s); ok { + v.SyncStartDate = (r) + } + } +} + +func stringsPipelinesJiraConnectorOptions(c *stringsWalk, n *Node, v *pipelines.JiraConnectorOptions) { + if n.isRef() { + return + } + if !n.IsRef("include_jira_spaces") { + if len(v.IncludeJiraSpaces) > 0 { + cn := n.Child("include_jira_spaces") + for i := range v.IncludeJiraSpaces { + if s := string(v.IncludeJiraSpaces[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.IncludeJiraSpaces[i] = (r) + } + } + } + } + } +} + +func stringsPipelinesKafkaOptions(c *stringsWalk, n *Node, v *pipelines.KafkaOptions) { + if n.isRef() { + return + } + if !n.IsRef("client_config") { + if len(v.ClientConfig) > 0 { + cn := n.Child("client_config") + for _, k := range sortedKeys(v.ClientConfig) { + if s := string(v.ClientConfig[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.ClientConfig[k] = (r) + } + } + } + } + } + if !n.IsRef("key_transformer") { + if x := v.KeyTransformer; x != nil { + ch := n.Child("key_transformer") + stringsPipelinesTransformer(c, &ch, x) + } + } + if c.mode == EmptyFields && v.StartingOffset == "" && Forced(v.ForceSendFields, "StartingOffset") { + ch := n.Child("starting_offset") + c.fn(&ch, "") + } + if s := string(v.StartingOffset); c.match(s) { + ch := n.Child("starting_offset") + if r, ok := c.fn(&ch, s); ok { + v.StartingOffset = (r) + } + } + if c.mode == EmptyFields && v.TopicPattern == "" && Forced(v.ForceSendFields, "TopicPattern") { + ch := n.Child("topic_pattern") + c.fn(&ch, "") + } + if s := string(v.TopicPattern); c.match(s) { + ch := n.Child("topic_pattern") + if r, ok := c.fn(&ch, s); ok { + v.TopicPattern = (r) + } + } + if !n.IsRef("topics") { + if len(v.Topics) > 0 { + cn := n.Child("topics") + for i := range v.Topics { + if s := string(v.Topics[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Topics[i] = (r) + } + } + } + } + } + if !n.IsRef("value_transformer") { + if x := v.ValueTransformer; x != nil { + ch := n.Child("value_transformer") + stringsPipelinesTransformer(c, &ch, x) + } + } +} + +func stringsPipelinesLinkedInAdsOptions(c *stringsWalk, n *Node, v *pipelines.LinkedInAdsOptions) { + if n.isRef() { + return + } + if !n.IsRef("custom_report_options") { + if x := v.CustomReportOptions; x != nil { + ch := n.Child("custom_report_options") + stringsPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptions(c, &ch, x) + } + } + if c.mode == EmptyFields && v.SyncStartDate == "" && Forced(v.ForceSendFields, "SyncStartDate") { + ch := n.Child("sync_start_date") + c.fn(&ch, "") + } + if s := string(v.SyncStartDate); c.match(s) { + ch := n.Child("sync_start_date") + if r, ok := c.fn(&ch, s); ok { + v.SyncStartDate = (r) + } + } +} + +func stringsPipelinesMarketoOptions(c *stringsWalk, n *Node, v *pipelines.MarketoOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.SyncStartDate == "" && Forced(v.ForceSendFields, "SyncStartDate") { + ch := n.Child("sync_start_date") + c.fn(&ch, "") + } + if s := string(v.SyncStartDate); c.match(s) { + ch := n.Child("sync_start_date") + if r, ok := c.fn(&ch, s); ok { + v.SyncStartDate = (r) + } + } +} + +func stringsPipelinesMetaMarketingOptions(c *stringsWalk, n *Node, v *pipelines.MetaMarketingOptions) { + if n.isRef() { + return + } + if !n.IsRef("action_attribution_windows") { + if len(v.ActionAttributionWindows) > 0 { + cn := n.Child("action_attribution_windows") + for i := range v.ActionAttributionWindows { + if s := string(v.ActionAttributionWindows[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ActionAttributionWindows[i] = (r) + } + } + } + } + } + if !n.IsRef("action_breakdowns") { + if len(v.ActionBreakdowns) > 0 { + cn := n.Child("action_breakdowns") + for i := range v.ActionBreakdowns { + if s := string(v.ActionBreakdowns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ActionBreakdowns[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.ActionReportTime == "" && Forced(v.ForceSendFields, "ActionReportTime") { + ch := n.Child("action_report_time") + c.fn(&ch, "") + } + if s := string(v.ActionReportTime); c.match(s) { + ch := n.Child("action_report_time") + if r, ok := c.fn(&ch, s); ok { + v.ActionReportTime = (r) + } + } + if !n.IsRef("breakdowns") { + if len(v.Breakdowns) > 0 { + cn := n.Child("breakdowns") + for i := range v.Breakdowns { + if s := string(v.Breakdowns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Breakdowns[i] = (r) + } + } + } + } + } + if !n.IsRef("custom_report_options") { + if x := v.CustomReportOptions; x != nil { + ch := n.Child("custom_report_options") + stringsPipelinesMetaMarketingOptionsMetaMarketingCustomReportOptions(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Level == "" && Forced(v.ForceSendFields, "Level") { + ch := n.Child("level") + c.fn(&ch, "") + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = (r) + } + } + if c.mode == EmptyFields && v.StartDate == "" && Forced(v.ForceSendFields, "StartDate") { + ch := n.Child("start_date") + c.fn(&ch, "") + } + if s := string(v.StartDate); c.match(s) { + ch := n.Child("start_date") + if r, ok := c.fn(&ch, s); ok { + v.StartDate = (r) + } + } + if c.mode == EmptyFields && v.TimeIncrement == "" && Forced(v.ForceSendFields, "TimeIncrement") { + ch := n.Child("time_increment") + c.fn(&ch, "") + } + if s := string(v.TimeIncrement); c.match(s) { + ch := n.Child("time_increment") + if r, ok := c.fn(&ch, s); ok { + v.TimeIncrement = (r) + } + } +} + +func stringsPipelinesOutlookOptions(c *stringsWalk, n *Node, v *pipelines.OutlookOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.AttachmentMode == "" && Forced(v.ForceSendFields, "AttachmentMode") { + ch := n.Child("attachment_mode") + c.fn(&ch, "") + } + if s := string(v.AttachmentMode); c.match(s) { + ch := n.Child("attachment_mode") + if r, ok := c.fn(&ch, s); ok { + v.AttachmentMode = pipelines.OutlookAttachmentMode(r) + } + } + if c.mode == EmptyFields && v.BodyFormat == "" && Forced(v.ForceSendFields, "BodyFormat") { + ch := n.Child("body_format") + c.fn(&ch, "") + } + if s := string(v.BodyFormat); c.match(s) { + ch := n.Child("body_format") + if r, ok := c.fn(&ch, s); ok { + v.BodyFormat = pipelines.OutlookBodyFormat(r) + } + } + if !n.IsRef("folder_filter") { + if len(v.FolderFilter) > 0 { + cn := n.Child("folder_filter") + for i := range v.FolderFilter { + if s := string(v.FolderFilter[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.FolderFilter[i] = (r) + } + } + } + } + } + if !n.IsRef("include_folders") { + if len(v.IncludeFolders) > 0 { + cn := n.Child("include_folders") + for i := range v.IncludeFolders { + if s := string(v.IncludeFolders[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.IncludeFolders[i] = (r) + } + } + } + } + } + if !n.IsRef("include_mailboxes") { + if len(v.IncludeMailboxes) > 0 { + cn := n.Child("include_mailboxes") + for i := range v.IncludeMailboxes { + if s := string(v.IncludeMailboxes[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.IncludeMailboxes[i] = (r) + } + } + } + } + } + if !n.IsRef("include_senders") { + if len(v.IncludeSenders) > 0 { + cn := n.Child("include_senders") + for i := range v.IncludeSenders { + if s := string(v.IncludeSenders[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.IncludeSenders[i] = (r) + } + } + } + } + } + if !n.IsRef("include_subjects") { + if len(v.IncludeSubjects) > 0 { + cn := n.Child("include_subjects") + for i := range v.IncludeSubjects { + if s := string(v.IncludeSubjects[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.IncludeSubjects[i] = (r) + } + } + } + } + } + if !n.IsRef("sender_filter") { + if len(v.SenderFilter) > 0 { + cn := n.Child("sender_filter") + for i := range v.SenderFilter { + if s := string(v.SenderFilter[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.SenderFilter[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.StartDate == "" && Forced(v.ForceSendFields, "StartDate") { + ch := n.Child("start_date") + c.fn(&ch, "") + } + if s := string(v.StartDate); c.match(s) { + ch := n.Child("start_date") + if r, ok := c.fn(&ch, s); ok { + v.StartDate = (r) + } + } + if !n.IsRef("subject_filter") { + if len(v.SubjectFilter) > 0 { + cn := n.Child("subject_filter") + for i := range v.SubjectFilter { + if s := string(v.SubjectFilter[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.SubjectFilter[i] = (r) + } + } + } + } + } +} + +func stringsPipelinesRabbitmqOptions(c *stringsWalk, n *Node, v *pipelines.RabbitmqOptions) { + if n.isRef() { + return + } + if s := string(v.Queue); c.match(s) { + ch := n.Child("queue") + if r, ok := c.fn(&ch, s); ok { + v.Queue = (r) + } + } +} + +func stringsPipelinesRedditAdsOptions(c *stringsWalk, n *Node, v *pipelines.RedditAdsOptions) { + if n.isRef() { + return + } + if !n.IsRef("custom_report_options") { + if x := v.CustomReportOptions; x != nil { + ch := n.Child("custom_report_options") + stringsPipelinesRedditAdsOptionsRedditAdsCustomReportOptions(c, &ch, x) + } + } + if c.mode == EmptyFields && v.SyncStartDate == "" && Forced(v.ForceSendFields, "SyncStartDate") { + ch := n.Child("sync_start_date") + c.fn(&ch, "") + } + if s := string(v.SyncStartDate); c.match(s) { + ch := n.Child("sync_start_date") + if r, ok := c.fn(&ch, s); ok { + v.SyncStartDate = (r) + } + } +} + +func stringsPipelinesSharepointOptions(c *stringsWalk, n *Node, v *pipelines.SharepointOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.EntityType == "" && Forced(v.ForceSendFields, "EntityType") { + ch := n.Child("entity_type") + c.fn(&ch, "") + } + if s := string(v.EntityType); c.match(s) { + ch := n.Child("entity_type") + if r, ok := c.fn(&ch, s); ok { + v.EntityType = pipelines.SharepointOptionsSharepointEntityType(r) + } + } + if !n.IsRef("file_ingestion_options") { + if x := v.FileIngestionOptions; x != nil { + ch := n.Child("file_ingestion_options") + stringsPipelinesFileIngestionOptions(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Url == "" && Forced(v.ForceSendFields, "Url") { + ch := n.Child("url") + c.fn(&ch, "") + } + if s := string(v.Url); c.match(s) { + ch := n.Child("url") + if r, ok := c.fn(&ch, s); ok { + v.Url = (r) + } + } +} + +func stringsPipelinesTikTokAdsOptions(c *stringsWalk, n *Node, v *pipelines.TikTokAdsOptions) { + if n.isRef() { + return + } + if !n.IsRef("custom_report_options") { + if x := v.CustomReportOptions; x != nil { + ch := n.Child("custom_report_options") + stringsPipelinesTikTokAdsOptionsTikTokAdsCustomReportOptions(c, &ch, x) + } + } + if c.mode == EmptyFields && v.DataLevel == "" && Forced(v.ForceSendFields, "DataLevel") { + ch := n.Child("data_level") + c.fn(&ch, "") + } + if s := string(v.DataLevel); c.match(s) { + ch := n.Child("data_level") + if r, ok := c.fn(&ch, s); ok { + v.DataLevel = pipelines.TikTokAdsOptionsTikTokDataLevel(r) + } + } + if !n.IsRef("dimensions") { + if len(v.Dimensions) > 0 { + cn := n.Child("dimensions") + for i := range v.Dimensions { + if s := string(v.Dimensions[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Dimensions[i] = (r) + } + } + } + } + } + if !n.IsRef("metrics") { + if len(v.Metrics) > 0 { + cn := n.Child("metrics") + for i := range v.Metrics { + if s := string(v.Metrics[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Metrics[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.ReportType == "" && Forced(v.ForceSendFields, "ReportType") { + ch := n.Child("report_type") + c.fn(&ch, "") + } + if s := string(v.ReportType); c.match(s) { + ch := n.Child("report_type") + if r, ok := c.fn(&ch, s); ok { + v.ReportType = pipelines.TikTokAdsOptionsTikTokReportType(r) + } + } + if c.mode == EmptyFields && v.SyncStartDate == "" && Forced(v.ForceSendFields, "SyncStartDate") { + ch := n.Child("sync_start_date") + c.fn(&ch, "") + } + if s := string(v.SyncStartDate); c.match(s) { + ch := n.Child("sync_start_date") + if r, ok := c.fn(&ch, s); ok { + v.SyncStartDate = (r) + } + } +} + +func stringsPipelinesZendeskSupportOptions(c *stringsWalk, n *Node, v *pipelines.ZendeskSupportOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.StartDate == "" && Forced(v.ForceSendFields, "StartDate") { + ch := n.Child("start_date") + c.fn(&ch, "") + } + if s := string(v.StartDate); c.match(s) { + ch := n.Child("start_date") + if r, ok := c.fn(&ch, s); ok { + v.StartDate = (r) + } + } +} + +func stringsPipelinesTransformer(c *stringsWalk, n *Node, v *pipelines.Transformer) { + if n.isRef() { + return + } + if !n.IsRef("avro_options") { + if x := v.AvroOptions; x != nil { + ch := n.Child("avro_options") + stringsPipelinesAvroTransformerOptions(c, &ch, x) + } + } + if c.mode == EmptyFields && v.Format == "" && Forced(v.ForceSendFields, "Format") { + ch := n.Child("format") + c.fn(&ch, "") + } + if s := string(v.Format); c.match(s) { + ch := n.Child("format") + if r, ok := c.fn(&ch, s); ok { + v.Format = pipelines.TransformerFormat(r) + } + } + if c.mode == EmptyFields && v.InputColumn == "" && Forced(v.ForceSendFields, "InputColumn") { + ch := n.Child("input_column") + c.fn(&ch, "") + } + if s := string(v.InputColumn); c.match(s) { + ch := n.Child("input_column") + if r, ok := c.fn(&ch, s); ok { + v.InputColumn = (r) + } + } + if !n.IsRef("json_options") { + if x := v.JsonOptions; x != nil { + ch := n.Child("json_options") + stringsPipelinesJsonTransformerOptions(c, &ch, x) + } + } + if c.mode == EmptyFields && v.OutputColumn == "" && Forced(v.ForceSendFields, "OutputColumn") { + ch := n.Child("output_column") + c.fn(&ch, "") + } + if s := string(v.OutputColumn); c.match(s) { + ch := n.Child("output_column") + if r, ok := c.fn(&ch, s); ok { + v.OutputColumn = (r) + } + } + if !n.IsRef("protobuf_options") { + if x := v.ProtobufOptions; x != nil { + ch := n.Child("protobuf_options") + stringsPipelinesProtobufTransformerOptions(c, &ch, x) + } + } +} + +func stringsPipelinesPostgresSlotConfig(c *stringsWalk, n *Node, v *pipelines.PostgresSlotConfig) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.PublicationName == "" && Forced(v.ForceSendFields, "PublicationName") { + ch := n.Child("publication_name") + c.fn(&ch, "") + } + if s := string(v.PublicationName); c.match(s) { + ch := n.Child("publication_name") + if r, ok := c.fn(&ch, s); ok { + v.PublicationName = (r) + } + } + if c.mode == EmptyFields && v.SlotName == "" && Forced(v.ForceSendFields, "SlotName") { + ch := n.Child("slot_name") + c.fn(&ch, "") + } + if s := string(v.SlotName); c.match(s) { + ch := n.Child("slot_name") + if r, ok := c.fn(&ch, s); ok { + v.SlotName = (r) + } + } +} + +func stringsServingApiKeyAuth(c *stringsWalk, n *Node, v *serving.ApiKeyAuth) { + if n.isRef() { + return + } + if s := string(v.Key); c.match(s) { + ch := n.Child("key") + if r, ok := c.fn(&ch, s); ok { + v.Key = (r) + } + } + if c.mode == EmptyFields && v.Value == "" && Forced(v.ForceSendFields, "Value") { + ch := n.Child("value") + c.fn(&ch, "") + } + if s := string(v.Value); c.match(s) { + ch := n.Child("value") + if r, ok := c.fn(&ch, s); ok { + v.Value = (r) + } + } + if c.mode == EmptyFields && v.ValuePlaintext == "" && Forced(v.ForceSendFields, "ValuePlaintext") { + ch := n.Child("value_plaintext") + c.fn(&ch, "") + } + if s := string(v.ValuePlaintext); c.match(s) { + ch := n.Child("value_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.ValuePlaintext = (r) + } + } +} + +func stringsServingBearerTokenAuth(c *stringsWalk, n *Node, v *serving.BearerTokenAuth) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Token == "" && Forced(v.ForceSendFields, "Token") { + ch := n.Child("token") + c.fn(&ch, "") + } + if s := string(v.Token); c.match(s) { + ch := n.Child("token") + if r, ok := c.fn(&ch, s); ok { + v.Token = (r) + } + } + if c.mode == EmptyFields && v.TokenPlaintext == "" && Forced(v.ForceSendFields, "TokenPlaintext") { + ch := n.Child("token_plaintext") + c.fn(&ch, "") + } + if s := string(v.TokenPlaintext); c.match(s) { + ch := n.Child("token_plaintext") + if r, ok := c.fn(&ch, s); ok { + v.TokenPlaintext = (r) + } + } +} + +func stringsCatalogModelProviderServiceConfigSecretReference(c *stringsWalk, n *Node, v *catalog.ModelProviderServiceConfigSecretReference) { + if n.isRef() { + return + } + if s := string(v.Name); c.match(s) { + ch := n.Child("name") + if r, ok := c.fn(&ch, s); ok { + v.Name = (r) + } + } +} + +func stringsJobsSubscriptionAttachmentDataAttachmentWidget(c *stringsWalk, n *Node, v *jobs.SubscriptionAttachmentDataAttachmentWidget) { + if n.isRef() { + return + } + if s := string(v.PageId); c.match(s) { + ch := n.Child("page_id") + if r, ok := c.fn(&ch, s); ok { + v.PageId = (r) + } + } + if s := string(v.WidgetId); c.match(s) { + ch := n.Child("widget_id") + if r, ok := c.fn(&ch, s); ok { + v.WidgetId = (r) + } + } +} + +func stringsPipelinesFileIngestionOptions(c *stringsWalk, n *Node, v *pipelines.FileIngestionOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.CorruptRecordColumn == "" && Forced(v.ForceSendFields, "CorruptRecordColumn") { + ch := n.Child("corrupt_record_column") + c.fn(&ch, "") + } + if s := string(v.CorruptRecordColumn); c.match(s) { + ch := n.Child("corrupt_record_column") + if r, ok := c.fn(&ch, s); ok { + v.CorruptRecordColumn = (r) + } + } + if !n.IsRef("file_filters") { + if len(v.FileFilters) > 0 { + cn := n.Child("file_filters") + for i := range v.FileFilters { + { + ch := cn.Elem(i) + stringsPipelinesFileFilter(c, &ch, &v.FileFilters[i]) + } + } + } + } + if c.mode == EmptyFields && v.Format == "" && Forced(v.ForceSendFields, "Format") { + ch := n.Child("format") + c.fn(&ch, "") + } + if s := string(v.Format); c.match(s) { + ch := n.Child("format") + if r, ok := c.fn(&ch, s); ok { + v.Format = pipelines.FileIngestionOptionsFileFormat(r) + } + } + if !n.IsRef("format_options") { + if len(v.FormatOptions) > 0 { + cn := n.Child("format_options") + for _, k := range sortedKeys(v.FormatOptions) { + if s := string(v.FormatOptions[k]); c.match(s) { + ch := cn.Child(string(k)) + if r, ok := c.fn(&ch, s); ok { + v.FormatOptions[k] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.RescuedDataColumn == "" && Forced(v.ForceSendFields, "RescuedDataColumn") { + ch := n.Child("rescued_data_column") + c.fn(&ch, "") + } + if s := string(v.RescuedDataColumn); c.match(s) { + ch := n.Child("rescued_data_column") + if r, ok := c.fn(&ch, s); ok { + v.RescuedDataColumn = (r) + } + } + if c.mode == EmptyFields && v.SchemaEvolutionMode == "" && Forced(v.ForceSendFields, "SchemaEvolutionMode") { + ch := n.Child("schema_evolution_mode") + c.fn(&ch, "") + } + if s := string(v.SchemaEvolutionMode); c.match(s) { + ch := n.Child("schema_evolution_mode") + if r, ok := c.fn(&ch, s); ok { + v.SchemaEvolutionMode = pipelines.FileIngestionOptionsSchemaEvolutionMode(r) + } + } + if c.mode == EmptyFields && v.SchemaHints == "" && Forced(v.ForceSendFields, "SchemaHints") { + ch := n.Child("schema_hints") + c.fn(&ch, "") + } + if s := string(v.SchemaHints); c.match(s) { + ch := n.Child("schema_hints") + if r, ok := c.fn(&ch, s); ok { + v.SchemaHints = (r) + } + } + if c.mode == EmptyFields && v.SingleVariantColumn == "" && Forced(v.ForceSendFields, "SingleVariantColumn") { + ch := n.Child("single_variant_column") + c.fn(&ch, "") + } + if s := string(v.SingleVariantColumn); c.match(s) { + ch := n.Child("single_variant_column") + if r, ok := c.fn(&ch, s); ok { + v.SingleVariantColumn = (r) + } + } +} + +func stringsPipelinesGoogleAdsCustomReportOptions(c *stringsWalk, n *Node, v *pipelines.GoogleAdsCustomReportOptions) { + if n.isRef() { + return + } + if !n.IsRef("metrics") { + if len(v.Metrics) > 0 { + cn := n.Child("metrics") + for i := range v.Metrics { + if s := string(v.Metrics[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Metrics[i] = (r) + } + } + } + } + } + if s := string(v.Resource); c.match(s) { + ch := n.Child("resource") + if r, ok := c.fn(&ch, s); ok { + v.Resource = (r) + } + } + if !n.IsRef("resource_fields") { + if len(v.ResourceFields) > 0 { + cn := n.Child("resource_fields") + for i := range v.ResourceFields { + if s := string(v.ResourceFields[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ResourceFields[i] = (r) + } + } + } + } + } + if !n.IsRef("segments") { + if len(v.Segments) > 0 { + cn := n.Child("segments") + for i := range v.Segments { + if s := string(v.Segments[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Segments[i] = (r) + } + } + } + } + } +} + +func stringsPipelinesLinkedInAdsOptionsLinkedInAdsCustomReportOptions(c *stringsWalk, n *Node, v *pipelines.LinkedInAdsOptionsLinkedInAdsCustomReportOptions) { + if n.isRef() { + return + } + if !n.IsRef("entity_granularity") { + if len(v.EntityGranularity) > 0 { + cn := n.Child("entity_granularity") + for i := range v.EntityGranularity { + if s := string(v.EntityGranularity[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.EntityGranularity[i] = pipelines.LinkedInAdsOptionsLinkedInAdsCustomReportOptionsLinkedInAdsEntityGranularity(r) + } + } + } + } + } + if s := string(v.Finder); c.match(s) { + ch := n.Child("finder") + if r, ok := c.fn(&ch, s); ok { + v.Finder = pipelines.LinkedInAdsOptionsLinkedInAdsCustomReportOptionsLinkedInAdsFinder(r) + } + } + if !n.IsRef("metrics") { + if len(v.Metrics) > 0 { + cn := n.Child("metrics") + for i := range v.Metrics { + if s := string(v.Metrics[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Metrics[i] = (r) + } + } + } + } + } + if s := string(v.TimeGranularity); c.match(s) { + ch := n.Child("time_granularity") + if r, ok := c.fn(&ch, s); ok { + v.TimeGranularity = pipelines.LinkedInAdsOptionsLinkedInAdsCustomReportOptionsLinkedInAdsTimeGranularity(r) + } + } +} + +func stringsPipelinesMetaMarketingOptionsMetaMarketingCustomReportOptions(c *stringsWalk, n *Node, v *pipelines.MetaMarketingOptionsMetaMarketingCustomReportOptions) { + if n.isRef() { + return + } + if !n.IsRef("action_attribution_windows") { + if len(v.ActionAttributionWindows) > 0 { + cn := n.Child("action_attribution_windows") + for i := range v.ActionAttributionWindows { + if s := string(v.ActionAttributionWindows[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ActionAttributionWindows[i] = (r) + } + } + } + } + } + if !n.IsRef("action_breakdowns") { + if len(v.ActionBreakdowns) > 0 { + cn := n.Child("action_breakdowns") + for i := range v.ActionBreakdowns { + if s := string(v.ActionBreakdowns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.ActionBreakdowns[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.ActionReportTime == "" && Forced(v.ForceSendFields, "ActionReportTime") { + ch := n.Child("action_report_time") + c.fn(&ch, "") + } + if s := string(v.ActionReportTime); c.match(s) { + ch := n.Child("action_report_time") + if r, ok := c.fn(&ch, s); ok { + v.ActionReportTime = (r) + } + } + if !n.IsRef("breakdowns") { + if len(v.Breakdowns) > 0 { + cn := n.Child("breakdowns") + for i := range v.Breakdowns { + if s := string(v.Breakdowns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Breakdowns[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.Level == "" && Forced(v.ForceSendFields, "Level") { + ch := n.Child("level") + c.fn(&ch, "") + } + if s := string(v.Level); c.match(s) { + ch := n.Child("level") + if r, ok := c.fn(&ch, s); ok { + v.Level = (r) + } + } + if c.mode == EmptyFields && v.TimeIncrement == "" && Forced(v.ForceSendFields, "TimeIncrement") { + ch := n.Child("time_increment") + c.fn(&ch, "") + } + if s := string(v.TimeIncrement); c.match(s) { + ch := n.Child("time_increment") + if r, ok := c.fn(&ch, s); ok { + v.TimeIncrement = (r) + } + } +} + +func stringsPipelinesRedditAdsOptionsRedditAdsCustomReportOptions(c *stringsWalk, n *Node, v *pipelines.RedditAdsOptionsRedditAdsCustomReportOptions) { + if n.isRef() { + return + } + if !n.IsRef("breakdowns") { + if len(v.Breakdowns) > 0 { + cn := n.Child("breakdowns") + for i := range v.Breakdowns { + if s := string(v.Breakdowns[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Breakdowns[i] = (r) + } + } + } + } + } + if !n.IsRef("fields") { + if len(v.Fields) > 0 { + cn := n.Child("fields") + for i := range v.Fields { + if s := string(v.Fields[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Fields[i] = (r) + } + } + } + } + } +} + +func stringsPipelinesTikTokAdsOptionsTikTokAdsCustomReportOptions(c *stringsWalk, n *Node, v *pipelines.TikTokAdsOptionsTikTokAdsCustomReportOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DataLevel == "" && Forced(v.ForceSendFields, "DataLevel") { + ch := n.Child("data_level") + c.fn(&ch, "") + } + if s := string(v.DataLevel); c.match(s) { + ch := n.Child("data_level") + if r, ok := c.fn(&ch, s); ok { + v.DataLevel = pipelines.TikTokAdsOptionsTikTokDataLevel(r) + } + } + if !n.IsRef("dimensions") { + if len(v.Dimensions) > 0 { + cn := n.Child("dimensions") + for i := range v.Dimensions { + if s := string(v.Dimensions[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Dimensions[i] = (r) + } + } + } + } + } + if !n.IsRef("metrics") { + if len(v.Metrics) > 0 { + cn := n.Child("metrics") + for i := range v.Metrics { + if s := string(v.Metrics[i]); c.match(s) { + ch := cn.Elem(i) + if r, ok := c.fn(&ch, s); ok { + v.Metrics[i] = (r) + } + } + } + } + } + if c.mode == EmptyFields && v.ReportType == "" && Forced(v.ForceSendFields, "ReportType") { + ch := n.Child("report_type") + c.fn(&ch, "") + } + if s := string(v.ReportType); c.match(s) { + ch := n.Child("report_type") + if r, ok := c.fn(&ch, s); ok { + v.ReportType = pipelines.TikTokAdsOptionsTikTokReportType(r) + } + } +} + +func stringsPipelinesAvroTransformerOptions(c *stringsWalk, n *Node, v *pipelines.AvroTransformerOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ParseMode == "" && Forced(v.ForceSendFields, "ParseMode") { + ch := n.Child("parse_mode") + c.fn(&ch, "") + } + if s := string(v.ParseMode); c.match(s) { + ch := n.Child("parse_mode") + if r, ok := c.fn(&ch, s); ok { + v.ParseMode = pipelines.ParseMode(r) + } + } + if c.mode == EmptyFields && v.Schema == "" && Forced(v.ForceSendFields, "Schema") { + ch := n.Child("schema") + c.fn(&ch, "") + } + if s := string(v.Schema); c.match(s) { + ch := n.Child("schema") + if r, ok := c.fn(&ch, s); ok { + v.Schema = (r) + } + } + if c.mode == EmptyFields && v.SchemaFilePath == "" && Forced(v.ForceSendFields, "SchemaFilePath") { + ch := n.Child("schema_file_path") + c.fn(&ch, "") + } + if s := string(v.SchemaFilePath); c.match(s) { + ch := n.Child("schema_file_path") + if r, ok := c.fn(&ch, s); ok { + v.SchemaFilePath = (r) + } + } + if !n.IsRef("schema_registry") { + if x := v.SchemaRegistry; x != nil { + ch := n.Child("schema_registry") + stringsPipelinesSchemaRegistryConfig(c, &ch, x) + } + } +} + +func stringsPipelinesJsonTransformerOptions(c *stringsWalk, n *Node, v *pipelines.JsonTransformerOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Schema == "" && Forced(v.ForceSendFields, "Schema") { + ch := n.Child("schema") + c.fn(&ch, "") + } + if s := string(v.Schema); c.match(s) { + ch := n.Child("schema") + if r, ok := c.fn(&ch, s); ok { + v.Schema = (r) + } + } + if c.mode == EmptyFields && v.SchemaEvolutionMode == "" && Forced(v.ForceSendFields, "SchemaEvolutionMode") { + ch := n.Child("schema_evolution_mode") + c.fn(&ch, "") + } + if s := string(v.SchemaEvolutionMode); c.match(s) { + ch := n.Child("schema_evolution_mode") + if r, ok := c.fn(&ch, s); ok { + v.SchemaEvolutionMode = pipelines.FileIngestionOptionsSchemaEvolutionMode(r) + } + } + if c.mode == EmptyFields && v.SchemaFilePath == "" && Forced(v.ForceSendFields, "SchemaFilePath") { + ch := n.Child("schema_file_path") + c.fn(&ch, "") + } + if s := string(v.SchemaFilePath); c.match(s) { + ch := n.Child("schema_file_path") + if r, ok := c.fn(&ch, s); ok { + v.SchemaFilePath = (r) + } + } + if c.mode == EmptyFields && v.SchemaHints == "" && Forced(v.ForceSendFields, "SchemaHints") { + ch := n.Child("schema_hints") + c.fn(&ch, "") + } + if s := string(v.SchemaHints); c.match(s) { + ch := n.Child("schema_hints") + if r, ok := c.fn(&ch, s); ok { + v.SchemaHints = (r) + } + } +} + +func stringsPipelinesProtobufTransformerOptions(c *stringsWalk, n *Node, v *pipelines.ProtobufTransformerOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.DescFilePath == "" && Forced(v.ForceSendFields, "DescFilePath") { + ch := n.Child("desc_file_path") + c.fn(&ch, "") + } + if s := string(v.DescFilePath); c.match(s) { + ch := n.Child("desc_file_path") + if r, ok := c.fn(&ch, s); ok { + v.DescFilePath = (r) + } + } + if c.mode == EmptyFields && v.MessageName == "" && Forced(v.ForceSendFields, "MessageName") { + ch := n.Child("message_name") + c.fn(&ch, "") + } + if s := string(v.MessageName); c.match(s) { + ch := n.Child("message_name") + if r, ok := c.fn(&ch, s); ok { + v.MessageName = (r) + } + } + if c.mode == EmptyFields && v.ParseMode == "" && Forced(v.ForceSendFields, "ParseMode") { + ch := n.Child("parse_mode") + c.fn(&ch, "") + } + if s := string(v.ParseMode); c.match(s) { + ch := n.Child("parse_mode") + if r, ok := c.fn(&ch, s); ok { + v.ParseMode = pipelines.ParseMode(r) + } + } + if !n.IsRef("schema_registry") { + if x := v.SchemaRegistry; x != nil { + ch := n.Child("schema_registry") + stringsPipelinesSchemaRegistryConfig(c, &ch, x) + } + } +} + +func stringsPipelinesFileFilter(c *stringsWalk, n *Node, v *pipelines.FileFilter) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.ModifiedAfter == "" && Forced(v.ForceSendFields, "ModifiedAfter") { + ch := n.Child("modified_after") + c.fn(&ch, "") + } + if s := string(v.ModifiedAfter); c.match(s) { + ch := n.Child("modified_after") + if r, ok := c.fn(&ch, s); ok { + v.ModifiedAfter = (r) + } + } + if c.mode == EmptyFields && v.ModifiedBefore == "" && Forced(v.ForceSendFields, "ModifiedBefore") { + ch := n.Child("modified_before") + c.fn(&ch, "") + } + if s := string(v.ModifiedBefore); c.match(s) { + ch := n.Child("modified_before") + if r, ok := c.fn(&ch, s); ok { + v.ModifiedBefore = (r) + } + } + if c.mode == EmptyFields && v.PathFilter == "" && Forced(v.ForceSendFields, "PathFilter") { + ch := n.Child("path_filter") + c.fn(&ch, "") + } + if s := string(v.PathFilter); c.match(s) { + ch := n.Child("path_filter") + if r, ok := c.fn(&ch, s); ok { + v.PathFilter = (r) + } + } +} + +func stringsPipelinesSchemaRegistryConfig(c *stringsWalk, n *Node, v *pipelines.SchemaRegistryConfig) { + if n.isRef() { + return + } + if !n.IsRef("confluent_options") { + if x := v.ConfluentOptions; x != nil { + ch := n.Child("confluent_options") + stringsPipelinesConfluentSchemaRegistryOptions(c, &ch, x) + } + } + if c.mode == EmptyFields && v.ConnectionName == "" && Forced(v.ForceSendFields, "ConnectionName") { + ch := n.Child("connection_name") + c.fn(&ch, "") + } + if s := string(v.ConnectionName); c.match(s) { + ch := n.Child("connection_name") + if r, ok := c.fn(&ch, s); ok { + v.ConnectionName = (r) + } + } + if c.mode == EmptyFields && v.ProtobufMessageName == "" && Forced(v.ForceSendFields, "ProtobufMessageName") { + ch := n.Child("protobuf_message_name") + c.fn(&ch, "") + } + if s := string(v.ProtobufMessageName); c.match(s) { + ch := n.Child("protobuf_message_name") + if r, ok := c.fn(&ch, s); ok { + v.ProtobufMessageName = (r) + } + } +} + +func stringsPipelinesConfluentSchemaRegistryOptions(c *stringsWalk, n *Node, v *pipelines.ConfluentSchemaRegistryOptions) { + if n.isRef() { + return + } + if c.mode == EmptyFields && v.Subject == "" && Forced(v.ForceSendFields, "Subject") { + ch := n.Child("subject") + c.fn(&ch, "") + } + if s := string(v.Subject); c.match(s) { + ch := n.Child("subject") + if r, ok := c.fn(&ch, s); ok { + v.Subject = (r) + } + } +} + +func stringsResource(c *stringsWalk, n *Node, v *config.Resources, typ, name string) { + switch typ { + case "jobs": + if x := v.Jobs[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesJob(c, &rn, x) + } + case "job_runs": + if x := v.JobRuns[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesJobRun(c, &rn, x) + } + case "pipelines": + if x := v.Pipelines[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesPipeline(c, &rn, x) + } + case "features": + if x := v.Features[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesFeature(c, &rn, x) + } + case "models": + if x := v.Models[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesMlflowModel(c, &rn, x) + } + case "experiments": + if x := v.Experiments[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesMlflowExperiment(c, &rn, x) + } + case "model_serving_endpoints": + if x := v.ModelServingEndpoints[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesModelServingEndpoint(c, &rn, x) + } + case "model_services": + if x := v.ModelServices[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesModelService(c, &rn, x) + } + case "mcp_services": + if x := v.McpServices[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesMcpService(c, &rn, x) + } + case "model_provider_services": + if x := v.ModelProviderServices[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesModelProviderService(c, &rn, x) + } + case "registered_models": + if x := v.RegisteredModels[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesRegisteredModel(c, &rn, x) + } + case "quality_monitors": + if x := v.QualityMonitors[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesQualityMonitor(c, &rn, x) + } + case "catalogs": + if x := v.Catalogs[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesCatalog(c, &rn, x) + } + case "schemas": + if x := v.Schemas[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesSchema(c, &rn, x) + } + case "volumes": + if x := v.Volumes[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesVolume(c, &rn, x) + } + case "external_locations": + if x := v.ExternalLocations[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesExternalLocation(c, &rn, x) + } + case "clusters": + if x := v.Clusters[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesCluster(c, &rn, x) + } + case "dashboards": + if x := v.Dashboards[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesDashboard(c, &rn, x) + } + case "genie_spaces": + if x := v.GenieSpaces[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesGenieSpace(c, &rn, x) + } + case "apps": + if x := v.Apps[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesApp(c, &rn, x) + } + case "secret_scopes": + if x := v.SecretScopes[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesSecretScope(c, &rn, x) + } + case "alerts": + if x := v.Alerts[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesAlert(c, &rn, x) + } + case "sql_warehouses": + if x := v.SqlWarehouses[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesSqlWarehouse(c, &rn, x) + } + case "database_instances": + if x := v.DatabaseInstances[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesDatabaseInstance(c, &rn, x) + } + case "database_catalogs": + if x := v.DatabaseCatalogs[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesDatabaseCatalog(c, &rn, x) + } + case "synced_database_tables": + if x := v.SyncedDatabaseTables[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesSyncedDatabaseTable(c, &rn, x) + } + case "postgres_projects": + if x := v.PostgresProjects[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesPostgresProject(c, &rn, x) + } + case "postgres_branches": + if x := v.PostgresBranches[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesPostgresBranch(c, &rn, x) + } + case "postgres_endpoints": + if x := v.PostgresEndpoints[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesPostgresEndpoint(c, &rn, x) + } + case "postgres_catalogs": + if x := v.PostgresCatalogs[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesPostgresCatalog(c, &rn, x) + } + case "postgres_databases": + if x := v.PostgresDatabases[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesPostgresDatabase(c, &rn, x) + } + case "postgres_roles": + if x := v.PostgresRoles[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesPostgresRole(c, &rn, x) + } + case "postgres_synced_tables": + if x := v.PostgresSyncedTables[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesPostgresSyncedTable(c, &rn, x) + } + case "postgres_snapshot_schedules": + if x := v.PostgresSnapshotSchedules[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesPostgresSnapshotSchedule(c, &rn, x) + } + case "vector_search_endpoints": + if x := v.VectorSearchEndpoints[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesVectorSearchEndpoint(c, &rn, x) + } + case "vector_search_indexes": + if x := v.VectorSearchIndexes[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesVectorSearchIndex(c, &rn, x) + } + case "instance_pools": + if x := v.InstancePools[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesInstancePool(c, &rn, x) + } + case "secrets": + if x := v.Secrets[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesSecret(c, &rn, x) + } + case "cluster_policies": + if x := v.ClusterPolicies[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesClusterPolicy(c, &rn, x) + } + case "internal_immutable_snapshots": + if x := v.Snapshots[name]; x != nil { + ch := n.Child(typ) + rn := ch.Child(name) + stringsResourcesSnapshot(c, &rn, x) + } + } +} diff --git a/bundle/config/workspace.go b/bundle/config/workspace.go index b3311bf07d2..d5fbc7278ca 100644 --- a/bundle/config/workspace.go +++ b/bundle/config/workspace.go @@ -173,8 +173,11 @@ func (w *Workspace) NormalizeHostURL() { func (w *Workspace) Client(ctx context.Context) (*databricks.WorkspaceClient, error) { // Extract query parameters (?o=, ?a=) from the host URL before building // the SDK config. This ensures workspace_id and account_id are available - // for profile resolution during EnsureResolved(). - w.NormalizeHostURL() + // for profile resolution during EnsureResolved(). A copy is normalized: the + // configuration keeps the host as it is written. + normalized := *w + normalized.NormalizeHostURL() + w = &normalized cfg := w.Config(ctx) diff --git a/bundle/configsync/diff.go b/bundle/configsync/diff.go index e42f0a1d83a..aeb593afe50 100644 --- a/bundle/configsync/diff.go +++ b/bundle/configsync/diff.go @@ -8,10 +8,9 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/deployplan" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/structs/structdiff" + "github.com/databricks/cli/libs/structs/structvar" ) type OperationType string @@ -39,13 +38,8 @@ type ResourceChanges map[string]*ConfigChangeDesc type Changes map[string]ResourceChanges -func normalizeValue(v any) (any, error) { - dynValue, err := convert.FromTyped(v, dyn.NilValue) - if err != nil { - return nil, fmt.Errorf("failed to convert value of type %T: %w", v, err) - } - - return dynValue.AsAny(), nil +func normalizeValue(v any) any { + return structvar.NewView(v, nil, nil).AsAny() } func filterEntityDefaults(basePath string, value any) any { @@ -92,10 +86,7 @@ func convertChangeDesc(path string, cd *deployplan.ChangeDesc) (*ConfigChangeDes // cd.Old in this check would classify the change as Replace and fail later in // resolveSelectors because the old key no longer exists in the YAML. hasConfigValue := cd.New != nil - normalizedValue, err := normalizeValue(cd.Remote) - if err != nil { - return nil, fmt.Errorf("failed to normalize remote value: %w", err) - } + normalizedValue := normalizeValue(cd.Remote) if shouldSkipField(path, normalizedValue, hasConfigValue) { return &ConfigChangeDesc{ @@ -129,11 +120,7 @@ func convertChangeDesc(path string, cd *deployplan.ChangeDesc) (*ConfigChangeDes // which keys on the path component (index 3), so a resource literally named // "permissions" ("resources.jobs.permissions") is not misclassified. func isPermissionsOrGrantsSubResource(resourceKey string) bool { - path, err := dyn.NewPathFromString(resourceKey) - if err != nil { - return false - } - _, nodeType := config.GetNodeAndType(path) + nodeType := config.GetResourceTypeFromKey(resourceKey) return strings.HasSuffix(nodeType, ".permissions") || strings.HasSuffix(nodeType, ".grants") } diff --git a/bundle/configsync/resolve.go b/bundle/configsync/resolve.go index cdad5182336..e97c0d5e98c 100644 --- a/bundle/configsync/resolve.go +++ b/bundle/configsync/resolve.go @@ -12,10 +12,10 @@ import ( "strings" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/notebook" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) type FieldChange struct { @@ -42,7 +42,7 @@ type resolvedChange struct { path *structpath.PatternNode steps []sequenceStep // leaf is the merged value the change addresses, invalid for a new field. - leaf dyn.Value + leaf structvar.View // operation decides how many destinations the change needs: a removal has to // reach every definition, an edit only the one that wins the merge. operation OperationType @@ -62,28 +62,23 @@ func resolveSelectors(pathStr string, b *bundle.Bundle, operation OperationType) nodes := node.AsSlice() var result *structpath.PatternNode var steps []sequenceStep - var currentPath dyn.Path - currentValue := b.Config.Value() + var currentPath *structpath.PathNode + currentValue := b.Config.View() for component, n := range nodes { if key, ok := n.StringKey(); ok { result = structpath.NewPatternStringKey(result, key) - currentPath = append(currentPath, dyn.Key(key)) - if currentValue.IsValid() { - currentValue, _ = dyn.GetByPath(currentValue, dyn.Path{dyn.Key(key)}) - } + currentPath = structpath.NewStringKey(currentPath, key) + currentValue = currentValue.Get(key) continue } if idx, ok := n.Index(); ok { - sequencePath := slices.Clone(currentPath) + sequencePath := currentPath result = structpath.NewPatternIndex(result, idx) - currentPath = append(currentPath, dyn.Index(idx)) - var element dyn.Value - if currentValue.IsValid() { - element, _ = dyn.GetByPath(currentValue, dyn.Path{dyn.Index(idx)}) - currentValue = element - } + currentPath = structpath.NewIndex(currentPath, idx) + element := currentValue.Index(idx) + currentValue = element steps = append(steps, sequenceStep{ component: component, sequencePath: sequencePath, @@ -98,26 +93,22 @@ func resolveSelectors(pathStr string, b *bundle.Bundle, operation OperationType) // Check for key-value selector: [key='value'] if key, value, ok := n.KeyValue(); ok { - if !currentValue.IsValid() || currentValue.Kind() != dyn.KindSequence { + if !currentValue.IsValid() || currentValue.Kind() != structvar.KindSequence { return resolvedChange{}, fmt.Errorf("cannot apply [%s='%s'] selector to non-array value in path %s", key, value, pathStr) } - seq, _ := currentValue.AsSequence() foundIndex := -1 + var foundElement structvar.View - for i, elem := range seq { - keyValue, err := dyn.GetByPath(elem, dyn.Path{dyn.Key(key)}) - if err != nil { - continue - } - - if keyValue.Kind() == dyn.KindString && keyValue.MustString() == value { + for i, elem := range currentValue.Sequence() { + if keyValue, ok := elem.Get(key).AsString(); ok && keyValue == value { foundIndex = i + foundElement = elem break } } - sequencePath := slices.Clone(currentPath) + sequencePath := currentPath if foundIndex == -1 { if operation == OperationAdd { @@ -128,20 +119,20 @@ func resolveSelectors(pathStr string, b *bundle.Bundle, operation OperationType) newElement: true, }) // Can't navigate further into non-existent element - currentValue = dyn.Value{} + currentValue = structvar.View{} continue } return resolvedChange{}, fmt.Errorf("no array element found with %s='%s' in path %s", key, value, pathStr) } result = structpath.NewPatternIndex(result, foundIndex) - currentPath = append(currentPath, dyn.Index(foundIndex)) + currentPath = structpath.NewIndex(currentPath, foundIndex) steps = append(steps, sequenceStep{ component: component, sequencePath: sequencePath, - element: seq[foundIndex], + element: foundElement, }) - currentValue = seq[foundIndex] + currentValue = foundElement continue } } @@ -200,7 +191,7 @@ func adjustArrayIndex(path *structpath.PatternNode, scope string, operations map // disappears looks identical to one that was written. preResolved is the merged // config with ${...} references still literal, used to detect variable-reference // parents. -func ResolveChanges(ctx context.Context, b *bundle.Bundle, configChanges Changes, preResolved dyn.Value) ([]FieldChange, int, error) { +func ResolveChanges(ctx context.Context, b *bundle.Bundle, configChanges Changes, preResolved structvar.View) ([]FieldChange, int, error) { var result []FieldChange skipped := 0 targetName := b.Config.Bundle.Target @@ -360,8 +351,11 @@ func ResolveChanges(ctx context.Context, b *bundle.Bundle, configChanges Changes // on it belongs in that same block. filePath = block.file } else { - resourceLocation := b.Config.GetLocation(resourceKey) - filePath = resourceLocation.File + resourcePath, err := structpath.ParsePath(resourceKey) + if err != nil { + return nil, 0, fmt.Errorf("failed to parse resource key %s: %w", resourceKey, err) + } + filePath = b.Config.GetLocationOfExact(resourcePath).File } if filePath == "" { return nil, 0, fmt.Errorf("failed to find location for resource %s for a field %s", resourceKey, fieldPath) diff --git a/bundle/configsync/sequences.go b/bundle/configsync/sequences.go index 4ce0274fdb3..3a9bd84dd1e 100644 --- a/bundle/configsync/sequences.go +++ b/bundle/configsync/sequences.go @@ -12,9 +12,9 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) // Write-back has to turn a position in the merged configuration into a position in a @@ -56,13 +56,13 @@ func (b sourceBlock) scopeKey() string { } // blockResolver answers which physical block a merged value came from, by location: -// merging accumulates them (libs/dyn/merge), so a value assembled from two blocks +// merging accumulates them, so a value assembled from two blocks // reports a location in each. Selecting a target folds its overrides into resources and // drops the targets subtree, so the blocks are recovered by parsing the files again. type blockResolver struct { // blocks holds one parsed file per contributing file, keyed by block, so a // path relative to a block can be looked up inside it. - blocks map[sourceBlock]dyn.Value + blocks map[sourceBlock]structvar.View target string byLocation map[diag.Location]sourceBlock } @@ -74,13 +74,10 @@ type blockResolver struct { // The contributing files are parsed directly rather than reloaded through the mutator // pipeline, which would run the bundle's preinit script a second time. func newBlockResolver(ctx context.Context, b *bundle.Bundle) *blockResolver { - root := b.Config.Value() - if !root.IsValid() { - return nil - } + root := b.Config.View() r := &blockResolver{ - blocks: make(map[sourceBlock]dyn.Value), + blocks: make(map[sourceBlock]structvar.View), target: b.Config.Bundle.Target, byLocation: make(map[diag.Location]sourceBlock), } @@ -92,14 +89,15 @@ func newBlockResolver(ctx context.Context, b *bundle.Bundle) *blockResolver { log.Debugf(ctx, "config-remote-sync: cannot read %s, treating its sequences as unsplit: %v", file, err) continue } - parsed, diags := config.LoadFromBytes(file, contents) + loaded, diags := config.LoadFromBytes(file, contents) if diags.HasError() { log.Debugf(ctx, "config-remote-sync: cannot parse %s, treating its sequences as unsplit: %v", file, diags.Error()) continue } - r.registerBlock(parsed.Value(), sourceBlock{file: file}) + parsed := loaded.View() + r.registerBlock(parsed, sourceBlock{file: file}) if r.target != "" { - r.registerBlock(parsed.Value(), sourceBlock{override: true, file: file}) + r.registerBlock(parsed, sourceBlock{override: true, file: file}) } } @@ -109,9 +107,9 @@ func newBlockResolver(ctx context.Context, b *bundle.Bundle) *blockResolver { return r } -func referencedFiles(root dyn.Value) map[string]struct{} { +func referencedFiles(root structvar.View) map[string]struct{} { files := map[string]struct{}{} - _ = dyn.WalkReadOnly(root, func(_ dyn.Path, v dyn.Value) error { + _ = structvar.Walk(root, func(_ *structpath.PathNode, v structvar.View) error { for _, location := range v.Locations() { if location.File != "" { files[location.File] = struct{}{} @@ -126,16 +124,16 @@ func referencedFiles(root dyn.Value) map[string]struct{} { // resources subtree back to it, so a merged value can later be traced to the // region it was written in. Does nothing when the file has no such region, which // is why r.blocks doubles as the set of blocks that exist. -func (r *blockResolver) registerBlock(parsed dyn.Value, block sourceBlock) { - subtree, err := dyn.GetByPath(parsed, r.regionPath(block, dyn.NewPath(dyn.Key("resources")))) - if err != nil { +func (r *blockResolver) registerBlock(parsed structvar.View, block sourceBlock) { + subtree := parsed.Lookup(r.regionPath(block, structpath.NewStringKey(nil, "resources"))) + if !subtree.IsValid() { return } // Keep the parsed file: resolving a path inside this block needs the tree it // came from, and the entry also marks the block as present for sortedBlocks. r.blocks[block] = parsed - _ = dyn.WalkReadOnly(subtree, func(_ dyn.Path, v dyn.Value) error { + _ = structvar.Walk(subtree, func(_ *structpath.PathNode, v structvar.View) error { for _, location := range v.Locations() { // A file only carries locations of its own, so a location seen here // belongs to this block. First writer wins: an outer node accumulates @@ -153,11 +151,11 @@ func (r *blockResolver) registerBlock(parsed dyn.Value, block sourceBlock) { } // regionPath prefixes a resources-relative path with the region it belongs to. -func (r *blockResolver) regionPath(block sourceBlock, path dyn.Path) dyn.Path { +func (r *blockResolver) regionPath(block sourceBlock, path *structpath.PathNode) *structpath.PathNode { if !block.override { return path } - return append(dyn.NewPath(dyn.Key("targets"), dyn.Key(r.target)), path...) + return structpath.Join(structpath.NewPath(nil, "targets", r.target), path.AsSlice()...) } // candidatePath renders a resolved path the way the patch layer addresses it, @@ -211,7 +209,7 @@ func compareBlocks(a, b sourceBlock) int { // blocksOf returns the distinct blocks that contributed to value, sorted with the // top-level block first. More than one result means the value is assembled from // several regions and has no single source location. -func (r *blockResolver) blocksOf(value dyn.Value) []sourceBlock { +func (r *blockResolver) blocksOf(value structvar.View) []sourceBlock { var blocks []sourceBlock for _, location := range value.Locations() { block, ok := r.byLocation[location] @@ -233,7 +231,7 @@ func (r *blockResolver) blocksOf(value dyn.Value) []sourceBlock { // Locations accumulate in merge order, so the first one that maps to a block is the // winner and writing any other copy would leave the effective value unchanged. Load // order is the only thing that distinguishes two blocks in the same scope. -func (r *blockResolver) winningBlock(value dyn.Value) (sourceBlock, bool) { +func (r *blockResolver) winningBlock(value structvar.View) (sourceBlock, bool) { for _, location := range value.Locations() { if block, ok := r.byLocation[location]; ok { return block, true @@ -245,17 +243,13 @@ func (r *blockResolver) winningBlock(value dyn.Value) (sourceBlock, bool) { // indexWithinBlock returns the position of element inside block, where sequencePath is // relative to the block. A block is one parsed file, so the sequence read here holds // only that file's entries and a plain index into it is block-local. -func (r *blockResolver) indexWithinBlock(block sourceBlock, sequencePath dyn.Path, element dyn.Value) (int, bool) { +func (r *blockResolver) indexWithinBlock(block sourceBlock, sequencePath *structpath.PathNode, element structvar.View) (int, bool) { parsed, ok := r.blocks[block] if !ok { return 0, false } - sequence, err := dyn.GetByPath(parsed, r.regionPath(block, sequencePath)) - if err != nil { - return 0, false - } - entries, ok := sequence.AsSequence() - if !ok { + sequence := parsed.Lookup(r.regionPath(block, sequencePath)) + if sequence.Kind() != structvar.KindSequence { return 0, false } @@ -264,7 +258,7 @@ func (r *blockResolver) indexWithinBlock(block sourceBlock, sequencePath dyn.Pat locations[location] = struct{}{} } - for local, entry := range entries { + for local, entry := range sequence.Sequence() { for _, location := range entry.Locations() { if _, ok := locations[location]; ok { return local, true @@ -323,7 +317,7 @@ func (d routeDestination) scopeKey() string { func (r *blockResolver) routeDestinations(change resolvedChange) ([]routeDestination, error) { // The value whose definitions have to be reached: the element itself when the // change addresses one, otherwise the field being removed. - var target dyn.Value + var target structvar.View if addressesWholeElement(change) { target = change.steps[len(change.steps)-1].element } else if change.operation == OperationRemove { @@ -489,15 +483,15 @@ func (r *blockResolver) blockForNewElement(change resolvedChange) (sourceBlock, // blocksDefiningSequence returns the blocks that write the sequence at sequencePath. // This is for a value that does not exist yet, where only the receiving sequence is // known; an existing value is traced through its own locations instead. -func (r *blockResolver) blocksDefiningSequence(change resolvedChange, sequencePath dyn.Path) []sourceBlock { +func (r *blockResolver) blocksDefiningSequence(change resolvedChange, sequencePath *structpath.PathNode) []sourceBlock { var blocks []sourceBlock for _, block := range r.sortedBlocks() { blockPath, ok := r.sequencePathWithinBlock(block, change, sequencePath) if !ok { continue } - sequence, err := dyn.GetByPath(r.blocks[block], r.regionPath(block, blockPath)) - if err != nil { + sequence := r.blocks[block].Lookup(r.regionPath(block, blockPath)) + if !sequence.IsValid() { continue } if slices.ContainsFunc(sequence.Locations(), func(l diag.Location) bool { return l.File == block.file }) { @@ -513,22 +507,22 @@ func (r *blockResolver) blocksDefiningSequence(change resolvedChange, sequencePa // // change.steps is ordered outermost first, so each translated index is already known // by the time a deeper step needs it. -func (r *blockResolver) sequencePathWithinBlock(block sourceBlock, change resolvedChange, sequencePath dyn.Path) (dyn.Path, bool) { - result := slices.Clone(sequencePath) +func (r *blockResolver) sequencePathWithinBlock(block sourceBlock, change resolvedChange, sequencePath *structpath.PathNode) (*structpath.PathNode, bool) { + result := sequencePath.AsSlice() for _, step := range change.steps { - at := len(step.sequencePath) - if step.newElement || at >= len(sequencePath) { + at := step.sequencePath.Len() + if step.newElement || at >= len(result) { continue } // The step's own path is a prefix of sequencePath, so the indices translated // so far already apply to it. - index, ok := r.indexWithinBlock(block, result[:at], step.element) + index, ok := r.indexWithinBlock(block, structpath.Join(nil, result[:at]...), step.element) if !ok { return nil, false } - result[at] = dyn.Index(index) + result[at] = structpath.NewIndex(nil, index) } - return result, true + return structpath.Join(nil, result...), true } // pathWithinBlock rewrites a change's path so every sequence index addresses the @@ -582,8 +576,8 @@ type sequenceStep struct { // component is how many path components precede this sequence's index. component int // sequencePath is the sequence itself, e.g. resources.jobs.j.tasks. - sequencePath dyn.Path - element dyn.Value + sequencePath *structpath.PathNode + element structvar.View // newElement marks an Add whose key is not in the merged sequence yet. newElement bool } diff --git a/bundle/configsync/telemetry_test.go b/bundle/configsync/telemetry_test.go index d2a3da537f8..001ecb2f9d2 100644 --- a/bundle/configsync/telemetry_test.go +++ b/bundle/configsync/telemetry_test.go @@ -3,7 +3,6 @@ package configsync import ( "testing" - "github.com/databricks/cli/libs/dyn" "github.com/stretchr/testify/assert" ) @@ -63,37 +62,30 @@ func TestResourceTypeFromKey(t *testing.T) { } func TestRestoreStatsCounters(t *testing.T) { - resolved := dyn.V(map[string]dyn.Value{ - "variables": dyn.V(map[string]dyn.Value{ - "region": dyn.V(map[string]dyn.Value{"value": dyn.V("us-east-1")}), - "other": dyn.V(map[string]dyn.Value{"value": dyn.V("eu-west-1")}), - }), - }) + resolved := variablesConfig(t, map[string]any{"region": "us-east-1", "other": "eu-west-1"}) // Original pure ref still matching: restored but not counted (safe path). var kept RestoreStats - result := restoreOriginalRefs("us-east-1", dyn.V("${var.region}"), resolved, &kept) + result := restoreOriginalRefs("us-east-1", scalarView(t, "${var.region}"), resolved, &kept) assert.Equal(t, "${var.region}", result) assert.Equal(t, RestoreStats{}, kept) // Pure ref whose value changed to another variable's value: re-targeted. var retargeted RestoreStats - result = restoreOriginalRefs("eu-west-1", dyn.V("${var.region}"), resolved, &retargeted) + result = restoreOriginalRefs("eu-west-1", scalarView(t, "${var.region}"), resolved, &retargeted) assert.Equal(t, "${var.other}", result) assert.Equal(t, RestoreStats{Retargeted: 1}, retargeted) // New sequence element leaf restored from a sibling reference. - siblings := []dyn.Value{ - dyn.V(map[string]dyn.Value{"region": dyn.V("${var.region}")}), - } + siblings := tasksView(t, `task_key: "${var.region}"`) var fromSiblings RestoreStats - resultMap := restoreFromSiblings(map[string]any{"region": "us-east-1"}, siblings, resolved, &fromSiblings).(map[string]any) - assert.Equal(t, "${var.region}", resultMap["region"]) + resultMap := restoreFromSiblings(map[string]any{"task_key": "us-east-1"}, siblings, resolved, &fromSiblings).(map[string]any) + assert.Equal(t, "${var.region}", resultMap["task_key"]) assert.Equal(t, RestoreStats{FromSiblings: 1}, fromSiblings) // Hardcoded value: nothing restored, nothing counted. var none RestoreStats - result = restoreOriginalRefs("hardcoded", dyn.V("hardcoded"), resolved, &none) + result = restoreOriginalRefs("hardcoded", scalarView(t, "hardcoded"), resolved, &none) assert.Equal(t, "hardcoded", result) assert.Equal(t, RestoreStats{}, none) } diff --git a/bundle/configsync/variables.go b/bundle/configsync/variables.go index 442224158f5..c6e98e1bb73 100644 --- a/bundle/configsync/variables.go +++ b/bundle/configsync/variables.go @@ -3,26 +3,26 @@ package configsync import ( "context" "errors" + "reflect" "strings" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator" "github.com/databricks/cli/bundle/config/mutator/resourcemutator" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) -// varPrefix is the dyn.Path prefix for the ${var.X} shorthand. -var varPrefix = dyn.NewPath(dyn.Key("var")) +// varPrefix is the path prefix for the ${var.X} shorthand. +var varPrefix = structpath.NewStringKey(nil, "var") // RestoreVariableReferences replaces hardcoded change values with variable // references (${var.foo}, ${bundle.target}, ${resources.X.Y.id}) when the // value can be traced back to a reference in the original YAML. Resource IDs // are injected from state since they aren't materialized into the resolved -// config's dyn.Value tree. +// config's tree. // // For Replace operations, restoration consults the pre-resolved YAML at the // exact field position and tries three steps in order: @@ -52,11 +52,11 @@ var varPrefix = dyn.NewPath(dyn.Key("var")) // Restoration counts by mechanism are accumulated into stats (used for // telemetry); pass nil when counters are not needed (the counter methods are // nil-safe). -func RestoreVariableReferences(ctx context.Context, b *bundle.Bundle, fieldChanges []FieldChange, preResolved dyn.Value, stats *RestoreStats) error { +func RestoreVariableReferences(ctx context.Context, b *bundle.Bundle, fieldChanges []FieldChange, preResolved structvar.View, stats *RestoreStats) error { if !preResolved.IsValid() { return errors.New("pre-resolved config unavailable; variable-backed fields will be hardcoded") } - resolved := b.Config.Value() + resolved := resolvedConfig{view: b.Config.View(), overrides: map[string]any{}} // Mirror mutator.lookup's source-linked deployment override: when enabled, // ${workspace.file_path} resolves to b.SyncRootPath rather than the typed @@ -65,21 +65,18 @@ func RestoreVariableReferences(ctx context.Context, b *bundle.Bundle, fieldChang // actual deployed path and variables are lost on Replace. Keep this in // sync with mutator.lookup if new overrides are added there. if config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) { - fpPath := dyn.NewPath(dyn.Key("workspace"), dyn.Key("file_path")) - if updated, err := dyn.SetByPath(resolved, fpPath, dyn.V(b.SyncRootPath)); err == nil { - resolved = updated - } + resolved.overrides["workspace.file_path"] = b.SyncRootPath } // Augment resolved with resource IDs from state — only when the config // actually uses ${resources.X.Y.id} references. The IDs aren't materialized - // into b.Config.Value() (they live in the StateDB), so we inject them here + // into b.Config (they live in the StateDB), so we inject them here // to enable sibling-based restoration. Skipped entirely for bundles with // no resource refs to avoid opening state DB files unnecessarily. resourceRefs := collectResourceIDRefs(preResolved) if len(resourceRefs) > 0 { if lookup := resourceIDLookup(b); lookup != nil { - resolved = injectResourceIDs(ctx, resolved, resourceRefs, lookup) + injectResourceIDs(ctx, resolved, resourceRefs, lookup) } else { log.Debugf(ctx, "variable restoration: state DB unavailable, skipping resource ID injection for %d refs", len(resourceRefs)) } @@ -116,10 +113,10 @@ func RestoreVariableReferences(ctx context.Context, b *bundle.Bundle, fieldChang // LoadPreResolvedConfig loads the bundle's configuration through the standard // loader mutators (entry point, includes, target overrides) but without -// variable resolution. The resulting dyn.Value is fully merged across files +// variable resolution. The resulting configuration is fully merged across files // and targets, yet retains ${...} references as literal strings. Returns -// InvalidValue if loading fails (restoration is then skipped). -func LoadPreResolvedConfig(ctx context.Context, b *bundle.Bundle) dyn.Value { +// an invalid value if loading fails (restoration is then skipped). +func LoadPreResolvedConfig(ctx context.Context, b *bundle.Bundle) structvar.View { fresh := &bundle.Bundle{ BundleRootPath: b.BundleRootPath, BundleRoot: b.BundleRoot, @@ -141,7 +138,26 @@ func LoadPreResolvedConfig(ctx context.Context, b *bundle.Bundle) dyn.Value { resourcemutator.MergePipelineClusters(), resourcemutator.MergeApps(), ) - return fresh.Config.Value() + return fresh.Config.View() +} + +// resolvedConfig is the bundle's resolved configuration plus values that are known +// only outside of it (the source-linked file path, resource IDs from state). +type resolvedConfig struct { + view structvar.View + overrides map[string]any // by path +} + +// lookup returns the value at path as a Go value, and whether it exists. +func (c resolvedConfig) lookup(path *structpath.PathNode) (any, bool) { + if v, ok := c.overrides[path.String()]; ok { + return v, true + } + v := c.view.Lookup(path) + if !v.IsValid() { + return nil, false + } + return v.AsAny(), true } // resourceIDLookup returns a function that resolves resource keys to their @@ -158,20 +174,17 @@ func resourceIDLookup(b *bundle.Bundle) func(string) string { // ${resources...id} references. Returns the unique set of paths // so the caller can inject IDs at those positions; returns nil if no such // references exist. -func collectResourceIDRefs(preResolved dyn.Value) []dyn.Path { +func collectResourceIDRefs(preResolved structvar.View) []*structpath.PathNode { seen := map[string]bool{} - var paths []dyn.Path - _ = dyn.WalkReadOnly(preResolved, func(_ dyn.Path, v dyn.Value) error { + var paths []*structpath.PathNode + _ = structvar.Walk(preResolved, func(_ *structpath.PathNode, v structvar.View) error { s, ok := v.AsString() - if !ok || !dynvar.IsPureVariableReference(s) || seen[s] { + if !ok || !structvar.IsPureVariableReference(s) || seen[s] { return nil } seen[s] = true - p, ok := dynvar.PureReferenceToPath(s) - if !ok { - return nil - } - if len(p) != 4 || p[0].Key() != "resources" || p[3].Key() != "id" { + p, ok := structvar.PureReferenceToPath(s) + if !ok || p.Len() != 4 || p.KeyAt(0) != "resources" || p.KeyAt(3) != "id" { return nil } paths = append(paths, p) @@ -180,47 +193,33 @@ func collectResourceIDRefs(preResolved dyn.Value) []dyn.Path { return paths } -// injectResourceIDs populates the resolved dyn.Value with IDs from state for -// the given resource reference paths. Skips references whose IDs aren't in -// state or that can't be written back into the dyn.Value tree. -func injectResourceIDs(ctx context.Context, resolved dyn.Value, paths []dyn.Path, lookupID func(string) string) dyn.Value { +// injectResourceIDs records IDs from state for the given resource reference paths in +// resolved. Skips references whose IDs aren't in state. +func injectResourceIDs(ctx context.Context, resolved resolvedConfig, paths []*structpath.PathNode, lookupID func(string) string) { for _, p := range paths { - resourceKey := p[:3].String() + resourceKey := p.Prefix(3).String() id := lookupID(resourceKey) if id == "" { log.Debugf(ctx, "variable restoration: no state entry for resource %q", resourceKey) continue } - updated, err := dyn.SetByPath(resolved, p, dyn.V(id)) - if err != nil { - log.Debugf(ctx, "variable restoration: SetByPath failed for %s: %v", p, err) - continue - } - resolved = updated + resolved.overrides[p.String()] = id } - return resolved } -// resolveReferencePath converts a variable reference string to the dyn.Path +// resolveReferencePath converts a variable reference string to the path // where its resolved value can be found in the bundle config. It applies the // same ${var.X} → variables.X.value shorthand rewriting as the variable // resolution mutator. -func resolveReferencePath(refStr string) (dyn.Path, bool) { - p, ok := dynvar.PureReferenceToPath(refStr) +func resolveReferencePath(refStr string) (*structpath.PathNode, bool) { + p, ok := structvar.PureReferenceToPath(refStr) if !ok { return nil, false } - if p.HasPrefix(varPrefix) && len(p) >= 2 { - newPath := dyn.NewPath( - dyn.Key("variables"), - p[1], - dyn.Key("value"), - ) - if len(p) > 2 { - newPath = newPath.Append(p[2:]...) - } - return newPath, true + if p.HasPrefix(varPrefix) && p.Len() >= 2 { + newPath := structpath.NewPath(nil, "variables", p.KeyAt(1), "value") + return structpath.Join(newPath, p.AsSlice()[2:]...), true } return p, true @@ -234,7 +233,7 @@ func resolveReferencePath(refStr string) (dyn.Path, bool) { // new value, falls back to a global lookup: if the new value uniquely matches // a different variable, that variable is used instead. The field's prior use // of a variable is the false-positive guard. -func restoreOriginalRefs(value any, preResolved, resolved dyn.Value, stats *RestoreStats) any { +func restoreOriginalRefs(value any, preResolved structvar.View, resolved resolvedConfig, stats *RestoreStats) any { switch v := value.(type) { case string, bool, int64: if ref, ok := matchOriginalRef(value, preResolved, resolved); ok { @@ -254,26 +253,14 @@ func restoreOriginalRefs(value any, preResolved, resolved dyn.Value, stats *Rest return value case map[string]any: - preMap, _ := preResolved.AsMap() for key, val := range v { - var childPre dyn.Value - if preMap.Len() > 0 { - if p, ok := preMap.GetPairByString(key); ok { - childPre = p.Value - } - } - v[key] = restoreOriginalRefs(val, childPre, resolved, stats) + v[key] = restoreOriginalRefs(val, preResolved.Get(key), resolved, stats) } return v case []any: - preSeq, _ := preResolved.AsSequence() for i, val := range v { - var childPre dyn.Value - if i < len(preSeq) { - childPre = preSeq[i] - } - v[i] = restoreOriginalRefs(val, childPre, resolved, stats) + v[i] = restoreOriginalRefs(val, preResolved.Index(i), resolved, stats) } return v @@ -287,37 +274,34 @@ func restoreOriginalRefs(value any, preResolved, resolved dyn.Value, stats *Rest // relative path: if exactly one unique pure variable reference across siblings // resolves to the leaf value, that reference is substituted. Multiple // different matching references are treated as ambiguous and skipped. -func restoreFromSiblings(value any, siblings []dyn.Value, resolved dyn.Value, stats *RestoreStats) any { - return restoreFromSiblingsAt(value, siblings, resolved, dyn.EmptyPath, stats) +func restoreFromSiblings(value any, siblings []structvar.View, resolved resolvedConfig, stats *RestoreStats) any { + return restoreFromSiblingsAt(value, siblings, resolved, nil, stats) } -func restoreFromSiblingsAt(value any, siblings []dyn.Value, resolved dyn.Value, relPath dyn.Path, stats *RestoreStats) any { +func restoreFromSiblingsAt(value any, siblings []structvar.View, resolved resolvedConfig, relPath *structpath.PathNode, stats *RestoreStats) any { switch v := value.(type) { case string, bool, int64: refs := map[string]struct{}{} strVal, isStr := value.(string) for _, sib := range siblings { - sv, err := dyn.GetByPath(sib, relPath) - if err != nil { - continue - } + sv := sib.Lookup(relPath) s, ok := sv.AsString() if !ok { continue } - if dynvar.IsPureVariableReference(s) { + if structvar.IsPureVariableReference(s) { rp, ok := resolveReferencePath(s) if !ok { continue } - rv, getErr := dyn.GetByPath(resolved, rp) - if getErr != nil { + rv, found := resolved.lookup(rp) + if !found { continue } - if rv.AsAny() == value { + if equalValues(rv, value) { refs[s] = struct{}{} } - } else if isStr && dynvar.ContainsVariableReference(s) { + } else if isStr && structvar.ContainsVariableReference(s) { // Compound interpolation in sibling: try to align the new // value against the sibling's template. If all variables // match at their positions, the template (possibly with @@ -337,13 +321,13 @@ func restoreFromSiblingsAt(value any, siblings []dyn.Value, resolved dyn.Value, case map[string]any: for key, val := range v { - v[key] = restoreFromSiblingsAt(val, siblings, resolved, relPath.Append(dyn.Key(key)), stats) + v[key] = restoreFromSiblingsAt(val, siblings, resolved, structpath.NewStringKey(relPath, key), stats) } return v case []any: for i, val := range v { - v[i] = restoreFromSiblingsAt(val, siblings, resolved, relPath.Append(dyn.Index(i)), stats) + v[i] = restoreFromSiblingsAt(val, siblings, resolved, structpath.NewIndex(relPath, i), stats) } return v @@ -355,51 +339,33 @@ func restoreFromSiblingsAt(value any, siblings []dyn.Value, resolved dyn.Value, // isPureVarRef reports whether the pre-resolved value at the field is a pure // ${var.X} reference. Used to gate the fallback substitution: only fields that // already used a variable can be re-targeted to a different variable. -func isPureVarRef(preResolved dyn.Value) bool { +func isPureVarRef(preResolved structvar.View) bool { if !preResolved.IsValid() { return false } s, ok := preResolved.AsString() - if !ok || !dynvar.IsPureVariableReference(s) { + if !ok || !structvar.IsPureVariableReference(s) { return false } - p, ok := dynvar.PureReferenceToPath(s) - if !ok { - return false - } - return p.HasPrefix(varPrefix) + p, ok := structvar.PureReferenceToPath(s) + return ok && p.HasPrefix(varPrefix) } // matchAnyVariable searches all bundle variables for a unique scalar value that // equals remoteValue. Returns the ${var.X} reference on a unique match, "" // otherwise. Multiple matches are treated as ambiguous and skipped. -func matchAnyVariable(remoteValue any, resolved dyn.Value) (string, bool) { - variables, err := dyn.GetByPath(resolved, dyn.NewPath(dyn.Key("variables"))) - if err != nil { - return "", false - } - vmap, ok := variables.AsMap() - if !ok { - return "", false - } +func matchAnyVariable(remoteValue any, resolved resolvedConfig) (string, bool) { var match string count := 0 - for _, pair := range vmap.Pairs() { - name, ok := pair.Key.AsString() - if !ok { - continue - } - v, getErr := dyn.GetByPath(pair.Value, dyn.NewPath(dyn.Key("value"))) - if getErr != nil { - continue - } + for name, variable := range resolved.view.Get("variables").MapItems() { + v := variable.Get("value") switch v.Kind() { - case dyn.KindString, dyn.KindInt, dyn.KindBool: - if v.AsAny() == remoteValue { - match = pathToRef(varPrefix.Append(dyn.Key(name))) + case structvar.KindString, structvar.KindInt, structvar.KindBool: + if equalValues(v.AsAny(), remoteValue) { + match = pathToRef(structpath.NewStringKey(varPrefix, name)) count++ } - case dyn.KindInvalid, dyn.KindMap, dyn.KindSequence, dyn.KindFloat, dyn.KindTime, dyn.KindNil: + case structvar.KindInvalid, structvar.KindMap, structvar.KindSequence, structvar.KindFloat, structvar.KindTime, structvar.KindNil: // Skip non-scalar / unsupported variable values. } } @@ -409,19 +375,19 @@ func matchAnyVariable(remoteValue any, resolved dyn.Value) (string, bool) { return "", false } -// pathToRef formats a dyn.Path as a "${...}" interpolation reference. -func pathToRef(p dyn.Path) string { +// pathToRef formats a path as a "${...}" interpolation reference. +func pathToRef(p *structpath.PathNode) string { return "${" + p.String() + "}" } // matchOriginalRef checks if the pre-resolved config value at this position // was a pure variable reference whose resolved value equals remoteValue. -func matchOriginalRef(remoteValue any, preResolved, resolved dyn.Value) (string, bool) { +func matchOriginalRef(remoteValue any, preResolved structvar.View, resolved resolvedConfig) (string, bool) { if !preResolved.IsValid() { return "", false } s, ok := preResolved.AsString() - if !ok || !dynvar.IsPureVariableReference(s) { + if !ok || !structvar.IsPureVariableReference(s) { return "", false } @@ -430,12 +396,12 @@ func matchOriginalRef(remoteValue any, preResolved, resolved dyn.Value) (string, return "", false } - resolvedV, err := dyn.GetByPath(resolved, resolvedPath) - if err != nil { + resolvedV, found := resolved.lookup(resolvedPath) + if !found { return "", false } - if resolvedV.AsAny() == remoteValue { + if equalValues(resolvedV, remoteValue) { return s, true } return "", false @@ -455,12 +421,12 @@ func matchOriginalRef(remoteValue any, preResolved, resolved dyn.Value) (string, // new value contains "in" inside an unrelated word, that occurrence is still // rewritten to ${var.X}. Variables in the template are processed in order of // appearance, which is usually what the user expects. -func restoreCompoundInterpolation(remoteValue string, preResolved, resolved dyn.Value) (string, bool) { +func restoreCompoundInterpolation(remoteValue string, preResolved structvar.View, resolved resolvedConfig) (string, bool) { if !preResolved.IsValid() { return "", false } template, ok := preResolved.AsString() - if !ok || !dynvar.ContainsVariableReference(template) || dynvar.IsPureVariableReference(template) { + if !ok || !structvar.ContainsVariableReference(template) || structvar.IsPureVariableReference(template) { return "", false } @@ -481,7 +447,7 @@ func restoreCompoundInterpolation(remoteValue string, preResolved, resolved dyn. result = result[:idx] + seg.raw + result[idx+len(seg.resolvedValue):] } - if !dynvar.ContainsVariableReference(result) { + if !structvar.ContainsVariableReference(result) { return "", false } return result, true @@ -498,8 +464,8 @@ type templateSegment struct { // parseTemplateSegments splits a template string like "/mnt/${var.X}/raw" // into alternating literal and variable segments, resolving each variable. // Returns nil if any variable can't be resolved. -func parseTemplateSegments(template string, resolved dyn.Value) []templateSegment { - ref, ok := dynvar.NewRef(dyn.V(template)) +func parseTemplateSegments(template string, resolved resolvedConfig) []templateSegment { + ref, ok := structvar.NewRef(template) if !ok { return nil } @@ -526,12 +492,12 @@ func parseTemplateSegments(template string, resolved dyn.Value) []templateSegmen return nil } - resolvedV, err := dyn.GetByPath(resolved, resolvedPath) - if err != nil { + resolvedV, found := resolved.lookup(resolvedPath) + if !found { return nil } - resolvedStr, ok := resolvedV.AsString() + resolvedStr, ok := resolvedV.(string) if !ok { return nil } @@ -554,18 +520,15 @@ func parseTemplateSegments(template string, resolved dyn.Value) []templateSegmen return segments } -// preResolvedValueAt returns the pre-resolved dyn.Value at the field path, +// preResolvedValueAt returns the pre-resolved value at the field path, // if the field exists in the merged pre-resolved config. -func preResolvedValueAt(preResolved dyn.Value, fieldPath string) (dyn.Value, bool) { - p, err := dyn.NewPathFromString(fieldPath) +func preResolvedValueAt(preResolved structvar.View, fieldPath string) (structvar.View, bool) { + p, err := structpath.ParsePath(fieldPath) if err != nil { - return dyn.InvalidValue, false + return structvar.View{}, false } - v, err := dyn.GetByPath(preResolved, p) - if err != nil { - return dyn.InvalidValue, false - } - return v, true + v := preResolved.Lookup(p) + return v, v.IsValid() } // parentIsVariableReference reports whether the parent of fieldPath resolves to a @@ -573,7 +536,7 @@ func preResolvedValueAt(preResolved dyn.Value, fieldPath string) (dyn.Value, boo // ${var.spark_conf}). A nested key or index cannot be written into such a scalar, // so config-remote-sync skips the change. fieldPath is a merged-index path, the // same space as FieldChange.originalPath. -func parentIsVariableReference(preResolved dyn.Value, fieldPath string) bool { +func parentIsVariableReference(preResolved structvar.View, fieldPath string) bool { node, err := structpath.ParsePattern(fieldPath) if err != nil { return false @@ -587,7 +550,7 @@ func parentIsVariableReference(preResolved dyn.Value, fieldPath string) bool { return false } s, ok := v.AsString() - return ok && dynvar.ContainsVariableReference(s) + return ok && structvar.ContainsVariableReference(s) } // sequenceSiblings returns the sibling elements of the parent sequence when @@ -595,7 +558,7 @@ func parentIsVariableReference(preResolved dyn.Value, fieldPath string) bool { // last component must be an index ([*] or [N]) and the parent must resolve // to a sequence in the pre-resolved config. Returns false for non-sequence // Adds (e.g., new map fields). -func sequenceSiblings(preResolved dyn.Value, fieldPath string) ([]dyn.Value, bool) { +func sequenceSiblings(preResolved structvar.View, fieldPath string) ([]structvar.View, bool) { node, err := structpath.ParsePattern(fieldPath) if err != nil { return nil, false @@ -604,17 +567,39 @@ func sequenceSiblings(preResolved dyn.Value, fieldPath string) ([]dyn.Value, boo if !hasIndex && !node.BracketStar() { return nil, false } - p, err := dyn.NewPathFromString(node.Parent().String()) + p, err := structpath.ParsePath(node.Parent().String()) if err != nil { return nil, false } - parentValue, err := dyn.GetByPath(preResolved, p) - if err != nil { + parentValue := preResolved.Lookup(p) + if parentValue.Kind() != structvar.KindSequence { return nil, false } - seq, ok := parentValue.AsSequence() - if !ok { - return nil, false + var seq []structvar.View + for _, elem := range parentValue.Sequence() { + seq = append(seq, elem) } return seq, true } + +// equalValues reports whether two configuration values are equal, treating integers of +// different Go types as equal: variables decoded from YAML hold int, while typed +// fields of the remote resource hold int64. +func equalValues(a, b any) bool { + ai, aok := asInt64(a) + bi, bok := asInt64(b) + if aok || bok { + return aok && bok && ai == bi + } + return a == b +} + +func asInt64(v any) (int64, bool) { + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int(), true + default: + return 0, false + } +} diff --git a/bundle/configsync/variables_test.go b/bundle/configsync/variables_test.go index 15f11d5eb3c..b82664aa9c4 100644 --- a/bundle/configsync/variables_test.go +++ b/bundle/configsync/variables_test.go @@ -1,22 +1,64 @@ package configsync import ( + "fmt" + "strings" "testing" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func loadTestConfig(t *testing.T, yaml string) *config.Root { + t.Helper() + root, diags := config.LoadFromBytes("test.yml", []byte(yaml)) + require.NoError(t, diags.Error()) + return root +} + +// scalarView returns a view of the string s. +func scalarView(t *testing.T, s string) structvar.View { + t.Helper() + root := loadTestConfig(t, fmt.Sprintf("bundle:\n name: %q\n", s)) + return root.View().Lookup(structpath.MustParsePath("bundle.name")) +} + +// tasksView returns views of job tasks, each given as the YAML of its fields in flow style. +func tasksView(t *testing.T, tasks ...string) []structvar.View { + t.Helper() + var sb strings.Builder + sb.WriteString("resources:\n jobs:\n j:\n tasks:\n") + for _, task := range tasks { + fmt.Fprintf(&sb, " - {%s}\n", task) + } + root := loadTestConfig(t, sb.String()) + var out []structvar.View + for _, elem := range root.View().Lookup(structpath.MustParsePath("resources.jobs.j.tasks")).Sequence() { + out = append(out, elem) + } + return out +} + +// variablesConfig returns a resolved configuration with the given variable values. +func variablesConfig(t *testing.T, values map[string]any) resolvedConfig { + t.Helper() + var sb strings.Builder + sb.WriteString("variables:\n") + for name, value := range values { + fmt.Fprintf(&sb, " %s:\n value: %#v\n", name, value) + } + return resolvedConfig{view: loadTestConfig(t, sb.String()).View(), overrides: map[string]any{}} +} + // TestRestoreOriginalRefs_HardcodedFieldNotRewritten fences the Replace safety // invariant: a hardcoded leaf must never be rewritten to a variable reference // just because the remote value coincidentally matches a variable elsewhere. func TestRestoreOriginalRefs_HardcodedFieldNotRewritten(t *testing.T) { - preResolved := dyn.V("us-east-1") - resolved := dyn.V(map[string]dyn.Value{ - "variables": dyn.V(map[string]dyn.Value{ - "region": dyn.V(map[string]dyn.Value{"value": dyn.V("main")}), - }), - }) + preResolved := scalarView(t, "us-east-1") + resolved := variablesConfig(t, map[string]any{"region": "main"}) // Even though "main" matches ${var.region}, restoreOriginalRefs must NOT // rewrite it — the original was hardcoded. result := restoreOriginalRefs("main", preResolved, resolved, &RestoreStats{}) @@ -29,17 +71,8 @@ func TestRestoreOriginalRefs_HardcodedFieldNotRewritten(t *testing.T) { func TestRestoreFromSiblings_ValueMatchesVariableButDifferentPath(t *testing.T) { // Sibling uses ${var.retry_count}=5 at .max_retries. New element has // .min_retry_interval=5 — coincidental match at a DIFFERENT relative path. - siblings := []dyn.Value{ - dyn.V(map[string]dyn.Value{ - "task_key": dyn.V("main"), - "max_retries": dyn.V("${var.retry_count}"), - }), - } - resolved := dyn.V(map[string]dyn.Value{ - "variables": dyn.V(map[string]dyn.Value{ - "retry_count": dyn.V(map[string]dyn.Value{"value": dyn.V(int64(5))}), - }), - }) + siblings := tasksView(t, `task_key: main, max_retries: "${var.retry_count}"`) + resolved := variablesConfig(t, map[string]any{"retry_count": 5}) value := map[string]any{ "task_key": "other", "min_retry_interval": int64(5), @@ -52,33 +85,32 @@ func TestRestoreFromSiblings_ValueMatchesVariableButDifferentPath(t *testing.T) // TestRestoreFromSiblings_AmbiguousAcrossSiblings fences the multi-variable // same-value rule: when two siblings use different variables at the same // relative path that both resolve to the same value, restoration is skipped. +func TestRestoreFromSiblings_IntVariableMatchesInt64Field(t *testing.T) { + // The variable decodes from YAML as int; the remote field is a typed int64. + siblings := tasksView(t, `task_key: main, max_retries: "${var.retry_count}"`) + resolved := variablesConfig(t, map[string]any{"retry_count": 5}) + value := map[string]any{"task_key": "other", "max_retries": int64(5)} + result := restoreFromSiblings(value, siblings, resolved, &RestoreStats{}).(map[string]any) + assert.Equal(t, "${var.retry_count}", result["max_retries"]) +} + func TestRestoreFromSiblings_AmbiguousAcrossSiblings(t *testing.T) { - siblings := []dyn.Value{ - dyn.V(map[string]dyn.Value{"default": dyn.V("${var.landing_schema}")}), - dyn.V(map[string]dyn.Value{"default": dyn.V("${var.curated_schema}")}), - } - resolved := dyn.V(map[string]dyn.Value{ - "variables": dyn.V(map[string]dyn.Value{ - "landing_schema": dyn.V(map[string]dyn.Value{"value": dyn.V("raw_data")}), - "curated_schema": dyn.V(map[string]dyn.Value{"value": dyn.V("raw_data")}), - }), - }) - value := map[string]any{"default": "raw_data"} + siblings := tasksView(t, `task_key: "${var.landing_schema}"`, `task_key: "${var.curated_schema}"`) + resolved := variablesConfig(t, map[string]any{"landing_schema": "raw_data", "curated_schema": "raw_data"}) + value := map[string]any{"task_key": "raw_data"} result := restoreFromSiblings(value, siblings, resolved, &RestoreStats{}).(map[string]any) - assert.Equal(t, "raw_data", result["default"]) + assert.Equal(t, "raw_data", result["task_key"]) } // TestRestoreCompoundInterpolation covers the template alignment algorithm. // End-to-end coverage (pure ref match, sibling match, non-sequence skip, etc.) // lives in acceptance/bundle/config-remote-sync/resolve_variables. func TestRestoreCompoundInterpolation(t *testing.T) { - resolved := dyn.V(map[string]dyn.Value{ - "variables": dyn.V(map[string]dyn.Value{ - "host": dyn.V(map[string]dyn.Value{"value": dyn.V("dev-sql.example.com")}), - "port": dyn.V(map[string]dyn.Value{"value": dyn.V("1433")}), - "db": dyn.V(map[string]dyn.Value{"value": dyn.V("analytics_dev")}), - "acct": dyn.V(map[string]dyn.Value{"value": dyn.V("acct")}), - }), + resolved := variablesConfig(t, map[string]any{ + "host": "dev-sql.example.com", + "port": "1433", + "db": "analytics_dev", + "acct": "acct", }) tests := []struct { @@ -108,7 +140,7 @@ func TestRestoreCompoundInterpolation(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := restoreOriginalRefs(tt.remote, dyn.V(tt.template), resolved, &RestoreStats{}) + result := restoreOriginalRefs(tt.remote, scalarView(t, tt.template), resolved, &RestoreStats{}) assert.Equal(t, tt.want, result) }) } diff --git a/bundle/deploy/metadata/compute.go b/bundle/deploy/metadata/compute.go index 1fa4c3bf2d9..a82d4ae1b50 100644 --- a/bundle/deploy/metadata/compute.go +++ b/bundle/deploy/metadata/compute.go @@ -10,6 +10,7 @@ import ( "github.com/databricks/cli/bundle/metadata" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/structs/structpath" ) type compute struct{} @@ -46,7 +47,7 @@ func (m *compute) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics for name, job := range b.Config.Resources.Jobs { // Compute config file path the job is defined in, relative to the bundle // root - l := b.Config.GetLocation("resources.jobs." + name) + l := b.Config.GetLocationOfExact(structpath.NewPath(nil, "resources", "jobs", name)) if l.File == "" { // Skip resources that exist only in the deployment state: statemgmt.Load, // which runs before this mutator, injects them into the config without a @@ -73,7 +74,7 @@ func (m *compute) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics for name, pipeline := range b.Config.Resources.Pipelines { // Compute config file path the pipeline is defined in, relative to the bundle // root - l := b.Config.GetLocation("resources.pipelines." + name) + l := b.Config.GetLocationOfExact(structpath.NewPath(nil, "resources", "pipelines", name)) if l.File == "" { // Skip resources that exist only in the deployment state: statemgmt.Load, // which runs before this mutator, injects them into the config without a @@ -98,7 +99,7 @@ func (m *compute) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics for name, dashboard := range b.Config.Resources.Dashboards { // Compute config file path the dashboard is defined in, relative to the bundle // root - l := b.Config.GetLocation("resources.dashboards." + name) + l := b.Config.GetLocationOfExact(structpath.NewPath(nil, "resources", "dashboards", name)) if l.File == "" { // Skip resources that exist only in the deployment state: statemgmt.Load, // which runs before this mutator, injects them into the config without a diff --git a/bundle/direct/bundle_plan.go b/bundle/direct/bundle_plan.go index abf10376039..200a0455e38 100644 --- a/bundle/direct/bundle_plan.go +++ b/bundle/direct/bundle_plan.go @@ -11,12 +11,11 @@ import ( "strings" "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/walk" "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct/dresources" "github.com/databricks/cli/bundle/direct/dstate" "github.com/databricks/cli/bundle/terraform_dabs_map" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" "github.com/databricks/cli/libs/retry" @@ -885,7 +884,7 @@ func isEmpty(rv reflect.Value) bool { // Empty slices and maps cannot be represented in proto and because of that they cannot be represented // by SDK's JSON encoder. However, they can be provided by users in the config and can be represented in - // Bundle struct (currently libs/structs and libs/dyn use ForceSendFields for maps and slices, unlike SDK). + // Bundle struct (currently libs/structs uses ForceSendFields for maps and slices, unlike SDK). // Thus we get permanent drift because we see that new config is [] but in the state it is omitted. if rv.Kind() == reflect.Slice { @@ -1086,7 +1085,7 @@ func (b *DeploymentBundle) resolveReferences(ctx context.Context, resourceKey st var resolved bool for fieldPathStr, refString := range sv.Refs { - refs, ok := dynvar.NewRef(dyn.V(refString)) + refs, ok := structvar.NewRef(refString) if !ok { logdiag.LogError(ctx, fmt.Errorf("%s: cannot parse %q", errorPrefix, refString)) return false @@ -1160,36 +1159,17 @@ func (b *DeploymentBundle) makePlan(ctx context.Context, configRoot *config.Root existingKeys := maps.Clone(db.State) - patterns := []dyn.Pattern{ - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey(), dyn.Key("permissions")), - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey(), dyn.Key("grants")), - } - - // Walk? if configRoot != nil { - for _, pat := range patterns { - _, err := dyn.MapByPattern( - configRoot.Value(), - pat, - func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - s := p.String() - resourceType := config.GetResourceTypeFromKey(s) - if resourceType == "" { - return v, fmt.Errorf("cannot parse resource key: %q", s) - } - _, ok := dresources.SupportedResources[resourceType] - if !ok { - return v, fmt.Errorf("unsupported resource type: %s", resourceType) - } - - nodes = append(nodes, s) - return dyn.InvalidValue, nil - }, - ) - if err != nil { - return nil, err + for _, s := range configRoot.ResourceNodes() { + resourceType := config.GetResourceTypeFromKey(s) + if resourceType == "" { + return nil, fmt.Errorf("cannot parse resource key: %q", s) + } + _, ok := dresources.SupportedResources[resourceType] + if !ok { + return nil, fmt.Errorf("unsupported resource type: %s", resourceType) } + nodes = append(nodes, s) } } @@ -1244,7 +1224,7 @@ func (b *DeploymentBundle) makePlan(ctx context.Context, configRoot *config.Root // This means input and state must be compatible: input can have more fields, but existing fields should not be moved // This means one cannot refer to fields not present in state (e.g. ${resources.jobs.foo.permissions}) - refs, err := extractReferences(configRoot.Value(), node, adapter.StateType()) + refs, err := extractReferences(configRoot, node, adapter.StateType()) if err != nil { return nil, fmt.Errorf("failed to read references from config for %s: %w", node, err) } @@ -1253,13 +1233,13 @@ func (b *DeploymentBundle) makePlan(ctx context.Context, configRoot *config.Root var dependsOn []deployplan.DependsOnEntry for _, reference := range refs { - ref, ok := dynvar.NewRef(dyn.V(reference)) + ref, ok := structvar.NewRef(reference) if !ok { continue } for _, targetPath := range ref.References() { - targetPathParsed, err := dyn.NewPathFromString(targetPath) + targetPathParsed, err := structpath.ParsePath(targetPath) if err != nil { return nil, fmt.Errorf("parsing %q: %w", targetPath, err) } @@ -1348,72 +1328,67 @@ func (b *DeploymentBundle) makePlan(ctx context.Context, configRoot *config.Root // keeping only those whose field path exists in stateType (references in input-only or // bundle:"readonly" fields, such as volumes' computed volume_path, are skipped). // Returns a map from structpath string (field path within the resource) to template string. -func ExtractReferences(root dyn.Value, node string, stateType reflect.Type) (map[string]string, error) { +func ExtractReferences(root *config.Root, node string, stateType reflect.Type) (map[string]string, error) { return extractReferences(root, node, stateType) } -func extractReferences(root dyn.Value, node string, stateType reflect.Type) (map[string]string, error) { +func extractReferences(root *config.Root, node string, stateType reflect.Type) (map[string]string, error) { nodeType := config.GetResourceTypeFromKey(node) refs := make(map[string]string) - path, err := dyn.NewPathFromString(node) + path, err := structpath.ParsePath(node) if err != nil { return nil, fmt.Errorf("internal error: bad node key: %q: %w", node, err) } - val, err := dyn.GetByPath(root, path) - if err != nil { - return nil, err + if _, ok := root.Resources.GetResource(path.KeyAt(1), path.KeyAt(2)); !ok { + return nil, fmt.Errorf("no value at %s", node) } - err = dyn.WalkReadOnly(val, func(p dyn.Path, v dyn.Value) error { - fullPath := append(path, p...) - targetType := config.GetResourceTypeFromKey(fullPath.String()) - if targetType != nodeType { - // Make sure these are associated with different nodes: - // resources.jobs.foo... - // resources.jobs.foo.permissions... - // resources.jobs.foo.grants... - return nil - } - ref, ok := dynvar.NewRef(v) - if !ok { - return nil - } - // ValidatePath and the refs keys both operate on structpath (keys are - // re-parsed and applied to the typed state in structvar.ResolveRef). - // structpath's bracket notation (['key.with.dots']) also round-trips - // keys with dots, which dyn.Path.String()'s dot notation cannot. - fieldPath := dynPathToStructPath(p) - - // References resolve against the state type, not the input config (see PlanResources - // and dresources.TestInputSubset). A field in input but not in state — e.g. a - // bundle:"readonly" field like volumes' volume_path — is dropped before deploy, so a - // reference it carries cannot resolve into state and is not a dependency here. Such - // references are still resolved earlier during initialize. - if structaccess.ValidatePath(stateType, fieldPath) == nil { - // Store the original string that contains references, not individual references. - refs[fieldPath.String()] = ref.Str - } - return nil + // The walk covers the whole resource: keep the strings under node. + walk.ResourceStringsOf(root, path.KeyAt(1), path.KeyAt(2), walk.WithReferences, func(n *walk.Node, s string) (string, bool) { + addReference(refs, path, nodeType, n.Path(), s, stateType) + return "", false }) - if err != nil { - return nil, fmt.Errorf("parsing refs: %w", err) + for p, ref := range root.ReferencesUnder(path) { + addReference(refs, path, nodeType, p, ref, stateType) } return refs, nil } -// dynPathToStructPath converts a dyn.Path to a structpath.PathNode. -func dynPathToStructPath(p dyn.Path) *structpath.PathNode { - var node *structpath.PathNode - for _, c := range p { - if key := c.Key(); key != "" { - node = structpath.NewStringKey(node, key) - } else { - node = structpath.NewIndex(node, c.Index()) - } +// addReference records in refs the string s with references at fullPath, keyed by its +// path relative to the node at path, if it belongs to that node. +func addReference(refs map[string]string, path *structpath.PathNode, nodeType string, fullPath *structpath.PathNode, s string, stateType reflect.Type) { + if !fullPath.HasPrefix(path) { + return + } + targetType := config.GetResourceTypeFromKey(fullPath.String()) + if targetType != nodeType { + // Make sure these are associated with different nodes: + // resources.jobs.foo... + // resources.jobs.foo.permissions... + // resources.jobs.foo.grants... + return + } + ref, ok := structvar.NewRef(s) + if !ok { + return + } + // ValidatePath and the refs keys both operate on structpath (keys are + // re-parsed and applied to the typed state in structvar.ResolveRef). + // structpath's bracket notation (['key.with.dots']) also round-trips + // keys with dots. + fieldPath := fullPath.SkipPrefix(path.Len()) + + // References resolve against the state type, not the input config (see PlanResources + // and dresources.TestInputSubset). A field in input but not in state — e.g. a + // bundle:"readonly" field like volumes' volume_path — is dropped before deploy, so a + // reference it carries cannot resolve into state and is not a dependency here. Such + // references are still resolved earlier during initialize. + if structaccess.ValidatePath(stateType, fieldPath) == nil { + // Store the original string that contains references, not individual references. + refs[fieldPath.String()] = ref.Str } - return node } func (b *DeploymentBundle) getAdapterForKey(resourceKey string) (*dresources.Adapter, error) { diff --git a/bundle/direct/bundle_plan_test.go b/bundle/direct/bundle_plan_test.go index 2d051dc21b9..59d8f37093b 100644 --- a/bundle/direct/bundle_plan_test.go +++ b/bundle/direct/bundle_plan_test.go @@ -1,15 +1,13 @@ package direct import ( - "bytes" "slices" "testing" + "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct/dresources" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlloader" "github.com/databricks/cli/libs/structs/structdiff" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/structs/structvar" @@ -20,35 +18,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestDynPathToStructPath(t *testing.T) { - tests := []struct { - path dyn.Path - expected string - }{ - { - path: dyn.NewPath(dyn.Key("foo"), dyn.Key("bar")), - expected: "foo.bar", - }, - { - path: dyn.NewPath(dyn.Key("foo"), dyn.Index(1), dyn.Key("bar")), - expected: "foo[1].bar", - }, - { - path: dyn.NewPath(dyn.Key("configuration"), dyn.Key("europris.swipe.egress_streaming_schema")), - expected: "configuration['europris.swipe.egress_streaming_schema']", - }, - { - path: dyn.NewPath(dyn.Key("tags"), dyn.Key("it's.here")), - expected: "tags['it''s.here']", - }, - } - - for _, tc := range tests { - node := dynPathToStructPath(tc.path) - assert.Equal(t, tc.expected, node.String()) - } -} - // extractReferences gates references on the state type: a reference in an input-only field // (e.g. a bundle:"readonly" field like volumes' volume_path) must not become a dependency, // while references in state fields (e.g. comment) are still extracted. @@ -69,8 +38,8 @@ resources: comment: "${resources.schemas.kept.name}" volume_path: "/Volumes/main/${resources.schemas.dropped.name}/myvol" ` - root, err := yamlloader.LoadYAML("test", bytes.NewBufferString(yml)) - require.NoError(t, err) + root, diags := config.LoadFromBytes("test", []byte(yml)) + require.NoError(t, diags.Error()) refs, err := extractReferences(root, "resources.volumes.v", stateType) require.NoError(t, err) diff --git a/bundle/direct/dresources/adapter.go b/bundle/direct/dresources/adapter.go index 80ece24b2be..3f2da957804 100644 --- a/bundle/direct/dresources/adapter.go +++ b/bundle/direct/dresources/adapter.go @@ -512,7 +512,7 @@ func (a *Adapter) FieldIsStableOutput(path *structpath.PathNode) bool { // references needed to complete it. Resources without PrepareInputConfig pass their config through. func (a *Adapter) PrepareInputConfig(inputConfig any, resourceKey string) (*structvar.StructVar, error) { if a.prepareInputConfig == nil { - return &structvar.StructVar{Value: inputConfig, Refs: nil}, nil + return &structvar.StructVar{Value: inputConfig, Refs: nil, Locations: nil}, nil } outs, err := a.prepareInputConfig.Call(inputConfig, resourceKey) diff --git a/bundle/direct/dresources/cluster.go b/bundle/direct/dresources/cluster.go index 82167080c4d..bc3467786d2 100644 --- a/bundle/direct/dresources/cluster.go +++ b/bundle/direct/dresources/cluster.go @@ -528,7 +528,7 @@ func makeEditCluster(id string, config *compute.ClusterSpec) compute.EditCluster } // librariesPath is the ClusterState path of the libraries slice, used to detect library changes. -var librariesPath = structpath.MustParsePath("libraries") +var librariesPath = structpath.NewPath(nil, "libraries") // KeyedSlices compares libraries by identity rather than index so reordering (or the // read-order the status API returns) does not produce phantom diffs. diff --git a/bundle/direct/dresources/grants.go b/bundle/direct/dresources/grants.go index 3aa48c58279..3fac2e0800b 100644 --- a/bundle/direct/dresources/grants.go +++ b/bundle/direct/dresources/grants.go @@ -75,6 +75,7 @@ func (r *ResourceGrants) PrepareInputConfig(inputConfig *[]catalog.PrivilegeAssi Refs: map[string]string{ "full_name": "${" + baseNode + ".id}", }, + Locations: nil, }, nil } diff --git a/bundle/direct/dresources/model_serving_endpoint.go b/bundle/direct/dresources/model_serving_endpoint.go index f9c0a2314ac..b763a08fd05 100644 --- a/bundle/direct/dresources/model_serving_endpoint.go +++ b/bundle/direct/dresources/model_serving_endpoint.go @@ -15,11 +15,11 @@ import ( // Precalculated paths for HasChange checks var ( - pathTags = structpath.MustParsePath("tags") - pathAiGateway = structpath.MustParsePath("ai_gateway") - pathConfig = structpath.MustParsePath("config") - pathEmailNotifications = structpath.MustParsePath("email_notifications") - pathTelemetryConfig = structpath.MustParsePath("telemetry_config") + pathTags = structpath.NewPath(nil, "tags") + pathAiGateway = structpath.NewPath(nil, "ai_gateway") + pathConfig = structpath.NewPath(nil, "config") + pathEmailNotifications = structpath.NewPath(nil, "email_notifications") + pathTelemetryConfig = structpath.NewPath(nil, "telemetry_config") ) type ResourceModelServingEndpoint struct { diff --git a/bundle/direct/dresources/permissions.go b/bundle/direct/dresources/permissions.go index c2aebb4d0ac..b09e9cfe2ff 100644 --- a/bundle/direct/dresources/permissions.go +++ b/bundle/direct/dresources/permissions.go @@ -116,6 +116,7 @@ func (r *ResourcePermissions) PrepareInputConfig(inputConfig any, resourceKey st Refs: map[string]string{ "object_id": r.objectType + "${" + baseNode + "." + r.idField + "}", }, + Locations: nil, }, nil } diff --git a/bundle/direct/dresources/secret_scope_acls.go b/bundle/direct/dresources/secret_scope_acls.go index 1de301bde2c..61269ce4097 100644 --- a/bundle/direct/dresources/secret_scope_acls.go +++ b/bundle/direct/dresources/secret_scope_acls.go @@ -60,6 +60,7 @@ func (*ResourceSecretScopeAcls) PrepareInputConfig(inputConfig *[]resources.Secr Refs: map[string]string{ "scope_name": "${" + baseNode + ".name}", }, + Locations: nil, }, nil } diff --git a/bundle/direct/dresources/serialize_test.go b/bundle/direct/dresources/serialize_test.go index 76078460193..e1bd30579c6 100644 --- a/bundle/direct/dresources/serialize_test.go +++ b/bundle/direct/dresources/serialize_test.go @@ -130,7 +130,7 @@ func TestRoundtripAllFieldsRemoteType(t *testing.T) { // TestRoundtripAllFieldsInputConfigType verifies InputConfigType, the typed // bundle config a resource is loaded into, survives a JSON round-trip with every -// field populated. Bundle config is normally read and written through libs/dyn, +// field populated. Bundle config is normally read and written through libs/structs/structvar, // which walks the struct itself and never calls these marshalers, so this is a // latent trap rather than live corruption. It is guarded anyway because it is the // same trap as StateType and RemoteType: a resource that embeds a member with its diff --git a/bundle/direct/dresources/vector_search_endpoint.go b/bundle/direct/dresources/vector_search_endpoint.go index 12470872b62..3775f793b60 100644 --- a/bundle/direct/dresources/vector_search_endpoint.go +++ b/bundle/direct/dresources/vector_search_endpoint.go @@ -13,8 +13,8 @@ import ( ) var ( - pathBudgetPolicyId = structpath.MustParsePath("budget_policy_id") - pathTargetQps = structpath.MustParsePath("target_qps") + pathBudgetPolicyId = structpath.NewPath(nil, "budget_policy_id") + pathTargetQps = structpath.NewPath(nil, "target_qps") ) // VectorSearchEndpointRemote is remote state for a vector search endpoint. It embeds API response diff --git a/bundle/direct/resolve_config.go b/bundle/direct/resolve_config.go index 8ce28d710e1..3d8727faf2c 100644 --- a/bundle/direct/resolve_config.go +++ b/bundle/direct/resolve_config.go @@ -1,14 +1,18 @@ package direct import ( + "fmt" + "maps" + "slices" + "github.com/databricks/cli/bundle/config" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/dynvar" - "github.com/databricks/cli/libs/dyn/jsonloader" + "github.com/databricks/cli/bundle/config/walk" + "github.com/databricks/cli/libs/structs/structaccess" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) -var resourcesPrefix = dyn.MustPathFromString("resources") +var resourcesPrefix = structpath.NewPath(nil, "resources") // ResolveConfigAgainstState resolves ${resources.*} references within the resource at // target so its runner ("bundle run") sees concrete values rather than references. For a @@ -24,48 +28,102 @@ var resourcesPrefix = dyn.MustPathFromString("resources") // // The state DB holds fields like the immutable snapshot's full_path that never reach the // config, so it must be open. -func (b *DeploymentBundle) ResolveConfigAgainstState(cfg *config.Root, target dyn.Path) error { - return cfg.Mutate(func(root dyn.Value) (dyn.Value, error) { - // Fall back to the fully-normalized config so references to fields that are - // implied (not explicitly set) still resolve. - normalized, _ := convert.Normalize(cfg, root, convert.IncludeMissingFields) +func (b *DeploymentBundle) ResolveConfigAgainstState(cfg *config.Root, target *structpath.PathNode) error { + typ, name := target.KeyAt(1), target.KeyAt(2) + if _, ok := cfg.Resources.GetResource(typ, name); !ok { + return fmt.Errorf("resource %s not found in configuration", target) + } + view := cfg.View() + + // Collect every reference string first: resolving them in one call shares the + // lookups, and writing back changes the view. + templates := map[string]structvar.Template{} + paths := map[string]*structpath.PathNode{} + + lookup := func(path *structpath.PathNode) (structvar.View, error) { + if !path.HasPrefix(resourcesPrefix) { + return structvar.View{}, structvar.ErrSkipResolution + } + if v, ok := b.lookupStateField(path); ok { + return v, nil + } + // Fall back to the config, including fields that are implied (not explicitly set). + if v := view.Lookup(path); v.IsValid() { + return v, nil + } + got, err := structaccess.Get(cfg, path) + if err != nil { + return structvar.View{}, structvar.ErrSkipResolution + } + return structvar.NewView(&got, nil, nil), nil + } - return dyn.MapByPath(root, target, func(_ dyn.Path, resource dyn.Value) (dyn.Value, error) { - return dynvar.Resolve(resource, func(path dyn.Path) (dyn.Value, error) { - if !path.HasPrefix(resourcesPrefix) { - return dyn.InvalidValue, dynvar.ErrSkipResolution - } - if v, ok := b.lookupStateField(path); ok { - return v, nil - } - v, err := dyn.GetByPath(normalized, path) - if err != nil { - return dyn.InvalidValue, dynvar.ErrSkipResolution - } - return v, nil - }) - }) + walk.ResourceStringsOf(cfg, typ, name, walk.WithReferences, func(n *walk.Node, s string) (string, bool) { + addTemplate(templates, paths, target, n.Path(), s) + return "", false }) + for p, ref := range cfg.ReferencesUnder(target) { + addTemplate(templates, paths, target, p, ref) + } + + resolved, err := structvar.Resolve(templates, lookup) + if err != nil { + return err + } + + // Snapshot all values before writing: a resolved view may point into the configuration. + values := map[string]any{} + for key, v := range resolved { + values[key] = v.AsAny() + } + for _, key := range slices.Sorted(maps.Keys(values)) { + value := values[key] + // Decode converts like loading did, e.g. a string id into an int job_id. + diags, err := cfg.Decode(paths[key], structvar.NewView(&value, nil, nil)) + if err != nil { + return err + } + if diags.HasError() { + return diags.Error() + } + } + return nil +} + +// addTemplate adds the string s at path to templates if it has references, keyed by its +// path relative to target. +func addTemplate(templates map[string]structvar.Template, paths map[string]*structpath.PathNode, target, path *structpath.PathNode, s string) { + if _, ok := structvar.NewRef(s); !ok { + return + } + key := path.SkipPrefix(target.Len()).String() + templates[key] = structvar.Template{Value: s} + paths[key] = path } // lookupStateField returns the value at resources... from the // resource's persisted state, if that resource is in state and holds the field. -func (b *DeploymentBundle) lookupStateField(path dyn.Path) (dyn.Value, bool) { - if len(path) < 4 || path[0].Key() != "resources" { - return dyn.InvalidValue, false +func (b *DeploymentBundle) lookupStateField(path *structpath.PathNode) (structvar.View, bool) { + if path.Len() < 4 || path.KeyAt(0) != "resources" { + return structvar.View{}, false } - resourceKey := "resources." + path[1].Key() + "." + path[2].Key() + resourceKey := path.Prefix(3).String() entry, ok := b.StateDB.GetResourceEntry(resourceKey) if !ok || len(entry.State) == 0 { - return dyn.InvalidValue, false + return structvar.View{}, false } - stateVal, err := jsonloader.LoadJSON(entry.State, resourceKey) + // ParseJSON keeps large ids exact. + node, err := structvar.ParseJSON(resourceKey, entry.State) if err != nil { - return dyn.InvalidValue, false + return structvar.View{}, false + } + var state any + if _, _, err := structvar.DecodeYAMLNode(resourceKey, node, &state, nil); err != nil { + return structvar.View{}, false } - fieldVal, err := dyn.GetByPath(stateVal, path[3:]) - if err != nil || !fieldVal.IsValid() { - return dyn.InvalidValue, false + v := structvar.NewView(&state, nil, nil).Lookup(path.SkipPrefix(3)) + if !v.IsValid() { + return structvar.View{}, false } - return fieldVal, true + return v, true } diff --git a/bundle/direct/unescape.go b/bundle/direct/unescape.go index 31f9925e098..40ec5d02bed 100644 --- a/bundle/direct/unescape.go +++ b/bundle/direct/unescape.go @@ -5,9 +5,9 @@ import ( "reflect" "strings" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/structs/structaccess" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" "github.com/databricks/cli/libs/structs/structwalk" ) @@ -24,7 +24,7 @@ func unescapeRefs(state any) error { return } paths = append(paths, path) - values = append(values, dynvar.Unescape(s)) + values = append(values, structvar.Unescape(s)) }) if err != nil { return err diff --git a/bundle/generate/alert.go b/bundle/generate/alert.go index eba68126dd8..d3765205d17 100644 --- a/bundle/generate/alert.go +++ b/bundle/generate/alert.go @@ -1,19 +1,16 @@ package generate import ( - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/databricks-sdk-go/service/sql" ) -func ConvertAlertToValue(alert *sql.AlertV2, filePath string) (dyn.Value, error) { +func ConvertAlertToValue(alert *sql.AlertV2, filePath string) (structyaml.Map, error) { // The majority of fields of the alert struct are present in .dbalert.json file. // We copy the relevant fields manually. - dv := map[string]dyn.Value{ - "display_name": dyn.NewValue(alert.DisplayName, []diag.Location{{Line: 1}}), - "warehouse_id": dyn.NewValue(alert.WarehouseId, []diag.Location{{Line: 2}}), - "file_path": dyn.NewValue(filePath, []diag.Location{{Line: 3}}), - } - - return dyn.V(dv), nil + return structyaml.M( + "display_name", alert.DisplayName, + "warehouse_id", alert.WarehouseId, + "file_path", filePath, + ), nil } diff --git a/bundle/generate/app.go b/bundle/generate/app.go index 0714fc9b324..a0f40a5a4ef 100644 --- a/bundle/generate/app.go +++ b/bundle/generate/app.go @@ -1,54 +1,47 @@ package generate import ( - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/databricks-sdk-go/service/apps" ) -func ConvertAppToValue(app *apps.App, sourceCodePath string) (dyn.Value, error) { - ar, err := convert.FromTyped(app.Resources, dyn.NilValue) - if err != nil { - return dyn.NilValue, err - } - +func ConvertAppToValue(app *apps.App, sourceCodePath string) (structyaml.Map, error) { // The majority of fields of the app struct are read-only. // We copy the relevant fields manually. - dv := map[string]dyn.Value{ - "name": dyn.NewValue(app.Name, []diag.Location{{Line: 1}}), - "description": dyn.NewValue(app.Description, []diag.Location{{Line: 2}}), - } + dv := structyaml.M( + "name", app.Name, + "description", app.Description, + ) // For a git-backed app, emit git_repository + git_source instead of a // workspace source_code_path. Otherwise the generated bundle would silently // down-convert the app to workspace source and point source_code_path at a // local directory that has nothing downloaded into it. if app.GitRepository != nil { - dv["git_repository"] = gitRepositoryValue(app.GitRepository) - if gs := gitSourceValue(app); gs.Kind() != dyn.KindNil { - dv["git_source"] = gs + dv.Add("git_repository", gitRepositoryValue(app.GitRepository)) + if gs := gitSourceValue(app); len(gs) > 0 { + dv.Add("git_source", gs) } } else { - dv["source_code_path"] = dyn.NewValue(sourceCodePath, []diag.Location{{Line: 4}}) + dv.Add("source_code_path", sourceCodePath) } - if ar.Kind() != dyn.KindNil { - dv["resources"] = ar.WithLocations([]diag.Location{{Line: 5}}) + if ar := structyaml.Value(app.Resources); ar != nil { + dv.Add("resources", ar) } - return dyn.V(dv), nil + return dv, nil } -func gitRepositoryValue(r *apps.GitRepository) dyn.Value { - m := map[string]dyn.Value{ - "url": dyn.NewValue(r.Url, []diag.Location{{Line: 1}}), - "provider": dyn.NewValue(r.Provider, []diag.Location{{Line: 2}}), - } +func gitRepositoryValue(r *apps.GitRepository) structyaml.Map { + m := structyaml.M( + "url", r.Url, + "provider", r.Provider, + ) if r.AutoDeploy { - m["auto_deploy"] = dyn.NewValue(r.AutoDeploy, []diag.Location{{Line: 3}}) + m.Add("auto_deploy", r.AutoDeploy) } - return dyn.NewValue(m, []diag.Location{{Line: 3}}) + return m } // gitSourceValue returns the reference the app deploys from (branch, tag, or @@ -56,29 +49,26 @@ func gitRepositoryValue(r *apps.GitRepository) dyn.Value { // configured git_source and falls back to the default source of the app's most // recent deployment. System-populated fields (resolved_commit and the nested // git_repository) are intentionally omitted. -func gitSourceValue(app *apps.App) dyn.Value { +func gitSourceValue(app *apps.App) structyaml.Map { src := app.GitSource if src == nil { src = app.DefaultGitSource } if src == nil { - return dyn.NilValue + return nil } - m := map[string]dyn.Value{} + var m structyaml.Map switch { case src.Branch != "": - m["branch"] = dyn.NewValue(src.Branch, []diag.Location{{Line: 1}}) + m.Add("branch", src.Branch) case src.Tag != "": - m["tag"] = dyn.NewValue(src.Tag, []diag.Location{{Line: 1}}) + m.Add("tag", src.Tag) case src.Commit != "": - m["commit"] = dyn.NewValue(src.Commit, []diag.Location{{Line: 1}}) + m.Add("commit", src.Commit) } if src.SourceCodePath != "" { - m["source_code_path"] = dyn.NewValue(src.SourceCodePath, []diag.Location{{Line: 2}}) - } - if len(m) == 0 { - return dyn.NilValue + m.Add("source_code_path", src.SourceCodePath) } - return dyn.NewValue(m, []diag.Location{{Line: 4}}) + return m } diff --git a/bundle/generate/dashboard.go b/bundle/generate/dashboard.go index 6f045a39cb2..a12e8b57f5d 100644 --- a/bundle/generate/dashboard.go +++ b/bundle/generate/dashboard.go @@ -1,19 +1,16 @@ package generate import ( - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/databricks-sdk-go/service/dashboards" ) -func ConvertDashboardToValue(dashboard *dashboards.Dashboard, filePath string) (dyn.Value, error) { +func ConvertDashboardToValue(dashboard *dashboards.Dashboard, filePath string) (structyaml.Map, error) { // The majority of fields of the dashboard struct are read-only. // We copy the relevant fields manually. - dv := map[string]dyn.Value{ - "display_name": dyn.NewValue(dashboard.DisplayName, []diag.Location{{Line: 1}}), - "warehouse_id": dyn.NewValue(dashboard.WarehouseId, []diag.Location{{Line: 2}}), - "file_path": dyn.NewValue(filePath, []diag.Location{{Line: 3}}), - } - - return dyn.V(dv), nil + return structyaml.M( + "display_name", dashboard.DisplayName, + "warehouse_id", dashboard.WarehouseId, + "file_path", filePath, + ), nil } diff --git a/bundle/generate/genie_space.go b/bundle/generate/genie_space.go index 639c65f314b..88cbe8b05d9 100644 --- a/bundle/generate/genie_space.go +++ b/bundle/generate/genie_space.go @@ -1,29 +1,28 @@ package generate import ( - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/databricks-sdk-go/service/dashboards" ) -func ConvertGenieSpaceToValue(genieSpace *dashboards.GenieSpace, filePath string) (dyn.Value, error) { +func ConvertGenieSpaceToValue(genieSpace *dashboards.GenieSpace, filePath string) (structyaml.Map, error) { // Emit only the fields a user authors in a bundle. serialized_space is // written to a separate file and referenced via file_path, and output-only // fields (e.g. space_id, etag) must not appear in the generated config, so // we build the value field by field rather than marshaling the struct. - dv := map[string]dyn.Value{ - "title": dyn.NewValue(genieSpace.Title, []diag.Location{{Line: 1}}), - "warehouse_id": dyn.NewValue(genieSpace.WarehouseId, []diag.Location{{Line: 2}}), - "file_path": dyn.NewValue(filePath, []diag.Location{{Line: 3}}), - } + dv := structyaml.M( + "title", genieSpace.Title, + "warehouse_id", genieSpace.WarehouseId, + "file_path", filePath, + ) if genieSpace.Description != "" { - dv["description"] = dyn.NewValue(genieSpace.Description, []diag.Location{{Line: 4}}) + dv.Add("description", genieSpace.Description) } if genieSpace.ParentPath != "" { - dv["parent_path"] = dyn.NewValue(ensureWorkspacePrefix(genieSpace.ParentPath), []diag.Location{{Line: 5}}) + dv.Add("parent_path", ensureWorkspacePrefix(genieSpace.ParentPath)) } - return dyn.V(dv), nil + return dv, nil } diff --git a/bundle/generate/job.go b/bundle/generate/job.go index 801eee1f5bd..b1784258e1c 100644 --- a/bundle/generate/job.go +++ b/bundle/generate/job.go @@ -1,50 +1,51 @@ package generate import ( - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlsaver" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/databricks-sdk-go/service/jobs" ) var ( - jobOrder = yamlsaver.NewOrder([]string{"name", "job_clusters", "compute", "tasks"}) - taskOrder = yamlsaver.NewOrder([]string{"task_key", "depends_on", "existing_cluster_id", "new_cluster", "job_cluster_key"}) + jobOrder = []string{"name", "job_clusters", "compute", "tasks", "parameters"} + taskOrder = []string{"task_key", "depends_on", "existing_cluster_id", "new_cluster", "job_cluster_key"} ) -func ConvertJobToValue(job *jobs.Job) (dyn.Value, error) { - value := make(map[string]dyn.Value) +func ConvertJobToValue(job *jobs.Job) (structyaml.Map, error) { + // Tasks and parameters are processed separately. + value, err := structyaml.Struct(job.Settings, "format", "new_cluster", "existing_cluster_id", "tasks", "parameters") + if err != nil { + return nil, err + } + if job.Settings.Tasks != nil { - var tasks []dyn.Value + var tasks []any for _, task := range job.Settings.Tasks { - v, err := convertTaskToValue(task, taskOrder) + v, err := convertTaskToValue(task) if err != nil { - return dyn.InvalidValue, err + return nil, err } tasks = append(tasks, v) } - // We're using location lines to define the order of keys in exported YAML. - value["tasks"] = dyn.NewValue(tasks, []diag.Location{{Line: jobOrder.Get("tasks")}}) + value.Add("tasks", tasks) } // We're processing job.Settings.Parameters separately to retain empty default values. if len(job.Settings.Parameters) > 0 { - var params []dyn.Value + var params []any for _, parameter := range job.Settings.Parameters { - p := map[string]dyn.Value{ - "name": dyn.NewValue(parameter.Name, []diag.Location{{Line: 0}}), // We use Line: 0 to ensure that the name goes first. - "default": dyn.NewValue(parameter.Default, []diag.Location{{Line: 1}}), - } - params = append(params, dyn.V(p)) + params = append(params, structyaml.M("name", parameter.Name, "default", parameter.Default)) } - value["parameters"] = dyn.NewValue(params, []diag.Location{{Line: jobOrder.Get("parameters")}}) + value.Add("parameters", params) } - return yamlsaver.ConvertToMapValue(job.Settings, jobOrder, []string{"format", "new_cluster", "existing_cluster_id"}, value) + return value.Order(jobOrder...), nil } -func convertTaskToValue(task jobs.Task, order *yamlsaver.Order) (dyn.Value, error) { - dst := make(map[string]dyn.Value) - return yamlsaver.ConvertToMapValue(task, order, []string{"format"}, dst) +func convertTaskToValue(task jobs.Task) (structyaml.Map, error) { + value, err := structyaml.Struct(task, "format") + if err != nil { + return nil, err + } + return value.Order(taskOrder...), nil } diff --git a/bundle/generate/pipeline.go b/bundle/generate/pipeline.go index 115330ea0f6..beb8bf2e5ad 100644 --- a/bundle/generate/pipeline.go +++ b/bundle/generate/pipeline.go @@ -3,14 +3,13 @@ package generate import ( "strings" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlsaver" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/databricks-sdk-go/service/pipelines" ) -var pipelineOrder = yamlsaver.NewOrder([]string{"name", "clusters", "configuration", "libraries"}) +var pipelineOrder = []string{"name", "clusters", "configuration", "libraries"} -func ConvertPipelineToValue(pipeline *pipelines.PipelineSpec, rootPath, remoteRootPath string) (dyn.Value, error) { +func ConvertPipelineToValue(pipeline *pipelines.PipelineSpec, rootPath, remoteRootPath string) (structyaml.Map, error) { if pipeline.RootPath != "" { pipeline.RootPath = rootPath } @@ -24,11 +23,14 @@ func ConvertPipelineToValue(pipeline *pipelines.PipelineSpec, rootPath, remoteRo } } - value := make(map[string]dyn.Value) // We ignore the following fields: // - id: this is a read-only field // - storage: changes to this field are rare because changing the storage recreates pipeline-related resources // - edition: this field is rarely changed // - development: this field is specific to the mode where it's used and does not need to be saved to the bundle configuration. - return yamlsaver.ConvertToMapValue(pipeline, pipelineOrder, []string{"id", "storage", "edition", "development"}, value) + value, err := structyaml.Struct(pipeline, "id", "storage", "edition", "development") + if err != nil { + return nil, err + } + return value.Order(pipelineOrder...), nil } diff --git a/bundle/internal/bundletest/benchmark.go b/bundle/internal/bundletest/benchmark.go index 59647d814fd..2cb4b738652 100644 --- a/bundle/internal/bundletest/benchmark.go +++ b/bundle/internal/bundletest/benchmark.go @@ -9,7 +9,6 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/require" ) @@ -210,32 +209,6 @@ const jobExample = ` } }` -func BundleV(b *testing.B, numJobs int) dyn.Value { - allJobs := map[string]*resources.Job{} - for i := range numJobs { - job := jobs.JobSettings{} - err := json.Unmarshal([]byte(jobExample), &job) - require.NoError(b, err) - - allJobs[strconv.Itoa(i)] = &resources.Job{ - JobSettings: job, - } - } - - myBundle := bundle.Bundle{ - Config: config.Root{ - Resources: config.Resources{ - Jobs: allJobs, - }, - }, - } - - // Apply noop mutator to initialize the bundle value. - bundle.ApplyFuncContext(b.Context(), &myBundle, func(ctx context.Context, b *bundle.Bundle) {}) - - return myBundle.Config.Value() -} - func Bundle(b *testing.B, numJobs int) *bundle.Bundle { allJobs := map[string]*resources.Job{} for i := range numJobs { diff --git a/bundle/internal/bundletest/benchmark_test.go b/bundle/internal/bundletest/benchmark_test.go index 05999daf6fb..df8d3659a4b 100644 --- a/bundle/internal/bundletest/benchmark_test.go +++ b/bundle/internal/bundletest/benchmark_test.go @@ -3,30 +3,18 @@ package bundletest import ( "testing" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" "github.com/stretchr/testify/assert" ) -// This took 40ms to run on 18th June 2025. func BenchmarkWalkReadOnly(b *testing.B) { - input := BundleV(b, 10000) + input := Bundle(b, 10000).Config.View() for b.Loop() { - err := dyn.WalkReadOnly(input, func(p dyn.Path, v dyn.Value) error { + err := structvar.Walk(input, func(p *structpath.PathNode, v structvar.View) error { return nil }) assert.NoError(b, err) } } - -// This took 160ms to run on 18th June 2025. -func BenchmarkWalk(b *testing.B) { - input := BundleV(b, 10000) - - for b.Loop() { - _, err := dyn.Walk(input, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - return v, nil - }) - assert.NoError(b, err) - } -} diff --git a/bundle/internal/bundletest/component_benchmark_test.go b/bundle/internal/bundletest/component_benchmark_test.go new file mode 100644 index 00000000000..4ec348c0933 --- /dev/null +++ b/bundle/internal/bundletest/component_benchmark_test.go @@ -0,0 +1,97 @@ +package bundletest + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "testing" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/mutator" + "github.com/databricks/cli/bundle/config/validate" + "github.com/databricks/cli/bundle/libraries" + "github.com/stretchr/testify/require" +) + +// The benchmarks below run one mutator (or loading) on a bundle with 1000 jobs. They +// only use APIs that also exist in the libs/dyn implementation, so the same file can +// be run there to compare components one by one. + +const componentJobs = 1000 + +func benchmarkMutator(b *testing.B, m bundle.Mutator) { + myBundle := Bundle(b, componentJobs) + myBundle.BundleRootPath = b.TempDir() + myBundle.SyncRootPath = myBundle.BundleRootPath + for b.Loop() { + bundle.Apply(b.Context(), myBundle, m) + } +} + +// BenchmarkComponentNoop measures the cost bundle.Apply adds around every mutator, so +// it can be subtracted from the other results. +func BenchmarkComponentNoop(b *testing.B) { + myBundle := Bundle(b, componentJobs) + for b.Loop() { + bundle.ApplyFuncContext(b.Context(), myBundle, func(context.Context, *bundle.Bundle) {}) + } +} + +func BenchmarkComponentRequired(b *testing.B) { benchmarkMutator(b, validate.Required()) } + +func BenchmarkComponentEnum(b *testing.B) { benchmarkMutator(b, validate.Enum()) } + +func BenchmarkComponentSingleNodeCluster(b *testing.B) { + benchmarkMutator(b, validate.SingleNodeCluster()) +} + +func BenchmarkComponentTFOnlyReferences(b *testing.B) { + benchmarkMutator(b, validate.TFOnlyReferences()) +} + +func BenchmarkComponentTranslatePaths(b *testing.B) { benchmarkMutator(b, mutator.TranslatePaths()) } + +func BenchmarkComponentNormalizePaths(b *testing.B) { benchmarkMutator(b, mutator.NormalizePaths()) } + +func BenchmarkComponentRewriteWorkspacePrefix(b *testing.B) { + benchmarkMutator(b, mutator.RewriteWorkspacePrefix()) +} + +func BenchmarkComponentPrependWorkspacePrefix(b *testing.B) { + benchmarkMutator(b, mutator.PrependWorkspacePrefix()) +} + +func BenchmarkComponentDropEmptyStrings(b *testing.B) { + benchmarkMutator(b, mutator.DropEmptyStrings()) +} + +func BenchmarkComponentResolveVariableReferences(b *testing.B) { + benchmarkMutator(b, mutator.ResolveVariableReferencesOnlyResources("bundle", "workspace", "variables")) +} + +func BenchmarkComponentExpandGlobReferences(b *testing.B) { + benchmarkMutator(b, libraries.ExpandGlobReferences()) +} + +// BenchmarkComponentLoad loads a configuration file with 1000 jobs (JSON is valid YAML). +func BenchmarkComponentLoad(b *testing.B) { + var job map[string]any + require.NoError(b, json.Unmarshal([]byte(jobExample), &job)) + jobs := map[string]any{} + for i := range componentJobs { + jobs[fmt.Sprintf("job_%d", i)] = job + } + raw, err := json.Marshal(map[string]any{ + "bundle": map[string]any{"name": "benchmark"}, + "resources": map[string]any{"jobs": jobs}, + }) + require.NoError(b, err) + require.True(b, strings.HasPrefix(string(raw), "{")) + + for b.Loop() { + _, diags := config.LoadFromBytes("databricks.yml", raw) + require.False(b, diags.HasError(), "%v", diags) + } +} diff --git a/bundle/internal/bundletest/location.go b/bundle/internal/bundletest/location.go index b96cab0907d..7290a8ae2a9 100644 --- a/bundle/internal/bundletest/location.go +++ b/bundle/internal/bundletest/location.go @@ -3,32 +3,17 @@ package bundletest import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) // SetLocation sets the location of all values in the bundle to the given path. // This is useful for testing where we need to associate configuration // with the path it is loaded from. func SetLocation(b *bundle.Bundle, prefix string, locations []diag.Location) { - start := dyn.MustPathFromString(prefix) - err := b.Config.Mutate(func(root dyn.Value) (dyn.Value, error) { - return dyn.Walk(root, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - // If the path has the given prefix, set the location. - if p.HasPrefix(start) { - return v.WithLocations(locations), nil - } - - // The path is not nested under the given prefix. - // If the path is a prefix of the prefix, keep traversing and return the node verbatim. - if start.HasPrefix(p) { - return v, nil - } - - // Return verbatim, but skip traversal. - return v, dyn.ErrSkip - }) - }) - if err != nil { - panic("Mutate() failed: " + err.Error()) + // "." is the root of the configuration. + var path *structpath.PathNode + if prefix != "." { + path = structpath.MustParsePath(prefix) } + b.Config.SetLocations(path, locations) } diff --git a/bundle/internal/bundletest/mutate.go b/bundle/internal/bundletest/mutate.go deleted file mode 100644 index 00adc7d29e0..00000000000 --- a/bundle/internal/bundletest/mutate.go +++ /dev/null @@ -1,17 +0,0 @@ -package bundletest - -import ( - "context" - "testing" - - "github.com/databricks/cli/bundle" - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/require" -) - -func Mutate(t *testing.T, b *bundle.Bundle, f func(v dyn.Value) (dyn.Value, error)) { - bundle.ApplyFuncContext(t.Context(), b, func(ctx context.Context, b *bundle.Bundle) { - err := b.Config.Mutate(f) - require.NoError(t, err) - }) -} diff --git a/bundle/internal/bundletest/mutator_benchmark_test.go b/bundle/internal/bundletest/mutator_benchmark_test.go index 03d63dcd072..5e60f0cfab0 100644 --- a/bundle/internal/bundletest/mutator_benchmark_test.go +++ b/bundle/internal/bundletest/mutator_benchmark_test.go @@ -7,7 +7,8 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config/validate" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" "github.com/stretchr/testify/assert" ) @@ -35,9 +36,9 @@ func benchmarkWalkReadOnlyBaseline(b *testing.B, numJobs int) { myBundle := Bundle(b, numJobs) for b.Loop() { - var paths []dyn.Path + var paths []*structpath.PathNode bundle.ApplyFuncContext(b.Context(), myBundle, func(ctx context.Context, b *bundle.Bundle) { - _ = dyn.WalkReadOnly(b.Config.Value(), func(p dyn.Path, v dyn.Value) error { + _ = structvar.Walk(b.Config.View(), func(p *structpath.PathNode, v structvar.View) error { paths = append(paths, p) return nil }) diff --git a/bundle/internal/schema/annotations.go b/bundle/internal/schema/annotations.go index 548cf5ae95f..894930f5491 100644 --- a/bundle/internal/schema/annotations.go +++ b/bundle/internal/schema/annotations.go @@ -2,15 +2,14 @@ package main import ( "fmt" + "maps" "reflect" "regexp" + "slices" "strings" "github.com/databricks/cli/bundle/internal/annotation" "github.com/databricks/cli/internal/clijson" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/merge" "github.com/databricks/cli/libs/jsonschema" ) @@ -27,12 +26,8 @@ type annotationHandler struct { // Adds annotations to the JSON schema reading from the annotation files. // More details https://json-schema.org/understanding-json-schema/reference/annotations func newAnnotationHandler(extracted, fromFile annotation.File) (*annotationHandler, error) { - merged, err := mergeAnnotationFiles(extracted, fromFile) - if err != nil { - return nil, err - } return &annotationHandler{ - parsedAnnotations: merged, + parsedAnnotations: mergeAnnotationFiles(extracted, fromFile), fileAnnotations: fromFile, missingAnnotations: annotation.File{}, }, nil @@ -79,25 +74,66 @@ func isEmptyDescriptor(d annotation.Descriptor) bool { // mergeAnnotationFiles merges later layers over earlier ones with the same // semantics the on-disk annotation files used to be merged with: maps merge // recursively, scalars take the later value, sequences concatenate. -func mergeAnnotationFiles(files ...annotation.File) (annotation.File, error) { - prev := dyn.NilValue +func mergeAnnotationFiles(files ...annotation.File) annotation.File { + merged := annotation.File{} for _, f := range files { - v, err := convert.FromTyped(f, dyn.NilValue) - if err != nil { - return nil, err + for typeKey, ta := range f { + merged.SetSelf(typeKey, mergeDescriptor(merged[typeKey].Self, ta.Self)) + for name, d := range ta.Fields { + merged.SetField(typeKey, name, mergeDescriptor(merged[typeKey].Fields[name], d)) + } } - prev, err = merge.Merge(prev, v) - if err != nil { - return nil, err + } + return merged +} + +// mergeDescriptor merges the set fields of b over a: maps merge by key, sequences +// concatenate and other values are replaced. +func mergeDescriptor(a, b annotation.Descriptor) annotation.Descriptor { + dst, src := reflect.ValueOf(&a).Elem(), reflect.ValueOf(b) + for i := range dst.NumField() { + f, g := dst.Field(i), src.Field(i) + switch { + case g.IsZero(): + case f.Kind() == reflect.Slice: + f.Set(reflect.AppendSlice(reflect.AppendSlice(reflect.Zero(f.Type()), f), g)) + case f.Kind() == reflect.Interface: + f.Set(reflect.ValueOf(mergeAny(f.Interface(), g.Interface()))) + case f.Kind() == reflect.Map: + m := reflect.MakeMap(f.Type()) + for _, v := range []reflect.Value{f, g} { + for iter := v.MapRange(); iter.Next(); { + m.SetMapIndex(iter.Key(), iter.Value()) + } + } + f.Set(m) + default: + f.Set(g) } } + return a +} - var data annotation.File - err := convert.ToTyped(&data, prev) - if err != nil { - return nil, err +// mergeAny merges b over a like mergeDescriptor merges fields: maps merge by key +// (recursively), sequences concatenate and other values are replaced. +func mergeAny(a, b any) any { + switch bv := b.(type) { + case map[string]any: + av, ok := a.(map[string]any) + if !ok { + return b + } + out := maps.Clone(av) + for k, v := range bv { + out[k] = mergeAny(av[k], v) + } + return out + case []any: + if av, ok := a.([]any); ok { + return slices.Concat(av, bv) + } } - return data, nil + return b } func (d *annotationHandler) addAnnotations(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema { @@ -125,10 +161,7 @@ func (d *annotationHandler) addAnnotations(typ reflect.Type, s jsonschema.Schema // descriptions for fields that have no documentation anywhere. Entries for // fields that no longer exist in the config are dropped with a warning. func (d *annotationHandler) syncWithMissingAnnotations(outputPath string, g *typeGraph) error { - updated, err := mergeAnnotationFiles(d.fileAnnotations, d.missingAnnotations) - if err != nil { - return err - } + updated := mergeAnnotationFiles(d.fileAnnotations, d.missingAnnotations) detached, err := saveAnnotationsFile(outputPath, updated, g) if err != nil { diff --git a/bundle/internal/schema/annotations_file.go b/bundle/internal/schema/annotations_file.go index 78e279f4476..df759fe29c4 100644 --- a/bundle/internal/schema/annotations_file.go +++ b/bundle/internal/schema/annotations_file.go @@ -2,20 +2,19 @@ package main import ( "bytes" + "cmp" + "errors" "fmt" "os" + "path/filepath" "reflect" "slices" + "strconv" "strings" - yaml3 "go.yaml.in/yaml/v3" - "github.com/databricks/cli/bundle/internal/annotation" - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/yamlloader" - "github.com/databricks/cli/libs/dyn/yamlsaver" + "github.com/databricks/cli/libs/structs/structvar" + "go.yaml.in/yaml/v3" ) // fieldsKey nests a type's block of field nodes inside the node of a field @@ -34,14 +33,6 @@ const ( typeDocKey = "$type" ) -// lineTypeDoc and lineFields sort the "$type" and "$fields" keys after a -// node's inline descriptor keys, which the saver orders with small line -// numbers (see descriptorKeyOrder). -const ( - lineTypeDoc = 9999 - lineFields = 10000 -) - const annotationsFileHeader = `# This file contains the documentation the CLI owns for the bundle # configuration JSON schema: docs for fields that do not exist in the upstream # API spec (.codegen/cli.json), and overrides of upstream docs. Documentation @@ -82,26 +73,71 @@ var descriptorKeys = func() map[string]bool { // follow alphabetically. var descriptorKeyOrder = []string{"description", "markdown_description", "title", "default", "enum"} -// descriptorToMap serializes d into dst with its keys ordered. It returns the -// nil value (writing nothing) when d carries no content. -// -// The empty check relies on the dyn.NilValue reference: with a nil reference, -// FromTyped omits zero-valued fields, so an all-zero descriptor collapses to -// KindNil rather than an empty map. Do not pass a map reference here, or empty -// descriptors would start serializing as "{}". -func descriptorToMap(d annotation.Descriptor, dst map[string]dyn.Value) (dyn.Value, error) { - v, err := convert.FromTyped(d, dyn.NilValue) - if err != nil || v.Kind() == dyn.KindNil { - return dyn.NilValue, err - } - return yamlsaver.ConvertToMapValue(d, yamlsaver.NewOrder(descriptorKeyOrder), []string{}, dst) +// descriptorNodes serializes d into the key and value nodes of a mapping, with its +// keys ordered. It returns nothing when d carries no content. +func descriptorNodes(d annotation.Descriptor, style yaml.Style) []*yaml.Node { + var pairs [][2]*yaml.Node + for key, v := range structvar.NewView(d, nil, nil).MapItems() { + pairs = append(pairs, [2]*yaml.Node{{Kind: yaml.ScalarNode, Value: key, Style: style}, valueNode(v, style)}) + } + // Keys in descriptorKeyOrder come first, the others follow alphabetically. + rank := func(key string) int { + if i := slices.Index(descriptorKeyOrder, key); i >= 0 { + return i + } + return len(descriptorKeyOrder) + } + slices.SortFunc(pairs, func(a, b [2]*yaml.Node) int { + return cmp.Or(cmp.Compare(rank(a[0].Value), rank(b[0].Value)), cmp.Compare(a[0].Value, b[0].Value)) + }) + var out []*yaml.Node + for _, p := range pairs { + out = append(out, p[:]...) + } + return out } -// descriptorEmpty reports whether d carries no documentation. See -// descriptorToMap for why the nil reference is what makes this work. +// valueNode converts a value of a descriptor to a YAML node. +func valueNode(x structvar.View, style yaml.Style) *yaml.Node { + switch x.Kind() { + case structvar.KindMap: + n := &yaml.Node{Kind: yaml.MappingNode, Style: style} + for k, c := range x.MapItems() { + n.Content = append(n.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: k, Style: style}, valueNode(c, style)) + } + return n + case structvar.KindSequence: + n := &yaml.Node{Kind: yaml.SequenceNode, Style: style} + for _, c := range x.Sequence() { + n.Content = append(n.Content, valueNode(c, style)) + } + return n + case structvar.KindString: + s, _ := x.AsString() + // A string that reads as another scalar (bool, number) is quoted to stay a string. + if isScalarValueInString(s) { + style = yaml.DoubleQuotedStyle + } + return &yaml.Node{Kind: yaml.ScalarNode, Value: s, Style: style} + default: + return &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprint(x.AsAny()), Style: style} + } +} + +func isScalarValueInString(s string) bool { + if s == "true" || s == "false" || s == "" { + return true + } + if _, err := strconv.ParseInt(s, 0, 64); err == nil { + return true + } + _, err := strconv.ParseFloat(s, 64) + return err == nil +} + +// descriptorEmpty reports whether d carries no documentation. func descriptorEmpty(d annotation.Descriptor) bool { - v, err := convert.FromTyped(d, dyn.NilValue) - return err == nil && v.Kind() == dyn.KindNil + return structvar.NewView(d, nil, nil).Kind() == structvar.KindNil } // loadAnnotationsFile reads the tree-format annotations file and flattens it @@ -113,10 +149,14 @@ func loadAnnotationsFile(path string, g *typeGraph) (annotation.File, []string, if err != nil { return nil, nil, err } - v, err := yamlloader.LoadYAML(path, bytes.NewBuffer(b)) + v, err := structvar.ParseYAML(bytes.NewReader(b)) if err != nil { return nil, nil, err } + if v == nil { + // An empty document has no annotations. + return annotation.File{}, nil, nil + } l := &fileLoader{graph: g, data: annotation.File{}} err = l.block(v, g.root, "") @@ -133,17 +173,14 @@ type fileLoader struct { } // block loads one type's block of field nodes. -func (l *fileLoader) block(v dyn.Value, typeKey, where string) error { - if v.Kind() == dyn.KindNil { - return nil - } - m, ok := v.AsMap() - if !ok { - return fmt.Errorf("%s: expected a mapping, got %s", where, v.Kind()) +func (l *fileLoader) block(v *yaml.Node, typeKey, where string) error { + pairs, err := mappingPairs(v, where) + if err != nil { + return err } - for _, pair := range m.Pairs() { - key := pair.Key.MustString() + for i := 0; i < len(pairs); i += 2 { + key := pairs[i].Value child := where + "." + key if where == "" { child = key @@ -154,7 +191,7 @@ func (l *fileLoader) block(v dyn.Value, typeKey, where string) error { l.unknown = append(l.unknown, child) continue } - err := l.node(pair.Value, typeKey, edge, child) + err := l.node(pairs[i+1], typeKey, edge, child) if err != nil { return err } @@ -165,26 +202,23 @@ func (l *fileLoader) block(v dyn.Value, typeKey, where string) error { // node loads one field's node: the inline descriptor for the field, the // "$type" docs for the type it resolves to, and the "$fields" block of that // type's fields. -func (l *fileLoader) node(v dyn.Value, typeKey string, edge fieldEdge, where string) error { - if v.Kind() == dyn.KindNil { - return nil - } - m, ok := v.AsMap() - if !ok { - return fmt.Errorf("%s: expected a mapping, got %s", where, v.Kind()) +func (l *fileLoader) node(v *yaml.Node, typeKey string, edge fieldEdge, where string) error { + pairs, err := mappingPairs(v, where) + if err != nil { + return err } - desc := dyn.NewMapping() - for _, pair := range m.Pairs() { - key := pair.Key.MustString() + var desc []*yaml.Node + for i := 0; i < len(pairs); i += 2 { + key, value := pairs[i].Value, pairs[i+1] switch { case key == fieldsKey && edge.typ != "": - err := l.block(pair.Value, edge.typ, where+"."+fieldsKey) + err := l.block(value, edge.typ, where+"."+fieldsKey) if err != nil { return err } case key == typeDocKey && edge.typ != "": - d, ok, err := l.descriptor(pair.Value, where+"."+typeDocKey) + d, ok, err := l.descriptor(value, where+"."+typeDocKey) if err != nil { return err } @@ -192,13 +226,13 @@ func (l *fileLoader) node(v dyn.Value, typeKey string, edge fieldEdge, where str l.data.SetSelf(edge.typ, d) } case descriptorKeys[key]: - desc.SetLoc(key, nil, pair.Value) + desc = append(desc, pairs[i], value) default: l.unknown = append(l.unknown, where+"."+key) } } - if desc.Len() > 0 { + if len(desc) > 0 { d, err := toDescriptor(desc, where) if err != nil { return err @@ -211,21 +245,20 @@ func (l *fileLoader) node(v dyn.Value, typeKey string, edge fieldEdge, where str // descriptor parses a mapping of descriptor keys (the value of a "$type" key). // Non-descriptor keys are flagged as unknown. The second return is false when // the mapping carries no descriptor keys. -func (l *fileLoader) descriptor(v dyn.Value, where string) (annotation.Descriptor, bool, error) { - m, ok := v.AsMap() - if !ok { - return annotation.Descriptor{}, false, fmt.Errorf("%s: expected a mapping, got %s", where, v.Kind()) - } - desc := dyn.NewMapping() - for _, pair := range m.Pairs() { - key := pair.Key.MustString() +func (l *fileLoader) descriptor(v *yaml.Node, where string) (annotation.Descriptor, bool, error) { + if v.Kind != yaml.MappingNode { + return annotation.Descriptor{}, false, fmt.Errorf("%s: expected a mapping, got %s", where, nodeKind(v)) + } + var desc []*yaml.Node + for i := 0; i < len(v.Content); i += 2 { + key := v.Content[i].Value if descriptorKeys[key] { - desc.SetLoc(key, nil, pair.Value) + desc = append(desc, v.Content[i], v.Content[i+1]) } else { l.unknown = append(l.unknown, where+"."+key) } } - if desc.Len() == 0 { + if len(desc) == 0 { return annotation.Descriptor{}, false, nil } d, err := toDescriptor(desc, where) @@ -233,15 +266,40 @@ func (l *fileLoader) descriptor(v dyn.Value, where string) (annotation.Descripto } // toDescriptor converts a mapping of descriptor keys to a typed descriptor. -func toDescriptor(desc dyn.Mapping, where string) (annotation.Descriptor, error) { +func toDescriptor(desc []*yaml.Node, where string) (annotation.Descriptor, error) { var d annotation.Descriptor - err := convert.ToTyped(&d, dyn.V(desc)) + _, diags, err := structvar.DecodeYAMLNode(where, &yaml.Node{Kind: yaml.MappingNode, Content: desc}, &d, nil) + if err == nil { + err = diags.Error() + } if err != nil { return annotation.Descriptor{}, fmt.Errorf("%s: %w", where, err) } return d, nil } +// mappingPairs returns the alternating key and value nodes of a mapping. A null node has none. +func mappingPairs(v *yaml.Node, where string) ([]*yaml.Node, error) { + switch { + case v.ShortTag() == "!!null": + return nil, nil + case v.Kind != yaml.MappingNode: + return nil, fmt.Errorf("%s: expected a mapping, got %s", where, nodeKind(v)) + } + return v.Content, nil +} + +func nodeKind(v *yaml.Node) string { + switch v.Kind { + case yaml.MappingNode: + return "map" + case yaml.SequenceNode: + return "sequence" + default: + return strings.TrimPrefix(v.ShortTag(), "!!") + } +} + // saveAnnotationsFile writes data to path in the canonical tree layout: a // depth-first walk over the config type graph in struct declaration order // expands every type at its first occurrence; keys are emitted alphabetically. @@ -258,18 +316,10 @@ func saveAnnotationsFile(path string, data annotation.File, g *typeGraph) ([]str } s.assignCanonical(g.root) - root, err := s.block(g.root) - if err != nil { - return nil, err - } - - // Style every top-level key so all nested scalars render in literal block - // style, matching the formatting of the previous annotation files. - style := map[string]yaml3.Style{} - for k := range root { - style[k] = yaml3.LiteralStyle - } - err = yamlsaver.NewSaverWithStyle(style).SaveAsYAML(root, path, true) + // Everything below the top-level keys renders in literal block style, matching + // the formatting of the previous annotation files. + root := &yaml.Node{Kind: yaml.MappingNode, Content: s.block(g.root, 0)} + err := writeYAML(path, root) if err != nil { return nil, err } @@ -280,6 +330,20 @@ func saveAnnotationsFile(path string, data annotation.File, g *typeGraph) ([]str return s.detached(), nil } +func writeYAML(path string, node *yaml.Node) error { + err := os.MkdirAll(filepath.Dir(path), 0o755) + if err != nil { + return err + } + f, err := os.Create(path) + if err != nil { + return err + } + enc := yaml.NewEncoder(f) + enc.SetIndent(2) + return errors.Join(enc.Encode(node), f.Close()) +} + type edgeKey struct { typ string name string @@ -307,61 +371,52 @@ func (s *fileSaver) assignCanonical(typeKey string) { } } -// block renders one type's block of field nodes, emitted alphabetically. -// Lines in the value locations encode the output order for the YAML saver. -func (s *fileSaver) block(typeKey string) (map[string]dyn.Value, error) { - out := map[string]dyn.Value{} - line := 0 +// block renders one type's block of field nodes as the content of a mapping, emitted +// alphabetically, with the keys in keyStyle. +func (s *fileSaver) block(typeKey string, keyStyle yaml.Style) []*yaml.Node { + var out []*yaml.Node edges := slices.Clone(s.graph.fields[typeKey]) slices.SortFunc(edges, func(a, b fieldEdge) int { return strings.Compare(a.name, b.name) }) for _, edge := range edges { - node, err := s.node(typeKey, edge) - if err != nil { - return nil, err - } + node := s.node(typeKey, edge) if len(node) > 0 { - out[edge.name] = dyn.NewValue(node, []diag.Location{{Line: line}}) - line++ + out = append(out, &yaml.Node{Kind: yaml.ScalarNode, Value: edge.name, Style: keyStyle}, mapping(node)) } } - return out, nil + return out +} + +func mapping(content []*yaml.Node) *yaml.Node { + return &yaml.Node{Kind: yaml.MappingNode, Content: content, Style: yaml.LiteralStyle} +} + +func literalKey(name string) *yaml.Node { + return &yaml.Node{Kind: yaml.ScalarNode, Value: name, Style: yaml.LiteralStyle} } // node renders one field's node: the inline field descriptor plus, at the // field's canonical position, the resolved type's "$type" docs and the // "fields" block of its fields. -func (s *fileSaver) node(typeKey string, edge fieldEdge) (map[string]dyn.Value, error) { - out := map[string]dyn.Value{} +func (s *fileSaver) node(typeKey string, edge fieldEdge) []*yaml.Node { + var out []*yaml.Node - // The inline descriptor keys are written directly into the node, sharing - // it with the "$type" and "$fields" keys added below. if d, ok := s.takeField(typeKey, edge.name); ok { - if _, err := descriptorToMap(d, out); err != nil { - return nil, err - } + out = descriptorNodes(d, yaml.LiteralStyle) } if s.expandAt[edgeKey{typeKey, edge.name}] { - v, err := descriptorToMap(s.takeSelf(edge.typ), map[string]dyn.Value{}) - if err != nil { - return nil, err - } - if v.Kind() != dyn.KindNil { - out[typeDocKey] = v.WithLocations([]diag.Location{{Line: lineTypeDoc}}) + if doc := descriptorNodes(s.takeSelf(edge.typ), yaml.LiteralStyle); len(doc) > 0 { + out = append(out, literalKey(typeDocKey), mapping(doc)) } - child, err := s.block(edge.typ) - if err != nil { - return nil, err - } - if len(child) > 0 { - out[fieldsKey] = dyn.NewValue(child, []diag.Location{{Line: lineFields}}) + if child := s.block(edge.typ, yaml.LiteralStyle); len(child) > 0 { + out = append(out, literalKey(fieldsKey), mapping(child)) } } - return out, nil + return out } // takeField returns the descriptor for a field and marks it consumed for the diff --git a/bundle/internal/schema/annotations_file_test.go b/bundle/internal/schema/annotations_file_test.go index 42899b0c184..d50dcf6ff1b 100644 --- a/bundle/internal/schema/annotations_file_test.go +++ b/bundle/internal/schema/annotations_file_test.go @@ -167,3 +167,15 @@ func TestAnnotationsFileDetachedEntries(t *testing.T) { "github.com/databricks/cli/no/such.Type: field", }, detached) } + +func TestAnnotationsFileEmpty(t *testing.T) { + g, err := newTypeGraph(reflect.TypeFor[tgRoot]()) + require.NoError(t, err) + path := filepath.Join(t.TempDir(), "annotations.yml") + require.NoError(t, os.WriteFile(path, []byte("# no annotations yet\n"), 0o644)) + + data, unknown, err := loadAnnotationsFile(path, g) + require.NoError(t, err) + assert.Empty(t, data) + assert.Empty(t, unknown) +} diff --git a/bundle/internal/schema/annotations_test.go b/bundle/internal/schema/annotations_test.go index 3ad9bae6fd6..e43acc97c20 100644 --- a/bundle/internal/schema/annotations_test.go +++ b/bundle/internal/schema/annotations_test.go @@ -270,3 +270,16 @@ func TestBuildEnumDescriptions(t *testing.T) { assert.Equal(t, []string{"[Public Preview]", "", "[Beta]"}, got) }) } + +func TestMergeAnnotationFilesMergesDefaults(t *testing.T) { + a := annotation.File{} + a.SetField("t", "f", annotation.Descriptor{Default: map[string]any{"a": 1, "nested": map[string]any{"x": 1}}}) + a.SetField("t", "g", annotation.Descriptor{Default: []any{1}}) + b := annotation.File{} + b.SetField("t", "f", annotation.Descriptor{Default: map[string]any{"b": 2, "nested": map[string]any{"y": 2}}}) + b.SetField("t", "g", annotation.Descriptor{Default: []any{2}}) + + merged := mergeAnnotationFiles(a, b) + assert.Equal(t, map[string]any{"a": 1, "b": 2, "nested": map[string]any{"x": 1, "y": 2}}, merged["t"].Fields["f"].Default) + assert.Equal(t, []any{1, 2}, merged["t"].Fields["g"].Default) +} diff --git a/bundle/internal/schema/main.go b/bundle/internal/schema/main.go index 01a743933b2..75d36dd4824 100644 --- a/bundle/internal/schema/main.go +++ b/bundle/internal/schema/main.go @@ -13,17 +13,17 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/config/variable" "github.com/databricks/cli/internal/clijson" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/jsonschema" + "github.com/databricks/cli/libs/structs/structvar" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/pipelines" ) // interpolationPattern builds a JSON Schema regex for ${prefix.path...} references. -// Path segments use [dynvar.BaseVarDef]; unlike the runtime matcher in ref.go, this +// Path segments use [structvar.BaseVarDef]; unlike the runtime matcher in ref.go, this // requires a fixed prefix (var, resources, ...) and at least one ".segment" after it. func interpolationPattern(s string) string { - return fmt.Sprintf(`\$\{(%s(\.%s(\[[0-9]+\])*)+)\}`, s, dynvar.BaseVarDef) + return fmt.Sprintf(`\$\{(%s(\.%s(\[[0-9]+\])*)+)\}`, s, structvar.BaseVarDef) } func addInterpolationPatterns(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema { diff --git a/bundle/internal/schema/main_test.go b/bundle/internal/schema/main_test.go index 92184a2d48f..4f6a4c741d8 100644 --- a/bundle/internal/schema/main_test.go +++ b/bundle/internal/schema/main_test.go @@ -1,21 +1,20 @@ package main import ( - "bytes" "io" + "maps" "os" "path" "reflect" + "slices" "strings" "testing" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/merge" - "github.com/databricks/cli/libs/dyn/yamlloader" "github.com/databricks/cli/libs/jsonschema" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v3" ) const cliJSONPath = "../../../.codegen/cli.json" @@ -73,36 +72,45 @@ func TestRequiredAnnotationsForNewFields(t *testing.T) { require.NoError(t, err) currentFile, err := os.ReadFile(annotationsPath) require.NoError(t, err) - original, err := yamlloader.LoadYAML("", bytes.NewBuffer(originalFile)) - require.NoError(t, err) - current, err := yamlloader.LoadYAML("", bytes.NewBuffer(currentFile)) - require.NoError(t, err) + var original, current any + require.NoError(t, yaml.Unmarshal(originalFile, &original)) + require.NoError(t, yaml.Unmarshal(currentFile, ¤t)) // Regenerating from the committed file must be a no-op: no new placeholders // (a new undocumented config field) and no deletes/updates (stale - // placeholders not yet pruned). VisitDelete/VisitUpdate must be set or - // Override panics on any change. + // placeholders not yet pruned). var addedFieldPaths []string var changedFieldPaths []string - _, err = merge.Override(original, current, merge.OverrideVisitor{ - VisitInsert: func(basePath dyn.Path, right dyn.Value) (dyn.Value, error) { - addedFieldPaths = append(addedFieldPaths, basePath.String()) - return right, nil - }, - VisitDelete: func(basePath dyn.Path, left dyn.Value) error { - changedFieldPaths = append(changedFieldPaths, basePath.String()) - return nil - }, - VisitUpdate: func(basePath dyn.Path, left, right dyn.Value) (dyn.Value, error) { - changedFieldPaths = append(changedFieldPaths, basePath.String()) - return right, nil - }, - }) - assert.NoError(t, err) + diffYAML("", original, current, &addedFieldPaths, &changedFieldPaths) assert.Empty(t, addedFieldPaths, "Missing JSON-schema descriptions for new config fields in bundle/internal/schema/annotations.yml:\n%s", strings.Join(addedFieldPaths, "\n")) assert.Empty(t, changedFieldPaths, "annotations.yml is out of sync; run `./task generate-schema` and commit the result:\n%s", strings.Join(changedFieldPaths, "\n")) } +// diffYAML records the paths of the values added to, and removed from or changed in, left to get right. +func diffYAML(path string, left, right any, added, changed *[]string) { + l, lok := left.(map[string]any) + r, rok := right.(map[string]any) + if !lok || !rok { + if !reflect.DeepEqual(left, right) { + *changed = append(*changed, path) + } + return + } + for _, k := range slices.Sorted(maps.Keys(l)) { + if _, ok := r[k]; !ok { + *changed = append(*changed, strings.TrimPrefix(path+"."+k, ".")) + } + } + for _, k := range slices.Sorted(maps.Keys(r)) { + p := strings.TrimPrefix(path+"."+k, ".") + if lv, ok := l[k]; ok { + diffYAML(p, lv, r[k], added, changed) + } else { + *added = append(*added, p) + } + } +} + // Checks that the annotations file only contains entries that match the // current bundle configuration structure. func TestNoDetachedAnnotations(t *testing.T) { diff --git a/bundle/internal/validation/enum.go b/bundle/internal/validation/enum.go index e3abcd72940..3251e21951e 100644 --- a/bundle/internal/validation/enum.go +++ b/bundle/internal/validation/enum.go @@ -90,8 +90,8 @@ func getEnumValues(typ reflect.Type) ([]string, error) { return enumStrings, nil } -// extractEnumFields walks through a struct type and returns the valid values of the -// enum fields by their pattern. +// extractEnumFields walks through a struct type and returns the valid values by the +// pattern of the enum fields. func extractEnumFields(typ reflect.Type) (map[string][]string, error) { fieldsByPattern := make(map[string][]string) diff --git a/bundle/internal/validation/main.go b/bundle/internal/validation/main.go index bd35dfcd06d..29f8809f372 100644 --- a/bundle/internal/validation/main.go +++ b/bundle/internal/validation/main.go @@ -4,8 +4,9 @@ import ( "log" ) -// This package is meant to be run from the root of the CLI repo. It generates the -// typed walkers of the required and enum field validators. +// This package is meant to be run from the root of the CLI repo. It generates typed +// walkers over the bundle configuration: those of the required and enum field +// validators, and the string walker of bundle/config/walk. func main() { err := generateWalkers("bundle/config/validate") if err != nil { diff --git a/bundle/internal/validation/required.go b/bundle/internal/validation/required.go index f8ed375224c..1f89b3adc38 100644 --- a/bundle/internal/validation/required.go +++ b/bundle/internal/validation/required.go @@ -1,15 +1,32 @@ package main import ( + "fmt" "reflect" + "strings" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/structs/structtag" "github.com/databricks/cli/libs/structs/structwalk" ) +// formatSliceToString formats a list of values into string of the form `{value1, value2, ...}` +// representing a Go slice literal. +func formatSliceToString(values []string) string { + if len(values) == 0 { + return "{}" + } + + var quoted []string + for _, value := range values { + quoted = append(quoted, fmt.Sprintf("%q", value)) + } + + return "{" + strings.Join(quoted, ", ") + "}" +} + // extractRequiredFields walks through a struct type and returns the required fields -// (JSON names, in declaration order) by the pattern of their parent. +// (JSON names) by the pattern of the struct that has them. func extractRequiredFields(typ reflect.Type) (map[string][]string, error) { fieldsByPattern := make(map[string][]string) @@ -49,3 +66,16 @@ func extractRequiredFields(typ reflect.Type) (map[string][]string, error) { return fieldsByPattern, err } + +// getGroupingKey determines the grouping key for organizing patterns +func getGroupingKey(pattern string) string { + parts := strings.Split(pattern, ".") + + // Group resources by their resource type (e.g., "resources.jobs") + if parts[0] == "resources" && len(parts) > 1 { + return parts[0] + "." + parts[1] + } + + // Use the top level key for other fields + return parts[0] +} diff --git a/bundle/internal/validation/resources_iterate.go b/bundle/internal/validation/resources_iterate.go new file mode 100644 index 00000000000..f156dac683b --- /dev/null +++ b/bundle/internal/validation/resources_iterate.go @@ -0,0 +1,142 @@ +package main + +import ( + "bytes" + "cmp" + _ "embed" + "fmt" + "go/format" + "os" + "path" + "reflect" + "slices" + "strconv" + "strings" + "text/template" + + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/libs/structs/structvar" + "github.com/databricks/databricks-sdk-go/service/catalog" +) + +//go:embed resources_iterate.go.tmpl +var resourcesIterateSource string + +var resourcesIterateTemplate = template.Must(template.New("resources_iterate").Parse(resourcesIterateSource)) + +// generateResourcesIterate writes bundle/config/resources_iterate.go: typed iterators +// over all resources, their permissions and their grants, generated from the fields of +// config.Resources so that new resource types are covered. +func generateResourcesIterate(file string) error { + g := newGen("", false) + g.aliases["config"] = true + rt := reflect.TypeFor[config.Resources]() + info := structvar.GetStructInfo(rt) + + type resourceType struct { + key, field string + elem reflect.Type // the resource struct + perms string // Go field of the permissions, if any + permElem reflect.Type + grants string // Go field of the grants, if any + id string // Go field of the deployed id, if any + status bool // the resource has a modified_status + } + var types []resourceType + grantsType := reflect.TypeFor[[]catalog.PrivilegeAssignment]() + for _, f := range info.List { + ft := rt.FieldByIndex(f.Index).Type + if ft.Kind() != reflect.Map || ft.Elem().Kind() != reflect.Pointer { + return fmt.Errorf("resources.%s: expected a map of pointers, got %s", f.Key, ft) + } + t := resourceType{key: f.Key, field: rt.FieldByIndex(f.Index).Name, elem: ft.Elem().Elem()} + ei := structvar.GetStructInfo(t.elem) + if idx, ok := ei.Fields["permissions"]; ok { + pf := t.elem.FieldByIndex(idx) + if !isPermissionsSlice(pf.Type) { + return fmt.Errorf("resources.%s.permissions: unexpected type %s", f.Key, pf.Type) + } + t.perms = pf.Name + t.permElem = pf.Type.Elem() + } + if idx, ok := ei.Fields["grants"]; ok { + gf := t.elem.FieldByIndex(idx) + if gf.Type != grantsType { + return fmt.Errorf("resources.%s.grants: unexpected type %s", f.Key, gf.Type) + } + t.grants = gf.Name + } + if idx, ok := ei.Fields["id"]; ok && t.elem.FieldByIndex(idx).Type.Kind() == reflect.String { + t.id = t.elem.FieldByIndex(idx).Name + } + _, t.status = ei.Fields["modified_status"] + types = append(types, t) + } + slices.SortFunc(types, func(a, b resourceType) int { return cmp.Compare(a.key, b.key) }) + + // Type names register their imports, so the data is built before the header. + type typeData struct { + Key, Field, Elem string + Perms, PermElem, PermLevel string + Grants, ID string + HasState bool + } + data := struct { + Imports []string + Types []typeData + ModifiedStatus, Grant string + }{ + ModifiedStatus: g.typeName(reflect.TypeFor[resources.ModifiedStatus]()), + Grant: g.typeName(grantsType.Elem()), + } + for _, t := range types { + d := typeData{Key: t.key, Field: t.field, Elem: g.typeName(t.elem), Perms: t.perms, Grants: t.grants, ID: t.id, HasState: t.id != "" && t.status} + if t.perms != "" { + d.PermElem = g.typeName(t.permElem) + d.PermLevel = g.typeName(t.permElem.Field(0).Type) + } + data.Types = append(data.Types, d) + } + for p, alias := range g.imports { + switch { + case p == "github.com/databricks/cli/bundle/config": + case path.Base(p) == alias: + data.Imports = append(data.Imports, strconv.Quote(p)) + default: + data.Imports = append(data.Imports, alias+" "+strconv.Quote(p)) + } + } + slices.SortFunc(data.Imports, func(a, b string) int { return cmp.Compare(importPath(a), importPath(b)) }) + + var src bytes.Buffer + if err := resourcesIterateTemplate.Execute(&src, data); err != nil { + return err + } + formatted, err := format.Source(src.Bytes()) + if err != nil { + _ = os.WriteFile(file+".broken", src.Bytes(), 0o644) + return fmt.Errorf("failed to format %s: %w", file, err) + } + return os.WriteFile(file, formatted, 0o644) +} + +// importPath returns the quoted path of an import line (with or without an alias). +func importPath(line string) string { + _, p, _ := strings.Cut(line, `"`) + return p +} + +// isPermissionsSlice reports whether t is a slice of a struct with the fields of +// resources.PermissionFields. +func isPermissionsSlice(t reflect.Type) bool { + if t.Kind() != reflect.Slice || t.Elem().Kind() != reflect.Struct { + return false + } + e := t.Elem() + var names []string + for i := range e.NumField() { + names = append(names, e.Field(i).Name) + } + return strings.Join(names, ",") == "Level,UserName,ServicePrincipalName,GroupName" && e.Field(0).Type.Kind() == reflect.String +} diff --git a/bundle/internal/validation/resources_iterate.go.tmpl b/bundle/internal/validation/resources_iterate.go.tmpl new file mode 100644 index 00000000000..6befe8b5642 --- /dev/null +++ b/bundle/internal/validation/resources_iterate.go.tmpl @@ -0,0 +1,149 @@ +// Code generated by bundle/internal/validation. DO NOT EDIT. + +package config + +import ( +{{- range .Imports}} + {{.}} +{{- end}} +) + +// IterateAllResources calls fn for every resource, in the order of the resource type +// and then of the name, until fn returns false. Resources defined as null are +// visited with a nil res. +func (r *Resources) IterateAllResources(fn func(typ, name string, res ConfigResource) bool) { +{{- range .Types}} + for _, name := range sortedKeys(r.{{.Field}}) { + var res ConfigResource + if x := r.{{.Field}}[name]; x != nil { + res = x + } + if !fn({{printf "%q" .Key}}, name, res) { + return + } + } +{{- end}} +} + +// Select returns the resources of r whose names are in keys (by type): new maps that +// share the resources. Types not in keys are nil. +func (r *Resources) Select(keys map[string]map[string]struct{}) Resources { + var out Resources +{{- range .Types}} + if names, ok := keys[{{printf "%q" .Key}}]; ok { + out.{{.Field}} = make(map[string]*{{.Elem}}, len(names)) + for name := range names { + if x, ok := r.{{.Field}}[name]; ok { + out.{{.Field}}[name] = x + } + } + } +{{- end}} + return out +} + +// CopyResource sets the resource name of type typ to the one in src (shared, not +// copied). It returns false if src has no such resource. +func (r *Resources) CopyResource(src *Resources, typ, name string) bool { + switch typ { +{{- range .Types}} + case {{printf "%q" .Key}}: + x, ok := src.{{.Field}}[name] + if !ok { + return false + } + if r.{{.Field}} == nil { + r.{{.Field}} = map[string]*{{.Elem}}{} + } + r.{{.Field}}[name] = x + return true +{{- end}} + } + return false +} + +// GetResource returns the resource name of type typ. ok is false if there is no such +// resource or it is defined as null. +func (r *Resources) GetResource(typ, name string) (res ConfigResource, ok bool) { + switch typ { +{{- range .Types}} + case {{printf "%q" .Key}}: + if x := r.{{.Field}}[name]; x != nil { + return x, true + } +{{- end}} + } + return nil, false +} + +// IterateAllPermissions calls fn for the permissions of every resource that can have +// them, in the order of IterateAllResources, until fn returns false. +func (r *Resources) IterateAllPermissions(fn func(typ, name string, perms PermissionList) bool) { +{{- range .Types}}{{if .Perms}} + for _, name := range sortedKeys(r.{{.Field}}) { + if x := r.{{.Field}}[name]; x != nil && !fn({{printf "%q" .Key}}, name, permissionList[{{.PermElem}}, {{.PermLevel}}]{&x.{{.Perms}}}) { + return + } + } +{{- end}}{{end}} +} + +// PermissionsOf returns the permissions of the resource name of type typ. ok is false +// if there is no such resource or resources of type typ have no permissions. +func (r *Resources) PermissionsOf(typ, name string) (perms PermissionList, ok bool) { + switch typ { +{{- range .Types}}{{if .Perms}} + case {{printf "%q" .Key}}: + if x := r.{{.Field}}[name]; x != nil { + return permissionList[{{.PermElem}}, {{.PermLevel}}]{&x.{{.Perms}}}, true + } +{{- end}}{{end}} + } + return nil, false +} + +// IterateAllResourceStates calls fn for the deployed id and modified status of every +// resource that has them, in the order of IterateAllResources, until fn returns false. +func (r *Resources) IterateAllResourceStates(fn func(typ, name string, id *string, status *{{.ModifiedStatus}}) bool) { +{{- range .Types}}{{if .HasState}} + for _, name := range sortedKeys(r.{{.Field}}) { + if x := r.{{.Field}}[name]; x != nil && !fn({{printf "%q" .Key}}, name, &x.{{.ID}}, &x.ModifiedStatus) { + return + } + } +{{- end}}{{end}} +} + +// GetOrCreateState returns the deployed id and modified status of the resource name of +// type typ, creating the resource if it does not exist (created is then true). ok is +// false if resources of type typ have no state. +func (r *Resources) GetOrCreateState(typ, name string) (id *string, status *{{.ModifiedStatus}}, created, ok bool) { + switch typ { +{{- range .Types}}{{if .HasState}} + case {{printf "%q" .Key}}: + x, exists := r.{{.Field}}[name] + if x == nil { + if r.{{.Field}} == nil { + r.{{.Field}} = map[string]*{{.Elem}}{} + } + x = &{{.Elem}}{} + r.{{.Field}}[name] = x + } + return &x.{{.ID}}, &x.ModifiedStatus, !exists, true +{{- end}}{{end}} + default: + return nil, nil, false, false + } +} + +// IterateAllGrants calls fn for the grants of every resource that can have them, in +// the order of IterateAllResources, until fn returns false. +func (r *Resources) IterateAllGrants(fn func(typ, name string, grants *[]{{.Grant}}) bool) { +{{- range .Types}}{{if .Grants}} + for _, name := range sortedKeys(r.{{.Field}}) { + if x := r.{{.Field}}[name]; x != nil && !fn({{printf "%q" .Key}}, name, &x.{{.Grants}}) { + return + } + } +{{- end}}{{end}} +} diff --git a/bundle/internal/validation/strings_walker.go b/bundle/internal/validation/strings_walker.go new file mode 100644 index 00000000000..83b55843839 --- /dev/null +++ b/bundle/internal/validation/strings_walker.go @@ -0,0 +1,247 @@ +package main + +import ( + "bytes" + "cmp" + "fmt" + "go/format" + "os" + "path" + "reflect" + "slices" + "strings" + + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structvar" +) + +// stringsGen generates the walker visiting every string of the configuration +// (bundle/config/walk/strings_walk.go): one function per struct type that contains +// strings, with plain field access. +type stringsGen struct { + gen + hasStrings map[reflect.Type]bool + names map[reflect.Type]string +} + +// containsStrings reports whether values of type t can contain strings. +func (g *stringsGen) containsStrings(t reflect.Type, visiting map[reflect.Type]bool) bool { + if v, ok := g.hasStrings[t]; ok { + return v + } + switch t.Kind() { + case reflect.String, reflect.Interface: + return true + case reflect.Pointer, reflect.Slice, reflect.Map: + return g.containsStrings(t.Elem(), visiting) + case reflect.Struct: + if structvar.IsSDKNativeType(t) { + return false + } + if visiting[t] { + return false + } + visiting[t] = true + defer delete(visiting, t) + found := false + info := structvar.GetStructInfo(t) + for _, f := range info.List { + if g.containsStrings(t.FieldByIndex(f.Index).Type, visiting) { + found = true + } + } + if len(visiting) == 1 { + g.hasStrings[t] = found + } + return found + default: + return false + } +} + +func (g *stringsGen) funcName(t reflect.Type) string { + if name, ok := g.names[t]; ok { + return name + } + base := "strings" + capitalize(path.Base(t.PkgPath())) + t.Name() + g.gen.names[base]++ + name := base + if n := g.gen.names[base]; n > 1 { + name = fmt.Sprintf("%s%d", base, n) + } + g.names[t] = name + g.pendingTypes = append(g.pendingTypes, t) + return name +} + +// emitStringsStruct writes the function visiting the strings of struct type t. +func (g *stringsGen) emitStringsStruct(t reflect.Type) { + // A value replaced by a pure reference hides what is below it, as in the view; the + // reference itself is in config.Root.References. + fmt.Fprintf(&g.out, "\nfunc %s(c *stringsWalk, n *Node, v *%s) {\n\tif n.isRef() {\n\t\treturn\n\t}\n", g.funcName(t), g.typeName(t)) + info := structvar.GetStructInfo(t) + for _, f := range info.List { + ft := t.FieldByIndex(f.Index).Type + if !g.containsStrings(ft, map[reflect.Type]bool{}) { + continue + } + expr, guards := selector("v", t, f.Index) + if t == reflect.TypeFor[config.Root]() && f.Key == "resources" { + // See walk.StringsWithoutResources. + guards = append(guards, "!c.skipResources") + } + switch ft.Kind() { + case reflect.String, reflect.Interface: + default: + // A field replaced by a pure reference hides what is below it, as in the + // view; the reference itself is in config.Root.References. + guards = append(guards, fmt.Sprintf("!n.IsRef(%q)", f.Key)) + } + if len(guards) > 0 { + fmt.Fprintf(&g.out, "\tif %s {\n", strings.Join(guards, " && ")) + } + g.emitEmptyField(structField("v", t, f.Key), fmt.Sprintf("n.Child(%q)", f.Key)) + g.emitValue("\t", expr, ft, fmt.Sprintf("n.Child(%q)", f.Key), true) + if len(guards) > 0 { + fmt.Fprintf(&g.out, "\t}\n") + } + } + fmt.Fprintf(&g.out, "}\n") +} + +// emitEmptyField writes the code visiting the struct field f in mode EmptyFields if it +// holds an empty string that [structvar.View] finds present: a forced string, a non-nil +// pointer to a string, or an interface. Fields without omitempty are skipped: their +// zero value is always sent. +func (g *stringsGen) emitEmptyField(f field, nodeExpr string) { + if f.forceEmpty { + return + } + var cond string + switch { + case f.typ.Kind() == reflect.String && f.fsf != "": + cond = fmt.Sprintf(`%s == "" && Forced(%s, %q)`, f.expr, f.fsf, f.goName) + case f.typ.Kind() == reflect.Pointer && f.typ.Elem().Kind() == reflect.String: + cond = fmt.Sprintf(`%s != nil && *%s == ""`, f.expr, f.expr) + case f.typ.Kind() == reflect.Interface: + cond = f.expr + ` == ""` + default: + return + } + fmt.Fprintf(&g.out, "\tif c.mode == EmptyFields && %s {\n\t\tch := %s\n\t\tc.fn(&ch, \"\")\n\t}\n", cond, nodeExpr) +} + +// emitValue writes the code visiting the value expr of type t whose node is nodeExpr. +// settable reports whether expr can be assigned to. +func (g *stringsGen) emitValue(indent, expr string, t reflect.Type, nodeExpr string, settable bool) { + switch t.Kind() { + case reflect.String: + conv := "" + if t.PkgPath() != "" { + conv = g.typeName(t) + } + fmt.Fprintf(&g.out, "%sif s := string(%s); c.match(s) {\n%s\tch := %s\n%s\tif r, ok := c.fn(&ch, s); ok {\n", indent, expr, indent, nodeExpr, indent) + if settable { + fmt.Fprintf(&g.out, "%s\t\t%s = %s(r)\n", indent, expr, conv) + } else { + fmt.Fprintf(&g.out, "%s\t\t_ = r\n", indent) + } + fmt.Fprintf(&g.out, "%s\t}\n%s}\n", indent, indent) + case reflect.Interface: + fmt.Fprintf(&g.out, "%sif %s != nil {\n%s\tch := %s\n%s\tc.any(&ch, &%s)\n%s}\n", indent, expr, indent, nodeExpr, indent, expr, indent) + case reflect.Pointer: + et := t.Elem() + if et.Kind() == reflect.Struct { + fmt.Fprintf(&g.out, "%sif x := %s; x != nil {\n%s\tch := %s\n%s\t%s(c, &ch, x)\n%s}\n", indent, expr, indent, nodeExpr, indent, g.funcName(et), indent) + } else { + fmt.Fprintf(&g.out, "%sif x := %s; x != nil {\n", indent, expr) + g.emitValue(indent+"\t", "*x", et, nodeExpr, true) + fmt.Fprintf(&g.out, "%s}\n", indent) + } + case reflect.Struct: + fmt.Fprintf(&g.out, "%s{\n%s\tch := %s\n%s\t%s(c, &ch, &%s)\n%s}\n", indent, indent, nodeExpr, indent, g.funcName(t), expr, indent) + case reflect.Slice: + fmt.Fprintf(&g.out, "%sif len(%s) > 0 {\n%s\tcn := %s\n%s\tfor i := range %s {\n", indent, expr, indent, nodeExpr, indent, expr) + g.emitValue(indent+"\t\t", expr+"[i]", t.Elem(), "cn.Elem(i)", true) + fmt.Fprintf(&g.out, "%s\t}\n%s}\n", indent, indent) + case reflect.Map: + et := t.Elem() + fmt.Fprintf(&g.out, "%sif len(%s) > 0 {\n%s\tcn := %s\n%s\tfor _, k := range sortedKeys(%s) {\n", indent, expr, indent, nodeExpr, indent, expr) + switch { + case et.Kind() == reflect.Pointer: + g.emitValue(indent+"\t\t", expr+"[k]", et, "cn.Child(string(k))", false) + case et.Kind() == reflect.String: + // Map values are not addressable: write the replacement back. + fmt.Fprintf(&g.out, "%s\t\tif s := string(%s[k]); c.match(s) {\n%s\t\t\tch := cn.Child(string(k))\n%s\t\t\tif r, ok := c.fn(&ch, s); ok {\n%s\t\t\t\t%s[k] = %s(r)\n%s\t\t\t}\n%s\t\t}\n", + indent, expr, indent, indent, indent, expr, g.convName(et), indent, indent) + default: + fmt.Fprintf(&g.out, "%s\t\tx := %s[k]\n", indent, expr) + g.emitValue(indent+"\t\t", "x", et, "cn.Child(string(k))", true) + fmt.Fprintf(&g.out, "%s\t\t%s[k] = x\n", indent, expr) + } + fmt.Fprintf(&g.out, "%s\t}\n%s}\n", indent, indent) + default: + panic(fmt.Sprintf("unsupported type %s", t)) + } +} + +// emitResourceDispatch writes stringsResource, which visits the strings of one resource +// by its type and name. +func (g *stringsGen) emitResourceDispatch() { + rt := reflect.TypeFor[config.Resources]() + fmt.Fprintf(&g.out, "\nfunc stringsResource(c *stringsWalk, n *Node, v *config.Resources, typ, name string) {\n\tswitch typ {\n") + for _, f := range structvar.GetStructInfo(rt).List { + ft := rt.FieldByIndex(f.Index) + et := ft.Type.Elem().Elem() + if !g.containsStrings(et, map[reflect.Type]bool{}) { + continue + } + fmt.Fprintf(&g.out, "\tcase %q:\n\t\tif x := v.%s[name]; x != nil {\n\t\t\tch := n.Child(typ)\n\t\t\trn := ch.Child(name)\n\t\t\t%s(c, &rn, x)\n\t\t}\n", f.Key, ft.Name, g.funcName(et)) + } + fmt.Fprintf(&g.out, "\t}\n}\n") +} + +func (g *stringsGen) convName(t reflect.Type) string { + if t.PkgPath() == "" { + return "" + } + return g.typeName(t) +} + +func generateStringsWalker(file string) error { + g := &stringsGen{gen: *newGen("strings", false), hasStrings: map[reflect.Type]bool{}, names: map[reflect.Type]string{}} + g.aliases["strings"] = true + g.aliases["stringsWalk"] = true + root := reflect.TypeFor[config.Root]() + g.funcName(root) + for len(g.pendingTypes) > 0 { + t := g.pendingTypes[0] + g.pendingTypes = g.pendingTypes[1:] + g.emitStringsStruct(t) + } + // After the loop: the resource types are all named by then, so their order stays. + g.emitResourceDispatch() + + var src bytes.Buffer + fmt.Fprintf(&src, "// Code generated by bundle/internal/validation. DO NOT EDIT.\n\npackage walk\n\nimport (\n") + paths := make([]string, 0, len(g.imports)) + for p := range g.imports { + paths = append(paths, p) + } + slices.SortFunc(paths, cmp.Compare) + for _, p := range paths { + if path.Base(p) == g.imports[p] { + fmt.Fprintf(&src, "\t%q\n", p) + } else { + fmt.Fprintf(&src, "\t%s %q\n", g.imports[p], p) + } + } + fmt.Fprintf(&src, ")\n") + src.Write(g.out.Bytes()) + formatted, err := format.Source(src.Bytes()) + if err != nil { + _ = os.WriteFile(file+".broken", src.Bytes(), 0o644) + return fmt.Errorf("failed to format %s: %w", file, err) + } + return os.WriteFile(file, formatted, 0o644) +} diff --git a/bundle/internal/validation/walker.go b/bundle/internal/validation/walker.go index 8f20007daef..cda81aa7910 100644 --- a/bundle/internal/validation/walker.go +++ b/bundle/internal/validation/walker.go @@ -5,7 +5,6 @@ import ( "cmp" "fmt" "go/format" - "maps" "os" "path" "reflect" @@ -14,28 +13,32 @@ import ( "strings" "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/resourcedefaults" "github.com/databricks/cli/libs/structs/structpath" - "github.com/databricks/cli/libs/structs/structtag" + "github.com/databricks/cli/libs/structs/structvar" + "github.com/databricks/databricks-sdk-go/service/jobs" ) -// The walkers generated here replace matching every node of the configuration against -// the required and enum field patterns: they descend only along the patterns' paths -// of the typed configuration, with plain field access, and check the fields of each -// node on the way. -// -// The typed configuration cannot tell an unset field from one set to its zero value, -// null or a pure variable reference, and it has no locations. The generated code -// asks the node (bundle/config/validate/walk_node.go) for those. +// The walkers generated here replace matching every configuration node against the +// required and enum field patterns: they descend only along the patterns' paths, with +// plain field access, and check each node's fields during a single walk. // tnode is a node of the tree of pattern paths, rooted at config.Root. type tnode struct { typ reflect.Type // the declared type of the value at this node required []string // required fields (JSON names) of the struct at this node enum []string // valid values if the value at this node is an enum + defaults []defaultKey // fields of the struct at this node that have a default children map[string]*tnode sig string } +// defaultKey is a field (JSON name) with a default, the index of the default. +type defaultKey struct { + key string + index int +} + func newTnode(typ reflect.Type) *tnode { return &tnode{typ: typ, children: map[string]*tnode{}} } @@ -52,28 +55,25 @@ func (t *tnode) insert(p string) (*tnode, error) { var next reflect.Type elem := deref(n.typ) switch { - case c.DotStar(): - if elem.Kind() != reflect.Map { - return nil, fmt.Errorf("%s: .* on %s", p, elem) - } + case c.DotStar() || c.BracketStar(): key = "*" - next = elem.Elem() - case c.BracketStar(): - if elem.Kind() != reflect.Slice { - return nil, fmt.Errorf("%s: [*] on %s", p, elem) + if c.BracketStar() { + key = "[*]" + } + if elem.Kind() != reflect.Map && elem.Kind() != reflect.Slice { + return nil, fmt.Errorf("%s: wildcard on %s", p, elem) } - key = "[*]" next = elem.Elem() default: k, ok := c.StringKey() if !ok || elem.Kind() != reflect.Struct { return nil, fmt.Errorf("%s: unsupported component %s on %s", p, c, elem) } - index, ok := fieldIndex(elem, k) + key = k + index, ok := structvar.GetStructInfo(elem).Fields[k] if !ok { return nil, fmt.Errorf("%s: no field %q in %s", p, k, elem) } - key = k next = elem.FieldByIndex(index).Type } child, ok := n.children[key] @@ -86,35 +86,6 @@ func (t *tnode) insert(p string) (*tnode, error) { return n, nil } -// fieldIndex returns the index path of the field with JSON name key in struct st, as -// [convert.ToTyped] resolves it: fields of embedded structs are promoted, and a field -// of an outer struct takes precedence. -func fieldIndex(st reflect.Type, key string) ([]int, bool) { - queue := [][]int{{}} - for i := 0; i < len(queue); i++ { - prefix := queue[i] - typ := st - if len(prefix) > 0 { - typ = deref(st.FieldByIndex(prefix).Type) - } - for j := range typ.NumField() { - sf := typ.Field(j) - index := append(slices.Clone(prefix), j) - if sf.Anonymous { - queue = append(queue, index) - continue - } - if !sf.IsExported() { - continue - } - if structtag.JSONTag(sf.Tag.Get("json")).Name() == key { - return index, true - } - } - } - return nil, false -} - func deref(t reflect.Type) reflect.Type { for t.Kind() == reflect.Pointer { t = t.Elem() @@ -128,7 +99,7 @@ func (t *tnode) signature() string { return t.sig } var b strings.Builder - fmt.Fprintf(&b, "%s|r=%v|e=%v|", t.typ, t.required, t.enum) + fmt.Fprintf(&b, "%s|r=%v|e=%v|d=%v|", t.typ, t.required, t.enum, t.defaults) for _, k := range sortedKeys(t.children) { fmt.Fprintf(&b, "%s{%s}", k, t.children[k].signature()) } @@ -152,24 +123,46 @@ type gen struct { aliases map[string]bool funcs map[string]string // signature -> function name names map[string]int - enums map[reflect.Type]enumVar // enum type -> its valid values + enums map[string]string // joined values -> variable name out bytes.Buffer pending []*tnode + + // pendingTypes are the struct types whose functions are still to be written + // (strings walker). + pendingTypes []reflect.Type + + // walkerType is the name of the type of the walker state (first parameter). + walkerType string + + // refBranches visits nil pointer fields that are present (in ForceSendFields, or + // written without omitempty) or hold a pure reference as nodes without fields, so + // their required fields are reported. + refBranches bool } -func newGen(prefix string) *gen { +func newGen(prefix string, refBranches bool) *gen { return &gen{ - prefix: prefix, - imports: map[string]string{}, - aliases: map[string]bool{"slices": true, "dyn": true}, - funcs: map[string]string{}, - names: map[string]int{}, - enums: map[reflect.Type]enumVar{}, + prefix: prefix, + refBranches: refBranches, + imports: map[string]string{}, + aliases: map[string]bool{"slices": true, "structvar": true, "config": false}, + funcs: map[string]string{}, + names: map[string]int{}, + enums: map[string]string{}, } } -// typeName returns the Go name of the named type t, importing its package. +// typeName returns the Go name of t, importing its package. func (g *gen) typeName(t reflect.Type) string { + switch t.Kind() { + case reflect.Pointer: + return "*" + g.typeName(t.Elem()) + case reflect.Slice: + return "[]" + g.typeName(t.Elem()) + case reflect.Map: + return "map[" + g.typeName(t.Key()) + "]" + g.typeName(t.Elem()) + default: + } if t.PkgPath() == "" { return t.String() } @@ -210,248 +203,266 @@ func capitalize(s string) string { return strings.ToUpper(s[:1]) + s[1:] } -// enumVar is the variable holding the valid values of an enum type. -type enumVar struct { - name string - values []string +func (g *gen) enumVar(values []string) string { + key := strings.Join(values, "\x00") + if name, ok := g.enums[key]; ok { + return name + } + name := fmt.Sprintf("%sValues%d", g.prefix, len(g.enums)) + g.enums[key] = name + return name } -// enumVarFor returns the name of the variable holding the valid values of the enum type -// typ, e.g. enumValuesPostgresPipelineChannel for postgres.PipelineChannel. -func (g *gen) enumVarFor(typ reflect.Type, values []string) (string, error) { - if v, ok := g.enums[typ]; ok { - if !slices.Equal(v.values, values) { - return "", fmt.Errorf("enum type %s has different valid values at different paths", typ) +// selector returns the Go expression selecting the field with the given index path +// from v, and the nil checks of the embedded pointers on the way. +func selector(v string, t reflect.Type, index []int) (string, []string) { + var guards []string + var expr strings.Builder + expr.WriteString(v) + for i, x := range index { + f := deref(t).Field(x) + expr.WriteString("." + f.Name) + if i < len(index)-1 && f.Type.Kind() == reflect.Pointer { + guards = append(guards, expr.String()+" != nil") } - return v.name, nil - } - base := g.prefix + "Values" + capitalize(path.Base(typ.PkgPath())) + typ.Name() - g.names[base]++ - name := base - if g.names[base] > 1 { - name = base + strconv.Itoa(g.names[base]) + t = f.Type } - g.enums[typ] = enumVar{name: name, values: values} - return name, nil + return expr.String(), guards +} + +// zeroIsValid lists the required fields (by struct type) whose zero value is a valid +// value, so it is not reported as missing. Presence comes from ForceSendFields only, and +// these structs have none to record an explicit zero value. +var zeroIsValid = map[reflect.Type]string{ + // An empty default is a common, valid job parameter default. + reflect.TypeFor[jobs.JobParameterDefinition](): "default", } -// field is the field with JSON name key of the struct v points to. +// field describes the field k of the struct type st as seen from variable v. type field struct { - key string - typ reflect.Type - expr string // selector of the field - guards []string // nil checks of the embedded pointers on the way + key, goName string + typ reflect.Type + expr string + guards []string + fsf string // selector of the governing ForceSendFields, if any + forceEmpty bool // the field has no omitempty } -func structField(st reflect.Type, key string) (field, bool) { - index, ok := fieldIndex(st, key) - if !ok { - return field{}, false - } - f := field{key: key, expr: "v"} - t := st - for i, x := range index { - sf := deref(t).Field(x) - f.expr += "." + sf.Name - if i < len(index)-1 && sf.Type.Kind() == reflect.Pointer { - f.guards = append(f.guards, f.expr+" != nil") - } - t = sf.Type - } - f.typ = t - return f, true +func structField(v string, st reflect.Type, k string) field { + info := structvar.GetStructInfo(st) + expr, guards := selector(v, st, info.Fields[k]) + f := field{key: k, goName: info.GolangNames[k], typ: st.FieldByIndex(info.Fields[k]).Type, expr: expr, guards: guards, forceEmpty: info.ForceEmpty[k]} + if index, ok := info.ForceSendFieldsIndex[k]; ok { + f.fsf, _ = selector(v, st, index) + } + return f } -func (f field) guarded(cond string) string { - if len(f.guards) == 0 { +func guarded(guards []string, cond string) string { + if len(guards) == 0 { return cond } - return strings.Join(f.guards, " && ") + " && " + cond + return "(" + strings.Join(guards, " && ") + " && (" + cond + "))" } -// typedSet returns the condition under which the typed value shows the field is set, -// or "" if it cannot (a struct is never nil). A field that is not set by this -// condition can still be set to its zero value, so the node is asked. -func (f field) typedSet() string { +// forcedCond returns the condition that the field is listed in its ForceSendFields. +func (f field) forcedCond() string { + if f.fsf == "" { + return "" + } + return fmt.Sprintf(" || walk.Forced(%s, %q)", f.fsf, f.goName) +} + +// present returns the condition under which a required field counts as set. Presence +// comes from the value and ForceSendFields only, never from locations: a scalar or a +// struct is set if it is not zero or is in ForceSendFields (decoding records a written +// zero value there, e.g. a job parameter's `default: ""`); a pointer, slice, map or +// interface if it is not nil. A value that cannot hold a string can also be replaced +// by a pure reference. +func (f field) present() string { + ref := fmt.Sprintf(" || n.IsRef(%q)", f.key) switch f.typ.Kind() { case reflect.String: - return f.guarded(f.expr + ` != ""`) + return guarded(f.guards, f.expr+` != ""`+f.forcedCond()) case reflect.Pointer, reflect.Slice, reflect.Map, reflect.Interface: - return f.guarded(f.expr + " != nil") + return guarded(f.guards, f.expr+" != nil") + ref case reflect.Bool: - return f.guarded(f.expr) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, - reflect.Float32, reflect.Float64: - return f.guarded(f.expr + " != 0") + return guarded(f.guards, f.expr+f.forcedCond()) + ref + case reflect.Struct: + // A struct whose fields are only references is zero, but set. + return guarded(f.guards, fmt.Sprintf("!walk.IsZeroStruct(&%s)", f.expr)+f.forcedCond()) + fmt.Sprintf(" || n.HasRefs(%q)", f.key) default: - return "" + return guarded(f.guards, f.expr+" != 0"+f.forcedCond()) + ref + } +} + +// isSet returns the condition under which the field is set as [structvar.View.Get] +// finds it valid: like [field.present], and a forced nil pointer, slice or map is also +// set. +func (f field) isSet() string { + switch f.typ.Kind() { + case reflect.Pointer, reflect.Slice, reflect.Map, reflect.Interface: + return guarded(f.guards, f.expr+" != nil"+f.forcedCond()) + fmt.Sprintf(" || n.IsRef(%q)", f.key) + default: + return f.present() } } // emitStruct writes the function walking struct node t. -func (g *gen) emitStruct(t *tnode) error { +func (g *gen) emitStruct(t *tnode) { st := deref(t.typ) - if st.Kind() != reflect.Struct { - return fmt.Errorf("%s is not a struct", st) + fmt.Fprintf(&g.out, "\nfunc %s(w *%s, n *walk.Node, v *%s) {\n", g.funcFor(t), g.walkerType, g.typeName(st)) + fmt.Fprintf(&g.out, "\tif v == nil {\n") + for _, k := range t.required { + fmt.Fprintf(&g.out, "\t\tw.missing(n, %q)\n", k) } - name := g.typeName(st) - fmt.Fprintf(&g.out, "\nfunc %s(w *walker, n *node, v *%s) {\n", g.funcFor(t), name) - // The value is null or a pure variable reference. - fmt.Fprintf(&g.out, "\tif v == nil {\n\t\tv = &%s{}\n\t}\n", name) + fmt.Fprintf(&g.out, "\t\treturn\n\t}\n") for _, k := range t.required { - cond := "" - if f, ok := structField(st, k); ok { - cond = f.typedSet() - } - if cond == "" { - fmt.Fprintf(&g.out, "\tif !n.has(%q) {\n\t\tw.missing(n, %q)\n\t}\n", k, k) - } else { - fmt.Fprintf(&g.out, "\tif !(%s) && !n.has(%q) {\n\t\tw.missing(n, %q)\n\t}\n", cond, k, k) + if zeroIsValid[st] == k { + continue } + f := structField("v", st, k) + fmt.Fprintf(&g.out, "\tif !(%s) {\n\t\tw.missing(n, %q)\n\t}\n", f.present(), k) + } + for _, d := range t.defaults { + f := structField("v", st, d.key) + fmt.Fprintf(&g.out, "\tif !(%s) {\n\t\tc := n.Child(%q)\n\t\tw.unset(&c, %d)\n\t}\n", f.isSet(), d.key, d.index) } for _, k := range sortedKeys(t.children) { - f, ok := structField(st, k) - if !ok { - return fmt.Errorf("no field %q in %s", k, st) - } - if err := g.emitField(f, t.children[k]); err != nil { - return err - } + g.emitField(structField("v", st, k), t.children[k]) } fmt.Fprintf(&g.out, "}\n") - return nil } -// emitField writes the code visiting the field f of the struct v (node n). -func (g *gen) emitField(f field, child *tnode) error { +// emitField writes the code visiting the field f of the struct v. +func (g *gen) emitField(f field, child *tnode) { if len(f.guards) > 0 { fmt.Fprintf(&g.out, "\tif %s {\n", strings.Join(f.guards, " && ")) } switch { case child.enum != nil: - if f.typ.Kind() != reflect.String { - return fmt.Errorf("enum field %q of type %s", f.key, f.typ) - } - values, err := g.enumVarFor(f.typ, child.enum) - if err != nil { - return err - } - fmt.Fprintf(&g.out, "\tw.enumField(n, %q, string(%s), %s)\n", f.key, f.expr, values) + fmt.Fprintf(&g.out, "\tif s := string(%s); s != \"\"%s {\n\t\tc := n.Child(%q)\n\t\tw.enumValue(&c, s, %s)\n\t}\n", f.expr, f.forcedCond(), f.key, g.enumVar(child.enum)) case f.typ.Kind() == reflect.Pointer: - fmt.Fprintf(&g.out, "\tif c := n.child(%q); c.exists() {\n\t\t%s(w, &c, %s)\n\t}\n", f.key, g.funcFor(child), f.expr) + fn := g.funcFor(child) + fmt.Fprintf(&g.out, "\tif x := %s; x != nil {\n\t\tc := n.Child(%q)\n\t\t%s(w, &c, x)\n\t}", f.expr, f.key, fn) + if g.refBranches { + // A forced nil value (e.g. "run_job_task:") or one replaced by a pure + // reference is a node without fields. + cond := strings.TrimPrefix(f.forcedCond()+fmt.Sprintf(" || n.IsRef(%q)", f.key), " || ") + fmt.Fprintf(&g.out, " else if %s {\n\t\tc := n.Child(%q)\n\t\t%s(w, &c, nil)\n\t}", cond, f.key, fn) + } + fmt.Fprintf(&g.out, "\n") case f.typ.Kind() == reflect.Struct: - fmt.Fprintf(&g.out, "\tif c := n.child(%q); c.exists() {\n\t\t%s(w, &c, &%s)\n\t}\n", f.key, g.funcFor(child), f.expr) + fmt.Fprintf(&g.out, "\tif x := &%s; !walk.IsZeroStruct(x)%s || n.HasRefs(%q) {\n\t\tc := n.Child(%q)\n\t\t%s(w, &c, x)\n\t}\n", f.expr, f.forcedCond(), f.key, f.key, g.funcFor(child)) case f.typ.Kind() == reflect.Slice || f.typ.Kind() == reflect.Map: - fmt.Fprintf(&g.out, "\tif len(%s) > 0 {\n\t\tc := n.child(%q)\n", f.expr, f.key) - if err := g.emitContainer("\t\t", f.expr, f.typ, child, 0); err != nil { - return err - } + fmt.Fprintf(&g.out, "\tif len(%s) > 0 {\n\t\tc := n.Child(%q)\n", f.expr, f.key) + g.emitContainer("\t\t", f.expr, f.typ, child) fmt.Fprintf(&g.out, "\t}\n") default: - return fmt.Errorf("unsupported field %q of type %s", f.key, f.typ) + panic(fmt.Sprintf("unsupported field %s of type %s", f.key, f.typ)) } if len(f.guards) > 0 { fmt.Fprintf(&g.out, "\t}\n") } - return nil } // emitContainer writes the loop over the slice or map expr (node c) and its elements. -func (g *gen) emitContainer(indent, expr string, typ reflect.Type, t *tnode, depth int) error { - if len(t.children) != 1 { - return fmt.Errorf("container %s needs exactly one wildcard child", typ) - } +func (g *gen) emitContainer(indent, expr string, typ reflect.Type, t *tnode) { var elem *tnode for _, c := range t.children { elem = c } + if elem == nil || len(t.children) != 1 { + panic(fmt.Sprintf("container %s needs exactly one wildcard child", typ)) + } if typ.Kind() == reflect.Slice { - i := fmt.Sprintf("i%d", depth) - fmt.Fprintf(&g.out, "%sfor %s := range %s {\n%s\te := c.elem(%s)\n", indent, i, expr, indent, i) - if err := g.emitElem(indent+"\t", expr+"["+i+"]", typ.Elem(), elem, depth); err != nil { - return err - } + fmt.Fprintf(&g.out, "%sfor i := range %s {\n%s\te := c.Elem(i)\n", indent, expr, indent) + g.emitElem(indent+"\t", expr+"[i]", typ.Elem(), elem) } else { - k, x := fmt.Sprintf("k%d", depth), fmt.Sprintf("x%d", depth) - key := k - if typ.Key() != reflect.TypeFor[string]() { - key = "string(" + k + ")" - } // The dynamic walk this replaces never matched an empty key against the wildcard. - fmt.Fprintf(&g.out, "%sfor %s, %s := range %s {\n%s\tif %s == \"\" {\n%s\t\tcontinue\n%s\t}\n%s\te := c.child(%s)\n", indent, k, x, expr, indent, key, indent, indent, indent, key) - if err := g.emitElem(indent+"\t", x, typ.Elem(), elem, depth); err != nil { - return err - } + fmt.Fprintf(&g.out, "%sfor k, x := range %s {\n%s\tif k == \"\" {\n%s\t\tcontinue\n%s\t}\n%s\te := c.Child(k)\n", indent, expr, indent, indent, indent, indent) + g.emitElem(indent+"\t", "x", typ.Elem(), elem) } fmt.Fprintf(&g.out, "%s}\n", indent) - return nil } // emitElem writes the code visiting the element expr (node e) of type typ. -func (g *gen) emitElem(indent, expr string, typ reflect.Type, t *tnode, depth int) error { +func (g *gen) emitElem(indent, expr string, typ reflect.Type, t *tnode) { switch { case t.enum != nil: - if typ.Kind() != reflect.String { - return fmt.Errorf("enum element of type %s", typ) - } - values, err := g.enumVarFor(typ, t.enum) - if err != nil { - return err - } - fmt.Fprintf(&g.out, "%sw.enumElem(&e, string(%s), %s)\n", indent, expr, values) + fmt.Fprintf(&g.out, "%sw.enumValue(&e, string(%s), %s)\n", indent, expr, g.enumVar(t.enum)) case typ.Kind() == reflect.Pointer: fmt.Fprintf(&g.out, "%s%s(w, &e, %s)\n", indent, g.funcFor(t), expr) case typ.Kind() == reflect.Struct: fmt.Fprintf(&g.out, "%s%s(w, &e, &%s)\n", indent, g.funcFor(t), expr) case typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map: fmt.Fprintf(&g.out, "%s{\n%s\tc := e\n", indent, indent) - if err := g.emitContainer(indent+"\t", expr, typ, t, depth+1); err != nil { - return err - } + g.emitContainer(indent+"\t", expr, typ, t) fmt.Fprintf(&g.out, "%s}\n", indent) default: - return fmt.Errorf("unsupported element type %s", typ) + panic(fmt.Sprintf("unsupported element type %s", typ)) } - return nil } -// generateWalker writes the walker of the required fields (by parent pattern) and the -// enum values (by field pattern) to file. The entry point is ConfigRoot. -func generateWalker(file, prefix, doc string, required, enums map[string][]string) error { +// generateWalker writes the walker for the patterns to file; fn is the name of the +// entry point. +// walkerSpec describes a generated walker. +type walkerSpec struct { + file, pkg, prefix, doc string + walkerType string + required, enums map[string][]string + defaults []string // patterns of fields with a default, in order +} + +func generateWalker(spec walkerSpec) error { + file, prefix, doc := spec.file, spec.prefix, spec.doc root := newTnode(reflect.TypeFor[config.Root]()) - for _, p := range sortedKeys(required) { + for _, p := range sortedKeys(spec.required) { n, err := root.insert(p) if err != nil { return err } - n.required = required[p] + n.required = spec.required[p] } - for _, p := range sortedKeys(enums) { + for _, p := range sortedKeys(spec.enums) { n, err := root.insert(p) if err != nil { return err } - n.enum = enums[p] + n.enum = spec.enums[p] + } + for i, p := range spec.defaults { + pattern, err := structpath.ParsePattern(p) + if err != nil { + return err + } + key, ok := pattern.StringKey() + if !ok { + return fmt.Errorf("default %s: the last component must be a key", p) + } + n, err := root.insert(pattern.Parent().String()) + if err != nil { + return err + } + n.defaults = append(n.defaults, defaultKey{key, i}) } - g := newGen(prefix) + g := newGen(prefix, spec.required != nil) + g.walkerType = spec.walkerType g.funcFor(root) for len(g.pending) > 0 { t := g.pending[0] g.pending = g.pending[1:] - if err := g.emitStruct(t); err != nil { - return err - } + g.emitStruct(t) } var src bytes.Buffer - fmt.Fprintf(&src, "// Code generated by bundle/internal/validation. DO NOT EDIT.\n\n// %s\n\npackage validate\n\nimport (\n", doc) + fmt.Fprintf(&src, "// Code generated by bundle/internal/validation. DO NOT EDIT.\n\n// %s\n\npackage %s\n\nimport (\n\t\"github.com/databricks/cli/bundle/config/walk\"\n", doc, spec.pkg) paths := make([]string, 0, len(g.imports)) for p := range g.imports { paths = append(paths, p) } - slices.Sort(paths) + slices.SortFunc(paths, cmp.Compare) for _, p := range paths { if path.Base(p) == g.imports[p] { fmt.Fprintf(&src, "\t%q\n", p) @@ -460,49 +471,84 @@ func generateWalker(file, prefix, doc string, required, enums map[string][]strin } } fmt.Fprintf(&src, ")\n") - vars := slices.SortedFunc(maps.Values(g.enums), func(a, b enumVar) int { return cmp.Compare(a.name, b.name) }) - for _, e := range vars { - var quoted []string - for _, value := range e.values { - quoted = append(quoted, strconv.Quote(value)) - } - fmt.Fprintf(&src, "\nvar %s = []string{%s}\n", e.name, strings.Join(quoted, ", ")) + keys := make([]string, 0, len(g.enums)) + for k := range g.enums { + keys = append(keys, k) + } + slices.SortFunc(keys, func(a, b string) int { return cmp.Compare(g.enums[a], g.enums[b]) }) + for _, k := range keys { + values := strings.Split(k, "\x00") + fmt.Fprintf(&src, "\nvar %s = %s\n", g.enums[k], "[]string"+formatSliceToString(values)) } src.Write(g.out.Bytes()) formatted, err := format.Source(src.Bytes()) if err != nil { + _ = os.WriteFile(file+".broken", src.Bytes(), 0o644) return fmt.Errorf("failed to format %s: %w", file, err) } return os.WriteFile(file, formatted, 0o644) } -// withoutTargets drops the patterns under targets and environments: they are merged -// into the root before validation. -func withoutTargets(patterns map[string][]string) map[string][]string { - out := map[string][]string{} - for p, v := range patterns { - top, _, _ := strings.Cut(p, ".") - if top == "targets" || top == "environments" { - continue +// patternMaps returns the required fields by parent pattern and the enum values by +// field pattern, without targets and environments (they are merged into the root). +func patternMaps() (map[string][]string, map[string][]string, error) { + required, err := extractRequiredFields(reflect.TypeFor[config.Root]()) + if err != nil { + return nil, nil, err + } + enums, err := extractEnumFields(reflect.TypeFor[config.Root]()) + if err != nil { + return nil, nil, err + } + skip := func(p string) bool { + k := getGroupingKey(p) + return k == "targets" || k == "environments" + } + r := map[string][]string{} + for p, fields := range required { + if !skip(p) { + r[p] = fields + } + } + e := map[string][]string{} + for p, values := range enums { + if !skip(p) { + e[p] = values } - out[p] = v } - return out + return r, e, nil } func generateWalkers(dir string) error { - required, err := extractRequiredFields(reflect.TypeFor[config.Root]()) + required, enums, err := patternMaps() if err != nil { - return fmt.Errorf("failed to extract required fields: %w", err) + return err } - enums, err := extractEnumFields(reflect.TypeFor[config.Root]()) + err = generateWalker(walkerSpec{ + file: dir + "/required_walk.go", pkg: "validate", prefix: "required", walkerType: "walker", + doc: "requiredWalk reports required fields that are not set.", required: required, + }) if err != nil { - return fmt.Errorf("failed to extract enum fields: %w", err) + return err + } + err = generateWalker(walkerSpec{ + file: dir + "/enum_walk.go", pkg: "validate", prefix: "enum", walkerType: "walker", + doc: "enumWalk reports enum fields with invalid values.", enums: enums, + }) + if err != nil { + return err + } + err = generateWalker(walkerSpec{ + file: "bundle/config/mutator/resourcemutator/defaults_walk.go", pkg: "resourcemutator", prefix: "defaults", walkerType: "defaultsWalker", + doc: "defaultsWalk calls unset for the fields with a default (resourcedefaults.Defaults) that are not set.", defaults: resourcedefaults.Patterns(), + }) + if err != nil { + return err } - err = generateWalker(dir+"/required_walk.go", "required", "requiredConfigRoot reports the required fields that are not set.", withoutTargets(required), nil) + err = generateStringsWalker("bundle/config/walk/strings_walk.go") if err != nil { return err } - return generateWalker(dir+"/enum_walk.go", "enum", "enumConfigRoot reports the enum fields with invalid values.", nil, withoutTargets(enums)) + return generateResourcesIterate("bundle/config/resources_iterate.go") } diff --git a/bundle/libraries/expand_glob_references.go b/bundle/libraries/expand_glob_references.go index df089f880a7..8d6876b2ac5 100644 --- a/bundle/libraries/expand_glob_references.go +++ b/bundle/libraries/expand_glob_references.go @@ -8,31 +8,29 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/patchwheel" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go/service/compute" ) type expand struct{} -func matchError(p dyn.Path, l []diag.Location, message string) diag.Diagnostic { +func matchError(p *structpath.PathNode, l []diag.Location, message string) diag.Diagnostic { return diag.Diagnostic{ Severity: diag.Error, Summary: message, Locations: l, - Paths: dyn.ToStructPaths(p), + Paths: []*structpath.PathNode{p}, } } -func getLibDetails(v dyn.Value) (string, string, bool) { - m := v.MustMap() - whl, ok := m.GetByString("whl") - if ok { - return whl.MustString(), "whl", true +func getLibDetails(lib compute.Library) (string, string, bool) { + if lib.Whl != "" { + return lib.Whl, "whl", true } - jar, ok := m.GetByString("jar") - if ok { - return jar.MustString(), "jar", true + if lib.Jar != "" { + return lib.Jar, "jar", true } return "", "", false @@ -72,48 +70,70 @@ func isGlobPattern(path string) bool { return strings.ContainsAny(path, "*?[") } -func expandLibraries(ctx context.Context, b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diagnostics, []dyn.Value) { - var output []dyn.Value +// expandFunc expands the item at path p into the items it is replaced with. +// If relocate is not empty, it names the field of each new item that takes the locations of the item. +type expandFunc[T any] func(ctx context.Context, b *bundle.Bundle, p *structpath.PathNode, item T) (output []T, relocate string, diags diag.Diagnostics) + +// expandSequence replaces the items of the sequence at path p with the items expandFunc returns for them. +// New items keep the locations of the item they were expanded from. +func expandSequence[T any](ctx context.Context, b *bundle.Bundle, p *structpath.PathNode, items *[]T, fn expandFunc[T]) diag.Diagnostics { var diags diag.Diagnostics + output := make([]T, 0, len(*items)) + var sources [][]int + var relocates []string + var locations [][]diag.Location + for i, item := range *items { + ip := structpath.NewIndex(p, i) + expanded, relocate, d := fn(ctx, b, ip, item) + diags = diags.Extend(d) + locs := b.Config.GetLocationsOfExact(ip) + for _, e := range expanded { + output = append(output, e) + sources = append(sources, []int{i}) + relocates = append(relocates, relocate) + locations = append(locations, locs) + } + } + + *items = output + b.Config.UpdateSequence(p, sources) + for i, relocate := range relocates { + if relocate != "" { + b.Config.SetLocations(structpath.NewPath(p, i, relocate), locations[i]) + } + } - libs := v.MustSequence() - for i, lib := range libs { - lp := p.Append(dyn.Index(i)) + return diags +} + +func expandLibraries(ctx context.Context, b *bundle.Bundle, p *structpath.PathNode, libs *[]compute.Library) diag.Diagnostics { + return expandSequence(ctx, b, p, libs, func(ctx context.Context, b *bundle.Bundle, ip *structpath.PathNode, lib compute.Library) ([]compute.Library, string, diag.Diagnostics) { path, libType, supported := getLibDetails(lib) if !supported || !IsLibraryLocal(path) { - output = append(output, lib) - continue + return []compute.Library{lib}, "", nil } - lp = lp.Append(dyn.Key(libType)) - matches, err := findMatches(ctx, b, path) if err != nil { - diags = diags.Append(matchError(lp, lib.Locations(), err.Error())) - continue + return nil, "", diag.Diagnostics{matchError(structpath.NewStringKey(ip, libType), b.Config.GetLocationsOfExact(ip), err.Error())} } + var output []compute.Library for _, match := range matches { - output = append(output, dyn.NewValue(map[string]dyn.Value{ - libType: dyn.NewValue(match, lib.Locations()), - }, lib.Locations())) + if libType == "whl" { + output = append(output, compute.Library{Whl: match}) + } else { + output = append(output, compute.Library{Jar: match}) + } } - } - - return diags, output + return output, libType, nil + }) } -func expandEnvironmentDeps(ctx context.Context, b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diagnostics, []dyn.Value) { - var output []dyn.Value - var diags diag.Diagnostics - - deps := v.MustSequence() - for i, dep := range deps { - lp := p.Append(dyn.Index(i)) - path := dep.MustString() +func expandEnvironmentDeps(ctx context.Context, b *bundle.Bundle, p *structpath.PathNode, deps *[]string) diag.Diagnostics { + return expandSequence(ctx, b, p, deps, func(ctx context.Context, b *bundle.Bundle, ip *structpath.PathNode, path string) ([]string, string, diag.Diagnostics) { if !IsLibraryLocal(path) { - output = append(output, dep) - continue + return []string{path}, "", nil } // Strip extras before globbing so "[...]" isn't read as a glob class, then re-append. @@ -121,134 +141,41 @@ func expandEnvironmentDeps(ctx context.Context, b *bundle.Bundle, p dyn.Path, v matches, err := findMatches(ctx, b, path) if err != nil { - diags = diags.Append(matchError(lp, dep.Locations(), err.Error())) - continue + return nil, "", diag.Diagnostics{matchError(ip, b.Config.GetLocationsOfExact(ip), err.Error())} } + var output []string for _, match := range matches { - output = append(output, dyn.NewValue(match+extras, dep.Locations())) + output = append(output, match+extras) } - } - - return diags, output -} - -type expandPattern struct { - pattern dyn.Pattern - fn func(ctx context.Context, b *bundle.Bundle, p dyn.Path, v dyn.Value) (diag.Diagnostics, []dyn.Value) + return output, "", nil + }) } -var taskLibrariesPattern = dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("jobs"), - dyn.AnyKey(), - dyn.Key("tasks"), - dyn.AnyIndex(), - dyn.Key("libraries"), -) - -var forEachTaskLibrariesPattern = dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("jobs"), - dyn.AnyKey(), - dyn.Key("tasks"), - dyn.AnyIndex(), - dyn.Key("for_each_task"), - dyn.Key("task"), - dyn.Key("libraries"), -) - -var aiRuntimeCodeSourcePattern = dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("jobs"), - dyn.AnyKey(), - dyn.Key("tasks"), - dyn.AnyIndex(), - dyn.Key("ai_runtime_task"), - dyn.Key("code_source_path"), -) - -var forEachAiRuntimeCodeSourcePattern = dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("jobs"), - dyn.AnyKey(), - dyn.Key("tasks"), - dyn.AnyIndex(), - dyn.Key("for_each_task"), - dyn.Key("task"), - dyn.Key("ai_runtime_task"), - dyn.Key("code_source_path"), -) - -var envDepsPattern = dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("jobs"), - dyn.AnyKey(), - dyn.Key("environments"), - dyn.AnyIndex(), - dyn.Key("spec"), - dyn.Key("dependencies"), -) - -var pipelineEnvDepsPattern = dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("pipelines"), - dyn.AnyKey(), - dyn.Key("environment"), - dyn.Key("dependencies"), -) - -var clusterLibrariesPattern = dyn.NewPattern( - dyn.Key("resources"), - dyn.Key("clusters"), - dyn.AnyKey(), - dyn.Key("libraries"), -) - func (e *expand) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - expanders := []expandPattern{ - { - pattern: taskLibrariesPattern, - fn: expandLibraries, - }, - { - pattern: forEachTaskLibrariesPattern, - fn: expandLibraries, - }, - { - pattern: envDepsPattern, - fn: expandEnvironmentDeps, - }, - { - pattern: pipelineEnvDepsPattern, - fn: expandEnvironmentDeps, - }, - { - pattern: clusterLibrariesPattern, - fn: expandLibraries, - }, - } - var diags diag.Diagnostics - - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - var err error - for _, expander := range expanders { - v, err = dyn.MapByPattern(v, expander.pattern, func(p dyn.Path, lv dyn.Value) (dyn.Value, error) { - d, output := expander.fn(ctx, b, p, lv) - diags = diags.Extend(d) - return dyn.NewValue(output, lv.Locations()), nil - }) - if err != nil { - return dyn.InvalidValue, err - } - } - - return v, nil - }) - if err != nil { - diags = diags.Extend(diag.FromErr(err)) + r := &b.Config.Resources + + // Task libraries, the libraries of tasks in for_each_task, job and pipeline + // environment dependencies, then cluster libraries. + for _, nested := range []bool{false, true} { + r.IterateTaskLibraries(nested, func(p *structpath.PathNode, libs *[]compute.Library) bool { + diags = diags.Extend(expandLibraries(ctx, b, p, libs)) + return true + }) } + r.IterateJobEnvironmentDependencies(func(p *structpath.PathNode, deps *[]string) bool { + diags = diags.Extend(expandEnvironmentDeps(ctx, b, p, deps)) + return true + }) + r.IteratePipelineEnvironmentDependencies(func(p *structpath.PathNode, deps *[]string) bool { + diags = diags.Extend(expandEnvironmentDeps(ctx, b, p, deps)) + return true + }) + r.IterateClusterLibraries(func(p *structpath.PathNode, libs *[]compute.Library) bool { + diags = diags.Extend(expandLibraries(ctx, b, p, libs)) + return true + }) return diags } diff --git a/bundle/libraries/expand_glob_references_test.go b/bundle/libraries/expand_glob_references_test.go index 331fbf2e7cb..037a91a4d32 100644 --- a/bundle/libraries/expand_glob_references_test.go +++ b/bundle/libraries/expand_glob_references_test.go @@ -10,7 +10,7 @@ import ( "github.com/databricks/cli/bundle/internal/bundletest" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/pipelines" @@ -283,11 +283,19 @@ func TestExpandGlobReferencesPreservesLocations(t *testing.T) { diags := bundle.Apply(t.Context(), b, ExpandGlobReferences()) require.Empty(t, diags) - libs, err := dyn.GetByPath(b.Config.Value(), dyn.MustPathFromString("resources.jobs.job.tasks[0].libraries")) - require.NoError(t, err) - assert.Equal(t, loc.File, libs.Location().File) + libs := b.Config.GetLocationsOfExact(structpath.MustParsePath("resources.jobs.job.tasks[0].libraries")) + require.NotEmpty(t, libs) + assert.Equal(t, loc.File, libs[0].File) - deps, err := dyn.GetByPath(b.Config.Value(), dyn.MustPathFromString("resources.pipelines.pipeline.environment.dependencies")) - require.NoError(t, err) - assert.Equal(t, loc.File, deps.Location().File) + lib := b.Config.GetLocationsOfExact(structpath.MustParsePath("resources.jobs.job.tasks[0].libraries[0].whl")) + require.NotEmpty(t, lib) + assert.Equal(t, loc.File, lib[0].File) + + deps := b.Config.GetLocationsOfExact(structpath.MustParsePath("resources.pipelines.pipeline.environment.dependencies")) + require.NotEmpty(t, deps) + assert.Equal(t, loc.File, deps[0].File) + + dep := b.Config.GetLocationsOfExact(structpath.MustParsePath("resources.pipelines.pipeline.environment.dependencies[0]")) + require.NotEmpty(t, dep) + assert.Equal(t, loc.File, dep[0].File) } diff --git a/bundle/libraries/library_values.go b/bundle/libraries/library_values.go new file mode 100644 index 00000000000..81b21a469a9 --- /dev/null +++ b/bundle/libraries/library_values.go @@ -0,0 +1,69 @@ +package libraries + +import ( + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/databricks-sdk-go/service/compute" +) + +// visitLibraryValues calls fn for every library path value of the configuration, in +// this order: the wheels and jars of task libraries, of the libraries of tasks in +// for_each_task and of cluster libraries, the environment dependencies of jobs and of +// pipelines, and (codeSources) the code_source_path of AI Runtime tasks (also nested). +// Empty values are not visited. It stops at the first error fn returns. +func visitLibraryValues(r *config.Resources, codeSources bool, fn func(p *structpath.PathNode, s *string) error) error { + v := &libraryValues{fn: fn} + r.IterateTaskLibraries(false, v.wheels) + r.IterateTaskLibraries(false, v.jars) + r.IterateTaskLibraries(true, v.wheels) + r.IterateTaskLibraries(true, v.jars) + r.IterateClusterLibraries(v.wheels) + r.IterateClusterLibraries(v.jars) + r.IterateJobEnvironmentDependencies(v.dependencies) + r.IteratePipelineEnvironmentDependencies(v.dependencies) + if codeSources { + r.IterateAiRuntimeCodeSources(false, v.value) + r.IterateAiRuntimeCodeSources(true, v.value) + } + return v.err +} + +// libraryValues visits library path values with fn until it returns an error. +type libraryValues struct { + fn func(p *structpath.PathNode, s *string) error + err error +} + +func (v *libraryValues) value(p *structpath.PathNode, s *string) bool { + if v.err == nil && *s != "" { + v.err = v.fn(p, s) + } + return v.err == nil +} + +func (v *libraryValues) wheels(p *structpath.PathNode, libs *[]compute.Library) bool { + for i := range *libs { + if (*libs)[i].Whl != "" && !v.value(structpath.NewPath(p, i, "whl"), &(*libs)[i].Whl) { + return false + } + } + return v.err == nil +} + +func (v *libraryValues) jars(p *structpath.PathNode, libs *[]compute.Library) bool { + for i := range *libs { + if (*libs)[i].Jar != "" && !v.value(structpath.NewPath(p, i, "jar"), &(*libs)[i].Jar) { + return false + } + } + return v.err == nil +} + +func (v *libraryValues) dependencies(p *structpath.PathNode, deps *[]string) bool { + for i := range *deps { + if (*deps)[i] != "" && !v.value(structpath.NewIndex(p, i), &(*deps)[i]) { + return false + } + } + return v.err == nil +} diff --git a/bundle/libraries/remote_path.go b/bundle/libraries/remote_path.go index 70e577a1a2a..796f1a920f6 100644 --- a/bundle/libraries/remote_path.go +++ b/bundle/libraries/remote_path.go @@ -11,8 +11,8 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/patchwheel" + "github.com/databricks/cli/libs/structs/structpath" ) // LocalLibraryPaths returns the local file paths of all libraries in the bundle @@ -101,25 +101,20 @@ func ReplaceWithRemotePath(ctx context.Context, b *bundle.Bundle) (map[string][] sources := slices.Sorted(maps.Keys(libs)) // Update all the config paths to point to the uploaded location - err = b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - for _, source := range sources { - locations := libs[source] - remotePath := path.Join(uploadPath, filepath.Base(source)) - - for _, location := range locations { - // Re-append the extras suffix that was stripped before upload. - remotePathWithExtras := remotePath + location.extras - v, err = dyn.SetByPath(v, location.configPath, dyn.NewValue(remotePathWithExtras, []diag.Location{location.location})) - if err != nil { - return v, fmt.Errorf("internal error: failed to update path %#v to %#v: %w", source, remotePathWithExtras, err) - } + for _, source := range sources { + locations := libs[source] + remotePath := path.Join(uploadPath, filepath.Base(source)) + + for _, location := range locations { + // Re-append the extras suffix that was stripped before upload. + remotePathWithExtras := remotePath + location.extras + err = b.Config.Set(location.configPath, remotePathWithExtras) + if err != nil { + diags = diags.Extend(diag.FromErr(fmt.Errorf("internal error: failed to update path %#v to %#v: %w", source, remotePathWithExtras, err))) + return libs, diags } + b.Config.SetLocations(location.configPath, []diag.Location{location.location}) } - - return v, nil - }) - if err != nil { - diags = diags.Extend(diag.FromErr(err)) } return libs, diags @@ -134,94 +129,51 @@ func ReplaceWithRemotePath(ctx context.Context, b *bundle.Bundle) (map[string][] func collectLocalLibraries(b *bundle.Bundle) (map[string][]LocationToUpdate, error) { libs := make(map[string]([]LocationToUpdate)) - patterns := []dyn.Pattern{ - taskLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("whl")), - taskLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("jar")), - forEachTaskLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("whl")), - forEachTaskLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("jar")), - clusterLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("whl")), - clusterLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("jar")), - envDepsPattern.Append(dyn.AnyIndex()), - pipelineEnvDepsPattern.Append(dyn.AnyIndex()), - // The AI Runtime task's code_source_path is a local archive (typically an - // artifact-built .tar.gz) that must be uploaded and referenced by its remote - // path, exactly like a wheel or jar library. - aiRuntimeCodeSourcePattern, - forEachAiRuntimeCodeSourcePattern, - } - - for _, pattern := range patterns { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.MapByPattern(v, pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - source, ok := v.AsString() - if !ok { - return v, fmt.Errorf("expected string, got %s", v.Kind()) - } - - if !IsLibraryLocal(source) { - return v, nil - } - - // Split off any pip extras suffix so the upload targets the real - // file; the suffix is re-appended to the remote path afterwards. - source, extras := patchwheel.SplitWheelExtras(source) + // The AI Runtime task's code_source_path is a local archive (typically an + // artifact-built .tar.gz) that must be uploaded and referenced by its remote path, + // exactly like a wheel or jar library. + err := visitLibraryValues(&b.Config.Resources, true, func(p *structpath.PathNode, s *string) error { + source := *s + if !IsLibraryLocal(source) { + return nil + } - source = filepath.Join(b.SyncRootPath, source) - libs[source] = append(libs[source], LocationToUpdate{ - configPath: p, - location: v.Location(), - extras: extras, - }) + // Split off any pip extras suffix so the upload targets the real + // file; the suffix is re-appended to the remote path afterwards. + source, extras := patchwheel.SplitWheelExtras(source) - return v, nil - }) + source = filepath.Join(b.SyncRootPath, source) + libs[source] = append(libs[source], LocationToUpdate{ + configPath: p, + location: b.Config.GetLocationOfExact(p), + extras: extras, }) - if err != nil { - return nil, err - } + return nil + }) + if err != nil { + return nil, err } - artifactPattern := dyn.NewPattern( - dyn.Key("artifacts"), - dyn.AnyKey(), - dyn.Key("files"), - dyn.AnyIndex(), - ) - - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.MapByPattern(v, artifactPattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - file, ok := v.AsMap() - if !ok { - return v, fmt.Errorf("expected map, got %s", v.Kind()) - } - - sv, ok := file.GetByString("source") - if !ok { - return v, nil - } - - source, ok := sv.AsString() - if !ok { - return v, fmt.Errorf("expected string, got %s", v.Kind()) + for _, name := range slices.Sorted(maps.Keys(b.Config.Artifacts)) { + a := b.Config.Artifacts[name] + if a == nil { + continue + } + for i, f := range a.Files { + p := structpath.NewPath(nil, "artifacts", name, "files", i) + // An empty source is missing (ArtifactFile has no ForceSendFields). + if f.Source == "" { + continue } - - if sv, ok = file.GetByString("patched"); ok { - patched, ok := sv.AsString() - if ok && patched != "" { - source = patched - } + source := f.Source + if f.Patched != "" { + source = f.Patched } - libs[source] = append(libs[source], LocationToUpdate{ - configPath: p.Append(dyn.Key("remote_path")), - location: v.Location(), + configPath: structpath.NewStringKey(p, "remote_path"), + location: b.Config.GetLocationOfExact(p), }) - - return v, nil - }) - }) - if err != nil { - return nil, err + } } return libs, nil diff --git a/bundle/libraries/same_name_libraries.go b/bundle/libraries/same_name_libraries.go index 722d42c7f64..bead58bce36 100644 --- a/bundle/libraries/same_name_libraries.go +++ b/bundle/libraries/same_name_libraries.go @@ -7,23 +7,11 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" ) type checkForSameNameLibraries struct{} -var patterns = []dyn.Pattern{ - taskLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("whl")), - taskLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("jar")), - forEachTaskLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("whl")), - forEachTaskLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("jar")), - clusterLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("whl")), - clusterLibrariesPattern.Append(dyn.AnyIndex(), dyn.Key("jar")), - envDepsPattern.Append(dyn.AnyIndex()), - pipelineEnvDepsPattern.Append(dyn.AnyIndex()), -} - type libData struct { fullPath string locations []diag.Location @@ -35,49 +23,33 @@ func (c checkForSameNameLibraries) Apply(ctx context.Context, b *bundle.Bundle) var diags diag.Diagnostics libs := make(map[string]*libData) - err := b.Config.Mutate(func(rootConfig dyn.Value) (dyn.Value, error) { - var err error - for _, pattern := range patterns { - rootConfig, err = dyn.MapByPattern(rootConfig, pattern, func(p dyn.Path, libraryValue dyn.Value) (dyn.Value, error) { - libPath, ok := libraryValue.AsString() - if !ok { - return libraryValue, nil - } - - // If not local library, skip the check - if !IsLibraryLocal(libPath) { - return libraryValue, nil - } - - lib := filepath.Base(libPath) - // If the same basename was seen already but full path is different - // then it's a duplicate. Add the location to the location list. - lp, ok := libs[lib] - if !ok { - libs[lib] = &libData{ - fullPath: libPath, - locations: []diag.Location{libraryValue.Location()}, - paths: dyn.ToStructPaths(p), - otherPaths: []string{}, - } - } else if lp.fullPath != libPath { - lp.locations = append(lp.locations, libraryValue.Location()) - lp.paths = append(lp.paths, dyn.ToStructPath(p)) - lp.otherPaths = append(lp.otherPaths, libPath) - } + err := visitLibraryValues(&b.Config.Resources, false, func(p *structpath.PathNode, s *string) error { + libPath := *s - return libraryValue, nil - }) - if err != nil { - return dyn.InvalidValue, err - } + // If not local library, skip the check + if !IsLibraryLocal(libPath) { + return nil } - if err != nil { - return dyn.InvalidValue, err + lib := filepath.Base(libPath) + loc := b.Config.GetLocationOfExact(p) + // If the same basename was seen already but full path is different + // then it's a duplicate. Add the location to the location list. + lp, ok := libs[lib] + if !ok { + libs[lib] = &libData{ + fullPath: libPath, + locations: []diag.Location{loc}, + paths: []*structpath.PathNode{p}, + otherPaths: []string{}, + } + } else if lp.fullPath != libPath { + lp.locations = append(lp.locations, loc) + lp.paths = append(lp.paths, p) + lp.otherPaths = append(lp.otherPaths, libPath) } - return rootConfig, nil + return nil }) // Iterate over all the libraries and check if there are any duplicates. diff --git a/bundle/libraries/upload.go b/bundle/libraries/upload.go index eeb71cfb9e8..7c7087aca36 100644 --- a/bundle/libraries/upload.go +++ b/bundle/libraries/upload.go @@ -12,10 +12,10 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/filer" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/retry" + "github.com/databricks/cli/libs/structs/structpath" "golang.org/x/sync/errgroup" ) @@ -37,7 +37,7 @@ type upload struct { } type LocationToUpdate struct { - configPath dyn.Path + configPath *structpath.PathNode location diag.Location // extras is the pip extras suffix (e.g. "[train]") to re-append to the // rewritten remote path. Empty for libraries that carry no extras. diff --git a/bundle/migrate/build_state.go b/bundle/migrate/build_state.go index 62d68905dc4..f665bac96d7 100644 --- a/bundle/migrate/build_state.go +++ b/bundle/migrate/build_state.go @@ -14,8 +14,6 @@ import ( "github.com/databricks/cli/bundle/direct" "github.com/databricks/cli/bundle/direct/dresources" "github.com/databricks/cli/bundle/direct/dstate" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/structs/structaccess" "github.com/databricks/cli/libs/structs/structpath" @@ -58,25 +56,7 @@ func BuildStateFromTF( ) (bool, error) { warningsSeen := false // Collect all resource nodes (same patterns as makePlan). - var nodes []string - patterns := []dyn.Pattern{ - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey(), dyn.Key("permissions")), - dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey(), dyn.Key("grants")), - } - for _, pat := range patterns { - _, err := dyn.MapByPattern( - configRoot.Value(), - pat, - func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - nodes = append(nodes, p.String()) - return dyn.InvalidValue, nil - }, - ) - if err != nil { - return warningsSeen, err - } - } + nodes := configRoot.ResourceNodes() for _, node := range nodes { id, ok := tfIDs[node] @@ -113,7 +93,7 @@ func BuildStateFromTF( return warningsSeen, fmt.Errorf("%s: PrepareState: %w", node, err) } - refs, err := direct.ExtractReferences(configRoot.Value(), node, adapter.StateType()) + refs, err := direct.ExtractReferences(configRoot, node, adapter.StateType()) if err != nil { return warningsSeen, fmt.Errorf("%s: extracting references: %w", node, err) } @@ -126,12 +106,12 @@ func BuildStateFromTF( // Same logic as makePlan in bundle/direct/bundle_plan.go. var dependsOn []deployplan.DependsOnEntry //nolint:prealloc for _, refTemplate := range refs { - ref, ok := dynvar.NewRef(dyn.V(refTemplate)) + ref, ok := structvar.NewRef(refTemplate) if !ok { continue } for _, targetPath := range ref.References() { - targetPathParsed, err := dyn.NewPathFromString(targetPath) + targetPathParsed, err := structpath.ParsePath(targetPath) if err != nil { continue } @@ -272,18 +252,18 @@ func BuildStateFromTF( // referencesUndeployed reports whether refTemplate references a resource that is not in the terraform state. func referencesUndeployed(refTemplate string, tfIDs map[string]string) bool { - ref, ok := dynvar.NewRef(dyn.V(refTemplate)) + ref, ok := structvar.NewRef(refTemplate) if !ok { return false } for _, targetPath := range ref.References() { - path, err := dyn.NewPathFromString(targetPath) + path, err := structpath.ParsePath(targetPath) if err != nil { continue } targetNode, _ := config.GetNodeAndType(path) // Not a resource reference (e.g. ${var.x}). - if len(targetNode) == 0 { + if targetNode == nil { continue } if _, ok := tfIDs[targetNode.String()]; !ok { diff --git a/bundle/migrate/build_state_test.go b/bundle/migrate/build_state_test.go index 108c0591968..c1e97c37f17 100644 --- a/bundle/migrate/build_state_test.go +++ b/bundle/migrate/build_state_test.go @@ -1,7 +1,6 @@ package migrate_test import ( - "bytes" "encoding/json" "os" "path/filepath" @@ -12,24 +11,18 @@ import ( "github.com/databricks/cli/bundle/direct/dresources" "github.com/databricks/cli/bundle/direct/dstate" "github.com/databricks/cli/bundle/migrate" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/yamlloader" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // rootFromYAML builds a config.Root from a YAML snippet. // Template strings like "${resources.jobs.src.name}" are preserved in the -// internal dyn.Value so BuildStateFromTF can find them via ExtractReferences. +// configuration so BuildStateFromTF can find them via ExtractReferences. func rootFromYAML(t *testing.T, yaml string) config.Root { t.Helper() - v, err := yamlloader.LoadYAML("test", bytes.NewBufferString(yaml)) - require.NoError(t, err) - var root config.Root - require.NoError(t, convert.ToTyped(&root, v)) - require.NoError(t, root.Mutate(func(_ dyn.Value) (dyn.Value, error) { return v, nil })) - return root + root, diags := config.LoadFromBytes("test", []byte(yaml)) + require.NoError(t, diags.Error()) + return *root } func runBuildStateFromTF( diff --git a/bundle/migrate/resolve.go b/bundle/migrate/resolve.go index 23d0180e3c6..41e09176e47 100644 --- a/bundle/migrate/resolve.go +++ b/bundle/migrate/resolve.go @@ -5,16 +5,15 @@ import ( "fmt" "strings" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) // evaluateTemplate evaluates a template string like "${resources.pipelines.bar.cluster[0].label}" // by looking up each ${...} reference from TF state. func evaluateTemplate(state TFStateAttrs, template string) (string, error) { - ref, ok := dynvar.NewRef(dyn.V(template)) + ref, ok := structvar.NewRef(template) if !ok { return template, nil } diff --git a/bundle/mutator.go b/bundle/mutator.go index 41ef8fda48c..ed9bd5e56e5 100644 --- a/bundle/mutator.go +++ b/bundle/mutator.go @@ -75,19 +75,6 @@ func ApplyContext(ctx context.Context, b *Bundle, m Mutator) { log.Debugf(ctx, "Apply") - err := b.Config.MarkMutatorEntry(ctx) - if err != nil { - logdiag.LogError(ctx, fmt.Errorf("entry error: %w", err)) - return - } - - defer func() { - err := b.Config.MarkMutatorExit(ctx) - if err != nil { - logdiag.LogError(ctx, fmt.Errorf("exit error: %w", err)) - } - }() - diags := m.Apply(ctx, b) for _, d := range diags { @@ -104,34 +91,6 @@ func ApplySeqContext(ctx context.Context, b *Bundle, mutators ...Mutator) { } } -// ApplySeqInScopeContext applies mutators without opening a mutator scope per mutator, -// reusing the caller's scope instead. -// -// [ApplyContext] converts the whole configuration tree between its typed and dynamic -// representations on entry and exit (see [config.Root.MarkMutatorEntry]). That cost is -// proportional to the size of the accumulated configuration, so applying N mutators this -// way is quadratic in N. For a bundle with thousands of included files that dominates -// load time, hence this variant. -// -// Only use it for mutators that modify the configuration through [config.Root.Mutate] -// (which keeps both representations in sync). A mutator that assigns to a typed field -// directly relies on the scope entry to carry that value into the dynamic tree, and -// would lose it here. -func ApplySeqInScopeContext(ctx context.Context, b *Bundle, mutators ...Mutator) { - for _, m := range mutators { - mctx := log.NewContext(ctx, log.GetLogger(ctx).With("mutator", m.Name())) - log.Debugf(mctx, "Apply") - - for _, d := range m.Apply(mctx, b) { - logdiag.LogDiag(mctx, d) - } - - if logdiag.HasError(ctx) { - break - } - } -} - type funcMutator struct { fn func(context.Context, *Bundle) } diff --git a/bundle/mutator_test.go b/bundle/mutator_test.go index 45473634119..bd98f835c76 100644 --- a/bundle/mutator_test.go +++ b/bundle/mutator_test.go @@ -5,10 +5,7 @@ import ( "testing" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/logdiag" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) type testMutator struct { @@ -52,79 +49,6 @@ func TestMutator(t *testing.T) { assert.Equal(t, 1, nested[1].applyCalled) } -// ApplySeqInScopeContext applies each mutator once, in order. -func TestApplySeqInScopeContext(t *testing.T) { - var order []string - makeMutator := func(name string) *testMutator { - return &testMutator{fn: func(ctx context.Context, b *Bundle) diag.Diagnostics { - order = append(order, name) - return nil - }} - } - first := makeMutator("first") - second := makeMutator("second") - - b := &Bundle{} - ctx := logdiag.InitContext(t.Context()) - logdiag.SetCollect(ctx, true) - ApplySeqInScopeContext(ctx, b, first, second) - - assert.Equal(t, 1, first.applyCalled) - assert.Equal(t, 1, second.applyCalled) - assert.Equal(t, []string{"first", "second"}, order) - assert.Empty(t, logdiag.FlushCollected(ctx)) -} - -// ApplySeqInScopeContext collects the diagnostics returned by each mutator and -// stops at the first one that logs an error, without applying later mutators. -func TestApplySeqInScopeContextStopsOnError(t *testing.T) { - failing := &testMutator{fn: func(ctx context.Context, b *Bundle) diag.Diagnostics { - return diag.Diagnostics{diag.Diagnostic{Severity: diag.Error, Summary: "boom"}} - }} - later := &testMutator{} - - b := &Bundle{} - ctx := logdiag.InitContext(t.Context()) - logdiag.SetCollect(ctx, true) - ApplySeqInScopeContext(ctx, b, failing, later) - - assert.Equal(t, 1, failing.applyCalled) - assert.Equal(t, 0, later.applyCalled, "mutator after an error must not run") - - diags := logdiag.FlushCollected(ctx) - require.Len(t, diags, 1) - assert.Equal(t, "boom", diags[0].Summary) -} - -// Unlike ApplySeqContext, ApplySeqInScopeContext does not open a mutator scope per -// mutator, so it must be called from within one. Changes made through Root.Mutate -// keep the typed and dynamic configuration in sync and survive without a per-mutator -// scope. This is the property ProcessRootIncludes relies on. -func TestApplySeqInScopeContextPreservesMutateChanges(t *testing.T) { - setHost := &testMutator{fn: func(ctx context.Context, b *Bundle) diag.Diagnostics { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "bundle", dyn.V(map[string]dyn.Value{"name": dyn.V("set-in-scope")})) - }) - require.NoError(t, err) - return nil - }} - - // Enclosing mutator that applies setHost in its own scope, mirroring how - // ProcessRootIncludes applies the per-file includes. - outer := &testMutator{fn: func(ctx context.Context, b *Bundle) diag.Diagnostics { - ApplySeqInScopeContext(ctx, b, setHost) - return nil - }} - - b := &Bundle{} - diags := Apply(t.Context(), b, outer) - require.NoError(t, diags.Error()) - - // Visible in both representations once the enclosing scope exits. - assert.Equal(t, "set-in-scope", b.Config.Bundle.Name) - assert.Equal(t, "set-in-scope", b.Config.Value().Get("bundle").Get("name").MustString()) -} - func TestSafeMutatorName(t *testing.T) { tests := []struct { name string @@ -136,11 +60,6 @@ func TestSafeMutatorName(t *testing.T) { mutator: funcMutator{fn: nil}, expected: "bundle.(funcMutator)", }, - { - name: "setDefault mutator", - mutator: SetDefaultMutator(dyn.NewPattern(dyn.Key("test")), "key", "value"), - expected: "bundle.(setDefault)", - }, { name: "funcMutator as pointer", mutator: &funcMutator{fn: nil}, diff --git a/bundle/phases/plan.go b/bundle/phases/plan.go index 4be81a102d2..5482b354061 100644 --- a/bundle/phases/plan.go +++ b/bundle/phases/plan.go @@ -13,7 +13,7 @@ import ( "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct/dresources" "github.com/databricks/cli/bundle/statemgmt" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) // PreDeployChecks is common set of mutators between "bundle plan" and "bundle deploy". @@ -52,30 +52,30 @@ func pipelineDeletionCascades(b *bundle.Bundle, action deployplan.Action) (bool, // checkForPreventDestroy checks if the resource has lifecycle.prevent_destroy set, but the plan calls for this resource to be recreated or destroyed. // If it does, it returns an error. func checkForPreventDestroy(b *bundle.Bundle, actions []deployplan.Action) error { - root := b.Config.Value() var errs []error for _, action := range actions { if action.ActionType != deployplan.Recreate && action.ActionType != deployplan.Delete { continue } - path, err := dyn.NewPathFromString(action.ResourceKey) + path, err := structpath.ParsePath(action.ResourceKey) if err != nil { return fmt.Errorf("failed to parse %q", action.ResourceKey) } - path = append(path, dyn.Key("lifecycle"), dyn.Key("prevent_destroy")) - - preventDestroyV, err := dyn.GetByPath(root, path) - if err != nil { + // Only resources have a lifecycle (not e.g. resources.jobs.foo.permissions). + if path.Len() != 3 { continue } - - preventDestroy, ok := preventDestroyV.AsBool() + res, ok := b.Config.Resources.GetResource(path.KeyAt(1), path.KeyAt(2)) if !ok { + continue + } + + if b.Config.IsReferenceAt(structpath.NewPath(path, "lifecycle", "prevent_destroy")) { return fmt.Errorf("internal error: prevent_destroy is not a boolean for %s", action.ResourceKey) } - if preventDestroy { + if res.GetLifecycle().HasPreventDestroy() { errs = append(errs, fmt.Errorf("%s has lifecycle.prevent_destroy set, but the plan calls for this resource to be recreated or destroyed. To avoid this error, disable lifecycle.prevent_destroy for %s", action.ResourceKey, action.ResourceKey)) } } diff --git a/bundle/phases/plan_test.go b/bundle/phases/plan_test.go index 772f7401f9b..6063cc6a0bd 100644 --- a/bundle/phases/plan_test.go +++ b/bundle/phases/plan_test.go @@ -7,9 +7,10 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct/dstate" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -77,20 +78,14 @@ func TestPipelineDeletionCascades(t *testing.T) { func TestCheckPreventDestroyForAllResources(t *testing.T) { for resourceType := range config.SupportedResources() { + // Snapshots are internal and their lifecycle is not part of the configuration (json:"-"). + if resourceType == "internal_immutable_snapshots" { + continue + } t.Run(resourceType, func(t *testing.T) { b := &bundle.Bundle{} - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources", dyn.NewValue(map[string]dyn.Value{ - resourceType: dyn.NewValue(map[string]dyn.Value{ - "test_resource": dyn.NewValue(map[string]dyn.Value{ - "lifecycle": dyn.NewValue(map[string]dyn.Value{ - "prevent_destroy": dyn.NewValue(true, nil), - }, nil), - }, nil), - }, nil), - }, nil)) - }) + err := b.Config.Set(structpath.MustParsePath("resources."+resourceType+".test_resource.lifecycle.prevent_destroy"), true) require.NoError(t, err) actions := []deployplan.Action{ @@ -111,17 +106,7 @@ func TestCheckPreventDestroyForAllResources(t *testing.T) { func TestCheckPreventDestroyForJob(t *testing.T) { b := &bundle.Bundle{} - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources", dyn.NewValue(map[string]dyn.Value{ - "jobs": dyn.NewValue(map[string]dyn.Value{ - "test_resource": dyn.NewValue(map[string]dyn.Value{ - "lifecycle": dyn.NewValue(map[string]dyn.Value{ - "prevent_destroy": dyn.NewValue(true, nil), - }, nil), - }, nil), - }, nil), - }, nil)) - }) + err := b.Config.Set(structpath.MustParsePath("resources.jobs.test_resource.lifecycle.prevent_destroy"), true) require.NoError(t, err) actions := []deployplan.Action{ @@ -140,17 +125,7 @@ func TestCheckPreventDestroyForJob(t *testing.T) { func TestCheckPreventDestroyForApp(t *testing.T) { b := &bundle.Bundle{} - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources", dyn.NewValue(map[string]dyn.Value{ - "apps": dyn.NewValue(map[string]dyn.Value{ - "test_resource": dyn.NewValue(map[string]dyn.Value{ - "lifecycle": dyn.NewValue(map[string]dyn.Value{ - "prevent_destroy": dyn.NewValue(true, nil), - }, nil), - }, nil), - }, nil), - }, nil)) - }) + err := b.Config.Set(structpath.MustParsePath("resources.apps.test_resource.lifecycle.prevent_destroy"), true) require.NoError(t, err) actions := []deployplan.Action{ @@ -167,14 +142,7 @@ func TestCheckPreventDestroyForApp(t *testing.T) { func TestCheckPreventDestroyNoError(t *testing.T) { b := &bundle.Bundle{} - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources", dyn.NewValue(map[string]dyn.Value{ - "jobs": dyn.NewValue(map[string]dyn.Value{ - "test_resource": dyn.NewValue(map[string]dyn.Value{}, nil), - }, nil), - }, nil)) - }) - require.NoError(t, err) + b.Config.Resources.Jobs = map[string]*resources.Job{"test_resource": {}} actions := []deployplan.Action{ { @@ -183,26 +151,14 @@ func TestCheckPreventDestroyNoError(t *testing.T) { }, } - err = checkForPreventDestroy(b, actions) + err := checkForPreventDestroy(b, actions) require.NoError(t, err) } func TestCheckForPreventDestroyWhenFirstHasNoPreventDestroy(t *testing.T) { b := &bundle.Bundle{} - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.Set(v, "resources", dyn.NewValue(map[string]dyn.Value{ - "jobs": dyn.NewValue(map[string]dyn.Value{ - "test_job": dyn.NewValue(map[string]dyn.Value{}, nil), - }, nil), - "apps": dyn.NewValue(map[string]dyn.Value{ - "test_app": dyn.NewValue(map[string]dyn.Value{ - "lifecycle": dyn.NewValue(map[string]dyn.Value{ - "prevent_destroy": dyn.NewValue(true, nil), - }, nil), - }, nil), - }, nil), - }, nil)) - }) + b.Config.Resources.Jobs = map[string]*resources.Job{"test_job": {}} + err := b.Config.Set(structpath.MustParsePath("resources.apps.test_app.lifecycle.prevent_destroy"), true) require.NoError(t, err) actions := []deployplan.Action{ diff --git a/bundle/phases/telemetry.go b/bundle/phases/telemetry.go index 99464f3ef24..87669cbc27f 100644 --- a/bundle/phases/telemetry.go +++ b/bundle/phases/telemetry.go @@ -14,8 +14,6 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/bundle/metrics" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/telemetry" "github.com/databricks/cli/libs/telemetry/protos" "github.com/databricks/databricks-sdk-go/service/jobs" @@ -201,13 +199,10 @@ func LogDeployTelemetry(ctx context.Context, b *bundle.Bundle, errMsg string) { b.Metrics.SetBoolValue(metrics.DeploymentHistoryEnabled, b.DeploymentBundle.StateDB.IsDeploymentMetadataService()) resourcesCount := int64(0) - _, err := dyn.MapByPattern(b.Config.Value(), dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), func(p dyn.Path, v dyn.Value) (dyn.Value, error) { + b.Config.Resources.IterateAllResources(func(string, string, config.ConfigResource) bool { resourcesCount++ - return v, nil + return true }) - if err != nil { - log.Debugf(ctx, "failed to count resources: %s", err) - } var jobsIds []string for _, job := range b.Config.Resources.Jobs { diff --git a/bundle/set_default.go b/bundle/set_default.go deleted file mode 100644 index 3100f67d57c..00000000000 --- a/bundle/set_default.go +++ /dev/null @@ -1,64 +0,0 @@ -package bundle - -import ( - "context" - "fmt" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/logdiag" -) - -type setDefault struct { - pattern dyn.Pattern - key dyn.Path - value any -} - -func SetDefaultMutator(pattern dyn.Pattern, key string, value any) Mutator { - return &setDefault{ - pattern: pattern, - key: dyn.NewPath(dyn.Key(key)), - value: value, - } -} - -func (m *setDefault) Name() string { - return fmt.Sprintf("SetDefaultMutator(%v, %v, %v)", m.pattern, m.key, m.value) -} - -func (m *setDefault) Apply(ctx context.Context, b *Bundle) diag.Diagnostics { - err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { - return dyn.MapByPattern(v, m.pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - _, err := dyn.GetByPath(v, m.key) - switch { - case dyn.IsNoSuchKeyError(err): - return dyn.SetByPath(v, m.key, dyn.V(m.value)) - default: - return v, err - } - }) - }) - if err != nil { - return diag.FromErr(err) - } - - return nil -} - -func SetDefault(ctx context.Context, b *Bundle, pattern string, value any) { - pat, err := dyn.NewPatternFromString(pattern) - if err != nil { - logdiag.LogError(ctx, fmt.Errorf("internal error: invalid pattern: %s: %w", pattern, err)) - return - } - - pat, key := pat.SplitKey() - if pat == nil || key == "" { - logdiag.LogError(ctx, fmt.Errorf("internal error: invalid pattern: %s", pattern)) - return - } - - m := SetDefaultMutator(pat, key, value) - ApplyContext(ctx, b, m) -} diff --git a/bundle/statemgmt/direct_migration.go b/bundle/statemgmt/direct_migration.go index f3d632f138f..7ad17b780ab 100644 --- a/bundle/statemgmt/direct_migration.go +++ b/bundle/statemgmt/direct_migration.go @@ -21,7 +21,6 @@ import ( "github.com/databricks/cli/bundle/metrics" "github.com/databricks/cli/bundle/migrate" "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" @@ -269,19 +268,11 @@ func convertTFStateToDirect(ctx context.Context, b *bundle.Bundle, tfState *migr // The config may use terraform-style references (${databricks_pipeline.x.id}). // BuildStateFromTF expects ${resources.*} references, so rewrite them first. - uninterpolatedRoot, err := reverseInterpolate(b.Config.Value()) + uninterpolatedConfig, err := reverseInterpolateConfig(&b.Config) if err != nil { return tempStatePath, false, nil, fmt.Errorf("failed to reverse interpolation: %w", err) } - var uninterpolatedConfig config.Root - err = uninterpolatedConfig.Mutate(func(_ dyn.Value) (dyn.Value, error) { - return uninterpolatedRoot, nil - }) - if err != nil { - return tempStatePath, false, nil, fmt.Errorf("failed to create uninterpolated config: %w", err) - } - adapters, err := dresources.InitAll(nil) if err != nil { return tempStatePath, false, nil, err @@ -296,7 +287,7 @@ func convertTFStateToDirect(ctx context.Context, b *bundle.Bundle, tfState *migr } // warnPrefix labels the conversion's warnings as coming from the background dry run. - hasWarnings, err := migrate.BuildStateFromTF(ctx, &uninterpolatedConfig, adapters, &stateDB, tfState.Attrs, tfState.IDs, warnPrefix) + hasWarnings, err := migrate.BuildStateFromTF(ctx, uninterpolatedConfig, adapters, &stateDB, tfState.Attrs, tfState.IDs, warnPrefix) if err != nil { return tempStatePath, hasWarnings, nil, err } @@ -310,5 +301,5 @@ func convertTFStateToDirect(ctx context.Context, b *bundle.Bundle, tfState *migr return tempStatePath, hasWarnings, nil, errors.New("state conversion failed") } - return tempStatePath, hasWarnings, &uninterpolatedConfig, nil + return tempStatePath, hasWarnings, uninterpolatedConfig, nil } diff --git a/bundle/statemgmt/reverse_interpolate.go b/bundle/statemgmt/reverse_interpolate.go index d133c0e0f7d..ce80662b00c 100644 --- a/bundle/statemgmt/reverse_interpolate.go +++ b/bundle/statemgmt/reverse_interpolate.go @@ -3,34 +3,103 @@ package statemgmt import ( "fmt" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/walk" "github.com/databricks/cli/bundle/deploy/terraform" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" ) -// reverseInterpolate converts terraform-style resource references to bundle-style. +// reverseInterpolate converts terraform-style resource references in s to bundle-style. // Example: ${databricks_pipeline.my_etl.id} → ${resources.pipelines.my_etl.id} -func reverseInterpolate(root dyn.Value) (dyn.Value, error) { - return dynvar.Resolve(root, func(path dyn.Path) (dyn.Value, error) { +func reverseInterpolate(s string) (string, error) { + out, err := structvar.Resolve(map[string]structvar.Template{"": {Value: s}}, func(sp *structpath.PathNode) (structvar.View, error) { // Need at least 2 components: resource_type.resource_name - if len(path) < 2 { - return dyn.InvalidValue, dynvar.ErrSkipResolution + if sp.Len() < 2 { + return structvar.View{}, structvar.ErrSkipResolution } - resourceType := path[0].Key() + resourceType := sp.KeyAt(0) isAlreadyBundleFormat := resourceType == "resources" if isAlreadyBundleFormat { - return dyn.InvalidValue, dynvar.ErrSkipResolution + return structvar.View{}, structvar.ErrSkipResolution } bundleGroup, ok := terraform.TerraformToGroupName[resourceType] if !ok { - return dyn.InvalidValue, dynvar.ErrSkipResolution + return structvar.View{}, structvar.ErrSkipResolution } // Reconstruct path in bundle format: // databricks_pipeline.my_pipeline.id → resources.pipelines.my_pipeline.id - bundlePath := dyn.NewPath(dyn.Key("resources"), dyn.Key(bundleGroup)).Append(path[1:]...) - return dyn.V(fmt.Sprintf("${%s}", bundlePath.String())), nil + ref := fmt.Sprintf("${resources.%s.%s}", bundleGroup, sp.SkipPrefix(1)) + return structvar.NewView(&ref, nil, nil), nil }) + if err != nil { + return "", err + } + if v, ok := out[""]; ok { + rs, _ := v.AsString() + return rs, nil + } + return s, nil +} + +// reverseInterpolateConfig returns a copy of cfg in which terraform-style references +// are rewritten to bundle-style ones (see [reverseInterpolate]). cfg is not changed. +func reverseInterpolateConfig(cfg *config.Root) (*config.Root, error) { + out := &config.Root{} + if err := out.Assign(nil, cfg.View()); err != nil { + return nil, err + } + + var u reverseUpdates + walk.Strings(cfg, walk.WithReferences, func(n *walk.Node, s string) (string, bool) { + u.add(n.Path, s) + return "", false + }) + for p, ref := range cfg.References() { + u.add(func() *structpath.PathNode { return p }, ref) + } + if u.err != nil { + return nil, u.err + } + + for _, up := range u.updates { + if err := out.SetReference(up.path, up.s); err != nil { + return nil, err + } + } + return out, nil +} + +type reverseUpdate struct { + path *structpath.PathNode + s string +} + +// reverseUpdates collects the strings of a configuration that change when reverse +// interpolated, and the first error. +type reverseUpdates struct { + updates []reverseUpdate + err error +} + +// add records the reverse interpolation of s, a value at path, if it has references +// and changes. path is only called then. +func (u *reverseUpdates) add(path func() *structpath.PathNode, s string) { + if u.err != nil { + return + } + if _, ok := structvar.NewRef(s); !ok { + return + } + rs, err := reverseInterpolate(s) + if err != nil { + u.err = err + return + } + if rs != s { + u.updates = append(u.updates, reverseUpdate{path(), rs}) + } } diff --git a/bundle/statemgmt/reverse_interpolate_test.go b/bundle/statemgmt/reverse_interpolate_test.go index fe1779cd65b..8d898b35063 100644 --- a/bundle/statemgmt/reverse_interpolate_test.go +++ b/bundle/statemgmt/reverse_interpolate_test.go @@ -6,129 +6,86 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestReverseInterpolatePreservesBConfigValue(t *testing.T) { - // This test verifies that our approach of getting b.Config.Value(), - // reverse interpolating it, and wrapping in a new config.Root - // does NOT mutate b.Config + // This test verifies that reverse interpolating a config returns a new + // config.Root and does NOT mutate the original. - b := &bundle.Bundle{ - Config: config.Root{ - Bundle: config.Bundle{ - Name: "test", - }, - }, - } + root, diags := config.LoadFromBytes("test.yml", []byte(` +bundle: + name: test +resources: + jobs: + my_job: + name: My Job + description: ${databricks_pipeline.my_pipeline.id} + max_concurrent_runs: ${databricks_job.other.id} +`)) + require.NoError(t, diags.Error()) + b := &bundle.Bundle{Config: *root} - err := b.Config.Mutate(func(_ dyn.Value) (dyn.Value, error) { - return dyn.V(map[string]dyn.Value{ - "bundle": dyn.V(map[string]dyn.Value{ - "name": dyn.V("test"), - }), - "resources": dyn.V(map[string]dyn.Value{ - "jobs": dyn.V(map[string]dyn.Value{ - "my_job": dyn.V(map[string]dyn.Value{ - "name": dyn.V("My Job"), - "depends_on": dyn.V("${databricks_pipeline.my_pipeline.id}"), - }), - }), - }), - }), nil - }) + originalJSON, err := json.Marshal(b.Config.View().AsAny()) require.NoError(t, err) - originalValue := b.Config.Value() - originalJSON, err := json.Marshal(originalValue.AsAny()) + uninterpolatedConfig, err := reverseInterpolateConfig(&b.Config) require.NoError(t, err) - interpolatedRoot := b.Config.Value() - - uninterpolatedRoot, err := reverseInterpolate(interpolatedRoot) - require.NoError(t, err) + uninterpolated := uninterpolatedConfig.View() + description, ok := uninterpolated.Lookup(structpath.MustParsePath("resources.jobs.my_job.description")).AsString() + require.True(t, ok) + assert.Equal(t, "${resources.pipelines.my_pipeline.id}", description, "should be bundle-style after reverse interpolation") - dependsOn, err := dyn.GetByPath(uninterpolatedRoot, dyn.MustPathFromString("resources.jobs.my_job.depends_on")) - require.NoError(t, err) - dependsOnStr, ok := dependsOn.AsString() + // References in fields that cannot hold a string are rewritten too. + maxRuns, ok := uninterpolated.Lookup(structpath.MustParsePath("resources.jobs.my_job.max_concurrent_runs")).AsString() require.True(t, ok) - assert.Equal(t, "${resources.pipelines.my_pipeline.id}", dependsOnStr, "should be bundle-style after reverse interpolation") + assert.Equal(t, "${resources.jobs.other.id}", maxRuns) - var uninterpolatedConfig config.Root - err = uninterpolatedConfig.Mutate(func(_ dyn.Value) (dyn.Value, error) { - return uninterpolatedRoot, nil - }) - require.NoError(t, err) + // Locations are kept. + assert.Equal(t, b.Config.GetLocationsOf(structpath.NewPath(nil, "resources", "jobs", "my_job", "description")), uninterpolatedConfig.GetLocationsOf(structpath.NewPath(nil, "resources", "jobs", "my_job", "description"))) - afterValue := b.Config.Value() - afterJSON, err := json.Marshal(afterValue.AsAny()) + afterJSON, err := json.Marshal(b.Config.View().AsAny()) require.NoError(t, err) + assert.Equal(t, string(originalJSON), string(afterJSON), "b.Config should not change") - assert.Equal(t, string(originalJSON), string(afterJSON), "b.Config.Value() should not change") - - originalDependsOn, err := dyn.GetByPath(afterValue, dyn.MustPathFromString("resources.jobs.my_job.depends_on")) - require.NoError(t, err) - originalDependsOnStr, ok := originalDependsOn.AsString() + originalDescription, ok := b.Config.View().Lookup(structpath.MustParsePath("resources.jobs.my_job.description")).AsString() require.True(t, ok) - assert.Equal(t, "${databricks_pipeline.my_pipeline.id}", originalDependsOnStr, "terraform-style reference should be preserved in b.Config") + assert.Equal(t, "${databricks_pipeline.my_pipeline.id}", originalDescription, "terraform-style reference should be preserved in b.Config") } func TestReverseInterpolate(t *testing.T) { tests := []struct { name string - input dyn.Value - expected dyn.Value + input string + expected string }{ { - name: "converts terraform-style job reference to bundle-style", - input: dyn.V(map[string]dyn.Value{ - "job_id": dyn.V("${databricks_job.my_job.id}"), - }), - expected: dyn.V(map[string]dyn.Value{ - "job_id": dyn.V("${resources.jobs.my_job.id}"), - }), + name: "converts terraform-style job reference to bundle-style", + input: "${databricks_job.my_job.id}", + expected: "${resources.jobs.my_job.id}", }, { - name: "leaves bundle-style references unchanged", - input: dyn.V(map[string]dyn.Value{ - "pipeline_id": dyn.V("${resources.pipelines.my_pipeline.id}"), - }), - expected: dyn.V(map[string]dyn.Value{ - "pipeline_id": dyn.V("${resources.pipelines.my_pipeline.id}"), - }), + name: "leaves bundle-style references unchanged", + input: "${resources.pipelines.my_pipeline.id}", + expected: "${resources.pipelines.my_pipeline.id}", }, { - name: "handles nested paths", - input: dyn.V(map[string]dyn.Value{ - "config": dyn.V(map[string]dyn.Value{ - "source": dyn.V("${databricks_pipeline.my_pipeline.url}"), - }), - }), - expected: dyn.V(map[string]dyn.Value{ - "config": dyn.V(map[string]dyn.Value{ - "source": dyn.V("${resources.pipelines.my_pipeline.url}"), - }), - }), + name: "handles nested paths", + input: "${databricks_pipeline.my_pipeline.url}", + expected: "${resources.pipelines.my_pipeline.url}", }, { - name: "skips unknown terraform resource types", - input: dyn.V(map[string]dyn.Value{ - "unknown": dyn.V("${unknown_resource.my_resource.id}"), - }), - expected: dyn.V(map[string]dyn.Value{ - "unknown": dyn.V("${unknown_resource.my_resource.id}"), - }), + name: "skips unknown terraform resource types", + input: "${unknown_resource.my_resource.id}", + expected: "${unknown_resource.my_resource.id}", }, { - name: "handles multiple references in one value", - input: dyn.V(map[string]dyn.Value{ - "combined": dyn.V("${databricks_job.job1.id}/${databricks_pipeline.pipeline1.id}"), - }), - expected: dyn.V(map[string]dyn.Value{ - "combined": dyn.V("${resources.jobs.job1.id}/${resources.pipelines.pipeline1.id}"), - }), + name: "handles multiple references in one value", + input: "${databricks_job.job1.id}/${databricks_pipeline.pipeline1.id}", + expected: "${resources.jobs.job1.id}/${resources.pipelines.pipeline1.id}", }, } @@ -136,7 +93,7 @@ func TestReverseInterpolate(t *testing.T) { t.Run(tt.name, func(t *testing.T) { result, err := reverseInterpolate(tt.input) require.NoError(t, err) - assert.Equal(t, tt.expected.AsAny(), result.AsAny()) + assert.Equal(t, tt.expected, result) }) } } diff --git a/bundle/statemgmt/state_load.go b/bundle/statemgmt/state_load.go index 3c6b7df07b4..6b92e11b532 100644 --- a/bundle/statemgmt/state_load.go +++ b/bundle/statemgmt/state_load.go @@ -3,7 +3,6 @@ package statemgmt import ( "context" "errors" - "fmt" "slices" "strings" @@ -12,7 +11,6 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/statemgmt/resourcestate" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" ) type ( @@ -93,78 +91,54 @@ func applyState(ctx context.Context, b *bundle.Bundle, state ExportedResourcesMa return nil } -func ensureMap(v dyn.Value, path dyn.Path) (dyn.Value, error) { - item, _ := dyn.GetByPath(v, path) - if !item.IsValid() { - var err error - v, err = dyn.SetByPath(v, path, dyn.V(dyn.NewMapping())) - if err != nil { - return dyn.InvalidValue, fmt.Errorf("internal error: failed to create %s: %w", path, err) +func StateToBundle(ctx context.Context, state ExportedResourcesMap, cfg *config.Root) error { + for resourceKey, attrs := range state { + // Parse resource key like "resources.jobs.foo" or "resources.jobs.foo.permissions" + parts := strings.Split(resourceKey, ".") + if len(parts) < 3 || parts[0] != "resources" { + continue // Skip invalid resource keys } - } - return v, nil -} -func StateToBundle(ctx context.Context, state ExportedResourcesMap, config *config.Root) error { - return config.Mutate(func(v dyn.Value) (dyn.Value, error) { - var err error - v, err = ensureMap(v, dyn.Path{dyn.Key("resources")}) - if err != nil { - return v, err + groupName := parts[1] + resourceName := parts[2] + + // Skip permissions for now as they are sub-resources + if len(parts) > 3 { + continue } - for resourceKey, attrs := range state { - // Parse resource key like "resources.jobs.foo" or "resources.jobs.foo.permissions" - parts := strings.Split(resourceKey, ".") - if len(parts) < 3 || parts[0] != "resources" { - continue // Skip invalid resource keys - } - - groupName := parts[1] - resourceName := parts[2] - - // Skip permissions for now as they are sub-resources - if len(parts) > 3 { - continue - } - - var err error - v, err = ensureMap(v, dyn.Path{dyn.Key("resources"), dyn.Key(groupName)}) - if err != nil { - return v, err - } - - path := dyn.Path{dyn.Key("resources"), dyn.Key(groupName), dyn.Key(resourceName)} - resource, err := dyn.GetByPath(v, path) - if !resource.IsValid() { - m := dyn.NewMapping() - m.SetLoc("id", nil, dyn.V(attrs.ID)) - m.SetLoc("modified_status", nil, dyn.V(resources.ModifiedStatusDeleted)) - v, err = dyn.SetByPath(v, path, dyn.V(m)) - if err != nil { - return dyn.InvalidValue, err - } - } else if err != nil { - return dyn.InvalidValue, err - } else { - v, err = dyn.SetByPath(v, dyn.Path{dyn.Key("resources"), dyn.Key(groupName), dyn.Key(resourceName), dyn.Key("id")}, dyn.V(attrs.ID)) - if err != nil { - return dyn.InvalidValue, err - } - } + if !hasID(groupName) { + continue } - return dyn.MapByPattern(v, dyn.Pattern{dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()}, func(p dyn.Path, inner dyn.Value) (dyn.Value, error) { - idPath := dyn.Path{dyn.Key("id")} - statusPath := dyn.Path{dyn.Key("modified_status")} - id, _ := dyn.GetByPath(inner, idPath) - status, _ := dyn.GetByPath(inner, statusPath) - if !id.IsValid() && !status.IsValid() { - return dyn.SetByPath(inner, statusPath, dyn.V(resources.ModifiedStatusCreated)) - } - return inner, nil - }) + id, status, created, ok := cfg.Resources.GetOrCreateState(groupName, resourceName) + if !ok { + continue + } + if created { + *status = resources.ModifiedStatusDeleted + } + *id = attrs.ID + } + + cfg.Resources.IterateAllResourceStates(func(typ, _ string, id *string, status *resources.ModifiedStatus) bool { + if hasID(typ) && *id == "" && *status == "" { + *status = resources.ModifiedStatusCreated + } + return true }) + return nil +} + +// hasID reports whether resources of group can hold the deployed id and status. Groups +// that are not part of the configuration, and internal snapshots, cannot. +func hasID(group string) bool { + typ, ok := config.ResourcesTypes[group] + if !ok { + return false + } + _, ok = typ.FieldByName("ID") + return ok } func validateLoadedState(state ExportedResourcesMap, modes []LoadMode) error { diff --git a/bundle/trampoline/python_dbr_warning.go b/bundle/trampoline/python_dbr_warning.go index 4cc7a67dc8c..bfcd885d1b8 100644 --- a/bundle/trampoline/python_dbr_warning.go +++ b/bundle/trampoline/python_dbr_warning.go @@ -10,8 +10,8 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/structs/structvar" "github.com/databricks/databricks-sdk-go" "golang.org/x/mod/semver" ) @@ -82,18 +82,18 @@ func hasIncompatibleWheelTasks(ctx context.Context, b *bundle.Bundle) diag.Diagn // So we can get the version from the cluster definition. // It's defined in a form of resources.clusters..id if strings.HasPrefix(task.ExistingClusterId, "${") { - p, ok := dynvar.PureReferenceToPath(task.ExistingClusterId) - if !ok || len(p) < 3 { + p, ok := structvar.PureReferenceToPath(task.ExistingClusterId) + if !ok || p.Len() < 3 { log.Warnf(ctx, "unable to parse cluster key from %s", task.ExistingClusterId) continue } - if p[0].Key() != "resources" || p[1].Key() != "clusters" { + if p.KeyAt(0) != "resources" || p.KeyAt(1) != "clusters" { log.Warnf(ctx, "incorrect variable reference for cluster id %s", task.ExistingClusterId) continue } - clusterKey := p[2].Key() + clusterKey := p.KeyAt(2) cluster, ok := b.Config.Resources.Clusters[clusterKey] if !ok { log.Warnf(ctx, "unable to find cluster with key %s", clusterKey) diff --git a/cmd/air/convert_to_dabs.go b/cmd/air/convert_to_dabs.go index 02b29a4d02f..910835a036a 100644 --- a/cmd/air/convert_to_dabs.go +++ b/cmd/air/convert_to_dabs.go @@ -13,9 +13,7 @@ import ( "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlsaver" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/spf13/cobra" ) @@ -112,10 +110,10 @@ does not contact the workspace.`, // convertToDabs builds the DABs bundle value and the loose launch artifacts for a // run config. It reads only what the run path's buildArtifacts reads, so the // mapping is unit-testable in isolation. Returns the bundle root as a -// map[string]dyn.Value (ready for yamlsaver) and the loose artifacts (command.sh + +// structyaml.Map (ready for structyaml.Save) and the loose artifacts (command.sh + // env/secret/param sidecars) to write under generated_artifacts/. It does not touch the // code_source; the emitted `tgz` artifact packages it at deploy. -func convertToDabs(ctx context.Context, cfg *runConfig, configPath, bundleDir string) (map[string]dyn.Value, []uploadItem, error) { +func convertToDabs(ctx context.Context, cfg *runConfig, configPath, bundleDir string) (structyaml.Map, []uploadItem, error) { // idempotency_token is intentionally not mapped: it dedups a single runs/submit // call, which has no analogue for a persistent, repeatedly-runnable bundle job. // @@ -259,14 +257,6 @@ func bundleCodeSourcePath(ctx context.Context, cfg *runConfig, configPath, bundl return localBundlePath(filepath.ToSlash(rel)), nil } -// nv builds a dyn.Value at "position" n: yamlsaver orders a map's keys by their -// Location line, so assigning ascending n values fixes the emitted key order. -// It routes through dyn.V so nested Go maps/slices are converted recursively, -// then stamps the ordering location. -func nv(v any, n int) dyn.Value { - return dyn.V(v).WithLocations([]diag.Location{{Line: n}}) -} - // localBundlePath renders a bundle-relative path with a leading "./" so bundle // deploy classifies it as a local artifact to upload (see IsLibraryLocal). It is // built from path.Join (forward slashes) so the emitted YAML is identical across @@ -275,10 +265,10 @@ func localBundlePath(p string) string { return "./" + p } -// buildBundleValue assembles the bundle root as an ordered map[string]dyn.Value. +// buildBundleValue assembles the bundle root as an ordered structyaml.Map. // codeSourcePath is the "./"-prefixed code_source dir relative to the bundle (empty // when the config has no code_source); command.sh is a bundle-local artifact. -func buildBundleValue(ctx context.Context, cfg *runConfig, configPath, codeSourcePath string, art *codeArtifact) map[string]dyn.Value { +func buildBundleValue(ctx context.Context, cfg *runConfig, configPath, codeSourcePath string, art *codeArtifact) structyaml.Map { name := cfg.ExperimentName // ai_runtime_task: experiment + one deployment (command_path + compute) + @@ -288,67 +278,57 @@ func buildBundleValue(ctx context.Context, cfg *runConfig, configPath, codeSourc // command_path is "./"-prefixed so bundle deploy treats it as LOCAL and uploads // it: libraries.IsLibraryLocal classifies a bare, extensionless path as a PyPI // package name, which would deploy a path the backend can't resolve. - compute := map[string]dyn.Value{ - "accelerator_type": nv(cfg.Compute.AcceleratorType, 1), - "accelerator_count": nv(cfg.Compute.NumAccelerators, 2), - } + compute := structyaml.M( + "accelerator_type", cfg.Compute.AcceleratorType, + "accelerator_count", cfg.Compute.NumAccelerators, + ) if cfg.Compute.PoolID != nil { - compute["provisioned_capacity_id"] = nv(*cfg.Compute.PoolID, 3) - } - deployment := map[string]dyn.Value{ - "command_path": nv(localBundlePath(path.Join(generatedArtifactsDir, commandScriptName)), 1), - "compute": nv(compute, 2), + compute.Add("provisioned_capacity_id", *cfg.Compute.PoolID) } + deployment := structyaml.M( + "command_path", localBundlePath(path.Join(generatedArtifactsDir, commandScriptName)), + "compute", compute, + ) - aiRuntimeTask := map[string]dyn.Value{ - "experiment": nv(name, 1), - "deployments": nv([]dyn.Value{dyn.V(deployment)}, 2), - } - line := 3 + aiRuntimeTask := structyaml.M( + "experiment", name, + "deployments", []any{deployment}, + ) if codeSourcePath != "" { // Points at the `tgz` artifact's built tarball; deploy uploads it and rewrites // this to the uploaded workspace path. - aiRuntimeTask["code_source_path"] = nv(codeSourcePath, line) - line++ + aiRuntimeTask.Add("code_source_path", codeSourcePath) } if cfg.MLflowRunName != nil { - aiRuntimeTask["mlflow_run"] = nv(*cfg.MLflowRunName, line) - line++ + aiRuntimeTask.Add("mlflow_run", *cfg.MLflowRunName) } if cfg.MLflowExperimentDirectory != nil { - aiRuntimeTask["mlflow_experiment_directory"] = nv(*cfg.MLflowExperimentDirectory, line) - line++ + aiRuntimeTask.Add("mlflow_experiment_directory", *cfg.MLflowExperimentDirectory) } if cfg.MLflowArtifactLocation != nil { - aiRuntimeTask["mlflow_artifact_location"] = nv(*cfg.MLflowArtifactLocation, line) - line++ + aiRuntimeTask.Add("mlflow_artifact_location", *cfg.MLflowArtifactLocation) } if cfg.Compute.PriorityClass != nil { - aiRuntimeTask["priority_class"] = nv(*cfg.Compute.PriorityClass, line) - line++ + aiRuntimeTask.Add("priority_class", *cfg.Compute.PriorityClass) } if cfg.Environment != nil && cfg.Environment.UnityCatalogImage != "" { - aiRuntimeTask["unity_catalog_image_path"] = nv(cfg.Environment.UnityCatalogImage, line) + aiRuntimeTask.Add("unity_catalog_image_path", cfg.Environment.UnityCatalogImage) } // Task wrapper: task_key + framework fields (retries/timeout) + env key + // the ai_runtime_task. Framework fields live here per the schema, not inside // ai_runtime_task. - task := map[string]dyn.Value{ - "task_key": nv(name, 1), - "environment_key": nv(aiRuntimeEnvironmentKey, 2), - } - taskLine := 3 maxRetries := cfg.maxRetries() - task["max_retries"] = nv(maxRetries, taskLine) - taskLine++ - task["retry_on_timeout"] = nv(maxRetries > 0, taskLine) - taskLine++ + task := structyaml.M( + "task_key", name, + "environment_key", aiRuntimeEnvironmentKey, + "max_retries", maxRetries, + "retry_on_timeout", maxRetries > 0, + ) if cfg.TimeoutMinutes != nil { - task["timeout_seconds"] = nv(cfg.timeoutSeconds(), taskLine) - taskLine++ + task.Add("timeout_seconds", cfg.timeoutSeconds()) } - task["ai_runtime_task"] = nv(aiRuntimeTask, taskLine) + task.Add("ai_runtime_task", aiRuntimeTask) // environments[]: version + the dependency set. The runtime installs deps from this // spec directly, so the full dependency set (whether authored inline or in a @@ -358,65 +338,57 @@ func buildBundleValue(ctx context.Context, cfg *runConfig, configPath, codeSourc // default channel) so a config without an explicit version still pins the version // the workload would have run with — not an empty spec. envVersion, deps := bundleEnvironmentDeps(ctx, cfg) - envSpec := map[string]dyn.Value{} + var envSpec structyaml.Map if strings.HasPrefix(envVersion, databricksAIPrefix) { - envSpec["base_environment"] = nv("workspace-base-environments/"+envVersion, 1) + envSpec.Add("base_environment", "workspace-base-environments/"+envVersion) } else { - envSpec["environment_version"] = nv(envVersion, 1) + envSpec.Add("environment_version", envVersion) } if len(deps) > 0 { - depVals := make([]dyn.Value, len(deps)) - for i, d := range deps { - depVals[i] = dyn.V(d) - } - envSpec["dependencies"] = nv(depVals, 2) - } - environment := map[string]dyn.Value{ - "environment_key": nv(aiRuntimeEnvironmentKey, 1), - "spec": nv(envSpec, 2), + envSpec.Add("dependencies", deps) } + environment := structyaml.M( + "environment_key", aiRuntimeEnvironmentKey, + "spec", envSpec, + ) - job := map[string]dyn.Value{ - "name": nv(name, 1), - "tasks": nv([]dyn.Value{dyn.V(task)}, 2), - "environments": nv([]dyn.Value{dyn.V(environment)}, 3), - } + job := structyaml.M( + "name", name, + "tasks", []any{task}, + "environments", []any{environment}, + ) // usage_policy_id is an already-resolved budget policy id, so it maps directly // to the job's budget_policy_id. (usage_policy_name needs server-side resolution // and is rejected in convertToDabs.) if cfg.UsagePolicyID != nil { - job["budget_policy_id"] = nv(*cfg.UsagePolicyID, 4) + job.Add("budget_policy_id", *cfg.UsagePolicyID) } - if perms := buildPermissionsValue(cfg.Permissions); perms.Kind() != dyn.KindInvalid { - job["permissions"] = nv(perms.MustSequence(), 5) + if perms := buildPermissionsValue(cfg.Permissions); perms != nil { + job.Add("permissions", perms) } - rootValue := map[string]dyn.Value{ - "bundle": nv(map[string]dyn.Value{ - "name": nv(name, 1), - }, 1), + rootValue := structyaml.M( + "bundle", structyaml.M("name", name), // sync.paths replaces the default of syncing the whole bundle root. The code // directory is omitted deliberately: the `tgz` artifact packages it, so syncing // it too would upload the tree twice. - "sync": nv(map[string]dyn.Value{ - "paths": nv([]dyn.Value{nv(generatedArtifactsDir, 1)}, 1), - }, 2), - "targets": nv(map[string]dyn.Value{ - dabsTargetName: nv(map[string]dyn.Value{ - "mode": nv("development", 1), - "default": nv(true, 2), - }, 1), - }, 4), - "resources": nv(map[string]dyn.Value{ - "jobs": nv(map[string]dyn.Value{ - bundleResourceKey(name): nv(job, 1), - }, 1), - }, 5), - } + "sync", structyaml.M("paths", []any{generatedArtifactsDir}), + ) // The `tgz` artifact that packages the code_source (nil only when there is none). if art != nil { - rootValue["artifacts"] = nv(buildArtifactsValue(art), 3) - } + rootValue.Add("artifacts", buildArtifactsValue(art)) + } + rootValue.Add("targets", structyaml.M( + dabsTargetName, structyaml.M( + "mode", "development", + "default", true, + ), + )) + rootValue.Add("resources", structyaml.M( + "jobs", structyaml.M( + bundleResourceKey(name), job, + ), + )) return rootValue } @@ -424,34 +396,23 @@ func buildBundleValue(ctx context.Context, cfg *runConfig, configPath, codeSourc // single `tgz` artifact whose `path` is the code-source root, carrying the git ref // and/or include subpaths, and whose `files` output is the tarball code_source_path // points at. -func buildArtifactsValue(art *codeArtifact) map[string]dyn.Value { - a := map[string]dyn.Value{ - "type": nv("tgz", 1), - "path": nv(art.path, 2), - } - fileLine := 3 +func buildArtifactsValue(art *codeArtifact) structyaml.Map { + a := structyaml.M( + "type", "tgz", + "path", art.path, + ) if art.gitCommit != nil || art.gitBranch != nil { - g := map[string]dyn.Value{} // commit wins over branch, matching the artifact builder. if art.gitCommit != nil { - g["commit"] = nv(*art.gitCommit, 1) + a.Add("git", structyaml.M("commit", *art.gitCommit)) } else { - g["branch"] = nv(*art.gitBranch, 1) + a.Add("git", structyaml.M("branch", *art.gitBranch)) } - a["git"] = nv(g, fileLine) - fileLine++ } // include is always set: the basename (whole dir) or basename-prefixed subpaths. - vals := make([]dyn.Value, len(art.include)) - for i, p := range art.include { - vals[i] = dyn.V(p) - } - a["include"] = nv(vals, fileLine) - fileLine++ - a["files"] = nv([]dyn.Value{ - dyn.V(map[string]dyn.Value{"source": nv(art.tgzPath, 1)}), - }, fileLine) - return map[string]dyn.Value{codeSourceArtifactKey: nv(a, 1)} + a.Add("include", art.include) + a.Add("files", []any{structyaml.M("source", art.tgzPath)}) + return structyaml.M(codeSourceArtifactKey, a) } // bundleEnvironmentDeps resolves the runtime version and the inline dependency @@ -476,7 +437,7 @@ func bundleResourceKey(name string) string { } // quoteJobKey rewrites the emitted job resource key as a quoted YAML key when the -// name would otherwise load as a non-string scalar. yamlsaver emits map keys +// name would otherwise load as a non-string scalar. structyaml emits map keys // unquoted, and the bundle loader rejects a key that types as something other than // a string ("12345" -> !!int, "true" -> !!bool) with "invalid key tag". Only the // job key needs this: every other key convert emits is a fixed schema field name. @@ -516,25 +477,22 @@ func yamlKeyNeedsQuoting(key string) bool { } // buildPermissionsValue maps run-config permissions to DABs job permissions -// (level → principal). Returns an invalid value when there are none. -func buildPermissionsValue(perms []permission) dyn.Value { - if len(perms) == 0 { - return dyn.InvalidValue - } - out := make([]dyn.Value, 0, len(perms)) +// (level → principal). Returns nil when there are none. +func buildPermissionsValue(perms []permission) []any { + var out []any for _, p := range perms { - m := map[string]dyn.Value{"level": nv(p.Level, 1)} + m := structyaml.M("level", p.Level) switch { case p.UserName != nil: - m["user_name"] = nv(*p.UserName, 2) + m.Add("user_name", *p.UserName) case p.GroupName != nil: - m["group_name"] = nv(*p.GroupName, 2) + m.Add("group_name", *p.GroupName) case p.ServicePrincipalName != nil: - m["service_principal_name"] = nv(*p.ServicePrincipalName, 2) + m.Add("service_principal_name", *p.ServicePrincipalName) } - out = append(out, dyn.V(m)) + out = append(out, m) } - return dyn.V(out) + return out } // writeBundle writes the bundle into dir: databricks.yml plus the loose launch @@ -580,9 +538,9 @@ func writeBundle(ctx context.Context, cfg *runConfig, configPath, dir string, fo return nil, fmt.Errorf("databricks.yml already exists in %s; pass --force to overwrite or remove it", dir) } } - // SaveAsYAML's force arg is passed true unconditionally: the collision check + // Save's force arg is passed true unconditionally: the collision check // above already decided whether overwriting is allowed. - if err := yamlsaver.NewSaver().SaveAsYAML(root, bundlePath, true); err != nil { + if err := structyaml.Save(bundlePath, root, true, nil); err != nil { return nil, err } if err := quoteJobKey(bundlePath, bundleResourceKey(cfg.ExperimentName)); err != nil { diff --git a/cmd/air/convert_to_dabs_test.go b/cmd/air/convert_to_dabs_test.go index cbffa6b224c..ac07cac17d5 100644 --- a/cmd/air/convert_to_dabs_test.go +++ b/cmd/air/convert_to_dabs_test.go @@ -3,10 +3,11 @@ package aircmd import ( "os" "path/filepath" + "strconv" "strings" "testing" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.yaml.in/yaml/v3" @@ -86,17 +87,71 @@ func TestConvertToDabsCommandShape(t *testing.T) { assert.Error(t, cmd.Args(cmd, []string{"a", "b"})) } +// val is a value of the emitted bundle root. +type val struct{ v any } + +func (v val) MustString() string { return v.v.(string) } +func (v val) MustInt() int64 { return int64(v.v.(int)) } +func (v val) MustBool() bool { return v.v.(bool) } + +func (v val) MustSequence() []val { + if ss, ok := v.v.([]string); ok { + out := make([]val, len(ss)) + for i, s := range ss { + out[i] = val{s} + } + return out + } + var out []val + for _, e := range v.v.([]any) { + out = append(out, val{e}) + } + return out +} + +func (v val) Get(key string) val { + for _, p := range v.v.(structyaml.Map) { + if p.Key == key { + return val{p.Value} + } + } + return val{} +} + +// lookup reads a dotted path, with optional [index] suffixes, out of the emitted bundle root. +func lookup(root structyaml.Map, path string) (val, bool) { + v := val{root} + for part := range strings.SplitSeq(path, ".") { + key, idx, hasIdx := strings.Cut(strings.TrimSuffix(part, "]"), "[") + if _, ok := v.v.(structyaml.Map); !ok { + return val{}, false + } + if v = v.Get(key); v.v == nil { + return val{}, false + } + if hasIdx { + i, err := strconv.Atoi(idx) + seq, ok := v.v.([]any) + if err != nil || !ok || i >= len(seq) { + return val{}, false + } + v = val{seq[i]} + } + } + return v, true +} + // get is a small helper: read a dotted path out of the emitted bundle root. -func get(t *testing.T, root map[string]dyn.Value, path string) dyn.Value { +func get(t *testing.T, root structyaml.Map, path string) val { t.Helper() - v, err := dyn.GetByPath(dyn.V(root), dyn.MustPathFromString(path)) - require.NoError(t, err, "path %q should exist", path) + v, ok := lookup(root, path) + require.True(t, ok, "path %q should exist", path) return v } -func has(root map[string]dyn.Value, path string) bool { - _, err := dyn.GetByPath(dyn.V(root), dyn.MustPathFromString(path)) - return err == nil +func has(root structyaml.Map, path string) bool { + _, ok := lookup(root, path) + return ok } // A full config maps onto a schema-shaped bundle: bundle name, job/task keys, the @@ -543,11 +598,7 @@ func TestConvertToDabsSafeJobKey(t *testing.T) { root, _, err := convertToDabs(t.Context(), loaded, path, dir) require.NoError(t, err) // The key is the raw name; name/experiment keep the same value. - jobs, err := dyn.GetByPath(dyn.V(root), dyn.MustPathFromString("resources.jobs")) - require.NoError(t, err) - job := jobs.Get(name) - require.True(t, job.IsValid(), "job must be keyed by %q", name) - assert.Equal(t, name, job.Get("name").MustString()) + assert.Equal(t, name, get(t, root, "resources.jobs."+name+".name").MustString()) // The emitted YAML must load back with the key still a string. _, err = writeBundle(t.Context(), loaded, path, dir, true) diff --git a/cmd/apps/import.go b/cmd/apps/import.go index f552c3182ce..c639867a788 100644 --- a/cmd/apps/import.go +++ b/cmd/apps/import.go @@ -25,12 +25,9 @@ import ( "github.com/databricks/cli/libs/apps/prompt" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/yamlsaver" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/cli/libs/textutil" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/apps" @@ -456,22 +453,13 @@ func generateAppBundle(ctx context.Context, w *databricks.WorkspaceClient, app * } } - // Create the bundle configuration with explicit line numbers to control ordering // Use the app name for the bundle name bundleName := textutil.NormalizeString(app.Name) - bundleConfig := map[string]dyn.Value{ - "bundle": dyn.NewValue(map[string]dyn.Value{ - "name": dyn.NewValue(bundleName, []diag.Location{{Line: 1}}), - }, []diag.Location{{Line: 1}}), - "workspace": dyn.NewValue(map[string]dyn.Value{ - "host": dyn.NewValue(w.Config.Host, []diag.Location{{Line: 2}}), - }, []diag.Location{{Line: 10}}), - "resources": dyn.NewValue(map[string]dyn.Value{ - "apps": dyn.V(map[string]dyn.Value{ - appKey: v, - }), - }, []diag.Location{{Line: 20}}), - } + bundleConfig := structyaml.M( + "bundle", structyaml.M("name", bundleName), + "workspace", structyaml.M("host", w.Config.Host), + "resources", structyaml.M("apps", structyaml.M(appKey, v)), + ) // Download the app source files err = downloader.FlushToDisk(ctx, false) @@ -481,8 +469,7 @@ func generateAppBundle(ctx context.Context, w *databricks.WorkspaceClient, app * // Save databricks.yml databricksYml := filepath.Join(".", "databricks.yml") - saver := yamlsaver.NewSaver() - err = saver.SaveAsYAML(bundleConfig, databricksYml, false) + err = structyaml.Save(databricksYml, bundleConfig, false, nil) if err != nil { return "", fmt.Errorf("failed to save databricks.yml: %w", err) } @@ -545,7 +532,7 @@ func addBlankLinesBetweenTopLevelKeys(filename string) error { } // inlineAppConfigFile reads app.yml or app.yaml, inlines it into the app value, and returns the filename -func inlineAppConfigFile(appValue *dyn.Value) (string, error) { +func inlineAppConfigFile(appValue *structyaml.Map) (string, error) { // Check for app.yml first, then app.yaml var appConfigFile string var appConfigData []byte @@ -574,62 +561,33 @@ func inlineAppConfigFile(appValue *dyn.Value) (string, error) { return "", fmt.Errorf("failed to parse %s: %w", appConfigFile, err) } - // Get the current app value as a map - appMap, ok := appValue.AsMap() - if !ok { - return "", errors.New("app value is not a map") - } - - // Build the new app map with the config section - newPairs := make([]dyn.Pair, 0, len(appMap.Pairs())+2) - - // Copy existing pairs - newPairs = append(newPairs, appMap.Pairs()...) - // Create config section - configMap := make(map[string]dyn.Value) + var config structyaml.Map // Add command if present if cmd, ok := appConfig["command"]; ok { - cmdValue, err := convert.FromTyped(cmd, dyn.NilValue) - if err != nil { - return "", fmt.Errorf("failed to convert command: %w", err) - } - configMap["command"] = cmdValue + config.Add("command", structyaml.Value(cmd)) } // Add env if present if env, ok := appConfig["env"]; ok { - envValue, err := convert.FromTyped(env, dyn.NilValue) - if err != nil { - return "", fmt.Errorf("failed to convert env: %w", err) - } - configMap["env"] = envValue + config.Add("env", structyaml.Value(env)) } + // The config section and the top-level resources go before the existing fields. + var added structyaml.Map + // Add the config section if we have any items - if len(configMap) > 0 { - newPairs = append(newPairs, dyn.Pair{ - Key: dyn.V("config"), - Value: dyn.V(configMap), - }) + if len(config) > 0 { + added.Add("config", config) } // Add resources at top level if present if resources, ok := appConfig["resources"]; ok { - resourcesValue, err := convert.FromTyped(resources, dyn.NilValue) - if err != nil { - return "", fmt.Errorf("failed to convert resources: %w", err) - } - newPairs = append(newPairs, dyn.Pair{ - Key: dyn.V("resources"), - Value: resourcesValue, - }) + added.Add("resources", structyaml.Value(resources)) } - // Create the new app value with the config section - newMapping := dyn.NewMappingFromPairs(newPairs) - *appValue = dyn.NewValue(newMapping, appValue.Locations()) + *appValue = append(added, *appValue...) return appConfigFile, nil } diff --git a/cmd/apps/import_test.go b/cmd/apps/import_test.go index b50ec937991..30e9c8d239f 100644 --- a/cmd/apps/import_test.go +++ b/cmd/apps/import_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -16,17 +16,15 @@ func TestInlineAppConfigFile(t *testing.T) { tests := []struct { name string setupFiles map[string]string - inputValue dyn.Value + inputValue structyaml.Map expectedFile string expectedConfig map[string]any expectError bool }{ { - name: "no app config file", - setupFiles: map[string]string{}, - inputValue: dyn.V(map[string]dyn.Value{ - "name": dyn.V("test-app"), - }), + name: "no app config file", + setupFiles: map[string]string{}, + inputValue: structyaml.M("name", "test-app"), expectedFile: "", expectedConfig: map[string]any{"name": "test-app"}, }, @@ -38,9 +36,7 @@ env: - name: FOO value: bar`, }, - inputValue: dyn.V(map[string]dyn.Value{ - "name": dyn.V("test-app"), - }), + inputValue: structyaml.M("name", "test-app"), expectedFile: "app.yml", expectedConfig: nil, // Will check manually }, @@ -53,9 +49,7 @@ env: - name: TEST value: value`, }, - inputValue: dyn.V(map[string]dyn.Value{ - "name": dyn.V("test-app"), - }), + inputValue: structyaml.M("name", "test-app"), expectedFile: "app.yml", expectedConfig: nil, // Will check manually }, @@ -68,9 +62,7 @@ resources: serving_endpoint: name: my-endpoint`, }, - inputValue: dyn.V(map[string]dyn.Value{ - "name": dyn.V("test-app"), - }), + inputValue: structyaml.M("name", "test-app"), expectedFile: "app.yml", expectedConfig: nil, // Will check manually }, @@ -80,9 +72,7 @@ resources: "app.yml": `resources: - name: SERVING_ENDPOINT`, }, - inputValue: dyn.V(map[string]dyn.Value{ - "name": dyn.V("test-app"), - }), + inputValue: structyaml.M("name", "test-app"), expectedFile: "app.yml", expectedConfig: nil, // Will check manually }, @@ -92,9 +82,7 @@ resources: "app.yml": `command: ["python", "app.py"] env: []`, }, - inputValue: dyn.V(map[string]dyn.Value{ - "name": dyn.V("test-app"), - }), + inputValue: structyaml.M("name", "test-app"), expectedFile: "app.yml", expectedConfig: nil, // Will check manually }, @@ -125,20 +113,17 @@ env: []`, // Verify the structure if expectedConfig is set if tt.expectedConfig != nil { - appMap := appValue.MustMap() result := make(map[string]any) - for _, pair := range appMap.Pairs() { - key := pair.Key.MustString() - result[key] = pair.Value.AsAny() + for _, pair := range appValue { + result[pair.Key] = pair.Value } assert.Equal(t, tt.expectedConfig, result) } else if tt.expectedFile != "" { // Just verify that config or resources were added - appMap := appValue.MustMap() var hasConfigOrResources bool - for _, pair := range appMap.Pairs() { - key := pair.Key.MustString() + for _, pair := range appValue { + key := pair.Key if key == "config" || key == "resources" { hasConfigOrResources = true break @@ -158,24 +143,12 @@ func TestInlineAppConfigFileErrors(t *testing.T) { err := os.WriteFile("app.yml", []byte("invalid: yaml: content:\n - broken"), 0o644) require.NoError(t, err) - appValue := dyn.V(map[string]dyn.Value{"name": dyn.V("test")}) + appValue := structyaml.M("name", "test") _, err = inlineAppConfigFile(&appValue) assert.Error(t, err) assert.Contains(t, err.Error(), "failed to parse") }) - t.Run("app value not a map", func(t *testing.T) { - t.Chdir(t.TempDir()) - - err := os.WriteFile("app.yml", []byte("command: [\"test\"]"), 0o644) - require.NoError(t, err) - - appValue := dyn.V("not a map") - _, err = inlineAppConfigFile(&appValue) - assert.Error(t, err) - assert.Contains(t, err.Error(), "app value is not a map") - }) - t.Run("unreadable app.yml", func(t *testing.T) { t.Chdir(t.TempDir()) @@ -202,7 +175,7 @@ func TestInlineAppConfigFileErrors(t *testing.T) { require.NoError(t, err) } - appValue := dyn.V(map[string]dyn.Value{"name": dyn.V("test")}) + appValue := structyaml.M("name", "test") _, err = inlineAppConfigFile(&appValue) assert.Error(t, err) assert.Contains(t, err.Error(), "failed to read") @@ -223,21 +196,19 @@ resources: name: test`), 0o644) require.NoError(t, err) - appValue := dyn.V(map[string]dyn.Value{ - "name": dyn.V("test-app"), - "description": dyn.V("existing description"), - }) + appValue := structyaml.M( + "name", "test-app", + "description", "existing description", + ) filename, err := inlineAppConfigFile(&appValue) require.NoError(t, err) assert.Equal(t, "app.yml", filename) // Verify structure - appMap := appValue.MustMap() result := make(map[string]any) - for _, pair := range appMap.Pairs() { - key := pair.Key.MustString() - result[key] = pair.Value.AsAny() + for _, pair := range appValue { + result[pair.Key] = pair.Value } // Should have original fields plus config and resources diff --git a/cmd/bundle/generate/alert.go b/cmd/bundle/generate/alert.go index cb78b24de1e..2e4c5150370 100644 --- a/cmd/bundle/generate/alert.go +++ b/cmd/bundle/generate/alert.go @@ -12,9 +12,8 @@ import ( "github.com/databricks/cli/bundle/generate" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlsaver" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/cli/libs/textutil" "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/service/sql" @@ -141,13 +140,7 @@ After generation, you can deploy this alert to other targets using: return err } - result := map[string]dyn.Value{ - "resources": dyn.V(map[string]dyn.Value{ - "alerts": dyn.V(map[string]dyn.Value{ - alertKey: v, - }), - }), - } + result := structyaml.M("resources", structyaml.M("alerts", structyaml.M(alertKey, v))) // Create config directory if needed if err := os.MkdirAll(configDir, 0o755); err != nil { @@ -156,11 +149,11 @@ After generation, you can deploy this alert to other targets using: // Save configuration file configPath := filepath.Join(configDir, alertKey+".alert.yml") - saver := yamlsaver.NewSaverWithStyle(map[string]yaml.Style{ + styles := map[string]yaml.Style{ "display_name": yaml.DoubleQuotedStyle, - }) + } - err = saver.SaveAsYAML(result, configPath, force) + err = structyaml.Save(configPath, result, force, styles) if err != nil { return err } diff --git a/cmd/bundle/generate/app.go b/cmd/bundle/generate/app.go index bec005408cd..0705dc3f0c2 100644 --- a/cmd/bundle/generate/app.go +++ b/cmd/bundle/generate/app.go @@ -8,9 +8,8 @@ import ( "github.com/databricks/cli/cmd/bundle/deployment" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlsaver" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/cli/libs/textutil" "github.com/databricks/databricks-sdk-go/service/apps" "github.com/spf13/cobra" @@ -107,13 +106,7 @@ per target environment.`, appKey = textutil.NormalizeString(app.Name) } - result := map[string]dyn.Value{ - "resources": dyn.V(map[string]dyn.Value{ - "apps": dyn.V(map[string]dyn.Value{ - appKey: v, - }), - }), - } + result := structyaml.M("resources", structyaml.M("apps", structyaml.M(appKey, v))) err = downloader.FlushToDisk(ctx, force) if err != nil { @@ -122,8 +115,7 @@ per target environment.`, filename := filepath.Join(configDir, appKey+".app.yml") - saver := yamlsaver.NewSaver() - err = saver.SaveAsYAML(result, filename, force) + err = structyaml.Save(filename, result, force, nil) if err != nil { return err } diff --git a/cmd/bundle/generate/dashboard.go b/cmd/bundle/generate/dashboard.go index 9c4be81b391..d5058e60a5e 100644 --- a/cmd/bundle/generate/dashboard.go +++ b/cmd/bundle/generate/dashboard.go @@ -22,9 +22,8 @@ import ( "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlsaver" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/cli/libs/textutil" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/apierr" @@ -215,13 +214,7 @@ func (d *dashboard) saveConfiguration(ctx context.Context, b *bundle.Bundle, das return err } - result := map[string]dyn.Value{ - "resources": dyn.V(map[string]dyn.Value{ - "dashboards": dyn.V(map[string]dyn.Value{ - key: v, - }), - }), - } + result := structyaml.M("resources", structyaml.M("dashboards", structyaml.M(key, v))) // Make sure the output directory exists. if err := os.MkdirAll(d.resourceDir, 0o755); err != nil { @@ -230,9 +223,9 @@ func (d *dashboard) saveConfiguration(ctx context.Context, b *bundle.Bundle, das // Save the configuration to the resource directory. resourcePath := filepath.Join(d.resourceDir, key+".dashboard.yml") - saver := yamlsaver.NewSaverWithStyle(map[string]yaml.Style{ + styles := map[string]yaml.Style{ "display_name": yaml.DoubleQuotedStyle, - }) + } // Attempt to make the path relative to the bundle root. rel, err := filepath.Rel(b.BundleRootPath, resourcePath) @@ -241,7 +234,7 @@ func (d *dashboard) saveConfiguration(ctx context.Context, b *bundle.Bundle, das } cmdio.LogString(ctx, "Writing configuration to "+filepath.ToSlash(rel)) - err = saver.SaveAsYAML(result, resourcePath, d.force) + err = structyaml.Save(resourcePath, result, d.force, styles) if err != nil { return err } diff --git a/cmd/bundle/generate/genie_space.go b/cmd/bundle/generate/genie_space.go index 7c520abc320..2107d62ffd8 100644 --- a/cmd/bundle/generate/genie_space.go +++ b/cmd/bundle/generate/genie_space.go @@ -18,9 +18,8 @@ import ( "github.com/databricks/cli/cmd/bundle/deployment" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlsaver" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/cli/libs/textutil" "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/service/dashboards" @@ -130,13 +129,7 @@ func (g *genieSpace) saveConfiguration(ctx context.Context, b *bundle.Bundle, ge return err } - result := map[string]dyn.Value{ - "resources": dyn.V(map[string]dyn.Value{ - "genie_spaces": dyn.V(map[string]dyn.Value{ - key: v, - }), - }), - } + result := structyaml.M("resources", structyaml.M("genie_spaces", structyaml.M(key, v))) // Make sure the output directory exists. if err := os.MkdirAll(g.resourceDir, 0o755); err != nil { @@ -145,9 +138,9 @@ func (g *genieSpace) saveConfiguration(ctx context.Context, b *bundle.Bundle, ge // Save the configuration to the resource directory. resourcePath := filepath.Join(g.resourceDir, key+".genie_space.yml") - saver := yamlsaver.NewSaverWithStyle(map[string]yaml.Style{ + styles := map[string]yaml.Style{ "title": yaml.DoubleQuotedStyle, - }) + } // Attempt to make the path relative to the bundle root. rel, err := filepath.Rel(b.BundleRootPath, resourcePath) @@ -156,7 +149,7 @@ func (g *genieSpace) saveConfiguration(ctx context.Context, b *bundle.Bundle, ge } cmdio.LogString(ctx, "Writing configuration to "+filepath.ToSlash(rel)) - err = saver.SaveAsYAML(result, resourcePath, g.force) + err = structyaml.Save(resourcePath, result, g.force, styles) if err != nil { return err } diff --git a/cmd/bundle/generate/job.go b/cmd/bundle/generate/job.go index db6839ade98..07066c0ec8a 100644 --- a/cmd/bundle/generate/job.go +++ b/cmd/bundle/generate/job.go @@ -12,9 +12,8 @@ import ( "github.com/databricks/cli/cmd/bundle/deployment" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlsaver" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/cli/libs/textutil" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/spf13/cobra" @@ -115,13 +114,7 @@ After generation, you can deploy this job to other targets using: jobKey = textutil.NormalizeString(job.Settings.Name) } - result := map[string]dyn.Value{ - "resources": dyn.V(map[string]dyn.Value{ - "jobs": dyn.V(map[string]dyn.Value{ - jobKey: v, - }), - }), - } + result := structyaml.M("resources", structyaml.M("jobs", structyaml.M(jobKey, v))) err = downloader.FlushToDisk(ctx, force) if err != nil { @@ -141,13 +134,13 @@ After generation, you can deploy this job to other targets using: return fmt.Errorf("failed to rename file %s. DABs uses the resource type as a sub-extension for generated content, please rename it to %s, err: %w", oldFilename, filename, err) } - saver := yamlsaver.NewSaverWithStyle(map[string]yaml.Style{ + styles := map[string]yaml.Style{ // Including all JobSettings and nested fields which are map[string]string type "spark_conf": yaml.DoubleQuotedStyle, "custom_tags": yaml.DoubleQuotedStyle, "tags": yaml.DoubleQuotedStyle, - }) - err = saver.SaveAsYAML(result, filename, force) + } + err = structyaml.Save(filename, result, force, styles) if err != nil { return err } diff --git a/cmd/bundle/generate/pipeline.go b/cmd/bundle/generate/pipeline.go index cdde5984d40..d6de9ab9cc4 100644 --- a/cmd/bundle/generate/pipeline.go +++ b/cmd/bundle/generate/pipeline.go @@ -11,9 +11,8 @@ import ( "github.com/databricks/cli/cmd/bundle/deployment" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlsaver" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/cli/libs/textutil" "github.com/databricks/databricks-sdk-go/service/pipelines" "github.com/spf13/cobra" @@ -112,13 +111,7 @@ like catalogs, schemas, and compute configurations per target.`, pipelineKey = textutil.NormalizeString(pipeline.Name) } - result := map[string]dyn.Value{ - "resources": dyn.V(map[string]dyn.Value{ - "pipelines": dyn.V(map[string]dyn.Value{ - pipelineKey: v, - }), - }), - } + result := structyaml.M("resources", structyaml.M("pipelines", structyaml.M(pipelineKey, v))) err = downloader.FlushToDisk(ctx, force) if err != nil { @@ -136,15 +129,13 @@ like catalogs, schemas, and compute configurations per target.`, return fmt.Errorf("failed to rename file %s. DABs uses the resource type as a sub-extension for generated content, please rename it to %s, err: %w", oldFilename, filename, err) } - saver := yamlsaver.NewSaverWithStyle( - // Including all CreatePipeline and nested fields which are map[string]string type - map[string]yaml.Style{ - "spark_conf": yaml.DoubleQuotedStyle, - "custom_tags": yaml.DoubleQuotedStyle, - "configuration": yaml.DoubleQuotedStyle, - }, - ) - err = saver.SaveAsYAML(result, filename, force) + // Including all CreatePipeline and nested fields which are map[string]string type + styles := map[string]yaml.Style{ + "spark_conf": yaml.DoubleQuotedStyle, + "custom_tags": yaml.DoubleQuotedStyle, + "configuration": yaml.DoubleQuotedStyle, + } + err = structyaml.Save(filename, result, force, styles) if err != nil { return err } diff --git a/cmd/bundle/run.go b/cmd/bundle/run.go index 17199a6cabf..4ca32fd92a1 100644 --- a/cmd/bundle/run.go +++ b/cmd/bundle/run.go @@ -21,10 +21,10 @@ import ( "github.com/databricks/cli/libs/auth" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/execv" "github.com/databricks/cli/libs/flags" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structpath" "github.com/spf13/cobra" ) @@ -189,7 +189,7 @@ Example usage: // runner sees concrete values (e.g. an app's env vars referencing another // resource, or its source_code_path pointing at the immutable snapshot's // full_path, which lives only in the deployed state). - target := dyn.NewPath(dyn.Key("resources"), dyn.Key(ref.Description.PluralName), dyn.Key(ref.Key)) + target := structpath.NewPath(nil, "resources", ref.Description.PluralName, ref.Key) if err := b.DeploymentBundle.ResolveConfigAgainstState(&b.Config, target); err != nil { return err } diff --git a/cmd/bundle/utils/process.go b/cmd/bundle/utils/process.go index 21c3008f719..d9e92a73a36 100644 --- a/cmd/bundle/utils/process.go +++ b/cmd/bundle/utils/process.go @@ -28,10 +28,10 @@ import ( "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dms" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" "github.com/databricks/cli/libs/retry" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/sync" "github.com/databricks/cli/libs/telemetry" "github.com/databricks/cli/libs/telemetry/protos" @@ -721,8 +721,7 @@ func isNewerVersion(stateVersion, currentVersion string) bool { func rejectDefinitions(ctx context.Context, b *bundle.Bundle) { if b.Config.Definitions != nil { - v := dyn.GetValue(b.Config.Value(), "definitions") - loc := v.Locations() + loc := b.Config.GetLocationsOf(structpath.NewPath(nil, "definitions")) filename := "input yaml" if len(loc) > 0 { filename = filepath.ToSlash(loc[0].File) diff --git a/cmd/bundle/validate.go b/cmd/bundle/validate.go index d28f4ab8d3b..7b0e1cfcaf7 100644 --- a/cmd/bundle/validate.go +++ b/cmd/bundle/validate.go @@ -10,7 +10,6 @@ import ( "github.com/databricks/cli/bundle/render" "github.com/databricks/cli/cmd/bundle/utils" "github.com/databricks/cli/cmd/root" - "github.com/databricks/cli/libs/dyn/convert" "github.com/databricks/cli/libs/flags" "github.com/databricks/cli/libs/logdiag" "github.com/spf13/cobra" @@ -24,11 +23,7 @@ func renderJsonOutput(cmd *cobra.Command, b *bundle.Bundle) error { if err != nil { return err } - converted, err := convert.FromTyped(redactedRoot, b.Config.Value()) - if err != nil { - return err - } - buf, err := json.MarshalIndent(converted.AsAny(), "", " ") + buf, err := json.MarshalIndent(redactedRoot.View().AsAny(), "", " ") if err != nil { return err } diff --git a/cmd/pipelines/generate.go b/cmd/pipelines/generate.go index c865213c36a..cf362bd89f1 100644 --- a/cmd/pipelines/generate.go +++ b/cmd/pipelines/generate.go @@ -10,12 +10,9 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/yamlloader" - "github.com/databricks/cli/libs/dyn/yamlsaver" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/structs/structvar" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/databricks/databricks-sdk-go/service/pipelines" "github.com/spf13/cobra" ) @@ -83,8 +80,7 @@ Use --existing-pipeline-dir to generate pipeline configuration from spark-pipeli return fmt.Errorf("failed to construct .pipeline.yml: %w", err) } - saver := yamlsaver.NewSaver() - err = saver.SaveAsYAML(resourcesMap, outputFile, force) + err = structyaml.Save(outputFile, resourcesMap, force, nil) if err != nil { return err } @@ -208,31 +204,21 @@ func parseSparkPipelineYAML(ctx context.Context, filePath string) (*sdpPipeline, } defer file.Close() - dv, err := yamlloader.LoadYAML(filePath, file) + out := sdpPipeline{} + _, diags, err := structvar.DecodeYAML(filePath, file, &out) if err != nil { return nil, fmt.Errorf("failed to load %s: %w", filePath, err) } - out := sdpPipeline{} - normalized, diags := convert.Normalize(&out, dv) - if diags.HasError() { - return nil, fmt.Errorf("failed to parse %s: %w", filePath, diags.Error()) - } - for _, diag := range diags { logdiag.LogDiag(ctx, diag) } - err = convert.ToTyped(&out, normalized) - if err != nil { - return nil, fmt.Errorf("failed to parse %s: %w", filePath, diags.Error()) - } - return &out, nil } // convertToResources converts a spark-pipeline.yml spec to DABs YAML format with "resources" property -func convertToResources(spec *sdpPipeline, resourceName, srcFolder string) (map[string]dyn.Value, error) { +func convertToResources(spec *sdpPipeline, resourceName, srcFolder string) (structyaml.Map, error) { // YAML paths are relative to directory containing YAML file, in this case: // DABs YAML is in "./resources/.pipeline.yml" // SDP YAML is in ".//spark-pipeline.yml" @@ -256,55 +242,29 @@ func convertToResources(spec *sdpPipeline, resourceName, srcFolder string) (map[ }, } - environmentDyn, err := convert.FromTyped(environment, dyn.NilValue) - if err != nil { - return nil, fmt.Errorf("failed to convert environments into dyn.Value: %w", err) - } - - librariesDyn, err := convertLibraries(relativePath, spec.Libraries) - if err != nil { - return nil, fmt.Errorf("failed to convert libraries into dyn.Value: %w", err) - } - - // maps are unordered, and saver is sorting keys by diag.Location - // this is helper function to monotonically assign locations as keys are created - var line int - nextLocation := func() []diag.Location { - line += 1 - return []diag.Location{{Line: line}} - } - - pipelineMap := map[string]dyn.Value{ - "name": dyn.V(spec.Name).WithLocations(nextLocation()), - "catalog": dyn.V(catalog).WithLocations(nextLocation()), - "schema": dyn.V(schema).WithLocations(nextLocation()), - "root_path": dyn.V(relativePath).WithLocations(nextLocation()), - "serverless": dyn.V(true).WithLocations(nextLocation()), - "libraries": librariesDyn.WithLocations(nextLocation()), - } + // Keys are written in the order they are added. + pipelineMap := structyaml.M( + "name", spec.Name, + "catalog", catalog, + "schema", schema, + "root_path", relativePath, + "serverless", true, + "libraries", convertLibraries(relativePath, spec.Libraries), + ) // configuration is optional field, skip if empty if spec.Configuration != nil { - dv, err := convert.FromTyped(spec.Configuration, dyn.NilValue) - if err != nil { - return nil, fmt.Errorf("failed to convert configuration into dyn.Value: %w", err) - } - - // NB: golang maps are unordered, and currently we don't preserve the order - pipelineMap["configuration"] = dv.WithLocations(nextLocation()) + pipelineMap.Add("configuration", structyaml.Value(spec.Configuration)) } - pipelineMap["environment"] = environmentDyn.WithLocations(nextLocation()) + pipelineMap.Add("environment", structyaml.Value(environment)) - resourcesMap := map[string]dyn.Value{ - "resources": dyn.V(map[string]dyn.Value{ - "pipelines": dyn.V(map[string]dyn.Value{ - resourceName: dyn.V(pipelineMap), - }), - }), - } + resourcesMap := structyaml.M("resources", structyaml.M("pipelines", structyaml.M(resourceName, pipelineMap))) - _, diag := convert.Normalize(&config.Root{}, dyn.V(resourcesMap)) + _, diag, err := structvar.DecodeYAMLNode("", structyaml.Node(resourcesMap, nil), &config.Root{}, nil) + if err != nil { + return nil, err + } if len(diag) > 0 { return nil, fmt.Errorf("generated output doesn't match expected schema: %v", diag) } @@ -316,7 +276,7 @@ func convertToResources(spec *sdpPipeline, resourceName, srcFolder string) (map[ // // relativePath contains a path to append into SDP libraries path to make // them relative to generated DABs YAML -func convertLibraries(relativePath string, specLibraries []sdpPipelineLibrary) (dyn.Value, error) { +func convertLibraries(relativePath string, specLibraries []sdpPipelineLibrary) any { var libraries []pipelines.PipelineLibrary for _, lib := range specLibraries { @@ -329,16 +289,11 @@ func convertLibraries(relativePath string, specLibraries []sdpPipelineLibrary) ( } } - librariesDyn, err := convert.FromTyped(libraries, dyn.NilValue) - if err != nil { - return dyn.InvalidValue, fmt.Errorf("failed to convert libraries into dyn.Value: %w", err) - } - - // FromTyped returns NilValue if libraries is an empty array - if librariesDyn.Kind() == dyn.KindNil { - // we always want to leave empty array as a placeholder in generated YAML - return dyn.V([]dyn.Value{}), nil + // Value returns nil if libraries is an empty array + if v := structyaml.Value(libraries); v != nil { + return v } - return librariesDyn, nil + // we always want to leave empty array as a placeholder in generated YAML + return []any{} } diff --git a/cmd/pipelines/generate_test.go b/cmd/pipelines/generate_test.go index 7135031ccbb..c795d463164 100644 --- a/cmd/pipelines/generate_test.go +++ b/cmd/pipelines/generate_test.go @@ -5,9 +5,7 @@ import ( "path/filepath" "testing" - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynassert" + "github.com/databricks/cli/libs/structs/structyaml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -174,38 +172,27 @@ func TestConvertToResources(t *testing.T) { }, } - expected := map[string]dyn.Value{ - "resources": dyn.V(map[string]dyn.Value{ - "pipelines": dyn.V(map[string]dyn.Value{ - "my_pipeline": dyn.V(map[string]dyn.Value{ - "name": dyn.V("My Pipeline").WithLocations([]diag.Location{{Line: 1}}), - "catalog": dyn.V("${var.catalog}").WithLocations([]diag.Location{{Line: 2}}), - "schema": dyn.V("${var.schema}").WithLocations([]diag.Location{{Line: 3}}), - "root_path": dyn.V("../src/my_pipeline").WithLocations([]diag.Location{{Line: 4}}), - "serverless": dyn.V(true).WithLocations([]diag.Location{{Line: 5}}), - "libraries": dyn.V([]dyn.Value{ - dyn.V(map[string]dyn.Value{ - "glob": dyn.V(map[string]dyn.Value{ - "include": dyn.V("../src/my_pipeline/transformations/**"), - }), - }), - }).WithLocations([]diag.Location{{Line: 6}}), - "configuration": dyn.V(map[string]dyn.Value{ - "key0": dyn.V("value0"), - "key1": dyn.V("value1"), - }).WithLocations([]diag.Location{{Line: 7}}), - "environment": dyn.V(map[string]dyn.Value{ - "dependencies": dyn.V([]dyn.Value{ - dyn.V("--editable ${workspace.file_path}"), - }), - }).WithLocations([]diag.Location{{Line: 8}}), - }), - }), - }), - } + expected := structyaml.M( + "resources", structyaml.M( + "pipelines", structyaml.M( + "my_pipeline", structyaml.M( + "name", "My Pipeline", + "catalog", "${var.catalog}", + "schema", "${var.schema}", + "root_path", "../src/my_pipeline", + "serverless", true, + "libraries", []any{ + structyaml.M("glob", structyaml.M("include", "../src/my_pipeline/transformations/**")), + }, + "configuration", structyaml.M("key0", "value0", "key1", "value1"), + "environment", structyaml.M("dependencies", []any{"--editable ${workspace.file_path}"}), + ), + ), + ), + ) actual, err := convertToResources(&input, "my_pipeline", "src/my_pipeline") require.NoError(t, err) - dynassert.Equal(t, dyn.V(expected), dyn.V(actual)) + assert.Equal(t, expected, actual) } diff --git a/integration/assumptions/dashboard_assumptions_test.go b/integration/assumptions/dashboard_assumptions_test.go index 111482a6f81..e7d6bd44a2e 100644 --- a/integration/assumptions/dashboard_assumptions_test.go +++ b/integration/assumptions/dashboard_assumptions_test.go @@ -6,9 +6,8 @@ import ( "github.com/databricks/cli/integration/internal/acc" "github.com/databricks/cli/internal/testutil" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/merge" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/service/dashboards" "github.com/databricks/databricks-sdk-go/service/workspace" @@ -86,27 +85,21 @@ func TestDashboardAssumptions_WorkspaceImport(t *testing.T) { }) require.NoError(t, err) - // Convert the dashboard object to a [dyn.Value] to make comparison easier. - previous, err := convert.FromTyped(previousDashboard, dyn.NilValue) - require.NoError(t, err) - current, err := convert.FromTyped(currentDashboard, dyn.NilValue) - require.NoError(t, err) - // Collect updated and deleted paths. var updatedFieldPaths []string var deletedFieldPaths []string - _, err = merge.Override(previous, current, merge.OverrideVisitor{ - VisitDelete: func(basePath dyn.Path, left dyn.Value) error { - deletedFieldPaths = append(deletedFieldPaths, basePath.String()) + _, err = structvar.PlanOverride(structvar.NewView(previousDashboard, nil, nil), structvar.NewView(currentDashboard, nil, nil), structvar.OverrideVisitor{ + VisitDelete: func(path *structpath.PathNode, left structvar.View) error { + deletedFieldPaths = append(deletedFieldPaths, path.String()) return nil }, - VisitInsert: func(basePath dyn.Path, right dyn.Value) (dyn.Value, error) { + VisitInsert: func(path *structpath.PathNode, right structvar.View) error { assert.Fail(t, "unexpected insert operation") - return right, nil + return nil }, - VisitUpdate: func(basePath dyn.Path, left, right dyn.Value) (dyn.Value, error) { - updatedFieldPaths = append(updatedFieldPaths, basePath.String()) - return right, nil + VisitUpdate: func(path *structpath.PathNode, left, right structvar.View) error { + updatedFieldPaths = append(updatedFieldPaths, path.String()) + return nil }, }) require.NoError(t, err) diff --git a/libs/dyn/convert/end_to_end_test.go b/libs/dyn/convert/end_to_end_test.go deleted file mode 100644 index e3ea273cb04..00000000000 --- a/libs/dyn/convert/end_to_end_test.go +++ /dev/null @@ -1,171 +0,0 @@ -package convert - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func assertFromTypedToTypedEqual[T any](t *testing.T, src T) { - nv, err := FromTyped(src, dyn.NilValue) - require.NoError(t, err) - - var dst T - err = ToTyped(&dst, nv) - require.NoError(t, err) - assert.Equal(t, src, dst) -} - -func TestAdditional(t *testing.T) { - type StructType struct { - Str string `json:"str"` - } - - type Tmp struct { - MapToPointer map[string]*string `json:"map_to_pointer"` - SliceOfPointer []*string `json:"slice_of_pointer"` - NestedStruct StructType `json:"nested_struct"` - } - - t.Run("nil", func(t *testing.T) { - assertFromTypedToTypedEqual(t, Tmp{}) - }) - - t.Run("empty map", func(t *testing.T) { - assertFromTypedToTypedEqual(t, Tmp{ - MapToPointer: map[string]*string{}, - }) - }) - - t.Run("map with empty string value", func(t *testing.T) { - s := "" - assertFromTypedToTypedEqual(t, Tmp{ - MapToPointer: map[string]*string{ - "key": &s, - }, - }) - }) - - t.Run("map with nil value", func(t *testing.T) { - assertFromTypedToTypedEqual(t, Tmp{ - MapToPointer: map[string]*string{ - "key": nil, - }, - }) - }) - - t.Run("empty slice", func(t *testing.T) { - assertFromTypedToTypedEqual(t, Tmp{ - SliceOfPointer: []*string{}, - }) - }) - - t.Run("slice with nil value", func(t *testing.T) { - assertFromTypedToTypedEqual(t, Tmp{ - SliceOfPointer: []*string{nil}, - }) - }) - - t.Run("pointer to a empty string", func(t *testing.T) { - s := "" - assertFromTypedToTypedEqual(t, &s) - }) - - t.Run("nil pointer", func(t *testing.T) { - var s *string - assertFromTypedToTypedEqual(t, s) - }) - - t.Run("pointer to struct with scalar values", func(t *testing.T) { - s := "" - type foo struct { - A string `json:"a"` - B int `json:"b"` - C bool `json:"c"` - D *string `json:"d"` - } - assertFromTypedToTypedEqual(t, &foo{ - A: "a", - B: 1, - C: true, - D: &s, - }) - assertFromTypedToTypedEqual(t, &foo{ - A: "", - B: 0, - C: false, - D: nil, - }) - }) - - t.Run("map with scalar values", func(t *testing.T) { - assertFromTypedToTypedEqual(t, map[string]string{ - "a": "a", - "b": "b", - "c": "", - }) - assertFromTypedToTypedEqual(t, map[string]int{ - "a": 1, - "b": 0, - "c": 2, - }) - }) -} - -func TestEndToEndForceSendFields(t *testing.T) { - type Inner struct { - InnerField string `json:"inner_field"` - ForceSendFields []string `json:"-"` - } - type Outer struct { - OuterField string `json:"outer_field"` - Inner - } - - // Test with zero value in embedded struct - src := Outer{ - OuterField: "outer_value", - Inner: Inner{ - InnerField: "", // Zero value - ForceSendFields: []string{"InnerField"}, // Should be preserved - }, - } - - assertFromTypedToTypedEqual(t, src) -} - -func TestEndToEndPointerForceSendFields(t *testing.T) { - type NewCluster struct { - NumWorkers int `json:"num_workers"` - SparkVersion string `json:"spark_version"` - ForceSendFields []string `json:"-"` - } - type JobCluster struct { - JobClusterKey string `json:"job_cluster_key"` - NewCluster *NewCluster `json:"new_cluster"` - } - type JobSettings struct { - JobClusters []JobCluster `json:"job_clusters"` - Name string `json:"name"` - ForceSendFields []string `json:"-"` - } - - // Test with zero value in pointer embedded struct (like acceptance test) - src := JobSettings{ - Name: "test-job", - JobClusters: []JobCluster{ - { - JobClusterKey: "key", - NewCluster: &NewCluster{ - NumWorkers: 0, // Zero value - SparkVersion: "13.3.x-scala2.12", - ForceSendFields: []string{"NumWorkers"}, // Should be preserved - }, - }, - }, - } - - assertFromTypedToTypedEqual(t, src) -} diff --git a/libs/dyn/convert/error.go b/libs/dyn/convert/error.go deleted file mode 100644 index d3770d82d74..00000000000 --- a/libs/dyn/convert/error.go +++ /dev/null @@ -1,16 +0,0 @@ -package convert - -import ( - "fmt" - - "github.com/databricks/cli/libs/dyn" -) - -type TypeError struct { - value dyn.Value - msg string -} - -func (e TypeError) Error() string { - return fmt.Sprintf("%s: %s", e.value.Location(), e.msg) -} diff --git a/libs/dyn/convert/from_typed.go b/libs/dyn/convert/from_typed.go deleted file mode 100644 index 66451124293..00000000000 --- a/libs/dyn/convert/from_typed.go +++ /dev/null @@ -1,362 +0,0 @@ -package convert - -import ( - "fmt" - "reflect" - "slices" - - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" -) - -type fromTypedOptions int - -const ( - // If this flag is set, zero values in the typed representation are resolved to - // the equivalent zero value in the dynamic representation. - // If it is not set, zero values resolve to [dyn.NilValue]. - // - // This flag exists to reconcile default values in Go being zero values with values - // being intentionally set to their zero value. We capture zero values in the dynamic - // configuration if they are 1) behind a pointer, 2) a map value, 3) a slice element, - // in the typed configuration. - includeZeroValues fromTypedOptions = 1 << iota -) - -// FromTyped converts changes made in the typed structure w.r.t. the configuration value -// back to the configuration value, retaining existing location information where possible. -// -// It uses the reference value both for location information and to determine if the typed -// value was changed or not. For example, if a struct-by-value field is nil in the reference -// it will be zero-valued in the typed configuration. If it remains zero-valued, this -// function will still emit a nil value in the dynamic representation. -func FromTyped(src any, ref dyn.Value) (dyn.Value, error) { - return fromTyped(src, ref) -} - -// Private implementation of FromTyped that allows for additional options not exposed -// in the public API. -func fromTyped(src any, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) { - srcv := reflect.ValueOf(src) - - // Dereference pointer if necessary - for srcv.Kind() == reflect.Pointer { - if srcv.IsNil() { - return dyn.NilValue.WithLocations(ref.Locations()), nil - } - srcv = srcv.Elem() - - // If a pointer to a type points to a zero value, we should include - // that zero value in the dynamic representation. - // This is because by default a pointer is nil in Go, and it not being nil - // indicates its value was intentionally set to zero. - if !slices.Contains(options, includeZeroValues) { - options = append(options, includeZeroValues) - } - } - - var v dyn.Value - var err error - switch srcv.Kind() { - case reflect.Struct: - // Handle SDK native types using JSON marshaling. - if slices.Contains(sdkNativeTypes, srcv.Type()) { - v, err = fromTypedSDKNative(srcv, ref, options...) - } else { - v, err = fromTypedStruct(srcv, ref, options...) - } - case reflect.Map: - v, err = fromTypedMap(srcv, ref) - case reflect.Slice: - v, err = fromTypedSlice(srcv, ref) - case reflect.String: - v, err = fromTypedString(srcv, ref, options...) - case reflect.Bool: - v, err = fromTypedBool(srcv, ref, options...) - case reflect.Int, reflect.Int32, reflect.Int64: - v, err = fromTypedInt(srcv, ref, options...) - case reflect.Float32, reflect.Float64: - v, err = fromTypedFloat(srcv, ref, options...) - case reflect.Invalid: - // If the value is untyped and not set (e.g. any type with nil value), we return nil. - v, err = dyn.NilValue, nil - default: - return dyn.InvalidValue, fmt.Errorf("unsupported type: %s", srcv.Kind()) - } - - // Ensure the location metadata is retained. - if err != nil { - return dyn.InvalidValue, err - } - return v.WithLocations(ref.Locations()), err -} - -func fromTypedStruct(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) { - // Check that the reference value is compatible or nil. - switch ref.Kind() { - case dyn.KindString: - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(ref.MustString()) { - return ref, nil - } - case dyn.KindMap, dyn.KindNil: - default: - return dyn.InvalidValue, fmt.Errorf("cannot convert struct field to dynamic type %#v: src=%#v ref=%#v", ref.Kind().String(), src, ref.AsAny()) - } - - refm, _ := ref.AsMap() - out := dyn.NewMapping() - info := getStructInfo(src.Type()) - - for _, fieldval := range info.FieldValues(src) { - k := fieldval.Key - v := fieldval.Value - isForced := fieldval.IsForced - pair, ok := refm.GetPairByString(k) - refloc := pair.Key.Locations() - refv := pair.Value - - // Use nil reference if there is no reference for this key - if !ok { - refloc = nil - refv = dyn.NilValue - } - - var options []fromTypedOptions - if v.Kind() == reflect.Interface { - options = append(options, includeZeroValues) - } - - // Convert the field taking into account the reference value (may be equal to config.NilValue). - nv, err := fromTyped(v.Interface(), refv, options...) - if err != nil { - return dyn.InvalidValue, err - } - - // Either if the key was set in the reference, the field is not zero-valued, OR it's forced - if ok || nv.Kind() != dyn.KindNil || isForced { - // If v isZero, it could be because it's a variable reference; so we check that nv is zero as well - // BUT: always include if it's forced - if v.Kind() != reflect.Struct && v.IsZero() && nv.IsZero() && !info.ForceEmpty[k] && !isForced { - continue - } - - // If the field is forced but nv is nil, convert it to the appropriate zero value - if isForced && nv.Kind() == dyn.KindNil { - // Convert the zero value using proper recursive conversion instead of dyn.V() directly - // This prevents "not handled" panics for complex types like structs, slices, maps, etc. - // Use refv to preserve location information from the original reference - var err error - nv, err = fromTyped(v.Interface(), refv, includeZeroValues) - if err != nil { - return dyn.InvalidValue, err - } - } - - out.SetLoc(k, refloc, nv) - } - } - - // Return the new mapping if: - // 1. The mapping has entries (i.e. the struct was not empty). - // 2. The reference is a map (i.e. the struct was and still is empty). - // 3. The "includeZeroValues" option is set (i.e. the struct is a non-nil pointer). - if out.Len() > 0 || ref.Kind() == dyn.KindMap || slices.Contains(options, includeZeroValues) { - return dyn.V(out), nil - } - - // Otherwise, return nil. - return dyn.NilValue, nil -} - -func fromTypedMap(src reflect.Value, ref dyn.Value) (dyn.Value, error) { - // Check that the reference value is compatible or nil. - switch ref.Kind() { - case dyn.KindString: - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(ref.MustString()) { - return ref, nil - } - case dyn.KindMap, dyn.KindNil: - default: - return dyn.InvalidValue, fmt.Errorf("cannot convert map field to dynamic type %#v: src=%#v ref=%#v", ref.Kind().String(), src, ref.AsAny()) - } - - // Return nil if the map is nil. - if src.IsNil() { - return dyn.NilValue, nil - } - - refm, _ := ref.AsMap() - out := dyn.NewMapping() - iter := src.MapRange() - for iter.Next() { - k := iter.Key().String() - v := iter.Value() - pair, ok := refm.GetPairByString(k) - refloc := pair.Key.Locations() - refv := pair.Value - - // Use nil reference if there is no reference for this key - if !ok { - refloc = nil - refv = dyn.NilValue - } - - // Convert entry taking into account the reference value (may be equal to dyn.NilValue). - nv, err := fromTyped(v.Interface(), refv, includeZeroValues) - if err != nil { - return dyn.InvalidValue, err - } - - // Every entry is represented, even if it is a nil. - // Otherwise, a map with zero-valued structs would yield a nil as well. - out.SetLoc(k, refloc, nv) - } - - return dyn.V(out), nil -} - -func fromTypedSlice(src reflect.Value, ref dyn.Value) (dyn.Value, error) { - // Check that the reference value is compatible or nil. - switch ref.Kind() { - case dyn.KindString: - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(ref.MustString()) { - return ref, nil - } - case dyn.KindSequence, dyn.KindNil: - default: - return dyn.InvalidValue, fmt.Errorf("cannot convert slice field to dynamic type %#v: src=%#v ref=%#v", ref.Kind().String(), src, ref.AsAny()) - } - - // Return nil if the slice is nil. - if src.IsNil() { - return dyn.NilValue, nil - } - - out := make([]dyn.Value, src.Len()) - for i := range src.Len() { - v := src.Index(i) - refv := ref.Index(i) - - // Use nil reference if there is no reference for this index. - if refv.Kind() == dyn.KindInvalid { - refv = dyn.NilValue - } - - // Convert entry taking into account the reference value (may be equal to dyn.NilValue). - nv, err := fromTyped(v.Interface(), refv, includeZeroValues) - if err != nil { - return dyn.InvalidValue, err - } - - out[i] = nv - } - - return dyn.V(out), nil -} - -func fromTypedString(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) { - switch ref.Kind() { - case dyn.KindString: - value := src.String() - if value == ref.MustString() { - return ref, nil - } - - return dyn.V(value), nil - case dyn.KindNil: - // This field is not set in the reference. We set it to nil if it's zero - // valued in the typed representation and the includeZeroValues option is not set. - if src.IsZero() && !slices.Contains(options, includeZeroValues) { - return dyn.NilValue, nil - } - return dyn.V(src.String()), nil - default: - // Fall through to the error case. - } - - return dyn.InvalidValue, fmt.Errorf("cannot convert string field to dynamic type %#v: src=%#v ref=%#v", ref.Kind().String(), src, ref.AsAny()) -} - -func fromTypedBool(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) { - switch ref.Kind() { - case dyn.KindBool: - value := src.Bool() - if value == ref.MustBool() { - return ref, nil - } - return dyn.V(value), nil - case dyn.KindNil: - // This field is not set in the reference. We set it to nil if it's zero - // valued in the typed representation and the includeZeroValues option is not set. - if src.IsZero() && !slices.Contains(options, includeZeroValues) { - return dyn.NilValue, nil - } - return dyn.V(src.Bool()), nil - case dyn.KindString: - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(ref.MustString()) { - return ref, nil - } - default: - // Fall through to the error case. - } - - return dyn.InvalidValue, fmt.Errorf("cannot convert bool field to dynamic type %#v: src=%#v ref=%#v", ref.Kind().String(), src, ref.AsAny()) -} - -func fromTypedInt(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) { - switch ref.Kind() { - case dyn.KindInt: - value := src.Int() - if value == ref.MustInt() { - return ref, nil - } - return dyn.V(value), nil - case dyn.KindNil: - // This field is not set in the reference. We set it to nil if it's zero - // valued in the typed representation and the includeZeroValues option is not set. - if src.IsZero() && !slices.Contains(options, includeZeroValues) { - return dyn.NilValue, nil - } - return dyn.V(src.Int()), nil - case dyn.KindString: - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(ref.MustString()) { - return ref, nil - } - default: - // Fall through to the error case. - } - - return dyn.InvalidValue, fmt.Errorf("cannot convert int field to dynamic type %#v: src=%#v ref=%#v", ref.Kind().String(), src, ref.AsAny()) -} - -func fromTypedFloat(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) { - switch ref.Kind() { - case dyn.KindFloat: - value := src.Float() - if value == ref.MustFloat() { - return ref, nil - } - return dyn.V(value), nil - case dyn.KindNil: - // This field is not set in the reference. We set it to nil if it's zero - // valued in the typed representation and the includeZeroValues option is not set. - if src.IsZero() && !slices.Contains(options, includeZeroValues) { - return dyn.NilValue, nil - } - return dyn.V(src.Float()), nil - case dyn.KindString: - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(ref.MustString()) { - return ref, nil - } - default: - // Fall through to the error case. - } - - return dyn.InvalidValue, fmt.Errorf("cannot convert float field to dynamic type %#v: src=%#v ref=%#v", ref.Kind().String(), src, ref.AsAny()) -} diff --git a/libs/dyn/convert/from_typed_test.go b/libs/dyn/convert/from_typed_test.go deleted file mode 100644 index 3420c46cbd7..00000000000 --- a/libs/dyn/convert/from_typed_test.go +++ /dev/null @@ -1,927 +0,0 @@ -package convert - -import ( - "testing" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynassert" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestFromTypedStructZeroFields(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar"` - } - - src := Tmp{} - - // For an empty struct with a nil reference we expect a nil. - nv, err := FromTyped(src, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) - - // For an empty struct with a non-nil reference we expect an empty map. - nv, err = FromTyped(src, dyn.V(map[string]dyn.Value{})) - require.NoError(t, err) - assert.Equal(t, dyn.V(map[string]dyn.Value{}), nv) -} - -func TestFromTypedStructPointerZeroFields(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar"` - } - - var src *Tmp - var nv dyn.Value - var err error - - // For a nil pointer with a nil reference we expect a nil. - src = nil - nv, err = FromTyped(src, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) - - // For a nil pointer with a non-nil reference we expect a nil. - src = nil - nv, err = FromTyped(src, dyn.V(map[string]dyn.Value{})) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) - - // For an initialized pointer with a nil reference we expect an empty map. - src = &Tmp{} - nv, err = FromTyped(src, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, dyn.V(map[string]dyn.Value{}), nv) - - // For an initialized pointer with a non-nil reference we expect an empty map. - src = &Tmp{} - nv, err = FromTyped(src, dyn.V(map[string]dyn.Value{})) - require.NoError(t, err) - assert.Equal(t, dyn.V(map[string]dyn.Value{}), nv) -} - -func TestFromTypedStructNilFields(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar"` - } - - // For a zero value struct with a reference containing nil fields we expect the nils to be retained. - src := Tmp{} - ref := dyn.V(map[string]dyn.Value{ - "foo": dyn.NilValue, - "bar": dyn.NilValue, - }) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.NilValue, - "bar": dyn.NilValue, - }), nv) -} - -func TestFromTypedStructSetFields(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar"` - } - - src := Tmp{ - Foo: "foo", - Bar: "bar", - } - - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V("foo"), - "bar": dyn.V("bar"), - }), nv) -} - -func TestFromTypedStructSetFieldsIntoExistingFields(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar"` - } - - src := Tmp{ - Foo: "foo", - Bar: "bar", - } - - ref := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("foo_old"), - "bar": dyn.V("bar_old"), - }) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V("foo"), - "bar": dyn.V("bar"), - }), nv) -} - -func TestFromTypedStructZeroFieldsIntoExistingFields(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar,omitempty"` - BazBugz string `json:"baz_bugs,omitempty"` - ForceSendFields []string `json:"-"` - } - - src := Tmp{ForceSendFields: []string{"BazBugz"}} - - ref := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("foo_old"), - "bar": dyn.V("bar_old"), - "baz_bugs": dyn.V("baz_old"), - }) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V(""), - "baz_bugs": dyn.V(""), - }), nv) -} - -func TestFromTypedStructZeroFieldsIntoExistingFieldsFloat64(t *testing.T) { - type Tmp struct { - Foo float64 `json:"foo"` - Bar float64 `json:"bar,omitempty"` - BazBugz float64 `json:"baz_bugs,omitempty"` - ForceSendFields []string `json:"-"` - } - - src := Tmp{ForceSendFields: []string{"BazBugz"}} - - ref := dyn.V(map[string]dyn.Value{ - "foo": dyn.V(1.2), - "bar": dyn.V(1.2), - "baz_bugs": dyn.V(1.2), - }) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V(0.0), - "baz_bugs": dyn.V(0.0), - }), nv) -} - -func TestFromTypedStructSetFieldsRetainLocation(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar"` - } - - src := Tmp{ - Foo: "bar", - Bar: "qux", - } - - ref := dyn.V(map[string]dyn.Value{ - "foo": dyn.NewValue("bar", []diag.Location{{File: "foo"}}), - "bar": dyn.NewValue("baz", []diag.Location{{File: "bar"}}), - }) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - - // Assert foo and bar have retained their location. - assert.Equal(t, dyn.NewValue("bar", []diag.Location{{File: "foo"}}), nv.Get("foo")) - assert.Equal(t, dyn.NewValue("qux", []diag.Location{{File: "bar"}}), nv.Get("bar")) -} - -func TestFromTypedStringMapWithZeroValue(t *testing.T) { - ref := dyn.NilValue - src := map[string]string{ - "foo": "", - "bar": "fuzz", - } - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V(""), - "bar": dyn.V("fuzz"), - }), nv) -} - -func TestFromTypedStringSliceWithZeroValue(t *testing.T) { - ref := dyn.NilValue - src := []string{"a", "", "c"} - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V([]dyn.Value{ - dyn.V("a"), dyn.V(""), dyn.V("c"), - }), nv) -} - -func TestFromTypedStringStructWithZeroValue(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar"` - } - - ref := dyn.NilValue - src := Tmp{ - Foo: "foo", - Bar: "", - } - - // Note, the zero value is not included in the output. - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V("foo"), - }), nv) -} - -func TestFromTypedBoolMapWithZeroValue(t *testing.T) { - ref := dyn.NilValue - src := map[string]bool{ - "foo": false, - "bar": true, - } - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V(false), - "bar": dyn.V(true), - }), nv) -} - -func TestFromTypedBoolSliceWithZeroValue(t *testing.T) { - ref := dyn.NilValue - src := []bool{true, false, true} - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V([]dyn.Value{ - dyn.V(true), dyn.V(false), dyn.V(true), - }), nv) -} - -func TestFromTypedBoolStructWithZeroValue(t *testing.T) { - type Tmp struct { - Foo bool `json:"foo"` - Bar bool `json:"bar"` - } - - ref := dyn.NilValue - src := Tmp{ - Foo: true, - Bar: false, - } - - // Note, the zero value is not included in the output. - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V(true), - }), nv) -} - -func TestFromTypedIntMapWithZeroValue(t *testing.T) { - ref := dyn.NilValue - src := map[string]int{ - "foo": 0, - "bar": 1, - } - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V(int64(0)), - "bar": dyn.V(int64(1)), - }), nv) -} - -func TestFromTypedIntSliceWithZeroValue(t *testing.T) { - ref := dyn.NilValue - src := []int{1, 0, 2} - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V([]dyn.Value{ - dyn.V(int64(1)), dyn.V(int64(0)), dyn.V(int64(2)), - }), nv) -} - -func TestFromTypedIntStructWithZeroValue(t *testing.T) { - type Tmp struct { - Foo int `json:"foo"` - Bar int `json:"bar"` - } - - ref := dyn.NilValue - src := Tmp{ - Foo: 1, - Bar: 0, - } - - // Note, the zero value is not included in the output. - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V(int64(1)), - }), nv) -} - -func TestFromTypedFloatMapWithZeroValue(t *testing.T) { - ref := dyn.NilValue - src := map[string]float64{ - "foo": 0.0, - "bar": 1.0, - } - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V(0.0), - "bar": dyn.V(1.0), - }), nv) -} - -func TestFromTypedFloatSliceWithZeroValue(t *testing.T) { - ref := dyn.NilValue - src := []float64{1.0, 0.0, 2.0} - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V([]dyn.Value{ - dyn.V(1.0), dyn.V(0.0), dyn.V(2.0), - }), nv) -} - -func TestFromTypedFloatStructWithZeroValue(t *testing.T) { - type Tmp struct { - Foo float64 `json:"foo"` - Bar float64 `json:"bar"` - } - - ref := dyn.NilValue - src := Tmp{ - Foo: 1.0, - Bar: 0.0, - } - - // Note, the zero value is not included in the output. - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V(1.0), - }), nv) -} - -func TestFromTypedMapNil(t *testing.T) { - var src map[string]string = nil - - ref := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V("baz"), - }) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) -} - -func TestFromTypedMapEmpty(t *testing.T) { - src := map[string]string{} - - ref := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V("baz"), - }) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(map[string]dyn.Value{}), nv) -} - -func TestFromTypedMapNonEmpty(t *testing.T) { - src := map[string]string{ - "foo": "foo", - "bar": "bar", - } - - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V("foo"), - "bar": dyn.V("bar"), - }), nv) -} - -func TestFromTypedMapNonEmptyRetainLocation(t *testing.T) { - src := map[string]string{ - "foo": "bar", - "bar": "qux", - } - - ref := dyn.V(map[string]dyn.Value{ - "foo": dyn.NewValue("bar", []diag.Location{{File: "foo"}}), - "bar": dyn.NewValue("baz", []diag.Location{{File: "bar"}}), - }) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - - // Assert foo and bar have retained their locations. - assert.Equal(t, dyn.NewValue("bar", []diag.Location{{File: "foo"}}), nv.Get("foo")) - assert.Equal(t, dyn.NewValue("qux", []diag.Location{{File: "bar"}}), nv.Get("bar")) -} - -func TestFromTypedMapFieldWithZeroValue(t *testing.T) { - src := map[string]string{ - "foo": "", - } - - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V(""), - }), nv) -} - -func TestFromTypedSliceNil(t *testing.T) { - var src []string = nil - - ref := dyn.V([]dyn.Value{ - dyn.V("bar"), - dyn.V("baz"), - }) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) -} - -func TestFromTypedSliceEmpty(t *testing.T) { - //nolint:gocritic - src := []string{} - - ref := dyn.V([]dyn.Value{ - dyn.V("bar"), - dyn.V("baz"), - }) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V([]dyn.Value{}), nv) -} - -func TestFromTypedSliceNonEmpty(t *testing.T) { - src := []string{ - "foo", - "bar", - } - - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V([]dyn.Value{ - dyn.V("foo"), - dyn.V("bar"), - }), nv) -} - -func TestFromTypedSliceNonEmptyRetainLocation(t *testing.T) { - src := []string{ - "foo", - "bar", - } - - ref := dyn.V([]dyn.Value{ - dyn.NewValue("foo", []diag.Location{{File: "foo"}}), - dyn.NewValue("bar", []diag.Location{{File: "bar"}}), - }) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - - // Assert foo and bar have retained their locations. - assert.Equal(t, dyn.NewValue("foo", []diag.Location{{File: "foo"}}), nv.Index(0)) - assert.Equal(t, dyn.NewValue("bar", []diag.Location{{File: "bar"}}), nv.Index(1)) -} - -func TestFromTypedStringEmpty(t *testing.T) { - var src string - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) -} - -func TestFromTypedStringEmptyOverwrite(t *testing.T) { - var src string - ref := dyn.V("old") - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(""), nv) -} - -func TestFromTypedStringNonEmpty(t *testing.T) { - src := "new" - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V("new"), nv) -} - -func TestFromTypedStringNonEmptyOverwrite(t *testing.T) { - src := "new" - ref := dyn.V("old") - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V("new"), nv) -} - -func TestFromTypedStringRetainsLocations(t *testing.T) { - ref := dyn.NewValue("foo", []diag.Location{{File: "foo"}}) - - // case: value has not been changed - src := "foo" - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NewValue("foo", []diag.Location{{File: "foo"}}), nv) - - // case: value has been changed - src = "bar" - nv, err = FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NewValue("bar", []diag.Location{{File: "foo"}}), nv) -} - -func TestFromTypedStringTypeError(t *testing.T) { - src := "foo" - ref := dyn.V(1234) - _, err := FromTyped(src, ref) - require.Error(t, err) -} - -func TestFromTypedBoolEmpty(t *testing.T) { - var src bool - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) -} - -func TestFromTypedBoolEmptyOverwrite(t *testing.T) { - var src bool - ref := dyn.V(true) - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(false), nv) -} - -func TestFromTypedBoolNonEmpty(t *testing.T) { - src := true - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(true), nv) -} - -func TestFromTypedBoolNonEmptyOverwrite(t *testing.T) { - src := true - ref := dyn.V(false) - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(true), nv) -} - -func TestFromTypedBoolRetainsLocations(t *testing.T) { - ref := dyn.NewValue(true, []diag.Location{{File: "foo"}}) - - // case: value has not been changed - src := true - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NewValue(true, []diag.Location{{File: "foo"}}), nv) - - // case: value has been changed - src = false - nv, err = FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NewValue(false, []diag.Location{{File: "foo"}}), nv) -} - -func TestFromTypedBoolVariableReference(t *testing.T) { - src := true - ref := dyn.V("${var.foo}") - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V("${var.foo}"), nv) -} - -func TestFromTypedBoolTypeError(t *testing.T) { - src := true - ref := dyn.V("string") - _, err := FromTyped(src, ref) - require.Error(t, err) -} - -func TestFromTypedIntEmpty(t *testing.T) { - var src int - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) -} - -func TestFromTypedIntEmptyOverwrite(t *testing.T) { - var src int - ref := dyn.V(1234) - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(int64(0)), nv) -} - -func TestFromTypedIntNonEmpty(t *testing.T) { - src := 1234 - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(int64(1234)), nv) -} - -func TestFromTypedIntNonEmptyOverwrite(t *testing.T) { - src := 1234 - ref := dyn.V(1233) - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(int64(1234)), nv) -} - -func TestFromTypedIntRetainsLocations(t *testing.T) { - ref := dyn.NewValue(1234, []diag.Location{{File: "foo"}}) - - // case: value has not been changed - src := 1234 - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NewValue(1234, []diag.Location{{File: "foo"}}), nv) - - // case: value has been changed - src = 1235 - nv, err = FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NewValue(int64(1235), []diag.Location{{File: "foo"}}), nv) -} - -func TestFromTypedIntVariableReference(t *testing.T) { - src := 1234 - ref := dyn.V("${var.foo}") - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V("${var.foo}"), nv) -} - -func TestFromTypedIntTypeError(t *testing.T) { - src := 1234 - ref := dyn.V("string") - _, err := FromTyped(src, ref) - require.Error(t, err) -} - -func TestFromTypedFloatEmpty(t *testing.T) { - var src float64 - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) -} - -func TestFromTypedFloatEmptyOverwrite(t *testing.T) { - var src float64 - ref := dyn.V(1.23) - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(0.0), nv) -} - -func TestFromTypedFloatNonEmpty(t *testing.T) { - src := 1.23 - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(1.23), nv) -} - -func TestFromTypedFloatNonEmptyOverwrite(t *testing.T) { - src := 1.23 - ref := dyn.V(1.24) - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V(1.23), nv) -} - -func TestFromTypedFloatRetainsLocations(t *testing.T) { - var src float64 - ref := dyn.NewValue(1.23, []diag.Location{{File: "foo"}}) - - // case: value has not been changed - src = 1.23 - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NewValue(1.23, []diag.Location{{File: "foo"}}), nv) - - // case: value has been changed - src = 1.24 - nv, err = FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NewValue(1.24, []diag.Location{{File: "foo"}}), nv) -} - -func TestFromTypedFloatVariableReference(t *testing.T) { - src := 1.23 - ref := dyn.V("${var.foo}") - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V("${var.foo}"), nv) -} - -func TestFromTypedFloatTypeError(t *testing.T) { - src := 1.23 - ref := dyn.V("string") - _, err := FromTyped(src, ref) - require.Error(t, err) -} - -func TestFromTypedAny(t *testing.T) { - type Tmp struct { - Foo any `json:"foo"` - Bar any `json:"bar"` - Foz any `json:"foz"` - Baz any `json:"baz"` - } - - src := Tmp{ - Foo: "foo", - Bar: false, - Foz: 0, - Baz: map[string]any{ - "foo": "foo", - "bar": 1234, - "qux": 0, - "nil": nil, - }, - } - - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "foo": dyn.V("foo"), - "bar": dyn.V(false), - "foz": dyn.V(int64(0)), - "baz": dyn.V(map[string]dyn.Value{ - "foo": dyn.V("foo"), - "bar": dyn.V(int64(1234)), - "qux": dyn.V(int64(0)), - "nil": dyn.V(nil), - }), - }), nv) -} - -func TestFromTypedAnyNil(t *testing.T) { - var src any = nil - ref := dyn.NilValue - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) -} - -func TestFromTypedNilPointerRetainsLocations(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar"` - } - - var src *Tmp - ref := dyn.NewValue(nil, []diag.Location{{File: "foobar"}}) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NewValue(nil, []diag.Location{{File: "foobar"}}), nv) -} - -func TestFromTypedNilMapRetainsLocation(t *testing.T) { - var src map[string]string - ref := dyn.NewValue(nil, []diag.Location{{File: "foobar"}}) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NewValue(nil, []diag.Location{{File: "foobar"}}), nv) -} - -func TestFromTypedNilSliceRetainsLocation(t *testing.T) { - var src []string - ref := dyn.NewValue(nil, []diag.Location{{File: "foobar"}}) - - nv, err := FromTyped(src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.NewValue(nil, []diag.Location{{File: "foobar"}}), nv) -} - -func TestFromTypedForceSendFieldsComplexTypes(t *testing.T) { - type Inner struct { - Value string `json:"value"` - } - - tests := []struct { - name string - src any - }{ - { - name: "struct_pointer", - src: struct { - Field *Inner `json:"field"` - ForceSendFields []string `json:"-"` - }{Field: nil, ForceSendFields: []string{"Field"}}, - }, - { - name: "struct_value", - src: struct { - Field Inner `json:"field"` - ForceSendFields []string `json:"-"` - }{Field: Inner{}, ForceSendFields: []string{"Field"}}, - }, - { - name: "slice", - src: struct { - Field []string `json:"field"` - ForceSendFields []string `json:"-"` - }{Field: nil, ForceSendFields: []string{"Field"}}, - }, - { - name: "map", - src: struct { - Field map[string]string `json:"field"` - ForceSendFields []string `json:"-"` - }{Field: nil, ForceSendFields: []string{"Field"}}, - }, - { - name: "interface", - src: struct { - Field any `json:"field"` - ForceSendFields []string `json:"-"` - }{Field: (*string)(nil), ForceSendFields: []string{"Field"}}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - nv, err := FromTyped(tt.src, dyn.NilValue) - require.NoError(t, err) - - // All should include the field because it's in ForceSendFields - field := nv.Get("field") - assert.True(t, field.IsValid(), "field should be present due to ForceSendFields") - }) - } -} - -// Test embedded structs with ForceSendFields (separate test due to different structure) -func TestFromTypedForceSendFieldsEmbedded(t *testing.T) { - type Inner struct { - Field *string `json:"field"` - ForceSendFields []string `json:"-"` - } - type Outer struct { - Inner - Other string `json:"other"` - } - - src := Outer{ - Field: nil, - ForceSendFields: []string{"Field"}, - Other: "value", - } - - nv, err := FromTyped(src, dyn.NilValue) - require.NoError(t, err) - - // Both fields should be present - field := nv.Get("field") - other := nv.Get("other") - assert.True(t, field.IsValid(), "embedded field should be present due to ForceSendFields") - assert.Equal(t, dyn.KindNil, field.Kind(), "embedded field should be present due to ForceSendFields") - assert.Equal(t, dyn.V("value"), other) -} diff --git a/libs/dyn/convert/normalize.go b/libs/dyn/convert/normalize.go deleted file mode 100644 index e41ed4a0a87..00000000000 --- a/libs/dyn/convert/normalize.go +++ /dev/null @@ -1,507 +0,0 @@ -package convert - -import ( - "fmt" - "reflect" - "slices" - "strconv" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" -) - -// NormalizeOption is the type for options that can be passed to Normalize. -type NormalizeOption int - -const ( - // IncludeMissingFields causes the normalization to include fields that defined on the given - // type but are missing in the source value. They are included with their zero values. - IncludeMissingFields NormalizeOption = iota - - // DropEmptyStrings drops struct fields whose value is an empty string, unless - // the field is tagged without omitempty (i.e. its zero value must be sent). - // This mirrors JSON serialization, where an omitempty string field set to "" - // is omitted from the request. Without this, an explicitly-set "" reaches - // ToTyped, which force-sends it, and the backend rejects it (e.g. - // "'' is not a valid cluster policy ID"). - DropEmptyStrings -) - -type normalizeOptions struct { - includeMissingFields bool - dropEmptyStrings bool -} - -func Normalize(dst any, src dyn.Value, opts ...NormalizeOption) (dyn.Value, diag.Diagnostics) { - var n normalizeOptions - for _, opt := range opts { - switch opt { - case IncludeMissingFields: - n.includeMissingFields = true - case DropEmptyStrings: - n.dropEmptyStrings = true - } - } - - return n.normalizeType(reflect.TypeOf(dst), src, []reflect.Type{}, dyn.EmptyPath) -} - -func (n normalizeOptions) normalizeType(typ reflect.Type, src dyn.Value, seen []reflect.Type, path dyn.Path) (dyn.Value, diag.Diagnostics) { - for typ.Kind() == reflect.Pointer { - typ = typ.Elem() - } - - switch typ.Kind() { - case reflect.Struct: - // Handle SDK native types as strings since they use custom JSON marshaling. - if slices.Contains(sdkNativeTypes, typ) { - return n.normalizeString(reflect.TypeFor[string](), src, path) - } - return n.normalizeStruct(typ, src, append(seen, typ), path) - case reflect.Map: - return n.normalizeMap(typ, src, append(seen, typ), path) - case reflect.Slice: - return n.normalizeSlice(typ, src, append(seen, typ), path) - case reflect.String: - return n.normalizeString(typ, src, path) - case reflect.Bool: - return n.normalizeBool(typ, src, path) - case reflect.Int, reflect.Int32, reflect.Int64: - return n.normalizeInt(typ, src, path) - case reflect.Float32, reflect.Float64: - return n.normalizeFloat(typ, src, path) - case reflect.Interface: - return n.normalizeInterface(typ, src, path) - default: - // Fall through to the error case. - } - - return dyn.InvalidValue, diag.Errorf("unsupported type: %s", typ.Kind()) -} - -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), - Locations: []diag.Location{src.Location()}, - Paths: dyn.ToStructPaths(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()), - Locations: []diag.Location{src.Location()}, - Paths: dyn.ToStructPaths(path), - } -} - -// isAnchorContainer reports whether v is a YAML anchor or a non-empty -// sequence/map composed entirely of anchor containers. Anchors define reusable -// blocks and must not trigger "unknown field" warnings, including when nested -// inside a container. -func isAnchorContainer(v dyn.Value) bool { - if v.IsAnchor() { - return true - } - - var elements []dyn.Value - switch v.Kind() { - case dyn.KindSequence: - elements = v.MustSequence() - case dyn.KindMap: - elements = v.MustMap().Values() - default: - return false - } - - if len(elements) == 0 { - return false - } - for _, e := range elements { - if !isAnchorContainer(e) { - return false - } - } - return true -} - -func (n normalizeOptions) normalizeStruct(typ reflect.Type, src dyn.Value, seen []reflect.Type, path dyn.Path) (dyn.Value, diag.Diagnostics) { - var diags diag.Diagnostics - - switch src.Kind() { - case dyn.KindMap: - out := dyn.NewMapping() - info := getStructInfo(typ) - for _, pair := range src.MustMap().Pairs() { - pk := pair.Key - pv := pair.Value - - fieldName := pk.MustString() - index, ok := info.Fields[fieldName] - if !ok { - if !isAnchorContainer(pv) { - // Special case: provide a more helpful message for "valueFrom" vs "value_from" - if fieldName == "valueFrom" { - if _, hasValueFrom := info.Fields["value_from"]; hasValueFrom { - diags = diags.Append(diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Use 'value_from' instead of 'valueFrom'", - Detail: "The field 'valueFrom' should be 'value_from' (snake_case). The 'valueFrom' field will be ignored.", - Locations: pk.Locations(), - Paths: dyn.ToStructPaths(path), - }) - continue - } - } - - diags = diags.Append(diag.Diagnostic{ - Severity: diag.Warning, - Summary: "unknown field: " + fieldName, - Locations: pk.Locations(), - Paths: dyn.ToStructPaths(path), - }) - } - continue - } - - // Normalize the value according to the field type. - nv, err := n.normalizeType(typ.FieldByIndex(index).Type, pv, seen, path.Append(dyn.Key(pk.MustString()))) - if err != nil { - diags = diags.Extend(err) - // Skip the element if it cannot be normalized. - if !nv.IsValid() { - continue - } - } - - // Drop an empty string on an omitempty field so it is not force-sent - // to the backend (see DropEmptyStrings). ForceEmpty marks fields whose - // zero value must be kept, so those are left in place. - if n.dropEmptyStrings && !info.ForceEmpty[fieldName] { - if s, ok := nv.AsString(); ok && s == "" { - continue - } - } - - out.SetLoc(pk.MustString(), pk.Locations(), nv) - } - - // Return the normalized value if missing fields are not included. - if !n.includeMissingFields { - return dyn.NewValue(out, src.Locations()), diags - } - - // Populate missing fields with their zero values. - for k, index := range info.Fields { - if _, ok := out.GetByString(k); ok { - continue - } - - // Optionally dereference pointers to get the underlying field type. - ftyp := typ.FieldByIndex(index).Type - for ftyp.Kind() == reflect.Pointer { - ftyp = ftyp.Elem() - } - - // Skip field if we have already seen its type to avoid infinite recursion - // when filling in the zero value of a recursive type. - if slices.Contains(seen, ftyp) { - continue - } - - var v dyn.Value - switch ftyp.Kind() { - case reflect.Struct, reflect.Map: - v, _ = n.normalizeType(ftyp, dyn.V(map[string]dyn.Value{}), seen, path.Append(dyn.Key(k))) - case reflect.Slice: - v, _ = n.normalizeType(ftyp, dyn.V([]dyn.Value{}), seen, path.Append(dyn.Key(k))) - case reflect.String: - v, _ = n.normalizeType(ftyp, dyn.V(""), seen, path.Append(dyn.Key(k))) - case reflect.Bool: - v, _ = n.normalizeType(ftyp, dyn.V(false), seen, path.Append(dyn.Key(k))) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - v, _ = n.normalizeType(ftyp, dyn.V(int64(0)), seen, path.Append(dyn.Key(k))) - case reflect.Float32, reflect.Float64: - v, _ = n.normalizeType(ftyp, dyn.V(float64(0)), seen, path.Append(dyn.Key(k))) - default: - // Skip fields for which we do not have a natural [dyn.Value] equivalent. - // For example, we don't handle reflect.Complex* and reflect.Uint* types. - continue - } - if v.IsValid() { - out.SetLoc(k, nil, v) - } - } - - return dyn.NewValue(out, src.Locations()), diags - case dyn.KindNil: - return src, diags - - case dyn.KindString: - // Return verbatim if it's a pure variable reference. - if dynvar.IsPureVariableReference(src.MustString()) { - return src, nil - } - default: - // Fall through to the error case. - } - - // Cannot interpret as a struct. - return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindMap, src, path)) -} - -func (n normalizeOptions) normalizeMap(typ reflect.Type, src dyn.Value, seen []reflect.Type, path dyn.Path) (dyn.Value, diag.Diagnostics) { - var diags diag.Diagnostics - - switch src.Kind() { - case dyn.KindMap: - out := dyn.NewMapping() - for _, pair := range src.MustMap().Pairs() { - pk := pair.Key - pv := pair.Value - - // Normalize the value according to the map element type. - nv, err := n.normalizeType(typ.Elem(), pv, seen, path.Append(dyn.Key(pk.MustString()))) - if err != nil { - diags = diags.Extend(err) - // Skip the element if it cannot be normalized. - if !nv.IsValid() { - continue - } - } - - out.SetLoc(pk.MustString(), pk.Locations(), nv) - } - - return dyn.NewValue(out, src.Locations()), diags - case dyn.KindNil: - return src, diags - - case dyn.KindString: - // Return verbatim if it's a pure variable reference. - if dynvar.IsPureVariableReference(src.MustString()) { - return src, nil - } - default: - // Fall through to the error case. - } - - // Cannot interpret as a map. - return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindMap, src, path)) -} - -func (n normalizeOptions) normalizeSlice(typ reflect.Type, src dyn.Value, seen []reflect.Type, path dyn.Path) (dyn.Value, diag.Diagnostics) { - var diags diag.Diagnostics - - switch src.Kind() { - case dyn.KindSequence: - out := make([]dyn.Value, 0, len(src.MustSequence())) - for _, v := range src.MustSequence() { - // Normalize the value according to the slice element type. - v, err := n.normalizeType(typ.Elem(), v, seen, path.Append(dyn.Index(len(out)))) - if err != nil { - diags = diags.Extend(err) - // Skip the element if it cannot be normalized. - if !v.IsValid() { - continue - } - } - - out = append(out, v) - } - - return dyn.NewValue(out, src.Locations()), diags - case dyn.KindNil: - return src, diags - - case dyn.KindString: - // Return verbatim if it's a pure variable reference. - if dynvar.IsPureVariableReference(src.MustString()) { - return src, nil - } - default: - // Fall through to the error case. - } - - // Cannot interpret as a slice. - return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindSequence, src, path)) -} - -func (n normalizeOptions) normalizeString(typ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) { - var diags diag.Diagnostics - - switch src.Kind() { - case dyn.KindString: - return dyn.NewValue(src.MustString(), src.Locations()), nil - case dyn.KindBool: - return dyn.NewValue(strconv.FormatBool(src.MustBool()), src.Locations()), nil - case dyn.KindInt: - return dyn.NewValue(strconv.FormatInt(src.MustInt(), 10), src.Locations()), nil - case dyn.KindFloat: - return dyn.NewValue(strconv.FormatFloat(src.MustFloat(), 'f', -1, 64), src.Locations()), nil - case dyn.KindTime: - return dyn.NewValue(src.MustTime().String(), src.Locations()), nil - case dyn.KindNil: - // Return a warning if the field is present but has a null value. - return dyn.InvalidValue, diags.Append(nullWarning(dyn.KindString, src, path)) - default: - // Fall through to the error case. - } - - return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindString, src, path)) -} - -func (n normalizeOptions) normalizeBool(typ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) { - var diags diag.Diagnostics - - switch src.Kind() { - case dyn.KindBool: - return dyn.NewValue(src.MustBool(), src.Locations()), nil - case dyn.KindString: - // See https://yaml.org/type/bool.html. - switch src.MustString() { - case "true", "True", "TRUE", "y", "Y", "yes", "Yes", "YES", "on", "On", "ON": - return dyn.NewValue(true, src.Locations()), nil - case "false", "False", "FALSE", "n", "N", "no", "No", "NO", "off", "Off", "OFF": - return dyn.NewValue(false, src.Locations()), nil - default: - // Return verbatim if it's a pure variable reference. - if dynvar.IsPureVariableReference(src.MustString()) { - return src, nil - } - - // Cannot interpret as a boolean. - return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindBool, src, path)) - } - case dyn.KindNil: - // Return a warning if the field is present but has a null value. - return dyn.InvalidValue, diags.Append(nullWarning(dyn.KindBool, src, path)) - default: - // Fall through to the error case. - } - - return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindBool, src, path)) -} - -func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) { - var diags diag.Diagnostics - - switch src.Kind() { - case dyn.KindInt: - return dyn.NewValue(src.MustInt(), src.Locations()), nil - case dyn.KindFloat: - 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()), - Locations: []diag.Location{src.Location()}, - Paths: dyn.ToStructPaths(path), - }) - } - return dyn.NewValue(out, src.Locations()), nil - case dyn.KindString: - var err error - out, err := strconv.ParseInt(src.MustString(), 10, 64) - if err != nil { - // Return verbatim if it's a pure variable reference. - if dynvar.IsPureVariableReference(src.MustString()) { - return src, nil - } - - return dyn.InvalidValue, diags.Append(diag.Diagnostic{ - Severity: diag.Warning, - Summary: fmt.Sprintf("cannot parse %q as an integer", src.MustString()), - Locations: []diag.Location{src.Location()}, - Paths: dyn.ToStructPaths(path), - }) - } - return dyn.NewValue(out, src.Locations()), nil - case dyn.KindNil: - // Return a warning if the field is present but has a null value. - return dyn.InvalidValue, diags.Append(nullWarning(dyn.KindInt, src, path)) - default: - // Fall through to the error case. - } - - return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindInt, src, path)) -} - -func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) { - var diags diag.Diagnostics - - switch src.Kind() { - case dyn.KindFloat: - return dyn.NewValue(src.MustFloat(), src.Locations()), nil - case dyn.KindInt: - 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()), - Locations: []diag.Location{src.Location()}, - Paths: dyn.ToStructPaths(path), - }) - } - return dyn.NewValue(out, src.Locations()), nil - case dyn.KindString: - var err error - out, err := strconv.ParseFloat(src.MustString(), 64) - if err != nil { - // Return verbatim if it's a pure variable reference. - if dynvar.IsPureVariableReference(src.MustString()) { - return src, nil - } - - return dyn.InvalidValue, diags.Append(diag.Diagnostic{ - Severity: diag.Warning, - Summary: fmt.Sprintf("cannot parse %q as a floating point number", src.MustString()), - Locations: []diag.Location{src.Location()}, - Paths: dyn.ToStructPaths(path), - }) - } - return dyn.NewValue(out, src.Locations()), nil - case dyn.KindNil: - // Return a warning if the field is present but has a null value. - return dyn.InvalidValue, diags.Append(nullWarning(dyn.KindFloat, src, path)) - default: - // Fall through to the error case. - } - - return dyn.InvalidValue, diags.Append(typeMismatch(dyn.KindFloat, src, path)) -} - -func (n normalizeOptions) normalizeInterface(_ reflect.Type, src dyn.Value, path dyn.Path) (dyn.Value, diag.Diagnostics) { - // Deal with every [dyn.Kind] here to ensure completeness. - switch src.Kind() { - case dyn.KindMap: - // Fall through - case dyn.KindSequence: - // Fall through - case dyn.KindString: - // Fall through - case dyn.KindBool: - // Fall through - case dyn.KindInt: - // Fall through - case dyn.KindFloat: - // Fall through - case dyn.KindTime: - // Conversion of a time value to an interface{}. - // The [dyn.Value.AsAny] equivalent for this kind is the [time.Time] struct. - // If we convert to a typed representation and back again, we cannot distinguish - // a [time.Time] struct from any other struct. - // - // Therefore, we normalize the time value to a string. - return dyn.NewValue(src.MustTime().String(), src.Locations()), nil - case dyn.KindNil: - // Fall through - default: - return dyn.InvalidValue, diag.Errorf("unsupported kind: %s", src.Kind()) - } - - return src, nil -} diff --git a/libs/dyn/convert/normalize_test.go b/libs/dyn/convert/normalize_test.go deleted file mode 100644 index ac95aaafac6..00000000000 --- a/libs/dyn/convert/normalize_test.go +++ /dev/null @@ -1,1018 +0,0 @@ -package convert - -import ( - "testing" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynassert" - "github.com/databricks/cli/libs/structs/structpath" - "github.com/stretchr/testify/assert" -) - -func TestNormalizeStruct(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar"` - } - - var typ Tmp - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V("baz"), - }) - - vout, diags := Normalize(typ, vin) - assert.Empty(t, diags) - assert.Equal(t, vin, vout) -} - -func TestNormalizeStructElementDiagnostic(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar"` - } - - var typ Tmp - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V(map[string]dyn.Value{"an": dyn.V("error")}), - }) - - vout, diags := Normalize(typ, vin) - assert.Len(t, diags, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected string, found map`, - Locations: []diag.Location{{}}, - Paths: structpath.NewPathSlice("bar"), - }, diags[0]) - - // Elements that encounter an error during normalization are dropped. - assert.Equal(t, map[string]any{ - "foo": "bar", - }, vout.AsAny()) -} - -func TestNormalizeStructUnknownField(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - } - - var typ Tmp - - m := dyn.NewMapping() - m.SetLoc("foo", nil, dyn.V("val-foo")) - - // Set the unknown field, with location information. - m.SetLoc("bar", []diag.Location{ - {File: "hello.yaml", Line: 1, Column: 1}, - {File: "world.yaml", Line: 2, Column: 2}, - }, dyn.V("var-bar")) - - vin := dyn.V(m) - - vout, diags := Normalize(typ, vin) - assert.Len(t, diags, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `unknown field: bar`, - // Assert location of the unknown field is included in the diagnostic. - Locations: []diag.Location{ - {File: "hello.yaml", Line: 1, Column: 1}, - {File: "world.yaml", Line: 2, Column: 2}, - }, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, diags[0]) - - // The field that can be mapped to the struct field is retained. - assert.Equal(t, map[string]any{ - "foo": "val-foo", - }, vout.AsAny()) -} - -func TestNormalizeStructNil(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - } - - var typ Tmp - vin := dyn.NilValue - vout, err := Normalize(typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeStructError(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - } - - var typ Tmp - vin := dyn.V("string") - _, err := Normalize(typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected map, found string`, - Locations: []diag.Location{vin.Get("foo").Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeStructNestedError(t *testing.T) { - type Nested struct { - F1 int `json:"f1"` - F2 int `json:"f2"` - } - type Tmp struct { - Foo Nested `json:"foo"` - Bar Nested `json:"bar"` - } - - var typ Tmp - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V(map[string]dyn.Value{ - "f1": dyn.V("error"), - "f2": dyn.V(1), - }), - "bar": dyn.V(map[string]dyn.Value{ - "f1": dyn.V(1), - "f2": dyn.V("error"), - }), - }) - vout, err := Normalize(typ, vin) - assert.Len(t, err, 2) - - // Verify that valid fields are retained. - dynassert.Equal(t, - dyn.V(map[string]dyn.Value{ - "foo": dyn.V(map[string]dyn.Value{ - "f2": dyn.V(int64(1)), - }), - "bar": dyn.V(map[string]dyn.Value{ - "f1": dyn.V(int64(1)), - }), - }), - vout, - ) -} - -func TestNormalizeStructIncludeMissingFields(t *testing.T) { - type Nested struct { - String string `json:"string"` - } - - type Tmp struct { - // Verify that fields that are already set in the dynamic value are not overridden. - Existing string `json:"existing"` - - // Verify that structs are recursively normalized if not set. - Nested Nested `json:"nested"` - Ptr *Nested `json:"ptr"` - - // Verify that containers are also zero-initialized if not set. - Map map[string]string `json:"map"` - Slice []string `json:"slice"` - - // Verify that primitive types are zero-initialized if not set. - String string `json:"string"` - Bool bool `json:"bool"` - Int int `json:"int"` - Float float64 `json:"float"` - } - - var typ Tmp - vin := dyn.V(map[string]dyn.Value{ - "existing": dyn.V("already set"), - }) - vout, err := Normalize(typ, vin, IncludeMissingFields) - assert.Empty(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "existing": dyn.V("already set"), - "nested": dyn.V(map[string]dyn.Value{ - "string": dyn.V(""), - }), - "ptr": dyn.V(map[string]dyn.Value{ - "string": dyn.V(""), - }), - "map": dyn.V(map[string]dyn.Value{}), - "slice": dyn.V([]dyn.Value{}), - "string": dyn.V(""), - "bool": dyn.V(false), - "int": dyn.V(int64(0)), - "float": dyn.V(float64(0)), - }), vout) -} - -func TestNormalizeStructIncludeMissingFieldsOnRecursiveType(t *testing.T) { - type Tmp struct { - // Verify that structs are recursively normalized if not set. - Ptr *Tmp `json:"ptr"` - - // Verify that primitive types are zero-initialized if not set. - String string `json:"string"` - } - - var typ Tmp - vin := dyn.V(map[string]dyn.Value{ - "ptr": dyn.V(map[string]dyn.Value{ - "ptr": dyn.V(map[string]dyn.Value{ - "string": dyn.V("already set"), - }), - }), - }) - vout, err := Normalize(typ, vin, IncludeMissingFields) - assert.Empty(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "ptr": dyn.V(map[string]dyn.Value{ - "ptr": dyn.V(map[string]dyn.Value{ - // Note: the ptr field is not zero-initialized because that would recurse. - "string": dyn.V("already set"), - }), - "string": dyn.V(""), - }), - "string": dyn.V(""), - }), vout) -} - -func TestNormalizeStructVariableReference(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - } - - var typ Tmp - vin := dyn.NewValue("${var.foo}", []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeStructRandomStringError(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - } - - var typ Tmp - vin := dyn.NewValue("var foo", []diag.Location{{File: "file", Line: 1, Column: 1}}) - _, err := Normalize(typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected map, found string`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeStructIntError(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - } - - var typ Tmp - vin := dyn.NewValue(1, []diag.Location{{File: "file", Line: 1, Column: 1}}) - _, err := Normalize(typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected map, found int`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeMap(t *testing.T) { - var typ map[string]string - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V("baz"), - }) - - vout, err := Normalize(typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeMapElementDiagnostic(t *testing.T) { - var typ map[string]string - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V(map[string]dyn.Value{"an": dyn.V("error")}), - }) - - vout, err := Normalize(typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected string, found map`, - Locations: []diag.Location{{}}, - Paths: structpath.NewPathSlice("bar"), - }, err[0]) - - // Elements that encounter an error during normalization are dropped. - assert.Equal(t, map[string]any{ - "foo": "bar", - }, vout.AsAny()) -} - -func TestNormalizeMapNil(t *testing.T) { - var typ map[string]string - vin := dyn.NilValue - vout, err := Normalize(typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeMapError(t *testing.T) { - var typ map[string]string - vin := dyn.V("string") - _, err := Normalize(typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected map, found string`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeMapNestedError(t *testing.T) { - type Nested struct { - F1 int `json:"f1"` - F2 int `json:"f2"` - } - - var typ map[string]Nested - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V(map[string]dyn.Value{ - "f1": dyn.V("error"), - "f2": dyn.V(1), - }), - "bar": dyn.V(map[string]dyn.Value{ - "f1": dyn.V(1), - "f2": dyn.V("error"), - }), - }) - vout, err := Normalize(typ, vin) - assert.Len(t, err, 2) - - // Verify that valid fields are retained. - dynassert.Equal(t, - dyn.V(map[string]dyn.Value{ - "foo": dyn.V(map[string]dyn.Value{ - "f2": dyn.V(int64(1)), - }), - "bar": dyn.V(map[string]dyn.Value{ - "f1": dyn.V(int64(1)), - }), - }), - vout, - ) -} - -func TestNormalizeMapVariableReference(t *testing.T) { - var typ map[string]string - vin := dyn.NewValue("${var.foo}", []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeMapRandomStringError(t *testing.T) { - var typ map[string]string - vin := dyn.NewValue("var foo", []diag.Location{{File: "file", Line: 1, Column: 1}}) - _, err := Normalize(typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected map, found string`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeMapIntError(t *testing.T) { - var typ map[string]string - vin := dyn.NewValue(1, []diag.Location{{File: "file", Line: 1, Column: 1}}) - _, err := Normalize(typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected map, found int`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeSlice(t *testing.T) { - var typ []string - vin := dyn.V([]dyn.Value{ - dyn.V("foo"), - dyn.V("bar"), - }) - - vout, err := Normalize(typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeSliceElementDiagnostic(t *testing.T) { - var typ []string - vin := dyn.V([]dyn.Value{ - dyn.V("foo"), - dyn.V("bar"), - dyn.V(map[string]dyn.Value{"an": dyn.V("error")}), - }) - - vout, err := Normalize(typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected string, found map`, - Locations: []diag.Location{{}}, - Paths: []*structpath.PathNode{structpath.NewIndex(nil, 2)}, - }, err[0]) - - // Elements that encounter an error during normalization are dropped. - assert.Equal(t, []any{"foo", "bar"}, vout.AsAny()) -} - -func TestNormalizeSliceNil(t *testing.T) { - var typ []string - vin := dyn.NilValue - vout, err := Normalize(typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeSliceError(t *testing.T) { - var typ []string - vin := dyn.V("string") - _, err := Normalize(typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected sequence, found string`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeSliceNestedError(t *testing.T) { - type Nested struct { - F1 int `json:"f1"` - F2 int `json:"f2"` - } - - var typ []Nested - vin := dyn.V([]dyn.Value{ - dyn.V(map[string]dyn.Value{ - "f1": dyn.V("error"), - "f2": dyn.V(1), - }), - dyn.V(map[string]dyn.Value{ - "f1": dyn.V(1), - "f2": dyn.V("error"), - }), - }) - vout, err := Normalize(typ, vin) - assert.Len(t, err, 2) - - // Verify that valid fields are retained. - dynassert.Equal(t, - dyn.V([]dyn.Value{ - dyn.V(map[string]dyn.Value{ - "f2": dyn.V(int64(1)), - }), - dyn.V(map[string]dyn.Value{ - "f1": dyn.V(int64(1)), - }), - }), - vout, - ) -} - -func TestNormalizeSliceVariableReference(t *testing.T) { - var typ []string - vin := dyn.NewValue("${var.foo}", []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeSliceRandomStringError(t *testing.T) { - var typ []string - vin := dyn.NewValue("var foo", []diag.Location{{File: "file", Line: 1, Column: 1}}) - _, err := Normalize(typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected sequence, found string`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeSliceIntError(t *testing.T) { - var typ []string - vin := dyn.NewValue(1, []diag.Location{{File: "file", Line: 1, Column: 1}}) - _, err := Normalize(typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected sequence, found int`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeString(t *testing.T) { - var typ string - vin := dyn.V("string") - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeStringNil(t *testing.T) { - var typ string - vin := dyn.NewValue(nil, []diag.Location{{File: "file", Line: 1, Column: 1}}) - _, err := Normalize(&typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected a string value, found null`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeStringFromBool(t *testing.T) { - var typ string - vin := dyn.NewValue(true, []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.NewValue("true", vin.Locations()), vout) -} - -func TestNormalizeStringFromInt(t *testing.T) { - var typ string - vin := dyn.NewValue(123, []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.NewValue("123", vin.Locations()), vout) -} - -func TestNormalizeStringFromFloat(t *testing.T) { - var typ string - vin := dyn.NewValue(1.20, []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.NewValue("1.2", vin.Locations()), vout) -} - -func TestNormalizeStringFromTime(t *testing.T) { - var typ string - vin := dyn.NewValue(dyn.MustTime("2024-08-29"), []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.NewValue("2024-08-29", vin.Locations()), vout) -} - -func TestNormalizeStringError(t *testing.T) { - var typ string - vin := dyn.V(map[string]dyn.Value{"an": dyn.V("error")}) - _, err := Normalize(&typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected string, found map`, - Locations: []diag.Location{{}}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeBool(t *testing.T) { - var typ bool - vin := dyn.V(true) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.V(true), vout) -} - -func TestNormalizeBoolNil(t *testing.T) { - var typ bool - vin := dyn.NewValue(nil, []diag.Location{{File: "file", Line: 1, Column: 1}}) - _, err := Normalize(&typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected a bool value, found null`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeBoolFromString(t *testing.T) { - var typ bool - - for _, c := range []struct { - Input string - Output bool - }{ - {"true", true}, - {"false", false}, - {"True", true}, - {"False", false}, - {"TRUE", true}, - {"FALSE", false}, - {"Y", true}, - {"N", false}, - {"on", true}, - {"off", false}, - } { - vin := dyn.V(c.Input) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.V(c.Output), vout) - } -} - -func TestNormalizeBoolFromStringVariableReference(t *testing.T) { - var typ bool - vin := dyn.V("${var.foo}") - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeBoolFromStringError(t *testing.T) { - var typ bool - vin := dyn.V("abc") - _, err := Normalize(&typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected bool, found string`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeBoolError(t *testing.T) { - var typ bool - vin := dyn.V(map[string]dyn.Value{"an": dyn.V("error")}) - _, err := Normalize(&typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected bool, found map`, - Locations: []diag.Location{{}}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeInt(t *testing.T) { - var typ int - vin := dyn.V(123) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.V(int64(123)), vout) -} - -func TestNormalizeIntNil(t *testing.T) { - var typ int - vin := dyn.NewValue(nil, []diag.Location{{File: "file", Line: 1, Column: 1}}) - _, err := Normalize(&typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected a int value, found null`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeIntFromFloat(t *testing.T) { - var typ int - vin := dyn.V(float64(1.0)) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.V(int64(1)), vout) -} - -func TestNormalizeIntFromFloatError(t *testing.T) { - var typ int - vin := dyn.V(1.5) - _, 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`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeIntFromString(t *testing.T) { - var typ int - vin := dyn.V("123") - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.V(int64(123)), vout) -} - -func TestNormalizeIntFromStringVariableReference(t *testing.T) { - var typ int - vin := dyn.V("${var.foo}") - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeIntFromStringError(t *testing.T) { - var typ int - vin := dyn.V("abc") - _, err := Normalize(&typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `cannot parse "abc" as an integer`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeIntError(t *testing.T) { - var typ int - vin := dyn.V(map[string]dyn.Value{"an": dyn.V("error")}) - _, err := Normalize(&typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected int, found map`, - Locations: []diag.Location{{}}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeFloat(t *testing.T) { - var typ float64 - vin := dyn.V(1.2) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.V(1.2), vout) -} - -func TestNormalizeFloatNil(t *testing.T) { - var typ float64 - vin := dyn.NewValue(nil, []diag.Location{{File: "file", Line: 1, Column: 1}}) - _, err := Normalize(&typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected a float value, found null`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeFloatFromInt(t *testing.T) { - var typ float64 - - // Maximum safe integer that can be accurately represented as a float. - vin := dyn.V(int64(9007199254740992)) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.V(float64(9007199254740992)), vout) -} - -func TestNormalizeFloatFromIntError(t *testing.T) { - var typ float64 - - // Minimum integer that cannot be accurately represented as a float. - vin := dyn.V(9007199254740992 + 1) - _, 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`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeFloatFromString(t *testing.T) { - var typ float64 - vin := dyn.V("1.2") - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.V(1.2), vout) -} - -func TestNormalizeFloatFromStringVariableReference(t *testing.T) { - var typ float64 - vin := dyn.V("${var.foo}") - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, vin, vout) -} - -func TestNormalizeFloatFromStringError(t *testing.T) { - var typ float64 - vin := dyn.V("abc") - _, 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`, - Locations: []diag.Location{vin.Location()}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeFloatError(t *testing.T) { - var typ float64 - vin := dyn.V(map[string]dyn.Value{"an": dyn.V("error")}) - _, err := Normalize(&typ, vin) - assert.Len(t, err, 1) - assert.Equal(t, diag.Diagnostic{ - Severity: diag.Warning, - Summary: `expected float, found map`, - Locations: []diag.Location{{}}, - Paths: dyn.ToStructPaths(dyn.EmptyPath), - }, err[0]) -} - -func TestNormalizeAnchors(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - } - - var typ Tmp - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "anchor": dyn.V("anchor").MarkAnchor(), - }) - - vout, err := Normalize(typ, vin) - assert.Empty(t, err) - - // The field that can be mapped to the struct field is retained. - assert.Equal(t, map[string]any{ - "foo": "bar", - }, vout.AsAny()) -} - -func TestNormalizeAnchorContainers(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - } - - anchor := func() dyn.Value { - return dyn.V(map[string]dyn.Value{"name": dyn.V("x")}).MarkAnchor() - } - - tcases := []struct { - name string - value dyn.Value - wantWarn bool - }{ - { - name: "list of anchors", - value: dyn.V([]dyn.Value{anchor(), anchor()}), - wantWarn: false, - }, - { - name: "map of anchors", - value: dyn.V(map[string]dyn.Value{ - "a": anchor(), - "b": anchor(), - }), - wantWarn: false, - }, - { - name: "nested list of anchors", - value: dyn.V([]dyn.Value{dyn.V([]dyn.Value{anchor()})}), - wantWarn: false, - }, - { - name: "list with a non-anchor element", - value: dyn.V([]dyn.Value{anchor(), dyn.V(map[string]dyn.Value{"name": dyn.V("x")})}), - wantWarn: true, - }, - { - name: "empty list", - value: dyn.V([]dyn.Value{}), - wantWarn: true, - }, - { - name: "empty map", - value: dyn.V(map[string]dyn.Value{}), - wantWarn: true, - }, - } - - for _, tc := range tcases { - t.Run(tc.name, func(t *testing.T) { - var typ Tmp - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "thing": tc.value, - }) - - vout, diags := Normalize(typ, vin) - if tc.wantWarn { - assert.Len(t, diags, 1) - assert.Equal(t, "unknown field: thing", diags[0].Summary) - } else { - assert.Empty(t, diags) - } - - // The unknown field is never retained regardless of the warning. - assert.Equal(t, map[string]any{ - "foo": "bar", - }, vout.AsAny()) - }) - } -} - -func TestNormalizeAnyFromSlice(t *testing.T) { - var typ any - v1 := dyn.NewValue(1, []diag.Location{{File: "file", Line: 1, Column: 1}}) - v2 := dyn.NewValue(2, []diag.Location{{File: "file", Line: 1, Column: 1}}) - vin := dyn.NewValue([]dyn.Value{v1, v2}, []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.NewValue([]dyn.Value{v1, v2}, []diag.Location{{File: "file", Line: 1, Column: 1}}), vout) -} - -func TestNormalizeAnyFromString(t *testing.T) { - var typ any - vin := dyn.NewValue("string", []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.NewValue("string", []diag.Location{{File: "file", Line: 1, Column: 1}}), vout) -} - -func TestNormalizeAnyFromBool(t *testing.T) { - var typ any - vin := dyn.NewValue(false, []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.NewValue(false, []diag.Location{{File: "file", Line: 1, Column: 1}}), vout) -} - -func TestNormalizeAnyFromInt(t *testing.T) { - var typ any - vin := dyn.NewValue(10, []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.NewValue(10, []diag.Location{{File: "file", Line: 1, Column: 1}}), vout) -} - -func TestNormalizeAnyFromTime(t *testing.T) { - var typ any - vin := dyn.NewValue(dyn.MustTime("2024-08-29"), []diag.Location{{File: "file", Line: 1, Column: 1}}) - vout, err := Normalize(&typ, vin) - assert.Empty(t, err) - assert.Equal(t, dyn.NewValue("2024-08-29", vin.Locations()), vout) -} - -func TestNormalizeStructDropEmptyStrings(t *testing.T) { - type Tmp struct { - // Optional: dropped when empty. - Opt string `json:"opt,omitempty"` - // Required (no omitempty): kept even when empty. - Req string `json:"req"` - } - - vin := dyn.V(map[string]dyn.Value{ - "opt": dyn.V(""), - "req": dyn.V(""), - }) - - vout, diags := Normalize(Tmp{}, vin, DropEmptyStrings) - assert.Empty(t, diags) - - _, hasOpt := vout.MustMap().Get(dyn.V("opt")) - assert.False(t, hasOpt, "empty omitempty string should be dropped") - req, hasReq := vout.MustMap().Get(dyn.V("req")) - assert.True(t, hasReq, "empty non-omitempty string should be kept") - assert.Empty(t, req.MustString()) -} - -func TestNormalizeStructDropEmptyStringsKeepsNonEmpty(t *testing.T) { - type Tmp struct { - Opt string `json:"opt,omitempty"` - } - - vin := dyn.V(map[string]dyn.Value{"opt": dyn.V("value")}) - - vout, diags := Normalize(Tmp{}, vin, DropEmptyStrings) - assert.Empty(t, diags) - opt, ok := vout.MustMap().Get(dyn.V("opt")) - assert.True(t, ok) - assert.Equal(t, "value", opt.MustString()) -} diff --git a/libs/dyn/convert/sdk_native_types.go b/libs/dyn/convert/sdk_native_types.go deleted file mode 100644 index b67d9b2b1e3..00000000000 --- a/libs/dyn/convert/sdk_native_types.go +++ /dev/null @@ -1,97 +0,0 @@ -package convert - -import ( - "encoding/json" - "fmt" - "reflect" - "slices" - - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" - sdkduration "github.com/databricks/databricks-sdk-go/common/types/duration" - sdkfieldmask "github.com/databricks/databricks-sdk-go/common/types/fieldmask" - sdktime "github.com/databricks/databricks-sdk-go/common/types/time" -) - -// sdkNativeTypes is a list of SDK native types that use custom JSON marshaling -// and should be treated as strings in dyn.Value. These types all implement -// json.Marshaler and json.Unmarshaler interfaces. -var sdkNativeTypes = []reflect.Type{ - reflect.TypeFor[sdkduration.Duration](), // Protobuf duration format (e.g., "300s") - reflect.TypeFor[sdktime.Time](), // RFC3339 timestamp format (e.g., "2023-12-25T10:30:00Z") - reflect.TypeFor[sdkfieldmask.FieldMask](), // Comma-separated paths (e.g., "name,age,email") -} - -// fromTypedSDKNative converts SDK native types to dyn.Value. -// SDK native types (duration.Duration, time.Time, fieldmask.FieldMask) use -// custom JSON marshaling with string representations. -func fromTypedSDKNative(src reflect.Value, ref dyn.Value, options ...fromTypedOptions) (dyn.Value, error) { - // Check that the reference value is compatible or nil. - switch ref.Kind() { - case dyn.KindString: - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(ref.MustString()) { - return ref, nil - } - case dyn.KindNil: - // Allow nil reference. - default: - return dyn.InvalidValue, fmt.Errorf("cannot convert SDK native type to dynamic type %#v", ref.Kind().String()) - } - - // Check for zero value first. - if src.IsZero() && !slices.Contains(options, includeZeroValues) { - return dyn.NilValue, nil - } - - // Use JSON marshaling since SDK native types implement json.Marshaler. - jsonBytes, err := json.Marshal(src.Interface()) - if err != nil { - return dyn.InvalidValue, err - } - - // All SDK native types marshal to JSON strings. Unmarshal to get the raw string value. - // For example: duration.Duration(300s) -> JSON "300s" -> string "300s" - var str string - if err := json.Unmarshal(jsonBytes, &str); err != nil { - return dyn.InvalidValue, err - } - - // Handle empty string as zero value. - if str == "" && !slices.Contains(options, includeZeroValues) { - return dyn.NilValue, nil - } - - return dyn.V(str), nil -} - -// toTypedSDKNative converts a dyn.Value to an SDK native type. -// SDK native types (duration.Duration, time.Time, fieldmask.FieldMask) use -// custom JSON marshaling with string representations. -func toTypedSDKNative(dst reflect.Value, src dyn.Value) error { - switch src.Kind() { - case dyn.KindString: - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(src.MustString()) { - dst.SetZero() - return nil - } - // Use JSON unmarshaling since SDK native types implement json.Unmarshaler. - // Marshal the string to create a valid JSON string literal for unmarshaling. - jsonBytes, err := json.Marshal(src.MustString()) - if err != nil { - return err - } - return json.Unmarshal(jsonBytes, dst.Addr().Interface()) - case dyn.KindNil: - dst.SetZero() - return nil - default: - // Fall through to the error case. - } - - return TypeError{ - value: src, - msg: fmt.Sprintf("expected a string, found a %s", src.Kind()), - } -} diff --git a/libs/dyn/convert/sdk_native_types_test.go b/libs/dyn/convert/sdk_native_types_test.go deleted file mode 100644 index cd3d14fa129..00000000000 --- a/libs/dyn/convert/sdk_native_types_test.go +++ /dev/null @@ -1,417 +0,0 @@ -package convert - -import ( - "reflect" - "testing" - "time" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - sdkduration "github.com/databricks/databricks-sdk-go/common/types/duration" - sdkfieldmask "github.com/databricks/databricks-sdk-go/common/types/fieldmask" - sdktime "github.com/databricks/databricks-sdk-go/common/types/time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// Roundtrip tests - verify SDK native types convert to dyn.Value and back for both value and pointer types - -func TestDurationRoundtrip(t *testing.T) { - tests := []struct { - name string - duration time.Duration - expectedString string - }{ - {"5min", 5 * time.Minute, "300s"}, - {"7days", 7 * 24 * time.Hour, "604800s"}, - {"1hour", 1 * time.Hour, "3600s"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Test value type - t.Run("value", func(t *testing.T) { - src := *sdkduration.New(tt.duration) - dynValue, err := FromTyped(src, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, tt.expectedString, dynValue.MustString()) - - var out sdkduration.Duration - err = ToTyped(&out, dynValue) - require.NoError(t, err) - assert.Equal(t, src.AsDuration(), out.AsDuration()) - }) - - // Test pointer type - t.Run("pointer", func(t *testing.T) { - src := *sdkduration.New(tt.duration) - dynValue, err := FromTyped(&src, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, tt.expectedString, dynValue.MustString()) - - var out *sdkduration.Duration - err = ToTyped(&out, dynValue) - require.NoError(t, err) - require.NotNil(t, out) - assert.Equal(t, src.AsDuration(), out.AsDuration()) - }) - }) - } -} - -func TestTimeRoundtrip(t *testing.T) { - tests := []struct { - name string - time time.Time - expectedString string - }{ - { - "no_nanos", - time.Date(2023, 12, 25, 10, 30, 0, 0, time.UTC), - "2023-12-25T10:30:00Z", - }, - { - "with_nanos", - time.Date(2023, 12, 25, 10, 30, 0, 123456789, time.UTC), - "2023-12-25T10:30:00.123456789Z", - }, - { - "epoch", - time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), - "1970-01-01T00:00:00Z", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Test value type - t.Run("value", func(t *testing.T) { - src := *sdktime.New(tt.time) - dynValue, err := FromTyped(src, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, tt.expectedString, dynValue.MustString()) - - var out sdktime.Time - err = ToTyped(&out, dynValue) - require.NoError(t, err) - assert.Equal(t, src.AsTime(), out.AsTime()) - }) - - // Test pointer type - t.Run("pointer", func(t *testing.T) { - src := *sdktime.New(tt.time) - dynValue, err := FromTyped(&src, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, tt.expectedString, dynValue.MustString()) - - var out *sdktime.Time - err = ToTyped(&out, dynValue) - require.NoError(t, err) - require.NotNil(t, out) - assert.Equal(t, src.AsTime(), out.AsTime()) - }) - }) - } -} - -func TestFieldMaskRoundtrip(t *testing.T) { - tests := []struct { - name string - paths []string - expectedString string - }{ - {"single", []string{"name"}, "name"}, - {"multiple", []string{"name", "age", "email"}, "name,age,email"}, - {"nested", []string{"user.name", "user.email"}, "user.name,user.email"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Test value type - t.Run("value", func(t *testing.T) { - src := *sdkfieldmask.New(tt.paths) - dynValue, err := FromTyped(src, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, tt.expectedString, dynValue.MustString()) - - var out sdkfieldmask.FieldMask - err = ToTyped(&out, dynValue) - require.NoError(t, err) - assert.Equal(t, src.Paths, out.Paths) - }) - - // Test pointer type - t.Run("pointer", func(t *testing.T) { - src := *sdkfieldmask.New(tt.paths) - dynValue, err := FromTyped(&src, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, tt.expectedString, dynValue.MustString()) - - var out *sdkfieldmask.FieldMask - err = ToTyped(&out, dynValue) - require.NoError(t, err) - require.NotNil(t, out) - assert.Equal(t, src.Paths, out.Paths) - }) - }) - } -} - -// Edge case tests - -func TestNilValues(t *testing.T) { - tests := []struct { - name string - typ any - }{ - {"duration", (*sdkduration.Duration)(nil)}, - {"time", (*sdktime.Time)(nil)}, - {"fieldmask", (*sdkfieldmask.FieldMask)(nil)}, - } - - t.Run("from_typed", func(t *testing.T) { - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - nv, err := FromTyped(tt.typ, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) - }) - } - }) - - t.Run("normalize", func(t *testing.T) { - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - vout, diags := Normalize(tt.typ, dyn.NilValue) - assert.Len(t, diags, 1) - assert.Equal(t, diag.Warning, diags[0].Severity) - assert.Equal(t, `expected a string value, found null`, diags[0].Summary) - assert.Equal(t, dyn.InvalidValue, vout) - }) - } - }) -} - -func TestToTypedErrors(t *testing.T) { - wrongTypeInput := dyn.V(map[string]dyn.Value{"foo": dyn.V("bar")}) - - tests := []struct { - name string - destValue any - input dyn.Value - expectedError string - }{ - { - name: "duration_wrong_type", - destValue: new(sdkduration.Duration), - input: wrongTypeInput, - expectedError: "expected a string", - }, - { - name: "duration_invalid_format", - destValue: new(sdkduration.Duration), - input: dyn.V("7d"), - expectedError: "invalid google.protobuf.Duration value", - }, - { - name: "time_wrong_type", - destValue: new(sdktime.Time), - input: wrongTypeInput, - expectedError: "expected a string", - }, - { - name: "time_invalid_format", - destValue: new(sdktime.Time), - input: dyn.V("not-a-time"), - expectedError: "invalid google.protobuf.Timestamp value", - }, - { - name: "fieldmask_wrong_type", - destValue: new(sdkfieldmask.FieldMask), - input: wrongTypeInput, - expectedError: "expected a string", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ToTyped(tt.destValue, tt.input) - require.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedError) - }) - } -} - -func TestFromTypedVariableReferences(t *testing.T) { - tests := []struct { - name string - src any - }{ - { - name: "duration", - src: *sdkduration.New(5 * time.Minute), - }, - { - name: "time", - src: *sdktime.New(time.Date(2023, 12, 25, 10, 30, 0, 0, time.UTC)), - }, - { - name: "fieldmask", - src: *sdkfieldmask.New([]string{"name", "age"}), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ref := dyn.V("${var.foo}") - nv, err := FromTyped(tt.src, ref) - require.NoError(t, err) - assert.Equal(t, dyn.V("${var.foo}"), nv) - }) - } -} - -func TestToTypedVariableReferences(t *testing.T) { - tests := []struct { - name string - dst any - zero any - }{ - { - name: "duration", - dst: sdkduration.New(5 * time.Minute), - zero: sdkduration.Duration{}, - }, - { - name: "time", - dst: sdktime.New(time.Date(2023, 12, 25, 10, 30, 0, 0, time.UTC)), - zero: sdktime.Time{}, - }, - { - name: "fieldmask", - dst: sdkfieldmask.New([]string{"name", "age"}), - zero: sdkfieldmask.FieldMask{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ToTyped(tt.dst, dyn.V("${var.foo}")) - require.NoError(t, err) - assert.Equal(t, tt.zero, reflect.ValueOf(tt.dst).Elem().Interface()) - }) - } -} - -func TestSpecialCases(t *testing.T) { - t.Run("duration_zero", func(t *testing.T) { - var src sdkduration.Duration - nv, err := FromTyped(src, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) - }) - - t.Run("fieldmask_empty_fromtyped", func(t *testing.T) { - src := sdkfieldmask.New([]string{}) - nv, err := FromTyped(src, dyn.NilValue) - require.NoError(t, err) - // Empty field mask marshals to empty string - assert.Equal(t, dyn.V(""), nv) - }) - - t.Run("fieldmask_empty_totyped", func(t *testing.T) { - var out sdkfieldmask.FieldMask - err := ToTyped(&out, dyn.V("")) - require.NoError(t, err) - assert.Empty(t, out.Paths) - }) -} - -// End-to-end tests with structs containing SDK native types - -func TestSDKTypesRoundTripWithPostgresBranchSpec(t *testing.T) { - type BranchSpec struct { - ExpireTime *sdktime.Time `json:"expire_time,omitempty"` - SourceBranchTime *sdktime.Time `json:"source_branch_time,omitempty"` - Ttl *sdkduration.Duration `json:"ttl,omitempty"` - IsProtected bool `json:"is_protected,omitempty"` - } - - original := BranchSpec{ - ExpireTime: sdktime.New(time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC)), - SourceBranchTime: sdktime.New(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)), - Ttl: sdkduration.New(7 * 24 * time.Hour), - IsProtected: true, - } - - dynValue, err := FromTyped(original, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, "2024-12-31T23:59:59Z", dynValue.Get("expire_time").MustString()) - assert.Equal(t, "2024-01-01T00:00:00Z", dynValue.Get("source_branch_time").MustString()) - assert.Equal(t, "604800s", dynValue.Get("ttl").MustString()) - assert.True(t, dynValue.Get("is_protected").MustBool()) - - var roundtrip BranchSpec - err = ToTyped(&roundtrip, dynValue) - require.NoError(t, err) - require.NotNil(t, roundtrip.ExpireTime) - require.NotNil(t, roundtrip.SourceBranchTime) - require.NotNil(t, roundtrip.Ttl) - assert.Equal(t, original.ExpireTime.AsTime(), roundtrip.ExpireTime.AsTime()) - assert.Equal(t, original.SourceBranchTime.AsTime(), roundtrip.SourceBranchTime.AsTime()) - assert.Equal(t, original.Ttl.AsDuration(), roundtrip.Ttl.AsDuration()) - assert.Equal(t, original.IsProtected, roundtrip.IsProtected) -} - -func TestSDKTypesRoundTripWithUpdateRequest(t *testing.T) { - type UpdateRequest struct { - Name string `json:"name"` - UpdateMask sdkfieldmask.FieldMask `json:"update_mask"` - } - - original := UpdateRequest{ - Name: "projects/123/branches/456", - UpdateMask: *sdkfieldmask.New([]string{"spec.ttl", "spec.is_protected"}), - } - - dynValue, err := FromTyped(original, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, "projects/123/branches/456", dynValue.Get("name").MustString()) - assert.Equal(t, "spec.ttl,spec.is_protected", dynValue.Get("update_mask").MustString()) - - var roundtrip UpdateRequest - err = ToTyped(&roundtrip, dynValue) - require.NoError(t, err) - assert.Equal(t, original.Name, roundtrip.Name) - assert.Equal(t, []string{"spec.ttl", "spec.is_protected"}, roundtrip.UpdateMask.Paths) -} - -func TestSDKTypesNormalizeWithStruct(t *testing.T) { - type BranchSpec struct { - ExpireTime *sdktime.Time `json:"expire_time,omitempty"` - Ttl *sdkduration.Duration `json:"ttl,omitempty"` - IsProtected bool `json:"is_protected,omitempty"` - } - - var typ BranchSpec - vin := dyn.V(map[string]dyn.Value{ - "expire_time": dyn.V("2024-12-31T23:59:59Z"), - "ttl": dyn.V("604800s"), - "is_protected": dyn.V(true), - }) - - vout, diags := Normalize(typ, vin) - assert.Empty(t, diags) - assert.Equal(t, "2024-12-31T23:59:59Z", vout.Get("expire_time").MustString()) - assert.Equal(t, "604800s", vout.Get("ttl").MustString()) - assert.True(t, vout.Get("is_protected").MustBool()) - - var out BranchSpec - err := ToTyped(&out, vout) - require.NoError(t, err) - require.NotNil(t, out.ExpireTime) - require.NotNil(t, out.Ttl) - assert.Equal(t, time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC), out.ExpireTime.AsTime()) - assert.Equal(t, 7*24*time.Hour, out.Ttl.AsDuration()) - assert.True(t, out.IsProtected) -} diff --git a/libs/dyn/convert/struct_info.go b/libs/dyn/convert/struct_info.go deleted file mode 100644 index 69e8f868ed5..00000000000 --- a/libs/dyn/convert/struct_info.go +++ /dev/null @@ -1,211 +0,0 @@ -package convert - -import ( - "reflect" - "slices" - "sync" - - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/structs/structtag" -) - -// structInfo holds the type information we need to efficiently -// convert data from a [dyn.Value] to a Go struct. -type structInfo struct { - // FieldNames is ordered list of fields - FieldNames []string - - // Fields maps the JSON-name of the field to the field's index for use with [FieldByIndex]. - Fields map[string][]int - - // ValueField maps to the field with a [dyn.Value]. - // The underlying type is expected to only have one of these. - ValueField []int - - // Tracks which fields do not have omitempty annotation - ForceEmpty map[string]bool - - // Maps JSON-name of the field to Golang struct name - GolangNames map[string]string - - // ForceSendFieldsIndex maps the JSON-name of the field to the index path (for - // use with [reflect.Value.FieldByIndex]) of the ForceSendFields slice that - // governs it: the one declared by the struct that also declares the field. - // The path is static per type, so we resolve it once here rather than walking - // the value at conversion time. It can be more than one element deep because a - // field's declaring struct may be embedded several levels down (e.g. - // PostgresProject -> PostgresProjectConfig -> ProjectSpec). A field whose - // declaring struct has no ForceSendFields has no entry. - ForceSendFieldsIndex map[string][]int -} - -// structInfoCache caches type information. -var structInfoCache = make(map[reflect.Type]structInfo) - -// structInfoCacheLock guards concurrent access to structInfoCache. -var structInfoCacheLock sync.Mutex - -// getStructInfo returns the [structInfo] for the given type. -// It lazily populates a cache, so the first call for a given -// type is slower than subsequent calls for that same type. -func getStructInfo(typ reflect.Type) structInfo { - structInfoCacheLock.Lock() - defer structInfoCacheLock.Unlock() - - si, ok := structInfoCache[typ] - if !ok { - si = buildStructInfo(typ) - structInfoCache[typ] = si - } - - return si -} - -// buildStructInfo populates a new [structInfo] for the given type. -func buildStructInfo(typ reflect.Type) structInfo { - out := structInfo{ - Fields: make(map[string][]int), - ForceEmpty: make(map[string]bool), - GolangNames: make(map[string]string), - ForceSendFieldsIndex: make(map[string][]int), - } - - // Queue holds the indexes of the structs to visit. - // It is initialized with a single empty slice to visit the top level struct. - queue := [][]int{{}} - for i := 0; i < len(queue); i++ { - prefix := queue[i] - - // Traverse embedded anonymous types (if prefix is non-empty). - styp := typ - if len(prefix) > 0 { - styp = styp.FieldByIndex(prefix).Type - } - - // Dereference pointer type. - if styp.Kind() == reflect.Pointer { - styp = styp.Elem() - } - - // Index path to the ForceSendFields declared by this struct, if any. All - // fields declared directly by this struct are governed by it. The len==1 - // check excludes a ForceSendFields promoted from an embedded struct: that - // one governs the embedded struct's own fields, which we visit separately. - var forceSendFieldsIndex []int - if sf, ok := styp.FieldByName("ForceSendFields"); ok && len(sf.Index) == 1 { - forceSendFieldsIndex = append(slices.Clone(prefix), sf.Index...) - } - - nf := styp.NumField() - for j := range nf { - sf := styp.Field(j) - - // Recurse into anonymous fields. - if sf.Anonymous { - queue = append(queue, append(prefix, sf.Index...)) - continue - } - - // If this field has type [dyn.Value], we populate it with the source [dyn.Value] from [ToTyped]. - if sf.IsExported() && sf.Type == configValueType { - if out.ValueField != nil { - panic("multiple dyn.Value fields") - } - out.ValueField = append(prefix, sf.Index...) - continue - } - - jtag := structtag.JSONTag(sf.Tag.Get("json")) - name := jtag.Name() - if name == "" || name == "-" { - continue - } - - // Top level fields always take precedence. - // Therefore, if it is already set, we ignore it. - if _, ok := out.Fields[name]; ok { - continue - } - - out.FieldNames = append(out.FieldNames, name) - out.Fields[name] = append(prefix, sf.Index...) - if !jtag.OmitEmpty() && !jtag.OmitZero() { - out.ForceEmpty[name] = true - } - out.GolangNames[name] = sf.Name - - // The field is declared directly in this struct, so it is governed by - // this struct's ForceSendFields (if it has one). - if forceSendFieldsIndex != nil { - out.ForceSendFieldsIndex[name] = forceSendFieldsIndex - } - } - } - - return out -} - -type FieldValue struct { - Key string - Value reflect.Value - IsForced bool -} - -func (s *structInfo) FieldValues(v reflect.Value) []FieldValue { - out := make([]FieldValue, 0, len(s.Fields)) - - for _, k := range s.FieldNames { - fv := fieldByIndex(v, s.Fields[k]) - - if fv.IsValid() { - isForced := true - - // TODO: we should use isEmptyForOmitEmpty instead of IsZero() - if fv.IsZero() { - isForced = s.isForceSend(v, k) - } - - out = append(out, FieldValue{ - Key: k, - Value: fv, - IsForced: isForced, - }) - } - } - - return out -} - -// isForceSend reports whether the field named k is listed in the ForceSendFields -// that governs it (see structInfo.ForceSendFieldsIndex). -func (s *structInfo) isForceSend(v reflect.Value, k string) bool { - index, ok := s.ForceSendFieldsIndex[k] - if !ok { - return false - } - fsf := fieldByIndex(v, index) - if !fsf.IsValid() { - return false - } - return slices.Contains(fsf.Interface().([]string), s.GolangNames[k]) -} - -// fieldByIndex resolves the value at the given index path, dereferencing embedded -// pointer structs on the way. It returns an invalid value if a nil pointer is met. -func fieldByIndex(v reflect.Value, index []int) reflect.Value { - for i, x := range index { - if i > 0 { - if v.Kind() == reflect.Pointer && v.Type().Elem().Kind() == reflect.Struct { - if v.IsNil() { - return reflect.Value{} - } - v = v.Elem() - } - } - v = v.Field(x) - } - return v -} - -// Type of [dyn.Value]. -var configValueType = reflect.TypeFor[dyn.Value]() diff --git a/libs/dyn/convert/struct_info_test.go b/libs/dyn/convert/struct_info_test.go deleted file mode 100644 index f921523c391..00000000000 --- a/libs/dyn/convert/struct_info_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package convert - -import ( - "reflect" - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestStructInfoPlain(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar,omitempty"` - - // Baz must be skipped. - Baz string `json:""` - - // Qux must be skipped. - Qux string `json:"-"` - } - - si := getStructInfo(reflect.TypeFor[Tmp]()) - assert.Len(t, si.Fields, 2) - assert.Equal(t, []int{0}, si.Fields["foo"]) - assert.Equal(t, []int{1}, si.Fields["bar"]) -} - -func TestStructInfoAnonymousByValue(t *testing.T) { - type Bar struct { - Bar string `json:"bar"` - } - - type Foo struct { - Foo string `json:"foo"` - Bar - } - - type Tmp struct { - Foo - } - - si := getStructInfo(reflect.TypeFor[Tmp]()) - assert.Len(t, si.Fields, 2) - assert.Equal(t, []int{0, 0}, si.Fields["foo"]) - assert.Equal(t, []int{0, 1, 0}, si.Fields["bar"]) -} - -func TestStructInfoAnonymousByValuePrecedence(t *testing.T) { - type Bar struct { - Bar string `json:"bar"` - } - - type Foo struct { - Foo string `json:"foo"` - Bar - } - - type Tmp struct { - // "foo" comes from [Foo]. - Foo - // "bar" comes from [Bar] directly, not through [Foo]. - Bar - } - - si := getStructInfo(reflect.TypeFor[Tmp]()) - assert.Len(t, si.Fields, 2) - assert.Equal(t, []int{0, 0}, si.Fields["foo"]) - assert.Equal(t, []int{1, 0}, si.Fields["bar"]) -} - -func TestStructInfoAnonymousByPointer(t *testing.T) { - type Bar struct { - Bar string `json:"bar"` - } - - type Foo struct { - Foo string `json:"foo"` - *Bar - } - - type Tmp struct { - *Foo - } - - si := getStructInfo(reflect.TypeFor[Tmp]()) - assert.Len(t, si.Fields, 2) - assert.Equal(t, []int{0, 0}, si.Fields["foo"]) - assert.Equal(t, []int{0, 1, 0}, si.Fields["bar"]) -} - -func TestStructInfoFieldValues(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar"` - } - - src := Tmp{ - Foo: "foo", - Bar: "bar", - } - - si := getStructInfo(reflect.TypeFor[Tmp]()) - fv := si.FieldValues(reflect.ValueOf(src)) - assert.Len(t, fv, 2) - assert.Equal(t, "foo", fv[0].Key) - assert.True(t, reflect.ValueOf("foo").Equal(fv[0].Value)) - assert.Equal(t, "bar", fv[1].Key) - assert.True(t, reflect.ValueOf("bar").Equal(fv[1].Value)) -} - -func TestStructInfoFieldValuesAnonymousByValue(t *testing.T) { - type Bar struct { - Bar string `json:"bar"` - } - - type Foo struct { - Foo string `json:"foo"` - Bar - } - - type Tmp struct { - Foo - } - - src := Tmp{ - Foo: Foo{ - Foo: "foo", - Bar: Bar{ - Bar: "bar", - }, - }, - } - - si := getStructInfo(reflect.TypeFor[Tmp]()) - fv := si.FieldValues(reflect.ValueOf(src)) - assert.Len(t, fv, 2) - assert.Equal(t, "foo", fv[0].Key) - assert.Equal(t, "bar", fv[1].Key) -} - -func TestStructInfoFieldValuesAnonymousByPointer(t *testing.T) { - type Bar struct { - Bar string `json:"bar"` - } - - type Foo struct { - Foo string `json:"foo"` - *Bar - } - - type Tmp struct { - *Foo - } - - // Test that the embedded fields are dereferenced properly. - t.Run("all are set", func(t *testing.T) { - src := Tmp{ - Foo: &Foo{ - Foo: "foo", - Bar: &Bar{ - Bar: "bar", - }, - }, - } - - si := getStructInfo(reflect.TypeFor[Tmp]()) - fv := si.FieldValues(reflect.ValueOf(src)) - assert.Len(t, fv, 2) - assert.Equal(t, "foo", fv[0].Key) - assert.Equal(t, "bar", fv[1].Key) - }) - - // Test that fields of embedded types are skipped if the embedded type is nil. - t.Run("top level is set", func(t *testing.T) { - src := Tmp{ - Foo: &Foo{ - Foo: "foo", - Bar: nil, - }, - } - - si := getStructInfo(reflect.TypeFor[Tmp]()) - fv := si.FieldValues(reflect.ValueOf(src)) - assert.Len(t, fv, 1) - assert.Equal(t, "foo", fv[0].Key) - }) - - // Test that fields of embedded types are skipped if the embedded type is nil. - t.Run("none are set", func(t *testing.T) { - src := Tmp{ - Foo: nil, - } - - si := getStructInfo(reflect.TypeFor[Tmp]()) - fv := si.FieldValues(reflect.ValueOf(src)) - assert.Empty(t, fv) - }) -} - -func TestStructInfoValueFieldAbsent(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - } - - si := getStructInfo(reflect.TypeFor[Tmp]()) - assert.Nil(t, si.ValueField) -} - -func TestStructInfoValueFieldPresent(t *testing.T) { - type Tmp struct { - Foo dyn.Value - } - - si := getStructInfo(reflect.TypeFor[Tmp]()) - assert.NotNil(t, si.ValueField) -} - -func TestStructInfoValueFieldMultiple(t *testing.T) { - type Tmp struct { - Foo dyn.Value - Bar dyn.Value - } - - assert.Panics(t, func() { - getStructInfo(reflect.TypeFor[Tmp]()) - }) -} diff --git a/libs/dyn/convert/to_typed.go b/libs/dyn/convert/to_typed.go deleted file mode 100644 index 87b0fe7d8ca..00000000000 --- a/libs/dyn/convert/to_typed.go +++ /dev/null @@ -1,350 +0,0 @@ -package convert - -import ( - "fmt" - "reflect" - "slices" - "strconv" - - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" -) - -// Populate a destination typed value from a source dynamic value. -// -// At any point while walking the destination type tree using -// reflection, if this function sees an exported field with type dyn.Value it -// will populate that field with the appropriate source dynamic value. -// see PR: https://github.com/databricks/cli/pull/1010 -func ToTyped(dst any, src dyn.Value) error { - dstv := reflect.ValueOf(dst) - - // Dereference pointer if necessary - for dstv.Kind() == reflect.Pointer { - // If the source value is nil and the destination is a settable pointer, - // set the destination to nil. Also see `end_to_end_test.go`. - if dstv.CanSet() && src.Kind() == dyn.KindNil { - dstv.SetZero() - return nil - } - if dstv.IsNil() { - dstv.Set(reflect.New(dstv.Type().Elem())) - } - dstv = dstv.Elem() - } - - // Verify that vv is settable. - if !dstv.CanSet() { - panic("cannot set destination value") - } - - switch dstv.Kind() { - case reflect.Struct: - // Handle SDK native types using JSON unmarshaling. - if slices.Contains(sdkNativeTypes, dstv.Type()) { - return toTypedSDKNative(dstv, src) - } - return toTypedStruct(dstv, src) - case reflect.Map: - return toTypedMap(dstv, src) - case reflect.Slice: - return toTypedSlice(dstv, src) - case reflect.String: - return toTypedString(dstv, src) - case reflect.Bool: - return toTypedBool(dstv, src) - case reflect.Int, reflect.Int32, reflect.Int64: - return toTypedInt(dstv, src) - case reflect.Float32, reflect.Float64: - return toTypedFloat(dstv, src) - case reflect.Interface: - return toTypedInterface(dstv, src) - default: - // Fall through to the error case. - } - - return fmt.Errorf("unsupported type: %s", dstv.Kind()) -} - -func toTypedStruct(dst reflect.Value, src dyn.Value) error { - switch src.Kind() { - case dyn.KindMap: - // Zero the destination struct such that fields - // that aren't present in [src] are cleared. - dst.SetZero() - - info := getStructInfo(dst.Type()) - - for _, pair := range src.MustMap().Pairs() { - pk := pair.Key - pv := pair.Value - jsonKey := pk.MustString() - - index, ok := info.Fields[jsonKey] - if !ok { - // Ignore unknown fields. - // A warning will be printed later. See PR #904. - continue - } - - f := getOrNewFieldByIndex(dst, index) - - err := ToTyped(f.Addr().Interface(), pv) - if err != nil { - return err - } - - if pv.IsZero() { - // The field's zero value must still serialize, so append its Go name - // to the ForceSendFields of the struct that declares it. That struct - // shares a prefix with the field's index path, so it is already - // allocated by the walk above. - if fsfIndex, ok := info.ForceSendFieldsIndex[jsonKey]; ok { - fsf := getOrNewFieldByIndex(dst, fsfIndex) - fsf.Set(reflect.Append(fsf, reflect.ValueOf(info.GolangNames[jsonKey]))) - } - } - } - - // Populate field(s) for [dyn.Value], if any. - if info.ValueField != nil { - vv := dst.FieldByIndex(info.ValueField) - vv.Set(reflect.ValueOf(src)) - } - - return nil - case dyn.KindNil: - dst.SetZero() - return nil - case dyn.KindString: - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(src.MustString()) { - dst.SetZero() - return nil - } - default: - // Fall through to the error case. - } - - return TypeError{ - value: src, - msg: fmt.Sprintf("expected a map, found a %s", src.Kind()), - } -} - -// getOrNewFieldByIndex resolves the value at the given index path within an addressable -// struct, allocating intermediate structs embedded as pointer types along the way. -// Code inspired by [reflect.FieldByIndex] implementation. -func getOrNewFieldByIndex(v reflect.Value, index []int) reflect.Value { - for i, x := range index { - if i > 0 { - if v.Kind() == reflect.Pointer { - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - v = v.Elem() - } - } - v = v.Field(x) - } - return v -} - -func toTypedMap(dst reflect.Value, src dyn.Value) error { - switch src.Kind() { - case dyn.KindMap: - m := src.MustMap() - - // Always overwrite. - dst.Set(reflect.MakeMapWithSize(dst.Type(), m.Len())) - for _, pair := range m.Pairs() { - pk := pair.Key - pv := pair.Value - kv := reflect.ValueOf(pk.MustString()) - kt := dst.Type().Key() - vv := reflect.New(dst.Type().Elem()) - err := ToTyped(vv.Interface(), pv) - if err != nil { - return err - } - dst.SetMapIndex(kv.Convert(kt), vv.Elem()) - } - return nil - case dyn.KindNil: - dst.SetZero() - return nil - case dyn.KindString: - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(src.MustString()) { - dst.SetZero() - return nil - } - default: - // Fall through to the error case. - } - - return TypeError{ - value: src, - msg: fmt.Sprintf("expected a map, found a %s", src.Kind()), - } -} - -func toTypedSlice(dst reflect.Value, src dyn.Value) error { - switch src.Kind() { - case dyn.KindSequence: - seq := src.MustSequence() - - // Always overwrite. - dst.Set(reflect.MakeSlice(dst.Type(), len(seq), len(seq))) - for i := range seq { - err := ToTyped(dst.Index(i).Addr().Interface(), seq[i]) - if err != nil { - return err - } - } - return nil - case dyn.KindNil: - dst.SetZero() - return nil - case dyn.KindString: - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(src.MustString()) { - dst.SetZero() - return nil - } - default: - // Fall through to the error case. - } - - return TypeError{ - value: src, - msg: fmt.Sprintf("expected a sequence, found a %s", src.Kind()), - } -} - -func toTypedString(dst reflect.Value, src dyn.Value) error { - switch src.Kind() { - case dyn.KindString: - dst.SetString(src.MustString()) - return nil - case dyn.KindBool: - dst.SetString(strconv.FormatBool(src.MustBool())) - return nil - case dyn.KindInt: - dst.SetString(strconv.FormatInt(src.MustInt(), 10)) - return nil - case dyn.KindFloat: - dst.SetString(strconv.FormatFloat(src.MustFloat(), 'f', -1, 64)) - return nil - default: - // Fall through to the error case. - } - - return TypeError{ - value: src, - msg: fmt.Sprintf("expected a string, found a %s", src.Kind()), - } -} - -func toTypedBool(dst reflect.Value, src dyn.Value) error { - switch src.Kind() { - case dyn.KindBool: - dst.SetBool(src.MustBool()) - return nil - case dyn.KindString: - // See https://yaml.org/type/bool.html. - switch src.MustString() { - case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON", "true", "True", "TRUE": - dst.SetBool(true) - return nil - case "n", "N", "no", "No", "NO", "off", "Off", "OFF", "false", "False", "FALSE": - dst.SetBool(false) - return nil - } - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(src.MustString()) { - dst.SetZero() - return nil - } - default: - // Fall through to the error case. - } - - return TypeError{ - value: src, - msg: fmt.Sprintf("expected a boolean, found a %s", src.Kind()), - } -} - -func toTypedInt(dst reflect.Value, src dyn.Value) error { - switch src.Kind() { - case dyn.KindInt: - dst.SetInt(src.MustInt()) - return nil - case dyn.KindFloat: - v := src.MustFloat() - if v == float64(int64(v)) { - // If the destination is smaller than int64, but the value to set is bigger - // then destination overflows and is set to -1 - dst.SetInt(int64(src.MustFloat())) - return nil - } - - return TypeError{ - value: src, - msg: fmt.Sprintf("expected an int, found a %s", src.Kind()), - } - case dyn.KindString: - if i64, err := strconv.ParseInt(src.MustString(), 10, 64); err == nil { - dst.SetInt(i64) - return nil - } - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(src.MustString()) { - dst.SetZero() - return nil - } - default: - // Fall through to the error case. - } - - return TypeError{ - value: src, - msg: fmt.Sprintf("expected an int, found a %s", src.Kind()), - } -} - -func toTypedFloat(dst reflect.Value, src dyn.Value) error { - switch src.Kind() { - case dyn.KindFloat: - dst.SetFloat(src.MustFloat()) - return nil - case dyn.KindString: - if f64, err := strconv.ParseFloat(src.MustString(), 64); err == nil { - dst.SetFloat(f64) - return nil - } - // Ignore pure variable references (e.g. ${var.foo}). - if dynvar.IsPureVariableReference(src.MustString()) { - dst.SetZero() - return nil - } - default: - // Fall through to the error case. - } - - return TypeError{ - value: src, - msg: fmt.Sprintf("expected a float, found a %s", src.Kind()), - } -} - -func toTypedInterface(dst reflect.Value, src dyn.Value) error { - if src.Kind() == dyn.KindNil { - dst.Set(reflect.Zero(dst.Type())) - return nil - } - - dst.Set(reflect.ValueOf(src.AsAny())) - return nil -} diff --git a/libs/dyn/convert/to_typed_test.go b/libs/dyn/convert/to_typed_test.go deleted file mode 100644 index a838dfab33f..00000000000 --- a/libs/dyn/convert/to_typed_test.go +++ /dev/null @@ -1,790 +0,0 @@ -package convert - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestToTypedStruct(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar,omitempty"` - - // Baz must be skipped. - Baz string `json:""` - - // Qux must be skipped. - Qux string `json:"-"` - } - - var out Tmp - v := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V("baz"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Equal(t, "bar", out.Foo) - assert.Equal(t, "baz", out.Bar) -} - -func TestToTypedStructOverwrite(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar,omitempty"` - - // Baz must be skipped. - Baz string `json:""` - - // Qux must be skipped. - Qux string `json:"-"` - } - - out := Tmp{ - Foo: "baz", - Bar: "qux", - } - v := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V("baz"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Equal(t, "bar", out.Foo) - assert.Equal(t, "baz", out.Bar) -} - -func TestToTypedStructClearFields(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar,omitempty"` - } - - // Struct value with non-empty fields. - out := Tmp{ - Foo: "baz", - Bar: "qux", - } - - // Value is an empty map. - v := dyn.V(map[string]dyn.Value{}) - - // The previously set fields should be cleared. - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Equal(t, Tmp{}, out) -} - -func TestToTypedStructClearFieldsForceSend(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar,omitempty"` - ForceSendFields []string `json:"-"` - } - - // Struct value with non-empty fields. - out := Tmp{ - Foo: "baz", - Bar: "qux", - } - - // Value is an empty map. - v := dyn.V(map[string]dyn.Value{}) - - // The previously set fields should be cleared. - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Equal(t, Tmp{}, out) -} - -func TestToTypedStructZeroFields(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar,omitempty"` - } - - // Struct value with non-empty fields. - out := Tmp{ - Foo: "baz", - Bar: "qux", - } - - // Value is an empty map. - v := dyn.V(map[string]dyn.Value{ - "foo": dyn.V(""), - "bar": dyn.V(""), - }) - - // The previously set fields should be cleared. - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Equal(t, Tmp{}, out) -} - -func TestToTypedStructZeroFieldsForceSend(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - Bar string `json:"bar,omitempty"` - NumWorkers int `json:"num_workers,omitempty"` - ForceSendFields []string `json:"-"` - } - - // Struct value with non-empty fields. - out := Tmp{ - Foo: "baz", - Bar: "qux", - } - - // Value is an empty map. - m := dyn.Mapping{} - m.SetLoc("foo", nil, dyn.V("")) - m.SetLoc("bar", nil, dyn.V("")) - m.SetLoc("num_workers", nil, dyn.V(int64(0))) - v := dyn.V(m) - - // The previously set fields should be cleared. - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Equal(t, Tmp{ForceSendFields: []string{"Foo", "Bar", "NumWorkers"}}, out) -} - -func TestToTypedStructAnonymousByValue(t *testing.T) { - type Bar struct { - Bar string `json:"bar"` - } - - type Foo struct { - Foo string `json:"foo"` - Bar - } - - type Tmp struct { - Foo - } - - var out Tmp - v := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V("baz"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Equal(t, "bar", out.Foo.Foo) - assert.Equal(t, "baz", out.Bar.Bar) -} - -func TestToTypedStructAnonymousByPointer(t *testing.T) { - type Bar struct { - Bar string `json:"bar"` - } - - type Foo struct { - Foo string `json:"foo"` - *Bar - } - - type Tmp struct { - *Foo - } - - var out Tmp - v := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V("baz"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Equal(t, "bar", out.Foo.Foo) - assert.Equal(t, "baz", out.Bar.Bar) -} - -func TestToTypedStructNil(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - } - - out := Tmp{} - err := ToTyped(&out, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, Tmp{}, out) -} - -func TestToTypedStructNilOverwrite(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - } - - out := Tmp{"bar"} - err := ToTyped(&out, dyn.NilValue) - require.NoError(t, err) - assert.Equal(t, Tmp{}, out) -} - -func TestToTypedStructWithValueField(t *testing.T) { - type Tmp struct { - Foo string `json:"foo"` - - ConfigValue dyn.Value - } - - var out Tmp - v := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Equal(t, "bar", out.Foo) - assert.Equal(t, v, out.ConfigValue) -} - -func TestToTypedMap(t *testing.T) { - out := map[string]string{} - - v := dyn.V(map[string]dyn.Value{ - "key": dyn.V("value"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Len(t, out, 1) - assert.Equal(t, "value", out["key"]) -} - -func TestToTypedMapOverwrite(t *testing.T) { - out := map[string]string{ - "foo": "bar", - } - - v := dyn.V(map[string]dyn.Value{ - "bar": dyn.V("qux"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Len(t, out, 1) - assert.Equal(t, "qux", out["bar"]) -} - -func TestToTypedMapWithPointerElement(t *testing.T) { - var out map[string]*string - - v := dyn.V(map[string]dyn.Value{ - "key": dyn.V("value"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Len(t, out, 1) - assert.Equal(t, "value", *out["key"]) -} - -func TestToTypedMapNil(t *testing.T) { - out := map[string]string{} - err := ToTyped(&out, dyn.NilValue) - require.NoError(t, err) - assert.Nil(t, out) -} - -func TestToTypedMapNilOverwrite(t *testing.T) { - out := map[string]string{ - "foo": "bar", - } - err := ToTyped(&out, dyn.NilValue) - require.NoError(t, err) - assert.Nil(t, out) -} - -func TestToTypedSlice(t *testing.T) { - var out []string - - v := dyn.V([]dyn.Value{ - dyn.V("foo"), - dyn.V("bar"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Len(t, out, 2) - assert.Equal(t, "foo", out[0]) - assert.Equal(t, "bar", out[1]) -} - -func TestToTypedSliceOverwrite(t *testing.T) { - out := []string{"qux"} - - v := dyn.V([]dyn.Value{ - dyn.V("foo"), - dyn.V("bar"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Len(t, out, 2) - assert.Equal(t, "foo", out[0]) - assert.Equal(t, "bar", out[1]) -} - -func TestToTypedSliceWithPointerElement(t *testing.T) { - var out []*string - - v := dyn.V([]dyn.Value{ - dyn.V("foo"), - dyn.V("bar"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Len(t, out, 2) - assert.Equal(t, "foo", *out[0]) - assert.Equal(t, "bar", *out[1]) -} - -func TestToTypedSliceNil(t *testing.T) { - var out []string - err := ToTyped(&out, dyn.NilValue) - require.NoError(t, err) - assert.Nil(t, out) -} - -func TestToTypedSliceNilOverwrite(t *testing.T) { - out := []string{"foo"} - err := ToTyped(&out, dyn.NilValue) - require.NoError(t, err) - assert.Nil(t, out) -} - -func TestToTypedString(t *testing.T) { - var out string - err := ToTyped(&out, dyn.V("foo")) - require.NoError(t, err) - assert.Equal(t, "foo", out) -} - -func TestToTypedStringOverwrite(t *testing.T) { - out := "bar" - err := ToTyped(&out, dyn.V("foo")) - require.NoError(t, err) - assert.Equal(t, "foo", out) -} - -func TestToTypedStringFromBool(t *testing.T) { - var out string - err := ToTyped(&out, dyn.V(true)) - require.NoError(t, err) - assert.Equal(t, "true", out) -} - -func TestToTypedStringFromInt(t *testing.T) { - var out string - err := ToTyped(&out, dyn.V(123)) - require.NoError(t, err) - assert.Equal(t, "123", out) -} - -func TestToTypedStringFromFloat(t *testing.T) { - var out string - err := ToTyped(&out, dyn.V(1.2)) - require.NoError(t, err) - assert.Equal(t, "1.2", out) -} - -func TestToTypedBool(t *testing.T) { - var out bool - err := ToTyped(&out, dyn.V(true)) - require.NoError(t, err) - assert.True(t, out) -} - -func TestToTypedBoolOverwrite(t *testing.T) { - out := true - err := ToTyped(&out, dyn.V(false)) - require.NoError(t, err) - assert.False(t, out) -} - -func TestToTypedBoolFromString(t *testing.T) { - var out bool - - // True-ish - for _, v := range []string{"y", "yes", "on", "true", "True", "TRUE"} { - err := ToTyped(&out, dyn.V(v)) - require.NoError(t, err) - assert.True(t, out) - } - - // False-ish - for _, v := range []string{"n", "no", "off", "false", "False", "FALSE"} { - err := ToTyped(&out, dyn.V(v)) - require.NoError(t, err) - assert.False(t, out) - } - - // Other - err := ToTyped(&out, dyn.V("some other string")) - require.Error(t, err) -} - -func TestToTypedBoolFromStringVariableReference(t *testing.T) { - out := true - err := ToTyped(&out, dyn.V("${var.foo}")) - require.NoError(t, err) - assert.False(t, out) -} - -func TestToTypedInt(t *testing.T) { - var out int - err := ToTyped(&out, dyn.V(1234)) - require.NoError(t, err) - assert.Equal(t, int(1234), out) -} - -func TestToTypedInt32(t *testing.T) { - var out32 int32 - err := ToTyped(&out32, dyn.V(1235)) - require.NoError(t, err) - assert.Equal(t, int32(1235), out32) -} - -func TestToTypedInt64(t *testing.T) { - var out64 int64 - err := ToTyped(&out64, dyn.V(1236)) - require.NoError(t, err) - assert.Equal(t, int64(1236), out64) -} - -func TestToTypedIntOverwrite(t *testing.T) { - out := 123 - err := ToTyped(&out, dyn.V(1234)) - require.NoError(t, err) - assert.Equal(t, int(1234), out) -} - -func TestToTypedInt32Overwrite(t *testing.T) { - var out32 int32 = 123 - err := ToTyped(&out32, dyn.V(1234)) - require.NoError(t, err) - assert.Equal(t, int32(1234), out32) -} - -func TestToTypedInt64Overwrite(t *testing.T) { - var out64 int64 = 123 - err := ToTyped(&out64, dyn.V(1234)) - require.NoError(t, err) - assert.Equal(t, int64(1234), out64) -} - -func TestToTypedIntFromStringError(t *testing.T) { - var out int - err := ToTyped(&out, dyn.V("abc")) - require.Error(t, err) -} - -func TestToTypedIntFromStringInt(t *testing.T) { - var out int - err := ToTyped(&out, dyn.V("123")) - require.NoError(t, err) - assert.Equal(t, int(123), out) -} - -func TestToTypedIntFromStringVariableReference(t *testing.T) { - out := 123 - err := ToTyped(&out, dyn.V("${var.foo}")) - require.NoError(t, err) - assert.Equal(t, int(0), out) -} - -func TestToTypedIntFromFloat(t *testing.T) { - var out int - err := ToTyped(&out, dyn.V(1.0)) - require.NoError(t, err) - assert.Equal(t, int(1), out) -} - -func TestToTypedIntFromFloatError(t *testing.T) { - var out int - err := ToTyped(&out, dyn.V(1.2)) - require.ErrorContains(t, err, "expected an int, found a float") -} - -func TestToTypedFloat32(t *testing.T) { - var out float32 - err := ToTyped(&out, dyn.V(float32(1.0))) - require.NoError(t, err) - assert.Zero(t, 1.0-out) -} - -func TestToTypedFloat64(t *testing.T) { - var out float64 - err := ToTyped(&out, dyn.V(float64(1.0))) - require.NoError(t, err) - assert.Zero(t, 1.0-out) -} - -func TestToTypedFloat32Overwrite(t *testing.T) { - var out float32 = 1.0 - err := ToTyped(&out, dyn.V(float32(2.0))) - require.NoError(t, err) - assert.Zero(t, 2.0-out) -} - -func TestToTypedFloat64Overwrite(t *testing.T) { - out := 1.0 - err := ToTyped(&out, dyn.V(float64(2.0))) - require.NoError(t, err) - assert.Zero(t, 2.0-out) -} - -func TestToTypedFloat32FromStringError(t *testing.T) { - var out float32 - err := ToTyped(&out, dyn.V("abc")) - require.Error(t, err) -} - -func TestToTypedFloat64FromStringError(t *testing.T) { - var out float64 - err := ToTyped(&out, dyn.V("abc")) - require.Error(t, err) -} - -func TestToTypedFloat32FromString(t *testing.T) { - var out float32 - err := ToTyped(&out, dyn.V("1.2")) - require.NoError(t, err) - assert.Zero(t, 1.2-out) -} - -func TestToTypedFloat64FromString(t *testing.T) { - var out float64 - err := ToTyped(&out, dyn.V("1.2")) - require.NoError(t, err) - assert.Zero(t, 1.2-out) -} - -func TestToTypedFloat32FromStringVariableReference(t *testing.T) { - var out float32 = 1.0 - err := ToTyped(&out, dyn.V("${var.foo}")) - require.NoError(t, err) - assert.Zero(t, out) -} - -func TestToTypedFloat64FromStringVariableReference(t *testing.T) { - out := 1.0 - err := ToTyped(&out, dyn.V("${var.foo}")) - require.NoError(t, err) - assert.Zero(t, out) -} - -func TestToTypedWithAliasKeyType(t *testing.T) { - type custom string - - var out map[custom]string - v := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V("baz"), - }) - - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Len(t, out, 2) - assert.Equal(t, "bar", out["foo"]) - assert.Equal(t, "baz", out["bar"]) -} - -func TestToTypedAnyWithBool(t *testing.T) { - var out any - err := ToTyped(&out, dyn.V(false)) - require.NoError(t, err) - assert.Equal(t, false, out) - - err = ToTyped(&out, dyn.V(true)) - require.NoError(t, err) - assert.Equal(t, true, out) -} - -func TestToTypedAnyWithMap(t *testing.T) { - var out any - v := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - "bar": dyn.V("baz"), - }) - err := ToTyped(&out, v) - require.NoError(t, err) - assert.Equal(t, map[string]any{"foo": "bar", "bar": "baz"}, out) -} - -func TestToTypedAnyWithNil(t *testing.T) { - var out any - err := ToTyped(&out, dyn.NilValue) - require.NoError(t, err) - assert.Nil(t, out) -} - -func TestToTypedEmbeddedStructForceSendFields(t *testing.T) { - type Inner struct { - InnerField string `json:"inner_field"` - ForceSendFields []string `json:"-"` - } - - type Outer struct { - OuterField string `json:"outer_field"` - Inner - } - - var out Outer - m := dyn.Mapping{} - m.SetLoc("outer_field", nil, dyn.V("")) - m.SetLoc("inner_field", nil, dyn.V("")) - v := dyn.V(m) - - err := ToTyped(&out, v) - require.NoError(t, err) - - // Bug: ForceSendFields contains "OuterField" which belongs to outer struct - // Expected: ForceSendFields should only contain "InnerField" - assert.Equal(t, []string{"InnerField"}, out.ForceSendFields) -} - -func TestToTypedMultipleEmbeddedStructsForceSendFields(t *testing.T) { - type First struct { - FirstField string `json:"first_field"` - ForceSendFields []string `json:"-"` - } - - type Second struct { - SecondField string `json:"second_field"` - ForceSendFields []string `json:"-"` - } - - type Outer struct { - OuterField string `json:"outer_field"` - First - Second - } - - var out Outer - m := dyn.Mapping{} - m.SetLoc("outer_field", nil, dyn.V("")) - m.SetLoc("first_field", nil, dyn.V("")) - m.SetLoc("second_field", nil, dyn.V("")) - v := dyn.V(m) - - err := ToTyped(&out, v) - require.NoError(t, err) - - // Each embedded struct should only get its own fields in ForceSendFields - assert.Equal(t, []string{"FirstField"}, out.First.ForceSendFields) - assert.Equal(t, []string{"SecondField"}, out.Second.ForceSendFields) -} - -func TestToTypedMixedForceSendFields(t *testing.T) { - type First struct { - FirstField string `json:"first_field"` - // No ForceSendFields - } - - type Second struct { - SecondField string `json:"second_field"` - ForceSendFields []string `json:"-"` - } - - type Outer struct { - OuterField string `json:"outer_field"` - ForceSendFields []string `json:"-"` - First - Second - } - - var out Outer - m := dyn.Mapping{} - m.SetLoc("outer_field", nil, dyn.V("")) - m.SetLoc("first_field", nil, dyn.V("")) - m.SetLoc("second_field", nil, dyn.V("")) - v := dyn.V(m) - - err := ToTyped(&out, v) - require.NoError(t, err) - - // Outer should get its own direct fields - assert.Equal(t, []string{"OuterField"}, out.ForceSendFields) - // First has no ForceSendFields field, so nothing to check - // Second should get its own field - assert.Equal(t, []string{"SecondField"}, out.Second.ForceSendFields) -} - -func TestToTypedFieldByNameBugRegressionTest(t *testing.T) { - // This test reproduces the EXACT bug: only direct field is zero, embedded fields are non-zero - // The bug occurred because dst.FieldByName("ForceSendFields") finds embedded ForceSendFields - // instead of direct ForceSendFields (which doesn't exist) - type BaseResource struct { - ID string `json:"id,omitempty"` - } - - type JobSettings struct { - Name string `json:"name"` - ForceSendFields []string `json:"-"` - } - - type Job struct { - BaseResource // embedded at [0] - JobSettings // embedded at [1], has ForceSendFields - Permissions []string `json:"permissions,omitempty"` // direct at [2] - } - - var out Job - m := dyn.Mapping{} - // Non-zero JobSettings fields - these should NOT go to ForceSendFields - m.SetLoc("name", nil, dyn.V("test-job")) - // Zero direct field - this SHOULD trigger the bug with old code - m.SetLoc("permissions", nil, dyn.V([]dyn.Value{})) - v := dyn.V(m) - - err := ToTyped(&out, v) - require.NoError(t, err) - - // "Permissions" should be dropped, JobSettings.ForceSendFields stays empty - assert.Empty(t, out.ForceSendFields) - assert.Equal(t, "test-job", out.Name) - assert.Empty(t, out.Permissions) -} - -func TestToTypedDeeplyEmbeddedStructForceSendFields(t *testing.T) { - // Mirrors resources.PostgresProject -> PostgresProjectConfig -> ProjectSpec: - // Spec is embedded two levels down and Wrapper shadows ForceSendFields to keep - // its own direct field out of Spec's ForceSendFields. A zero-value spec field - // must route to Spec.ForceSendFields, not Wrapper's, otherwise the SDK marshaler - // fails with "field ... cannot be found in struct". - type Spec struct { - SpecField bool `json:"spec_field,omitempty"` - ForceSendFields []string `json:"-"` - } - - type Wrapper struct { - Spec - WrapperField string `json:"wrapper_field,omitempty"` - ForceSendFields []string `json:"-"` - } - - type Outer struct { - Wrapper - } - - var out Outer - m := dyn.Mapping{} - m.SetLoc("spec_field", nil, dyn.V(false)) - m.SetLoc("wrapper_field", nil, dyn.V("")) - v := dyn.V(m) - - err := ToTyped(&out, v) - require.NoError(t, err) - - assert.Equal(t, []string{"SpecField"}, out.Spec.ForceSendFields) - assert.Equal(t, []string{"WrapperField"}, out.ForceSendFields) -} diff --git a/libs/dyn/drop_keys.go b/libs/dyn/drop_keys.go deleted file mode 100644 index 494f9b9cd88..00000000000 --- a/libs/dyn/drop_keys.go +++ /dev/null @@ -1,27 +0,0 @@ -package dyn - -func DropKeys(v Value, drop []string) (Value, error) { - var err error - nv, err := Walk(v, func(p Path, v Value) (Value, error) { - if len(p) == 0 { - return v, nil - } - - // Check if this key should be dropped. - for _, key := range drop { - if p[0].Key() != key { - continue - } - - return InvalidValue, ErrDrop - } - - // Pass through all other values. - return v, ErrSkip - }) - if err != nil { - return InvalidValue, err - } - - return nv, nil -} diff --git a/libs/dyn/drop_keys_test.go b/libs/dyn/drop_keys_test.go deleted file mode 100644 index 83a9744ca65..00000000000 --- a/libs/dyn/drop_keys_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package dyn - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestDropKeysTest(t *testing.T) { - v := V(map[string]Value{ - "key1": V("value1"), - "key2": V("value2"), - "key3": V("value3"), - }) - - vout, err := DropKeys(v, []string{"key1", "key3"}) - require.NoError(t, err) - - mv := vout.MustMap() - require.Equal(t, 1, mv.Len()) - v, ok := mv.GetByString("key2") - require.True(t, ok) - require.Equal(t, "value2", v.MustString()) -} diff --git a/libs/dyn/dynassert/assert.go b/libs/dyn/dynassert/assert.go deleted file mode 100644 index 9e02eeff39c..00000000000 --- a/libs/dyn/dynassert/assert.go +++ /dev/null @@ -1,36 +0,0 @@ -package dynassert - -import ( - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -// Equal is a variant of assert.Equal that first converts dyn.Value to any. The practical consequence is that maps are compared -// without order. Another difference here is that locations are compared via Location() function, which drops all -// but first and get rids of nils. -func Equal(t assert.TestingT, expected, actual any, msgAndArgs ...any) bool { - ev, eok := expected.(dyn.Value) - av, aok := actual.(dyn.Value) - if eok && aok && ev.IsValid() && av.IsValid() { - if !assert.Equal(t, ev.AsAny(), av.AsAny(), msgAndArgs...) { - return false - } - - // The values are equal on contents. Now compare the locations. - if !assert.Equal(t, ev.Location(), av.Location(), msgAndArgs...) { - return false - } - - // Walk ev and av and compare the locations of each element. - _, err := dyn.Walk(ev, func(p dyn.Path, evv dyn.Value) (dyn.Value, error) { - avv, err := dyn.GetByPath(av, p) - if assert.NoError(t, err, "unable to get value from actual value at path %v", p.String()) { - assert.Equal(t, evv.Location(), avv.Location()) - } - return evv, nil - }) - return assert.NoError(t, err) - } - - return assert.Equal(t, expected, actual, msgAndArgs...) -} diff --git a/libs/dyn/dynvar/lookup.go b/libs/dyn/dynvar/lookup.go deleted file mode 100644 index 2bc08f47d47..00000000000 --- a/libs/dyn/dynvar/lookup.go +++ /dev/null @@ -1,21 +0,0 @@ -package dynvar - -import ( - "errors" - - "github.com/databricks/cli/libs/dyn" -) - -// Lookup is the type of lookup functions that can be used with [Resolve]. -type Lookup func(path dyn.Path) (dyn.Value, error) - -// ErrSkipResolution is returned by a lookup function to indicate that the -// resolution of a variable reference should be skipped. -var ErrSkipResolution = errors.New("skip resolution") - -// DefaultLookup is the default lookup function used by [Resolve]. -func DefaultLookup(in dyn.Value) Lookup { - return func(path dyn.Path) (dyn.Value, error) { - return dyn.GetByPath(in, path) - } -} diff --git a/libs/dyn/dynvar/lookup_test.go b/libs/dyn/dynvar/lookup_test.go deleted file mode 100644 index 2341d72084b..00000000000 --- a/libs/dyn/dynvar/lookup_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package dynvar_test - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" - "github.com/stretchr/testify/assert" -) - -func TestDefaultLookup(t *testing.T) { - lookup := dynvar.DefaultLookup(dyn.V(map[string]dyn.Value{ - "a": dyn.V("a"), - "b": dyn.V("b"), - })) - - v1, err := lookup(dyn.NewPath(dyn.Key("a"))) - assert.NoError(t, err) - assert.Equal(t, dyn.V("a"), v1) - - v2, err := lookup(dyn.NewPath(dyn.Key("b"))) - assert.NoError(t, err) - assert.Equal(t, dyn.V("b"), v2) - - _, err = lookup(dyn.NewPath(dyn.Key("c"))) - assert.True(t, dyn.IsNoSuchKeyError(err)) -} diff --git a/libs/dyn/dynvar/resolve.go b/libs/dyn/dynvar/resolve.go deleted file mode 100644 index 99981a2e51b..00000000000 --- a/libs/dyn/dynvar/resolve.go +++ /dev/null @@ -1,255 +0,0 @@ -package dynvar - -import ( - "errors" - "fmt" - "maps" - "slices" - "strings" - - "github.com/databricks/cli/libs/dyn" -) - -// Resolve resolves variable references in the given input value using the provided lookup function. -// It returns the resolved output value and any error encountered during the resolution process. -// -// For example, given the input value: -// -// { -// "a": "a", -// "b": "${a}", -// "c": "${b}${b}", -// } -// -// The output value will be: -// -// { -// "a": "a", -// "b": "a", -// "c": "aa", -// } -// -// If the input value contains a variable reference that cannot be resolved, an error is returned. -// If a cycle is detected in the variable references, an error is returned. -// If for some path the resolution function returns [ErrSkipResolution], the variable reference is left in place. -// This is useful when some variable references are not yet ready to be interpolated. -func Resolve(in dyn.Value, fn Lookup) (out dyn.Value, err error) { - return resolver{in: in, fn: fn}.run() -} - -// ReferenceError is returned for an unresolved variable reference. Suggestions -// are carried as data so callers (which can import libs/diag) format them. -type ReferenceError struct { - Reference string // original reference text, e.g. "var.hst" - Suggestions []string // corrected references, e.g. ["var.host", "var.hosts"] -} - -func (e *ReferenceError) Error() string { - return fmt.Sprintf("reference does not exist: ${%s}", e.Reference) -} - -type lookupResult struct { - v dyn.Value - err error -} - -type resolver struct { //nolint:recvcheck // value receiver for run(), pointer for mutation methods - in dyn.Value - fn Lookup - - refs map[string]Ref - resolved map[string]dyn.Value - - // Memoization for lookups. - lookups map[string]lookupResult -} - -func (r resolver) run() (out dyn.Value, err error) { - err = r.collectVariableReferences() - if err != nil { - return dyn.InvalidValue, err - } - - err = r.resolveVariableReferences() - if err != nil { - return dyn.InvalidValue, err - } - - out, err = r.replaceVariableReferences() - if err != nil { - return dyn.InvalidValue, err - } - - return out, nil -} - -func (r *resolver) collectVariableReferences() (err error) { - r.refs = make(map[string]Ref) - - // First walk the input to gather all values with a variable reference. - _, err = dyn.Walk(r.in, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - ref, ok := NewRef(v) - if !ok { - // Skip values without variable references. - return v, nil - } - - r.refs[p.String()] = ref - return v, nil - }) - - return err -} - -func (r *resolver) resolveVariableReferences() (err error) { - // Initialize cache for lookups. - r.lookups = make(map[string]lookupResult) - - // Initialize cache for resolved variable references. - r.resolved = make(map[string]dyn.Value) - - // Resolve each variable reference (in order). - // We sort the keys here to ensure that we always resolve the same variable reference first. - // This is done such that the cycle detection error is deterministic. If we did not do this, - // we could enter the cycle at any point in the cycle and return varying errors. - keys := slices.Sorted(maps.Keys(r.refs)) - for _, key := range keys { - v, err := r.resolveRef(r.refs[key], []string{key}) - if err != nil { - return err - } - r.resolved[key] = v - } - - return nil -} - -func (r *resolver) resolveRef(ref Ref, seen []string) (dyn.Value, error) { - // This is an unresolved variable reference. - deps := ref.References() - - // Resolve each of the dependencies, then interpolate them in the ref. - resolved := make([]dyn.Value, len(deps)) - complete := true - - for j, dep := range deps { - // Cycle detection. - if slices.Contains(seen, dep) { - return dyn.InvalidValue, fmt.Errorf( - "cycle detected in field resolution: %s", - strings.Join(append(seen, dep), " -> "), - ) - } - - v, err := r.resolveKey(dep, append(seen, dep)) - - // If we should skip resolution of this key, index j will hold an invalid [dyn.Value]. - if errors.Is(err, ErrSkipResolution) { - complete = false - continue - } else if err != nil { - // Otherwise, propagate the error. - return dyn.InvalidValue, err - } - - resolved[j] = v - } - - // Interpolate the resolved values. - if ref.IsPure() && complete { - // If the variable reference is pure, we can substitute it. - // This is useful for interpolating values of non-string types. - // - // Note: we use the location of the variable reference to preserve the information - // of where it is used. This also means that relative path resolution is done - // relative to where a variable is used, not where it is defined. - // - return dyn.NewValue(resolved[0].Value(), ref.Value.Locations()), nil - } - - // Not pure; perform string interpolation. - // - // Substitute by byte offset rather than by searching for the match text: the same - // reference may also appear escaped ("$${foo} ${foo}"), and a search would replace - // that occurrence instead, corrupting the literal and leaving the real one unresolved. - var sb strings.Builder - consumed := 0 - for j := range ref.Matches { - // The value is invalid if resolution returned [ErrSkipResolution]. - // We must skip those and leave the original variable reference in place, - // which happens naturally by not advancing past its span. - if !resolved[j].IsValid() { - continue - } - - // Try to turn the resolved value into a string. - s, ok := resolved[j].AsString() - if !ok { - // Only allow primitive types to be converted to string. - switch resolved[j].Kind() { - case dyn.KindString, dyn.KindBool, dyn.KindInt, dyn.KindFloat, dyn.KindTime, dyn.KindNil: - s = fmt.Sprint(resolved[j].AsAny()) - default: - return dyn.InvalidValue, fmt.Errorf("cannot interpolate non-primitive value of type %s into string", resolved[j].Kind()) - } - } - - start, end := ref.Spans[j][0], ref.Spans[j][1] - sb.WriteString(ref.Str[consumed:start]) - sb.WriteString(s) - consumed = end - } - sb.WriteString(ref.Str[consumed:]) - - return dyn.NewValue(sb.String(), ref.Value.Locations()), nil -} - -func (r *resolver) resolveKey(key string, seen []string) (dyn.Value, error) { - // Check if we have already looked up this key. - if v, ok := r.lookups[key]; ok { - return v.v, v.err - } - - // Parse the key into a path. - p, err := dyn.NewPathFromString(key) - if err != nil { - return dyn.InvalidValue, err - } - - // Look up the value for the given key. - v, err := r.fn(p) - if err != nil { - if dyn.IsNoSuchKeyError(err) { - // Carry suggestions as data; the caller formats them via libs/diag. - err = &ReferenceError{Reference: key, Suggestions: dyn.SuggestedReferences(err, key)} - } - - // Cache the return value and return to the caller. - r.lookups[key] = lookupResult{v: dyn.InvalidValue, err: err} - return dyn.InvalidValue, err - } - - // If the returned value is a valid variable reference, resolve it. - ref, ok := NewRef(v) - if ok { - v, err = r.resolveRef(ref, seen) - } - - // Cache the return value and return to the caller. - r.lookups[key] = lookupResult{v: v, err: err} - return v, err -} - -func (r *resolver) replaceVariableReferences() (dyn.Value, error) { - // Walk the input and replace all variable references. - return dyn.Walk(r.in, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - nv, ok := r.resolved[p.String()] - if !ok { - // No variable reference; return the original value. - return v, nil - } - - // We have a variable reference; return the resolved value. - return nv, nil - }) -} diff --git a/libs/dyn/dynvar/resolve_test.go b/libs/dyn/dynvar/resolve_test.go deleted file mode 100644 index c4b28e88d3f..00000000000 --- a/libs/dyn/dynvar/resolve_test.go +++ /dev/null @@ -1,430 +0,0 @@ -package dynvar_test - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynvar" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func getByPath(t *testing.T, v dyn.Value, path string) dyn.Value { - v, err := dyn.Get(v, path) - require.NoError(t, err) - return v -} - -func TestResolve(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V("a"), - "b": dyn.V("${a}"), - "c": dyn.V("${a}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - assert.Equal(t, "a", getByPath(t, out, "a").MustString()) - assert.Equal(t, "a", getByPath(t, out, "b").MustString()) - assert.Equal(t, "a", getByPath(t, out, "c").MustString()) -} - -func TestResolveNotFound(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "b": dyn.V("${a}"), - }) - - _, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.ErrorContains(t, err, `reference does not exist: ${a}`) -} - -func TestResolveNotFoundSuggestsCloseKey(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "host": dyn.V("example.com"), - "b": dyn.V("${hst}"), - }) - - _, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.ErrorContains(t, err, "reference does not exist: ${hst}") - - var refErr *dynvar.ReferenceError - require.ErrorAs(t, err, &refErr) - assert.Equal(t, []string{"host"}, refErr.Suggestions) -} - -func TestResolveWithNesting(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V("${f.a}"), - "f": dyn.V(map[string]dyn.Value{ - "a": dyn.V("a"), - "b": dyn.V("${f.a}"), - }), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - assert.Equal(t, "a", getByPath(t, out, "a").MustString()) - assert.Equal(t, "a", getByPath(t, out, "f.a").MustString()) - assert.Equal(t, "a", getByPath(t, out, "f.b").MustString()) -} - -func TestResolveWithRecursion(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V("a"), - "b": dyn.V("${a}"), - "c": dyn.V("${b}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - assert.Equal(t, "a", getByPath(t, out, "a").MustString()) - assert.Equal(t, "a", getByPath(t, out, "b").MustString()) - assert.Equal(t, "a", getByPath(t, out, "c").MustString()) -} - -func TestResolveWithRecursionLoop(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V("a"), - "b": dyn.V("${c}"), - "c": dyn.V("${d}"), - "d": dyn.V("${b}"), - }) - - _, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - assert.ErrorContains(t, err, "cycle detected in field resolution: b -> c -> d -> b") -} - -func TestResolveWithRecursionLoopSelf(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V("${a}"), - }) - - _, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - assert.ErrorContains(t, err, "cycle detected in field resolution: a -> a") -} - -func TestResolveWithStringConcatenation(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V("a"), - "b": dyn.V("b"), - "c": dyn.V("${a}${b}${a}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - assert.Equal(t, "a", getByPath(t, out, "a").MustString()) - assert.Equal(t, "b", getByPath(t, out, "b").MustString()) - assert.Equal(t, "aba", getByPath(t, out, "c").MustString()) -} - -func TestResolveWithEscapedReference(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V("A"), - "escaped_only": dyn.V("$${a}"), - "escaped_first": dyn.V("$${a} ${a}"), - "real_first": dyn.V("${a} $${a}"), - "surrounded": dyn.V("$${a} ${a} $${a}"), - "other_name": dyn.V("$${zz} ${a}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - // The escape is preserved here; it is removed when the API payload is built. - assert.Equal(t, "$${a}", getByPath(t, out, "escaped_only").MustString()) - assert.Equal(t, "$${a} A", getByPath(t, out, "escaped_first").MustString()) - assert.Equal(t, "A $${a}", getByPath(t, out, "real_first").MustString()) - assert.Equal(t, "$${a} A $${a}", getByPath(t, out, "surrounded").MustString()) - assert.Equal(t, "$${zz} A", getByPath(t, out, "other_name").MustString()) -} - -func TestResolveWithTypeInterpolation(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V(1), - "b": dyn.V(2), - "c": dyn.V("${a} ${b}"), - "float_val": dyn.V(3.14), - "bool_true": dyn.V(true), - "bool_false": dyn.V(false), - "time_val": dyn.V(dyn.MustTime("2024-01-01")), - "nil_val": dyn.NilValue, - // Test interpolation of different types in string templates - "float_interp": dyn.V("Value: ${float_val}"), - "bool_true_interp": dyn.V("Enabled: ${bool_true}"), - "bool_false_interp": dyn.V("Disabled: ${bool_false}"), - "time_interp": dyn.V("Date: ${time_val}"), - "nil_interp": dyn.V("Null: ${nil_val}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - // Integer interpolation - assert.Equal(t, "1 2", getByPath(t, out, "c").MustString()) - - // Float interpolation - assert.Equal(t, "Value: 3.14", getByPath(t, out, "float_interp").MustString()) - - // Bool interpolation - assert.Equal(t, "Enabled: true", getByPath(t, out, "bool_true_interp").MustString()) - assert.Equal(t, "Disabled: false", getByPath(t, out, "bool_false_interp").MustString()) - - // Time interpolation should convert to string representation of time.Time - // Note: time.Time string representation includes timezone info - timeResult := getByPath(t, out, "time_interp").MustString() - assert.Contains(t, timeResult, "Date: 2024-01-01") - assert.Contains(t, timeResult, "00:00:00") - - // Nil interpolation - assert.Equal(t, "Null: ", getByPath(t, out, "nil_interp").MustString()) -} - -func TestResolveWithTypeRetentionFailure(t *testing.T) { - // Test that mapping interpolation fails with an error - mappingTest := dyn.V(map[string]dyn.Value{ - "mapping": dyn.V(map[string]dyn.Value{"key": dyn.V("value")}), - "interp": dyn.V("Config: ${mapping}"), - }) - _, err := dynvar.Resolve(mappingTest, dynvar.DefaultLookup(mappingTest)) - require.Error(t, err) - assert.Equal(t, "cannot interpolate non-primitive value of type map into string", err.Error()) -} - -func TestResolveWithTypeRetention(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "int": dyn.V(1), - "int_var": dyn.V("${int}"), - "bool_true": dyn.V(true), - "bool_true_var": dyn.V("${bool_true}"), - "bool_false": dyn.V(false), - "bool_false_var": dyn.V("${bool_false}"), - "float": dyn.V(1.0), - "float_var": dyn.V("${float}"), - "string": dyn.V("a"), - "string_var": dyn.V("${string}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - assert.EqualValues(t, 1, getByPath(t, out, "int").MustInt()) - assert.EqualValues(t, 1, getByPath(t, out, "int_var").MustInt()) - - assert.True(t, getByPath(t, out, "bool_true").MustBool()) - assert.True(t, getByPath(t, out, "bool_true_var").MustBool()) - - assert.False(t, getByPath(t, out, "bool_false").MustBool()) - assert.False(t, getByPath(t, out, "bool_false_var").MustBool()) - - assert.Zero(t, 1.0-getByPath(t, out, "float").MustFloat()) - assert.Zero(t, 1.0-getByPath(t, out, "float_var").MustFloat()) - - assert.Equal(t, "a", getByPath(t, out, "string").MustString()) - assert.Equal(t, "a", getByPath(t, out, "string_var").MustString()) -} - -func TestResolveWithSkip(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V("a"), - "b": dyn.V("b"), - "c": dyn.V("${a}"), - "d": dyn.V("${b}"), - "e": dyn.V("${a} ${b}"), - "f": dyn.V("${b} ${a} ${a} ${b}"), - }) - - fallback := dynvar.DefaultLookup(in) - ignore := func(path dyn.Path) (dyn.Value, error) { - // If the variable reference to look up starts with "b", skip it. - if path.HasPrefix(dyn.NewPath(dyn.Key("b"))) { - return dyn.InvalidValue, dynvar.ErrSkipResolution - } - return fallback(path) - } - - out, err := dynvar.Resolve(in, ignore) - require.NoError(t, err) - - assert.Equal(t, "a", getByPath(t, out, "a").MustString()) - assert.Equal(t, "b", getByPath(t, out, "b").MustString()) - assert.Equal(t, "a", getByPath(t, out, "c").MustString()) - - // Check that the skipped variable references are not interpolated. - assert.Equal(t, "${b}", getByPath(t, out, "d").MustString()) - assert.Equal(t, "a ${b}", getByPath(t, out, "e").MustString()) - assert.Equal(t, "${b} a a ${b}", getByPath(t, out, "f").MustString()) //nolint:dupword -} - -func TestResolveWithSkipEverything(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V("a"), - "b": dyn.V("b"), - "c": dyn.V("${a}"), - "d": dyn.V("${b}"), - "e": dyn.V("${a} ${b}"), - "f": dyn.V("${b} ${a} ${a} ${b}"), - "g": dyn.V("${d} ${c} ${c} ${d}"), - }) - - // The call must not replace anything if the lookup function returns ErrSkipResolution. - out, err := dynvar.Resolve(in, func(path dyn.Path) (dyn.Value, error) { - return dyn.InvalidValue, dynvar.ErrSkipResolution - }) - require.NoError(t, err) - assert.Equal(t, "a", getByPath(t, out, "a").MustString()) - assert.Equal(t, "b", getByPath(t, out, "b").MustString()) - assert.Equal(t, "${a}", getByPath(t, out, "c").MustString()) - assert.Equal(t, "${b}", getByPath(t, out, "d").MustString()) - assert.Equal(t, "${a} ${b}", getByPath(t, out, "e").MustString()) - assert.Equal(t, "${b} ${a} ${a} ${b}", getByPath(t, out, "f").MustString()) - assert.Equal(t, "${d} ${c} ${c} ${d}", getByPath(t, out, "g").MustString()) -} - -func TestResolveWithInterpolateNewRef(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V("a"), - "b": dyn.V("${a}"), - }) - - // The call replaces ${a} with ${foobar} and skips everything else. - out, err := dynvar.Resolve(in, func(path dyn.Path) (dyn.Value, error) { - if path.String() == "a" { - return dyn.V("${foobar}"), nil - } - return dyn.InvalidValue, dynvar.ErrSkipResolution - }) - - require.NoError(t, err) - assert.Equal(t, "a", getByPath(t, out, "a").MustString()) - assert.Equal(t, "${foobar}", getByPath(t, out, "b").MustString()) -} - -func TestResolveWithInterpolateAliasedRef(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "a": dyn.V("a"), - "b": dyn.V("${a}"), - "c": dyn.V("${x}"), - }) - - // The call replaces ${x} with ${b} and skips everything else. - out, err := dynvar.Resolve(in, func(path dyn.Path) (dyn.Value, error) { - if path.String() == "x" { - return dyn.V("${b}"), nil - } - return dyn.GetByPath(in, path) - }) - - require.NoError(t, err) - assert.Equal(t, "a", getByPath(t, out, "a").MustString()) - assert.Equal(t, "a", getByPath(t, out, "b").MustString()) - assert.Equal(t, "a", getByPath(t, out, "c").MustString()) -} - -func TestResolveIndexedRefs(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "slice": dyn.V([]dyn.Value{dyn.V("a"), dyn.V("b")}), - "a": dyn.V("a: ${slice[0]}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - assert.Equal(t, "a: a", getByPath(t, out, "a").MustString()) -} - -func TestResolveIndexedRefsFromMap(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "map": dyn.V( - map[string]dyn.Value{ - "slice": dyn.V([]dyn.Value{dyn.V("a")}), - }), - "a": dyn.V("a: ${map.slice[0]}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - assert.Equal(t, "a: a", getByPath(t, out, "a").MustString()) -} - -func TestResolveMapFieldFromIndexedRefs(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "map": dyn.V( - map[string]dyn.Value{ - "slice": dyn.V([]dyn.Value{ - dyn.V(map[string]dyn.Value{ - "value": dyn.V("a"), - }), - }), - }), - "a": dyn.V("a: ${map.slice[0].value}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - assert.Equal(t, "a: a", getByPath(t, out, "a").MustString()) -} - -func TestResolveNestedIndexedRefs(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "slice": dyn.V([]dyn.Value{ - dyn.V([]dyn.Value{dyn.V("a")}), - }), - "a": dyn.V("a: ${slice[0][0]}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - assert.Equal(t, "a: a", getByPath(t, out, "a").MustString()) -} - -func TestResolveMapVariable(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "map": dyn.V(map[string]dyn.Value{ - "key1": dyn.V("value1"), - "key2": dyn.V("value2"), - }), - "var": dyn.V("${map}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - // Verify that the map variable was interpolated correctly - mapVal := getByPath(t, out, "var") - _, ok := mapVal.AsMap() - require.True(t, ok, "expected map value") - - // Verify the map contents - assert.Equal(t, "value1", getByPath(t, mapVal, "key1").MustString()) - assert.Equal(t, "value2", getByPath(t, mapVal, "key2").MustString()) -} - -func TestResolveSequenceVariable(t *testing.T) { - in := dyn.V(map[string]dyn.Value{ - "seq": dyn.V([]dyn.Value{ - dyn.V("value1"), - dyn.V("value2"), - }), - "var": dyn.V("${seq}"), - }) - - out, err := dynvar.Resolve(in, dynvar.DefaultLookup(in)) - require.NoError(t, err) - - // Verify that the sequence variable was interpolated correctly - seqVal := getByPath(t, out, "var") - seq, ok := seqVal.AsSequence() - require.True(t, ok, "expected sequence value") - require.Len(t, seq, 2) - - // Verify the sequence contents - assert.Equal(t, "value1", seq[0].MustString()) - assert.Equal(t, "value2", seq[1].MustString()) -} diff --git a/libs/dyn/jsonloader/json.go b/libs/dyn/jsonloader/json.go deleted file mode 100644 index ffb715e9c9c..00000000000 --- a/libs/dyn/jsonloader/json.go +++ /dev/null @@ -1,134 +0,0 @@ -package jsonloader - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" -) - -func LoadJSON(data []byte, source string) (dyn.Value, error) { - offset := BuildLineOffsets(data) - offset.SetSource(source) - - reader := bytes.NewReader(data) - decoder := json.NewDecoder(reader) - - // Use json.Number to avoid losing precision on int64 values above 2^53 (e.g. job and run IDs). - decoder.UseNumber() - - // Start decoding from the top-level value - value, err := decodeValue(decoder, &offset) - if err != nil { - if errors.Is(err, io.EOF) { - err = errors.New("unexpected end of JSON input") - } - // Get the current offset for error reporting - errorOffset := decoder.InputOffset() - errorLocation := offset.GetPosition(errorOffset) - return dyn.InvalidValue, fmt.Errorf("error decoding JSON at %s: %w", errorLocation, err) - } - - // Check if there are any remaining tokens (should not be valid JSON) - if decoder.More() { - errorOffset := decoder.InputOffset() - errorLocation := offset.GetPosition(errorOffset) - return dyn.InvalidValue, fmt.Errorf("error decoding JSON at %s: unexpected additional content", errorLocation) - } - - return value, nil -} - -func decodeValue(decoder *json.Decoder, o *Offset) (dyn.Value, error) { - // Read the next JSON token - token, err := decoder.Token() - if err != nil { - return dyn.InvalidValue, err - } - - // Get the current byte offset and the location. - // We will later use this location to store the location of the value in the file - // For objects and arrays, we will store the location of the opening '{' or '[' - // For primitive types, we will store the location of the value itself (end of the value) - // We can't reliably calculate the beginning of the value for primitive types because - // the decoder doesn't provide the offset of the beginning of the value and the value might or might not be quoted. - offset := decoder.InputOffset() - location := o.GetPosition(offset) - - switch tok := token.(type) { - case json.Delim: - switch tok { - case '{': - location = o.GetPosition(offset - 1) - // Decode JSON object - obj := dyn.NewMapping() - for decoder.More() { - // Decode the key - keyToken, err := decoder.Token() - if err != nil { - return invalidValueWithLocation(decoder, o), err - } - key, ok := keyToken.(string) - if !ok { - return invalidValueWithLocation(decoder, o), errors.New("expected string for object key") - } - - // Get the offset of the key by subtracting the length of the key and the '"' character - keyOffset := decoder.InputOffset() - int64(len(key)+1) - loc := []diag.Location{o.GetPosition(keyOffset)} - - // Decode the value recursively - val, err := decodeValue(decoder, o) - if err != nil { - return invalidValueWithLocation(decoder, o), err - } - - obj.SetLoc(key, loc, val) - } - // Consume the closing '}' - if _, err := decoder.Token(); err != nil { - return invalidValueWithLocation(decoder, o), err - } - return dyn.NewValue(obj, []diag.Location{location}), nil - case '[': - location = o.GetPosition(offset - 1) - // Decode JSON array - var arr []dyn.Value - for decoder.More() { - val, err := decodeValue(decoder, o) - if err != nil { - return invalidValueWithLocation(decoder, o), err - } - arr = append(arr, val) - } - // Consume the closing ']' - if _, err := decoder.Token(); err != nil { - return invalidValueWithLocation(decoder, o), err - } - return dyn.NewValue(arr, []diag.Location{location}), nil - } - case json.Number: - // Integers that overflow int64 fall back to float64, matching the decoder's behavior without UseNumber. - if i64, err := tok.Int64(); err == nil { - return dyn.NewValue(i64, []diag.Location{location}), nil - } - f64, err := tok.Float64() - if err != nil { - return invalidValueWithLocation(decoder, o), fmt.Errorf("invalid number %q: %w", tok.String(), err) - } - return dyn.NewValue(f64, []diag.Location{location}), nil - default: - return dyn.NewValue(tok, []diag.Location{location}), nil - } - - return invalidValueWithLocation(decoder, o), fmt.Errorf("unexpected token: %v", token) -} - -func invalidValueWithLocation(decoder *json.Decoder, o *Offset) dyn.Value { - location := o.GetPosition(decoder.InputOffset()) - return dyn.InvalidValue.WithLocations([]diag.Location{location}) -} diff --git a/libs/dyn/jsonloader/json_test.go b/libs/dyn/jsonloader/json_test.go deleted file mode 100644 index e274de559ce..00000000000 --- a/libs/dyn/jsonloader/json_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package jsonloader - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/databricks-sdk-go/service/jobs" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const jsonData = ` -{ - "job_id": 123, - "new_settings": { - "name": "xxx", - "email_notifications": { - "on_start": [], - "on_success": [], - "on_failure": [] - }, - "webhook_notifications": { - "on_start": [], - "on_failure": [] - }, - "notification_settings": { - "no_alert_for_skipped_runs": true, - "no_alert_for_canceled_runs": true - }, - "timeout_seconds": 0, - "max_concurrent_runs": 1, - "tasks": [ - { - "task_key": "xxx", - "email_notifications": {}, - "notification_settings": {}, - "timeout_seconds": 0, - "max_retries": 0, - "min_retry_interval_millis": 0, - "retry_on_timeout": "true" - } - ] - } -} -` - -func TestJsonLoader(t *testing.T) { - v, err := LoadJSON([]byte(jsonData), "(inline)") - assert.NoError(t, err) - - var r jobs.ResetJob - err = convert.ToTyped(&r, v) - assert.NoError(t, err) -} - -const malformedMap = ` -{ - "job_id": 123, - "new_settings": { - "name": "xxx", - "wrong", - } -} -` - -func TestJsonLoaderMalformedMap(t *testing.T) { - _, err := LoadJSON([]byte(malformedMap), "(inline)") - assert.ErrorContains(t, err, "error decoding JSON at (inline):6:16: invalid character ',' after object key") -} - -const malformedArray = ` -{ - "job_id": 123, - "new_settings": { - "name": "xxx", - "tasks": [1, "asd",] - } -}` - -func TestJsonLoaderMalformedArray(t *testing.T) { - _, err := LoadJSON([]byte(malformedArray), "path/to/file.json") - assert.ErrorContains(t, err, "error decoding JSON at path/to/file.json:6:27: invalid character ',' looking for beginning of value") -} - -const eofData = ` -{ - "job_id": 123, - "new_settings": { - "name": "xxx",` - -func TestJsonLoaderEOF(t *testing.T) { - _, err := LoadJSON([]byte(eofData), "path/to/file.json") - assert.ErrorContains(t, err, "unexpected end of JSON input") -} - -const mapWithNoBraces = ` -"job_id": 123, -"new_settings": { - "name": "xxx", - "wrong": "xxx", -} -` - -func TestJsonMapWithoutBraces(t *testing.T) { - _, err := LoadJSON([]byte(mapWithNoBraces), "path/to/file.json") - assert.ErrorContains(t, err, "error decoding JSON at") -} - -const validInline = `["job_id", 123]` - -func TestJsonValidInline(t *testing.T) { - _, err := LoadJSON([]byte(validInline), "path/to/file.json") - assert.NoError(t, err) -} - -func TestJsonLoaderNumbers(t *testing.T) { - for _, tc := range []struct { - input string - expected any - }{ - {`123`, int64(123)}, - {`-1`, int64(-1)}, - {`123456789012345678`, int64(123456789012345678)}, - {`-123456789012345678`, int64(-123456789012345678)}, - {`9223372036854775807`, int64(9223372036854775807)}, - {`2.0`, 2.0}, - {`2.5`, 2.5}, - {`1e3`, 1000.0}, - {`18446744073709551615`, 1.8446744073709552e+19}, - } { - v, err := LoadJSON([]byte(tc.input), "(inline)") - assert.NoError(t, err, tc.input) - assert.Equal(t, tc.expected, v.AsAny(), tc.input) - } -} - -func TestJsonLoaderNumberOutOfRange(t *testing.T) { - _, err := LoadJSON([]byte(`1e400`), "(inline)") - assert.ErrorContains(t, err, "value out of range") -} - -const mixedNumbersData = ` -{ - "job_id": 123456789012345678, - "new_settings": { - "name": "xxx", - "timeout_seconds": 100 - } -} -` - -func TestJsonLoaderMixedNumbersToTyped(t *testing.T) { - v, err := LoadJSON([]byte(mixedNumbersData), "(inline)") - require.NoError(t, err) - - var r jobs.ResetJob - err = convert.ToTyped(&r, v) - require.NoError(t, err) - assert.Equal(t, int64(123456789012345678), r.JobId) - assert.Equal(t, 100, r.NewSettings.TimeoutSeconds) -} diff --git a/libs/dyn/jsonloader/locations.go b/libs/dyn/jsonloader/locations.go deleted file mode 100644 index 3dbe2da6630..00000000000 --- a/libs/dyn/jsonloader/locations.go +++ /dev/null @@ -1,49 +0,0 @@ -package jsonloader - -import ( - "sort" - - "github.com/databricks/cli/libs/diag" -) - -type LineOffset struct { - Line int - Start int64 -} - -type Offset struct { //nolint:recvcheck // value receiver for read-only GetPosition, pointer for SetSource - offsets []LineOffset - source string -} - -// buildLineOffsets scans the input data and records the starting byte offset of each line. -func BuildLineOffsets(data []byte) Offset { - offsets := []LineOffset{{Line: 1, Start: 0}} - line := 1 - for i, b := range data { - if b == '\n' { - line++ - offsets = append(offsets, LineOffset{Line: line, Start: int64(i + 1)}) - } - } - return Offset{offsets: offsets} -} - -// GetPosition maps a byte offset to its corresponding line and column numbers. -func (o Offset) GetPosition(offset int64) diag.Location { - // Binary search to find the line - idx := max(sort.Search(len(o.offsets), func(i int) bool { - return o.offsets[i].Start > offset - })-1, 0) - - lineOffset := o.offsets[idx] - return diag.Location{ - File: o.source, - Line: lineOffset.Line, - Column: int(offset-lineOffset.Start) + 1, - } -} - -func (o *Offset) SetSource(source string) { - o.source = source -} diff --git a/libs/dyn/kind_test.go b/libs/dyn/kind_test.go deleted file mode 100644 index 84c90713fb8..00000000000 --- a/libs/dyn/kind_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package dyn_test - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestKindZeroValue(t *testing.T) { - // Assert that the zero value of [dyn.Kind] is the invalid kind. - var k dyn.Kind - assert.Equal(t, dyn.KindInvalid, k) -} - -func TestKindToString(t *testing.T) { - for _, tt := range []struct { - k dyn.Kind - s string - }{ - {dyn.KindInvalid, "invalid"}, - {dyn.KindMap, "map"}, - {dyn.KindSequence, "sequence"}, - {dyn.KindString, "string"}, - {dyn.KindBool, "bool"}, - {dyn.KindInt, "int"}, - {dyn.KindFloat, "float"}, - {dyn.KindTime, "time"}, - {dyn.KindNil, "nil"}, - } { - assert.Equal(t, tt.s, tt.k.String()) - } - - // Panic on unknown kind. - assert.PanicsWithValue(t, "invalid kind value: 100", func() { - _ = dyn.Kind(100).String() - }) -} diff --git a/libs/dyn/mapping.go b/libs/dyn/mapping.go deleted file mode 100644 index 8b29181e40f..00000000000 --- a/libs/dyn/mapping.go +++ /dev/null @@ -1,158 +0,0 @@ -package dyn - -import ( - "maps" - "slices" - - "github.com/databricks/cli/libs/diag" -) - -// Pair represents a single key-value pair in a Mapping. -type Pair struct { - Key Value - Value Value -} - -// Mapping represents a key-value map of dynamic values. -// It exists because plain Go maps cannot use dynamic values for keys. -// We need to use dynamic values for keys because it lets us associate metadata -// with keys (i.e. their definition location). Keys must be strings. -type Mapping struct { //nolint:recvcheck // value receivers for read-only accessors, pointer for mutators - pairs []Pair - index map[string]int -} - -// NewMapping creates a new empty Mapping. -func NewMapping() Mapping { - return Mapping{ - pairs: make([]Pair, 0), - index: make(map[string]int), - } -} - -// NewMappingFromPairs computes a [Mapping] from a list of [Pair]s. The index -// map does not need to be provided since that will be computed from the -// key-value pairs provided. -func NewMappingFromPairs(pairs []Pair) Mapping { - index := make(map[string]int) - for i, p := range pairs { - index[p.Key.MustString()] = i - } - - return Mapping{ - pairs: pairs, - index: index, - } -} - -// newMappingWithSize creates a new Mapping preallocated to the specified size. -func newMappingWithSize(size int) Mapping { - return Mapping{ - pairs: make([]Pair, 0, size), - index: make(map[string]int, size), - } -} - -// newMappingFromGoMap creates a new Mapping from a Go map of string keys and dynamic values. -func newMappingFromGoMap(vin map[string]Value) Mapping { - m := newMappingWithSize(len(vin)) - for k, v := range vin { - m.SetLoc(k, nil, v) - } - return m -} - -// Pairs returns all the key-value pairs in the Mapping. The pairs are sorted by -// their key in lexicographic order. -func (m Mapping) Pairs() []Pair { - return m.pairs -} - -// Len returns the number of key-value pairs in the Mapping. -func (m Mapping) Len() int { - return len(m.pairs) -} - -// GetPair returns the key-value pair with the specified key. -// It also returns a boolean indicating whether the pair was found. -func (m Mapping) GetPair(key Value) (Pair, bool) { - skey, ok := key.AsString() - if !ok { - return Pair{}, false - } - return m.GetPairByString(skey) -} - -// GetPairByString returns the key-value pair with the specified string key. -// It also returns a boolean indicating whether the pair was found. -func (m Mapping) GetPairByString(skey string) (Pair, bool) { - if i, ok := m.index[skey]; ok { - return m.pairs[i], true - } - return Pair{}, false -} - -// Get returns the value associated with the specified key. -// It also returns a boolean indicating whether the value was found. -func (m Mapping) Get(key Value) (Value, bool) { - p, ok := m.GetPair(key) - return p.Value, ok -} - -// GetByString returns the value associated with the specified string key. -// It also returns a boolean indicating whether the value was found. -func (m *Mapping) GetByString(skey string) (Value, bool) { - p, ok := m.GetPairByString(skey) - return p.Value, ok -} - -// Set sets the value for the given key in the mapping. -// If the key already exists, the value is updated. The location loc is ignored. -// If the key does not exist, a new key-value pair is added. -func (m *Mapping) SetLoc(skey string, loc []diag.Location, value Value) { - // If the key already exists, update the value. - if i, ok := m.index[skey]; ok { - m.pairs[i].Value = value - return - } - - // Otherwise, add a new pair. - m.pairs = append(m.pairs, Pair{NewValue(skey, loc), value}) - if m.index == nil { - m.index = make(map[string]int) - } - m.index[skey] = len(m.pairs) - 1 -} - -// Keys returns all the keys in the Mapping. -func (m Mapping) Keys() []Value { - keys := make([]Value, 0, len(m.pairs)) - for _, p := range m.pairs { - keys = append(keys, p.Key) - } - return keys -} - -// Values returns all the values in the Mapping. -func (m Mapping) Values() []Value { - values := make([]Value, 0, len(m.pairs)) - for _, p := range m.pairs { - values = append(values, p.Value) - } - return values -} - -// Clone creates a shallow copy of the Mapping. -func (m Mapping) Clone() Mapping { - return Mapping{ - pairs: slices.Clone(m.pairs), - index: maps.Clone(m.index), - } -} - -// Merge merges the key-value pairs from another Mapping into the current Mapping. -func (m *Mapping) Merge(n Mapping) { - for _, p := range n.pairs { - m.SetLoc(p.Key.MustString(), p.Key.Locations(), p.Value) - } -} diff --git a/libs/dyn/mapping_test.go b/libs/dyn/mapping_test.go deleted file mode 100644 index f646ab7c1d1..00000000000 --- a/libs/dyn/mapping_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package dyn_test - -import ( - "strconv" - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestNewMapping(t *testing.T) { - m := dyn.NewMapping() - assert.Equal(t, 0, m.Len()) -} - -func TestMappingZeroValue(t *testing.T) { - var m dyn.Mapping - assert.Equal(t, 0, m.Len()) - - value, ok := m.Get(dyn.V("key")) - assert.Equal(t, dyn.InvalidValue, value) - assert.False(t, ok) - assert.Empty(t, m.Keys()) - assert.Empty(t, m.Values()) -} - -func TestMappingGet(t *testing.T) { - var m dyn.Mapping - m.SetLoc("key", nil, dyn.V("value")) - assert.Equal(t, 1, m.Len()) - - // Call GetPair - p, ok := m.GetPair(dyn.V("key")) - assert.True(t, ok) - assert.Equal(t, dyn.V("key"), p.Key) - assert.Equal(t, dyn.V("value"), p.Value) - - // Modify the value to make sure we're not getting a reference - p.Value = dyn.V("newvalue") - - // Call GetPair with invalid key - p, ok = m.GetPair(dyn.V(1234)) - assert.False(t, ok) - assert.Equal(t, dyn.InvalidValue, p.Key) - assert.Equal(t, dyn.InvalidValue, p.Value) - - // Call GetPair with non-existent key - p, ok = m.GetPair(dyn.V("enoexist")) - assert.False(t, ok) - assert.Equal(t, dyn.InvalidValue, p.Key) - assert.Equal(t, dyn.InvalidValue, p.Value) - - // Call GetPairByString - p, ok = m.GetPairByString("key") - assert.True(t, ok) - assert.Equal(t, dyn.V("key"), p.Key) - assert.Equal(t, dyn.V("value"), p.Value) - - // Modify the value to make sure we're not getting a reference - p.Value = dyn.V("newvalue") - - // Call GetPairByString with non-existent key - p, ok = m.GetPairByString("enoexist") - assert.False(t, ok) - assert.Equal(t, dyn.InvalidValue, p.Key) - assert.Equal(t, dyn.InvalidValue, p.Value) - - // Call Get - value, ok := m.Get(dyn.V("key")) - assert.True(t, ok) - assert.Equal(t, dyn.V("value"), value) - - // Call Get with invalid key - value, ok = m.Get(dyn.V(1234)) - assert.False(t, ok) - assert.Equal(t, dyn.InvalidValue, value) - - // Call Get with non-existent key - value, ok = m.Get(dyn.V("enoexist")) - assert.False(t, ok) - assert.Equal(t, dyn.InvalidValue, value) - - // Call GetByString - value, ok = m.GetByString("key") - assert.True(t, ok) - assert.Equal(t, dyn.V("value"), value) - - // Call GetByString with non-existent key - value, ok = m.GetByString("enoexist") - assert.False(t, ok) - assert.Equal(t, dyn.InvalidValue, value) -} - -func TestMappingSetLoc(t *testing.T) { - var m dyn.Mapping - - // Set a value - m.SetLoc("key1", nil, dyn.V("foo")) - assert.Equal(t, 1, m.Len()) - - // Confirm the value - value, ok := m.GetByString("key1") - assert.True(t, ok) - assert.Equal(t, dyn.V("foo"), value) - - // Set another value - m.SetLoc("key2", nil, dyn.V("bar")) - assert.Equal(t, 2, m.Len()) - - // Confirm the value - value, ok = m.Get(dyn.V("key2")) - assert.True(t, ok) - assert.Equal(t, dyn.V("bar"), value) - - // Overwrite first value - m.SetLoc("key1", nil, dyn.V("qux")) - assert.Equal(t, 2, m.Len()) - - // Confirm the value - value, ok = m.Get(dyn.V("key1")) - assert.True(t, ok) - assert.Equal(t, dyn.V("qux"), value) -} - -func TestMappingKeysValues(t *testing.T) { - // Configure mapping - var m dyn.Mapping - m.SetLoc("key1", nil, dyn.V("foo")) - m.SetLoc("key2", nil, dyn.V("bar")) - - // Confirm keys - keys := m.Keys() - assert.Len(t, keys, 2) - assert.Contains(t, keys, dyn.V("key1")) - assert.Contains(t, keys, dyn.V("key2")) - - // Confirm values - values := m.Values() - assert.Len(t, values, 2) - assert.Contains(t, values, dyn.V("foo")) - assert.Contains(t, values, dyn.V("bar")) -} - -func TestMappingClone(t *testing.T) { - // Configure mapping - var m1 dyn.Mapping - m1.SetLoc("key1", nil, dyn.V("foo")) - m1.SetLoc("key2", nil, dyn.V("bar")) - - // Clone mapping - m2 := m1.Clone() - assert.Equal(t, m1.Len(), m2.Len()) - - // Modify original mapping - m1.SetLoc("key1", nil, dyn.V("qux")) - - // Confirm values - value, ok := m1.Get(dyn.V("key1")) - assert.True(t, ok) - assert.Equal(t, dyn.V("qux"), value) - value, ok = m2.Get(dyn.V("key1")) - assert.True(t, ok) - assert.Equal(t, dyn.V("foo"), value) -} - -func TestMappingMerge(t *testing.T) { - var m1 dyn.Mapping - for i := range 10 { - m1.SetLoc(strconv.Itoa(i), nil, dyn.V(i)) - } - - var m2 dyn.Mapping - for i := 5; i < 15; i++ { - m2.SetLoc(strconv.Itoa(i), nil, dyn.V(i)) - } - - var out dyn.Mapping - out.Merge(m1) - assert.Equal(t, 10, out.Len()) - out.Merge(m2) - assert.Equal(t, 15, out.Len()) -} diff --git a/libs/dyn/merge/elements_by_key.go b/libs/dyn/merge/elements_by_key.go deleted file mode 100644 index 6bf71d41ae5..00000000000 --- a/libs/dyn/merge/elements_by_key.go +++ /dev/null @@ -1,107 +0,0 @@ -package merge - -import ( - "slices" - - "github.com/databricks/cli/libs/dyn" -) - -type elementsByKey struct { - key string - keyFunc func(dyn.Value) string - sortKeys bool -} - -func (e elementsByKey) doMap(_ dyn.Path, v dyn.Value, mergeFunc func(a, b dyn.Value) (dyn.Value, error)) (dyn.Value, error) { - // We know the type of this value is a sequence. - // For additional defence, return self if it is not. - elements, ok := v.AsSequence() - if !ok { - return v, nil - } - - seen := make(map[string]dyn.Value, len(elements)) - keys := make([]string, 0, len(elements)) - - // Iterate in natural order. For a given key, we first see the - // base definition and merge instances that come after it. - for i := range elements { - kv := elements[i].Get(e.key) - key := e.keyFunc(kv) - - // Register element with key if not yet seen before. - ref, ok := seen[key] - if !ok { - keys = append(keys, key) - seen[key] = elements[i] - continue - } - - // Merge this instance into the reference. - nv, err := mergeFunc(ref, elements[i]) - if err != nil { - return v, err - } - - // Overwrite reference. - seen[key] = nv - } - - if e.sortKeys { - slices.Sort(keys) - } - - // Gather resulting elements in natural order. - out := make([]dyn.Value, 0, len(keys)) - for _, key := range keys { - // Preserve the location from the original key value so that - // downstream code can tell this field was defined in the YAML. - keyLoc := seen[key].Get(e.key).Locations() - nv, err := dyn.Set(seen[key], e.key, dyn.NewValue(key, keyLoc)) - if err != nil { - return dyn.InvalidValue, err - } - out = append(out, nv) - } - - return dyn.NewValue(out, v.Locations()), nil -} - -func (e elementsByKey) Map(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return e.doMap(nil, v, Merge) -} - -func (e elementsByKey) MapWithOverride(p dyn.Path, v dyn.Value) (dyn.Value, error) { - return e.doMap(nil, v, func(a, b dyn.Value) (dyn.Value, error) { - return Override(a, b, OverrideVisitor{ - VisitInsert: func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return v, nil - }, - VisitDelete: func(valuePath dyn.Path, left dyn.Value) error { - return nil - }, - VisitUpdate: func(_ dyn.Path, a, b dyn.Value) (dyn.Value, error) { - return b, nil - }, - }) - }) -} - -// ElementsByKey returns a [dyn.MapFunc] that operates on a sequence -// where each element is a map. It groups elements by a key and merges -// elements with the same key. -// -// The function that extracts the key from an element is provided as -// a parameter. The resulting elements get their key field overwritten -// with the value as returned by the key function. -func ElementsByKey(key string, keyFunc func(dyn.Value) string) dyn.MapFunc { - return elementsByKey{key, keyFunc, false}.Map -} - -func ElementsBySortedKey(key string, keyFunc func(dyn.Value) string) dyn.MapFunc { - return elementsByKey{key, keyFunc, true}.Map -} - -func ElementsByKeyWithOverride(key string, keyFunc func(dyn.Value) string) dyn.MapFunc { - return elementsByKey{key, keyFunc, false}.MapWithOverride -} diff --git a/libs/dyn/merge/elements_by_key_test.go b/libs/dyn/merge/elements_by_key_test.go deleted file mode 100644 index 2aa19306820..00000000000 --- a/libs/dyn/merge/elements_by_key_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package merge - -import ( - "strings" - "testing" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestElementByKey(t *testing.T) { - vin := dyn.V([]dyn.Value{ - dyn.V(map[string]dyn.Value{ - "key": dyn.V("foo"), - "value": dyn.V(42), - }), - dyn.V(map[string]dyn.Value{ - "key": dyn.V("bar"), - "value": dyn.V(43), - }), - dyn.V(map[string]dyn.Value{ - // Use upper case key to test that the resulting element has its - // key field assigned to the output of the key function. - // The key function in this test returns the lower case version of the key. - "key": dyn.V("FOO"), - "value": dyn.V(44), - }), - }) - - keyFunc := func(v dyn.Value) string { - return strings.ToLower(v.MustString()) - } - - vout, err := dyn.MapByPath(vin, dyn.EmptyPath, ElementsByKey("key", keyFunc)) - require.NoError(t, err) - assert.Len(t, vout.MustSequence(), 2) - assert.Equal(t, - map[string]any{ - "key": "foo", - "value": 44, - }, vout.Index(0).AsAny(), - ) - assert.Equal(t, - map[string]any{ - "key": "bar", - "value": 43, - }, vout.Index(1).AsAny(), - ) -} - -func TestElementByKeyPreservesLocations(t *testing.T) { - loc := diag.Location{File: "config.yml", Line: 10, Column: 5} - vin := dyn.V([]dyn.Value{ - dyn.V(map[string]dyn.Value{ - "key": dyn.NewValue("foo", []diag.Location{loc}), - "value": dyn.V(42), - }), - }) - - keyFunc := func(v dyn.Value) string { - return v.MustString() - } - - vout, err := dyn.MapByPath(vin, dyn.EmptyPath, ElementsByKey("key", keyFunc)) - require.NoError(t, err) - - // Verify the key field retains its original location. - keyValue := vout.Index(0).Get("key") - assert.Equal(t, loc, keyValue.Location()) -} - -func TestElementByKeyWithOverride(t *testing.T) { - vin := dyn.V([]dyn.Value{ - dyn.V(map[string]dyn.Value{ - "key": dyn.V("foo"), - "value": dyn.V(42), - }), - dyn.V(map[string]dyn.Value{ - "key": dyn.V("bar"), - "value": dyn.V(43), - }), - dyn.V(map[string]dyn.Value{ - "key": dyn.V("foo"), - "othervalue": dyn.V(44), - }), - }) - - keyFunc := func(v dyn.Value) string { - return strings.ToLower(v.MustString()) - } - - vout, err := dyn.MapByPath(vin, dyn.EmptyPath, ElementsByKeyWithOverride("key", keyFunc)) - require.NoError(t, err) - assert.Len(t, vout.MustSequence(), 2) - assert.Equal(t, - map[string]any{ - "key": "foo", - "othervalue": 44, - }, vout.Index(0).AsAny(), - ) - assert.Equal(t, - map[string]any{ - "key": "bar", - "value": 43, - }, vout.Index(1).AsAny(), - ) -} diff --git a/libs/dyn/merge/merge.go b/libs/dyn/merge/merge.go deleted file mode 100644 index 374450ef240..00000000000 --- a/libs/dyn/merge/merge.go +++ /dev/null @@ -1,119 +0,0 @@ -package merge - -import ( - "fmt" - - "github.com/databricks/cli/libs/dyn" -) - -// Merge recursively merges the specified values. -// -// Semantics are as follows: -// * Merging x with nil or nil with x always yields x. -// * Merging maps a and b means entries from map b take precedence. -// * Merging sequences a and b means concatenating them. -// -// Merging retains and accumulates the locations metadata associated with the values. -// This allows users of the module to track the provenance of values across merging of -// configuration trees, which is useful for reporting errors and warnings. -// -// Semantics for location metadata in the merged value are similar to the semantics -// for the values themselves: -// -// - When merging x with nil or nil with x, the location of x is retained. -// -// - When merging maps or sequences, the combined value retains the location of a and -// accumulates the location of b. The individual elements of the map or sequence retain -// their original locations, i.e., whether they were originally defined in a or b. -// -// The rationale for retaining location of a is that we would like to return -// the first location a bit of configuration showed up when reporting errors and warnings. -// -// - Merging primitive values means using the incoming value `b`. The location of the -// incoming value is retained and the location of the existing value `a` is accumulated. -// This is because the incoming value overwrites the existing value. -func Merge(a, b dyn.Value) (dyn.Value, error) { - return merge(a, b) -} - -func merge(a, b dyn.Value) (dyn.Value, error) { - ak := a.Kind() - bk := b.Kind() - - // If a is nil, return b. - if ak == dyn.KindNil { - return b.AppendLocationsFromValue(a), nil - } - - // If b is nil, return a. - if bk == dyn.KindNil { - return a.AppendLocationsFromValue(b), nil - } - - // Call the appropriate merge function based on the kind of a and b. - switch ak { - case dyn.KindMap: - if bk != dyn.KindMap { - return dyn.InvalidValue, fmt.Errorf("cannot merge map with %s", bk) - } - return mergeMap(a, b) - case dyn.KindSequence: - if bk != dyn.KindSequence { - return dyn.InvalidValue, fmt.Errorf("cannot merge sequence with %s", bk) - } - return mergeSequence(a, b) - default: - if ak != bk { - return dyn.InvalidValue, fmt.Errorf("cannot merge %s with %s", ak, bk) - } - return mergePrimitive(a, b) - } -} - -func mergeMap(a, b dyn.Value) (dyn.Value, error) { - out := dyn.NewMapping() - am := a.MustMap() - bm := b.MustMap() - - // Add the values from a into the output map. - out.Merge(am) - - // Merge the values from b into the output map. - for _, pair := range bm.Pairs() { - pk := pair.Key - key := pk.MustString() - pv := pair.Value - if ov, ok := out.Get(pk); ok { - // If the key already exists, merge the values. - merged, err := merge(ov, pv) - if err != nil { - return dyn.InvalidValue, err - } - out.SetLoc(key, pair.Key.Locations(), merged) - } else { - // Otherwise, just set the value. - out.SetLoc(key, pair.Key.Locations(), pv) - } - } - - // Preserve the location of the first value. Accumulate the locations of the second value. - return dyn.NewValue(out, a.Locations()).AppendLocationsFromValue(b), nil -} - -func mergeSequence(a, b dyn.Value) (dyn.Value, error) { - as := a.MustSequence() - bs := b.MustSequence() - - // Merging sequences means concatenating them. - out := make([]dyn.Value, len(as)+len(bs)) - copy(out[:], as) - copy(out[len(as):], bs) - - // Preserve the location of the first value. Accumulate the locations of the second value. - return dyn.NewValue(out, a.Locations()).AppendLocationsFromValue(b), nil -} - -func mergePrimitive(a, b dyn.Value) (dyn.Value, error) { - // Merging primitive values means using the incoming value. - return b.AppendLocationsFromValue(a), nil -} diff --git a/libs/dyn/merge/merge_test.go b/libs/dyn/merge/merge_test.go deleted file mode 100644 index 9545c752258..00000000000 --- a/libs/dyn/merge/merge_test.go +++ /dev/null @@ -1,301 +0,0 @@ -package merge - -import ( - "testing" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestMergeMaps(t *testing.T) { - l1 := diag.Location{File: "file1", Line: 1, Column: 2} - v1 := dyn.NewValue(map[string]dyn.Value{ - "foo": dyn.NewValue("bar", []diag.Location{l1}), - "bar": dyn.NewValue("baz", []diag.Location{l1}), - }, []diag.Location{l1}) - - l2 := diag.Location{File: "file2", Line: 3, Column: 4} - v2 := dyn.NewValue(map[string]dyn.Value{ - "bar": dyn.NewValue("qux", []diag.Location{l2}), - "qux": dyn.NewValue("foo", []diag.Location{l2}), - }, []diag.Location{l2}) - - // Merge v2 into v1. - { - out, err := Merge(v1, v2) - assert.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": "bar", - "bar": "qux", - "qux": "foo", - }, out.AsAny()) - - // Locations of both values should be preserved. - assert.Equal(t, []diag.Location{l1, l2}, out.Locations()) - assert.Equal(t, []diag.Location{l2, l1}, out.Get("bar").Locations()) - assert.Equal(t, []diag.Location{l1}, out.Get("foo").Locations()) - assert.Equal(t, []diag.Location{l2}, out.Get("qux").Locations()) - - // Location of the merged value should be the location of v1. - assert.Equal(t, l1, out.Location()) - - // Value of bar is "qux" which comes from v2. This .Location() should - // return the location of v2. - assert.Equal(t, l2, out.Get("bar").Location()) - - // Original locations of keys that were not overwritten should be preserved. - assert.Equal(t, l1, out.Get("foo").Location()) - assert.Equal(t, l2, out.Get("qux").Location()) - } - - // Merge v1 into v2. - { - out, err := Merge(v2, v1) - assert.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": "bar", - "bar": "baz", - "qux": "foo", - }, out.AsAny()) - - // Locations of both values should be preserved. - assert.Equal(t, []diag.Location{l2, l1}, out.Locations()) - assert.Equal(t, []diag.Location{l1, l2}, out.Get("bar").Locations()) - assert.Equal(t, []diag.Location{l1}, out.Get("foo").Locations()) - assert.Equal(t, []diag.Location{l2}, out.Get("qux").Locations()) - - // Location of the merged value should be the location of v2. - assert.Equal(t, l2, out.Location()) - - // Value of bar is "baz" which comes from v1. This .Location() should - // return the location of v1. - assert.Equal(t, l1, out.Get("bar").Location()) - - // Original locations of keys that were not overwritten should be preserved. - assert.Equal(t, l1, out.Get("foo").Location()) - assert.Equal(t, l2, out.Get("qux").Location()) - } -} - -func TestMergeMapsNil(t *testing.T) { - l := diag.Location{File: "file", Line: 1, Column: 2} - v := dyn.NewValue(map[string]dyn.Value{ - "foo": dyn.V("bar"), - }, []diag.Location{l}) - - nilL := diag.Location{File: "file", Line: 3, Column: 4} - nilV := dyn.NewValue(nil, []diag.Location{nilL}) - - // Merge nil into v. - { - out, err := Merge(v, nilV) - assert.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": "bar", - }, out.AsAny()) - - // Locations of both values should be preserved. - assert.Equal(t, []diag.Location{l, nilL}, out.Locations()) - - // Location of the non-nil value should be returned by .Location(). - assert.Equal(t, l, out.Location()) - } - - // Merge v into nil. - { - out, err := Merge(nilV, v) - assert.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": "bar", - }, out.AsAny()) - - // Locations of both values should be preserved. - assert.Equal(t, []diag.Location{l, nilL}, out.Locations()) - - // Location of the non-nil value should be returned by .Location(). - assert.Equal(t, l, out.Location()) - } -} - -func TestMergeMapsError(t *testing.T) { - v := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - }) - - other := dyn.V("string") - - // Merge a string into v. - { - out, err := Merge(v, other) - assert.EqualError(t, err, "cannot merge map with string") - assert.Equal(t, dyn.InvalidValue, out) - } -} - -func TestMergeSequences(t *testing.T) { - l1 := diag.Location{File: "file1", Line: 1, Column: 2} - v1 := dyn.NewValue([]dyn.Value{ - dyn.NewValue("bar", []diag.Location{l1}), - dyn.NewValue("baz", []diag.Location{l1}), - }, []diag.Location{l1}) - - l2 := diag.Location{File: "file2", Line: 3, Column: 4} - l3 := diag.Location{File: "file3", Line: 5, Column: 6} - v2 := dyn.NewValue([]dyn.Value{ - dyn.NewValue("qux", []diag.Location{l2}), - dyn.NewValue("foo", []diag.Location{l3}), - }, []diag.Location{l2, l3}) - - // Merge v2 into v1. - { - out, err := Merge(v1, v2) - assert.NoError(t, err) - assert.Equal(t, []any{ - "bar", - "baz", - "qux", - "foo", - }, out.AsAny()) - - // Locations of both values should be preserved. - assert.Equal(t, []diag.Location{l1, l2, l3}, out.Locations()) - - // Location of the merged value should be the location of v1. - assert.Equal(t, l1, out.Location()) - - // Location of the individual values should be preserved. - assert.Equal(t, l1, out.Index(0).Location()) // "bar" - assert.Equal(t, l1, out.Index(1).Location()) // "baz" - assert.Equal(t, l2, out.Index(2).Location()) // "qux" - assert.Equal(t, l3, out.Index(3).Location()) // "foo" - } - - // Merge v1 into v2. - { - out, err := Merge(v2, v1) - assert.NoError(t, err) - assert.Equal(t, []any{ - "qux", - "foo", - "bar", - "baz", - }, out.AsAny()) - - // Locations of both values should be preserved. - assert.Equal(t, []diag.Location{l2, l3, l1}, out.Locations()) - - // Location of the merged value should be the location of v2. - assert.Equal(t, l2, out.Location()) - - // Location of the individual values should be preserved. - assert.Equal(t, l2, out.Index(0).Location()) // "qux" - assert.Equal(t, l3, out.Index(1).Location()) // "foo" - assert.Equal(t, l1, out.Index(2).Location()) // "bar" - assert.Equal(t, l1, out.Index(3).Location()) // "baz" - } -} - -func TestMergeSequencesNil(t *testing.T) { - v := dyn.V([]dyn.Value{ - dyn.V("bar"), - }) - - // Merge nil into v. - { - out, err := Merge(v, dyn.NilValue) - assert.NoError(t, err) - assert.Equal(t, []any{ - "bar", - }, out.AsAny()) - } - - // Merge v into nil. - { - out, err := Merge(dyn.NilValue, v) - assert.NoError(t, err) - assert.Equal(t, []any{ - "bar", - }, out.AsAny()) - } -} - -func TestMergeSequencesError(t *testing.T) { - v := dyn.V([]dyn.Value{ - dyn.V("bar"), - }) - - other := dyn.V("string") - - // Merge a string into v. - { - out, err := Merge(v, other) - assert.EqualError(t, err, "cannot merge sequence with string") - assert.Equal(t, dyn.InvalidValue, out) - } -} - -func TestMergePrimitives(t *testing.T) { - l1 := diag.Location{File: "file1", Line: 1, Column: 2} - l2 := diag.Location{File: "file2", Line: 3, Column: 4} - v1 := dyn.NewValue("bar", []diag.Location{l1}) - v2 := dyn.NewValue("baz", []diag.Location{l2}) - - // Merge v2 into v1. - { - out, err := Merge(v1, v2) - assert.NoError(t, err) - assert.Equal(t, "baz", out.AsAny()) - - // Locations of both values should be preserved. - assert.Equal(t, []diag.Location{l2, l1}, out.Locations()) - - // Location of the merged value should be the location of v2, the second value. - assert.Equal(t, l2, out.Location()) - } - - // Merge v1 into v2. - { - out, err := Merge(v2, v1) - assert.NoError(t, err) - assert.Equal(t, "bar", out.AsAny()) - - // Locations of both values should be preserved. - assert.Equal(t, []diag.Location{l1, l2}, out.Locations()) - - // Location of the merged value should be the location of v1, the second value. - assert.Equal(t, l1, out.Location()) - } -} - -func TestMergePrimitivesNil(t *testing.T) { - v := dyn.V("bar") - - // Merge nil into v. - { - out, err := Merge(v, dyn.NilValue) - assert.NoError(t, err) - assert.Equal(t, "bar", out.AsAny()) - } - - // Merge v into nil. - { - out, err := Merge(dyn.NilValue, v) - assert.NoError(t, err) - assert.Equal(t, "bar", out.AsAny()) - } -} - -func TestMergePrimitivesError(t *testing.T) { - v := dyn.V("bar") - other := dyn.V(map[string]dyn.Value{ - "foo": dyn.V("bar"), - }) - - // Merge a map into v. - { - out, err := Merge(v, other) - assert.EqualError(t, err, "cannot merge string with map") - assert.Equal(t, dyn.InvalidValue, out) - } -} diff --git a/libs/dyn/merge/override.go b/libs/dyn/merge/override.go deleted file mode 100644 index 873c38eead1..00000000000 --- a/libs/dyn/merge/override.go +++ /dev/null @@ -1,200 +0,0 @@ -package merge - -import ( - "errors" - "fmt" - - "github.com/databricks/cli/libs/dyn" -) - -// OverrideVisitor is visiting the changes during the override process -// and allows to control what changes are allowed, or update the effective -// value. -// -// For instance, it can disallow changes outside the specific path(s), or update -// the location of the effective value. -// -// Values returned by 'VisitInsert' and 'VisitUpdate' are used as the final value -// of the node. 'VisitDelete' can return ErrOverrideUndoDelete to undo delete. -// -// 'VisitDelete' is called when a value is removed from mapping or sequence -// 'VisitInsert' is called when a new value is added to mapping or sequence -// 'VisitUpdate' is called when a leaf value is updated -type OverrideVisitor struct { - VisitDelete func(valuePath dyn.Path, left dyn.Value) error - VisitInsert func(valuePath dyn.Path, right dyn.Value) (dyn.Value, error) - VisitUpdate func(valuePath dyn.Path, left, right dyn.Value) (dyn.Value, error) -} - -var ErrOverrideUndoDelete = errors.New("undo delete operation") - -// Override overrides value 'leftRoot' with 'rightRoot', keeping 'location' if values -// haven't changed. Preserving 'location' is important to preserve the original source of the value -// for error reporting. -func Override(leftRoot, rightRoot dyn.Value, visitor OverrideVisitor) (dyn.Value, error) { - return override(dyn.EmptyPath, leftRoot, rightRoot, visitor) -} - -func override(basePath dyn.Path, left, right dyn.Value, visitor OverrideVisitor) (dyn.Value, error) { - if left.Kind() != right.Kind() { - return visitor.VisitUpdate(basePath, left, right) - } - - // NB: we only call 'VisitUpdate' on leaf values, and for sequences and mappings - // we don't know if value was updated or not - - switch left.Kind() { - case dyn.KindMap: - merged, err := overrideMapping(basePath, left.MustMap(), right.MustMap(), visitor) - if err != nil { - return dyn.InvalidValue, err - } - - return dyn.NewValue(merged, left.Locations()), nil - - case dyn.KindSequence: - // some sequences are keyed, and we can detect which elements are added/removed/updated, - // but we don't have this information - merged, err := overrideSequence(basePath, left.MustSequence(), right.MustSequence(), visitor) - if err != nil { - return dyn.InvalidValue, err - } - - return dyn.NewValue(merged, left.Locations()), nil - - case dyn.KindString: - if left.MustString() == right.MustString() { - return left, nil - } else { - return visitor.VisitUpdate(basePath, left, right) - } - - case dyn.KindFloat: - // TODO consider comparison with epsilon if normalization doesn't help, where do we use floats? - - if left.MustFloat() == right.MustFloat() { - return left, nil - } else { - return visitor.VisitUpdate(basePath, left, right) - } - - case dyn.KindBool: - if left.MustBool() == right.MustBool() { - return left, nil - } else { - return visitor.VisitUpdate(basePath, left, right) - } - - case dyn.KindTime: - if left.MustTime() == right.MustTime() { - return left, nil - } else { - return visitor.VisitUpdate(basePath, left, right) - } - - case dyn.KindInt: - if left.MustInt() == right.MustInt() { - return left, nil - } else { - return visitor.VisitUpdate(basePath, left, right) - } - case dyn.KindNil: - return left, nil - default: - // Fall through to the error case. - } - - return dyn.InvalidValue, fmt.Errorf("unexpected kind %s at %s", left.Kind(), basePath.String()) -} - -func overrideMapping(basePath dyn.Path, leftMapping, rightMapping dyn.Mapping, visitor OverrideVisitor) (dyn.Mapping, error) { - out := dyn.NewMapping() - - for _, leftPair := range leftMapping.Pairs() { - // detect if key was removed - if _, ok := rightMapping.GetPair(leftPair.Key); !ok { - key := leftPair.Key.MustString() - keyLoc := leftPair.Key.Locations() - path := basePath.Append(dyn.Key(key)) - - err := visitor.VisitDelete(path, leftPair.Value) - - // if 'delete' was undone, add it back - if errors.Is(err, ErrOverrideUndoDelete) { - out.SetLoc(key, keyLoc, leftPair.Value) - } else if err != nil { - return dyn.NewMapping(), err - } - } - } - - // iterating only right mapping will remove keys not present anymore - // and insert new keys - - for _, rightPair := range rightMapping.Pairs() { - key := rightPair.Key.MustString() - keyLoc := rightPair.Key.Locations() - if leftPair, ok := leftMapping.GetPair(rightPair.Key); ok { - path := basePath.Append(dyn.Key(key)) - newValue, err := override(path, leftPair.Value, rightPair.Value, visitor) - if err != nil { - return dyn.NewMapping(), err - } - - // key was there before, so keep its location - out.SetLoc(key, keyLoc, newValue) - } else { - path := basePath.Append(dyn.Key(rightPair.Key.MustString())) - - newValue, err := visitor.VisitInsert(path, rightPair.Value) - if err != nil { - return dyn.NewMapping(), err - } - - out.SetLoc(key, keyLoc, newValue) - } - } - - return out, nil -} - -func overrideSequence(basePath dyn.Path, left, right []dyn.Value, visitor OverrideVisitor) ([]dyn.Value, error) { - minLen := min(len(left), len(right)) - var values []dyn.Value - - for i := range minLen { - path := basePath.Append(dyn.Index(i)) - merged, err := override(path, left[i], right[i], visitor) - if err != nil { - return nil, err - } - - values = append(values, merged) - } - - if len(right) > len(left) { - for i := minLen; i < len(right); i++ { - path := basePath.Append(dyn.Index(i)) - newValue, err := visitor.VisitInsert(path, right[i]) - if err != nil { - return nil, err - } - - values = append(values, newValue) - } - } else if len(left) > len(right) { - for i := minLen; i < len(left); i++ { - path := basePath.Append(dyn.Index(i)) - err := visitor.VisitDelete(path, left[i]) - - // if 'delete' was undone, add it back - if errors.Is(err, ErrOverrideUndoDelete) { - values = append(values, left[i]) - } else if err != nil { - return nil, err - } - } - } - - return values, nil -} diff --git a/libs/dyn/merge/override_test.go b/libs/dyn/merge/override_test.go deleted file mode 100644 index 17602f57ad7..00000000000 --- a/libs/dyn/merge/override_test.go +++ /dev/null @@ -1,520 +0,0 @@ -package merge - -import ( - "errors" - "testing" - "time" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynassert" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type overrideTestCase struct { - name string - left dyn.Value - right dyn.Value - state visitorState - expected dyn.Value -} - -func TestOverride_Primitive(t *testing.T) { - leftLocation := diag.Location{File: "left.yml", Line: 1, Column: 1} - rightLocation := diag.Location{File: "right.yml", Line: 1, Column: 1} - - modifiedTestCases := []overrideTestCase{ - { - name: "string (updated)", - state: visitorState{updated: []string{"root"}}, - left: dyn.NewValue("a", []diag.Location{leftLocation}), - right: dyn.NewValue("b", []diag.Location{rightLocation}), - expected: dyn.NewValue("b", []diag.Location{rightLocation}), - }, - { - name: "string (not updated)", - state: visitorState{}, - left: dyn.NewValue("a", []diag.Location{leftLocation}), - right: dyn.NewValue("a", []diag.Location{rightLocation}), - expected: dyn.NewValue("a", []diag.Location{leftLocation}), - }, - { - name: "bool (updated)", - state: visitorState{updated: []string{"root"}}, - left: dyn.NewValue(true, []diag.Location{leftLocation}), - right: dyn.NewValue(false, []diag.Location{rightLocation}), - expected: dyn.NewValue(false, []diag.Location{rightLocation}), - }, - { - name: "bool (not updated)", - state: visitorState{}, - left: dyn.NewValue(true, []diag.Location{leftLocation}), - right: dyn.NewValue(true, []diag.Location{rightLocation}), - expected: dyn.NewValue(true, []diag.Location{leftLocation}), - }, - { - name: "int (updated)", - state: visitorState{updated: []string{"root"}}, - left: dyn.NewValue(1, []diag.Location{leftLocation}), - right: dyn.NewValue(2, []diag.Location{rightLocation}), - expected: dyn.NewValue(2, []diag.Location{rightLocation}), - }, - { - name: "int (not updated)", - state: visitorState{}, - left: dyn.NewValue(int32(1), []diag.Location{leftLocation}), - right: dyn.NewValue(int64(1), []diag.Location{rightLocation}), - expected: dyn.NewValue(int32(1), []diag.Location{leftLocation}), - }, - { - name: "float (updated)", - state: visitorState{updated: []string{"root"}}, - left: dyn.NewValue(1.0, []diag.Location{leftLocation}), - right: dyn.NewValue(2.0, []diag.Location{rightLocation}), - expected: dyn.NewValue(2.0, []diag.Location{rightLocation}), - }, - { - name: "float (not updated)", - state: visitorState{}, - left: dyn.NewValue(float32(1.0), []diag.Location{leftLocation}), - right: dyn.NewValue(float64(1.0), []diag.Location{rightLocation}), - expected: dyn.NewValue(float32(1.0), []diag.Location{leftLocation}), - }, - { - name: "time (updated)", - state: visitorState{updated: []string{"root"}}, - left: dyn.NewValue(dyn.FromTime(time.UnixMilli(10000)), []diag.Location{leftLocation}), - right: dyn.NewValue(dyn.FromTime(time.UnixMilli(10001)), []diag.Location{rightLocation}), - expected: dyn.NewValue(dyn.FromTime(time.UnixMilli(10001)), []diag.Location{rightLocation}), - }, - { - name: "time (not updated)", - state: visitorState{}, - left: dyn.NewValue(dyn.FromTime(time.UnixMilli(10000)), []diag.Location{leftLocation}), - right: dyn.NewValue(dyn.FromTime(time.UnixMilli(10000)), []diag.Location{rightLocation}), - expected: dyn.NewValue(dyn.FromTime(time.UnixMilli(10000)), []diag.Location{leftLocation}), - }, - { - name: "different types (updated)", - state: visitorState{updated: []string{"root"}}, - left: dyn.NewValue("a", []diag.Location{leftLocation}), - right: dyn.NewValue(42, []diag.Location{rightLocation}), - expected: dyn.NewValue(42, []diag.Location{rightLocation}), - }, - { - name: "map - remove 'a', update 'b'", - state: visitorState{ - removed: []string{"root.a"}, - updated: []string{"root.b"}, - }, - left: dyn.NewValue( - map[string]dyn.Value{ - "a": dyn.NewValue(42, []diag.Location{leftLocation}), - "b": dyn.NewValue(10, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}), - - right: dyn.NewValue( - map[string]dyn.Value{ - "b": dyn.NewValue(20, []diag.Location{rightLocation}), - }, - []diag.Location{rightLocation}), - - expected: dyn.NewValue( - map[string]dyn.Value{ - "b": dyn.NewValue(20, []diag.Location{rightLocation}), - }, - []diag.Location{leftLocation}), - }, - { - name: "map - add 'a'", - state: visitorState{ - added: []string{"root.a"}, - }, - left: dyn.NewValue( - map[string]dyn.Value{ - "b": dyn.NewValue(10, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}, - ), - - right: dyn.NewValue( - map[string]dyn.Value{ - "a": dyn.NewValue(42, []diag.Location{rightLocation}), - "b": dyn.NewValue(10, []diag.Location{rightLocation}), - }, - []diag.Location{leftLocation}, - ), - - expected: dyn.NewValue( - map[string]dyn.Value{ - "a": dyn.NewValue(42, []diag.Location{rightLocation}), - // location hasn't changed because value hasn't changed - "b": dyn.NewValue(10, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}, - ), - }, - { - name: "map - remove 'a'", - state: visitorState{ - removed: []string{"root.a"}, - }, - left: dyn.NewValue( - map[string]dyn.Value{ - "a": dyn.NewValue(42, []diag.Location{leftLocation}), - "b": dyn.NewValue(10, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}, - ), - - right: dyn.NewValue( - map[string]dyn.Value{ - "b": dyn.NewValue(10, []diag.Location{rightLocation}), - }, - []diag.Location{leftLocation}, - ), - - expected: dyn.NewValue( - map[string]dyn.Value{ - // location hasn't changed because value hasn't changed - "b": dyn.NewValue(10, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}, - ), - }, - { - name: "map - add 'jobs.job_1'", - state: visitorState{ - added: []string{"root.jobs.job_1"}, - }, - left: dyn.NewValue( - map[string]dyn.Value{ - "jobs": dyn.NewValue( - map[string]dyn.Value{ - "job_0": dyn.NewValue(42, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}, - ), - }, - []diag.Location{leftLocation}, - ), - - right: dyn.NewValue( - map[string]dyn.Value{ - "jobs": dyn.NewValue( - map[string]dyn.Value{ - "job_0": dyn.NewValue(42, []diag.Location{rightLocation}), - "job_1": dyn.NewValue(1337, []diag.Location{rightLocation}), - }, - []diag.Location{rightLocation}, - ), - }, - []diag.Location{rightLocation}, - ), - - expected: dyn.NewValue( - map[string]dyn.Value{ - "jobs": dyn.NewValue( - map[string]dyn.Value{ - "job_0": dyn.NewValue(42, []diag.Location{leftLocation}), - "job_1": dyn.NewValue(1337, []diag.Location{rightLocation}), - }, - []diag.Location{leftLocation}, - ), - }, - []diag.Location{leftLocation}, - ), - }, - { - name: "map - remove nested key", - state: visitorState{removed: []string{"root.jobs.job_1"}}, - left: dyn.NewValue( - map[string]dyn.Value{ - "jobs": dyn.NewValue( - map[string]dyn.Value{ - "job_0": dyn.NewValue(42, []diag.Location{leftLocation}), - "job_1": dyn.NewValue(1337, []diag.Location{rightLocation}), - }, - []diag.Location{leftLocation}, - ), - }, - []diag.Location{leftLocation}, - ), - right: dyn.NewValue( - map[string]dyn.Value{ - "jobs": dyn.NewValue( - map[string]dyn.Value{ - "job_0": dyn.NewValue(42, []diag.Location{rightLocation}), - }, - []diag.Location{rightLocation}, - ), - }, - []diag.Location{rightLocation}, - ), - expected: dyn.NewValue( - map[string]dyn.Value{ - "jobs": dyn.NewValue( - map[string]dyn.Value{ - "job_0": dyn.NewValue(42, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}, - ), - }, - []diag.Location{leftLocation}, - ), - }, - { - name: "sequence - add", - state: visitorState{added: []string{"root[1]"}}, - left: dyn.NewValue( - []dyn.Value{ - dyn.NewValue(42, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}, - ), - right: dyn.NewValue( - []dyn.Value{ - dyn.NewValue(42, []diag.Location{rightLocation}), - dyn.NewValue(10, []diag.Location{rightLocation}), - }, - []diag.Location{rightLocation}, - ), - expected: dyn.NewValue( - []dyn.Value{ - dyn.NewValue(42, []diag.Location{leftLocation}), - dyn.NewValue(10, []diag.Location{rightLocation}), - }, - []diag.Location{leftLocation}, - ), - }, - { - name: "sequence - remove", - state: visitorState{removed: []string{"root[1]"}}, - left: dyn.NewValue( - []dyn.Value{ - dyn.NewValue(42, []diag.Location{leftLocation}), - dyn.NewValue(10, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}, - ), - right: dyn.NewValue( - []dyn.Value{ - dyn.NewValue(42, []diag.Location{rightLocation}), - }, - []diag.Location{rightLocation}, - ), - expected: dyn.NewValue( - []dyn.Value{ - dyn.NewValue(42, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}, - ), - // location hasn't changed because value hasn't changed - }, - { - name: "sequence (not updated)", - state: visitorState{}, - left: dyn.NewValue( - []dyn.Value{ - dyn.NewValue(42, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}, - ), - right: dyn.NewValue( - []dyn.Value{ - dyn.NewValue(42, []diag.Location{rightLocation}), - }, - []diag.Location{rightLocation}, - ), - expected: dyn.NewValue( - []dyn.Value{ - dyn.NewValue(42, []diag.Location{leftLocation}), - }, - []diag.Location{leftLocation}, - ), - }, - { - name: "nil (not updated)", - state: visitorState{}, - left: dyn.NilValue.WithLocations([]diag.Location{leftLocation}), - right: dyn.NilValue.WithLocations([]diag.Location{rightLocation}), - expected: dyn.NilValue.WithLocations([]diag.Location{leftLocation}), - }, - { - name: "nil (updated)", - state: visitorState{updated: []string{"root"}}, - left: dyn.NilValue, - right: dyn.NewValue(42, []diag.Location{rightLocation}), - expected: dyn.NewValue(42, []diag.Location{rightLocation}), - }, - { - name: "change kind (updated)", - state: visitorState{updated: []string{"root"}}, - left: dyn.NewValue(42.0, []diag.Location{leftLocation}), - right: dyn.NewValue(42, []diag.Location{rightLocation}), - expected: dyn.NewValue(42, []diag.Location{rightLocation}), - }, - } - - for _, tc := range modifiedTestCases { - t.Run(tc.name, func(t *testing.T) { - s, visitor := createVisitor(visitorOpts{}) - out, err := override(dyn.NewPath(dyn.Key("root")), tc.left, tc.right, visitor) - - assert.NoError(t, err) - dynassert.Equal(t, tc.state, *s) - dynassert.Equal(t, tc.expected, out) - }) - - modified := len(tc.state.removed)+len(tc.state.added)+len(tc.state.updated) > 0 - - // visitor is not used unless there is a change - - if modified { - t.Run(tc.name+" - visitor has error", func(t *testing.T) { - _, visitor := createVisitor(visitorOpts{error: errors.New("unexpected change in test")}) - _, err := override(dyn.EmptyPath, tc.left, tc.right, visitor) - - assert.EqualError(t, err, "unexpected change in test") - }) - - t.Run(tc.name+" - visitor overrides value", func(t *testing.T) { - expected := dyn.V("return value") - s, visitor := createVisitor(visitorOpts{returnValue: &expected}) - out, err := override(dyn.EmptyPath, tc.left, tc.right, visitor) - - assert.NoError(t, err) - - for _, added := range s.added { - actual, err := dyn.GetByPath(out, dyn.MustPathFromString(added)) - - assert.NoError(t, err) - dynassert.Equal(t, expected, actual) - } - - for _, updated := range s.updated { - actual, err := dyn.GetByPath(out, dyn.MustPathFromString(updated)) - - assert.NoError(t, err) - dynassert.Equal(t, expected, actual) - } - }) - - if len(tc.state.removed) > 0 { - t.Run(tc.name+" - visitor can undo delete", func(t *testing.T) { - s, visitor := createVisitor(visitorOpts{deleteError: ErrOverrideUndoDelete}) - out, err := override(dyn.EmptyPath, tc.left, tc.right, visitor) - require.NoError(t, err) - - for _, removed := range s.removed { - expected, err := dyn.GetByPath(tc.left, dyn.MustPathFromString(removed)) - require.NoError(t, err) - - actual, err := dyn.GetByPath(out, dyn.MustPathFromString(removed)) - - assert.NoError(t, err) - dynassert.Equal(t, expected, actual) - } - }) - } - } - } -} - -func TestOverride_PreserveMappingKeys(t *testing.T) { - leftLocation := diag.Location{File: "left.yml", Line: 1, Column: 1} - leftKeyLocation := diag.Location{File: "left.yml", Line: 2, Column: 1} - leftValueLocation := diag.Location{File: "left.yml", Line: 3, Column: 1} - - rightLocation := diag.Location{File: "right.yml", Line: 1, Column: 1} - rightKeyLocation := diag.Location{File: "right.yml", Line: 2, Column: 1} - rightValueLocation := diag.Location{File: "right.yml", Line: 3, Column: 1} - - left := dyn.NewMapping() - left.SetLoc("a", []diag.Location{leftKeyLocation}, dyn.NewValue(42, []diag.Location{leftValueLocation})) - - right := dyn.NewMapping() - right.SetLoc("a", []diag.Location{rightKeyLocation}, dyn.NewValue(7, []diag.Location{rightValueLocation})) - - state, visitor := createVisitor(visitorOpts{}) - - out, err := override( - dyn.EmptyPath, - dyn.NewValue(left, []diag.Location{leftLocation}), - dyn.NewValue(right, []diag.Location{rightLocation}), - visitor, - ) - - assert.NoError(t, err) - - if err != nil { - outPairs := out.MustMap().Pairs() - - assert.Equal(t, visitorState{updated: []string{"a"}}, state) - assert.Len(t, outPairs, 1) - - // mapping was first defined in left, so it should keep its location - assert.Equal(t, leftLocation, out.Location()) - - // if there is a validation error for key value, it should point - // to where it was initially defined - assert.Equal(t, leftKeyLocation, outPairs[0].Key.Location()) - - // the value should have updated location, because it has changed - assert.Equal(t, rightValueLocation, outPairs[0].Value.Location()) - } -} - -type visitorState struct { - added []string - removed []string - updated []string -} - -type visitorOpts struct { - error error - deleteError error - returnValue *dyn.Value -} - -func createVisitor(opts visitorOpts) (*visitorState, OverrideVisitor) { - s := visitorState{} - - return &s, OverrideVisitor{ - VisitUpdate: func(valuePath dyn.Path, left, right dyn.Value) (dyn.Value, error) { - s.updated = append(s.updated, valuePath.String()) - - if opts.error != nil { - return dyn.NilValue, opts.error - } else if opts.returnValue != nil { - return *opts.returnValue, nil - } else { - return right, nil - } - }, - VisitDelete: func(valuePath dyn.Path, left dyn.Value) error { - s.removed = append(s.removed, valuePath.String()) - - if opts.error != nil { - return opts.error - } else if opts.deleteError != nil { - return opts.deleteError - } else { - return nil - } - }, - VisitInsert: func(valuePath dyn.Path, right dyn.Value) (dyn.Value, error) { - s.added = append(s.added, valuePath.String()) - - if opts.error != nil { - return dyn.NilValue, opts.error - } else if opts.returnValue != nil { - return *opts.returnValue, nil - } else { - return right, nil - } - }, - } -} diff --git a/libs/dyn/merge/select.go b/libs/dyn/merge/select.go deleted file mode 100644 index 9b09bfa2162..00000000000 --- a/libs/dyn/merge/select.go +++ /dev/null @@ -1,53 +0,0 @@ -package merge - -import ( - "fmt" - - "github.com/databricks/cli/libs/dyn" -) - -// Select returns a new map that contains only the keys specified in the included list. -func Select(value dyn.Value, included []string) (dyn.Value, error) { - mapping, ok := value.AsMap() - if !ok { - return dyn.InvalidValue, fmt.Errorf("expected a map, but found %s", value.Kind()) - } - - newMapping := dyn.NewMapping() - for _, key := range included { - pair, ok := mapping.GetPairByString(key) - - if ok { - newMapping.SetLoc(key, pair.Key.Locations(), pair.Value) - } - } - - return dyn.NewValue(newMapping, value.Locations()), nil -} - -// AntiSelect returns a new map with all keys from the input map except for the ones in the excluded list. -func AntiSelect(value dyn.Value, excluded []string) (dyn.Value, error) { - mapping, ok := value.AsMap() - if !ok { - return dyn.InvalidValue, fmt.Errorf("expected a map, but found %s", value.Kind()) - } - - excludedSet := make(map[string]struct{}) - for _, key := range excluded { - excludedSet[key] = struct{}{} - } - - included := make([]string, 0, len(mapping.Pairs())) - for _, pair := range mapping.Pairs() { - key, ok := pair.Key.AsString() - if !ok { - return dyn.InvalidValue, fmt.Errorf("expected a string key, but found %s", pair.Key.Kind()) - } - - if _, ok := excludedSet[key]; !ok { - included = append(included, pair.Key.MustString()) - } - } - - return Select(value, included) -} diff --git a/libs/dyn/merge/select_test.go b/libs/dyn/merge/select_test.go deleted file mode 100644 index c621f226849..00000000000 --- a/libs/dyn/merge/select_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package merge - -import ( - "testing" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestSelect(t *testing.T) { - locations := []diag.Location{{File: "foo.yml", Line: 1, Column: 1}} - included := []string{"foo"} - input := dyn.NewValue( - map[string]dyn.Value{ - "foo": dyn.V("bar"), - "baz": dyn.V("qux"), - }, - locations, - ) - expected := dyn.NewValue( - map[string]dyn.Value{ - "foo": dyn.V("bar"), - }, - locations, - ) - - actual, err := Select(input, included) - - assert.NoError(t, err) - assert.Equal(t, expected, actual) -} - -func TestAntiSelect(t *testing.T) { - locations := []diag.Location{{File: "foo.yml", Line: 1, Column: 1}} - excluded := []string{"foo"} - input := dyn.NewValue( - map[string]dyn.Value{ - "foo": dyn.V("bar"), - "baz": dyn.V("qux"), - }, - locations, - ) - expected := dyn.NewValue( - map[string]dyn.Value{ - "baz": dyn.V("qux"), - }, - locations, - ) - - actual, err := AntiSelect(input, excluded) - - assert.NoError(t, err) - assert.Equal(t, expected, actual) -} diff --git a/libs/dyn/path.go b/libs/dyn/path.go deleted file mode 100644 index 60579103ca8..00000000000 --- a/libs/dyn/path.go +++ /dev/null @@ -1,146 +0,0 @@ -package dyn - -import ( - "bytes" - "fmt" -) - -type pathComponent struct { - key string - index int -} - -func (c pathComponent) Key() string { - return c.key -} - -func (c pathComponent) Index() int { - return c.index -} - -func (c pathComponent) isKey() bool { - return c.key != "" -} - -func (c pathComponent) isIndex() bool { - return c.key == "" -} - -// Path represents a path to a value in a [Value] configuration tree. -type Path []pathComponent - -// EmptyPath is the empty path. -// It is defined for convenience and clarity. -var EmptyPath = Path{} - -// Key returns a path component for a key. -func Key(k string) pathComponent { - return pathComponent{key: k} -} - -// Index returns a path component for an index. -func Index(i int) pathComponent { - return pathComponent{index: i} -} - -// NewPath returns a new path from the given components. -// The individual components may be created with [Key] or [Index]. -func NewPath(cs ...pathComponent) Path { - return cs -} - -// Append appends the given components to the path. -// Mutations to the returned path do not affect the original path. -func (p Path) Append(cs ...pathComponent) Path { - out := make(Path, len(p)+len(cs)) - copy(out, p) - copy(out[len(p):], cs) - return out -} - -// Equal returns true if the paths are equal. -func (p Path) Equal(q Path) bool { - pl := len(p) - ql := len(q) - if pl != ql { - return false - } - for i := range pl { - if p[i] != q[i] { - return false - } - } - return true -} - -// HasPrefix returns true if the path has the specified prefix. -// The empty path is a prefix of all paths. -func (p Path) HasPrefix(q Path) bool { - pl := len(p) - ql := len(q) - if pl < ql { - return false - } - for i := range ql { - if p[i] != q[i] { - return false - } - } - return true -} - -// HasSuffix returns true if the path has the specified suffix. -// The empty path is a suffix of all paths. -func (p Path) HasSuffix(q Path) bool { - pl := len(p) - ql := len(q) - if pl < ql { - return false - } - for i := range ql { - if p[pl-ql+i] != q[i] { - return false - } - } - return true -} - -// CutPrefix returns the path with the specified prefix removed. -// If the path does not have the specified prefix, the original path is returned. -// The second return value is true if the prefix was removed. -// Logically equivalent to [strings.CutPrefix]. -func (p Path) CutPrefix(q Path) (Path, bool) { - if !p.HasPrefix(q) { - return p, false - } - return p[len(q):], true -} - -// CutSuffix returns the path with the specified suffix removed. -// If the path does not have the specified suffix, the original path is returned. -// The second return value is true if the suffix was removed. -// Logically equivalent to [strings.CutSuffix]. -func (p Path) CutSuffix(q Path) (Path, bool) { - if !p.HasSuffix(q) { - return p, false - } - return p[:len(p)-len(q)], true -} - -// String returns a string representation of the path. -func (p Path) String() string { - var buf bytes.Buffer - - for i, c := range p { - if i > 0 && c.key != "" { - buf.WriteRune('.') - } - if c.key != "" { - buf.WriteString(c.key) - } else { - fmt.Fprintf(&buf, "[%d]", c.index) - } - } - - return buf.String() -} diff --git a/libs/dyn/path_string.go b/libs/dyn/path_string.go deleted file mode 100644 index 2f17f9279c2..00000000000 --- a/libs/dyn/path_string.go +++ /dev/null @@ -1,89 +0,0 @@ -package dyn - -import ( - "fmt" - "strconv" - "strings" -) - -// MustPathFromString is like NewPathFromString but panics on error. -func MustPathFromString(input string) Path { - p, err := NewPathFromString(input) - if err != nil { - panic(err) - } - return p -} - -// NewPathFromString parses a path from a string. -// -// The string must be a sequence of keys and indices separated by dots. -// Indices must be enclosed in square brackets. -// The string may include a leading dot. -// -// Examples: -// - foo.bar -// - foo[1].bar -// - foo.bar[1] -// - foo.bar[1][2] -// - . -func NewPathFromString(input string) (Path, error) { - var path Path - - p := input - - // Trim leading dot. - if p != "" && p[0] == '.' { - p = p[1:] - } - - for p != "" { - // Every component may have a leading dot. - if p[0] == '.' { - p = p[1:] - } - - if p == "" { - return nil, fmt.Errorf("invalid path: %s", input) - } - - if p[0] == '[' { - // Find next ] - i := strings.Index(p, "]") - if i < 0 { - return nil, fmt.Errorf("invalid path: %s", input) - } - - // Parse index - j, err := strconv.Atoi(p[1:i]) - if err != nil { - return nil, fmt.Errorf("invalid path: %s", input) - } - - // Append index - path = append(path, Index(j)) - p = p[i+1:] - - // The next character must be a . or [ - if p != "" && strings.IndexAny(p, ".[") != 0 { - return nil, fmt.Errorf("invalid path: %s", input) - } - } else { - // Find next . or [ - i := strings.IndexAny(p, ".[") - if i < 0 { - i = len(p) - } - - if i == 0 { - return nil, fmt.Errorf("invalid path: %s", input) - } - - // Append key - path = append(path, Key(p[:i])) - p = p[i:] - } - } - - return path, nil -} diff --git a/libs/dyn/path_string_test.go b/libs/dyn/path_string_test.go deleted file mode 100644 index d3bfbd7f493..00000000000 --- a/libs/dyn/path_string_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package dyn_test - -import ( - "errors" - "testing" - - . "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestNewPathFromString(t *testing.T) { - for _, tc := range []struct { - input string - output Path - err error - }{ - { - input: "", - output: NewPath(), - }, - { - input: ".", - output: NewPath(), - }, - { - input: "foo.bar", - output: NewPath(Key("foo"), Key("bar")), - }, - { - input: "[1]", - output: NewPath(Index(1)), - }, - { - input: "foo[1].bar", - output: NewPath(Key("foo"), Index(1), Key("bar")), - }, - { - input: "foo.bar[1]", - output: NewPath(Key("foo"), Key("bar"), Index(1)), - }, - { - input: "foo.bar[1][2]", - output: NewPath(Key("foo"), Key("bar"), Index(1), Index(2)), - }, - { - input: "foo.bar[1][2][3]", - output: NewPath(Key("foo"), Key("bar"), Index(1), Index(2), Index(3)), - }, - { - input: "foo[1234]", - output: NewPath(Key("foo"), Index(1234)), - }, - { - input: "foo[123", - err: errors.New("invalid path: foo[123"), - }, - { - input: "foo[123]]", - err: errors.New("invalid path: foo[123]]"), - }, - { - input: "foo[[123]", - err: errors.New("invalid path: foo[[123]"), - }, - { - input: "foo[[123]]", - err: errors.New("invalid path: foo[[123]]"), - }, - { - input: "foo[foo]", - err: errors.New("invalid path: foo[foo]"), - }, - { - input: "foo..bar", - err: errors.New("invalid path: foo..bar"), - }, - { - input: "foo.bar.", - err: errors.New("invalid path: foo.bar."), - }, - { - // Every component may have a leading dot. - input: ".foo.[1].bar", - output: NewPath(Key("foo"), Index(1), Key("bar")), - }, - { - // But after an index there must be a dot. - input: "foo[1]bar", - err: errors.New("invalid path: foo[1]bar"), - }, - { - // * is parsed as regular string in NewPathFromString - input: "foo.*", - output: NewPath(Key("foo"), Key("*")), - }, - { - // * is parsed as regular string in NewPathFromString - input: "foo.*.bar", - output: NewPath(Key("foo"), Key("*"), Key("bar")), - }, - { - // This is an invalid path (but would be valid for patterns) - input: "foo[*].bar", - err: errors.New("invalid path: foo[*].bar"), - }, - } { - p, err := NewPathFromString(tc.input) - if tc.err != nil { - assert.EqualError(t, err, tc.err.Error(), tc.input) - } else { - assert.NoError(t, err) - assert.Equal(t, tc.output, p) - } - } -} diff --git a/libs/dyn/path_test.go b/libs/dyn/path_test.go deleted file mode 100644 index 49114587175..00000000000 --- a/libs/dyn/path_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package dyn_test - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestPathAppend(t *testing.T) { - p := dyn.NewPath(dyn.Key("foo")) - - // Single arg. - p1 := p.Append(dyn.Key("bar")) - assert.True(t, p1.Equal(dyn.NewPath(dyn.Key("foo"), dyn.Key("bar")))) - - // Multiple args. - p2 := p.Append(dyn.Key("bar"), dyn.Index(1)) - assert.True(t, p2.Equal(dyn.NewPath(dyn.Key("foo"), dyn.Key("bar"), dyn.Index(1)))) -} - -func TestPathAppendAlwaysNew(t *testing.T) { - p := make(dyn.Path, 0, 2) - p = append(p, dyn.Key("foo")) - - // There is room for a second element in the slice. - p1 := p.Append(dyn.Index(1)) - p2 := p.Append(dyn.Index(2)) - assert.NotEqual(t, p1, p2) -} - -func TestPathEqualEmpty(t *testing.T) { - assert.True(t, dyn.EmptyPath.Equal(dyn.EmptyPath)) -} - -func TestPathEqual(t *testing.T) { - p1 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1)) - p2 := dyn.NewPath(dyn.Key("bar"), dyn.Index(2)) - assert.False(t, p1.Equal(p2), "expected %q to not equal %q", p1, p2) - - p3 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1)) - assert.True(t, p1.Equal(p3), "expected %q to equal %q", p1, p3) - - p4 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1), dyn.Key("bar"), dyn.Index(2)) - assert.False(t, p1.Equal(p4), "expected %q to not equal %q", p1, p4) -} - -func TestPathHasPrefixEmpty(t *testing.T) { - empty := dyn.EmptyPath - nonEmpty := dyn.NewPath(dyn.Key("foo")) - assert.True(t, empty.HasPrefix(empty)) - assert.True(t, nonEmpty.HasPrefix(empty)) - assert.False(t, empty.HasPrefix(nonEmpty)) -} - -func TestPathHasPrefix(t *testing.T) { - p1 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1)) - p2 := dyn.NewPath(dyn.Key("bar"), dyn.Index(2)) - assert.False(t, p1.HasPrefix(p2), "expected %q to not have prefix %q", p1, p2) - - p3 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1), dyn.Key("bar")) - assert.False(t, p1.HasPrefix(p3), "expected %q to not have prefix %q", p1, p3) - - p4 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1)) - assert.True(t, p1.HasPrefix(p4), "expected %q to have prefix %q", p1, p4) - - p5 := dyn.NewPath(dyn.Key("foo")) - assert.True(t, p1.HasPrefix(p5), "expected %q to have prefix %q", p1, p5) -} - -func TestPathHasSuffixEmpty(t *testing.T) { - empty := dyn.EmptyPath - nonEmpty := dyn.NewPath(dyn.Key("foo")) - assert.True(t, empty.HasSuffix(empty)) - assert.True(t, nonEmpty.HasSuffix(empty)) - assert.False(t, empty.HasSuffix(nonEmpty)) -} - -func TestPathHasSuffix(t *testing.T) { - p1 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1)) - p2 := dyn.NewPath(dyn.Key("bar"), dyn.Index(2)) - assert.False(t, p1.HasSuffix(p2), "expected %q to not have suffix %q", p1, p2) - - p3 := dyn.NewPath(dyn.Index(1)) - assert.True(t, p1.HasSuffix(p3), "expected %q to have suffix %q", p1, p3) - - p4 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1)) - assert.True(t, p1.HasSuffix(p4), "expected %q to have suffix %q", p1, p4) - - p5 := dyn.NewPath(dyn.Key("bar"), dyn.Index(2), dyn.Key("baz")) - assert.False(t, p1.HasSuffix(p5), "expected %q to not have suffix %q", p1, p5) -} - -func TestPathCutPrefix(t *testing.T) { - p1 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1), dyn.Key("bar")) - prefix := dyn.NewPath(dyn.Key("foo"), dyn.Index(1)) - - // Cut a valid prefix. - rest, ok := p1.CutPrefix(prefix) - assert.True(t, ok, "expected %q to have prefix %q", p1, prefix) - assert.True(t, rest.Equal(dyn.NewPath(dyn.Key("bar"))), "expected rest to be %q, got %q", dyn.NewPath(dyn.Key("bar")), rest) - - // Try to cut an invalid prefix. - invalidPrefix := dyn.NewPath(dyn.Key("bar")) - rest, ok = p1.CutPrefix(invalidPrefix) - assert.False(t, ok, "expected %q to not have prefix %q", p1, invalidPrefix) - assert.True(t, rest.Equal(p1), "expected rest to be %q, got %q", p1, rest) - - // Cut an empty prefix. - emptyPrefix := dyn.EmptyPath - rest, ok = p1.CutPrefix(emptyPrefix) - assert.True(t, ok, "expected %q to have prefix %q", p1, emptyPrefix) - assert.True(t, rest.Equal(p1), "expected rest to be %q, got %q", p1, rest) - - // Cut a prefix equal to the path. - rest, ok = p1.CutPrefix(p1) - assert.True(t, ok, "expected %q to have prefix %q", p1, p1) - assert.True(t, rest.Equal(dyn.EmptyPath), "expected rest to be %q, got %q", dyn.EmptyPath, rest) -} - -func TestPathCutSuffix(t *testing.T) { - p1 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1), dyn.Key("bar")) - suffix := dyn.NewPath(dyn.Index(1), dyn.Key("bar")) - - // Cut a valid suffix. - rest, ok := p1.CutSuffix(suffix) - assert.True(t, ok, "expected %q to have suffix %q", p1, suffix) - assert.True(t, rest.Equal(dyn.NewPath(dyn.Key("foo"))), "expected rest to be %q, got %q", dyn.NewPath(dyn.Key("foo")), rest) - - // Try to cut an invalid suffix. - invalidSuffix := dyn.NewPath(dyn.Key("foo")) - rest, ok = p1.CutSuffix(invalidSuffix) - assert.False(t, ok, "expected %q to not have suffix %q", p1, invalidSuffix) - assert.True(t, rest.Equal(p1), "expected rest to be %q, got %q", p1, rest) - - // Cut an empty suffix. - emptySuffix := dyn.EmptyPath - rest, ok = p1.CutSuffix(emptySuffix) - assert.True(t, ok, "expected %q to have suffix %q", p1, emptySuffix) - assert.True(t, rest.Equal(p1), "expected rest to be %q, got %q", p1, rest) - - // Cut a suffix equal to the path. - rest, ok = p1.CutSuffix(p1) - assert.True(t, ok, "expected %q to have suffix %q", p1, p1) - assert.True(t, rest.Equal(dyn.EmptyPath), "expected rest to be %q, got %q", dyn.EmptyPath, rest) -} - -func TestPathString(t *testing.T) { - p1 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1)) - assert.Equal(t, "foo[1]", p1.String()) - - p2 := dyn.NewPath(dyn.Key("bar"), dyn.Index(2), dyn.Key("baz")) - assert.Equal(t, "bar[2].baz", p2.String()) - - p3 := dyn.NewPath(dyn.Key("foo"), dyn.Index(1), dyn.Key("bar"), dyn.Index(2), dyn.Key("baz")) - assert.Equal(t, "foo[1].bar[2].baz", p3.String()) -} diff --git a/libs/dyn/pattern.go b/libs/dyn/pattern.go deleted file mode 100644 index cc53a2b0480..00000000000 --- a/libs/dyn/pattern.go +++ /dev/null @@ -1,191 +0,0 @@ -package dyn - -import ( - "errors" - "fmt" - "slices" - "strings" -) - -// Pattern represents a matcher for paths in a [Value] configuration tree. -// It is used by [MapByPattern] to apply a function to the values whose paths match the pattern. -// Every [Path] is a valid [Pattern] that matches a single unique path. -// The reverse is not true; not every [Pattern] is a valid [Path], as patterns may contain wildcards. -type Pattern []patternComponent - -func (p Pattern) String() string { - buf := strings.Builder{} - first := true - - for _, c := range p { - switch c := c.(type) { - case anyKeyComponent: - if !first { - buf.WriteString(".") - } - buf.WriteString("*") - case anyIndexComponent: - buf.WriteString("[*]") - case pathComponent: - if c.isKey() { - if !first { - buf.WriteString(".") - } - buf.WriteString(c.Key()) - } else { - fmt.Fprintf(&buf, "[%d]", c.Index()) - } - default: - buf.WriteString("???") - } - - first = false - } - return buf.String() -} - -// A pattern component can visit a [Value] and recursively call into [visit] for matching elements. -// Fixed components can match a single key or index, while wildcards can match any key or index. -type patternComponent interface { - visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) -} - -// NewPattern returns a new pattern from the given components. -// The individual components may be created with [Key], [Index], or [Any]. -func NewPattern(cs ...patternComponent) Pattern { - return cs -} - -// NewPatternFromPath returns a new pattern from the given path. -func NewPatternFromPath(p Path) Pattern { - cs := make(Pattern, len(p)) - for i, c := range p { - cs[i] = c - } - return cs -} - -// Split . into and -// The last component must be dyn.Key() and there must be at least two components. -func (p Pattern) SplitKey() (Pattern, string) { - if len(p) <= 1 { - return nil, "" - } - parent := p[:len(p)-1] - leaf := p[len(p)-1] - pc, ok := leaf.(pathComponent) - if !ok { - return nil, "" - } - key := pc.Key() - if key == "" { - return nil, "" - } - return parent, key -} - -// Append appends the given components to the pattern. -func (p Pattern) Append(cs ...patternComponent) Pattern { - out := make(Pattern, len(p)+len(cs)) - copy(out, p) - copy(out[len(p):], cs) - return out -} - -type anyKeyComponent struct{} - -// AnyKey returns a pattern component that matches any key. -func AnyKey() patternComponent { - return anyKeyComponent{} -} - -type expectedMapError struct { - p Path - v Value -} - -func (e expectedMapError) Error() string { - return fmt.Sprintf("expected a map at %q, found %s", e.p, e.v.Kind()) -} - -type expectedSequenceError struct { - p Path - v Value -} - -func (e expectedSequenceError) Error() string { - return fmt.Sprintf("expected a sequence at %q, found %s", e.p, e.v.Kind()) -} - -// isNoMatchError reports whether err means the pattern suffix didn't match the -// visited value. Wildcard components skip such elements (e.g. a job with an -// empty "tasks:" block) instead of failing the visit for all valid siblings. -func isNoMatchError(err error) bool { - if IsNoSuchKeyError(err) || IsIndexOutOfBoundsError(err) || IsCannotTraverseNilError(err) { - return true - } - if _, ok := errors.AsType[expectedMapError](err); ok { - return true - } - _, ok := errors.AsType[expectedSequenceError](err) - return ok -} - -// This function implements the patternComponent interface. -func (c anyKeyComponent) visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) { - m, ok := v.AsMap() - if !ok { - return InvalidValue, expectedMapError{p: prefix, v: v} - } - - m = m.Clone() - for _, pair := range m.Pairs() { - pk := pair.Key - pv := pair.Value - - var err error - nv, err := visit(pv, append(prefix, Key(pk.MustString())), suffix, opts) - if err != nil { - // Leave the value intact if the suffix pattern didn't match any value. - if isNoMatchError(err) { - continue - } - return InvalidValue, err - } - - m.SetLoc(pk.MustString(), pk.Locations(), nv) - } - - return NewValue(m, v.Locations()), nil -} - -type anyIndexComponent struct{} - -// AnyIndex returns a pattern component that matches any index. -func AnyIndex() patternComponent { - return anyIndexComponent{} -} - -// This function implements the patternComponent interface. -func (c anyIndexComponent) visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) { - s, ok := v.AsSequence() - if !ok { - return InvalidValue, expectedSequenceError{p: prefix, v: v} - } - - s = slices.Clone(s) - for i, value := range s { - var err error - nv, err := visit(value, append(prefix, Index(i)), suffix, opts) - if err != nil { - // Leave the value intact if the suffix pattern didn't match any value. - if isNoMatchError(err) { - continue - } - return InvalidValue, err - } - s[i] = nv - } - - return NewValue(s, v.Locations()), nil -} diff --git a/libs/dyn/pattern_string.go b/libs/dyn/pattern_string.go deleted file mode 100644 index 6d770faeb92..00000000000 --- a/libs/dyn/pattern_string.go +++ /dev/null @@ -1,102 +0,0 @@ -package dyn - -import ( - "fmt" - "strconv" - "strings" -) - -// MustPatternFromString is like NewPatternFromString but panics on error. -func MustPatternFromString(input string) Pattern { - p, err := NewPatternFromString(input) - if err != nil { - panic(err) - } - return p -} - -// NewPatternFromString parses a pattern from a string. -// -// The string must be a sequence of keys and indices separated by dots. -// Indices must be enclosed in square brackets. -// The string may include a leading dot. -// The wildcard character '*' can be used to match any key or index. -// -// Examples: -// - foo.bar -// - foo[1].bar -// - foo.*.bar -// - foo[*].bar -// - . -func NewPatternFromString(input string) (Pattern, error) { - var pattern Pattern - - p := input - - // Trim leading dot. - if p != "" && p[0] == '.' { - p = p[1:] - } - - for p != "" { - // Every component may have a leading dot. - if p[0] == '.' { - p = p[1:] - } - - if p == "" { - return nil, fmt.Errorf("invalid pattern: %s", input) - } - - if p[0] == '[' { - // Find next ] - i := strings.Index(p, "]") - if i < 0 { - return nil, fmt.Errorf("invalid pattern: %s", input) - } - - // Check for wildcard - if p[1:i] == "*" { - pattern = append(pattern, AnyIndex()) - } else { - // Parse index - j, err := strconv.Atoi(p[1:i]) - if err != nil { - return nil, fmt.Errorf("invalid pattern: %s", input) - } - - // Append index - pattern = append(pattern, Index(j)) - } - - p = p[i+1:] - - // The next character must be a . or [ - if p != "" && strings.IndexAny(p, ".[") != 0 { - return nil, fmt.Errorf("invalid pattern: %s", input) - } - } else { - // Find next . or [ - i := strings.IndexAny(p, ".[") - if i < 0 { - i = len(p) - } - - if i == 0 { - return nil, fmt.Errorf("invalid pattern: %s", input) - } - - // Check for wildcard - if p[:i] == "*" { - pattern = append(pattern, AnyKey()) - } else { - // Append key - pattern = append(pattern, Key(p[:i])) - } - - p = p[i:] - } - } - - return pattern, nil -} diff --git a/libs/dyn/pattern_string_test.go b/libs/dyn/pattern_string_test.go deleted file mode 100644 index 85127c12ed1..00000000000 --- a/libs/dyn/pattern_string_test.go +++ /dev/null @@ -1,147 +0,0 @@ -package dyn_test - -import ( - "errors" - "fmt" - "testing" - - . "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestNewPatternFromString(t *testing.T) { - for ind, tc := range []struct { - input string - output Pattern - err error - }{ - { - input: "", - output: NewPattern(), - }, - { - input: ".", - output: NewPattern(), - }, - { - input: "foo.bar", - output: NewPattern(Key("foo"), Key("bar")), - }, - { - input: "[1]", - output: NewPattern(Index(1)), - }, - { - input: "foo[1].bar", - output: NewPattern(Key("foo"), Index(1), Key("bar")), - }, - { - input: "foo.bar[1]", - output: NewPattern(Key("foo"), Key("bar"), Index(1)), - }, - { - input: "foo.bar[1][2]", - output: NewPattern(Key("foo"), Key("bar"), Index(1), Index(2)), - }, - { - input: "foo.bar[1][2][3]", - output: NewPattern(Key("foo"), Key("bar"), Index(1), Index(2), Index(3)), - }, - { - input: "foo[1234]", - output: NewPattern(Key("foo"), Index(1234)), - }, - { - input: "foo[123", - err: errors.New("invalid pattern: foo[123"), - }, - { - input: "foo[123]]", - err: errors.New("invalid pattern: foo[123]]"), - }, - { - input: "foo[[123]", - err: errors.New("invalid pattern: foo[[123]"), - }, - { - input: "foo[[123]]", - err: errors.New("invalid pattern: foo[[123]]"), - }, - { - input: "foo[foo]", - err: errors.New("invalid pattern: foo[foo]"), - }, - { - input: "foo..bar", - err: errors.New("invalid pattern: foo..bar"), - }, - { - input: "foo.bar.", - err: errors.New("invalid pattern: foo.bar."), - }, - { - // Every component may have a leading dot. - input: ".foo.[1].bar", - output: NewPattern(Key("foo"), Index(1), Key("bar")), - }, - { - // But after an index there must be a dot. - input: "foo[1]bar", - err: errors.New("invalid pattern: foo[1]bar"), - }, - // Wildcard tests - { - input: "foo.*", - output: NewPattern(Key("foo"), AnyKey()), - }, - { - input: "foo.*.bar", - output: NewPattern(Key("foo"), AnyKey(), Key("bar")), - }, - { - input: "foo[*]", - output: NewPattern(Key("foo"), AnyIndex()), - }, - { - input: "foo[*].bar", - output: NewPattern(Key("foo"), AnyIndex(), Key("bar")), - }, - { - input: "*[1]", - output: NewPattern(AnyKey(), Index(1)), - }, - { - input: "*.*", - output: NewPattern(AnyKey(), AnyKey()), - }, - { - input: "*[*]", - output: NewPattern(AnyKey(), AnyIndex()), - }, - } { - t.Run(fmt.Sprintf("%d %s", ind, tc.input), func(t *testing.T) { - p, err := NewPatternFromString(tc.input) - if tc.err != nil { - assert.EqualError(t, err, tc.err.Error(), tc.input) - } else { - assert.NoError(t, err) - assert.Equal(t, tc.output, p) - } - }) - } -} - -func TestMustPatternFromString(t *testing.T) { - // Test valid pattern - p := MustPatternFromString("foo.bar") - assert.Equal(t, NewPattern(Key("foo"), Key("bar")), p) - - // Test with wildcards - p = MustPatternFromString("foo.*.bar[*]") - assert.Equal(t, NewPattern(Key("foo"), AnyKey(), Key("bar"), AnyIndex()), p) - - // Test that invalid pattern panics - assert.Panics(t, func() { - MustPatternFromString("foo[") - }) -} diff --git a/libs/dyn/pattern_test.go b/libs/dyn/pattern_test.go deleted file mode 100644 index be78b6df61a..00000000000 --- a/libs/dyn/pattern_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package dyn_test - -import ( - "fmt" - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestPatternString(t *testing.T) { - patterns := []string{ - "foo.bar", - "foo.bar.baz", - "foo[1]", - "foo[*]", - "*[1].bar", - "foo.*.bar", - "foo[*].bar", - "", - "foo", - "[1]", - "[*]", - "*", - } - - for _, p := range patterns { - pp := dyn.MustPatternFromString(p) - assert.Equal(t, p, pp.String()) - } -} - -func TestNewPattern(t *testing.T) { - pat := dyn.NewPattern( - dyn.Key("foo"), - dyn.Index(1), - ) - - assert.Len(t, pat, 2) -} - -func TestNewPatternFromPath(t *testing.T) { - path := dyn.NewPath( - dyn.Key("foo"), - dyn.Index(1), - ) - - pat1 := dyn.NewPattern(dyn.Key("foo"), dyn.Index(1)) - pat2 := dyn.NewPatternFromPath(path) - assert.Equal(t, pat1, pat2) -} - -func TestPatternAppend(t *testing.T) { - p := dyn.NewPattern(dyn.Key("foo")) - - // Single arg. - p1 := p.Append(dyn.Key("bar")) - assert.Equal(t, dyn.NewPattern(dyn.Key("foo"), dyn.Key("bar")), p1) - - // Multiple args. - p2 := p.Append(dyn.Key("bar"), dyn.Index(1)) - assert.Equal(t, dyn.NewPattern(dyn.Key("foo"), dyn.Key("bar"), dyn.Index(1)), p2) -} - -func TestPatternAppendAlwaysNew(t *testing.T) { - p := make(dyn.Pattern, 0, 2) - p = append(p, dyn.Key("foo")) - - // There is room for a second element in the slice. - p1 := p.Append(dyn.Index(1)) - p2 := p.Append(dyn.Index(2)) - assert.NotEqual(t, p1, p2) -} - -func TestPatternSplitKey(t *testing.T) { - p := dyn.NewPattern( - dyn.Key("foo"), - dyn.Key("bar"), - ) - - pat, key := p.SplitKey() - assert.Equal(t, "bar", key) - assert.Equal(t, dyn.NewPattern(dyn.Key("foo")), pat) -} - -func TestPatternSplitKeyError(t *testing.T) { - patterns := []dyn.Pattern{ - dyn.NewPattern( - dyn.Key("foo"), - dyn.AnyKey(), - ), - dyn.NewPattern( - dyn.Key("foo"), - dyn.AnyIndex(), - ), - dyn.NewPattern( - dyn.Key("foo"), - dyn.Index(1), - ), - dyn.NewPattern(), - } - - for ind, p := range patterns { - t.Run(fmt.Sprintf("%d %#v", ind, p), func(t *testing.T) { - pat, key := p.SplitKey() - assert.Empty(t, key) - assert.Empty(t, pat) - }) - } -} diff --git a/libs/dyn/structpath.go b/libs/dyn/structpath.go deleted file mode 100644 index 1cb740ee0d7..00000000000 --- a/libs/dyn/structpath.go +++ /dev/null @@ -1,41 +0,0 @@ -package dyn - -import "github.com/databricks/cli/libs/structs/structpath" - -// ToStructPath converts a [Path] to a [structpath.PathNode]. -func ToStructPath(p Path) *structpath.PathNode { - var result *structpath.PathNode - for _, c := range p { - if c.isKey() { - result = structpath.NewStringKey(result, c.key) - } else { - result = structpath.NewIndex(result, c.index) - } - } - return result -} - -// ToStructPaths converts each of paths with [ToStructPath], e.g. for diag.Diagnostic.Paths. -func ToStructPaths(paths ...Path) []*structpath.PathNode { - out := make([]*structpath.PathNode, len(paths)) - for i, p := range paths { - out[i] = ToStructPath(p) - } - return out -} - -// FromStructPath converts a [structpath.PathNode] of keys and indices to a [Path]. -// It reports false for nodes a [Path] cannot represent (key-value selectors, wildcards). -func FromStructPath(p *structpath.PathNode) (Path, bool) { - var out Path - for _, n := range p.AsSlice() { - if k, ok := n.StringKey(); ok { - out = append(out, Key(k)) - } else if i, ok := n.Index(); ok { - out = append(out, Index(i)) - } else { - return nil, false - } - } - return out, true -} diff --git a/libs/dyn/structpath_test.go b/libs/dyn/structpath_test.go deleted file mode 100644 index 6eed07a31fa..00000000000 --- a/libs/dyn/structpath_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package dyn_test - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/structs/structpath" - "github.com/stretchr/testify/assert" -) - -func TestToStructPath(t *testing.T) { - assert.Nil(t, dyn.ToStructPath(nil)) - - p := dyn.NewPath(dyn.Key("resources"), dyn.Key("jobs"), dyn.Key("a_job"), dyn.Key("tasks"), dyn.Index(1), dyn.Key("task_key")) - assert.Equal(t, "resources.jobs.a_job.tasks[1].task_key", dyn.ToStructPath(p).String()) - - p = dyn.NewPath(dyn.Key("resources"), dyn.Key("jobs"), dyn.Key("${var.env}_job")) - assert.Equal(t, "resources.jobs['${var.env}_job']", dyn.ToStructPath(p).String()) -} - -func TestToStructPaths(t *testing.T) { - paths := dyn.ToStructPaths( - dyn.MustPathFromString("a.b"), - dyn.NewPath(dyn.Key("c"), dyn.Index(1)), - ) - assert.Len(t, paths, 2) - assert.Equal(t, "a.b", paths[0].String()) - assert.Equal(t, "c[1]", paths[1].String()) - assert.Empty(t, dyn.ToStructPaths()) -} - -func TestFromStructPath(t *testing.T) { - p, ok := dyn.FromStructPath(structpath.NewPath(nil, "resources", "jobs", "a.b", "tasks", 2)) - assert.True(t, ok) - assert.Equal(t, dyn.NewPath(dyn.Key("resources"), dyn.Key("jobs"), dyn.Key("a.b"), dyn.Key("tasks"), dyn.Index(2)), p) - - _, ok = dyn.FromStructPath(structpath.MustParsePath("tasks[task_key='x']")) - assert.False(t, ok) -} diff --git a/libs/dyn/suggest_test.go b/libs/dyn/suggest_test.go deleted file mode 100644 index b0aa4a87168..00000000000 --- a/libs/dyn/suggest_test.go +++ /dev/null @@ -1,164 +0,0 @@ -package dyn - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestLevenshteinDistance(t *testing.T) { - tests := []struct { - a string - b string - want int - }{ - {"", "", 0}, - {"", "abc", 3}, - {"abc", "", 3}, - {"abc", "abc", 0}, - {"abc", "abd", 1}, - {"kitten", "sitting", 3}, - {"host", "hosts", 1}, - } - for _, tt := range tests { - assert.Equal(t, tt.want, levenshteinDistance(tt.a, tt.b), "levenshteinDistance(%q, %q)", tt.a, tt.b) - } -} - -func newSuggestMapping(keys ...string) Mapping { - var m Mapping - for _, k := range keys { - m.SetLoc(k, nil, V(k)) - } - return m -} - -func TestSuggestKeys(t *testing.T) { - tests := []struct { - name string - keys []string - typo string - want []string - }{ - { - // Keys within distance 2 are returned ordered by increasing - // distance; ties keep the map's insertion order. - name: "ordered by distance", - keys: []string{"host", "hosts", "token", "auth_type"}, - typo: "host", - want: []string{"host", "hosts"}, - }, - { - name: "no key close enough", - keys: []string{"host", "hosts", "token", "auth_type"}, - typo: "completely_different", - want: []string{}, - }, - { - // Distance-2 substitutions and insertions are both included. - name: "distance two included", - keys: []string{"profile", "prfile", "prof"}, - typo: "prfil", - want: []string{"prfile", "profile"}, - }, - { - name: "empty map", - keys: nil, - typo: "anything", - want: []string{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, suggestKeys(newSuggestMapping(tt.keys...), tt.typo)) - }) - } -} - -func TestDidYouMean(t *testing.T) { - tests := []struct { - name string - suggestions []string - want string - }{ - {"nil", nil, ""}, - {"empty", []string{}, ""}, - {"single", []string{"host"}, `, did you mean "host"?`}, - {"multiple", []string{"host", "hosts"}, `, did you mean one of: "host", "hosts"?`}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, didYouMean(tt.suggestions)) - }) - } -} - -func TestSuggestedReferences(t *testing.T) { - tests := []struct { - name string - err error - reference string - want []string - }{ - { - name: "single suggestion", - err: noSuchKeyError{p: NewPath(Key("variables"), Key("hst")), suggestions: []string{"host"}}, - reference: "var.hst", - want: []string{"var.host"}, - }, - { - name: "multiple suggestions", - err: noSuchKeyError{p: NewPath(Key("variables"), Key("hst")), suggestions: []string{"host", "hosts"}}, - reference: "var.hst", - want: []string{"var.host", "var.hosts"}, - }, - { - name: "nested outer-key typo keeps suffix", - err: noSuchKeyError{p: NewPath(Key("variables"), Key("clustr")), suggestions: []string{"cluster"}}, - reference: "var.clustr.spark_version", - want: []string{"var.cluster.spark_version"}, - }, - { - name: "deep leaf typo keeps prefix", - err: noSuchKeyError{p: NewPath(Key("variables"), Key("cluster"), Key("value"), Key("spark_versio")), suggestions: []string{"spark_version"}}, - reference: "var.cluster.spark_versio", - want: []string{"var.cluster.spark_version"}, - }, - { - name: "index component preserved", - err: noSuchKeyError{p: NewPath(Key("variables"), Key("librariez")), suggestions: []string{"libraries"}}, - reference: "var.librariez[0].jar", - want: []string{"var.libraries[0].jar"}, - }, - { - name: "non-var prefix", - err: noSuchKeyError{p: NewPath(Key("workspace"), Key("stot_path")), suggestions: []string{"root_path", "state_path"}}, - reference: "workspace.stot_path", - want: []string{"workspace.root_path", "workspace.state_path"}, - }, - { - name: "no suggestions", - err: noSuchKeyError{p: NewPath(Key("xyz"))}, - reference: "var.xyz", - want: nil, - }, - { - name: "other error type", - err: errors.New("some other error"), - reference: "var.xyz", - want: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, SuggestedReferences(tt.err, tt.reference)) - }) - } -} - -func TestReplaceKeyFallsBackWhenKeyAbsent(t *testing.T) { - // The failed key is not present in the reference, so only the replacement - // itself is returned rather than a spliced reference. - assert.Equal(t, "host", replaceKey("var.foo", "missing", "host")) -} diff --git a/libs/dyn/time.go b/libs/dyn/time.go deleted file mode 100644 index c7f043f06ca..00000000000 --- a/libs/dyn/time.go +++ /dev/null @@ -1,66 +0,0 @@ -package dyn - -import ( - "fmt" - "time" -) - -// Time represents a time-like primitive value. -// -// It represents a timestamp and includes the original string value -// that was parsed to create the timestamp. This makes it possible -// to coalesce a value that YAML interprets as a timestamp back into -// a string without losing information. -type Time struct { - t time.Time - s string -} - -// NewTime creates a new Time from the given string. -func NewTime(str string) (Time, error) { - // Try a couple of layouts - for _, layout := range []string{ - "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. - "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". - "2006-1-2 15:4:5.999999999", // space separated with no time zone - "2006-1-2", // date only - } { - t, terr := time.Parse(layout, str) - if terr == nil { - return Time{t: t, s: str}, nil - } - } - - return Time{}, fmt.Errorf("invalid time value: %q", str) -} - -// MustTime creates a new Time from the given string. -// It panics if the string cannot be parsed. -func MustTime(str string) Time { - t, err := NewTime(str) - if err != nil { - panic(err) - } - return t -} - -// FromTime creates a new Time from the given time.Time. -// It uses the RFC3339Nano format for its string representation. -// This guarantees that it can roundtrip into a string without losing information. -func FromTime(t time.Time) Time { - return Time{t: t, s: t.Format(time.RFC3339Nano)} -} - -// Time returns the time.Time value. -func (t Time) Time() time.Time { - return t.t -} - -// String returns the original string value that was parsed to create the timestamp. -func (t Time) String() string { - return t.s -} - -func (t Time) IsZero() bool { - return t.s == "" && t.t.IsZero() -} diff --git a/libs/dyn/time_test.go b/libs/dyn/time_test.go deleted file mode 100644 index 61cc4fef93d..00000000000 --- a/libs/dyn/time_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package dyn_test - -import ( - "testing" - "time" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestTimeValid(t *testing.T) { - for _, tc := range []string{ - "2024-08-29", - "2024-01-15T12:34:56.789012345Z", - } { - tm, err := dyn.NewTime(tc) - if assert.NoError(t, err) { - assert.NotEqual(t, time.Time{}, tm.Time()) - assert.Equal(t, tc, tm.String()) - } - } -} - -func TestTimeInvalid(t *testing.T) { - tm, err := dyn.NewTime("invalid") - assert.Error(t, err) - assert.Equal(t, dyn.Time{}, tm) -} - -func TestTimeFromTime(t *testing.T) { - tref := time.Now() - t1 := dyn.FromTime(tref) - - // Verify that the underlying value is the same. - assert.Equal(t, tref, t1.Time()) - - // Verify that the string representation can be used to construct the same. - t2, err := dyn.NewTime(t1.String()) - assert.NoError(t, err) - assert.True(t, t1.Time().Equal(t2.Time())) -} diff --git a/libs/dyn/value.go b/libs/dyn/value.go deleted file mode 100644 index 8cbd9b801ea..00000000000 --- a/libs/dyn/value.go +++ /dev/null @@ -1,245 +0,0 @@ -package dyn - -import ( - "fmt" - "reflect" - "slices" - - "github.com/databricks/cli/libs/diag" -) - -type Value struct { - v any - - k Kind - - // List of locations this value is defined at. The first location in the slice - // is the location returned by the `.Location()` method and is typically used - // for reporting errors and warnings associated with the value. - l []diag.Location - - // Whether or not this value is an anchor. - // If this node doesn't map to a type, we don't need to warn about it. - anchor bool -} - -// InvalidValue is equal to the zero-value of Value. -var InvalidValue = Value{ - k: KindInvalid, -} - -// NilValue is a convenient constant for a nil value. -var NilValue = Value{ - k: KindNil, -} - -// V constructs a new Value with the given value. -func V(v any) Value { - return NewValue(v, nil) -} - -// NewValue constructs a new Value with the given value and location. -func NewValue(v any, loc []diag.Location) Value { - switch vin := v.(type) { - case map[string]Value: - v = newMappingFromGoMap(vin) - } - - return Value{ - v: v, - k: kindOf(v), - - // create a copy of the locations, so that mutations to the original slice - // don't affect new value. - l: slices.Clone(loc), - } -} - -// WithLocations returns a new Value with its location set to the given value. -func (v Value) WithLocations(loc []diag.Location) Value { - return Value{ - v: v.v, - k: v.k, - - // create a copy of the locations, so that mutations to the original slice - // don't affect new value. - l: slices.Clone(loc), - } -} - -func (v Value) AppendLocationsFromValue(w Value) Value { - return Value{ - v: v.v, - k: v.k, - l: append(v.l, w.l...), - } -} - -func (v Value) Kind() Kind { - return v.k -} - -func (v Value) Value() any { - return v.v -} - -func (v Value) Locations() []diag.Location { - return v.l -} - -func (v Value) Location() diag.Location { - if len(v.l) == 0 { - return diag.Location{} - } - - return v.l[0] -} - -func (v Value) IsValid() bool { - return v.k != KindInvalid -} - -func (v Value) AsAny() any { - switch v.k { - case KindInvalid: - panic("invoked AsAny on invalid value") - case KindMap: - m := v.v.(Mapping) - out := make(map[string]any, m.Len()) - for _, pair := range m.pairs { - pk := pair.Key - pv := pair.Value - out[pk.MustString()] = pv.AsAny() - } - return out - case KindSequence: - vv := v.v.([]Value) - a := make([]any, len(vv)) - for i, v := range vv { - a[i] = v.AsAny() - } - return a - case KindNil: - return v.v - case KindString: - return v.v - case KindBool: - return v.v - case KindInt: - return v.v - case KindFloat: - return v.v - case KindTime: - t := v.v.(Time) - return t.Time() - default: - // Panic because we only want to deal with known types. - panic(fmt.Sprintf("invalid kind: %d", v.k)) - } -} - -func (v Value) IsZero() bool { - if v.v == nil { - return true - } - - switch x := v.v.(type) { - case Mapping: - return x.Len() == 0 - case []Value: - return len(x) == 0 - default: - return reflect.ValueOf(x).IsZero() - } -} - -func (v Value) Get(key string) Value { - m, ok := v.AsMap() - if !ok { - return InvalidValue - } - - vv, ok := m.GetByString(key) - if !ok { - return InvalidValue - } - - return vv -} - -func (v Value) Index(i int) Value { - s, ok := v.v.([]Value) - if !ok { - return InvalidValue - } - - if i < 0 || i >= len(s) { - return InvalidValue - } - - return s[i] -} - -func (v Value) MarkAnchor() Value { - return Value{ - v: v.v, - k: v.k, - l: v.l, - - anchor: true, - } -} - -func (v Value) IsAnchor() bool { - return v.anchor -} - -// eq is an internal only method that compares two values. -// It is used to determine if a value has changed during a visit. -// We need a custom implementation because maps and slices -// cannot be compared with the regular == operator. -func (v Value) eq(w Value) bool { - if v.k != w.k { - return false - } - if !slices.Equal(v.l, w.l) { - return false - } - - switch v.k { - case KindMap: - // Comparing &v.v to &w.v is always false (value receivers), so compare the Mapping's backing storage. - vm := v.v.(Mapping) - wm := w.v.(Mapping) - lv := vm.Len() - lw := wm.Len() - if lv == 0 && lw == 0 { - return true - } - if lv != lw { - return false - } - // Compare pointers to the underlying pairs slice. - // This is safe because we don't allow maps to be mutated. - return &vm.pairs[0] == &wm.pairs[0] - case KindSequence: - vs := v.v.([]Value) - ws := w.v.([]Value) - lv := len(vs) - lw := len(ws) - // If both slices are empty, they are equal. - if lv == 0 && lw == 0 { - return true - } - // If they have different lengths, they are not equal. - if lv != lw { - return false - } - // They are both non-empty and have the same length. - // Compare pointers to the underlying slice. - // This is safe because we don't allow slices to be mutated. - return &vs[0] == &ws[0] - default: - return v.v == w.v - } -} diff --git a/libs/dyn/value_eq_test.go b/libs/dyn/value_eq_test.go deleted file mode 100644 index 22fe2df0080..00000000000 --- a/libs/dyn/value_eq_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package dyn - -import ( - "testing" - - "github.com/databricks/cli/libs/diag" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestValueEqMap(t *testing.T) { - loc := []diag.Location{{File: "file", Line: 1, Column: 2}} - v := NewValue(map[string]Value{"key": V("value")}, loc) - - tests := []struct { - name string - a, b Value - want bool - }{ - { - name: "same underlying mapping", - a: v, - b: v, - want: true, - }, - { - name: "cloned mapping with equal contents", - a: v, - b: Value{v: v.v.(Mapping).Clone(), k: KindMap, l: v.l}, - want: false, - }, - { - name: "different lengths", - a: v, - b: NewValue(map[string]Value{"key": V("value"), "other": V("value")}, loc), - want: false, - }, - { - name: "different locations", - a: v, - b: v.WithLocations([]diag.Location{{File: "other", Line: 1, Column: 2}}), - want: false, - }, - { - name: "empty mappings", - a: NewValue(map[string]Value{}, loc), - b: NewValue(map[string]Value{}, loc), - want: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.want, tc.a.eq(tc.b)) - }) - } -} - -func TestValueEqMapVisitDoesNotRebuildAncestors(t *testing.T) { - vin := V(map[string]Value{ - "a": V(map[string]Value{ - "b": V("value"), - }), - }) - - vout, err := Map(vin, "a.b", func(_ Path, v Value) (Value, error) { - return v, nil - }) - require.NoError(t, err) - - // The identity transform must return the original value without - // cloning the ancestor maps. - vm := vin.v.(Mapping) - wm := vout.v.(Mapping) - require.Equal(t, vm.Len(), wm.Len()) - assert.Same(t, &vm.pairs[0], &wm.pairs[0]) -} diff --git a/libs/dyn/value_test.go b/libs/dyn/value_test.go deleted file mode 100644 index 667e2cbdd59..00000000000 --- a/libs/dyn/value_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package dyn_test - -import ( - "testing" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestInvalidValue(t *testing.T) { - // Assert that the zero value of [dyn.Value] is the invalid value. - var zero dyn.Value - assert.Equal(t, zero, dyn.InvalidValue) -} - -func TestValueIsAnchor(t *testing.T) { - var zero dyn.Value - assert.False(t, zero.IsAnchor()) - mark := zero.MarkAnchor() - assert.True(t, mark.IsAnchor()) -} - -func TestValueAsMap(t *testing.T) { - var zeroValue dyn.Value - _, ok := zeroValue.AsMap() - assert.False(t, ok) - - intValue := dyn.V(1) - _, ok = intValue.AsMap() - assert.False(t, ok) - - mapValue := dyn.NewValue( - map[string]dyn.Value{ - "key": dyn.NewValue( - "value", - []diag.Location{{File: "file", Line: 1, Column: 2}}), - }, - []diag.Location{{File: "file", Line: 1, Column: 2}}, - ) - - m, ok := mapValue.AsMap() - assert.True(t, ok) - assert.Equal(t, 1, m.Len()) -} - -func TestValueIsValid(t *testing.T) { - var zeroValue dyn.Value - assert.False(t, zeroValue.IsValid()) - intValue := dyn.V(1) - assert.True(t, intValue.IsValid()) -} - -func TestIsZero(t *testing.T) { - assert.True(t, dyn.V(0).IsZero(), "int") - assert.True(t, dyn.V(int(0)).IsZero(), "int") - assert.False(t, dyn.V(int(1)).IsZero(), "int") - assert.True(t, dyn.V(uint(0)).IsZero(), "uint") - - assert.True(t, dyn.V(int8(0)).IsZero(), "int8") - assert.True(t, dyn.V(uint8(0)).IsZero(), "uint8") - - assert.True(t, dyn.V(int16(0)).IsZero(), "int16") - assert.True(t, dyn.V(uint16(0)).IsZero(), "uint16") - - assert.True(t, dyn.V(int32(0)).IsZero(), "int32") - assert.False(t, dyn.V(int32(1)).IsZero(), "int32") - assert.True(t, dyn.V(uint32(0)).IsZero(), "uint32") - - assert.True(t, dyn.V(int64(0)).IsZero(), "int64") - assert.False(t, dyn.V(int64(-1)).IsZero(), "int64") - - assert.True(t, dyn.V(uint64(0)).IsZero(), "uint64") - assert.False(t, dyn.V(uint64(2)).IsZero(), "uint64") - - assert.True(t, dyn.V("").IsZero(), "string") - assert.False(t, dyn.V("x").IsZero(), "string") - - assert.True(t, dyn.V(false).IsZero(), "bool") - assert.False(t, dyn.V(true).IsZero(), "bool") - - assert.True(t, dyn.V(float32(0.0)).IsZero(), "float32") - assert.False(t, dyn.V(float32(0.01)).IsZero(), "float32") - - assert.True(t, dyn.V(float64(0.0)).IsZero(), "float64") - assert.False(t, dyn.V(float64(0.01)).IsZero(), "float64") - - assert.True(t, dyn.V(dyn.Time{}).IsZero(), "time") - assert.True(t, dyn.V(dyn.Mapping{}).IsZero(), "Mapping") - assert.True(t, dyn.V([]dyn.Value{}).IsZero(), "Sequence") - assert.False(t, dyn.V([]dyn.Value{dyn.V(0)}).IsZero(), "Sequence") -} diff --git a/libs/dyn/value_underlying.go b/libs/dyn/value_underlying.go deleted file mode 100644 index a33ecd38ed8..00000000000 --- a/libs/dyn/value_underlying.go +++ /dev/null @@ -1,138 +0,0 @@ -package dyn - -import ( - "fmt" -) - -// AsMap returns the underlying mapping if this value is a map, -// the zero value and false otherwise. -func (v Value) AsMap() (Mapping, bool) { - vv, ok := v.v.(Mapping) - return vv, ok -} - -// MustMap returns the underlying mapping if this value is a map, -// panics otherwise. -func (v Value) MustMap() Mapping { - vv, ok := v.AsMap() - if !ok || v.k != KindMap { - panic(fmt.Sprintf("expected kind %s, got %s", KindMap, v.k)) - } - return vv -} - -// AsSequence returns the underlying sequence if this value is a sequence, -// the zero value and false otherwise. -func (v Value) AsSequence() ([]Value, bool) { - vv, ok := v.v.([]Value) - return vv, ok -} - -// MustSequence returns the underlying sequence if this value is a sequence, -// panics otherwise. -func (v Value) MustSequence() []Value { - vv, ok := v.AsSequence() - if !ok || v.k != KindSequence { - panic(fmt.Sprintf("expected kind %s, got %s", KindSequence, v.k)) - } - return vv -} - -// AsString returns the underlying string if this value is a string, -// the zero value and false otherwise. -func (v Value) AsString() (string, bool) { - vv, ok := v.v.(string) - return vv, ok -} - -// MustString returns the underlying string if this value is a string, -// panics otherwise. -func (v Value) MustString() string { - vv, ok := v.AsString() - if !ok || v.k != KindString { - panic(fmt.Sprintf("expected kind %s, got %s", KindString, v.k)) - } - return vv -} - -// AsBool returns the underlying bool if this value is a bool, -// the zero value and false otherwise. -func (v Value) AsBool() (bool, bool) { - vv, ok := v.v.(bool) - return vv, ok -} - -// MustBool returns the underlying bool if this value is a bool, -// panics otherwise. -func (v Value) MustBool() bool { - vv, ok := v.AsBool() - if !ok || v.k != KindBool { - panic(fmt.Sprintf("expected kind %s, got %s", KindBool, v.k)) - } - return vv -} - -// AsInt returns the underlying int if this value is an int, -// the zero value and false otherwise. -func (v Value) AsInt() (int64, bool) { - switch vv := v.v.(type) { - case int: - return int64(vv), true - case int32: - return int64(vv), true - case int64: - return vv, true - default: - return 0, false - } -} - -// MustInt returns the underlying int if this value is an int, -// panics otherwise. -func (v Value) MustInt() int64 { - vv, ok := v.AsInt() - if !ok || v.k != KindInt { - panic(fmt.Sprintf("expected kind %s, got %s", KindInt, v.k)) - } - return vv -} - -// AsFloat returns the underlying float if this value is a float, -// the zero value and false otherwise. -func (v Value) AsFloat() (float64, bool) { - switch vv := v.v.(type) { - case float32: - return float64(vv), true - case float64: - return float64(vv), true - default: - return 0, false - } -} - -// MustFloat returns the underlying float if this value is a float, -// panics otherwise. -func (v Value) MustFloat() float64 { - vv, ok := v.AsFloat() - if !ok || v.k != KindFloat { - panic(fmt.Sprintf("expected kind %s, got %s", KindFloat, v.k)) - } - return vv -} - -// AsTime returns the underlying time if this value is a time, -// the zero value and false otherwise. -func (v Value) AsTime() (Time, bool) { - vv, ok := v.v.(Time) - return vv, ok -} - -// MustTime returns the underlying time if this value is a time, -// panics otherwise. -func (v Value) MustTime() Time { - vv, ok := v.AsTime() - if !ok || v.k != KindTime { - panic(fmt.Sprintf("expected kind %s, got %s", KindTime, v.k)) - } - return vv -} diff --git a/libs/dyn/value_underlying_test.go b/libs/dyn/value_underlying_test.go deleted file mode 100644 index 189a49a0ea3..00000000000 --- a/libs/dyn/value_underlying_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package dyn_test - -import ( - "testing" - "time" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestValueUnderlyingMap(t *testing.T) { - v := dyn.V( - map[string]dyn.Value{ - "key": dyn.NewValue("value", []diag.Location{{File: "file", Line: 1, Column: 2}}), - }, - ) - - vv1, ok := v.AsMap() - assert.True(t, ok) - - _, ok = dyn.InvalidValue.AsMap() - assert.False(t, ok) - - vv2 := v.MustMap() - assert.Equal(t, vv1, vv2) - - // Test panic. - assert.PanicsWithValue(t, "expected kind map, got invalid", func() { - dyn.InvalidValue.MustMap() - }) -} - -func TestValueUnderlyingSequence(t *testing.T) { - v := dyn.V( - []dyn.Value{ - dyn.NewValue("value", []diag.Location{{File: "file", Line: 1, Column: 2}}), - }, - ) - - vv1, ok := v.AsSequence() - assert.True(t, ok) - - _, ok = dyn.InvalidValue.AsSequence() - assert.False(t, ok) - - vv2 := v.MustSequence() - assert.Equal(t, vv1, vv2) - - // Test panic. - assert.PanicsWithValue(t, "expected kind sequence, got invalid", func() { - dyn.InvalidValue.MustSequence() - }) -} - -func TestValueUnderlyingString(t *testing.T) { - v := dyn.V("value") - - vv1, ok := v.AsString() - assert.True(t, ok) - - _, ok = dyn.InvalidValue.AsString() - assert.False(t, ok) - - vv2 := v.MustString() - assert.Equal(t, vv1, vv2) - - // Test panic. - assert.PanicsWithValue(t, "expected kind string, got invalid", func() { - dyn.InvalidValue.MustString() - }) -} - -func TestValueUnderlyingBool(t *testing.T) { - v := dyn.V(true) - - vv1, ok := v.AsBool() - assert.True(t, ok) - - _, ok = dyn.InvalidValue.AsBool() - assert.False(t, ok) - - vv2 := v.MustBool() - assert.Equal(t, vv1, vv2) - - // Test panic. - assert.PanicsWithValue(t, "expected kind bool, got invalid", func() { - dyn.InvalidValue.MustBool() - }) -} - -func TestValueUnderlyingInt(t *testing.T) { - v := dyn.V(int(1)) - - vv1, ok := v.AsInt() - assert.True(t, ok) - - _, ok = dyn.InvalidValue.AsInt() - assert.False(t, ok) - - vv2 := v.MustInt() - assert.Equal(t, vv1, vv2) - - // Test panic. - assert.PanicsWithValue(t, "expected kind int, got invalid", func() { - dyn.InvalidValue.MustInt() - }) - - // Test int32 type specifically. - v = dyn.V(int32(1)) - vv1, ok = v.AsInt() - assert.True(t, ok) - assert.Equal(t, int64(1), vv1) - - // Test int64 type specifically. - v = dyn.V(int64(1)) - vv1, ok = v.AsInt() - assert.True(t, ok) - assert.Equal(t, int64(1), vv1) -} - -func TestValueUnderlyingFloat(t *testing.T) { - v := dyn.V(float32(1.0)) - - vv1, ok := v.AsFloat() - assert.True(t, ok) - - _, ok = dyn.InvalidValue.AsFloat() - assert.False(t, ok) - - vv2 := v.MustFloat() - assert.Zero(t, vv1-vv2) - - // Test panic. - assert.PanicsWithValue(t, "expected kind float, got invalid", func() { - dyn.InvalidValue.MustFloat() - }) - - // Test float64 type specifically. - v = dyn.V(float64(1.0)) - vv1, ok = v.AsFloat() - assert.True(t, ok) - assert.Zero(t, 1.0-vv1) -} - -func TestValueUnderlyingTime(t *testing.T) { - v := dyn.V(dyn.FromTime(time.Now())) - - vv1, ok := v.AsTime() - assert.True(t, ok) - - _, ok = dyn.InvalidValue.AsTime() - assert.False(t, ok) - - vv2 := v.MustTime() - assert.Equal(t, vv1, vv2) - - // Test panic. - assert.PanicsWithValue(t, "expected kind time, got invalid", func() { - dyn.InvalidValue.MustTime() - }) -} diff --git a/libs/dyn/visit.go b/libs/dyn/visit.go deleted file mode 100644 index 58223e792b9..00000000000 --- a/libs/dyn/visit.go +++ /dev/null @@ -1,210 +0,0 @@ -package dyn - -import ( - "errors" - "fmt" - "slices" -) - -// This error is returned if the path indicates that a map or sequence is expected, but the value is nil. -type cannotTraverseNilError struct { - p Path -} - -func (e cannotTraverseNilError) Error() string { - component := e.p[len(e.p)-1] - switch { - case component.isKey(): - return fmt.Sprintf("expected a map to index %q, found nil", e.p) - case component.isIndex(): - return fmt.Sprintf("expected a sequence to index %q, found nil", e.p) - default: - panic("invalid component") - } -} - -func IsCannotTraverseNilError(err error) bool { - _, ok := errors.AsType[cannotTraverseNilError](err) - return ok -} - -type noSuchKeyError struct { - p Path - suggestions []string -} - -func (e noSuchKeyError) Error() string { - return fmt.Sprintf("key not found at %q%s", e.p, didYouMean(e.suggestions)) -} - -func IsNoSuchKeyError(err error) bool { - _, ok := errors.AsType[noSuchKeyError](err) - return ok -} - -// SuggestedReferences returns drop-in replacement references for a noSuchKeyError -// (nil otherwise), rebuilt by swapping the failed segment of reference for each -// suggestion (e.g. "var.hst" -> ["var.host"]). -func SuggestedReferences(err error, reference string) []string { - e, ok := errors.AsType[noSuchKeyError](err) - if !ok || len(e.suggestions) == 0 { - return nil - } - // Last component of e.p is the failed key (same in original and rewritten space). - failedKey := e.p[len(e.p)-1].Key() - refs := make([]string, len(e.suggestions)) - for i, s := range e.suggestions { - refs[i] = replaceKey(reference, failedKey, s) - } - return refs -} - -type indexOutOfBoundsError struct { - p Path -} - -func (e indexOutOfBoundsError) Error() string { - return fmt.Sprintf("index out of bounds at %q", e.p) -} - -func IsIndexOutOfBoundsError(err error) bool { - _, ok := errors.AsType[indexOutOfBoundsError](err) - return ok -} - -type expectedMapToIndexError struct { - p Path - v Value -} - -func (e expectedMapToIndexError) Error() string { - return fmt.Sprintf("expected a map to index %q, found %s", e.p, e.v.Kind()) -} - -type expectedSequenceToIndexError struct { - p Path - v Value -} - -func (e expectedSequenceToIndexError) Error() string { - return fmt.Sprintf("expected a sequence to index %q, found %s", e.p, e.v.Kind()) -} - -type visitOptions struct { - // The function to apply to the value once found. - // - // If this function returns the same value as it receives as argument, - // the original visit function call returns the original value unmodified. - // - // If this function returns a new value, the original visit function call - // returns a value with all the intermediate values updated. - // - // If this function returns an error, the original visit function call - // returns this error and the value is left unmodified. - fn func(Path, Value) (Value, error) -} - -func visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) { - if len(suffix) == 0 { - return opts.fn(slices.Clone(prefix), v) - } - - // Initialize prefix if it is empty. - // It is pre-allocated to its maximum size to avoid additional allocations. - if len(prefix) == 0 { - prefix = make(Path, 0, len(suffix)) - } - - component := suffix[0] - suffix = suffix[1:] - - // Visit the value with the current component. - return component.visit(v, prefix, suffix, opts) -} - -func (c pathComponent) visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) { - path := append(prefix, c) - - switch { - case c.isKey(): - // Expect a map to be set if this is a key. - switch v.Kind() { - case KindMap: - // OK - case KindNil: - return InvalidValue, cannotTraverseNilError{path} - default: - return InvalidValue, expectedMapToIndexError{p: path, v: v} - } - - m := v.MustMap() - - // Lookup current value in the map. - ev, ok := m.GetByString(c.key) - if !ok { - return InvalidValue, noSuchKeyError{p: path, suggestions: suggestKeys(m, c.key)} - } - - // Recursively transform the value. - nv, err := visit(ev, path, suffix, opts) - if err != nil { - return InvalidValue, err - } - - // Return the original value if the value hasn't changed. - if nv.eq(ev) { - return v, nil - } - - // Return an updated map value. - m = m.Clone() - m.SetLoc(c.key, nil, nv) - return Value{ - v: m, - k: KindMap, - l: v.l, - }, nil - - case c.isIndex(): - // Expect a sequence to be set if this is an index. - switch v.Kind() { - case KindSequence: - // OK - case KindNil: - return InvalidValue, cannotTraverseNilError{path} - default: - return InvalidValue, expectedSequenceToIndexError{p: path, v: v} - } - - s := v.MustSequence() - - // Lookup current value in the sequence. - if c.index < 0 || c.index >= len(s) { - return InvalidValue, indexOutOfBoundsError{path} - } - - // Recursively transform the value. - ev := s[c.index] - nv, err := visit(ev, path, suffix, opts) - if err != nil { - return InvalidValue, err - } - - // Return the original value if the value hasn't changed. - if nv.eq(ev) { - return v, nil - } - - // Return an updated sequence value. - s = slices.Clone(s) - s[c.index] = nv - return Value{ - v: s, - k: KindSequence, - l: v.l, - }, nil - - default: - panic("invalid component") - } -} diff --git a/libs/dyn/visit_get.go b/libs/dyn/visit_get.go deleted file mode 100644 index d23c1f97427..00000000000 --- a/libs/dyn/visit_get.go +++ /dev/null @@ -1,31 +0,0 @@ -package dyn - -// Get returns the value inside the specified value at the specified path. -// It is identical to [GetByPath], except that it takes a string path instead of a [Path]. -func Get(v Value, path string) (Value, error) { - p, err := NewPathFromString(path) - if err != nil { - return InvalidValue, err - } - return GetByPath(v, p) -} - -// GetValue returns the value without the error (InvalidValue indicates that error did happen) -func GetValue(v Value, path string) Value { - val, _ := Get(v, path) - return val -} - -// GetByPath returns the value inside the specified value at the specified path. -// If the path doesn't exist, it returns InvalidValue and an error. -func GetByPath(v Value, p Path) (Value, error) { - out := InvalidValue - _, err := visit(v, EmptyPath, NewPatternFromPath(p), visitOptions{ - fn: func(_ Path, ev Value) (Value, error) { - // Capture the value argument to return it. - out = ev - return ev, nil - }, - }) - return out, err -} diff --git a/libs/dyn/visit_get_test.go b/libs/dyn/visit_get_test.go deleted file mode 100644 index 524b4ed5cd9..00000000000 --- a/libs/dyn/visit_get_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package dyn_test - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestGetWithEmptyPath(t *testing.T) { - // An empty path means to return the value itself. - vin := dyn.V(42) - vout, err := dyn.GetByPath(vin, dyn.NewPath()) - assert.NoError(t, err) - assert.Equal(t, vin, vout) -} - -func TestGetOnNilValue(t *testing.T) { - var err error - _, err = dyn.GetByPath(dyn.NilValue, dyn.NewPath(dyn.Key("foo"))) - assert.ErrorContains(t, err, `expected a map to index "foo", found nil`) - _, err = dyn.GetByPath(dyn.NilValue, dyn.NewPath(dyn.Index(42))) - assert.ErrorContains(t, err, `expected a sequence to index "[42]", found nil`) -} - -func TestGetOnMap(t *testing.T) { - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V(42), - "bar": dyn.V(43), - }) - - var err error - - _, err = dyn.GetByPath(vin, dyn.NewPath(dyn.Index(42))) - assert.ErrorContains(t, err, `expected a sequence to index "[42]", found map`) - - _, err = dyn.GetByPath(vin, dyn.NewPath(dyn.Key("baz"))) - assert.True(t, dyn.IsNoSuchKeyError(err)) - // "baz" is one edit away from "bar", so the error suggests it. - assert.ErrorContains(t, err, `key not found at "baz", did you mean "bar"?`) - - // A key that is close to no existing key gets no suggestion. - _, err = dyn.GetByPath(vin, dyn.NewPath(dyn.Key("completely_different"))) - assert.True(t, dyn.IsNoSuchKeyError(err)) - assert.ErrorContains(t, err, `key not found at "completely_different"`) - assert.NotContains(t, err.Error(), "did you mean") - - vfoo, err := dyn.GetByPath(vin, dyn.NewPath(dyn.Key("foo"))) - assert.NoError(t, err) - assert.Equal(t, dyn.V(42), vfoo) - - vbar, err := dyn.GetByPath(vin, dyn.NewPath(dyn.Key("bar"))) - assert.NoError(t, err) - assert.Equal(t, dyn.V(43), vbar) -} - -func TestGetOnSequence(t *testing.T) { - vin := dyn.V([]dyn.Value{ - dyn.V(42), - dyn.V(43), - }) - - var err error - - _, err = dyn.GetByPath(vin, dyn.NewPath(dyn.Key("foo"))) - assert.ErrorContains(t, err, `expected a map to index "foo", found sequence`) - - _, err = dyn.GetByPath(vin, dyn.NewPath(dyn.Index(-1))) - assert.True(t, dyn.IsIndexOutOfBoundsError(err)) - assert.ErrorContains(t, err, `index out of bounds at "[-1]"`) - - _, err = dyn.GetByPath(vin, dyn.NewPath(dyn.Index(2))) - assert.True(t, dyn.IsIndexOutOfBoundsError(err)) - assert.ErrorContains(t, err, `index out of bounds at "[2]"`) - - v0, err := dyn.GetByPath(vin, dyn.NewPath(dyn.Index(0))) - assert.NoError(t, err) - assert.Equal(t, dyn.V(42), v0) - - v1, err := dyn.GetByPath(vin, dyn.NewPath(dyn.Index(1))) - assert.NoError(t, err) - assert.Equal(t, dyn.V(43), v1) -} diff --git a/libs/dyn/visit_map.go b/libs/dyn/visit_map.go deleted file mode 100644 index a20655e5c21..00000000000 --- a/libs/dyn/visit_map.go +++ /dev/null @@ -1,89 +0,0 @@ -package dyn - -import ( - "fmt" - "slices" -) - -// MapFunc is a function that maps a value to another value. -type MapFunc func(Path, Value) (Value, error) - -// Foreach returns a [MapFunc] that applies the specified [MapFunc] to each -// value in a map or sequence and returns the new map or sequence. -// If the input is nil, it returns nil. -func Foreach(fn MapFunc) MapFunc { - return func(p Path, v Value) (Value, error) { - switch v.Kind() { - case KindNil: - return v, nil - case KindMap: - m := v.MustMap().Clone() - for _, pair := range m.Pairs() { - pk := pair.Key - pv := pair.Value - nv, err := fn(p.Append(Key(pk.MustString())), pv) - if err != nil { - return InvalidValue, err - } - m.SetLoc(pk.MustString(), pk.Locations(), nv) - } - return NewValue(m, v.Locations()), nil - case KindSequence: - s := slices.Clone(v.MustSequence()) - for i, value := range s { - var err error - s[i], err = fn(p.Append(Index(i)), value) - if err != nil { - return InvalidValue, err - } - } - return NewValue(s, v.Locations()), nil - default: - return InvalidValue, fmt.Errorf("expected a map or sequence, found %s", v.Kind()) - } - } -} - -// Map applies a function to the value at the given path in the given value. -// It is identical to [MapByPath], except that it takes a string path instead of a [Path]. -func Map(v Value, path string, fn MapFunc) (Value, error) { - p, err := NewPathFromString(path) - if err != nil { - return InvalidValue, err - } - return MapByPath(v, p, fn) -} - -// MapByPath applies a function to the value at the given path in the given value. -// It is identical to [MapByPattern], except that it takes a [Path] instead of a [Pattern]. -// This means it only matches a single value, not a pattern of values. -func MapByPath(v Value, p Path, fn MapFunc) (Value, error) { - return MapByPattern(v, NewPatternFromPath(p), fn) -} - -// MapByPattern applies a function to the values whose paths match the given pattern in the given value. -// If successful, it returns the new value with all intermediate values copied and updated. -// -// If the pattern contains a key that doesn't exist, or an index that is out of bounds, -// it returns the original value and no error. -// -// If the pattern is invalid for the given value, it returns InvalidValue and an error. -func MapByPattern(v Value, p Pattern, fn MapFunc) (Value, error) { - nv, err := visit(v, EmptyPath, p, visitOptions{ - fn: fn, - }) - - // Check for success. - if err == nil { - return nv, nil - } - - // Return original value if: - // - any map or sequence is a nil, or - // - a key or index is missing - if IsCannotTraverseNilError(err) || IsNoSuchKeyError(err) || IsIndexOutOfBoundsError(err) { - return v, nil - } - - return nv, err -} diff --git a/libs/dyn/visit_map_test.go b/libs/dyn/visit_map_test.go deleted file mode 100644 index bff4b0bd3f9..00000000000 --- a/libs/dyn/visit_map_test.go +++ /dev/null @@ -1,477 +0,0 @@ -package dyn_test - -import ( - "errors" - "fmt" - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestMapWithEmptyPath(t *testing.T) { - // An empty path means to return the value itself. - vin := dyn.V(42) - vout, err := dyn.MapByPath(dyn.InvalidValue, dyn.EmptyPath, func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return vin, nil - }) - assert.NoError(t, err) - assert.Equal(t, vin, vout) -} - -func TestMapOnNilValue(t *testing.T) { - var nv dyn.Value - var err error - nv, err = dyn.MapByPath(dyn.NilValue, dyn.NewPath(dyn.Key("foo")), nil) - assert.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) - nv, err = dyn.MapByPath(dyn.NilValue, dyn.NewPath(dyn.Index(42)), nil) - assert.NoError(t, err) - assert.Equal(t, dyn.NilValue, nv) -} - -func TestMapFuncOnMap(t *testing.T) { - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V(42), - "bar": dyn.V(43), - }) - - var err error - - _, err = dyn.MapByPath(vin, dyn.NewPath(dyn.Index(42)), nil) - assert.ErrorContains(t, err, `expected a sequence to index "[42]", found map`) - - // A key that does not exist is not an error. - vout, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Key("baz")), nil) - assert.NoError(t, err) - assert.Equal(t, vin, vout) - - // Note: in the test cases below we implicitly test that the original - // value is not modified as we repeatedly set values on it. - vfoo, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Key("foo")), func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - assert.Equal(t, dyn.NewPath(dyn.Key("foo")), p) - assert.Equal(t, dyn.V(42), v) - return dyn.V(44), nil - }) - assert.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": 44, - "bar": 43, - }, vfoo.AsAny()) - - vbar, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Key("bar")), func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - assert.Equal(t, dyn.NewPath(dyn.Key("bar")), p) - assert.Equal(t, dyn.V(43), v) - return dyn.V(45), nil - }) - assert.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": 42, - "bar": 45, - }, vbar.AsAny()) - - // Return error from map function. - ref := errors.New("error") - verr, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Key("foo")), func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return dyn.InvalidValue, ref - }) - assert.Equal(t, dyn.InvalidValue, verr) - assert.ErrorIs(t, err, ref) -} - -func TestMapFuncOnMapWithEmptySequence(t *testing.T) { - variants := []dyn.Value{ - // empty sequence - dyn.V([]dyn.Value{}), - // non-empty sequence - dyn.V([]dyn.Value{dyn.V(42)}), - } - - for i := range variants { - vin := dyn.V(map[string]dyn.Value{ - "key": variants[i], - }) - - for j := range variants { - vout, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Key("key")), func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return variants[j], nil - }) - assert.NoError(t, err) - assert.Equal(t, variants[j], vout.Get("key")) - } - } -} - -func TestMapFuncOnSequence(t *testing.T) { - vin := dyn.V([]dyn.Value{ - dyn.V(42), - dyn.V(43), - }) - - var err error - - _, err = dyn.MapByPath(vin, dyn.NewPath(dyn.Key("foo")), nil) - assert.ErrorContains(t, err, `expected a map to index "foo", found sequence`) - - // An index that does not exist is not an error. - vout, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Index(2)), nil) - assert.NoError(t, err) - assert.Equal(t, vin, vout) - - // Note: in the test cases below we implicitly test that the original - // value is not modified as we repeatedly set values on it. - v0, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Index(0)), func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - assert.Equal(t, dyn.NewPath(dyn.Index(0)), p) - assert.Equal(t, dyn.V(42), v) - return dyn.V(44), nil - }) - assert.NoError(t, err) - assert.Equal(t, []any{44, 43}, v0.AsAny()) - - v1, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Index(1)), func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - assert.Equal(t, dyn.NewPath(dyn.Index(1)), p) - assert.Equal(t, dyn.V(43), v) - return dyn.V(45), nil - }) - assert.NoError(t, err) - assert.Equal(t, []any{42, 45}, v1.AsAny()) - - // Return error from map function. - ref := errors.New("error") - verr, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Index(0)), func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return dyn.InvalidValue, ref - }) - assert.Equal(t, dyn.InvalidValue, verr) - assert.ErrorIs(t, err, ref) -} - -func TestMapFuncOnSequenceWithEmptySequence(t *testing.T) { - variants := []dyn.Value{ - // empty sequence - dyn.V([]dyn.Value{}), - // non-empty sequence - dyn.V([]dyn.Value{dyn.V(42)}), - } - - for i := range variants { - vin := dyn.V([]dyn.Value{ - variants[i], - }) - - for j := range variants { - vout, err := dyn.MapByPath(vin, dyn.NewPath(dyn.Index(0)), func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return variants[j], nil - }) - assert.NoError(t, err) - assert.Equal(t, variants[j], vout.Index(0)) - } - } -} - -func TestMapForeachOnMap(t *testing.T) { - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V(42), - "bar": dyn.V(43), - }) - - var err error - - // Run foreach, adding 1 to each of the elements. - vout, err := dyn.Map(vin, ".", dyn.Foreach(func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - i, ok := v.AsInt() - require.True(t, ok, "expected an integer") - switch p[0].Key() { - case "foo": - assert.EqualValues(t, 42, i) - return dyn.V(43), nil - case "bar": - assert.EqualValues(t, 43, i) - return dyn.V(44), nil - default: - return dyn.InvalidValue, fmt.Errorf("unexpected key %q", p[0].Key()) - } - })) - assert.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": 43, - "bar": 44, - }, vout.AsAny()) - - // Check that the original has not been modified. - assert.Equal(t, map[string]any{ - "foo": 42, - "bar": 43, - }, vin.AsAny()) -} - -func TestMapForeachOnMapError(t *testing.T) { - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V(42), - "bar": dyn.V(43), - }) - - // Check that an error from the map function propagates. - ref := errors.New("error") - _, err := dyn.Map(vin, ".", dyn.Foreach(func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return dyn.InvalidValue, ref - })) - assert.ErrorIs(t, err, ref) -} - -func TestMapForeachOnSequence(t *testing.T) { - vin := dyn.V([]dyn.Value{ - dyn.V(42), - dyn.V(43), - }) - - var err error - - // Run foreach, adding 1 to each of the elements. - vout, err := dyn.Map(vin, ".", dyn.Foreach(func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - i, ok := v.AsInt() - require.True(t, ok, "expected an integer") - switch p[0].Index() { - case 0: - assert.EqualValues(t, 42, i) - return dyn.V(43), nil - case 1: - assert.EqualValues(t, 43, i) - return dyn.V(44), nil - default: - return dyn.InvalidValue, fmt.Errorf("unexpected index %d", p[0].Index()) - } - })) - assert.NoError(t, err) - assert.Equal(t, []any{43, 44}, vout.AsAny()) - - // Check that the original has not been modified. - assert.Equal(t, []any{42, 43}, vin.AsAny()) -} - -func TestMapForeachOnSequenceError(t *testing.T) { - vin := dyn.V([]dyn.Value{ - dyn.V(42), - dyn.V(43), - }) - - // Check that an error from the map function propagates. - ref := errors.New("error") - _, err := dyn.Map(vin, ".", dyn.Foreach(func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return dyn.InvalidValue, ref - })) - assert.ErrorIs(t, err, ref) -} - -func TestMapForeachOnOtherError(t *testing.T) { - vin := dyn.V(42) - - // Check that if foreach is applied to something other than a map or a sequence, it returns an error. - _, err := dyn.Map(vin, ".", dyn.Foreach(func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return dyn.InvalidValue, nil - })) - assert.ErrorContains(t, err, "expected a map or sequence, found int") -} - -func TestMapForeachOnNil(t *testing.T) { - vin := dyn.NilValue - - // Check that if foreach is applied to nil, it returns nil. - vout, err := dyn.Map(vin, ".", dyn.Foreach(func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return dyn.InvalidValue, nil - })) - assert.NoError(t, err) - assert.Equal(t, dyn.NilValue, vout) -} - -func TestMapByPatternOnNilValue(t *testing.T) { - var err error - _, err = dyn.MapByPattern(dyn.NilValue, dyn.NewPattern(dyn.AnyKey()), nil) - assert.ErrorContains(t, err, `expected a map at "", found nil`) - _, err = dyn.MapByPattern(dyn.NilValue, dyn.NewPattern(dyn.AnyIndex()), nil) - assert.ErrorContains(t, err, `expected a sequence at "", found nil`) -} - -func TestMapByPatternOnMap(t *testing.T) { - vin := dyn.V(map[string]dyn.Value{ - "a": dyn.V(map[string]dyn.Value{ - "b": dyn.V(42), - }), - "b": dyn.V(map[string]dyn.Value{ - "c": dyn.V(43), - }), - }) - - var err error - - // Expect an error if the pattern structure doesn't match the value structure. - _, err = dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyKey(), dyn.Index(0)), nil) - assert.ErrorContains(t, err, `expected a sequence to index`) - - // Apply function to pattern "*.b". - vout, err := dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyKey(), dyn.Key("b")), func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - assert.Equal(t, dyn.NewPath(dyn.Key("a"), dyn.Key("b")), p) - assert.Equal(t, dyn.V(42), v) - return dyn.V(44), nil - }) - assert.NoError(t, err) - assert.Equal(t, map[string]any{ - "a": map[string]any{ - "b": 44, - }, - "b": map[string]any{ - "c": 43, - }, - }, vout.AsAny()) -} - -func TestMapByPatternOnMapWithoutMatch(t *testing.T) { - vin := dyn.V(map[string]dyn.Value{ - "a": dyn.V(map[string]dyn.Value{ - "b": dyn.V(42), - }), - "b": dyn.V(map[string]dyn.Value{ - "c": dyn.V(43), - }), - }) - - // Apply function to pattern "*.zzz". - vout, err := dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyKey(), dyn.Key("zzz")), nil) - assert.NoError(t, err) - assert.Equal(t, vin, vout) -} - -func TestMapByPatternOnSequence(t *testing.T) { - vin := dyn.V([]dyn.Value{ - dyn.V([]dyn.Value{ - dyn.V(42), - }), - dyn.V([]dyn.Value{ - dyn.V(43), - dyn.V(44), - }), - }) - - var err error - - // Expect an error if the pattern structure doesn't match the value structure. - _, err = dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyIndex(), dyn.Key("a")), nil) - assert.ErrorContains(t, err, `expected a map to index`) - - // Apply function to pattern "*.c". - vout, err := dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyIndex(), dyn.Index(1)), func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - assert.Equal(t, dyn.NewPath(dyn.Index(1), dyn.Index(1)), p) - assert.Equal(t, dyn.V(44), v) - return dyn.V(45), nil - }) - assert.NoError(t, err) - assert.Equal(t, []any{ - []any{42}, - []any{43, 45}, - }, vout.AsAny()) -} - -func TestMapByPatternWildcardSkipsNonMatchingSiblings(t *testing.T) { - tests := []struct { - name string - vin dyn.Value - pattern dyn.Pattern - want any - visited []string - }{ - { - name: "nil sibling under any key", - vin: dyn.V(map[string]dyn.Value{ - "a": dyn.NilValue, - "b": dyn.V(map[string]dyn.Value{"x": dyn.V(1)}), - }), - pattern: dyn.NewPattern(dyn.AnyKey(), dyn.Key("x")), - want: map[string]any{ - "a": nil, - "b": map[string]any{"x": 42}, - }, - visited: []string{"b.x"}, - }, - { - name: "nil sequence sibling under any key", - vin: dyn.V(map[string]dyn.Value{ - "a": dyn.V(map[string]dyn.Value{"tasks": dyn.NilValue}), - "b": dyn.V(map[string]dyn.Value{"tasks": dyn.V([]dyn.Value{dyn.V(1)})}), - }), - pattern: dyn.NewPattern(dyn.AnyKey(), dyn.Key("tasks"), dyn.AnyIndex()), - want: map[string]any{ - "a": map[string]any{"tasks": nil}, - "b": map[string]any{"tasks": []any{42}}, - }, - visited: []string{"b.tasks[0]"}, - }, - { - name: "wrong kind sibling under any key", - vin: dyn.V(map[string]dyn.Value{ - "a": dyn.V(map[string]dyn.Value{"tasks": dyn.V("oops")}), - "b": dyn.V(map[string]dyn.Value{"tasks": dyn.V([]dyn.Value{dyn.V(1)})}), - }), - pattern: dyn.NewPattern(dyn.AnyKey(), dyn.Key("tasks"), dyn.AnyIndex()), - want: map[string]any{ - "a": map[string]any{"tasks": "oops"}, - "b": map[string]any{"tasks": []any{42}}, - }, - visited: []string{"b.tasks[0]"}, - }, - { - name: "nil element under any index", - vin: dyn.V([]dyn.Value{ - dyn.NilValue, - dyn.V(map[string]dyn.Value{"x": dyn.V(1)}), - }), - pattern: dyn.NewPattern(dyn.AnyIndex(), dyn.Key("x")), - want: []any{ - nil, - map[string]any{"x": 42}, - }, - visited: []string{"[1].x"}, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - var visited []string - vout, err := dyn.MapByPattern(tc.vin, tc.pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - visited = append(visited, p.String()) - return dyn.V(42), nil - }) - assert.NoError(t, err) - assert.Equal(t, tc.want, vout.AsAny()) - assert.Equal(t, tc.visited, visited) - }) - } -} - -func TestMapByPatternWildcardPropagatesMapFuncError(t *testing.T) { - vin := dyn.V(map[string]dyn.Value{ - "a": dyn.V(map[string]dyn.Value{"x": dyn.V(1)}), - }) - - ref := errors.New("error") - _, err := dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyKey(), dyn.Key("x")), func(_ dyn.Path, v dyn.Value) (dyn.Value, error) { - return dyn.InvalidValue, ref - }) - assert.ErrorIs(t, err, ref) -} - -func TestMapByPatternOnSequenceWithoutMatch(t *testing.T) { - vin := dyn.V([]dyn.Value{ - dyn.V([]dyn.Value{ - dyn.V(42), - }), - dyn.V([]dyn.Value{ - dyn.V(43), - dyn.V(44), - }), - }) - - // Apply function to pattern "*.zzz". - vout, err := dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyIndex(), dyn.Index(42)), nil) - assert.NoError(t, err) - assert.Equal(t, vin, vout) -} diff --git a/libs/dyn/visit_set.go b/libs/dyn/visit_set.go deleted file mode 100644 index 1b3c7f1b661..00000000000 --- a/libs/dyn/visit_set.go +++ /dev/null @@ -1,77 +0,0 @@ -package dyn - -import ( - "fmt" - "slices" -) - -// Set assigns a new value at the specified path in the specified value. -// It is identical to [SetByPath], except that it takes a string path instead of a [Path]. -func Set(v Value, path string, nv Value) (Value, error) { - p, err := NewPathFromString(path) - if err != nil { - return InvalidValue, err - } - return SetByPath(v, p, nv) -} - -// SetByPath assigns the given value at the specified path in the specified value. -// If successful, it returns the new value with all intermediate values copied and updated. -// If the path doesn't exist, it returns InvalidValue and an error. -func SetByPath(v Value, p Path, nv Value) (Value, error) { - lp := len(p) - if lp == 0 { - return nv, nil - } - - component := p[lp-1] - p = p[:lp-1] - - return visit(v, EmptyPath, NewPatternFromPath(p), visitOptions{ - fn: func(prefix Path, v Value) (Value, error) { - path := append(prefix, component) - - switch { - case component.isKey(): - // Expect a map to be set if this is a key. - m, ok := v.AsMap() - if !ok { - return InvalidValue, fmt.Errorf("expected a map to index %q, found %s", path, v.Kind()) - } - - // Return an updated map value. - m = m.Clone() - m.SetLoc(component.key, nil, nv) - return Value{ - v: m, - k: KindMap, - l: v.l, - }, nil - - case component.isIndex(): - // Expect a sequence to be set if this is an index. - s, ok := v.AsSequence() - if !ok { - return InvalidValue, fmt.Errorf("expected a sequence to index %q, found %s", path, v.Kind()) - } - - // Lookup current value in the sequence. - if component.index < 0 || component.index >= len(s) { - return InvalidValue, indexOutOfBoundsError{prefix} - } - - // Return an updated sequence value. - s = slices.Clone(s) - s[component.index] = nv - return Value{ - v: s, - k: KindSequence, - l: v.l, - }, nil - - default: - panic("invalid component") - } - }, - }) -} diff --git a/libs/dyn/visit_set_test.go b/libs/dyn/visit_set_test.go deleted file mode 100644 index b3847158757..00000000000 --- a/libs/dyn/visit_set_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package dyn_test - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestSetWithEmptyPath(t *testing.T) { - // An empty path means to return the value itself. - vin := dyn.V(42) - vout, err := dyn.SetByPath(dyn.InvalidValue, dyn.EmptyPath, vin) - assert.NoError(t, err) - assert.Equal(t, vin, vout) -} - -func TestSetOnNilValue(t *testing.T) { - var err error - _, err = dyn.SetByPath(dyn.NilValue, dyn.NewPath(dyn.Key("foo")), dyn.V(42)) - assert.ErrorContains(t, err, `expected a map to index "foo", found nil`) - _, err = dyn.SetByPath(dyn.NilValue, dyn.NewPath(dyn.Index(42)), dyn.V(42)) - assert.ErrorContains(t, err, `expected a sequence to index "[42]", found nil`) -} - -func TestSetOnMap(t *testing.T) { - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V(42), - "bar": dyn.V(43), - }) - - var err error - - _, err = dyn.SetByPath(vin, dyn.NewPath(dyn.Index(42)), dyn.V(42)) - assert.ErrorContains(t, err, `expected a sequence to index "[42]", found map`) - - // Note: in the test cases below we implicitly test that the original - // value is not modified as we repeatedly set values on it. - - vfoo, err := dyn.SetByPath(vin, dyn.NewPath(dyn.Key("foo")), dyn.V(44)) - assert.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": 44, - "bar": 43, - }, vfoo.AsAny()) - - vbar, err := dyn.SetByPath(vin, dyn.NewPath(dyn.Key("bar")), dyn.V(45)) - assert.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": 42, - "bar": 45, - }, vbar.AsAny()) - - vbaz, err := dyn.SetByPath(vin, dyn.NewPath(dyn.Key("baz")), dyn.V(46)) - assert.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": 42, - "bar": 43, - "baz": 46, - }, vbaz.AsAny()) -} - -func TestSetOnSequence(t *testing.T) { - vin := dyn.V([]dyn.Value{ - dyn.V(42), - dyn.V(43), - }) - - var err error - - _, err = dyn.SetByPath(vin, dyn.NewPath(dyn.Key("foo")), dyn.V(42)) - assert.ErrorContains(t, err, `expected a map to index "foo", found sequence`) - - // It is not allowed to set a value at an index that is out of bounds. - _, err = dyn.SetByPath(vin, dyn.NewPath(dyn.Index(-1)), dyn.V(42)) - assert.True(t, dyn.IsIndexOutOfBoundsError(err)) - _, err = dyn.SetByPath(vin, dyn.NewPath(dyn.Index(2)), dyn.V(42)) - assert.True(t, dyn.IsIndexOutOfBoundsError(err)) - - // Note: in the test cases below we implicitly test that the original - // value is not modified as we repeatedly set values on it. - - v0, err := dyn.SetByPath(vin, dyn.NewPath(dyn.Index(0)), dyn.V(44)) - assert.NoError(t, err) - assert.Equal(t, []any{44, 43}, v0.AsAny()) - - v1, err := dyn.SetByPath(vin, dyn.NewPath(dyn.Index(1)), dyn.V(45)) - assert.NoError(t, err) - assert.Equal(t, []any{42, 45}, v1.AsAny()) -} diff --git a/libs/dyn/visit_test.go b/libs/dyn/visit_test.go deleted file mode 100644 index b54e6ad37c7..00000000000 --- a/libs/dyn/visit_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package dyn_test - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestVisitCallbackPathCopy(t *testing.T) { - vin := dyn.V(map[string]dyn.Value{ - "foo": dyn.V(42), - "bar": dyn.V(43), - }) - - var paths []dyn.Path - - // The callback should receive a copy of the path. - // If the same underlying value is used, all collected paths will be the same. - // This test uses `MapByPattern` to collect all paths in the map. - // Visit itself doesn't have public functions and we exclusively use black-box testing for this package. - _, _ = dyn.MapByPattern(vin, dyn.NewPattern(dyn.AnyKey()), func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - paths = append(paths, p) - return v, nil - }) - - // Verify that the paths retained their original values. - var strings []string - for _, p := range paths { - strings = append(strings, p.String()) - } - assert.ElementsMatch(t, strings, []string{ - "foo", - "bar", - }) -} diff --git a/libs/dyn/walk.go b/libs/dyn/walk.go deleted file mode 100644 index 08123cf85e9..00000000000 --- a/libs/dyn/walk.go +++ /dev/null @@ -1,95 +0,0 @@ -package dyn - -import "errors" - -// WalkValueFunc is the type of the function called by Walk to traverse the configuration tree. -type WalkValueFunc func(p Path, v Value) (Value, error) - -// ErrDrop may be returned by WalkValueFunc to remove a value from the subtree. -var ErrDrop = errors.New("drop value from subtree") - -// ErrSkip may be returned by WalkValueFunc to skip traversal of a subtree. -var ErrSkip = errors.New("skip traversal of subtree") - -// Walk walks the configuration tree and calls the given function on each node. -// The callback may return ErrDrop to remove a value from the subtree. -// The callback may return ErrSkip to skip traversal of a subtree. -// If the callback returns another error, the walk is aborted, and the error is returned. -func Walk(v Value, fn func(p Path, v Value) (Value, error)) (Value, error) { - return walk(v, EmptyPath, fn) -} - -// Unexported counterpart to Walk. -// It carries the path leading up to the current node, -// such that it can be passed to the WalkValueFunc. -func walk(v Value, p Path, fn func(p Path, v Value) (Value, error)) (Value, error) { - v, err := fn(p, v) - if err != nil { - if errors.Is(err, ErrSkip) { - return v, nil - } - return InvalidValue, err - } - - switch v.Kind() { - case KindMap: - m := v.MustMap() - out := newMappingWithSize(m.Len()) - for _, pair := range m.Pairs() { - pk := pair.Key - pv := pair.Value - nv, err := walk(pv, append(p, Key(pk.MustString())), fn) - if errors.Is(err, ErrDrop) { - continue - } - if err != nil { - return InvalidValue, err - } - out.SetLoc(pk.MustString(), pk.Locations(), nv) - } - v.v = out - case KindSequence: - s := v.MustSequence() - out := make([]Value, 0, len(s)) - for i := range s { - nv, err := walk(s[i], append(p, Index(i)), fn) - if errors.Is(err, ErrDrop) { - continue - } - if err != nil { - return InvalidValue, err - } - out = append(out, nv) - } - v.v = out - default: - // Other kinds are not walkable. - // This was a leaf node. - } - - return v, nil -} - -// CollectLeafPaths traverses the value and returns all paths (as dot notation strings) to leaf nodes (non-map, non-sequence). -// The return value is not ordered. -func CollectLeafPaths(v Value) []string { - var paths []string - - Walk(v, func(p Path, v Value) (Value, error) { //nolint:errcheck - if len(p) == 0 { - return v, nil - } - - switch v.Kind() { - case KindMap, KindSequence: - // Ignore internal nodes. - default: - // This was a leaf node. - paths = append(paths, p.String()) - } - - return v, nil - }) - - return paths -} diff --git a/libs/dyn/walk_read_only.go b/libs/dyn/walk_read_only.go deleted file mode 100644 index 8f8d15e06ea..00000000000 --- a/libs/dyn/walk_read_only.go +++ /dev/null @@ -1,46 +0,0 @@ -package dyn - -import "errors" - -// WalkReadOnly walks the configuration tree in readonly mode and calls the given function on each node. -// The callback may return ErrSkip to skip traversal of a subtree. -// If the callback returns another error, the walk is aborted, and the error is returned. -func WalkReadOnly(v Value, fn func(p Path, v Value) error) error { - return walkReadOnly(v, EmptyPath, fn) -} - -// Unexported counterpart to WalkReadOnly. -// It carries the path leading up to the current node, -// such that it can be passed to the callback function. -func walkReadOnly(v Value, p Path, fn func(p Path, v Value) error) error { - if err := fn(p, v); err != nil { - if errors.Is(err, ErrSkip) { - return nil - } - return err - } - - switch v.Kind() { - case KindMap: - m := v.MustMap() - for _, pair := range m.Pairs() { - pk := pair.Key - pv := pair.Value - if err := walkReadOnly(pv, append(p, Key(pk.MustString())), fn); err != nil { - return err - } - } - case KindSequence: - s := v.MustSequence() - for i := range s { - if err := walkReadOnly(s[i], append(p, Index(i)), fn); err != nil { - return err - } - } - default: - // Other kinds are not walkable. - // This was a leaf node. - } - - return nil -} diff --git a/libs/dyn/walk_read_only_test.go b/libs/dyn/walk_read_only_test.go deleted file mode 100644 index f6df5344d8c..00000000000 --- a/libs/dyn/walk_read_only_test.go +++ /dev/null @@ -1,245 +0,0 @@ -package dyn_test - -import ( - "errors" - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestWalkReadOnly(t *testing.T) { - tests := []struct { - name string - input dyn.Value - expectedPaths []dyn.Path - expectedValues []dyn.Value - }{ - { - name: "simple map", - input: dyn.V(dyn.NewMappingFromPairs( - []dyn.Pair{ - {Key: dyn.V("a"), Value: dyn.V("1")}, - {Key: dyn.V("b"), Value: dyn.V("2")}, - }, - )), - expectedPaths: []dyn.Path{ - dyn.EmptyPath, - {dyn.Key("a")}, - {dyn.Key("b")}, - }, - expectedValues: []dyn.Value{ - dyn.V(dyn.NewMappingFromPairs( - []dyn.Pair{ - {Key: dyn.V("a"), Value: dyn.V("1")}, - {Key: dyn.V("b"), Value: dyn.V("2")}, - }, - )), - dyn.V("1"), - dyn.V("2"), - }, - }, - { - name: "nested map", - input: dyn.V(map[string]dyn.Value{ - "a": dyn.V(map[string]dyn.Value{ - "b": dyn.V("1"), - }), - }), - expectedPaths: []dyn.Path{ - dyn.EmptyPath, - {dyn.Key("a")}, - {dyn.Key("a"), dyn.Key("b")}, - }, - expectedValues: []dyn.Value{ - dyn.V(map[string]dyn.Value{ - "a": dyn.V(map[string]dyn.Value{ - "b": dyn.V("1"), - }), - }), - dyn.V(map[string]dyn.Value{ - "b": dyn.V("1"), - }), - dyn.V("1"), - }, - }, - { - name: "sequence", - input: dyn.V([]dyn.Value{ - dyn.V("1"), - dyn.V("2"), - }), - expectedPaths: []dyn.Path{ - dyn.EmptyPath, - {dyn.Index(0)}, - {dyn.Index(1)}, - }, - expectedValues: []dyn.Value{ - dyn.V([]dyn.Value{ - dyn.V("1"), - dyn.V("2"), - }), - dyn.V("1"), - dyn.V("2"), - }, - }, - { - name: "nested sequence", - input: dyn.V([]dyn.Value{ - dyn.V([]dyn.Value{ - dyn.V("1"), - }), - }), - expectedPaths: []dyn.Path{ - dyn.EmptyPath, - {dyn.Index(0)}, - {dyn.Index(0), dyn.Index(0)}, - }, - expectedValues: []dyn.Value{ - dyn.V([]dyn.Value{ - dyn.V([]dyn.Value{ - dyn.V("1"), - }), - }), - dyn.V([]dyn.Value{ - dyn.V("1"), - }), - dyn.V("1"), - }, - }, - { - name: "complex structure", - input: dyn.V(map[string]dyn.Value{ - "a": dyn.V([]dyn.Value{ - dyn.V(map[string]dyn.Value{ - "b": dyn.V("1"), - }), - }), - }), - expectedPaths: []dyn.Path{ - dyn.EmptyPath, - {dyn.Key("a")}, - {dyn.Key("a"), dyn.Index(0)}, - {dyn.Key("a"), dyn.Index(0), dyn.Key("b")}, - }, - expectedValues: []dyn.Value{ - dyn.V(map[string]dyn.Value{ - "a": dyn.V([]dyn.Value{ - dyn.V(map[string]dyn.Value{ - "b": dyn.V("1"), - }), - }), - }), - dyn.V([]dyn.Value{ - dyn.V(map[string]dyn.Value{ - "b": dyn.V("1"), - }), - }), - dyn.V(map[string]dyn.Value{ - "b": dyn.V("1"), - }), - dyn.V("1"), - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - visitedPaths := make([]dyn.Path, 0, len(tt.expectedPaths)) - visitedValues := make([]dyn.Value, 0, len(tt.expectedValues)) - - err := dyn.WalkReadOnly(tt.input, func(p dyn.Path, v dyn.Value) error { - visitedPaths = append(visitedPaths, p) - visitedValues = append(visitedValues, v) - return nil - }) - assert.NoError(t, err) - assert.Equal(t, tt.expectedPaths, visitedPaths) - assert.Equal(t, tt.expectedValues, visitedValues) - }) - } -} - -func TestWalkReadOnly_Error(t *testing.T) { - testErr := errors.New("test error") - input := dyn.V(map[string]dyn.Value{ - "a": dyn.V("1"), - }) - - err := dyn.WalkReadOnly(input, func(p dyn.Path, v dyn.Value) error { - if p.Equal(dyn.Path{dyn.Key("a")}) { - return testErr - } - return nil - }) - - assert.Equal(t, err, testErr) -} - -func TestWalkReadOnly_SkipPaths(t *testing.T) { - va := dyn.V(dyn.NewMappingFromPairs( - []dyn.Pair{ - {Key: dyn.V("b"), Value: dyn.V("1")}, - {Key: dyn.V("c"), Value: dyn.V("2")}, - }, - )) - - vd := dyn.V(dyn.NewMappingFromPairs( - []dyn.Pair{ - {Key: dyn.V("e"), Value: dyn.V("3")}, - }, - )) - - input := dyn.V(dyn.NewMappingFromPairs( - []dyn.Pair{ - { - Key: dyn.V("a"), - Value: va, - }, - { - Key: dyn.V("d"), - Value: vd, - }, - { - Key: dyn.V("f"), - Value: dyn.V("4"), - }, - }, - )) - - skipPaths := map[string]bool{ - "a.b": true, - "d": true, - } - - var visitedPaths []dyn.Path - var visitedValues []dyn.Value - - err := dyn.WalkReadOnly(input, func(p dyn.Path, v dyn.Value) error { - _, ok := skipPaths[p.String()] - if ok { - return dyn.ErrSkip - } - - visitedPaths = append(visitedPaths, p) - visitedValues = append(visitedValues, v) - return nil - }) - assert.NoError(t, err) - - expectedPaths := []dyn.Path{ - dyn.EmptyPath, - {dyn.Key("a")}, - {dyn.Key("a"), dyn.Key("c")}, - {dyn.Key("f")}, - } - expectedValues := []dyn.Value{ - input, - va, - dyn.V("2"), - dyn.V("4"), - } - - assert.Equal(t, expectedPaths, visitedPaths) - assert.Equal(t, expectedValues, visitedValues) -} diff --git a/libs/dyn/walk_test.go b/libs/dyn/walk_test.go deleted file mode 100644 index f59683f0381..00000000000 --- a/libs/dyn/walk_test.go +++ /dev/null @@ -1,269 +0,0 @@ -package dyn_test - -import ( - "errors" - "testing" - - . "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// Return values for specific paths. -type walkReturn struct { - path Path - - // Return values. - fn func(Value) Value - err error -} - -// Track the calls to the callback. -type walkCall struct { - path Path - value Value -} - -// Track the calls to the callback. -type walkCallTracker struct { - returns []walkReturn - calls []walkCall -} - -func (w *walkCallTracker) on(path string, fn func(Value) Value, err error) { - w.returns = append(w.returns, walkReturn{MustPathFromString(path), fn, err}) -} - -func (w *walkCallTracker) returnSkip(path string) { - w.on(path, func(v Value) Value { return v }, ErrSkip) -} - -func (w *walkCallTracker) returnDrop(path string) { - w.on(path, func(v Value) Value { return InvalidValue }, ErrDrop) -} - -func (w *walkCallTracker) track(p Path, v Value) (Value, error) { - w.calls = append(w.calls, walkCall{p, v}) - - // Look for matching return. - for _, r := range w.returns { - if p.Equal(r.path) { - return r.fn(v), r.err - } - } - - return v, nil -} - -func TestWalkEmpty(t *testing.T) { - var tracker walkCallTracker - - value := V(nil) - out, err := Walk(value, tracker.track) - require.NoError(t, err) - assert.Equal(t, value, out) - - // The callback should have been called once. - assert.Len(t, tracker.calls, 1) - - // The call should have been made with the empty path. - assert.Equal(t, EmptyPath, tracker.calls[0].path) - - // The value should be the same as the input. - assert.Equal(t, value, tracker.calls[0].value) -} - -func TestWalkMapSkip(t *testing.T) { - var tracker walkCallTracker - - // Skip traversal of the root value. - tracker.returnSkip(".") - - value := V(map[string]Value{ - "key": V("value"), - }) - out, err := Walk(value, tracker.track) - require.NoError(t, err) - assert.Equal( - t, - V(map[string]Value{ - "key": V("value"), - }), - out, - ) - - // The callback should have been called once. - assert.Len(t, tracker.calls, 1) - - // The call should have been made with the empty path. - assert.Equal(t, EmptyPath, tracker.calls[0].path) - - // The value should be the same as the input. - assert.Equal(t, value, tracker.calls[0].value) -} - -func TestWalkMapDrop(t *testing.T) { - var tracker walkCallTracker - - // Drop the value at key "foo". - tracker.returnDrop(".foo") - - value := V(map[string]Value{ - "foo": V("bar"), - "bar": V("baz"), - }) - out, err := Walk(value, tracker.track) - require.NoError(t, err) - assert.Equal( - t, - V(map[string]Value{ - "bar": V("baz"), - }), - out, - ) - - // The callback should have been called for the root and every key in the map. - assert.Len(t, tracker.calls, 3) - - // Calls 2 and 3 have been made for the keys in the map. - assert.ElementsMatch(t, - []Path{ - tracker.calls[1].path, - tracker.calls[2].path, - }, []Path{ - MustPathFromString(".foo"), - MustPathFromString(".bar"), - }) -} - -func TestWalkMapError(t *testing.T) { - var tracker walkCallTracker - - // Return an error from the callback for key "foo". - cerr := errors.New("error!") - tracker.on(".foo", func(v Value) Value { return v }, cerr) - - value := V(map[string]Value{ - "foo": V("bar"), - }) - out, err := Walk(value, tracker.track) - assert.Equal(t, cerr, err) - assert.Equal(t, InvalidValue, out) - - // The callback should have been called twice. - assert.Len(t, tracker.calls, 2) - - // The second call was for the value at key "foo". - assert.Equal(t, MustPathFromString(".foo"), tracker.calls[1].path) -} - -func TestWalkSequenceSkip(t *testing.T) { - var tracker walkCallTracker - - // Skip traversal of the root value. - tracker.returnSkip(".") - - value := V([]Value{ - V("foo"), - V("bar"), - }) - out, err := Walk(value, tracker.track) - require.NoError(t, err) - assert.Equal( - t, - V([]Value{ - V("foo"), - V("bar"), - }), - out, - ) - - // The callback should have been called once. - assert.Len(t, tracker.calls, 1) - - // The call should have been made with the empty path. - assert.Equal(t, EmptyPath, tracker.calls[0].path) - - // The value should be the same as the input. - assert.Equal(t, value, tracker.calls[0].value) -} - -func TestWalkSequenceDrop(t *testing.T) { - var tracker walkCallTracker - - // Drop the value at index 1. - tracker.returnDrop(".[1]") - - value := V([]Value{ - V("foo"), - V("bar"), - V("baz"), - }) - out, err := Walk(value, tracker.track) - require.NoError(t, err) - assert.Equal( - t, - V([]Value{ - V("foo"), - V("baz"), - }), - out, - ) - - // The callback should have been called for the root and every value in the sequence. - assert.Len(t, tracker.calls, 4) - - // The second call was for the value at index 0. - assert.Equal(t, MustPathFromString(".[0]"), tracker.calls[1].path) - assert.Equal(t, V("foo"), tracker.calls[1].value) - - // The third call was for the value at index 1. - assert.Equal(t, MustPathFromString(".[1]"), tracker.calls[2].path) - assert.Equal(t, V("bar"), tracker.calls[2].value) - - // The fourth call was for the value at index 2. - assert.Equal(t, MustPathFromString(".[2]"), tracker.calls[3].path) - assert.Equal(t, V("baz"), tracker.calls[3].value) -} - -func TestWalkSequenceError(t *testing.T) { - var tracker walkCallTracker - - // Return an error from the callback for index 1. - cerr := errors.New("error!") - tracker.on(".[1]", func(v Value) Value { return v }, cerr) - - value := V([]Value{ - V("foo"), - V("bar"), - }) - out, err := Walk(value, tracker.track) - assert.Equal(t, cerr, err) - assert.Equal(t, InvalidValue, out) - - // The callback should have been called three times. - assert.Len(t, tracker.calls, 3) - - // The second call was for the value at index 0. - assert.Equal(t, MustPathFromString(".[0]"), tracker.calls[1].path) - assert.Equal(t, V("foo"), tracker.calls[1].value) - - // The third call was for the value at index 1. - assert.Equal(t, MustPathFromString(".[1]"), tracker.calls[2].path) - assert.Equal(t, V("bar"), tracker.calls[2].value) -} - -func TestCollectLeafPaths(t *testing.T) { - v := V(map[string]Value{ - "a": V(1), - "b": V(map[string]Value{ - "c": V(2), - "d": V(map[string]Value{ - "e": V(3), - }), - }), - "f": V([]Value{V(4), V(5)}), - }) - paths := CollectLeafPaths(v) - assert.ElementsMatch(t, []string{"a", "b.c", "b.d.e", "f[0]", "f[1]"}, paths) -} diff --git a/libs/dyn/yamlloader/loader.go b/libs/dyn/yamlloader/loader.go deleted file mode 100644 index aebbf0883e9..00000000000 --- a/libs/dyn/yamlloader/loader.go +++ /dev/null @@ -1,291 +0,0 @@ -package yamlloader - -import ( - "fmt" - "math" - "strconv" - "strings" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "go.yaml.in/yaml/v3" -) - -// LocationError is an error with a YAML source location that can be displayed -// to the user with a file path, line, and column number. -type LocationError struct { - Loc diag.Location - Summary string -} - -func (e *LocationError) Error() string { - return fmt.Sprintf("yaml (%s): %s", e.Loc, e.Summary) -} - -type loader struct { - path string - - // Aliases currently being expanded. A self-referential anchor (e.g. - // "a: &x {b: *x}") makes the yaml.v3 node graph cyclic; without this - // bookkeeping, expansion would recurse until stack overflow. - activeAliases map[*yaml.Node]bool -} - -func errorf(loc diag.Location, format string, args ...any) error { - return fmt.Errorf("yaml (%s): %s", loc, fmt.Sprintf(format, args...)) -} - -func newLoader(path string) *loader { - return &loader{ - path: path, - activeAliases: make(map[*yaml.Node]bool), - } -} - -func (d *loader) location(node *yaml.Node) diag.Location { - return diag.Location{ - File: d.path, - Line: node.Line, - Column: node.Column, - } -} - -func (d *loader) load(node *yaml.Node) (dyn.Value, error) { - loc := diag.Location{ - File: d.path, - Line: node.Line, - Column: node.Column, - } - - var value dyn.Value - var err error - - switch node.Kind { - case yaml.DocumentNode: - value, err = d.loadDocument(node, loc) - case yaml.SequenceNode: - value, err = d.loadSequence(node, loc) - case yaml.MappingNode: - value, err = d.loadMapping(node, loc) - case yaml.ScalarNode: - value, err = d.loadScalar(node, loc) - case yaml.AliasNode: - value, err = d.loadAlias(node, loc) - default: - return dyn.InvalidValue, errorf(loc, "unknown node kind: %v", node.Kind) - } - - if err != nil { - return value, err - } - - // Mark value as anchor if needed. - // If this node doesn't map to a type, we don't need to warn about it. - if node.Anchor != "" { - value = value.MarkAnchor() - } - - return value, nil -} - -func (d *loader) loadDocument(node *yaml.Node, loc diag.Location) (dyn.Value, error) { - return d.load(node.Content[0]) -} - -func (d *loader) loadSequence(node *yaml.Node, loc diag.Location) (dyn.Value, error) { - acc := make([]dyn.Value, len(node.Content)) - for i, n := range node.Content { - v, err := d.load(n) - if err != nil { - return dyn.InvalidValue, err - } - - acc[i] = v - } - - return dyn.NewValue(acc, []diag.Location{loc}), nil -} - -func (d *loader) loadMapping(node *yaml.Node, loc diag.Location) (dyn.Value, error) { - var merge *yaml.Node - - acc := dyn.NewMapping() - for i := 0; i < len(node.Content); i += 2 { - key := node.Content[i] - val := node.Content[i+1] - - // Assert that keys are strings - if key.Kind != yaml.ScalarNode { - return dyn.InvalidValue, errorf(loc, "key is not a scalar") - } - - st := key.ShortTag() - switch st { - case "!!str": - // OK - case "!!null": - // A literal unquoted "null" is treated as a null value by the YAML parser. - // However, when used as a key, it is treated as the string "null". - case "!!merge": - if merge != nil { - // The YAML merge key spec allows a single '<<' key per mapping. - // To merge multiple maps, use a sequence: '<<: [*anchor1, *anchor2]'. - return dyn.InvalidValue, &LocationError{ - Loc: d.location(key), - Summary: "duplicate YAML merge key ('<<') is not allowed; to merge multiple maps, use a sequence: '<<: [*anchor1, *anchor2]'", - } - } - merge = val - continue - default: - return dyn.InvalidValue, errorf(loc, "invalid key tag: %v", st) - } - - loc := []diag.Location{{ - File: d.path, - Line: key.Line, - Column: key.Column, - }} - - v, err := d.load(val) - if err != nil { - return dyn.InvalidValue, err - } - - acc.SetLoc(key.Value, loc, v) - } - - if merge == nil { - return dyn.NewValue(acc, []diag.Location{loc}), nil - } - - // Build location for the merge node. - mloc := d.location(merge) - merr := errorf(mloc, "map merge requires map or sequence of maps as the value") - - // Flatten the merge node into a slice of nodes. - // It can be either a single node or a sequence of nodes. - var mnodes []*yaml.Node - switch merge.Kind { - case yaml.SequenceNode: - mnodes = merge.Content - case yaml.AliasNode: - mnodes = []*yaml.Node{merge} - default: - return dyn.NilValue, merr - } - - // Build a sequence of values to merge. - // The entries that we already accumulated have precedence. - var seq []dyn.Mapping - for _, n := range mnodes { - v, err := d.load(n) - if err != nil { - return dyn.InvalidValue, err - } - m, ok := v.AsMap() - if !ok { - return dyn.NilValue, merr - } - seq = append(seq, m) - } - - // Append the accumulated entries to the sequence. - seq = append(seq, acc) - out := dyn.NewMapping() - for _, m := range seq { - out.Merge(m) - } - - return dyn.NewValue(out, []diag.Location{loc}), nil -} - -func newIntValue(i64 int64, loc diag.Location) dyn.Value { - // Use regular int type instead of int64 if possible. - if i64 >= math.MinInt32 && i64 <= math.MaxInt32 { - return dyn.NewValue(int(i64), []diag.Location{loc}) - } - return dyn.NewValue(i64, []diag.Location{loc}) -} - -func (d *loader) loadScalar(node *yaml.Node, loc diag.Location) (dyn.Value, error) { - st := node.ShortTag() - switch st { - case "!!str": - return dyn.NewValue(node.Value, []diag.Location{loc}), nil - case "!!bool": - switch strings.ToLower(node.Value) { - case "true": - return dyn.NewValue(true, []diag.Location{loc}), nil - case "false": - return dyn.NewValue(false, []diag.Location{loc}), nil - default: - return dyn.InvalidValue, errorf(loc, "invalid bool value: %v", node.Value) - } - case "!!int": - // Try to parse the an integer value in base 10. - // We trim leading zeros to avoid octal parsing of the "0" prefix. - // See "testdata/spec_example_2.19.yml" for background. - i64, err := strconv.ParseInt(strings.TrimLeft(node.Value, "0"), 10, 64) - if err == nil { - return newIntValue(i64, loc), nil - } - // Let the [ParseInt] function figure out the base. - i64, err = strconv.ParseInt(node.Value, 0, 64) - if err == nil { - return newIntValue(i64, loc), nil - } - return dyn.InvalidValue, errorf(loc, "invalid int value: %v", node.Value) - case "!!float": - f64, err := strconv.ParseFloat(node.Value, 64) - if err != nil { - // Deal with infinity prefixes. - v := strings.ToLower(node.Value) - switch { - case strings.HasPrefix(v, "+"): - v = strings.TrimPrefix(v, "+") - f64 = math.Inf(1) - case strings.HasPrefix(v, "-"): - v = strings.TrimPrefix(v, "-") - f64 = math.Inf(-1) - default: - // No prefix. - f64 = math.Inf(1) - } - - // Deal with infinity and NaN values. - switch v { - case ".inf": - return dyn.NewValue(f64, []diag.Location{loc}), nil - case ".nan": - return dyn.NewValue(math.NaN(), []diag.Location{loc}), nil - } - - return dyn.InvalidValue, errorf(loc, "invalid float value: %v", node.Value) - } - return dyn.NewValue(f64, []diag.Location{loc}), nil - case "!!null": - return dyn.NewValue(nil, []diag.Location{loc}), nil - case "!!timestamp": - t, err := dyn.NewTime(node.Value) - if err == nil { - return dyn.NewValue(t, []diag.Location{loc}), nil - } - return dyn.InvalidValue, errorf(loc, "invalid timestamp value: %v", node.Value) - default: - return dyn.InvalidValue, errorf(loc, "unknown tag: %v", st) - } -} - -func (d *loader) loadAlias(node *yaml.Node, loc diag.Location) (dyn.Value, error) { - if d.activeAliases[node] { - return dyn.InvalidValue, errorf(loc, "cyclic reference to anchor %q", node.Value) - } - - // The same alias node may be reached again through another alias to an - // enclosing anchor, which is not a cycle. - d.activeAliases[node] = true - defer delete(d.activeAliases, node) - - return d.load(node.Alias) -} diff --git a/libs/dyn/yamlloader/yaml.go b/libs/dyn/yamlloader/yaml.go deleted file mode 100644 index 29570747ab1..00000000000 --- a/libs/dyn/yamlloader/yaml.go +++ /dev/null @@ -1,23 +0,0 @@ -package yamlloader - -import ( - "errors" - "io" - - "github.com/databricks/cli/libs/dyn" - "go.yaml.in/yaml/v3" -) - -func LoadYAML(path string, r io.Reader) (dyn.Value, error) { - var node yaml.Node - dec := yaml.NewDecoder(r) - err := dec.Decode(&node) - if err != nil { - if errors.Is(err, io.EOF) { - return dyn.NilValue, nil - } - return dyn.InvalidValue, err - } - - return newLoader(path).load(&node) -} diff --git a/libs/dyn/yamlloader/yaml_anchor_test.go b/libs/dyn/yamlloader/yaml_anchor_test.go deleted file mode 100644 index 00f4576a3e2..00000000000 --- a/libs/dyn/yamlloader/yaml_anchor_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package yamlloader_test - -import ( - "bytes" - "os" - "testing" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlloader" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestYAMLAnchor01(t *testing.T) { - file := "testdata/anchor_01.yml" - self := loadYAML(t, file) - assert.NotEqual(t, dyn.NilValue, self) - - assert.True(t, self.Get("defaults").IsAnchor()) - assert.False(t, self.Get("shirt1").IsAnchor()) - assert.False(t, self.Get("shirt2").IsAnchor()) - - pattern := self.Get("shirt1").Get("pattern") - assert.Equal(t, "striped", pattern.AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 8, Column: 12}, pattern.Location()) -} - -func TestYAMLAnchor02(t *testing.T) { - file := "testdata/anchor_02.yml" - self := loadYAML(t, file) - assert.NotEqual(t, dyn.NilValue, self) - - color := self.Get("shirt").Get("color") - assert.Equal(t, "red", color.AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 4, Column: 10}, color.Location()) - - primary := self.Get("shirt").Get("primary") - assert.Equal(t, "cotton", primary.AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 8, Column: 12}, primary.Location()) - - pattern := self.Get("shirt").Get("pattern") - assert.Equal(t, "striped", pattern.AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 13, Column: 12}, pattern.Location()) -} - -func TestYAMLAnchor03(t *testing.T) { - file := "testdata/anchor_03.yml" - self := loadYAML(t, file) - assert.NotEqual(t, dyn.NilValue, self) - - // Assert the override took place. - blue := self.Get("shirt").Get("color") - assert.Equal(t, "blue", blue.AsAny()) - assert.Equal(t, file, blue.Location().File) - assert.Equal(t, 10, blue.Location().Line) - assert.Equal(t, 10, blue.Location().Column) -} - -func TestYAMLAnchor04(t *testing.T) { - file := "testdata/anchor_04.yml" - self := loadYAML(t, file) - assert.NotEqual(t, dyn.NilValue, self) - - p1 := self.Get("person1").Get("address").Get("city") - assert.Equal(t, "San Francisco", p1.AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 4, Column: 9}, p1.Location()) - - p2 := self.Get("person2").Get("address").Get("city") - assert.Equal(t, "Los Angeles", p2.AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 16, Column: 11}, p2.Location()) -} - -func TestYAMLAnchor05(t *testing.T) { - file := "testdata/anchor_05.yml" - self := loadYAML(t, file) - assert.NotEqual(t, dyn.NilValue, self) - - features := self.Get("phone1").Get("features") - assert.Equal(t, "wifi", features.Index(0).AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 4, Column: 5}, features.Index(0).Location()) - assert.Equal(t, "bluetooth", features.Index(1).AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 5, Column: 5}, features.Index(1).Location()) -} - -func TestYAMLAnchor06(t *testing.T) { - file := "testdata/anchor_06.yml" - self := loadYAML(t, file) - assert.NotEqual(t, dyn.NilValue, self) - - greeting := self.Get("greeting1") - assert.Equal(t, "Hello, World!", greeting.AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 2, Column: 16}, greeting.Location()) -} - -func TestYAMLAnchor07(t *testing.T) { - file := "testdata/anchor_07.yml" - self := loadYAML(t, file) - assert.NotEqual(t, dyn.NilValue, self) - - name := self.Get("person1").Get("name") - assert.Equal(t, "Alice", name.AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 5, Column: 9}, name.Location()) - - age := self.Get("person1").Get("age") - assert.Equal(t, 25, age.AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 2, Column: 13}, age.Location()) -} - -func TestYAMLAnchor08(t *testing.T) { - file := "testdata/anchor_08.yml" - self := loadYAML(t, file) - assert.NotEqual(t, dyn.NilValue, self) - - username := self.Get("user1").Get("username") - assert.Equal(t, "user1", username.AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 5, Column: 13}, username.Location()) - - active := self.Get("user1").Get("active") - assert.Equal(t, true, active.AsAny()) - assert.Equal(t, diag.Location{File: file, Line: 2, Column: 11}, active.Location()) -} - -func TestYAMLAnchor09(t *testing.T) { - file := "testdata/anchor_09.yml" - input, err := os.ReadFile(file) - require.NoError(t, err) - - _, err = yamlloader.LoadYAML(file, bytes.NewBuffer(input)) - assert.ErrorContains(t, err, `cyclic reference to anchor "x"`) -} - -func TestYAMLAnchor10(t *testing.T) { - file := "testdata/anchor_10.yml" - self := loadYAML(t, file) - assert.NotEqual(t, dyn.NilValue, self) - - assert.Equal(t, 1, self.Get("use1").Get("value").AsAny()) - assert.Equal(t, 1, self.Get("use2").Get("value").AsAny()) -} diff --git a/libs/dyn/yamlloader/yaml_mix_test.go b/libs/dyn/yamlloader/yaml_mix_test.go deleted file mode 100644 index 307b93dbf3a..00000000000 --- a/libs/dyn/yamlloader/yaml_mix_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package yamlloader_test - -import ( - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" -) - -func TestYAMLMix01(t *testing.T) { - file := "testdata/mix_01.yml" - self := loadYAML(t, file) - assert.NotEqual(t, dyn.NilValue, self) - - assert.True(t, self.Get("base_address").IsAnchor()) - assert.False(t, self.Get("office_address").IsAnchor()) -} - -func TestYAMLMix02(t *testing.T) { - file := "testdata/mix_02.yml" - self := loadYAML(t, file) - assert.NotEqual(t, dyn.NilValue, self) - - assert.True(t, self.Get("base_colors").IsAnchor()) - assert.False(t, self.Get("theme").IsAnchor()) -} diff --git a/libs/dyn/yamlloader/yaml_spec_test.go b/libs/dyn/yamlloader/yaml_spec_test.go deleted file mode 100644 index 28ad2e7480c..00000000000 --- a/libs/dyn/yamlloader/yaml_spec_test.go +++ /dev/null @@ -1,824 +0,0 @@ -package yamlloader_test - -import ( - "bytes" - "math" - "os" - "testing" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynassert" - "github.com/databricks/cli/libs/dyn/yamlloader" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const NL = "\n" - -func loadExample(t *testing.T, file string) dyn.Value { - input, err := os.ReadFile(file) - require.NoError(t, err) - self, err := yamlloader.LoadYAML(file, bytes.NewBuffer(input)) - require.NoError(t, err) - return self -} - -func TestYAMLSpecExample_2_1(t *testing.T) { - file := "testdata/spec_example_2.1.yml" - self := loadExample(t, file) - - assert.Equal(t, dyn.NewValue( - []dyn.Value{ - dyn.NewValue("Mark McGwire", []diag.Location{{File: file, Line: 3, Column: 3}}), - dyn.NewValue("Sammy Sosa", []diag.Location{{File: file, Line: 4, Column: 3}}), - dyn.NewValue("Ken Griffey", []diag.Location{{File: file, Line: 5, Column: 3}}), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_2(t *testing.T) { - file := "testdata/spec_example_2.2.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "hr": dyn.NewValue(65, []diag.Location{{File: file, Line: 3, Column: 6}}), - "avg": dyn.NewValue(0.278, []diag.Location{{File: file, Line: 4, Column: 6}}), - "rbi": dyn.NewValue(147, []diag.Location{{File: file, Line: 5, Column: 6}}), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_3(t *testing.T) { - file := "testdata/spec_example_2.3.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "american": dyn.NewValue( - []dyn.Value{ - dyn.NewValue("Boston Red Sox", []diag.Location{{File: file, Line: 4, Column: 3}}), - dyn.NewValue("Detroit Tigers", []diag.Location{{File: file, Line: 5, Column: 3}}), - dyn.NewValue("New York Yankees", []diag.Location{{File: file, Line: 6, Column: 3}}), - }, - []diag.Location{{File: file, Line: 4, Column: 1}}, - ), - "national": dyn.NewValue( - []dyn.Value{ - dyn.NewValue("New York Mets", []diag.Location{{File: file, Line: 8, Column: 3}}), - dyn.NewValue("Chicago Cubs", []diag.Location{{File: file, Line: 9, Column: 3}}), - dyn.NewValue("Atlanta Braves", []diag.Location{{File: file, Line: 10, Column: 3}}), - }, - []diag.Location{{File: file, Line: 8, Column: 1}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_4(t *testing.T) { - file := "testdata/spec_example_2.4.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - []dyn.Value{ - dyn.NewValue( - map[string]dyn.Value{ - "name": dyn.NewValue("Mark McGwire", []diag.Location{{File: file, Line: 4, Column: 9}}), - "hr": dyn.NewValue(65, []diag.Location{{File: file, Line: 5, Column: 9}}), - "avg": dyn.NewValue(0.278, []diag.Location{{File: file, Line: 6, Column: 9}}), - }, - []diag.Location{{File: file, Line: 4, Column: 3}}, - ), - dyn.NewValue( - map[string]dyn.Value{ - "name": dyn.NewValue("Sammy Sosa", []diag.Location{{File: file, Line: 8, Column: 9}}), - "hr": dyn.NewValue(63, []diag.Location{{File: file, Line: 9, Column: 9}}), - "avg": dyn.NewValue(0.288, []diag.Location{{File: file, Line: 10, Column: 9}}), - }, - []diag.Location{{File: file, Line: 8, Column: 3}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_5(t *testing.T) { - file := "testdata/spec_example_2.5.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - []dyn.Value{ - dyn.NewValue( - []dyn.Value{ - dyn.NewValue("name", []diag.Location{{File: file, Line: 3, Column: 4}}), - dyn.NewValue("hr", []diag.Location{{File: file, Line: 3, Column: 18}}), - dyn.NewValue("avg", []diag.Location{{File: file, Line: 3, Column: 22}}), - }, - []diag.Location{{File: file, Line: 3, Column: 3}}, - ), - dyn.NewValue( - []dyn.Value{ - dyn.NewValue("Mark McGwire", []diag.Location{{File: file, Line: 4, Column: 4}}), - dyn.NewValue(65, []diag.Location{{File: file, Line: 4, Column: 18}}), - dyn.NewValue(0.278, []diag.Location{{File: file, Line: 4, Column: 22}}), - }, - []diag.Location{{File: file, Line: 4, Column: 3}}, - ), - dyn.NewValue( - []dyn.Value{ - dyn.NewValue("Sammy Sosa", []diag.Location{{File: file, Line: 5, Column: 4}}), - dyn.NewValue(63, []diag.Location{{File: file, Line: 5, Column: 18}}), - dyn.NewValue(0.288, []diag.Location{{File: file, Line: 5, Column: 22}}), - }, - []diag.Location{{File: file, Line: 5, Column: 3}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_6(t *testing.T) { - file := "testdata/spec_example_2.6.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "Mark McGwire": dyn.NewValue( - map[string]dyn.Value{ - "hr": dyn.NewValue(65, []diag.Location{{File: file, Line: 3, Column: 20}}), - "avg": dyn.NewValue(0.278, []diag.Location{{File: file, Line: 3, Column: 29}}), - }, - []diag.Location{{File: file, Line: 3, Column: 15}}, - ), - "Sammy Sosa": dyn.NewValue( - map[string]dyn.Value{ - "hr": dyn.NewValue(63, []diag.Location{{File: file, Line: 5, Column: 9}}), - "avg": dyn.NewValue(0.288, []diag.Location{{File: file, Line: 6, Column: 10}}), - }, - []diag.Location{{File: file, Line: 4, Column: 13}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_7(t *testing.T) { - file := "testdata/spec_example_2.7.yml" - self := loadExample(t, file) - - // Note: we do not support multiple documents in a single YAML file. - - dynassert.Equal(t, dyn.NewValue( - []dyn.Value{ - dyn.NewValue( - "Mark McGwire", - []diag.Location{{File: file, Line: 5, Column: 3}}, - ), - dyn.NewValue( - "Sammy Sosa", - []diag.Location{{File: file, Line: 6, Column: 3}}, - ), - dyn.NewValue( - "Ken Griffey", - []diag.Location{{File: file, Line: 7, Column: 3}}, - ), - }, - []diag.Location{{File: file, Line: 5, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_8(t *testing.T) { - file := "testdata/spec_example_2.8.yml" - self := loadExample(t, file) - - // Note: we do not support multiple documents in a single YAML file. - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "time": dyn.NewValue("20:03:20", []diag.Location{{File: file, Line: 4, Column: 7}}), - "player": dyn.NewValue("Sammy Sosa", []diag.Location{{File: file, Line: 5, Column: 9}}), - "action": dyn.NewValue("strike (miss)", []diag.Location{{File: file, Line: 6, Column: 9}}), - }, - []diag.Location{{File: file, Line: 4, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_9(t *testing.T) { - file := "testdata/spec_example_2.9.yml" - self := loadExample(t, file) - - // Note: we do not support multiple documents in a single YAML file. - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "hr": dyn.NewValue( - []dyn.Value{ - dyn.NewValue("Mark McGwire", []diag.Location{{File: file, Line: 5, Column: 3}}), - dyn.NewValue("Sammy Sosa", []diag.Location{{File: file, Line: 6, Column: 3}}), - }, - []diag.Location{{File: file, Line: 5, Column: 1}}, - ), - "rbi": dyn.NewValue( - []dyn.Value{ - dyn.NewValue("Sammy Sosa", []diag.Location{{File: file, Line: 9, Column: 3}}), - dyn.NewValue("Ken Griffey", []diag.Location{{File: file, Line: 10, Column: 3}}), - }, - []diag.Location{{File: file, Line: 9, Column: 1}}, - ), - }, - []diag.Location{{File: file, Line: 4, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_10(t *testing.T) { - file := "testdata/spec_example_2.10.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "hr": dyn.NewValue( - []dyn.Value{ - dyn.NewValue("Mark McGwire", []diag.Location{{File: file, Line: 5, Column: 3}}), - dyn.NewValue("Sammy Sosa", []diag.Location{{File: file, Line: 7, Column: 3}}), - }, - []diag.Location{{File: file, Line: 5, Column: 1}}, - ), - "rbi": dyn.NewValue( - []dyn.Value{ - // The location for an anchored value refers to the anchor, not the reference. - // This is the same location as the anchor that appears in the "hr" mapping. - dyn.NewValue("Sammy Sosa", []diag.Location{{File: file, Line: 7, Column: 3}}), - dyn.NewValue("Ken Griffey", []diag.Location{{File: file, Line: 10, Column: 3}}), - }, - []diag.Location{{File: file, Line: 9, Column: 1}}, - ), - }, - []diag.Location{{File: file, Line: 4, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_11(t *testing.T) { - file := "testdata/spec_example_2.11.yml" - input, err := os.ReadFile(file) - require.NoError(t, err) - - // Note: non-string mapping keys are not supported by "go.yaml.in/yaml/v3". - _, err = yamlloader.LoadYAML(file, bytes.NewBuffer(input)) - assert.ErrorContains(t, err, `: key is not a scalar`) -} - -func TestYAMLSpecExample_2_12(t *testing.T) { - file := "testdata/spec_example_2.12.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - []dyn.Value{ - dyn.NewValue( - map[string]dyn.Value{ - "item": dyn.NewValue("Super Hoop", []diag.Location{{File: file, Line: 5, Column: 13}}), - "quantity": dyn.NewValue(1, []diag.Location{{File: file, Line: 6, Column: 13}}), - }, - []diag.Location{{File: file, Line: 5, Column: 3}}, - ), - dyn.NewValue( - map[string]dyn.Value{ - "item": dyn.NewValue("Basketball", []diag.Location{{File: file, Line: 7, Column: 13}}), - "quantity": dyn.NewValue(4, []diag.Location{{File: file, Line: 8, Column: 13}}), - }, - []diag.Location{{File: file, Line: 7, Column: 3}}, - ), - dyn.NewValue( - map[string]dyn.Value{ - "item": dyn.NewValue("Big Shoes", []diag.Location{{File: file, Line: 9, Column: 13}}), - "quantity": dyn.NewValue(1, []diag.Location{{File: file, Line: 10, Column: 13}}), - }, - []diag.Location{{File: file, Line: 9, Column: 3}}, - ), - }, - []diag.Location{{File: file, Line: 5, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_13(t *testing.T) { - file := "testdata/spec_example_2.13.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - ``+ - `\//||\/||`+NL+ - "// || ||__"+NL, - []diag.Location{{File: file, Line: 4, Column: 5}}, - ), self) -} - -func TestYAMLSpecExample_2_14(t *testing.T) { - file := "testdata/spec_example_2.14.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - `Mark McGwire's year was crippled by a knee injury.`+NL, - []diag.Location{{File: file, Line: 3, Column: 5}}, - ), self) -} - -func TestYAMLSpecExample_2_15(t *testing.T) { - file := "testdata/spec_example_2.15.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - ``+ - `Sammy Sosa completed another fine season with great stats.`+NL+ - NL+ - ` 63 Home Runs`+NL+ - ` 0.288 Batting Average`+NL+ - NL+ - `What a year!`+NL, - []diag.Location{{File: file, Line: 3, Column: 5}}, - ), self) -} - -func TestYAMLSpecExample_2_16(t *testing.T) { - file := "testdata/spec_example_2.16.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "name": dyn.NewValue( - "Mark McGwire", - []diag.Location{{File: file, Line: 3, Column: 7}}, - ), - "accomplishment": dyn.NewValue( - `Mark set a major league home run record in 1998.`+NL, - []diag.Location{{File: file, Line: 4, Column: 17}}, - ), - "stats": dyn.NewValue( - ``+ - `65 Home Runs`+NL+ - `0.278 Batting Average`+NL, - []diag.Location{{File: file, Line: 7, Column: 8}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_17(t *testing.T) { - file := "testdata/spec_example_2.17.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "unicode": dyn.NewValue( - `Sosa did fine.`+"\u263A", - []diag.Location{{File: file, Line: 3, Column: 10}}, - ), - "control": dyn.NewValue( - "\b1998\t1999\t2000\n", - []diag.Location{{File: file, Line: 4, Column: 10}}, - ), - "hex esc": dyn.NewValue( - "\x0d\x0a is \r\n", - []diag.Location{{File: file, Line: 5, Column: 10}}, - ), - "single": dyn.NewValue( - `"Howdy!" he cried.`, - []diag.Location{{File: file, Line: 7, Column: 9}}, - ), - "quoted": dyn.NewValue( - ` # Not a 'comment'.`, - []diag.Location{{File: file, Line: 8, Column: 9}}, - ), - "tie-fighter": dyn.NewValue( - `|\-*-/|`, - []diag.Location{{File: file, Line: 9, Column: 14}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_18(t *testing.T) { - file := "testdata/spec_example_2.18.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "plain": dyn.NewValue( - `This unquoted scalar spans many lines.`, - []diag.Location{{File: file, Line: 4, Column: 3}}, - ), - "quoted": dyn.NewValue( - `So does this quoted scalar.`+NL, - []diag.Location{{File: file, Line: 7, Column: 9}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_19(t *testing.T) { - file := "testdata/spec_example_2.19.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "canonical": dyn.NewValue( - 12345, - []diag.Location{{File: file, Line: 3, Column: 12}}, - ), - "decimal": dyn.NewValue( - 12345, - []diag.Location{{File: file, Line: 4, Column: 10}}, - ), - "octal": dyn.NewValue( - 12, - []diag.Location{{File: file, Line: 5, Column: 8}}, - ), - "hexadecimal": dyn.NewValue( - 12, - []diag.Location{{File: file, Line: 6, Column: 14}}, - ), - "octal11": dyn.NewValue( - 12345, - []diag.Location{{File: file, Line: 15, Column: 10}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_20(t *testing.T) { - file := "testdata/spec_example_2.20.yml" - self := loadExample(t, file) - - // Equality assertion doesn't work with NaNs. - // See https://github.com/stretchr/testify/issues/624. - // - // Remove the NaN entry. - self, _ = dyn.Walk(self, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { - if f, ok := v.AsFloat(); ok && math.IsNaN(f) { - return dyn.InvalidValue, dyn.ErrDrop - } - return v, nil - }) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "canonical": dyn.NewValue( - 1230.15, - []diag.Location{{File: file, Line: 3, Column: 12}}, - ), - "exponential": dyn.NewValue( - 1230.15, - []diag.Location{{File: file, Line: 4, Column: 14}}, - ), - "fixed": dyn.NewValue( - 1230.15, - []diag.Location{{File: file, Line: 5, Column: 8}}, - ), - "negative infinity": dyn.NewValue( - math.Inf(-1), - []diag.Location{{File: file, Line: 6, Column: 20}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_21(t *testing.T) { - file := "testdata/spec_example_2.21.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "null": dyn.NewValue( - nil, - []diag.Location{{File: file, Line: 3, Column: 6}}, - ), - "booleans": dyn.NewValue( - []dyn.Value{ - dyn.NewValue(true, []diag.Location{{File: file, Line: 4, Column: 13}}), - dyn.NewValue(false, []diag.Location{{File: file, Line: 4, Column: 19}}), - }, - []diag.Location{{File: file, Line: 4, Column: 11}}, - ), - "string": dyn.NewValue( - "012345", - []diag.Location{{File: file, Line: 5, Column: 9}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_22(t *testing.T) { - file := "testdata/spec_example_2.22.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "canonical": dyn.NewValue( - dyn.MustTime("2001-12-15T02:59:43.1Z"), - []diag.Location{{File: file, Line: 3, Column: 12}}, - ), - "iso8601": dyn.NewValue( - dyn.MustTime("2001-12-14t21:59:43.10-05:00"), - []diag.Location{{File: file, Line: 4, Column: 10}}, - ), - "spaced": dyn.NewValue( - // This is parsed as a string, not a timestamp, - // both by "go.yaml.in/yaml/v3" and by our implementation. - "2001-12-14 21:59:43.10 -5", - []diag.Location{{File: file, Line: 5, Column: 9}}, - ), - "date": dyn.NewValue( - dyn.MustTime("2002-12-14"), - []diag.Location{{File: file, Line: 6, Column: 7}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 1}}, - ), self) -} - -func TestYAMLSpecExample_2_23(t *testing.T) { - file := "testdata/spec_example_2.23.yml" - input, err := os.ReadFile(file) - require.NoError(t, err) - - // Note: the !!binary tag is not supported by us. - - _, err = yamlloader.LoadYAML(file, bytes.NewBuffer(input)) - assert.ErrorContains(t, err, `: unknown tag: !!binary`) -} - -func TestYAMLSpecExample_2_24(t *testing.T) { - file := "testdata/spec_example_2.24.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - []dyn.Value{ - dyn.NewValue( - map[string]dyn.Value{ - "center": dyn.NewValue( - map[string]dyn.Value{ - "x": dyn.NewValue(73, []diag.Location{{File: file, Line: 8, Column: 23}}), - "y": dyn.NewValue(129, []diag.Location{{File: file, Line: 8, Column: 30}}), - }, - []diag.Location{{File: file, Line: 8, Column: 11}}, - ), - "radius": dyn.NewValue(7, []diag.Location{{File: file, Line: 9, Column: 11}}), - }, - []diag.Location{{File: file, Line: 7, Column: 3}}, - ), - dyn.NewValue( - map[string]dyn.Value{ - "start": dyn.NewValue( - map[string]dyn.Value{ - "x": dyn.NewValue(73, []diag.Location{{File: file, Line: 8, Column: 23}}), - "y": dyn.NewValue(129, []diag.Location{{File: file, Line: 8, Column: 30}}), - }, - []diag.Location{{File: file, Line: 8, Column: 11}}, - ), - "finish": dyn.NewValue( - map[string]dyn.Value{ - "x": dyn.NewValue(89, []diag.Location{{File: file, Line: 12, Column: 16}}), - "y": dyn.NewValue(102, []diag.Location{{File: file, Line: 12, Column: 23}}), - }, - []diag.Location{{File: file, Line: 12, Column: 11}}, - ), - }, - []diag.Location{{File: file, Line: 10, Column: 3}}, - ), - dyn.NewValue( - map[string]dyn.Value{ - "start": dyn.NewValue( - map[string]dyn.Value{ - "x": dyn.NewValue(73, []diag.Location{{File: file, Line: 8, Column: 23}}), - "y": dyn.NewValue(129, []diag.Location{{File: file, Line: 8, Column: 30}}), - }, - []diag.Location{{File: file, Line: 8, Column: 11}}, - ), - "color": dyn.NewValue(16772795, []diag.Location{{File: file, Line: 15, Column: 10}}), - "text": dyn.NewValue("Pretty vector drawing.", []diag.Location{{File: file, Line: 16, Column: 9}}), - }, - []diag.Location{{File: file, Line: 13, Column: 3}}, - ), - }, - []diag.Location{{File: file, Line: 4, Column: 5}}, - ), self) -} - -func TestYAMLSpecExample_2_25(t *testing.T) { - file := "testdata/spec_example_2.25.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "Mark McGwire": dyn.NewValue(nil, []diag.Location{{File: file, Line: 8, Column: 1}}), - "Sammy Sosa": dyn.NewValue(nil, []diag.Location{{File: file, Line: 9, Column: 1}}), - "Ken Griffey": dyn.NewValue(nil, []diag.Location{{File: file, Line: 10, Column: 1}}), - }, - []diag.Location{{File: file, Line: 6, Column: 5}}, - ), self) -} - -func TestYAMLSpecExample_2_26(t *testing.T) { - file := "testdata/spec_example_2.26.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - []dyn.Value{ - dyn.NewValue( - map[string]dyn.Value{ - "Mark McGwire": dyn.NewValue(65, []diag.Location{{File: file, Line: 7, Column: 17}}), - }, - []diag.Location{{File: file, Line: 7, Column: 3}}, - ), - dyn.NewValue( - map[string]dyn.Value{ - "Sammy Sosa": dyn.NewValue(63, []diag.Location{{File: file, Line: 8, Column: 15}}), - }, - []diag.Location{{File: file, Line: 8, Column: 3}}, - ), - dyn.NewValue( - map[string]dyn.Value{ - "Ken Griffey": dyn.NewValue(58, []diag.Location{{File: file, Line: 9, Column: 16}}), - }, - []diag.Location{{File: file, Line: 9, Column: 3}}, - ), - }, - []diag.Location{{File: file, Line: 6, Column: 5}}, - ), self) -} - -func TestYAMLSpecExample_2_27(t *testing.T) { - file := "testdata/spec_example_2.27.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "invoice": dyn.NewValue( - 34843, - []diag.Location{{File: file, Line: 4, Column: 10}}, - ), - "date": dyn.NewValue( - dyn.MustTime("2001-01-23"), - []diag.Location{{File: file, Line: 5, Column: 10}}, - ), - "bill-to": dyn.NewValue( - map[string]dyn.Value{ - "given": dyn.NewValue( - "Chris", - []diag.Location{{File: file, Line: 7, Column: 12}}, - ), - "family": dyn.NewValue( - "Dumars", - []diag.Location{{File: file, Line: 8, Column: 12}}, - ), - "address": dyn.NewValue( - map[string]dyn.Value{ - "lines": dyn.NewValue( - "458 Walkman Dr.\nSuite #292\n", - []diag.Location{{File: file, Line: 10, Column: 12}}, - ), - "city": dyn.NewValue( - "Royal Oak", - []diag.Location{{File: file, Line: 13, Column: 15}}, - ), - "state": dyn.NewValue( - "MI", - []diag.Location{{File: file, Line: 14, Column: 15}}, - ), - "postal": dyn.NewValue( - 48046, - []diag.Location{{File: file, Line: 15, Column: 15}}, - ), - }, - []diag.Location{{File: file, Line: 10, Column: 5}}, - ), - }, - []diag.Location{{File: file, Line: 6, Column: 10}}, - ), - "ship-to": dyn.NewValue( - map[string]dyn.Value{ - "given": dyn.NewValue( - "Chris", - []diag.Location{{File: file, Line: 7, Column: 12}}, - ), - "family": dyn.NewValue( - "Dumars", - []diag.Location{{File: file, Line: 8, Column: 12}}, - ), - "address": dyn.NewValue( - map[string]dyn.Value{ - "lines": dyn.NewValue( - "458 Walkman Dr.\nSuite #292\n", - []diag.Location{{File: file, Line: 10, Column: 12}}, - ), - "city": dyn.NewValue( - "Royal Oak", - []diag.Location{{File: file, Line: 13, Column: 15}}, - ), - "state": dyn.NewValue( - "MI", - []diag.Location{{File: file, Line: 14, Column: 15}}, - ), - "postal": dyn.NewValue( - 48046, - []diag.Location{{File: file, Line: 15, Column: 15}}, - ), - }, - []diag.Location{{File: file, Line: 10, Column: 5}}, - ), - }, - []diag.Location{{File: file, Line: 6, Column: 10}}, - ), - "product": dyn.NewValue( - []dyn.Value{ - dyn.NewValue( - map[string]dyn.Value{ - "sku": dyn.NewValue( - "BL394D", - []diag.Location{{File: file, Line: 18, Column: 17}}, - ), - "quantity": dyn.NewValue( - 4, - []diag.Location{{File: file, Line: 19, Column: 17}}, - ), - "description": dyn.NewValue( - "Basketball", - []diag.Location{{File: file, Line: 20, Column: 17}}, - ), - "price": dyn.NewValue( - 450.0, - []diag.Location{{File: file, Line: 21, Column: 17}}, - ), - }, - []diag.Location{{File: file, Line: 18, Column: 3}}, - ), dyn.NewValue( - map[string]dyn.Value{ - "sku": dyn.NewValue( - "BL4438H", - []diag.Location{{File: file, Line: 22, Column: 17}}, - ), - "quantity": dyn.NewValue( - 1, - []diag.Location{{File: file, Line: 23, Column: 17}}, - ), - "description": dyn.NewValue( - "Super Hoop", - []diag.Location{{File: file, Line: 24, Column: 17}}, - ), - "price": dyn.NewValue( - 2392.0, - []diag.Location{{File: file, Line: 25, Column: 17}}, - ), - }, - []diag.Location{{File: file, Line: 22, Column: 3}}, - ), - }, - []diag.Location{{File: file, Line: 18, Column: 1}}, - ), - "tax": dyn.NewValue( - 251.42, - []diag.Location{{File: file, Line: 26, Column: 8}}, - ), - "total": dyn.NewValue( - 4443.52, - []diag.Location{{File: file, Line: 27, Column: 8}}, - ), - "comments": dyn.NewValue( - "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.", - []diag.Location{{File: file, Line: 29, Column: 3}}, - ), - }, - []diag.Location{{File: file, Line: 3, Column: 5}}, - ), self) -} - -func TestYAMLSpecExample_2_28(t *testing.T) { - file := "testdata/spec_example_2.28.yml" - self := loadExample(t, file) - - dynassert.Equal(t, dyn.NewValue( - map[string]dyn.Value{ - "Time": dyn.NewValue( - "2001-11-23 15:01:42 -5", - []diag.Location{{File: file, Line: 4, Column: 7}}, - ), - "User": dyn.NewValue( - "ed", - []diag.Location{{File: file, Line: 5, Column: 7}}, - ), - "Warning": dyn.NewValue( - "This is an error message for the log file", - []diag.Location{{File: file, Line: 7, Column: 3}}, - ), - }, - []diag.Location{{File: file, Line: 4, Column: 1}}, - ), self) -} diff --git a/libs/dyn/yamlloader/yaml_test.go b/libs/dyn/yamlloader/yaml_test.go deleted file mode 100644 index e21065e92c0..00000000000 --- a/libs/dyn/yamlloader/yaml_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package yamlloader_test - -import ( - "bytes" - "os" - "testing" - - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/yamlloader" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.yaml.in/yaml/v3" -) - -func loadYAML(t *testing.T, path string) dyn.Value { - input, err := os.ReadFile(path) - require.NoError(t, err) - - var ref any - err = yaml.Unmarshal(input, &ref) - require.NoError(t, err) - - self, err := yamlloader.LoadYAML(path, bytes.NewBuffer(input)) - require.NoError(t, err) - assert.NotNil(t, self) - - // Deep-equal the two values to ensure that the loader is producing - assert.EqualValues(t, ref, self.AsAny()) - return self -} - -func TestYAMLEmpty(t *testing.T) { - self := loadYAML(t, "testdata/empty.yml") - assert.Equal(t, dyn.NilValue, self) -} diff --git a/libs/dyn/yamlsaver/order.go b/libs/dyn/yamlsaver/order.go deleted file mode 100644 index 439be8474a6..00000000000 --- a/libs/dyn/yamlsaver/order.go +++ /dev/null @@ -1,33 +0,0 @@ -package yamlsaver - -import "slices" - -// This struct is used to generate indexes for ordering of map keys. -// The ordering defined based on any predefined Order in `Order` field -// or running Order based on `index` -type Order struct { - index int - order []string -} - -func NewOrder(o []string) *Order { - return &Order{index: 0, order: o} -} - -// Returns an integer which represents the order of map key in resulting -// The lower the index, the earlier in the list the key is. -// If the order is not predefined, it uses running order and any subsequential call to -// order.Get returns an increasing index. -func (o *Order) Get(key string) int { - index := slices.Index(o.order, key) - // If the key is found in predefined order list - // We return a negative index which put the value at the top of the order compared to other - // not predefined keys. The earlier value in predefined list, the lower negative index value - if index != -1 { - return index - len(o.order) - } - - // Otherwise we just increase the order index - o.index += 1 - return o.index -} diff --git a/libs/dyn/yamlsaver/order_test.go b/libs/dyn/yamlsaver/order_test.go deleted file mode 100644 index ed2877f6c8c..00000000000 --- a/libs/dyn/yamlsaver/order_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package yamlsaver - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestOrderReturnsIncreasingIndex(t *testing.T) { - o := NewOrder([]string{}) - assert.Equal(t, 1, o.Get("a")) - assert.Equal(t, 2, o.Get("b")) - assert.Equal(t, 3, o.Get("c")) -} - -func TestOrderReturnsNegativeIndexForPredefinedKeys(t *testing.T) { - o := NewOrder([]string{"a", "b", "c"}) - assert.Equal(t, -3, o.Get("a")) - assert.Equal(t, -2, o.Get("b")) - assert.Equal(t, -1, o.Get("c")) - assert.Equal(t, 1, o.Get("d")) - assert.Equal(t, 2, o.Get("e")) - assert.Equal(t, 3, o.Get("f")) -} diff --git a/libs/dyn/yamlsaver/saver.go b/libs/dyn/yamlsaver/saver.go deleted file mode 100644 index 4c302e26f0d..00000000000 --- a/libs/dyn/yamlsaver/saver.go +++ /dev/null @@ -1,172 +0,0 @@ -package yamlsaver - -import ( - "cmp" - "fmt" - "io" - "os" - "path/filepath" - "slices" - "strconv" - - "github.com/databricks/cli/libs/dyn" - "go.yaml.in/yaml/v3" -) - -type saver struct { - nodesWithStyle map[string]yaml.Style -} - -func NewSaver() *saver { - return &saver{} -} - -func NewSaverWithStyle(nodesWithStyle map[string]yaml.Style) *saver { - return &saver{ - nodesWithStyle: nodesWithStyle, - } -} - -func (s *saver) SaveAsYAML(data any, filename string, force bool) error { - err := os.MkdirAll(filepath.Dir(filename), 0o755) - if err != nil { - return err - } - - // check that file exists - info, err := os.Stat(filename) - if err == nil { - if info.IsDir() { - return fmt.Errorf("%s is a directory", filename) - } - if !force { - return fmt.Errorf("%s already exists. Use --force to overwrite", filename) - } - } - - file, err := os.Create(filename) - if err != nil { - return err - } - defer file.Close() - - err = s.encode(data, file) - if err != nil { - return err - } - return nil -} - -func (s *saver) encode(data any, w io.Writer) error { - yamlNode, err := s.toYamlNode(dyn.V(data)) - if err != nil { - return err - } - enc := yaml.NewEncoder(w) - enc.SetIndent(2) - return enc.Encode(yamlNode) -} - -func (s *saver) toYamlNode(v dyn.Value) (*yaml.Node, error) { - return s.toYamlNodeWithStyle(v, yaml.Style(0)) -} - -func (s *saver) toYamlNodeWithStyle(v dyn.Value, style yaml.Style) (*yaml.Node, error) { - switch v.Kind() { - case dyn.KindMap: - m, _ := v.AsMap() - - // We're using location lines to define the order of keys in YAML. - // The location is set when we convert API response struct to config.Value representation - // See convert.convertMap for details - pairs := m.Pairs() - slices.SortStableFunc(pairs, func(a, b dyn.Pair) int { - return cmp.Compare(a.Value.Location().Line, b.Value.Location().Line) - }) - - var content []*yaml.Node - for _, pair := range pairs { - pk := pair.Key - pv := pair.Value - node := yaml.Node{Kind: yaml.ScalarNode, Value: pk.MustString(), Style: style} - var nestedNodeStyle yaml.Style - if customStyle, ok := s.hasStyle(pk.MustString()); ok { - nestedNodeStyle = customStyle - } else { - nestedNodeStyle = style - } - c, err := s.toYamlNodeWithStyle(pv, nestedNodeStyle) - if err != nil { - return nil, err - } - content = append(content, &node) - content = append(content, c) - } - - return &yaml.Node{Kind: yaml.MappingNode, Content: content, Style: style}, nil - case dyn.KindSequence: - seq, _ := v.AsSequence() - var content []*yaml.Node - for _, item := range seq { - node, err := s.toYamlNodeWithStyle(item, style) - if err != nil { - return nil, err - } - content = append(content, node) - } - return &yaml.Node{Kind: yaml.SequenceNode, Content: content, Style: style}, nil - case dyn.KindNil: - return &yaml.Node{Kind: yaml.ScalarNode, Value: "null", Style: style}, nil - case dyn.KindString: - // If the string is a scalar value (bool, int, float and etc.), we want to quote it. - if isScalarValueInString(v) { - return &yaml.Node{Kind: yaml.ScalarNode, Value: v.MustString(), Style: yaml.DoubleQuotedStyle}, nil - } - return &yaml.Node{Kind: yaml.ScalarNode, Value: v.MustString(), Style: style}, nil - case dyn.KindBool: - return &yaml.Node{Kind: yaml.ScalarNode, Value: strconv.FormatBool(v.MustBool()), Style: style}, nil - case dyn.KindInt: - return &yaml.Node{Kind: yaml.ScalarNode, Value: strconv.FormatInt(v.MustInt(), 10), Style: style}, nil - case dyn.KindFloat: - return &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprint(v.MustFloat()), Style: style}, nil - case dyn.KindTime: - return &yaml.Node{Kind: yaml.ScalarNode, Value: v.MustTime().String(), Style: style}, nil - default: - // Panic because we only want to deal with known types. - panic(fmt.Sprintf("invalid kind: %d", v.Kind())) - } -} - -func (s *saver) hasStyle(key string) (yaml.Style, bool) { - style, ok := s.nodesWithStyle[key] - return style, ok -} - -func isScalarValueInString(v dyn.Value) bool { - if v.Kind() != dyn.KindString { - return false - } - - // Parse value of the string and check if it's a scalar value. - // If it's a scalar value, we want to quote it. - switch v.MustString() { - case "true", "false": - return true - case "": - return true - default: - _, err := parseNumber(v.MustString()) - return err == nil - } -} - -func parseNumber(s string) (any, error) { - if i, err := strconv.ParseInt(s, 0, 64); err == nil { - return i, nil - } - - if f, err := strconv.ParseFloat(s, 64); err == nil { - return f, nil - } - return nil, fmt.Errorf("invalid number: %s", s) -} diff --git a/libs/dyn/yamlsaver/saver_test.go b/libs/dyn/yamlsaver/saver_test.go deleted file mode 100644 index a4f50e0b8fb..00000000000 --- a/libs/dyn/yamlsaver/saver_test.go +++ /dev/null @@ -1,275 +0,0 @@ -package yamlsaver - -import ( - "testing" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.yaml.in/yaml/v3" -) - -func TestMarshalNilValue(t *testing.T) { - s := NewSaver() - nilValue := dyn.NilValue - v, err := s.toYamlNode(nilValue) - assert.NoError(t, err) - assert.Equal(t, "null", v.Value) -} - -func TestMarshalIntValue(t *testing.T) { - s := NewSaver() - intValue := dyn.V(1) - v, err := s.toYamlNode(intValue) - assert.NoError(t, err) - assert.Equal(t, "1", v.Value) - assert.Equal(t, yaml.ScalarNode, v.Kind) -} - -func TestMarshalFloatValue(t *testing.T) { - s := NewSaver() - floatValue := dyn.V(1.0) - v, err := s.toYamlNode(floatValue) - assert.NoError(t, err) - assert.Equal(t, "1", v.Value) - assert.Equal(t, yaml.ScalarNode, v.Kind) -} - -func TestMarshalBoolValue(t *testing.T) { - s := NewSaver() - boolValue := dyn.V(true) - v, err := s.toYamlNode(boolValue) - assert.NoError(t, err) - assert.Equal(t, "true", v.Value) - assert.Equal(t, yaml.ScalarNode, v.Kind) -} - -func TestMarshalTimeValue(t *testing.T) { - tm, err := dyn.NewTime("1970-01-01") - require.NoError(t, err) - - s := NewSaver() - timeValue := dyn.V(tm) - v, err := s.toYamlNode(timeValue) - assert.NoError(t, err) - assert.Equal(t, "1970-01-01", v.Value) - assert.Equal(t, yaml.ScalarNode, v.Kind) -} - -func TestMarshalSequenceValue(t *testing.T) { - s := NewSaver() - sequenceValue := dyn.NewValue( - []dyn.Value{ - dyn.NewValue("value1", []diag.Location{{File: "file", Line: 1, Column: 2}}), - dyn.NewValue("value2", []diag.Location{{File: "file", Line: 2, Column: 2}}), - }, - []diag.Location{{File: "file", Line: 1, Column: 2}}, - ) - v, err := s.toYamlNode(sequenceValue) - assert.NoError(t, err) - assert.Equal(t, yaml.SequenceNode, v.Kind) - assert.Equal(t, "value1", v.Content[0].Value) - assert.Equal(t, "value2", v.Content[1].Value) -} - -func TestMarshalStringValue(t *testing.T) { - s := NewSaver() - stringValue := dyn.V("value") - v, err := s.toYamlNode(stringValue) - assert.NoError(t, err) - assert.Equal(t, "value", v.Value) - assert.Equal(t, yaml.ScalarNode, v.Kind) -} - -func TestMarshalMapValue(t *testing.T) { - s := NewSaver() - mapValue := dyn.NewValue( - map[string]dyn.Value{ - "key3": dyn.NewValue("value3", []diag.Location{{File: "file", Line: 3, Column: 2}}), - "key2": dyn.NewValue("value2", []diag.Location{{File: "file", Line: 2, Column: 2}}), - "key1": dyn.NewValue("value1", []diag.Location{{File: "file", Line: 1, Column: 2}}), - }, - []diag.Location{{File: "file", Line: 1, Column: 2}}, - ) - - v, err := s.toYamlNode(mapValue) - assert.NoError(t, err) - assert.Equal(t, yaml.MappingNode, v.Kind) - assert.Equal(t, "key1", v.Content[0].Value) - assert.Equal(t, "value1", v.Content[1].Value) - - assert.Equal(t, "key2", v.Content[2].Value) - assert.Equal(t, "value2", v.Content[3].Value) - - assert.Equal(t, "key3", v.Content[4].Value) - assert.Equal(t, "value3", v.Content[5].Value) -} - -func TestMarshalNestedValues(t *testing.T) { - s := NewSaver() - mapValue := dyn.NewValue( - map[string]dyn.Value{ - "key1": dyn.NewValue( - map[string]dyn.Value{ - "key2": dyn.NewValue("value", []diag.Location{{File: "file", Line: 1, Column: 2}}), - }, - []diag.Location{{File: "file", Line: 1, Column: 2}}, - ), - }, - []diag.Location{{File: "file", Line: 1, Column: 2}}, - ) - v, err := s.toYamlNode(mapValue) - assert.NoError(t, err) - assert.Equal(t, yaml.MappingNode, v.Kind) - assert.Equal(t, "key1", v.Content[0].Value) - assert.Equal(t, yaml.MappingNode, v.Content[1].Kind) - assert.Equal(t, "key2", v.Content[1].Content[0].Value) - assert.Equal(t, "value", v.Content[1].Content[1].Value) -} - -func TestMarshalHexadecimalValueIsQuoted(t *testing.T) { - s := NewSaver() - hexValue := dyn.V(0x123) - v, err := s.toYamlNode(hexValue) - assert.NoError(t, err) - assert.Equal(t, "291", v.Value) - assert.Equal(t, yaml.Style(0), v.Style) - assert.Equal(t, yaml.ScalarNode, v.Kind) - - stringValue := dyn.V("0x123") - v, err = s.toYamlNode(stringValue) - assert.NoError(t, err) - assert.Equal(t, "0x123", v.Value) - assert.Equal(t, yaml.DoubleQuotedStyle, v.Style) - assert.Equal(t, yaml.ScalarNode, v.Kind) -} - -func TestMarshalBinaryValueIsQuoted(t *testing.T) { - s := NewSaver() - binaryValue := dyn.V(0b101) - v, err := s.toYamlNode(binaryValue) - assert.NoError(t, err) - assert.Equal(t, "5", v.Value) - assert.Equal(t, yaml.Style(0), v.Style) - assert.Equal(t, yaml.ScalarNode, v.Kind) - - stringValue := dyn.V("0b101") - v, err = s.toYamlNode(stringValue) - assert.NoError(t, err) - assert.Equal(t, "0b101", v.Value) - assert.Equal(t, yaml.DoubleQuotedStyle, v.Style) - assert.Equal(t, yaml.ScalarNode, v.Kind) -} - -func TestMarshalOctalValueIsQuoted(t *testing.T) { - s := NewSaver() - octalValue := dyn.V(0o123) - v, err := s.toYamlNode(octalValue) - assert.NoError(t, err) - assert.Equal(t, "83", v.Value) - assert.Equal(t, yaml.Style(0), v.Style) - assert.Equal(t, yaml.ScalarNode, v.Kind) - - stringValue := dyn.V("0123") - v, err = s.toYamlNode(stringValue) - assert.NoError(t, err) - assert.Equal(t, "0123", v.Value) - assert.Equal(t, yaml.DoubleQuotedStyle, v.Style) - assert.Equal(t, yaml.ScalarNode, v.Kind) -} - -func TestMarshalFloatValueIsQuoted(t *testing.T) { - s := NewSaver() - floatValue := dyn.V(1.0) - v, err := s.toYamlNode(floatValue) - assert.NoError(t, err) - assert.Equal(t, "1", v.Value) - assert.Equal(t, yaml.Style(0), v.Style) - assert.Equal(t, yaml.ScalarNode, v.Kind) - - stringValue := dyn.V("1.0") - v, err = s.toYamlNode(stringValue) - assert.NoError(t, err) - assert.Equal(t, "1.0", v.Value) - assert.Equal(t, yaml.DoubleQuotedStyle, v.Style) - assert.Equal(t, yaml.ScalarNode, v.Kind) -} - -func TestMarshalBoolValueIsQuoted(t *testing.T) { - s := NewSaver() - boolValue := dyn.V(true) - v, err := s.toYamlNode(boolValue) - assert.NoError(t, err) - assert.Equal(t, "true", v.Value) - assert.Equal(t, yaml.Style(0), v.Style) - assert.Equal(t, yaml.ScalarNode, v.Kind) - - stringValue := dyn.V("true") - v, err = s.toYamlNode(stringValue) - assert.NoError(t, err) - assert.Equal(t, "true", v.Value) - assert.Equal(t, yaml.DoubleQuotedStyle, v.Style) - assert.Equal(t, yaml.ScalarNode, v.Kind) -} - -func TestCustomStylingWithNestedMap(t *testing.T) { - s := NewSaverWithStyle(map[string]yaml.Style{ - "styled": yaml.DoubleQuotedStyle, - }) - - styledMap := dyn.NewValue( - map[string]dyn.Value{ - "key1": dyn.NewValue("value1", []diag.Location{{File: "file", Line: 1, Column: 2}}), - "key2": dyn.NewValue("value2", []diag.Location{{File: "file", Line: 2, Column: 2}}), - }, - []diag.Location{{File: "file", Line: -2, Column: 2}}, - ) - - unstyledMap := dyn.NewValue( - map[string]dyn.Value{ - "key3": dyn.NewValue("value3", []diag.Location{{File: "file", Line: 1, Column: 2}}), - "key4": dyn.NewValue("value4", []diag.Location{{File: "file", Line: 2, Column: 2}}), - }, - []diag.Location{{File: "file", Line: -1, Column: 2}}, - ) - - val := dyn.NewValue( - map[string]dyn.Value{ - "styled": styledMap, - "unstyled": unstyledMap, - }, - []diag.Location{{File: "file", Line: 1, Column: 2}}, - ) - - mv, err := s.toYamlNode(val) - assert.NoError(t, err) - - // Check that the styled map is quoted - v := mv.Content[1] - - assert.Equal(t, yaml.MappingNode, v.Kind) - assert.Equal(t, "key1", v.Content[0].Value) - assert.Equal(t, "value1", v.Content[1].Value) - assert.Equal(t, yaml.DoubleQuotedStyle, v.Content[0].Style) - assert.Equal(t, yaml.DoubleQuotedStyle, v.Content[1].Style) - - assert.Equal(t, "key2", v.Content[2].Value) - assert.Equal(t, "value2", v.Content[3].Value) - assert.Equal(t, yaml.DoubleQuotedStyle, v.Content[2].Style) - assert.Equal(t, yaml.DoubleQuotedStyle, v.Content[3].Style) - - // Check that the unstyled map is not quoted - v = mv.Content[3] - - assert.Equal(t, yaml.MappingNode, v.Kind) - assert.Equal(t, "key3", v.Content[0].Value) - assert.Equal(t, "value3", v.Content[1].Value) - assert.Equal(t, yaml.Style(0), v.Content[0].Style) - assert.Equal(t, yaml.Style(0), v.Content[1].Style) - - assert.Equal(t, "key4", v.Content[2].Value) - assert.Equal(t, "value4", v.Content[3].Value) - assert.Equal(t, yaml.Style(0), v.Content[2].Style) - assert.Equal(t, yaml.Style(0), v.Content[3].Style) -} diff --git a/libs/dyn/yamlsaver/utils.go b/libs/dyn/yamlsaver/utils.go deleted file mode 100644 index 61f41790029..00000000000 --- a/libs/dyn/yamlsaver/utils.go +++ /dev/null @@ -1,94 +0,0 @@ -package yamlsaver - -import ( - "fmt" - "slices" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/convert" -) - -// Converts a struct to map. Skips any nil fields. -// It uses `skipFields` to skip unnecessary fields. -// Uses `order` to define the order of keys in resulting outout -func ConvertToMapValue(strct any, order *Order, skipFields []string, dst map[string]dyn.Value) (dyn.Value, error) { - ref := dyn.NilValue - mv, err := convert.FromTyped(strct, ref) - if err != nil { - return dyn.InvalidValue, err - } - - if mv.Kind() != dyn.KindMap { - return dyn.InvalidValue, fmt.Errorf("expected map, got %s", mv.Kind()) - } - - mv, err = sortMapAlphabetically(mv) - if err != nil { - return dyn.InvalidValue, err - } - - return skipAndOrder(mv, order, skipFields, dst) -} - -// Sort the map alphabetically by keys. This is used to produce stable output for generated YAML files. -func sortMapAlphabetically(mv dyn.Value) (dyn.Value, error) { - sortedMap := dyn.NewMapping() - mapV := mv.MustMap() - keys := mapV.Keys() - slices.SortStableFunc(keys, func(i, j dyn.Value) int { - iKey := i.MustString() - jKey := j.MustString() - if iKey < jKey { - return -1 - } - - if iKey > jKey { - return 1 - } - return 0 - }) - - for _, key := range keys { - value, _ := mapV.Get(key) - var err error - if value.Kind() == dyn.KindMap { - value, err = sortMapAlphabetically(value) - if err != nil { - return dyn.InvalidValue, err - } - } - sortedMap.SetLoc(key.MustString(), key.Locations(), value) - } - - return dyn.V(sortedMap), nil -} - -func skipAndOrder(mv dyn.Value, order *Order, skipFields []string, dst map[string]dyn.Value) (dyn.Value, error) { - for _, pair := range mv.MustMap().Pairs() { - k := pair.Key.MustString() - v := pair.Value - if v.Kind() == dyn.KindNil { - continue - } - - if slices.Contains(skipFields, k) { - continue - } - - // If the value is already defined in destination, it means it was - // manually set due to custom ordering or other customisation required - // So we're skipping processing it again - if _, ok := dst[k]; ok { - continue - } - - if order == nil { - dst[k] = v - } else { - dst[k] = dyn.NewValue(v.Value(), []diag.Location{{Line: order.Get(k)}}) - } - } - - return dyn.V(dst), nil -} diff --git a/libs/dyn/yamlsaver/utils_test.go b/libs/dyn/yamlsaver/utils_test.go deleted file mode 100644 index 8b056e0076f..00000000000 --- a/libs/dyn/yamlsaver/utils_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package yamlsaver - -import ( - "testing" - - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/dyn/dynassert" - "github.com/stretchr/testify/assert" -) - -func TestConvertToMap(t *testing.T) { - type test struct { - Name string `json:"name"` - Map map[string]string `json:"map"` - List []string `json:"list"` - LongNameField string `json:"long_name_field"` - ForceSendFields []string `json:"-"` - Format string `json:"format"` - } - - v := &test{ - Name: "test", - Map: map[string]string{ - "key2": "value2", - "key1": "value1", - }, - List: []string{"a", "b", "c"}, - ForceSendFields: []string{ - "Name", - }, - LongNameField: "long name goes here", - } - result, err := ConvertToMapValue(v, nil, []string{"format"}, map[string]dyn.Value{}) - assert.NoError(t, err) - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "list": dyn.V( - []dyn.Value{ - dyn.V("a"), - dyn.V("b"), - dyn.V("c"), - }, - ), - "long_name_field": dyn.V("long name goes here"), - "map": dyn.V( - map[string]dyn.Value{ - "key1": dyn.V("value1"), - "key2": dyn.V("value2"), - }, - ), - "name": dyn.V( - "test", - ), - }), result) -} - -func TestConvertToMapValueWithOrder(t *testing.T) { - type test struct { - Name string `json:"name"` - Map map[string]string `json:"map"` - List []string `json:"list"` - LongNameField string `json:"long_name_field"` - ForceSendFields []string `json:"-"` - Format string `json:"format"` - } - - v := &test{ - Name: "test", - Map: map[string]string{ - "key1": "value1", - "key2": "value2", - }, - List: []string{"a", "b", "c"}, - ForceSendFields: []string{ - "Name", - }, - LongNameField: "long name goes here", - } - result, err := ConvertToMapValue(v, NewOrder([]string{"list", "name", "map"}), []string{"format"}, map[string]dyn.Value{}) - assert.NoError(t, err) - - dynassert.Equal(t, dyn.V(map[string]dyn.Value{ - "list": dyn.NewValue( - []dyn.Value{ - dyn.V("a"), - dyn.V("b"), - dyn.V("c"), - }, - []diag.Location{{Line: -3}}, - ), - "name": dyn.NewValue( - "test", - []diag.Location{{Line: -2}}, - ), - "map": dyn.NewValue( - map[string]dyn.Value{ - "key1": dyn.V("value1"), - "key2": dyn.V("value2"), - }, - []diag.Location{{Line: -1}}, - ), - "long_name_field": dyn.NewValue("long name goes here", []diag.Location{{Line: 1}}), - }), result) -} diff --git a/libs/flags/json_flag.go b/libs/flags/json_flag.go index 3ea4834a5f2..775ea346c70 100644 --- a/libs/flags/json_flag.go +++ b/libs/flags/json_flag.go @@ -8,9 +8,9 @@ import ( "reflect" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn/convert" - "github.com/databricks/cli/libs/dyn/jsonloader" + "github.com/databricks/cli/libs/structs/structvar" "github.com/databricks/databricks-sdk-go/marshal" + "go.yaml.in/yaml/v3" ) type JsonFlag struct { @@ -48,27 +48,18 @@ func (j *JsonFlag) Unmarshal(v any) diag.Diagnostics { return nil } - dv, err := jsonloader.LoadJSON(j.raw, j.source) + node, err := structvar.ParseJSON(j.source, j.raw) if err != nil { return diag.FromErr(err) } - // First normalize the input data. - // It will convert all the values to the correct types. + // Convert the input to the types of the target, in a fresh value: fields set by + // other flags are already in v and must be kept. // For example string literals for booleans and integers will be converted to the correct types. - nv, diags := convert.Normalize(v, dv) - if diags.HasError() { - return diags - } - - if !nv.IsValid() { - kind := reflect.TypeOf(v).Kind() - if kind == reflect.Pointer { - kind = reflect.TypeOf(v).Elem().Kind() - } - + sv, diags, err := structvar.DecodeYAMLNode(j.source, node, reflect.New(reflect.TypeOf(v).Elem()).Interface(), nil) + if err != nil { var expectedJsonType string - switch kind { + switch reflect.TypeOf(v).Elem().Kind() { case reflect.Struct, reflect.Map: expectedJsonType = "object" case reflect.Slice: @@ -80,37 +71,28 @@ func (j *JsonFlag) Unmarshal(v any) diag.Diagnostics { return diags.Append(diag.Diagnostic{ Severity: diag.Error, Summary: "Invalid command input", - Detail: fmt.Sprintf("expected JSON %s, received %s", expectedJsonType, dv.Kind()), + Detail: fmt.Sprintf("expected JSON %s, received %s", expectedJsonType, nodeKind(node)), }) } // Then marshal the normalized data to the output. // It will serialize all set data with the correct types. - data, err := json.Marshal(nv.AsAny()) + data, err := json.Marshal(sv.View().AsAny()) if err != nil { return diags.Extend(diag.FromErr(err)) } - kind := reflect.ValueOf(v).Kind() - if kind == reflect.Pointer { - kind = reflect.ValueOf(v).Elem().Kind() - } - - if kind == reflect.Struct { + if reflect.TypeOf(v).Elem().Kind() == reflect.Struct { // Finally unmarshal the normalized data to the output. // It will fill in the ForceSendFields field if the struct contains it. err = marshal.Unmarshal(data, v) - if err != nil { - return diags.Extend(diag.FromErr(err)) - } } else { // If the output is not a struct, just unmarshal the data to the output. err = json.Unmarshal(data, v) - if err != nil { - return diags.Extend(diag.FromErr(err)) - } } - + if err != nil { + return diags.Extend(diag.FromErr(err)) + } return diags } @@ -133,7 +115,7 @@ func (j *JsonFlag) Validate() error { if j.raw == nil { return nil } - _, err := jsonloader.LoadJSON(j.raw, j.source) + _, err := structvar.ParseJSON(j.source, j.raw) return err } @@ -170,3 +152,15 @@ func (j *JsonFlag) RejectWrappedJSON(outerKey, example string) error { } return fmt.Errorf("%s", msg) } + +// nodeKind returns the kind of the JSON value in node, as a user sees it. +func nodeKind(node *yaml.Node) string { + switch node.Kind { + case yaml.MappingNode: + return "map" + case yaml.SequenceNode: + return "sequence" + default: + return map[string]string{"!!str": "string", "!!bool": "bool", "!!int": "int", "!!float": "float", "!!null": "nil"}[node.Tag] + } +} diff --git a/libs/flags/json_flag_test.go b/libs/flags/json_flag_test.go index 675e1014922..2dc73942719 100644 --- a/libs/flags/json_flag_test.go +++ b/libs/flags/json_flag_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/assert" @@ -202,7 +201,7 @@ func TestJsonUnmarshalRequestMismatch(t *testing.T) { Column: 6, }, }, - Paths: dyn.ToStructPaths(dyn.EmptyPath), + Paths: []*structpath.PathNode{nil}, }) } diff --git a/libs/structs/structaccess/bundle_test.go b/libs/structs/structaccess/bundle_test.go index 4de21ede392..12c06830dbc 100644 --- a/libs/structs/structaccess/bundle_test.go +++ b/libs/structs/structaccess/bundle_test.go @@ -1,4 +1,4 @@ -package structaccess +package structaccess_test import ( "reflect" @@ -6,6 +6,7 @@ import ( "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/libs/structs/structaccess" "github.com/databricks/databricks-sdk-go/service/apps" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/require" @@ -45,37 +46,37 @@ func TestGet_ConfigRoot_JobTagsAccess(t *testing.T) { } // Access a value inside the tags map - v, err := GetByString(root, "resources.jobs.my_job.tags.env") + v, err := structaccess.GetByString(root, "resources.jobs.my_job.tags.env") require.NoError(t, err) require.Equal(t, "dev", v) - require.NoError(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tags.env")) - require.NoError(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tags.anything")) - require.Error(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tags.env.inner")) - require.Error(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tags1")) + require.NoError(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tags.env")) + require.NoError(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tags.anything")) + require.Error(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tags.env.inner")) + require.Error(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tags1")) // Array indexing test (1) - v, err = GetByString(root, "resources.jobs.my_job.tasks[0].task_key") + v, err = structaccess.GetByString(root, "resources.jobs.my_job.tasks[0].task_key") require.NoError(t, err) require.Equal(t, "t1", v) - require.NoError(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].task_key")) - require.Error(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].task_key.inner")) - require.Error(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].task_key1")) + require.NoError(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].task_key")) + require.Error(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].task_key.inner")) + require.Error(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].task_key1")) // Array indexing test (2) - v, err = GetByString(root, "resources.jobs.my_job.tasks[0].notebook_task.notebook_path") + v, err = structaccess.GetByString(root, "resources.jobs.my_job.tasks[0].notebook_task.notebook_path") require.NoError(t, err) require.Equal(t, "/Workspace/Users/user@example.com/nb", v) - require.NoError(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].notebook_task.notebook_path")) - require.Error(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].notebook_task.notebook_path.inner")) - require.Error(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].notebook_task.notebook_path1")) + require.NoError(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].notebook_task.notebook_path")) + require.Error(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].notebook_task.notebook_path.inner")) + require.Error(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.jobs.my_job.tasks[0].notebook_task.notebook_path1")) // Test ambiguous field access: outer is ignored because it has bundle tag - v, err = GetByString(root, "resources.apps.my_app.url") + v, err = structaccess.GetByString(root, "resources.apps.my_app.url") require.NoError(t, err) require.Equal(t, "app_inner_url", v) - require.NoError(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.apps.my_app.url")) - require.Error(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.apps.my_app.url.inner")) - require.Error(t, ValidateByString(reflect.TypeFor[config.Root](), "resources.apps.my_app.url1")) + require.NoError(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.apps.my_app.url")) + require.Error(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.apps.my_app.url.inner")) + require.Error(t, structaccess.ValidateByString(reflect.TypeFor[config.Root](), "resources.apps.my_app.url1")) } // A bundle resource embeds a config struct that embeds the SDK request struct, so its @@ -86,29 +87,29 @@ func TestGetSet_DoublyEmbeddedField(t *testing.T) { project := &resources.PostgresProject{} //exhaustruct:ignore project.ProjectId = "p" - require.NoError(t, ValidateByString(reflect.TypeOf(project), "budget_policy_id")) + require.NoError(t, structaccess.ValidateByString(reflect.TypeOf(project), "budget_policy_id")) - require.NoError(t, SetByString(project, "budget_policy_id", "abc")) + require.NoError(t, structaccess.SetByString(project, "budget_policy_id", "abc")) require.Equal(t, "abc", project.BudgetPolicyId) - value, err := GetByString(project, "budget_policy_id") + value, err := structaccess.GetByString(project, "budget_policy_id") require.NoError(t, err) require.Equal(t, "abc", value) // An explicit empty value is recorded on ProjectSpec, which declares the field. - require.NoError(t, SetByString(project, "budget_policy_id", "")) + require.NoError(t, structaccess.SetByString(project, "budget_policy_id", "")) require.Contains(t, project.ProjectSpec.ForceSendFields, "BudgetPolicyId") require.NotContains(t, project.ForceSendFields, "BudgetPolicyId") - value, err = GetByString(project, "budget_policy_id") + value, err = structaccess.GetByString(project, "budget_policy_id") require.NoError(t, err) // The empty string, not nil: that is what separates an explicit "" from an absent field. require.Equal(t, any(""), value) // And dropping it again leaves the field absent. - require.NoError(t, SetByString(project, "budget_policy_id", nil)) + require.NoError(t, structaccess.SetByString(project, "budget_policy_id", nil)) require.NotContains(t, project.ProjectSpec.ForceSendFields, "BudgetPolicyId") - value, err = GetByString(project, "budget_policy_id") + value, err = structaccess.GetByString(project, "budget_policy_id") require.NoError(t, err) require.Nil(t, value) } diff --git a/libs/structs/structpath/path.go b/libs/structs/structpath/path.go index adf2be51331..e719c21003d 100644 --- a/libs/structs/structpath/path.go +++ b/libs/structs/structpath/path.go @@ -9,7 +9,7 @@ import ( ) // baseVarDef matches a path segment of a ${...} reference. It is the grammar of -// dynvar.BaseVarDef, which structpath cannot import; TestPureReferenceMatchesDynvar +// structvar.BaseVarDef, which structpath cannot import; TestPureReferenceMatchesStructvar // keeps them in agreement. const baseVarDef = `_*\p{L}+([-_]*[\p{L}\p{N}]+)*` @@ -191,6 +191,15 @@ func NewPathSlice(parts ...any) []*PathNode { return []*PathNode{NewPath(nil, parts...)} } +// Join appends the components of nodes (each node's own key, index or key-value, not +// its parents) to prefix. +func Join(prefix *PathNode, nodes ...*PathNode) *PathNode { + for _, n := range nodes { + prefix = &PathNode{prev: prefix, key: n.key, index: n.index, value: n.value} + } + return prefix +} + func NewKeyValue(prev *PathNode, key, value string) *PathNode { return &PathNode{ prev: prev, @@ -360,6 +369,37 @@ func NewPatternBracketStar(prev *PatternNode) *PatternNode { }) } +// wildcard is a NewPattern part that matches any key or element. +type wildcard int + +// Wildcards for [NewPattern]: AnyKey matches any map key or field (rendered ".*") and +// AnyIndex matches any sequence element (rendered "[*]"). +const ( + AnyKey wildcard = tagDotStar + AnyIndex wildcard = tagBracketStar +) + +// NewPattern appends parts to prev like [NewPath]; AnyKey and AnyIndex append wildcards. +func NewPattern(prev *PatternNode, parts ...any) *PatternNode { + for _, part := range parts { + switch v := part.(type) { + case string: + prev = NewPatternStringKey(prev, v) + case int: + prev = NewPatternIndex(prev, v) + case wildcard: + if v == AnyKey { + prev = NewPatternDotStar(prev) + } else { + prev = NewPatternBracketStar(prev) + } + default: + panic(fmt.Sprintf("structpath.NewPattern: unsupported part %#v", part)) + } + } + return prev +} + func NewPatternKeyValue(prev *PatternNode, key, value string) *PatternNode { return (*PatternNode)(NewKeyValue((*PathNode)(prev), key, value)) } @@ -712,7 +752,6 @@ func isValidField(s string) bool { } // PureReferenceToPath returns a PathNode if s is a pure variable reference, otherwise false. -// This function is similar to dynvar.PureReferenceToPath but returns a *PathNode instead of dyn.Path. func PureReferenceToPath(s string) (*PathNode, bool) { m := pureReference.FindStringSubmatch(s) if m == nil { @@ -986,3 +1025,13 @@ func (p *PatternNode) UnmarshalYAML(unmarshal func(any) error) error { *p = *parsed return nil } + +// KeyAt returns the string key of the i-th component of the path, or "" if it is not a key. +func (p *PathNode) KeyAt(i int) string { + nodes := p.AsSlice() + if i < 0 || i >= len(nodes) { + return "" + } + k, _ := nodes[i].StringKey() + return k +} diff --git a/libs/structs/structpath/path_test.go b/libs/structs/structpath/path_test.go index 40fc0282c2b..58cf6d050cb 100644 --- a/libs/structs/structpath/path_test.go +++ b/libs/structs/structpath/path_test.go @@ -1275,14 +1275,6 @@ paths: assert.Contains(t, err.Error(), "unexpected end of input") } -func TestMustParsePaths(t *testing.T) { - paths := MustParsePaths("bundle.terraform", "resources.jobs.foo.tasks[0]") - require.Len(t, paths, 2) - assert.Equal(t, "bundle.terraform", paths[0].String()) - assert.Equal(t, "resources.jobs.foo.tasks[0]", paths[1].String()) - assert.Empty(t, MustParsePaths()) -} - func TestNewPath(t *testing.T) { assert.Equal(t, "a.b['c d']", NewPath(NewStringKey(nil, "a"), "b", "c d").String()) assert.Equal(t, "resources.jobs.j.tasks[2].job_cluster_key", NewPath(nil, "resources", "jobs", "j", "tasks", 2, "job_cluster_key").String()) @@ -1291,9 +1283,34 @@ func TestNewPath(t *testing.T) { assert.Panics(t, func() { NewPath(nil, int64(1)) }) } +func TestJoin(t *testing.T) { + p := MustParsePath("tasks[task_key='x'].libraries[0]['a.b']") + assert.Equal(t, "resources.jobs.j.tasks[task_key='x'].libraries[0]['a.b']", Join(MustParsePath("resources.jobs.j"), p.AsSlice()...).String()) + assert.Equal(t, p.String(), Join(nil, p.AsSlice()...).String()) + assert.Equal(t, "a", Join(MustParsePath("a")).String()) +} + +func TestMustParsePaths(t *testing.T) { + paths := MustParsePaths("bundle.terraform", "resources.jobs.foo.tasks[0]") + require.Len(t, paths, 2) + assert.Equal(t, "bundle.terraform", paths[0].String()) + assert.Equal(t, "resources.jobs.foo.tasks[0]", paths[1].String()) + assert.Empty(t, MustParsePaths()) +} + func TestNewPathSlice(t *testing.T) { paths := NewPathSlice("resources", "jobs", "${var.env}_job") require.Len(t, paths, 1) assert.Equal(t, "resources.jobs['${var.env}_job']", paths[0].String()) assert.Equal(t, "sync.paths[3]", NewPathSlice("sync", "paths", 3)[0].String()) } + +func TestNewPattern(t *testing.T) { + p := NewPattern(nil, "resources", "jobs", AnyKey, "tasks", AnyIndex, "libraries") + assert.Equal(t, "resources.jobs.*.tasks[*].libraries", p.String()) + + ext := NewPattern(p, AnyIndex, "whl", 0) + assert.Equal(t, "resources.jobs.*.tasks[*].libraries[*].whl[0]", ext.String()) + + assert.Panics(t, func() { NewPattern(nil, 1.5) }) +} diff --git a/libs/structs/structpath/reference_test.go b/libs/structs/structpath/reference_test.go index 485b756c7fb..45360c24b4b 100644 --- a/libs/structs/structpath/reference_test.go +++ b/libs/structs/structpath/reference_test.go @@ -3,12 +3,12 @@ package structpath_test import ( "testing" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" "github.com/stretchr/testify/assert" ) -func TestPureReferenceMatchesDynvar(t *testing.T) { +func TestPureReferenceMatchesStructvar(t *testing.T) { for _, s := range []string{ "${var.foo}", "${resources.jobs.foo.tasks[1].env.key}", @@ -25,6 +25,6 @@ func TestPureReferenceMatchesDynvar(t *testing.T) { "plain_string", } { _, ok := structpath.PureReferenceToPath(s) - assert.Equal(t, dynvar.IsPureVariableReference(s), ok, s) + assert.Equal(t, structvar.IsPureVariableReference(s), ok, s) } } diff --git a/libs/structs/structvar/decode.go b/libs/structs/structvar/decode.go new file mode 100644 index 00000000000..233b48a948b --- /dev/null +++ b/libs/structs/structvar/decode.go @@ -0,0 +1,761 @@ +package structvar + +import ( + "encoding/json" + "fmt" + "io" + "maps" + "reflect" + "strconv" + + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/structs/structpath" + "go.yaml.in/yaml/v3" +) + +// source is a configuration tree to decode from (a parsed YAML document or a view). +type source interface { + kind() Kind + locations() []diag.Location + // anchor reports whether the value is a YAML anchor (or an alias of one). + anchor() bool + // scalar returns the value of a string, bool, int (int or int64), float or time (string). + scalar() any + pairs() ([]sourcePair, error) + elems() ([]source, error) +} + +type sourcePair struct { + key string + // keyLoc is the location of the key, if any. + keyLoc diag.Location + value source +} + +// keyLocations returns the locations of the key for a diagnostic. +func (p sourcePair) keyLocations() []diag.Location { + if p.keyLoc == (diag.Location{}) { + return nil + } + return []diag.Location{p.keyLoc} +} + +// DecodeYAML decodes the YAML document in r (read from file) into dst, a pointer to a +// typed configuration value, and returns its references and locations. +// +// Values are converted to the field types the way configuration is normalized: scalars +// are converted where possible, pure variable references are recorded in the returned +// references for fields that cannot hold a string, and values that cannot be converted +// or fields that do not exist are dropped with a warning. An error is returned for +// invalid YAML. +func DecodeYAML(file string, r io.Reader, dst any) (*StructVar, diag.Diagnostics, error) { + node, err := ParseYAML(r) + if err != nil { + return nil, nil, err + } + return DecodeYAMLNode(file, node, dst, nil) +} + +// LocationMapper returns the locations to record for the value at path, given the +// locations it has in the YAML document. +type LocationMapper func(path *structpath.PathNode, locs []diag.Location) []diag.Location + +// DecodeYAMLNode is [DecodeYAML] for a parsed YAML document (nil if it is empty). +// If mapLocations is not nil, it is applied to the location of every value. +func DecodeYAMLNode(file string, node *yaml.Node, dst any, mapLocations LocationMapper) (*StructVar, diag.Diagnostics, error) { + sv := &StructVar{Value: dst} + v := reflect.ValueOf(dst).Elem() + if node == nil { + // An empty document is null. + v.SetZero() + return sv, nil, nil + } + src, err := newYAMLSource(file, node, nil) + if err != nil { + return nil, nil, err + } + // Report YAML errors anywhere in the document, also in values that are dropped. + if err := validate(src); err != nil { + return nil, nil, err + } + src.mapLocations = mapLocations + var d decoder + n, ok, err := d.decode(v, src, nil) + if err == nil && !ok { + // The value could not be converted to dst at all (e.g. a list for a struct). + err = fmt.Errorf("expected a %s, found a %s", kindOf(v.Type()), src.kind()) + } + sv.Refs = n.refs(nil, nil) + sv.Locations = n.locations() + return sv, d.diags, err +} + +// Assign sets the value described by src at path, replacing what was there, with the +// references and locations of src. The value is converted to the type at path like +// [DecodeYAML] converts values; the diagnostics explain values that were dropped. A +// dropped slice element or map entry is removed, like [DecodeYAML] does. +func (sv *StructVar) Assign(path *structpath.PathNode, src View) (diag.Diagnostics, error) { + var d decoder + var n *dnode + dropped := false + err := update(reflect.ValueOf(sv.Value), components(path), func(dst reflect.Value) (bool, error) { + var ok bool + var err error + n, ok, err = d.decode(dst, viewSource{src}, path) + if !ok { + dst.SetZero() + dropped = err == nil + return false, err + } + return n.isZeroValue(dst), err + }) + if err != nil { + return d.diags, err + } + if dropped && path != nil { + return d.diags, sv.Delete(path) + } + sv.Refs = n.refs(path, withoutRefs(sv.Refs, path)) + sv.Locations = sv.Locations.With(path, n.locations()) + return d.diags, nil +} + +func validate(s source) error { + switch s.kind() { + case KindMap: + pairs, err := s.pairs() + if err != nil { + return err + } + for _, p := range pairs { + if err := validate(p.value); err != nil { + return err + } + } + case KindSequence: + elems, err := s.elems() + if err != nil { + return err + } + for _, e := range elems { + if err := validate(e); err != nil { + return err + } + } + default: + } + return nil +} + +type decoder struct { + diags diag.Diagnostics +} + +// dnode is the decoded shape of a value: what the typed value does not hold. +type dnode struct { + kind Kind + locs []diag.Location + ref string + keys map[string]*dnode + elems []*dnode +} + +// locations returns the locations of the decoded value. +func (n *dnode) locations() *Locations { + if n == nil { + return nil + } + out := &Locations{locs: n.locs} + for k, c := range n.keys { + if out.keys == nil { + out.keys = make(map[string]*Locations, len(n.keys)) + } + out.keys[k] = c.locations() + } + for _, c := range n.elems { + out.elems = append(out.elems, c.locations()) + } + return out +} + +// refs returns refs (copied) plus the references of the decoded value at path. +func (n *dnode) refs(path *structpath.PathNode, refs map[string]string) map[string]string { + var out map[string]string + n.collectRefs(path, func(p *structpath.PathNode, ref string) { + if out == nil { + out = maps.Clone(refs) + if out == nil { + out = map[string]string{} + } + } + out[p.String()] = ref + }) + if out == nil { + return refs + } + return out +} + +func (n *dnode) collectRefs(path *structpath.PathNode, fn func(*structpath.PathNode, string)) { + if n == nil { + return + } + if n.ref != "" { + fn(path, n.ref) + } + for k, c := range n.keys { + c.collectRefs(structpath.NewStringKey(path, k), fn) + } + for i, c := range n.elems { + c.collectRefs(structpath.NewIndex(path, i), fn) + } +} + +func (d *decoder) warn(summary string, src source, path *structpath.PathNode) { + d.diags = d.diags.Append(diag.Diagnostic{ + Severity: diag.Warning, + Summary: summary, + Locations: []diag.Location{firstLocation(src)}, + Paths: []*structpath.PathNode{path}, + }) +} + +func firstLocation(src source) diag.Location { + locs := src.locations() + if len(locs) == 0 { + return diag.Location{} + } + return locs[0] +} + +func (d *decoder) typeMismatch(expected Kind, src source, path *structpath.PathNode) { + d.warn(fmt.Sprintf("expected %s, found %s", expected, src.kind()), src, path) +} + +func (d *decoder) nullWarning(expected Kind, src source, path *structpath.PathNode) { + d.warn(fmt.Sprintf("expected a %s value, found null", expected), src, path) +} + +func pureRefOf(src source) string { + if src.kind() != KindString { + return "" + } + s, _ := src.scalar().(string) + if IsPureVariableReference(s) { + return s + } + return "" +} + +// decode decodes src into dst (settable) and returns its meta. It reports false if the +// value cannot be decoded and is dropped (the diagnostics say why). +func (d *decoder) decode(dst reflect.Value, src source, path *structpath.PathNode) (*dnode, bool, error) { + typ := dst.Type() + if typ.Kind() == reflect.Pointer { + if src.kind() == KindNil { + n, ok, err := d.decode(reflect.New(typ.Elem()).Elem(), src, path) + if ok { + dst.SetZero() + } + return n, ok, err + } + p := reflect.New(typ.Elem()) + n, ok, err := d.decode(p.Elem(), src, path) + if ok { + dst.Set(p) + } + return n, ok, err + } + + n := &dnode{kind: src.kind(), locs: src.locations()} + ref := pureRefOf(src) + + switch typ.Kind() { + case reflect.Struct: + if IsSDKNativeType(typ) { + s, ok := d.decodeString(src, path) + if !ok { + return nil, false, nil + } + n.kind = KindString + if ref != "" { + n.ref = ref + dst.SetZero() + return n, true, nil + } + buf, err := json.Marshal(s) + if err != nil { + return nil, false, err + } + return n, true, json.Unmarshal(buf, dst.Addr().Interface()) + } + return d.decodeStruct(dst, src, path, n, ref) + case reflect.Map: + switch src.kind() { + case KindMap: + pairs, err := src.pairs() + if err != nil { + return nil, false, err + } + out := reflect.MakeMapWithSize(typ, len(pairs)) + n.keys = make(map[string]*dnode, len(pairs)) + for _, p := range pairs { + e := reflect.New(typ.Elem()).Elem() + en, ok, err := d.decode(e, p.value, structpath.NewStringKey(path, p.key)) + if err != nil { + return nil, false, err + } + if !ok { + continue + } + out.SetMapIndex(reflect.ValueOf(p.key).Convert(typ.Key()), e) + n.keys[p.key] = en + } + dst.Set(out) + return n, true, nil + case KindNil: + dst.SetZero() + return n, true, nil + default: + } + if ref != "" { + n.ref = ref + dst.SetZero() + return n, true, nil + } + d.typeMismatch(KindMap, src, path) + return nil, false, nil + case reflect.Slice: + switch src.kind() { + case KindSequence: + elems, err := src.elems() + if err != nil { + return nil, false, err + } + out := reflect.MakeSlice(typ, 0, len(elems)) + for _, ev := range elems { + e := reflect.New(typ.Elem()).Elem() + en, ok, err := d.decode(e, ev, structpath.NewIndex(path, out.Len())) + if err != nil { + return nil, false, err + } + if !ok { + continue + } + out = reflect.Append(out, e) + n.elems = append(n.elems, en) + } + dst.Set(out) + return n, true, nil + case KindNil: + dst.SetZero() + return n, true, nil + default: + } + if ref != "" { + n.ref = ref + dst.SetZero() + return n, true, nil + } + d.typeMismatch(KindSequence, src, path) + return nil, false, nil + case reflect.String: + s, ok := d.decodeString(src, path) + if !ok { + return nil, false, nil + } + n.kind = KindString + dst.SetString(s) + return n, true, nil + case reflect.Bool: + return d.decodeBool(dst, src, path, n, ref) + case reflect.Int, reflect.Int32, reflect.Int64: + return d.decodeInt(dst, src, path, n, ref) + case reflect.Float32, reflect.Float64: + return d.decodeFloat(dst, src, path, n, ref) + case reflect.Interface: + v, err := d.decodeAny(src, n) + if err != nil { + return nil, false, err + } + if v == nil { + dst.SetZero() + } else { + dst.Set(reflect.ValueOf(v)) + } + return n, true, nil + default: + return nil, false, fmt.Errorf("unsupported type: %s", typ.Kind()) + } +} + +// isAnchorContainer reports whether v is a YAML anchor or a non-empty sequence or map +// composed entirely of anchor containers. Anchors define reusable blocks and must not +// trigger "unknown field" warnings, including when nested inside a container. +func isAnchorContainer(v source) bool { + if v.anchor() { + return true + } + var elements []source + switch v.kind() { + case KindSequence: + elements, _ = v.elems() + case KindMap: + pairs, _ := v.pairs() + for _, p := range pairs { + elements = append(elements, p.value) + } + default: + return false + } + if len(elements) == 0 { + return false + } + for _, e := range elements { + if !isAnchorContainer(e) { + return false + } + } + return true +} + +func (d *decoder) decodeStruct(dst reflect.Value, src source, path *structpath.PathNode, n *dnode, ref string) (*dnode, bool, error) { + switch src.kind() { + case KindMap: + case KindNil: + dst.SetZero() + return n, true, nil + default: + if ref != "" { + n.ref = ref + dst.SetZero() + return n, true, nil + } + d.typeMismatch(KindMap, src, path) + return nil, false, nil + } + + pairs, err := src.pairs() + if err != nil { + return nil, false, err + } + + dst.SetZero() + info := GetStructInfo(dst.Type()) + n.keys = make(map[string]*dnode, len(pairs)) + for _, p := range pairs { + index, ok := info.Fields[p.key] + if !ok { + if isAnchorContainer(p.value) { + continue + } + // Special case: provide a more helpful message for "valueFrom" vs "value_from". + if _, hasValueFrom := info.Fields["value_from"]; p.key == "valueFrom" && hasValueFrom { + d.diags = d.diags.Append(diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Use 'value_from' instead of 'valueFrom'", + Detail: "The field 'valueFrom' should be 'value_from' (snake_case). The 'valueFrom' field will be ignored.", + Locations: p.keyLocations(), + Paths: []*structpath.PathNode{path}, + }) + continue + } + d.diags = d.diags.Append(diag.Diagnostic{ + Severity: diag.Warning, + Summary: "unknown field: " + p.key, + Locations: p.keyLocations(), + Paths: []*structpath.PathNode{path}, + }) + continue + } + + // Decode into a fresh value so a dropped value leaves the field (and any + // embedded struct pointers on the way to it) untouched. + ft := dst.Type().FieldByIndex(index).Type + fv := reflect.New(ft).Elem() + fn, ok, err := d.decode(fv, p.value, structpath.NewStringKey(path, p.key)) + if err != nil { + return nil, false, err + } + if !ok { + continue + } + GetOrNewFieldByIndex(dst, index).Set(fv) + n.keys[p.key] = fn + + // An explicitly set zero value must still serialize: add it to ForceSendFields. + if fn.isZeroValue(fv) { + addForceSend(dst, info, p.key) + } + } + return n, true, nil +} + +// isZeroValue mirrors dyn.Value.IsZero for the decoded value: nil, an empty map or +// sequence, or a zero scalar (a pure reference is not zero). +func (n *dnode) isZeroValue(v reflect.Value) bool { + switch n.kind { + case KindNil: + return true + case KindMap: + return len(n.keys) == 0 + case KindSequence: + return len(n.elems) == 0 + default: + } + if n.ref != "" { + return false + } + for v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface { + if v.IsNil() { + return true + } + v = v.Elem() + } + if v.Kind() == reflect.Struct { + // SDK native types are strings in the configuration tree. + s, ok := sdkNativeString(v, includeZero) + return !ok || s == "" + } + return v.IsZero() +} + +func (d *decoder) decodeString(src source, path *structpath.PathNode) (string, bool) { + switch src.kind() { + case KindString, KindTime: + return src.scalar().(string), true + case KindBool: + return strconv.FormatBool(src.scalar().(bool)), true + case KindInt: + return strconv.FormatInt(toInt64(src.scalar()), 10), true + case KindFloat: + return strconv.FormatFloat(src.scalar().(float64), 'f', -1, 64), true + case KindNil: + d.nullWarning(KindString, src, path) + return "", false + default: + d.typeMismatch(KindString, src, path) + return "", false + } +} + +func toInt64(v any) int64 { + switch v := v.(type) { + case int: + return int64(v) + case int64: + return v + default: + panic(fmt.Sprintf("unexpected int type %T", v)) + } +} + +func (d *decoder) decodeBool(dst reflect.Value, src source, path *structpath.PathNode, n *dnode, ref string) (*dnode, bool, error) { + switch src.kind() { + case KindBool: + dst.SetBool(src.scalar().(bool)) + return n, true, nil + case KindString: + // See https://yaml.org/type/bool.html. + switch src.scalar().(string) { + case "true", "True", "TRUE", "y", "Y", "yes", "Yes", "YES", "on", "On", "ON": + dst.SetBool(true) + case "false", "False", "FALSE", "n", "N", "no", "No", "NO", "off", "Off", "OFF": + dst.SetBool(false) + default: + if ref != "" { + n.ref = ref + dst.SetZero() + return n, true, nil + } + d.typeMismatch(KindBool, src, path) + return nil, false, nil + } + n.kind = KindBool + return n, true, nil + case KindNil: + d.nullWarning(KindBool, src, path) + return nil, false, nil + default: + d.typeMismatch(KindBool, src, path) + return nil, false, nil + } +} + +func (d *decoder) decodeInt(dst reflect.Value, src source, path *structpath.PathNode, n *dnode, ref string) (*dnode, bool, error) { + switch src.kind() { + case KindInt: + dst.SetInt(toInt64(src.scalar())) + return n, true, nil + case KindFloat: + f := src.scalar().(float64) + out := int64(f) + if f != float64(out) { + d.warn(fmt.Sprintf(`cannot accurately represent "%g" as integer due to precision loss`, f), src, path) + return nil, false, nil + } + dst.SetInt(out) + n.kind = KindInt + return n, true, nil + case KindString: + s := src.scalar().(string) + out, err := strconv.ParseInt(s, 10, 64) + if err != nil { + if ref != "" { + n.ref = ref + dst.SetZero() + return n, true, nil + } + d.warn(fmt.Sprintf("cannot parse %q as an integer", s), src, path) + return nil, false, nil + } + dst.SetInt(out) + n.kind = KindInt + return n, true, nil + case KindNil: + d.nullWarning(KindInt, src, path) + return nil, false, nil + default: + d.typeMismatch(KindInt, src, path) + return nil, false, nil + } +} + +func (d *decoder) decodeFloat(dst reflect.Value, src source, path *structpath.PathNode, n *dnode, ref string) (*dnode, bool, error) { + switch src.kind() { + case KindFloat: + dst.SetFloat(src.scalar().(float64)) + return n, true, nil + case KindInt: + i := toInt64(src.scalar()) + out := float64(i) + if i != int64(out) { + d.warn(fmt.Sprintf(`cannot accurately represent "%d" as floating point number due to precision loss`, i), src, path) + return nil, false, nil + } + dst.SetFloat(out) + n.kind = KindFloat + return n, true, nil + case KindString: + s := src.scalar().(string) + out, err := strconv.ParseFloat(s, 64) + if err != nil { + if ref != "" { + n.ref = ref + dst.SetZero() + return n, true, nil + } + d.warn(fmt.Sprintf("cannot parse %q as a floating point number", s), src, path) + return nil, false, nil + } + dst.SetFloat(out) + n.kind = KindFloat + return n, true, nil + case KindNil: + d.nullWarning(KindFloat, src, path) + return nil, false, nil + default: + d.typeMismatch(KindFloat, src, path) + return nil, false, nil + } +} + +// decodeAny returns the generic Go value for src (maps, slices, scalars) and fills in +// the meta of everything below n. Timestamps become the string they were written as. +func (d *decoder) decodeAny(src source, n *dnode) (any, error) { + switch src.kind() { + case KindMap: + pairs, err := src.pairs() + if err != nil { + return nil, err + } + out := make(map[string]any, len(pairs)) + n.keys = make(map[string]*dnode, len(pairs)) + for _, p := range pairs { + cn := &dnode{kind: p.value.kind(), locs: p.value.locations()} + v, err := d.decodeAny(p.value, cn) + if err != nil { + return nil, err + } + out[p.key] = v + n.keys[p.key] = cn + } + return out, nil + case KindSequence: + elems, err := src.elems() + if err != nil { + return nil, err + } + out := make([]any, len(elems)) + for i, e := range elems { + cn := &dnode{kind: e.kind(), locs: e.locations()} + v, err := d.decodeAny(e, cn) + if err != nil { + return nil, err + } + out[i] = v + n.elems = append(n.elems, cn) + } + return out, nil + case KindTime: + n.kind = KindString + return src.scalar(), nil + case KindNil: + return nil, nil + default: + return src.scalar(), nil + } +} + +// viewSource is a [source] backed by a view. +type viewSource struct { + v View +} + +func (s viewSource) kind() Kind { return s.v.Kind() } +func (s viewSource) locations() []diag.Location { return s.v.Locations() } +func (s viewSource) anchor() bool { return false } + +func (s viewSource) scalar() any { + if str, ok := s.v.AsString(); ok { + return str + } + return s.v.AsAny() +} + +func (s viewSource) pairs() ([]sourcePair, error) { + var out []sourcePair + for k, c := range s.v.MapItems() { + out = append(out, sourcePair{key: k, value: viewSource{c}}) + } + return out, nil +} + +func (s viewSource) elems() ([]source, error) { + var out []source + for _, c := range s.v.Sequence() { + out = append(out, viewSource{c}) + } + return out, nil +} + +// kindOf returns the kind of configuration tree values of type t. +func kindOf(t reflect.Type) Kind { + for t.Kind() == reflect.Pointer { + t = t.Elem() + } + switch t.Kind() { + case reflect.Struct, reflect.Map: + return KindMap + case reflect.Slice: + return KindSequence + case reflect.Bool: + return KindBool + case reflect.Int, reflect.Int32, reflect.Int64: + return KindInt + case reflect.Float32, reflect.Float64: + return KindFloat + default: + return KindString + } +} diff --git a/libs/structs/structvar/decode_json.go b/libs/structs/structvar/decode_json.go new file mode 100644 index 00000000000..e332aa9d863 --- /dev/null +++ b/libs/structs/structvar/decode_json.go @@ -0,0 +1,126 @@ +package structvar + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "slices" + "strconv" + + "github.com/databricks/cli/libs/diag" + "go.yaml.in/yaml/v3" +) + +// ParseJSON parses data into a YAML node tree that carries the positions of the values +// in data: the start of an object or array, the first character of a key and the end of +// a scalar (the decoder does not report where a scalar starts). +func ParseJSON(source string, data []byte) (*yaml.Node, error) { + decoder := json.NewDecoder(bytes.NewReader(data)) + // Use json.Number to avoid losing precision on int64 values above 2^53 (e.g. job and run IDs). + decoder.UseNumber() + + lines := newLineIndex(data) + node, err := decodeJSONValue(decoder, lines) + if err == nil && decoder.More() { + err = errors.New("unexpected additional content") + } + if err != nil { + if errors.Is(err, io.EOF) { + err = errors.New("unexpected end of JSON input") + } + line, column := lines.position(decoder.InputOffset()) + return nil, fmt.Errorf("error decoding JSON at %s: %w", diag.Location{File: source, Line: line, Column: column}, err) + } + return node, nil +} + +// lineIndex holds the offsets at which the lines of the input start. +type lineIndex []int64 + +func newLineIndex(data []byte) lineIndex { + lines := lineIndex{0} + for i, b := range data { + if b == '\n' { + lines = append(lines, int64(i+1)) + } + } + return lines +} + +// position returns the 1-based line and column of offset. +func (l lineIndex) position(offset int64) (line, column int) { + // The line is the last one starting at or before offset. + i, found := slices.BinarySearch(l, offset) + if !found { + i-- + } + return i + 1, int(offset-l[i]) + 1 +} + +func newNode(lines lineIndex, offset int64, kind yaml.Kind, tag, value string) *yaml.Node { + line, column := lines.position(offset) + return &yaml.Node{Kind: kind, Tag: tag, Value: value, Line: line, Column: column} +} + +func decodeJSONValue(decoder *json.Decoder, lines lineIndex) (*yaml.Node, error) { + token, err := decoder.Token() + if err != nil { + return nil, err + } + offset := decoder.InputOffset() + + switch tok := token.(type) { + case json.Delim: + if tok == '{' { + node := newNode(lines, offset-1, yaml.MappingNode, "!!map", "") + for decoder.More() { + keyToken, err := decoder.Token() + if err != nil { + return nil, err + } + key, ok := keyToken.(string) + if !ok { + return nil, errors.New("expected string for object key") + } + // The decoder reports the end of the key: step back over the key and the closing quote. + keyNode := newNode(lines, decoder.InputOffset()-int64(len(key)+1), yaml.ScalarNode, "!!str", key) + value, err := decodeJSONValue(decoder, lines) + if err != nil { + return nil, err + } + node.Content = append(node.Content, keyNode, value) + } + // Consume the closing '}'. + _, err := decoder.Token() + return node, err + } + node := newNode(lines, offset-1, yaml.SequenceNode, "!!seq", "") + for decoder.More() { + value, err := decodeJSONValue(decoder, lines) + if err != nil { + return nil, err + } + node.Content = append(node.Content, value) + } + // Consume the closing ']'. + _, err := decoder.Token() + return node, err + case json.Number: + if _, err := tok.Int64(); err == nil { + return newNode(lines, offset, yaml.ScalarNode, "!!int", tok.String()), nil + } + // Integers that overflow int64 fall back to float64. + if _, err := tok.Float64(); err != nil { + return nil, fmt.Errorf("invalid number %q: %w", tok.String(), err) + } + return newNode(lines, offset, yaml.ScalarNode, "!!float", tok.String()), nil + case string: + return newNode(lines, offset, yaml.ScalarNode, "!!str", tok), nil + case bool: + return newNode(lines, offset, yaml.ScalarNode, "!!bool", strconv.FormatBool(tok)), nil + default: + return newNode(lines, offset, yaml.ScalarNode, "!!null", "null"), nil + } +} diff --git a/libs/structs/structvar/decode_yaml.go b/libs/structs/structvar/decode_yaml.go new file mode 100644 index 00000000000..0b3e85234ee --- /dev/null +++ b/libs/structs/structvar/decode_yaml.go @@ -0,0 +1,370 @@ +package structvar + +import ( + "errors" + "fmt" + "io" + "math" + "strconv" + "strings" + "time" + + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/structs/structpath" + "go.yaml.in/yaml/v3" +) + +// LocationError is a YAML syntax error with the location it occurred at. +type LocationError struct { + Loc diag.Location + Summary string +} + +func (e *LocationError) Error() string { + return fmt.Sprintf("yaml (%s): %s", e.Loc, e.Summary) +} + +// yamlSource is a [source] backed by a YAML node. Aliases are followed, merge keys +// are applied, and scalars are resolved by their tag. +type yamlSource struct { + file string + node *yaml.Node + + // The node the value comes from, after following aliases. + target *yaml.Node + + // Aliases followed on the way from the document root to this value, to detect + // cyclic anchors. + aliases *aliasChain + + // If set, mapLocations is applied to the locations of the value at path. + mapLocations LocationMapper + path *structpath.PathNode + + // Resolved scalar. + k Kind + v any +} + +// ParseYAML parses the YAML document in r. It returns nil for an empty document. +func ParseYAML(r io.Reader) (*yaml.Node, error) { + var doc yaml.Node + err := yaml.NewDecoder(r).Decode(&doc) + if errors.Is(err, io.EOF) { + return nil, nil + } + if err != nil { + return nil, err + } + return doc.Content[0], nil +} + +// aliasChain is an immutable list of alias nodes. +type aliasChain struct { + node *yaml.Node + prev *aliasChain +} + +func (c *aliasChain) contains(n *yaml.Node) bool { + for ; c != nil; c = c.prev { + if c.node == n { + return true + } + } + return false +} + +func newYAMLSource(file string, node *yaml.Node, aliases *aliasChain) (*yamlSource, error) { + s := &yamlSource{file: file, node: node, target: node, aliases: aliases} + loc := s.loc(node) + for s.target.Kind == yaml.AliasNode { + // The same alias may be reached again through another path, which is not a + // cycle; only an alias inside its own expansion is. + if s.aliases.contains(s.target) { + return nil, yamlErrorf(loc, "cyclic reference to anchor %q", s.target.Value) + } + s.aliases = &aliasChain{node: s.target, prev: s.aliases} + s.target = s.target.Alias + } + + switch s.target.Kind { + case yaml.MappingNode: + s.k = KindMap + case yaml.SequenceNode: + s.k = KindSequence + case yaml.ScalarNode: + if err := s.resolveScalar(loc); err != nil { + return nil, err + } + default: + return nil, yamlErrorf(loc, "unknown node kind: %v", s.target.Kind) + } + return s, nil +} + +func yamlErrorf(loc diag.Location, format string, args ...any) error { + return fmt.Errorf("yaml (%s): %s", loc, fmt.Sprintf(format, args...)) +} + +func (s *yamlSource) loc(n *yaml.Node) diag.Location { + return diag.Location{File: s.file, Line: n.Line, Column: n.Column} +} + +func intValue(i64 int64) any { + // Use regular int type instead of int64 if possible. + if i64 >= math.MinInt32 && i64 <= math.MaxInt32 { + return int(i64) + } + return i64 +} + +func (s *yamlSource) resolveScalar(loc diag.Location) error { + n := s.target + switch st := n.ShortTag(); st { + case "!!str": + s.k, s.v = KindString, n.Value + case "!!bool": + switch strings.ToLower(n.Value) { + case "true": + s.k, s.v = KindBool, true + case "false": + s.k, s.v = KindBool, false + default: + return yamlErrorf(loc, "invalid bool value: %v", n.Value) + } + case "!!int": + // Try to parse the integer value in base 10. Trim leading zeros to avoid + // octal parsing of the "0" prefix (YAML 1.2 spec example 2.19). + i64, err := strconv.ParseInt(strings.TrimLeft(n.Value, "0"), 10, 64) + if err != nil { + // Let ParseInt figure out the base. + i64, err = strconv.ParseInt(n.Value, 0, 64) + } + if err != nil { + return yamlErrorf(loc, "invalid int value: %v", n.Value) + } + s.k, s.v = KindInt, intValue(i64) + case "!!float": + f64, err := strconv.ParseFloat(n.Value, 64) + if err == nil { + s.k, s.v = KindFloat, f64 + break + } + // Deal with infinity prefixes. + v := strings.ToLower(n.Value) + switch { + case strings.HasPrefix(v, "+"): + v = strings.TrimPrefix(v, "+") + f64 = math.Inf(1) + case strings.HasPrefix(v, "-"): + v = strings.TrimPrefix(v, "-") + f64 = math.Inf(-1) + default: + f64 = math.Inf(1) + } + switch v { + case ".inf": + s.k, s.v = KindFloat, f64 + case ".nan": + s.k, s.v = KindFloat, math.NaN() + default: + return yamlErrorf(loc, "invalid float value: %v", n.Value) + } + case "!!null": + s.k = KindNil + case "!!timestamp": + if !isTimestamp(n.Value) { + return yamlErrorf(loc, "invalid timestamp value: %v", n.Value) + } + // Timestamps are kept as the string they were written as. + s.k, s.v = KindTime, n.Value + default: + return yamlErrorf(loc, "unknown tag: %v", st) + } + return nil +} + +func isTimestamp(s string) bool { + for _, layout := range []string{ + "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. + "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". + "2006-1-2 15:4:5.999999999", // space separated with no time zone + "2006-1-2", // date only + } { + if _, err := time.Parse(layout, s); err == nil { + return true + } + } + return false +} + +func (s *yamlSource) kind() Kind { + return s.k +} + +func (s *yamlSource) locations() []diag.Location { + // An alias has the location of the anchored value. + locs := []diag.Location{s.loc(s.target)} + if s.mapLocations != nil { + return s.mapLocations(s.path, locs) + } + return locs +} + +func (s *yamlSource) anchor() bool { + return s.node.Anchor != "" || s.target.Anchor != "" +} + +func (s *yamlSource) scalar() any { + return s.v +} + +// child returns the source of n, the value at path below this one (computed by path, +// only when locations are mapped). +func (s *yamlSource) child(n *yaml.Node, path func() *structpath.PathNode) (*yamlSource, error) { + c, err := newYAMLSource(s.file, n, s.aliases) + if err != nil { + return nil, err + } + if s.mapLocations != nil { + c.mapLocations = s.mapLocations + c.path = path() + } + return c, nil +} + +func (s *yamlSource) pairs() ([]sourcePair, error) { + node := s.target + loc := s.loc(node) + var merge *yaml.Node + var acc pairSet + for i := 0; i < len(node.Content); i += 2 { + key := node.Content[i] + val := node.Content[i+1] + + if key.Kind != yaml.ScalarNode { + return nil, yamlErrorf(loc, "key is not a scalar") + } + + switch st := key.ShortTag(); st { + case "!!str": + case "!!null": + // A literal unquoted "null" is treated as a null value by the YAML parser. + // However, when used as a key, it is treated as the string "null". + case "!!merge": + if merge != nil { + // The YAML merge key spec allows a single '<<' key per mapping. + return nil, &LocationError{ + Loc: s.loc(key), + Summary: "duplicate YAML merge key ('<<') is not allowed; to merge multiple maps, use a sequence: '<<: [*anchor1, *anchor2]'", + } + } + merge = val + continue + default: + return nil, yamlErrorf(loc, "invalid key tag: %v", st) + } + + v, err := s.child(val, func() *structpath.PathNode { return structpath.NewStringKey(s.path, key.Value) }) + if err != nil { + return nil, err + } + acc.set(sourcePair{key: key.Value, keyLoc: s.loc(key), value: v}) + } + + if merge == nil { + return acc.pairs, nil + } + + mloc := s.loc(merge) + merr := yamlErrorf(mloc, "map merge requires map or sequence of maps as the value") + + var mnodes []*yaml.Node + switch merge.Kind { + case yaml.SequenceNode: + mnodes = merge.Content + case yaml.AliasNode: + mnodes = []*yaml.Node{merge} + default: + return nil, merr + } + + // Merged maps come first; the entries of this mapping take precedence. + var out pairSet + for _, n := range mnodes { + // The merged mapping's values are at the paths of this mapping's. + ms, err := s.child(n, func() *structpath.PathNode { return s.path }) + if err != nil { + return nil, err + } + if ms.kind() != KindMap { + return nil, merr + } + mps, err := ms.pairs() + if err != nil { + return nil, err + } + for _, p := range mps { + out.set(p) + } + } + for _, p := range acc.pairs { + out.set(p) + } + return out.pairs, nil +} + +// setPair sets the pair in pairs: an existing key keeps its position and key location. +// pairSet is a list of pairs with unique keys, in the order the keys were first set. +type pairSet struct { + pairs []sourcePair + index map[string]int +} + +// pairSetIndexMin is the size from which a pairSet indexes its keys; smaller sets (most +// mappings) are searched linearly, which allocates nothing. +const pairSetIndexMin = 16 + +// set adds p, or replaces the value of the pair with the same key (keeping its key +// location and position). +func (s *pairSet) set(p sourcePair) { + if i, ok := s.find(p.key); ok { + s.pairs[i].value = p.value + return + } + if s.index != nil { + s.index[p.key] = len(s.pairs) + } + s.pairs = append(s.pairs, p) + if s.index == nil && len(s.pairs) >= pairSetIndexMin { + s.index = make(map[string]int, 2*len(s.pairs)) + for i, q := range s.pairs { + s.index[q.key] = i + } + } +} + +func (s *pairSet) find(key string) (int, bool) { + if s.index != nil { + i, ok := s.index[key] + return i, ok + } + for i := range s.pairs { + if s.pairs[i].key == key { + return i, true + } + } + return 0, false +} + +func (s *yamlSource) elems() ([]source, error) { + var out []source + for i, n := range s.target.Content { + v, err := s.child(n, func() *structpath.PathNode { return structpath.NewIndex(s.path, i) }) + if err != nil { + return nil, err + } + out = append(out, v) + } + return out, nil +} diff --git a/libs/dyn/kind.go b/libs/structs/structvar/kind.go similarity index 54% rename from libs/dyn/kind.go rename to libs/structs/structvar/kind.go index 1890e9e2ccd..a0775f98afe 100644 --- a/libs/dyn/kind.go +++ b/libs/structs/structvar/kind.go @@ -1,9 +1,8 @@ -package dyn +package structvar -import ( - "fmt" -) +import "fmt" +// Kind is the kind of a value in the configuration tree. type Kind int const ( @@ -19,31 +18,6 @@ const ( KindNil ) -func kindOf(v any) Kind { - switch v.(type) { - case Mapping: - return KindMap - case []Value: - return KindSequence - case string: - return KindString - case bool: - return KindBool - case int, int8, int16, int32, int64: - return KindInt - case uint, uint8, uint16, uint32, uint64: - return KindInt - case float32, float64: - return KindFloat - case Time: - return KindTime - case nil: - return KindNil - default: - panic(fmt.Sprintf("not handled: %T", v)) - } -} - func (k Kind) String() string { switch k { case KindInvalid: diff --git a/libs/structs/structvar/locations.go b/libs/structs/structvar/locations.go new file mode 100644 index 00000000000..e0e03d3edc6 --- /dev/null +++ b/libs/structs/structvar/locations.go @@ -0,0 +1,292 @@ +package structvar + +import ( + "iter" + "maps" + "slices" + + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/structs/structpath" +) + +// Locations records the source locations of the values of a typed value, as a tree +// that follows the value: one node per map key or sequence index. +// +// Locations is immutable: operations that change it return a new tree that shares +// unchanged subtrees, so copies of a struct holding one can share it safely. +// A nil *Locations is an empty tree. +type Locations struct { + locs []diag.Location + keys map[string]*Locations + elems []*Locations +} + +// pathComponent is a key or an index of a path. +type pathComponent struct { + key string + index int + isKey bool +} + +// components returns the keys and indices of p. +func components(p *structpath.PathNode) []pathComponent { + var out []pathComponent + for _, n := range p.AsSlice() { + if k, ok := n.StringKey(); ok { + out = append(out, pathComponent{key: k, isKey: true}) + } else if i, ok := n.Index(); ok { + out = append(out, pathComponent{index: i}) + } + } + return out +} + +func (l *Locations) child(c pathComponent) *Locations { + if l == nil { + return nil + } + if c.isKey { + return l.keys[c.key] + } + if c.index < 0 || c.index >= len(l.elems) { + return nil + } + return l.elems[c.index] +} + +// Key returns the locations of map key or struct field k. +func (l *Locations) Key(k string) *Locations { + return l.child(pathComponent{key: k, isKey: true}) +} + +// Index returns the locations of sequence element i. +func (l *Locations) Index(i int) *Locations { + return l.child(pathComponent{index: i}) +} + +// KeyItems returns the keys of l that have locations recorded below them, and their trees. +func (l *Locations) KeyItems() iter.Seq2[string, *Locations] { + return func(yield func(string, *Locations) bool) { + if l == nil { + return + } + for k, c := range l.keys { + if c != nil && !yield(k, c) { + return + } + } + } +} + +// Elems returns the sequence indices of l that have locations recorded below them, and +// their trees. +func (l *Locations) Elems() iter.Seq2[int, *Locations] { + return func(yield func(int, *Locations) bool) { + if l == nil { + return + } + for i, c := range l.elems { + if c != nil && !yield(i, c) { + return + } + } + } +} + +// Get returns the locations recorded for the value itself. +func (l *Locations) Get() []diag.Location { + if l == nil { + return nil + } + return slices.Clone(l.locs) +} + +// Exact returns the locations of the value at path. Values without recorded locations +// (e.g. set by code) have none. +func (l *Locations) Exact(path *structpath.PathNode) []diag.Location { + return l.lookup(components(path)).Get() +} + +// Nearest returns the locations of the value at path or, if it has none, of the +// closest ancestor that has some. Use it to point diagnostics at the most specific +// place there is, not to decide where a value was defined. +func (l *Locations) Nearest(path *structpath.PathNode) []diag.Location { + var nearest []diag.Location + n := l + for _, c := range components(path) { + if n == nil { + break + } + if len(n.locs) > 0 { + nearest = n.locs + } + n = n.child(c) + } + if n != nil && len(n.locs) > 0 { + nearest = n.locs + } + return slices.Clone(nearest) +} + +func (l *Locations) lookup(path []pathComponent) *Locations { + n := l + for _, c := range path { + n = n.child(c) + if n == nil { + return nil + } + } + return n +} + +// Sub returns the locations of the value at path and below. +func (l *Locations) Sub(path *structpath.PathNode) *Locations { + return l.lookup(components(path)) +} + +// With returns a copy of l where the locations of the value at path and below are sub. +func (l *Locations) With(path *structpath.PathNode, sub *Locations) *Locations { + return l.with(components(path), sub) +} + +func (l *Locations) with(path []pathComponent, sub *Locations) *Locations { + if len(path) == 0 { + return sub + } + child := l.child(path[0]).with(path[1:], sub) + return l.withChild(path[0], child) +} + +func (l *Locations) clone() *Locations { + if l == nil { + return &Locations{} + } + c := *l + c.keys = maps.Clone(l.keys) + c.elems = slices.Clone(l.elems) + return &c +} + +func (l *Locations) withChild(c pathComponent, child *Locations) *Locations { + out := l.clone() + out.setChild(c, child) + return out +} + +// setChild sets the child c of l in place. Only use it on a node the caller owns, +// e.g. one just returned by [Locations.WithLocations], which builds a fresh node. +func (l *Locations) setChild(c pathComponent, child *Locations) { + if c.isKey { + if child == nil { + delete(l.keys, c.key) + return + } + if l.keys == nil { + l.keys = map[string]*Locations{} + } + l.keys[c.key] = child + return + } + if c.index < 0 { + return + } + for len(l.elems) <= c.index { + l.elems = append(l.elems, nil) + } + l.elems[c.index] = child +} + +// WithLocations returns a copy of l where the value itself has the locations locs. +func (l *Locations) WithLocations(locs []diag.Location) *Locations { + c := l.clone() + c.locs = slices.Clone(locs) + return c +} + +// WithSequence returns a copy of l where the elements of the sequence at path are +// rebuilt from the old ones: new element i has the locations of old elements sources[i] +// merged (see [MergeLocations]). Use it after reordering, merging, splitting or +// dropping sequence elements. +func (l *Locations) WithSequence(path *structpath.PathNode, sources [][]int) *Locations { + p := components(path) + n := l.lookup(p) + if n == nil { + return l + } + c := n.clone() + c.elems = make([]*Locations, len(sources)) + for i, src := range sources { + var merged *Locations + for j, k := range src { + if j == 0 { + merged = n.Index(k) + continue + } + merged = MergeLocations(merged, n.Index(k)) + } + c.elems[i] = merged + } + return l.with(p, c) +} + +// MergeLocations returns the locations of the value obtained by merging the value +// described by b into the one described by a, following merge.Merge: maps and +// sequences keep a's locations and accumulate b's, other values take b's and +// accumulate a's. +func MergeLocations(a, b *Locations) *Locations { + switch { + case a == nil: + return b + case b == nil: + return a + } + isComposite := len(a.keys) > 0 || len(a.elems) > 0 || len(b.keys) > 0 || len(b.elems) > 0 + if !isComposite { + return b.WithLocations(concatLocations(b.locs, a.locs)) + } + c := a.clone() + c.locs = concatLocations(a.locs, b.locs) + for k, bv := range b.keys { + if c.keys == nil { + c.keys = map[string]*Locations{} + } + c.keys[k] = MergeLocations(a.keys[k], bv) + } + c.elems = append(slices.Clone(a.elems), b.elems...) + return c +} + +func concatLocations(a, b []diag.Location) []diag.Location { + if len(b) == 0 { + return a + } + return append(slices.Clone(a), b...) +} + +// Map returns a copy of l where the locations of every value are replaced by fn(path, locations). +func (l *Locations) Map(fn func(path *structpath.PathNode, locs []diag.Location) []diag.Location) *Locations { + return l.mapLocations(nil, fn) +} + +func (l *Locations) mapLocations(path *structpath.PathNode, fn func(*structpath.PathNode, []diag.Location) []diag.Location) *Locations { + if l == nil { + return nil + } + c := l.clone() + c.locs = fn(path, slices.Clone(l.locs)) + for k, v := range c.keys { + c.keys[k] = v.mapLocations(structpath.NewStringKey(path, k), fn) + } + for i, v := range c.elems { + c.elems[i] = v.mapLocations(structpath.NewIndex(path, i), fn) + } + return c +} + +// child returns the path of this component below prefix. +func (c pathComponent) child(prefix *structpath.PathNode) *structpath.PathNode { + if c.isKey { + return structpath.NewStringKey(prefix, c.key) + } + return structpath.NewIndex(prefix, c.index) +} diff --git a/libs/structs/structvar/lookup.go b/libs/structs/structvar/lookup.go new file mode 100644 index 00000000000..b4f3e3f1465 --- /dev/null +++ b/libs/structs/structvar/lookup.go @@ -0,0 +1,157 @@ +package structvar + +import ( + "fmt" + "reflect" + "slices" + + "github.com/databricks/cli/libs/structs/structpath" +) + +// LookupError is returned by [View.LookupWithDefaults] when the path cannot be followed. +type LookupError struct { + msg string +} + +func (e *LookupError) Error() string { + return e.msg +} + +// KeyNotFoundError is returned by [View.LookupWithDefaults] for a missing key. +type KeyNotFoundError struct { + Path *structpath.PathNode + Suggestions []string +} + +func (e *KeyNotFoundError) Error() string { + return fmt.Sprintf("key not found at %q%s", e.Path, DidYouMean(e.Suggestions)) +} + +func indexedKind(c pathComponent) string { + if c.isKey { + return "map" + } + return "sequence" +} + +// LookupWithDefaults returns the value at path. Fields that are declared in the type +// but not set resolve to their zero value: e.g. ${bundle.git.origin_url} resolves to an +// empty string if a bundle isn't located in a Git repository (yet). +func (x View) LookupWithDefaults(path *structpath.PathNode) (View, error) { + var p *structpath.PathNode + for _, c := range components(path) { + p = c.child(p) + kind := x.Kind() + if c.isKey { + switch kind { + case KindMap: + case KindNil: + return View{}, &LookupError{fmt.Sprintf("expected a %s to index %q, found nil", indexedKind(c), p)} + default: + return View{}, &LookupError{fmt.Sprintf("expected a map to index %q, found %s", p, kind)} + } + child := x.Get(c.key) + if !child.IsValid() { + zero, ok := zeroField(x, c.key) + if !ok { + return View{}, &KeyNotFoundError{Path: p, Suggestions: SuggestKeys(lookupKeys(x), c.key)} + } + child = zero + } + x = child + continue + } + + switch kind { + case KindSequence: + case KindNil: + return View{}, &LookupError{fmt.Sprintf("expected a %s to index %q, found nil", indexedKind(c), p)} + default: + return View{}, &LookupError{fmt.Sprintf("expected a sequence to index %q, found %s", p, kind)} + } + child := x.Index(c.index) + if !child.IsValid() { + return View{}, &LookupError{fmt.Sprintf("index out of bounds at %q", p)} + } + x = child + } + return x, nil +} + +// structType returns the struct type of the value, if it is a struct. +func structType(x View) (reflect.Type, bool) { + t := x.Reflect().Type() + for t.Kind() == reflect.Pointer || t.Kind() == reflect.Interface { + if t.Kind() == reflect.Interface { + v := x.Reflect() + for v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface { + if v.IsNil() { + return nil, false + } + v = v.Elem() + } + t = v.Type() + continue + } + t = t.Elem() + } + return t, t.Kind() == reflect.Struct && !IsSDKNativeType(t) +} + +// fillableFieldType returns the type of the struct field with JSON name k, if a field of +// that type is given a zero value when it is not set (see convert.IncludeMissingFields). +func fillableFieldType(t reflect.Type, k string) (reflect.Type, bool) { + info := GetStructInfo(t) + index, ok := info.Fields[k] + if !ok { + return nil, false + } + ft := t.FieldByIndex(index).Type + for ft.Kind() == reflect.Pointer { + ft = ft.Elem() + } + switch ft.Kind() { + case reflect.Struct, reflect.Map, reflect.Slice, reflect.String, reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64: + return ft, true + default: + return nil, false + } +} + +// zeroField returns the zero value of the struct field k of x that is not set. +func zeroField(x View, k string) (View, bool) { + t, ok := structType(x) + if !ok { + return View{}, false + } + ft, ok := fillableFieldType(t, k) + if !ok { + return View{}, false + } + zero := reflect.New(ft) + switch ft.Kind() { + case reflect.Map: + zero.Elem().Set(reflect.MakeMap(ft)) + case reflect.Slice: + zero.Elem().Set(reflect.MakeSlice(ft, 0, 0)) + default: + } + return NewView(zero.Interface(), nil, nil), true +} + +// lookupKeys returns the keys a lookup in x can find, for "did you mean" suggestions. +func lookupKeys(x View) []string { + var keys []string + for k := range x.MapItems() { + keys = append(keys, k) + } + if t, ok := structType(x); ok { + for _, k := range GetStructInfo(t).FieldNames { + if _, ok := fillableFieldType(t, k); ok && !slices.Contains(keys, k) { + keys = append(keys, k) + } + } + } + return keys +} diff --git a/libs/structs/structvar/merge.go b/libs/structs/structvar/merge.go new file mode 100644 index 00000000000..a80d50c8010 --- /dev/null +++ b/libs/structs/structvar/merge.go @@ -0,0 +1,225 @@ +package structvar + +import ( + "fmt" + "maps" + "reflect" + "slices" + + "github.com/databricks/cli/libs/structs/structpath" +) + +// Merge merges the value described by src into the value at path, with the semantics +// of merge.Merge: maps are merged recursively, sequences are concatenated, primitive +// values are replaced, and a nil value on either side yields the other one. +// Locations accumulate like merge.Merge accumulates them. A failed merge changes nothing. +func (sv *StructVar) Merge(path *structpath.PathNode, src View) error { + dstView := sv.View().Lookup(path) + if err := CheckMerge(dstView, src); err != nil { + return err + } + + m := merger{refs: maps.Clone(sv.Refs)} + var locs *Locations + err := update(reflect.ValueOf(sv.Value), components(path), func(dst reflect.Value) (bool, error) { + dstView.v = dst + var err error + locs, err = m.merge(dst, dstView, src, path) + return isZeroScalar(src), err + }) + if err != nil { + return err + } + sv.Refs = m.refs + sv.Locations = sv.Locations.With(path, locs) + return nil +} + +// isZeroScalar reports whether src is a present zero scalar, which must be kept as an +// explicitly set value. +func isZeroScalar(src View) bool { + k := src.Kind() + return k != KindNil && k != KindMap && k != KindSequence && src.isZero() +} + +// isNil reports whether v is a nil pointer, map, slice or interface. +func isNil(v reflect.Value) bool { + switch v.Kind() { + case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Interface: + return v.IsNil() + default: + return false + } +} + +// CheckMerge returns the error [StructVar.Merge] would return for merging src into dst. +func CheckMerge(dst, src View) error { + ak, bk := dst.Kind(), src.Kind() + if ak == KindInvalid || ak == KindNil || bk == KindInvalid || bk == KindNil { + return nil + } + switch ak { + case KindMap: + if bk != KindMap { + return fmt.Errorf("cannot merge map with %s", bk) + } + for k, sc := range src.MapItems() { + if err := CheckMerge(dst.Get(k), sc); err != nil { + return err + } + } + return nil + case KindSequence: + if bk != KindSequence { + return fmt.Errorf("cannot merge sequence with %s", bk) + } + return nil + default: + if ak != bk { + return fmt.Errorf("cannot merge %s with %s", ak, bk) + } + return nil + } +} + +type merger struct { + refs map[string]string +} + +// assign sets dst to src (see [StructVar.Assign]) and returns its locations. +func (m *merger) assign(dst reflect.Value, src View, path *structpath.PathNode) (*Locations, error) { + var d decoder + n, ok, err := d.decode(dst, viewSource{src}, path) + if err != nil { + return nil, err + } + if !ok { + dst.SetZero() + } + m.refs = n.refs(path, withoutRefs(m.refs, path)) + return n.locations(), nil +} + +// merge merges src into dst (settable; dstView is its view, invalid if absent). +func (m *merger) merge(dst reflect.Value, dstView, src View, path *structpath.PathNode) (*Locations, error) { + ak, bk := dstView.Kind(), src.Kind() + switch { + case bk == KindInvalid: + return dstView.loc, nil + case ak == KindInvalid: + return m.assign(dst, src, path) + case ak == KindNil: + locs, err := m.assign(dst, src, path) + return locs.WithLocations(concatLocations(src.loc.Get(), dstView.loc.Get())), err + case bk == KindNil: + return dstView.loc.WithLocations(concatLocations(dstView.loc.Get(), src.loc.Get())), nil + } + + switch ak { + case KindMap: + out := dstView.loc.WithLocations(concatLocations(dstView.loc.Get(), src.loc.Get())) + v := derefAlloc(dst) + for k, sc := range src.MapItems() { + c := pathComponent{key: k, isKey: true} + cp := structpath.NewStringKey(path, k) + var locs *Locations + err := updateChild(v, c, func(child reflect.Value) (bool, error) { + childView := dstView.Get(k) + childView.v = child + var err error + locs, err = m.merge(child, childView, sc, cp) + // A zero scalar, or a null that left the field empty, was written + // explicitly and must stay set. + return isZeroScalar(sc) || sc.Kind() == KindNil && isNil(child), err + }) + if err != nil { + return nil, err + } + out.setChild(c, locs) + } + return out, nil + case KindSequence: + out := dstView.loc.WithLocations(concatLocations(dstView.loc.Get(), src.loc.Get())) + v := derefAlloc(dst) + holder := v + if v.Kind() == reflect.Interface { + // A sequence held in an interface (e.g. a complex variable default) is + // appended to as a copy and stored back. + v = reflect.New(holder.Elem().Type()).Elem() + v.Set(holder.Elem()) + } + for _, sc := range src.Sequence() { + i := v.Len() + e := reflect.New(v.Type().Elem()).Elem() + locs, err := m.assign(e, sc, structpath.NewIndex(path, i)) + if err != nil { + return nil, err + } + v.Set(reflect.Append(v, e)) + out.setChild(pathComponent{index: i}, locs) + } + if holder.Kind() == reflect.Interface { + holder.Set(v) + } + return out, nil + default: + locs, err := m.assign(dst, src, path) + return locs.WithLocations(concatLocations(src.loc.Get(), dstView.loc.Get())), err + } +} + +// MergeElementsByKey merges the elements of the sequence at path that have the same +// key, like merge.ElementsByKey: elements are merged into the first one with the same +// key (in order), the key field of every element is set to its key, and the keys are +// sorted if sortKeys is set. +func (sv *StructVar) MergeElementsByKey(path *structpath.PathNode, keyField string, keyFn func(View) string, sortKeys bool) error { + seq := sv.View().Lookup(path) + if seq.Kind() != KindSequence { + return nil + } + + var keys []string + groups := map[string][]int{} + for i, e := range seq.Sequence() { + k := keyFn(e.Get(keyField)) + if _, ok := groups[k]; !ok { + keys = append(keys, k) + } + groups[k] = append(groups[k], i) + } + if len(keys) == 0 { + return nil + } + if sortKeys { + slices.Sort(keys) + } + + // Build the merged sequence separately: the first element of each group is + // assigned, the others are merged into it. + sliceType := seq.Reflect().Type() + for sliceType.Kind() == reflect.Pointer { + sliceType = sliceType.Elem() + } + tmp := &StructVar{Value: reflect.New(sliceType).Interface()} + for i, k := range keys { + elem := structpath.NewIndex(nil, i) + for j, idx := range groups[k] { + var err error + if j == 0 { + _, err = tmp.Assign(elem, seq.Index(idx)) + } else { + err = tmp.Merge(elem, seq.Index(idx)) + } + if err != nil { + return err + } + } + // The key field holds the key (e.g. a normalized one), keeping its location. + if err := tmp.Set(structpath.NewStringKey(elem, keyField), k); err != nil { + return err + } + } + tmp.Locations = tmp.Locations.WithLocations(seq.loc.Get()) + _, err := sv.Assign(path, tmp.View()) + return err +} diff --git a/libs/structs/structvar/override.go b/libs/structs/structvar/override.go new file mode 100644 index 00000000000..f44b5e08f5c --- /dev/null +++ b/libs/structs/structvar/override.go @@ -0,0 +1,163 @@ +package structvar + +import ( + "errors" + "fmt" + "reflect" + + "github.com/databricks/cli/libs/structs/structpath" +) + +// ErrOverrideUndoDelete can be returned by [OverrideVisitor.VisitDelete] to keep the +// deleted value. +var ErrOverrideUndoDelete = errors.New("undo delete operation") + +// OverrideVisitor is notified of the differences found by [PlanOverride]. +// Any error aborts the override. +type OverrideVisitor struct { + VisitDelete func(path *structpath.PathNode, left View) error + VisitInsert func(path *structpath.PathNode, right View) error + VisitUpdate func(path *structpath.PathNode, left, right View) error +} + +// OverridePlan is the result of [PlanOverride]; apply it with [StructVar.Override]. +type OverridePlan struct { + src View + locs *Locations + undos []undo +} + +type undo struct { + path *structpath.PathNode + value *StructVar +} + +// PlanOverride compares dst with src like merge.Override and reports the differences +// to the visitor. Applying the plan replaces dst with src, except that values unchanged +// in src keep their locations in dst, and values whose deletion the visitor undid are kept. +func PlanOverride(dst, src View, visitor OverrideVisitor) (*OverridePlan, error) { + plan := &OverridePlan{src: src} + locs, err := plan.override(nil, dst, src, visitor) + if err != nil { + return nil, err + } + plan.locs = locs + return plan, nil +} + +func (p *OverridePlan) override(path *structpath.PathNode, left, right View, visitor OverrideVisitor) (*Locations, error) { + lk, rk := left.Kind(), right.Kind() + if lk != rk { + return right.loc, visitor.VisitUpdate(path, left, right) + } + + switch lk { + case KindMap: + out := (*Locations)(nil).WithLocations(left.loc.Get()) + for k, lv := range left.MapItems() { + if right.Get(k).IsValid() { + continue + } + kp := structpath.NewStringKey(path, k) + err := visitor.VisitDelete(kp, lv) + if errors.Is(err, ErrOverrideUndoDelete) { + p.keep(kp, lv) + out.setChild(pathComponent{key: k, isKey: true}, lv.loc) + } else if err != nil { + return nil, err + } + } + for k, rv := range right.MapItems() { + kp := structpath.NewStringKey(path, k) + locs := rv.loc + if lv := left.Get(k); lv.IsValid() { + var err error + locs, err = p.override(kp, lv, rv, visitor) + if err != nil { + return nil, err + } + } else if err := visitor.VisitInsert(kp, rv); err != nil { + return nil, err + } + out.setChild(pathComponent{key: k, isKey: true}, locs) + } + return out, nil + case KindSequence: + out := (*Locations)(nil).WithLocations(left.loc.Get()) + var ls, rs []View + for _, v := range left.Sequence() { + ls = append(ls, v) + } + for _, v := range right.Sequence() { + rs = append(rs, v) + } + n := 0 + for i := range min(len(ls), len(rs)) { + locs, err := p.override(structpath.NewIndex(path, i), ls[i], rs[i], visitor) + if err != nil { + return nil, err + } + out.setChild(pathComponent{index: n}, locs) + n++ + } + for i := len(ls); i < len(rs); i++ { + if err := visitor.VisitInsert(structpath.NewIndex(path, i), rs[i]); err != nil { + return nil, err + } + out.setChild(pathComponent{index: n}, rs[i].loc) + n++ + } + for i := len(rs); i < len(ls); i++ { + err := visitor.VisitDelete(structpath.NewIndex(path, i), ls[i]) + if errors.Is(err, ErrOverrideUndoDelete) { + p.keep(structpath.NewIndex(path, n), ls[i]) + out.setChild(pathComponent{index: n}, ls[i].loc) + n++ + } else if err != nil { + return nil, err + } + } + return out, nil + case KindNil: + return left.loc, nil + case KindString, KindBool, KindInt, KindFloat: + if left.AsAny() == right.AsAny() || sameNumber(left, right) { + return left.loc, nil + } + return right.loc, visitor.VisitUpdate(path, left, right) + default: + return nil, fmt.Errorf("unexpected kind %s at %s", lk, path) + } +} + +func sameNumber(a, b View) bool { + if ai, ok := a.AsInt(); ok { + bi, ok := b.AsInt() + return ok && ai == bi + } + return false +} + +// keep records that the value at path (in the result) is the kept left value. It is +// copied now, because applying the plan overwrites the left side. +func (p *OverridePlan) keep(path *structpath.PathNode, v View) { + cp := &StructVar{Value: reflect.New(v.v.Type()).Interface()} + _, _ = cp.Assign(nil, v) + p.undos = append(p.undos, undo{path: path, value: cp}) +} + +// Override replaces sv.Value with the result of plan (see [PlanOverride]). +func (sv *StructVar) Override(plan *OverridePlan) error { + sv.Refs = nil + sv.Locations = nil + if _, err := sv.Assign(nil, plan.src); err != nil { + return err + } + for _, u := range plan.undos { + if _, err := sv.Assign(u.path, u.value.View()); err != nil { + return err + } + } + sv.Locations = plan.locs + return nil +} diff --git a/libs/structs/structvar/parity_walk_test.go b/libs/structs/structvar/parity_walk_test.go new file mode 100644 index 00000000000..f3111c64342 --- /dev/null +++ b/libs/structs/structvar/parity_walk_test.go @@ -0,0 +1,71 @@ +package structvar_test + +import ( + "testing" + + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structvar" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWalkVisitsParentsBeforeChildren(t *testing.T) { + root := config.Root{ + Bundle: config.Bundle{Name: "x"}, + Sync: config.Sync{Include: []string{"a", "b"}}, + } + + var visited []string + err := structvar.Walk(root.View(), func(p *structpath.PathNode, v structvar.View) error { + if s, ok := v.AsString(); ok { + visited = append(visited, p.String()+"="+s) + } + return nil + }) + require.NoError(t, err) + assert.Equal(t, []string{"bundle.name=x", "sync.include[0]=a", "sync.include[1]=b"}, visited) +} + +func TestWalkVisitsAllValues(t *testing.T) { + root, diags := config.LoadFromBytes("test.yml", []byte(` +resources: + jobs: + j: + name: n + max_concurrent_runs: ${var.x} + tags: + a: b +`)) + require.NoError(t, diags.Error()) + + var visited []string + err := structvar.Walk(root.View().Lookup(structpath.MustParsePath("resources.jobs.j")), func(p *structpath.PathNode, _ structvar.View) error { + visited = append(visited, p.String()) + return nil + }) + require.NoError(t, err) + assert.Equal(t, []string{"", "max_concurrent_runs", "name", "tags", "tags.a"}, visited) +} + +func TestSetReference(t *testing.T) { + root, diags := config.LoadFromBytes("test.yml", []byte(` +resources: + jobs: + j: + name: n + max_concurrent_runs: ${var.x} +`)) + require.NoError(t, diags.Error()) + + path := structpath.MustParsePath("resources.jobs.j.max_concurrent_runs") + require.NoError(t, root.SetReference(path, "${var.y}")) + s, ok := root.View().Lookup(path).AsString() + require.True(t, ok) + assert.Equal(t, "${var.y}", s) + + // A string field holds the reference itself. + name := structpath.MustParsePath("resources.jobs.j.name") + require.NoError(t, root.SetReference(name, "${var.y}")) + assert.Equal(t, "${var.y}", root.Resources.Jobs["j"].Name) +} diff --git a/libs/dyn/dynvar/ref.go b/libs/structs/structvar/ref.go similarity index 82% rename from libs/dyn/dynvar/ref.go rename to libs/structs/structvar/ref.go index de25f44a212..b619ae096d2 100644 --- a/libs/dyn/dynvar/ref.go +++ b/libs/structs/structvar/ref.go @@ -1,11 +1,11 @@ -package dynvar +package structvar import ( "fmt" "regexp" "strings" - "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/structs/structpath" ) // BaseVarDef matches a single dot-separated path segment in ${...} references. @@ -17,14 +17,9 @@ const BaseVarDef = `_*\p{L}+([-_]*[\p{L}\p{N}]+)*` var re = regexp.MustCompile(fmt.Sprintf(`\$\{(%s(\.%s(\[[0-9]+\])*)*(\[[0-9]+\])*)\}`, BaseVarDef, BaseVarDef)) -// Ref represents a variable reference. -// It is a string [dyn.Value] contained in a larger [dyn.Value]. -// Its path within the containing [dyn.Value] is also stored. +// Ref represents the variable references in a string. type Ref struct { - // Original value. - Value dyn.Value - - // String value in the original [dyn.Value]. + // The string. Str string // Matches of the variable reference in the string. @@ -36,9 +31,7 @@ type Ref struct { Spans [][2]int } -// NewRef returns a new Ref if the given [dyn.Value] contains a string -// with one or more variable references. It returns false if the given -// [dyn.Value] does not contain variable references. +// NewRef returns a new Ref if s contains one or more variable references. // // A reference preceded by "$" is treated as an escaped literal and is not // included in the returned Ref. For example, "$${foo}" contains no references @@ -49,12 +42,7 @@ type Ref struct { // - "${a.b.c}" // - "${a.b[0].c}" // - "${a} ${b} ${c}" -func NewRef(v dyn.Value) (Ref, bool) { - s, ok := v.AsString() - if !ok { - return Ref{}, false - } - +func NewRef(s string) (Ref, bool) { // Find all matches with their byte positions so we can detect escaped references. indices := re.FindAllStringSubmatchIndex(s, -1) if len(indices) == 0 { @@ -84,7 +72,6 @@ func NewRef(v dyn.Value) (Ref, bool) { } return Ref{ - Value: v, Str: s, Matches: m, Spans: spans, @@ -162,19 +149,14 @@ func ContainsVariableReference(s string) bool { return false } -// If s is a pure variable reference, this function returns the corresponding -// dyn.Path. Otherwise, it returns false. -func PureReferenceToPath(s string) (dyn.Path, bool) { - ref, ok := NewRef(dyn.V(s)) - if !ok { - return nil, false - } - - if !ref.IsPure() { +// PureReferenceToPath returns the path s refers to if s is a pure variable reference. +func PureReferenceToPath(s string) (*structpath.PathNode, bool) { + ref, ok := NewRef(s) + if !ok || !ref.IsPure() { return nil, false } - p, err := dyn.NewPathFromString(ref.References()[0]) + p, err := structpath.ParsePath(ref.References()[0]) if err != nil { return nil, false } diff --git a/libs/dyn/dynvar/ref_test.go b/libs/structs/structvar/ref_test.go similarity index 56% rename from libs/dyn/dynvar/ref_test.go rename to libs/structs/structvar/ref_test.go index 7b93ac633b9..107c612f0b3 100644 --- a/libs/dyn/dynvar/ref_test.go +++ b/libs/structs/structvar/ref_test.go @@ -1,22 +1,16 @@ -package dynvar +package structvar import ( "testing" - "github.com/databricks/cli/libs/dyn" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestNewRefNoString(t *testing.T) { - _, ok := NewRef(dyn.V(1)) - require.False(t, ok, "should not match non-string") -} - func TestNewRefSpans(t *testing.T) { // Spans must point at the unescaped occurrence, not the escaped one that // appears earlier with identical text. - ref, ok := NewRef(dyn.V("$${a.b} ${a.b}")) + ref, ok := NewRef("$${a.b} ${a.b}") require.True(t, ok) require.Len(t, ref.Matches, 1) require.Len(t, ref.Spans, 1) @@ -63,3 +57,52 @@ func TestReplaceRef(t *testing.T) { assert.Equal(t, tt.want, ReplaceRef(tt.in, "${a.b}", "V"), "input %q", tt.in) } } + +func TestPureReferenceToPath(t *testing.T) { + tests := []struct { + name string + input string + expected string + ok bool + }{ + { + name: "simple reference", + input: "${resources.jobs.foo.id}", + expected: "resources.jobs.foo.id", + ok: true, + }, + { + name: "simple reference", + input: "${resources.jobs.foo.tasks[1].env.key}", + expected: "resources.jobs.foo.tasks[1].env.key", + ok: true, + }, + { + name: "complex nested reference", + input: "${var.resources.jobs['my_job'].tasks[0]}", + // we use regex from dyn module which only support integers inside brackets: + // expected: "resources.jobs['my_job'].tasks[0]", + }, + { + name: "not a pure reference", + input: "prefix_${var.field}", + }, + { + name: "not a variable reference", + input: "plain_string", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pathNode, ok := PureReferenceToPath(tt.input) + assert.Equal(t, tt.ok, ok) + if tt.ok { + assert.NotNil(t, pathNode) + assert.Equal(t, tt.expected, pathNode.String()) + } else { + assert.Nil(t, pathNode) + } + }) + } +} diff --git a/libs/dyn/dynvar/ref_vectors_test.go b/libs/structs/structvar/ref_vectors_test.go similarity index 91% rename from libs/dyn/dynvar/ref_vectors_test.go rename to libs/structs/structvar/ref_vectors_test.go index d0084a6ae5c..ea58cc03fba 100644 --- a/libs/dyn/dynvar/ref_vectors_test.go +++ b/libs/structs/structvar/ref_vectors_test.go @@ -1,4 +1,4 @@ -package dynvar +package structvar import ( "encoding/json" @@ -6,7 +6,6 @@ import ( "path/filepath" "testing" - "github.com/databricks/cli/libs/dyn" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -41,7 +40,7 @@ func loadReferenceVectors(t *testing.T) []referenceVector { func TestReferenceVectors(t *testing.T) { for _, v := range loadReferenceVectors(t) { t.Run(v.ID, func(t *testing.T) { - ref, ok := NewRef(dyn.V(v.Input)) + ref, ok := NewRef(v.Input) assert.Equal(t, v.Match, ok, "NewRef match") if v.Match { @@ -59,7 +58,7 @@ func TestReferenceVectors(t *testing.T) { case v.Path != nil: path, ok := PureReferenceToPath(v.Input) require.True(t, ok, "PureReferenceToPath") - assert.Equal(t, dyn.MustPathFromString(*v.Path), path) + assert.Equal(t, *v.Path, path.String()) case v.PathOK != nil: _, ok := PureReferenceToPath(v.Input) assert.Equal(t, *v.PathOK, ok, "PureReferenceToPath") diff --git a/libs/structs/structvar/resolve.go b/libs/structs/structvar/resolve.go new file mode 100644 index 00000000000..3cb8ec44683 --- /dev/null +++ b/libs/structs/structvar/resolve.go @@ -0,0 +1,200 @@ +package structvar + +import ( + "errors" + "fmt" + "maps" + "slices" + "strings" + + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/structs/structpath" +) + +// ErrSkipResolution is returned by a [Lookup] to leave a reference in place. +var ErrSkipResolution = errors.New("skip resolution") + +// ReferenceError is returned for a reference to a path that does not exist. +// Suggestions are carried as data so callers (which can import libs/diag) format them. +type ReferenceError struct { + Reference string // original reference text, e.g. "var.hst" + Suggestions []string // corrected references, e.g. ["var.host", "var.hosts"] +} + +func (e *ReferenceError) Error() string { + return fmt.Sprintf("reference does not exist: ${%s}", e.Reference) +} + +// Template is a string with variable references and the locations it was written at. +type Template struct { + Value string + Locations []diag.Location +} + +// Lookup returns the value a reference path refers to. It returns +// ErrSkipResolution to leave the reference in place. +type Lookup func(path *structpath.PathNode) (View, error) + +// Resolve resolves the references in templates (keyed by path; the keys appear in +// cycle errors) using lookup, with the semantics of dynvar.Resolve: a referenced value +// that is a reference itself is resolved first, cycles are an error, a pure reference +// is replaced by the referenced value (with the location of the reference), and other +// references are interpolated into the string. It returns the views of the resolved +// values; templates that resolve to themselves are omitted. +func Resolve(templates map[string]Template, lookup Lookup) (map[string]View, error) { + r := resolver{lookup: lookup, lookups: map[string]lookupResult{}} + out := map[string]View{} + + // Resolve in key order, so the cycle detected is deterministic. + for _, key := range slices.Sorted(maps.Keys(templates)) { + t := templates[key] + ref, ok := NewRef(t.Value) + if !ok { + continue + } + v, err := r.resolveRef(ref, t.Locations, []string{key}) + if err != nil { + return nil, err + } + if s, ok := v.AsString(); ok && s == t.Value { + continue + } + out[key] = v + } + return out, nil +} + +type lookupResult struct { + v View + err error +} + +type resolver struct { + lookup Lookup + lookups map[string]lookupResult +} + +// stringView returns a view of the string s with locations locs. +func stringView(s string, locs []diag.Location) View { + return NewView(&s, nil, (*Locations)(nil).WithLocations(locs)) +} + +func (r *resolver) resolveRef(ref Ref, locs []diag.Location, seen []string) (View, error) { + deps := ref.References() + + // Resolve each of the dependencies, then interpolate them in the ref. + resolved := make([]View, len(deps)) + complete := true + + for j, dep := range deps { + // Cycle detection. + if slices.Contains(seen, dep) { + return View{}, fmt.Errorf( + "cycle detected in field resolution: %s", + strings.Join(append(seen, dep), " -> "), + ) + } + + v, err := r.resolveKey(dep, append(seen, dep)) + + // If we should skip resolution of this key, index j holds an invalid view. + if errors.Is(err, ErrSkipResolution) { + complete = false + continue + } else if err != nil { + return View{}, err + } + + resolved[j] = v + } + + // A pure reference is replaced by the value, which keeps its type. It takes the + // location of the reference, so relative paths resolve relative to where a + // variable is used, not where it is defined. + if ref.IsPure() && complete { + return resolved[0].WithLocations(locs), nil + } + + // Not pure; perform string interpolation. Substitute by byte offset: the same + // reference may also appear escaped ("$${foo} ${foo}"). + var sb strings.Builder + consumed := 0 + for j := range ref.Matches { + // Leave references that were skipped in place. + if !resolved[j].IsValid() { + continue + } + + s, ok := resolved[j].AsString() + if !ok { + // Only allow primitive types to be converted to string. + switch kind := resolved[j].Kind(); kind { + case KindBool, KindInt, KindFloat, KindNil: + s = fmt.Sprint(resolved[j].AsAny()) + default: + return View{}, fmt.Errorf("cannot interpolate non-primitive value of type %s into string", kind) + } + } + + start, end := ref.Spans[j][0], ref.Spans[j][1] + sb.WriteString(ref.Str[consumed:start]) + sb.WriteString(s) + consumed = end + } + sb.WriteString(ref.Str[consumed:]) + + return stringView(sb.String(), locs), nil +} + +func (r *resolver) resolveKey(key string, seen []string) (View, error) { + if v, ok := r.lookups[key]; ok { + return v.v, v.err + } + + p, err := structpath.ParsePath(key) + if err != nil { + return View{}, err + } + + v, err := r.lookup(p) + if err != nil { + if knf, ok := errors.AsType[*KeyNotFoundError](err); ok { + // Carry suggestions as data; the caller formats them. + err = &ReferenceError{Reference: key, Suggestions: suggestedReferences(knf, key)} + } + r.lookups[key] = lookupResult{err: err} + return View{}, err + } + + // If the value is a reference itself, resolve it. + if s, ok := v.AsString(); ok { + if ref, ok := NewRef(s); ok { + v, err = r.resolveRef(ref, v.Locations(), seen) + } + } + + r.lookups[key] = lookupResult{v: v, err: err} + return v, err +} + +// WithLocations returns the view with its own locations replaced by locs; the values +// below it keep theirs. +func (x View) WithLocations(locs []diag.Location) View { + x.loc = x.loc.WithLocations(locs) + return x +} + +// suggestedReferences returns drop-in replacement references for reference, rebuilt +// by swapping the key that was not found for each suggestion. +func suggestedReferences(err *KeyNotFoundError, reference string) []string { + if len(err.Suggestions) == 0 { + return nil + } + nodes := err.Path.AsSlice() + failedKey, _ := nodes[len(nodes)-1].StringKey() + refs := make([]string, len(err.Suggestions)) + for i, s := range err.Suggestions { + refs[i] = ReplaceKey(reference, failedKey, s) + } + return refs +} diff --git a/libs/structs/structvar/struct_info.go b/libs/structs/structvar/struct_info.go new file mode 100644 index 00000000000..e6c50c59433 --- /dev/null +++ b/libs/structs/structvar/struct_info.go @@ -0,0 +1,252 @@ +package structvar + +import ( + "reflect" + "slices" + "sync" + + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/structs/structtag" + sdkduration "github.com/databricks/databricks-sdk-go/common/types/duration" + sdkfieldmask "github.com/databricks/databricks-sdk-go/common/types/fieldmask" + sdktime "github.com/databricks/databricks-sdk-go/common/types/time" +) + +// StructInfo holds the type information of a struct for converting between +// the configuration tree and the typed value: JSON names, omitempty, ForceSendFields. +type StructInfo struct { + // FieldNames is ordered list of fields + FieldNames []string + + // Fields maps the JSON-name of the field to the field's index for use with [FieldByIndex]. + Fields map[string][]int + + // Tracks which fields do not have omitempty annotation + ForceEmpty map[string]bool + + // Maps JSON-name of the field to Golang struct name + GolangNames map[string]string + + // ForceSendFieldsIndex maps the JSON-name of the field to the index path (for + // use with [reflect.Value.FieldByIndex]) of the ForceSendFields slice that + // governs it: the one declared by the struct that also declares the field. + // The path is static per type, so we resolve it once here rather than walking + // the value at conversion time. It can be more than one element deep because a + // field's declaring struct may be embedded several levels down (e.g. + // PostgresProject -> PostgresProjectConfig -> ProjectSpec). A field whose + // declaring struct has no ForceSendFields has no entry. + ForceSendFieldsIndex map[string][]int + + // List holds the fields in FieldNames order, with everything traversal needs, so + // that visiting a struct does no lookups by name. + List []FieldInfo + + // ForceSendGroups holds the distinct ForceSendFields index paths; a struct with + // embedded structs can have several. + ForceSendGroups [][]int + + // ForceSendTargets maps, per ForceSendGroups entry, the Go field names listed in + // that ForceSendFields to their index in List. + ForceSendTargets []map[string]int +} + +// FieldInfo describes a field of a struct. +type FieldInfo struct { + Key string + Index []int + GolangName string + + // ForceEmpty is set for fields without omitempty. + ForceEmpty bool + + // Dot is set if Key can be written as a dotted path component (see + // [structpath.NewDotString]), which is true for every JSON name in practice. + Dot bool + + // ForceSendGroup is the index in [StructInfo.ForceSendGroups] of the + // ForceSendFields governing the field, or -1. + ForceSendGroup int +} + +var structInfoCache sync.Map // reflect.Type -> *StructInfo + +// GetStructInfo returns the [StructInfo] for the given type. +// It lazily populates a cache, so the first call for a given +// type is slower than subsequent calls for that same type. +func GetStructInfo(typ reflect.Type) *StructInfo { + if si, ok := structInfoCache.Load(typ); ok { + return si.(*StructInfo) + } + si, _ := structInfoCache.LoadOrStore(typ, buildStructInfo(typ)) + return si.(*StructInfo) +} + +// buildStructInfo populates a new [StructInfo] for the given type. +func buildStructInfo(typ reflect.Type) *StructInfo { + out := &StructInfo{ + Fields: make(map[string][]int), + ForceEmpty: make(map[string]bool), + GolangNames: make(map[string]string), + ForceSendFieldsIndex: make(map[string][]int), + } + + // Queue holds the indexes of the structs to visit. + // It is initialized with a single empty slice to visit the top level struct. + queue := [][]int{{}} + for i := 0; i < len(queue); i++ { + prefix := queue[i] + + // Traverse embedded anonymous types (if prefix is non-empty). + styp := typ + if len(prefix) > 0 { + styp = styp.FieldByIndex(prefix).Type + } + + // Dereference pointer type. + if styp.Kind() == reflect.Pointer { + styp = styp.Elem() + } + + // Index path to the ForceSendFields declared by this struct, if any. All + // fields declared directly by this struct are governed by it. The len==1 + // check excludes a ForceSendFields promoted from an embedded struct: that + // one governs the embedded struct's own fields, which we visit separately. + var forceSendFieldsIndex []int + if sf, ok := styp.FieldByName("ForceSendFields"); ok && len(sf.Index) == 1 { + forceSendFieldsIndex = append(slices.Clone(prefix), sf.Index...) + } + + nf := styp.NumField() + for j := range nf { + sf := styp.Field(j) + + // Recurse into anonymous fields. + if sf.Anonymous { + queue = append(queue, append(prefix, sf.Index...)) + continue + } + + jtag := structtag.JSONTag(sf.Tag.Get("json")) + name := jtag.Name() + if name == "" || name == "-" { + continue + } + + // Top level fields always take precedence. + // Therefore, if it is already set, we ignore it. + if _, ok := out.Fields[name]; ok { + continue + } + + out.FieldNames = append(out.FieldNames, name) + out.Fields[name] = append(prefix, sf.Index...) + if !jtag.OmitEmpty() && !jtag.OmitZero() { + out.ForceEmpty[name] = true + } + out.GolangNames[name] = sf.Name + + // The field is declared directly in this struct, so it is governed by + // this struct's ForceSendFields (if it has one). + group := -1 + if forceSendFieldsIndex != nil { + out.ForceSendFieldsIndex[name] = forceSendFieldsIndex + group = forceSendGroup(out, forceSendFieldsIndex) + } + out.List = append(out.List, FieldInfo{ + Key: name, + Index: out.Fields[name], + GolangName: sf.Name, + ForceEmpty: out.ForceEmpty[name], + Dot: structpath.NewStringKey(nil, name).IsDotString(), + ForceSendGroup: group, + }) + } + } + + out.ForceSendTargets = make([]map[string]int, len(out.ForceSendGroups)) + for i, f := range out.List { + if f.ForceSendGroup < 0 { + continue + } + m := out.ForceSendTargets[f.ForceSendGroup] + if m == nil { + m = map[string]int{} + out.ForceSendTargets[f.ForceSendGroup] = m + } + m[f.GolangName] = i + } + return out +} + +// forceSendGroup returns the index of the ForceSendFields path index in +// out.ForceSendGroups, adding it if needed. +func forceSendGroup(out *StructInfo, index []int) int { + for i, g := range out.ForceSendGroups { + if slices.Equal(g, index) { + return i + } + } + out.ForceSendGroups = append(out.ForceSendGroups, index) + return len(out.ForceSendGroups) - 1 +} + +// isForceSend reports whether the field named k is listed in the ForceSendFields +// that governs it (see StructInfo.ForceSendFieldsIndex). +func (s *StructInfo) IsForceSend(v reflect.Value, k string) bool { + index, ok := s.ForceSendFieldsIndex[k] + if !ok { + return false + } + fsf := FieldByIndex(v, index) + if !fsf.IsValid() { + return false + } + return containsString(fsf, s.GolangNames[k]) +} + +// fieldByIndex resolves the value at the given index path, dereferencing embedded +// pointer structs on the way. It returns an invalid value if a nil pointer is met. +func FieldByIndex(v reflect.Value, index []int) reflect.Value { + for i, x := range index { + if i > 0 { + if v.Kind() == reflect.Pointer && v.Type().Elem().Kind() == reflect.Struct { + if v.IsNil() { + return reflect.Value{} + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v +} + +// GetOrNewFieldByIndex resolves the value at the given index path within an addressable +// struct, allocating intermediate structs embedded as pointer types along the way. +func GetOrNewFieldByIndex(v reflect.Value, index []int) reflect.Value { + for i, x := range index { + if i > 0 { + if v.Kind() == reflect.Pointer { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v +} + +// sdkNativeTypes are SDK types with custom JSON marshaling to and from a string. +// The configuration represents them as strings. +var sdkNativeTypes = []reflect.Type{ + reflect.TypeFor[sdkduration.Duration](), // Protobuf duration format (e.g., "300s") + reflect.TypeFor[sdktime.Time](), // RFC3339 timestamp format (e.g., "2023-12-25T10:30:00Z") + reflect.TypeFor[sdkfieldmask.FieldMask](), // Comma-separated paths (e.g., "name,age,email") +} + +// IsSDKNativeType reports whether typ is an SDK type represented as a string. +func IsSDKNativeType(typ reflect.Type) bool { + return slices.Contains(sdkNativeTypes, typ) +} diff --git a/libs/structs/structvar/structvar.go b/libs/structs/structvar/structvar.go index cc894aced7f..2f5bde6642e 100644 --- a/libs/structs/structvar/structvar.go +++ b/libs/structs/structvar/structvar.go @@ -8,7 +8,6 @@ import ( "strings" "sync" - "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/cli/libs/structs/structaccess" "github.com/databricks/cli/libs/structs/structpath" ) @@ -20,6 +19,9 @@ type StructVar struct { // Refs holds unresolved references. Key is serialized PathNode pointing inside a struct (e.g. "name") // and value is either pure or multiple references string: "${resources.foo.jobs.id}" or "${a} ${b}" Refs map[string]string `json:"vars,omitempty"` + + // Locations holds the source locations of the values, if known (configuration loaded from files). + Locations *Locations `json:"-"` } // StructVarJSON is the serialized form of StructVar for persisting in plan files. @@ -103,18 +105,18 @@ func (sv *StructVar) ResolveRef(reference string, value any) error { return fmt.Errorf("cannot set %s to %T (%#v): %w", pathNode.String(), value, value, err) } - newValue := dynvar.ReplaceRef(refValue, reference, valueStr) + newValue := ReplaceRef(refValue, reference, valueStr) // The struct gets the unescaped form ("$${x}" -> "${x}"), since that is what // is sent to the API. sv.Refs keeps the escaped form so the still-pending // check below can tell a literal apart from a real reference. - err = structaccess.Set(sv.Value, pathNode, dynvar.Unescape(newValue)) + err = structaccess.Set(sv.Value, pathNode, Unescape(newValue)) if err != nil { return fmt.Errorf("cannot update %s to string: %w", pathNode.String(), err) } // Check if fully resolved (no unescaped ${} patterns left) - if !dynvar.ContainsVariableReference(newValue) { + if !ContainsVariableReference(newValue) { delete(sv.Refs, pathKey) } else { sv.Refs[pathKey] = newValue diff --git a/libs/structs/structvar/structvar_test.go b/libs/structs/structvar/structvar_test.go index 9d4c2532ad6..f1c2095dc77 100644 --- a/libs/structs/structvar/structvar_test.go +++ b/libs/structs/structvar/structvar_test.go @@ -2,8 +2,11 @@ package structvar_test import ( "reflect" + "strings" "testing" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/structs/structvar" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/stretchr/testify/assert" @@ -290,3 +293,126 @@ func TestToStructVarRequiresPointerType(t *testing.T) { require.NoError(t, err) assert.Equal(t, "test", sv.Value.(*TestObj).Name) } + +func TestMoveMetadata(t *testing.T) { + type obj struct { + Old map[string]TestObj `json:"old"` + New map[string]TestObj `json:"new"` + } + sv := structvar.NewStructVar(&obj{}, map[string]string{ + "old['a'].age": "${var.age}", + "older.age": "${var.other}", + "new['b'].age": "${var.replaced}", + }) + loc := diag.Location{File: "a.yml", Line: 1, Column: 1} + sv.Locations = sv.Locations.With(structpath.NewStringKey(nil, "old"), (*structvar.Locations)(nil).WithLocations([]diag.Location{loc})) + + sv.MoveMetadata(structpath.NewStringKey(nil, "old"), structpath.NewStringKey(nil, "new")) + + assert.Equal(t, map[string]string{ + "new['a'].age": "${var.age}", + "older.age": "${var.other}", + }, sv.Refs) + assert.Equal(t, []diag.Location{loc}, sv.Locations.Exact(structpath.NewStringKey(nil, "new"))) + assert.Nil(t, sv.Locations.Sub(structpath.NewStringKey(nil, "old"))) +} + +func TestCopyMetadata(t *testing.T) { + type obj struct { + Items map[string]TestObj `json:"items"` + } + dst := structvar.NewStructVar(&obj{}, map[string]string{ + "items.a.age": "${var.old}", + "items.b.age": "${var.kept}", + }) + src := structvar.NewStructVar(&obj{}, map[string]string{ + "items.a.name": "${var.new}", + "items.b.age": "${var.ignored}", + }) + loc := diag.Location{File: "a.yml", Line: 2, Column: 3} + a := structpath.NewPath(nil, "items", "a") + src.Locations = src.Locations.With(a, (*structvar.Locations)(nil).WithLocations([]diag.Location{loc})) + + dst.CopyMetadata(src, a) + + assert.Equal(t, map[string]string{ + "items.a.name": "${var.new}", + "items.b.age": "${var.kept}", + }, dst.Refs) + assert.Equal(t, []diag.Location{loc}, dst.Locations.Exact(a)) +} + +func TestDeleteSliceElement(t *testing.T) { + type obj struct { + Items []string `json:"items"` + } + sv := &structvar.StructVar{Value: &obj{Items: []string{"a", "b", "c"}}} + items := structpath.NewStringKey(nil, "items") + for i, name := range []string{"a", "b", "c"} { + loc := diag.Location{File: name, Line: i + 1} + sv.Locations = sv.Locations.With(structpath.NewIndex(items, i), (*structvar.Locations)(nil).WithLocations([]diag.Location{loc})) + } + sv.Refs = map[string]string{"items[2]": "${var.c}"} + + require.NoError(t, sv.Delete(structpath.NewIndex(items, 0))) + + assert.Equal(t, []string{"b", "c"}, sv.Value.(*obj).Items) + assert.Equal(t, []diag.Location{{File: "b", Line: 2}}, sv.Locations.Exact(structpath.NewIndex(items, 0))) + assert.Equal(t, []diag.Location{{File: "c", Line: 3}}, sv.Locations.Exact(structpath.NewIndex(items, 1))) + assert.Nil(t, sv.Locations.Exact(structpath.NewIndex(items, 2))) + assert.Equal(t, map[string]string{"items[1]": "${var.c}"}, sv.Refs) + + require.NoError(t, sv.Delete(structpath.NewIndex(items, 1))) + assert.Equal(t, []string{"b"}, sv.Value.(*obj).Items) + assert.Nil(t, sv.Locations.Exact(structpath.NewIndex(items, 1))) + assert.Empty(t, sv.Refs) +} + +func TestMergeNullKeepsFieldSet(t *testing.T) { + type inner struct { + Enabled bool `json:"enabled"` + } + type obj struct { + Name string `json:"name,omitempty"` + Queue *inner `json:"queue,omitempty"` + ForceSendFields []string `json:"-"` + } + + var dst obj + dstVar, _, err := structvar.DecodeYAML("dst.yml", strings.NewReader("name: x\n"), &dst) + require.NoError(t, err) + + var src obj + srcVar, _, err := structvar.DecodeYAML("src.yml", strings.NewReader("queue: null\n"), &src) + require.NoError(t, err) + + require.NoError(t, dstVar.Merge(nil, srcVar.View())) + + // An explicit null added by the merge stays set, as it does when it is decoded. + assert.Nil(t, dst.Queue) + assert.Equal(t, []string{"Queue"}, dst.ForceSendFields) +} + +func TestMergeNullIntoValueKeepsIt(t *testing.T) { + type inner struct { + Enabled bool `json:"enabled"` + } + type obj struct { + Queue *inner `json:"queue,omitempty"` + ForceSendFields []string `json:"-"` + } + + var dst obj + dstVar, _, err := structvar.DecodeYAML("dst.yml", strings.NewReader("queue:\n enabled: true\n"), &dst) + require.NoError(t, err) + + var src obj + srcVar, _, err := structvar.DecodeYAML("src.yml", strings.NewReader("queue: null\n"), &src) + require.NoError(t, err) + + require.NoError(t, dstVar.Merge(nil, srcVar.View())) + + // A null does not clear an existing value. + require.NotNil(t, dst.Queue) + assert.True(t, dst.Queue.Enabled) +} diff --git a/libs/dyn/suggest.go b/libs/structs/structvar/suggest.go similarity index 60% rename from libs/dyn/suggest.go rename to libs/structs/structvar/suggest.go index ef8008aae6f..2d2f4bccef7 100644 --- a/libs/dyn/suggest.go +++ b/libs/structs/structvar/suggest.go @@ -1,15 +1,17 @@ -package dyn +package structvar import ( "fmt" "slices" "strings" + + "github.com/databricks/cli/libs/structs/structpath" ) const maxSuggestionDistance = 2 -// levenshteinDistance computes the edit distance between two strings. -func levenshteinDistance(a, b string) int { +// LevenshteinDistance computes the edit distance between two strings. +func LevenshteinDistance(a, b string) int { if len(a) == 0 { return len(b) } @@ -43,19 +45,18 @@ func levenshteinDistance(a, b string) int { return prev[len(b)] } -// suggestKeys returns the keys in m whose edit distance from name is at most +// SuggestKeys returns the keys whose edit distance from name is at most // maxSuggestionDistance, ordered by increasing distance. It is used to build -// "did you mean" hints for a key that was not found in the map. -func suggestKeys(m Mapping, name string) []string { +// "did you mean" hints for a key that was not found in a map. +func SuggestKeys(keys []string, name string) []string { type candidate struct { key string dist int } var candidates []candidate - for _, kv := range m.Keys() { - key := kv.MustString() - d := levenshteinDistance(name, key) + for _, key := range keys { + d := LevenshteinDistance(name, key) if d <= maxSuggestionDistance { candidates = append(candidates, candidate{key, d}) } @@ -72,10 +73,10 @@ func suggestKeys(m Mapping, name string) []string { return suggestions } -// didYouMean formats a suggestion clause like `, did you mean "x"?` (or, for +// DidYouMean formats a suggestion clause like `, did you mean "x"?` (or, for // multiple candidates, `, did you mean one of: "x", "y"?`). It returns an empty // string when there are no suggestions. -func didYouMean(suggestions []string) string { +func DidYouMean(suggestions []string) string { switch len(suggestions) { case 0: return "" @@ -90,19 +91,28 @@ func didYouMean(suggestions []string) string { } } -// replaceKey returns reference with the component matching failedKey swapped for +// ReplaceKey returns reference with the component matching failedKey swapped for // replacement, or just replacement if reference can't be parsed or has no match. -func replaceKey(reference, failedKey, replacement string) string { - p, err := NewPathFromString(reference) +func ReplaceKey(reference, failedKey, replacement string) string { + p, err := structpath.ParsePath(reference) if err != nil { return replacement } - for i, c := range p { - if c.Key() == failedKey { - out := p.Append() - out[i] = Key(replacement) - return out.String() + var out *structpath.PathNode + replaced := false + for _, n := range p.AsSlice() { + if k, ok := n.StringKey(); ok { + if k == failedKey && !replaced { + k = replacement + replaced = true + } + out = structpath.NewStringKey(out, k) + } else if i, ok := n.Index(); ok { + out = structpath.NewIndex(out, i) } } - return replacement + if !replaced { + return replacement + } + return out.String() } diff --git a/libs/dyn/dynvar/testdata/reference_vectors.json b/libs/structs/structvar/testdata/reference_vectors.json similarity index 99% rename from libs/dyn/dynvar/testdata/reference_vectors.json rename to libs/structs/structvar/testdata/reference_vectors.json index 2c47b135450..c9c380ad7d0 100644 --- a/libs/dyn/dynvar/testdata/reference_vectors.json +++ b/libs/structs/structvar/testdata/reference_vectors.json @@ -1,5 +1,5 @@ { - "comment": "Shared contract for ${...} reference parsing. Consumed by libs/dyn/dynvar and python/databricks/bundles/core/_transform.py tests.", + "comment": "Shared contract for ${...} reference parsing. Consumed by libs/structs/structvar and python/databricks/bundles/core/_transform.py tests.", "vectors": [ { "id": "ascii-simple", diff --git a/libs/dyn/yamlloader/testdata/anchor_01.yml b/libs/structs/structvar/testdata/yaml/anchor_01.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/anchor_01.yml rename to libs/structs/structvar/testdata/yaml/anchor_01.yml diff --git a/libs/dyn/yamlloader/testdata/anchor_02.yml b/libs/structs/structvar/testdata/yaml/anchor_02.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/anchor_02.yml rename to libs/structs/structvar/testdata/yaml/anchor_02.yml diff --git a/libs/dyn/yamlloader/testdata/anchor_03.yml b/libs/structs/structvar/testdata/yaml/anchor_03.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/anchor_03.yml rename to libs/structs/structvar/testdata/yaml/anchor_03.yml diff --git a/libs/dyn/yamlloader/testdata/anchor_04.yml b/libs/structs/structvar/testdata/yaml/anchor_04.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/anchor_04.yml rename to libs/structs/structvar/testdata/yaml/anchor_04.yml diff --git a/libs/dyn/yamlloader/testdata/anchor_05.yml b/libs/structs/structvar/testdata/yaml/anchor_05.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/anchor_05.yml rename to libs/structs/structvar/testdata/yaml/anchor_05.yml diff --git a/libs/dyn/yamlloader/testdata/anchor_06.yml b/libs/structs/structvar/testdata/yaml/anchor_06.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/anchor_06.yml rename to libs/structs/structvar/testdata/yaml/anchor_06.yml diff --git a/libs/dyn/yamlloader/testdata/anchor_07.yml b/libs/structs/structvar/testdata/yaml/anchor_07.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/anchor_07.yml rename to libs/structs/structvar/testdata/yaml/anchor_07.yml diff --git a/libs/dyn/yamlloader/testdata/anchor_08.yml b/libs/structs/structvar/testdata/yaml/anchor_08.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/anchor_08.yml rename to libs/structs/structvar/testdata/yaml/anchor_08.yml diff --git a/libs/dyn/yamlloader/testdata/anchor_09.yml b/libs/structs/structvar/testdata/yaml/anchor_09.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/anchor_09.yml rename to libs/structs/structvar/testdata/yaml/anchor_09.yml diff --git a/libs/dyn/yamlloader/testdata/anchor_10.yml b/libs/structs/structvar/testdata/yaml/anchor_10.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/anchor_10.yml rename to libs/structs/structvar/testdata/yaml/anchor_10.yml diff --git a/libs/dyn/yamlloader/testdata/empty.yml b/libs/structs/structvar/testdata/yaml/empty.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/empty.yml rename to libs/structs/structvar/testdata/yaml/empty.yml diff --git a/libs/dyn/yamlloader/testdata/error_01.yml b/libs/structs/structvar/testdata/yaml/error_01.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/error_01.yml rename to libs/structs/structvar/testdata/yaml/error_01.yml diff --git a/libs/dyn/yamlloader/testdata/error_02.yml b/libs/structs/structvar/testdata/yaml/error_02.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/error_02.yml rename to libs/structs/structvar/testdata/yaml/error_02.yml diff --git a/libs/dyn/yamlloader/testdata/error_03.yml b/libs/structs/structvar/testdata/yaml/error_03.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/error_03.yml rename to libs/structs/structvar/testdata/yaml/error_03.yml diff --git a/libs/dyn/yamlloader/testdata/mix_01.yml b/libs/structs/structvar/testdata/yaml/mix_01.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/mix_01.yml rename to libs/structs/structvar/testdata/yaml/mix_01.yml diff --git a/libs/dyn/yamlloader/testdata/mix_02.yml b/libs/structs/structvar/testdata/yaml/mix_02.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/mix_02.yml rename to libs/structs/structvar/testdata/yaml/mix_02.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.1.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.1.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.1.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.1.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.10.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.10.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.10.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.10.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.11.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.11.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.11.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.11.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.12.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.12.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.12.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.12.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.13.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.13.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.13.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.13.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.14.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.14.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.14.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.14.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.15.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.15.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.15.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.15.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.16.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.16.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.16.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.16.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.17.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.17.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.17.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.17.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.18.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.18.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.18.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.18.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.19.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.19.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.19.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.19.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.2.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.2.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.2.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.2.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.20.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.20.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.20.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.20.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.21.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.21.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.21.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.21.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.22.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.22.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.22.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.22.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.23.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.23.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.23.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.23.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.24.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.24.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.24.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.24.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.25.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.25.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.25.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.25.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.26.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.26.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.26.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.26.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.27.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.27.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.27.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.27.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.28.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.28.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.28.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.28.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.3.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.3.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.3.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.3.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.4.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.4.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.4.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.4.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.5.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.5.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.5.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.5.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.6.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.6.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.6.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.6.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.7.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.7.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.7.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.7.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.8.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.8.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.8.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.8.yml diff --git a/libs/dyn/yamlloader/testdata/spec_example_2.9.yml b/libs/structs/structvar/testdata/yaml/spec_example_2.9.yml similarity index 100% rename from libs/dyn/yamlloader/testdata/spec_example_2.9.yml rename to libs/structs/structvar/testdata/yaml/spec_example_2.9.yml diff --git a/libs/structs/structvar/view.go b/libs/structs/structvar/view.go new file mode 100644 index 00000000000..fd01de64712 --- /dev/null +++ b/libs/structs/structvar/view.go @@ -0,0 +1,569 @@ +package structvar + +import ( + "encoding/json" + "iter" + "reflect" + "slices" + "strings" + + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/structs/structpath" +) + +type option uint8 + +const ( + // includeZero keeps zero values: the value is behind a pointer, a map value, + // a slice element, or held in an interface. + includeZero option = 1 << iota + + // fromInterface is set for values held in an interface (e.g. variable defaults). + fromInterface +) + +// View is a read-only view of a typed value with its references and locations, as the +// configuration tree they describe. +// +// Presence is decided by the value and ForceSendFields only, never by locations: a +// value is present if it is not zero, is listed in its struct's ForceSendFields, holds +// a pure reference, or is behind a pointer, a map value or a slice element. Decoding +// records an explicitly written zero value in ForceSendFields, so it stays present. +// Unlike libs/dyn, a written zero value in a struct without ForceSendFields (e.g. +// `level: ""` of a permission) and an empty struct value (e.g. `presets: {}`) are absent. +// +// The zero View is invalid and represents an absent value. +type View struct { + v reflect.Value + + // Locations of the value. + loc *Locations + + // Path of the value and the references of the whole value the view was created for. + // The path is only tracked if there are references. + path *structpath.PathNode + refs map[string]string + + opts option + valid bool +} + +// NewView returns the view of the typed value v (typically a pointer to a struct) +// with references refs (keyed by path string) and locations. +func NewView(v any, refs map[string]string, locs *Locations) View { + return View{v: reflect.ValueOf(v), loc: locs, refs: refs, valid: true} +} + +// View returns the view of sv. +func (sv *StructVar) View() View { + return NewView(sv.Value, sv.Refs, sv.Locations) +} + +// ref returns the pure reference held by the value, if its type cannot hold a string. +func (x View) ref() string { + if len(x.refs) == 0 { + return "" + } + return x.refs[x.path.String()] +} + +// child returns a view of the child value v at key k (or index i). +func (x View) childKey(v reflect.Value, k string, opts option) View { + c := View{v: v, loc: x.loc.Key(k), refs: x.refs, opts: opts, valid: true} + if len(x.refs) > 0 { + c.path = structpath.NewStringKey(x.path, k) + } + return c +} + +func (x View) childIndex(v reflect.Value, i int, opts option) View { + c := View{v: v, loc: x.loc.Index(i), refs: x.refs, opts: opts, valid: true} + if len(x.refs) > 0 { + c.path = structpath.NewIndex(x.path, i) + } + return c +} + +// IsValid reports whether the value is present. +func (x View) IsValid() bool { + return x.valid +} + +// Locations returns the source locations of the value. +func (x View) Locations() []diag.Location { + if !x.valid { + return nil + } + return x.loc.Get() +} + +// Location returns the first source location of the value. +func (x View) Location() diag.Location { + locs := x.loc.Get() + if !x.valid || len(locs) == 0 { + return diag.Location{} + } + return locs[0] +} + +// Reflect returns the typed value the view is based on (it may be a pointer or interface). +// It is settable if it was reached through pointers or is the root pointer's element. +func (x View) Reflect() reflect.Value { + return x.v +} + +// Locs returns the locations of this value and the values below it. +func (x View) Locs() *Locations { + return x.loc +} + +// elemOptions returns the options for a map value or a slice element: they keep +// zero values. +func elemOptions(v reflect.Value) option { + if v.Kind() == reflect.Interface { + return includeZero | fromInterface + } + return includeZero +} + +// deref follows pointers and interfaces; it returns an invalid value for nil. +func (x View) deref() (reflect.Value, option) { + v, opts := x.v, x.opts + for { + switch v.Kind() { + case reflect.Pointer: + if v.IsNil() { + return reflect.Value{}, opts + } + v = v.Elem() + // A pointer to a zero value was intentionally set. + opts |= includeZero + case reflect.Interface: + if v.IsNil() { + return reflect.Value{}, opts + } + v = v.Elem() + default: + return v, opts + } + } +} + +// Kind returns the kind of the value in the configuration tree. +func (x View) Kind() Kind { + if !x.valid { + return KindInvalid + } + v, opts := x.deref() + if !v.IsValid() { + return KindNil + } + ref := x.ref() + switch v.Kind() { + case reflect.Struct: + if ref != "" { + return KindString + } + if IsSDKNativeType(v.Type()) { + if _, ok := sdkNativeString(v, opts); ok { + return KindString + } + return KindNil + } + // A struct is present if a field is, or if it is kept (e.g. behind a pointer). + // Locations never make a value present: an empty struct is absent. + if opts&includeZero != 0 || x.hasFields(v) { + return KindMap + } + return KindNil + case reflect.Map: + if ref != "" { + return KindString + } + if v.IsNil() { + return KindNil + } + return KindMap + case reflect.Slice: + if ref != "" { + return KindString + } + if v.IsNil() { + return KindNil + } + return KindSequence + case reflect.String, reflect.Bool, reflect.Int, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64: + if ref != "" { + return KindString + } + // A zero value is absent unless it is kept (forced, or behind a pointer). + if v.IsZero() && opts&includeZero == 0 { + return KindNil + } + switch v.Kind() { + case reflect.String: + return KindString + case reflect.Bool: + return KindBool + case reflect.Float32, reflect.Float64: + return KindFloat + default: + return KindInt + } + default: + return KindInvalid + } +} + +func sdkNativeString(v reflect.Value, opts option) (string, bool) { + if v.IsZero() && opts&includeZero == 0 { + return "", false + } + // SDK native types implement json.Marshaler and marshal to a JSON string. + buf, err := json.Marshal(v.Interface()) + if err != nil { + return "", false + } + var s string + if json.Unmarshal(buf, &s) != nil { + return "", false + } + if s == "" && opts&includeZero == 0 { + return "", false + } + return s, true +} + +// isZero mirrors [dyn.Value.IsZero] for the value. +func (x View) isZero() bool { + switch x.Kind() { + case KindInvalid, KindNil: + return true + case KindMap: + for range x.MapItems() { + return false + } + return true + case KindSequence: + v, _ := x.deref() + return v.Len() == 0 + default: + if x.ref() != "" { + return false + } + s, ok := x.AsString() + if ok { + return s == "" + } + v, _ := x.deref() + return v.IsZero() + } +} + +// field returns the view of the struct field k, and whether it is represented. +func (x View) field(info *StructInfo, sv reflect.Value, k string, fv reflect.Value, isForced bool) (View, bool) { + var opts option + if fv.Kind() == reflect.Interface { + opts = includeZero | fromInterface + } + child := x.childKey(fv, k, opts) + kind := child.Kind() + + // The field is not zero-valued, or it's forced. Locations do not make it present. + if kind == KindNil && !isForced { + return View{}, false + } + + // A zero field is absent unless forced (it may still be a reference: check the view too). + if fv.Kind() != reflect.Struct && fv.IsZero() && child.isZero() && !info.ForceEmpty[k] && !isForced { + return View{}, false + } + + // A forced field with a nil value is represented by its zero value. + if isForced && kind == KindNil { + child.opts |= includeZero + } + return child, true +} + +func (x View) hasFields(v reflect.Value) bool { + return !x.eachField(v, func(string, bool, View) bool { return false }) +} + +// eachField calls fn for the represented fields of the struct v, in declaration order, +// until fn returns false. It returns false if fn did. fn also gets whether the key can +// be a dotted path component, so callers building paths need not check it. +// +// The traversal helpers take a callback rather than returning an iterator so that +// walking a value does not allocate a closure per visited node. +func (x View) eachField(v reflect.Value, fn func(string, bool, View) bool) bool { + info := GetStructInfo(v.Type()) + + // Mark the fields listed in the struct's ForceSendFields, read once per visit. + var forcedBuf [128]bool + var forced []bool + if len(info.List) <= len(forcedBuf) { + forced = forcedBuf[:len(info.List)] + } else { + forced = make([]bool, len(info.List)) + } + for g, index := range info.ForceSendGroups { + names := FieldByIndex(v, index) + if !names.IsValid() { + continue + } + for j := range names.Len() { + if i, ok := info.ForceSendTargets[g][names.Index(j).String()]; ok { + forced[i] = true + } + } + } + + for i := range info.List { + f := &info.List[i] + fv := FieldByIndex(v, f.Index) + if !fv.IsValid() { + continue + } + isForced := forced[i] || !fv.IsZero() + if !isForced && x.isAbsentZeroField(fv, f.Key) { + continue + } + child, ok := x.field(info, v, f.Key, fv, isForced) + if ok && !fn(f.Key, f.Dot, child) { + return false + } + } + return true +} + +// containsString reports whether the []string value s (possibly invalid) contains name. +// It iterates with reflect: converting s with Interface would allocate. +func containsString(s reflect.Value, name string) bool { + if !s.IsValid() { + return false + } + for i := range s.Len() { + if s.Index(i).String() == name { + return true + } + } + return false +} + +// isAbsentZeroField reports whether the zero, unforced field fv (key k) is absent +// without building its view: [View.field] would find it absent, because it is neither +// a struct nor an interface (whose presence depends on their contents) and holds no +// reference. +func (x View) isAbsentZeroField(fv reflect.Value, k string) bool { + switch fv.Kind() { + case reflect.Struct, reflect.Interface: + return false + default: + } + return len(x.refs) == 0 || x.refs[structpath.NewStringKey(x.path, k).String()] == "" +} + +// eachMapItem calls fn for the entries of a map value (struct fields in declaration +// order, map entries in key order) until fn returns false. It returns false if fn did. +func (x View) eachMapItem(fn func(string, bool, View) bool) bool { + if x.ref() != "" { + return true + } + v, _ := x.deref() + switch { + case !v.IsValid(): + case v.Kind() == reflect.Struct && !IsSDKNativeType(v.Type()): + return x.eachField(v, fn) + case v.Kind() == reflect.Map: + keys := v.MapKeys() + slices.SortFunc(keys, func(a, b reflect.Value) int { + return strings.Compare(a.String(), b.String()) + }) + for _, key := range keys { + ev := v.MapIndex(key) + k := key.String() + if !fn(k, false, x.childKey(ev, k, elemOptions(ev))) { + return false + } + } + } + return true +} + +// eachElem calls fn for the elements of a sequence value until fn returns false. +// It returns false if fn did. +func (x View) eachElem(fn func(int, View) bool) bool { + if x.Kind() != KindSequence { + return true + } + v, _ := x.deref() + for i := range v.Len() { + ev := v.Index(i) + if !fn(i, x.childIndex(ev, i, elemOptions(ev))) { + return false + } + } + return true +} + +// MapItems returns the entries of a map value: struct fields in declaration order, +// map entries in key order. +func (x View) MapItems() iter.Seq2[string, View] { + return func(yield func(string, View) bool) { + x.eachMapItem(func(k string, _ bool, c View) bool { return yield(k, c) }) + } +} + +// Sequence returns the elements of a sequence value. +func (x View) Sequence() iter.Seq2[int, View] { + return func(yield func(int, View) bool) { x.eachElem(yield) } +} + +// Get returns the value of key k of a map value, or an invalid value. +func (x View) Get(k string) View { + if x.Kind() != KindMap { + return View{} + } + v, _ := x.deref() + switch v.Kind() { + case reflect.Struct: + info := GetStructInfo(v.Type()) + index, ok := info.Fields[k] + if !ok { + return View{} + } + fv := FieldByIndex(v, index) + if !fv.IsValid() { + return View{} + } + isForced := !fv.IsZero() || info.IsForceSend(v, k) + child, ok := x.field(info, v, k, fv, isForced) + if !ok { + return View{} + } + return child + case reflect.Map: + ev := v.MapIndex(reflect.ValueOf(k).Convert(v.Type().Key())) + if !ev.IsValid() { + return View{} + } + child := x.childKey(ev, k, elemOptions(ev)) + return child + default: + return View{} + } +} + +// Index returns element i of a sequence value, or an invalid value. +func (x View) Index(i int) View { + if x.Kind() != KindSequence { + return View{} + } + v, _ := x.deref() + if i < 0 || i >= v.Len() { + return View{} + } + ev := v.Index(i) + child := x.childIndex(ev, i, elemOptions(ev)) + return child +} + +// Lookup returns the value at path, or an invalid value if there is none. +func (x View) Lookup(path *structpath.PathNode) View { + return x.lookupComponents(components(path)) +} + +func (x View) lookupComponents(path []pathComponent) View { + for _, c := range path { + if c.isKey { + x = x.Get(c.key) + } else { + x = x.Index(c.index) + } + if !x.valid { + return x + } + } + return x +} + +// AsString returns the string value (a string, a pure reference, or an SDK native value). +func (x View) AsString() (string, bool) { + if x.Kind() != KindString { + return "", false + } + if ref := x.ref(); ref != "" { + return ref, true + } + v, opts := x.deref() + if v.Kind() == reflect.Struct { + return sdkNativeString(v, opts) + } + return v.String(), true +} + +// AsBool returns the bool value. +func (x View) AsBool() (bool, bool) { + if x.Kind() != KindBool { + return false, false + } + v, _ := x.deref() + return v.Bool(), true +} + +// AsInt returns the int value. +func (x View) AsInt() (int64, bool) { + if x.Kind() != KindInt { + return 0, false + } + v, _ := x.deref() + return v.Int(), true +} + +// AsFloat returns the float value. +func (x View) AsFloat() (float64, bool) { + if x.Kind() != KindFloat { + return 0, false + } + v, _ := x.deref() + return v.Float(), true +} + +// AsAny returns the value as a generic Go value (map[string]any, []any, string, +// bool, int64 or int, float64, or nil), like [dyn.Value.AsAny]. +func (x View) AsAny() any { + switch x.Kind() { + case KindMap: + out := map[string]any{} + for k, c := range x.MapItems() { + out[k] = c.AsAny() + } + return out + case KindSequence: + out := []any{} //nolint:gocritic // an empty sequence renders as [], not null + for _, c := range x.Sequence() { + out = append(out, c.AsAny()) + } + return out + case KindString: + s, _ := x.AsString() + return s + case KindBool: + b, _ := x.AsBool() + return b + case KindInt: + v, opts := x.deref() + // Values held in an interface keep the type the YAML loader produced (int), + // typed fields hold the normalized int64. + if opts&fromInterface != 0 && v.Type() == reflect.TypeFor[int]() { + return int(v.Int()) + } + return v.Int() + case KindFloat: + f, _ := x.AsFloat() + return f + default: + return nil + } +} diff --git a/libs/structs/structvar/walk.go b/libs/structs/structvar/walk.go new file mode 100644 index 00000000000..76b1b849a9e --- /dev/null +++ b/libs/structs/structvar/walk.go @@ -0,0 +1,52 @@ +package structvar + +import ( + "errors" + + "github.com/databricks/cli/libs/structs/structpath" +) + +// Walk calls fn for x and every value below it, parents before children, like +// [dyn.WalkReadOnly] visits a tree. Maps are visited in [View.MapItems] order. +// fn may return [ErrSkip] to skip the subtree of the visited value. +// +// fn may change the configuration at or below the visited path (e.g. with [Set]), +// but must not add or remove the keys and elements being iterated. +func Walk(x View, fn func(*structpath.PathNode, View) error) error { + return walk(x, nil, fn) +} + +func walk(x View, p *structpath.PathNode, fn func(*structpath.PathNode, View) error) error { + if err := fn(p, x); err != nil { + if errors.Is(err, ErrSkip) { + return nil + } + return err + } + + var err error + switch x.Kind() { + case KindMap: + x.eachMapItem(func(k string, dot bool, c View) bool { + var cp *structpath.PathNode + if dot { + cp = structpath.NewDotString(p, k) + } else { + cp = structpath.NewStringKey(p, k) + } + err = walk(c, cp, fn) + return err == nil + }) + case KindSequence: + x.eachElem(func(i int, c View) bool { + err = walk(c, structpath.NewIndex(p, i), fn) + return err == nil + }) + default: + } + return err +} + +// ErrSkip can be returned by the function passed to [Walk] to skip the subtree of the +// visited value. +var ErrSkip = errors.New("skip traversal of subtree") diff --git a/libs/structs/structvar/write.go b/libs/structs/structvar/write.go new file mode 100644 index 00000000000..e5c20c76e01 --- /dev/null +++ b/libs/structs/structvar/write.go @@ -0,0 +1,435 @@ +package structvar + +import ( + "errors" + "fmt" + "reflect" + "slices" + "strings" + + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/structs/structpath" +) + +// The methods in this file change sv.Value (a pointer) and keep sv.Refs and +// sv.Locations in sync. Values along the path are allocated as needed (nil pointers, +// nil maps), and map values are written back, so maps of structs can be changed too. +// Refs is replaced rather than modified, so copies of a StructVar can share it. + +// Set sets value at path, like setting it in the configuration tree: a zero value +// is kept as explicitly set (it is added to the governing ForceSendFields). The +// locations recorded for path are kept; references at and below path are dropped. +func (sv *StructVar) Set(path *structpath.PathNode, value any) error { + err := update(reflect.ValueOf(sv.Value), components(path), func(dst reflect.Value) (bool, error) { + rv := reflect.ValueOf(value) + if !rv.IsValid() { + dst.SetZero() + return false, nil + } + if err := setGoValue(dst, rv); err != nil { + return false, fmt.Errorf("cannot set %s: %w", path, err) + } + return rv.IsZero(), nil + }) + if err != nil { + return err + } + sv.Refs = withoutRefs(sv.Refs, path) + // The value keeps its own locations; those of the values it replaced below it are + // dropped. Rewriting the tree clones every ancestor (e.g. the map of all jobs), so + // it is skipped when there is nothing to drop. + cur := sv.Locations.Sub(path) + switch { + case cur == nil: + case value == nil: + sv.Locations = sv.Locations.With(path, nil) + case len(cur.keys) > 0 || len(cur.elems) > 0: + sv.Locations = sv.Locations.With(path, (*Locations)(nil).WithLocations(cur.Get())) + } + return nil +} + +func setGoValue(dst, src reflect.Value) error { + for src.Kind() == reflect.Interface || (src.Kind() == reflect.Pointer && dst.Kind() != reflect.Pointer) { + if src.IsNil() { + dst.SetZero() + return nil + } + src = src.Elem() + } + switch { + case src.Type().AssignableTo(dst.Type()): + dst.Set(src) + case dst.Kind() == reflect.Pointer && src.Type().AssignableTo(dst.Type().Elem()): + p := reflect.New(dst.Type().Elem()) + p.Elem().Set(src) + dst.Set(p) + case isScalarKind(src.Kind()) && isScalarKind(dst.Kind()) && src.Type().ConvertibleTo(dst.Type()) && (src.Kind() == reflect.String) == (dst.Kind() == reflect.String): + dst.Set(src.Convert(dst.Type())) + default: + return fmt.Errorf("cannot assign %s to %s", src.Type(), dst.Type()) + } + return nil +} + +func isScalarKind(k reflect.Kind) bool { + switch k { + case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: + return true + default: + return false + } +} + +// Delete removes the value at path: a map entry or slice element is deleted (the +// elements after it move down, with their locations and references), a struct field is +// zeroed. +func (sv *StructVar) Delete(path *structpath.PathNode) error { + p := components(path) + if len(p) == 0 { + return errors.New("cannot delete the root") + } + parent, last := p[:len(p)-1], p[len(p)-1] + shifted := false + err := walkTo(reflect.ValueOf(sv.Value), parent, func(v reflect.Value) error { + v = derefAlloc(v) + switch { + case v.Kind() == reflect.Map && last.isKey: + if !v.IsNil() { + v.SetMapIndex(reflect.ValueOf(last.key).Convert(v.Type().Key()), reflect.Value{}) + } + case v.Kind() == reflect.Slice && !last.isKey: + if last.index < 0 || last.index >= v.Len() { + return fmt.Errorf("index %d out of range at %s", last.index, path.Parent()) + } + n := v.Len() + sv.Refs = withoutRefs(sv.Refs, path) + sv.Locations = sv.Locations.With(path, nil) + shifted = true + reflect.Copy(v.Slice(last.index, n), v.Slice(last.index+1, n)) + v.Index(n - 1).SetZero() + v.SetLen(n - 1) + for i := last.index + 1; i < n; i++ { + sv.MoveMetadata(structpath.NewIndex(path.Parent(), i), structpath.NewIndex(path.Parent(), i-1)) + } + case v.Kind() == reflect.Struct && last.isKey: + info := GetStructInfo(v.Type()) + index, ok := info.Fields[last.key] + if !ok { + return fmt.Errorf("no field %q at %s", last.key, path.Parent()) + } + if f := FieldByIndex(v, index); f.IsValid() { + f.SetZero() + } + removeForceSend(v, info, last.key) + default: + return fmt.Errorf("cannot delete %s", path) + } + return nil + }) + if err != nil { + return err + } + if !shifted { + sv.Refs = withoutRefs(sv.Refs, path) + sv.Locations = sv.Locations.With(path, nil) + } + return nil +} + +// MoveMetadata moves the locations and references of the value at from and below it +// to to, replacing those at to. Values are not changed: the caller moves them. +func (sv *StructVar) MoveMetadata(from, to *structpath.PathNode) { + sv.CopyMetadataAt(from, to) + sv.Locations = sv.Locations.With(from, nil) + sv.Refs = withoutRefs(sv.Refs, from) +} + +// CopyMetadataAt copies the locations and references of the value at from and below +// it to to, replacing those at to. Values are not changed: the caller copies them. +func (sv *StructVar) CopyMetadataAt(from, to *structpath.PathNode) { + sv.Locations = sv.Locations.With(to, sv.Locations.Sub(from)) + if len(sv.Refs) == 0 { + return + } + fromKey := from.String() + refs := withoutRefs(sv.Refs, to) + for k, v := range sv.Refs { + if isUnder(k, fromKey) { + if moved, ok := rebaseKey(k, from, to); ok { + refs[moved] = v + } + } + } + sv.Refs = refs +} + +// CopyMetadata replaces the locations and references of the value at path and below it +// with those in src. Values are not changed: the caller copies them. +func (sv *StructVar) CopyMetadata(src *StructVar, path *structpath.PathNode) { + sv.Locations = sv.Locations.With(path, src.Locations.Sub(path)) + if len(sv.Refs) == 0 && len(src.Refs) == 0 { + return + } + prefix := path.String() + refs := withoutRefs(sv.Refs, path) + if len(refs) == 0 { + refs = map[string]string{} + } + for k, v := range src.Refs { + if isUnder(k, prefix) { + refs[k] = v + } + } + sv.Refs = refs +} + +// SetLocations sets the locations of the value at path and of every value below it, +// including values that have no locations yet (e.g. set in Go code). +func (sv *StructVar) SetLocations(path *structpath.PathNode, locs []diag.Location) { + x := sv.View().Lookup(path) + if !x.IsValid() { + return + } + sv.Locations = sv.Locations.With(path, locationsOf(x, locs)) +} + +func locationsOf(x View, locs []diag.Location) *Locations { + out := (*Locations)(nil).WithLocations(locs) + switch x.Kind() { + case KindMap: + for k, c := range x.MapItems() { + out.setChild(pathComponent{key: k, isKey: true}, locationsOf(c, locs)) + } + case KindSequence: + for i, c := range x.Sequence() { + out.setChild(pathComponent{index: i}, locationsOf(c, locs)) + } + default: + } + return out +} + +// UpdateSequence records that the elements of the sequence at path were rebuilt from +// the old ones: new element i comes from old elements sources[i] (see +// [Locations.WithSequence]). References of the old elements move along. +func (sv *StructVar) UpdateSequence(path *structpath.PathNode, sources [][]int) { + sv.Locations = sv.Locations.WithSequence(path, sources) + if len(sv.Refs) == 0 { + return + } + elems := path.String() + "[" + refs := make(map[string]string, len(sv.Refs)) + for k, v := range sv.Refs { + if !strings.HasPrefix(k, elems) { + refs[k] = v + } + } + for i, src := range sources { + to := structpath.NewIndex(path, i) + for _, old := range src { + from := structpath.NewIndex(path, old) + fromKey := from.String() + for k, v := range sv.Refs { + if isUnder(k, fromKey) { + if moved, ok := rebaseKey(k, from, to); ok { + refs[moved] = v + } + } + } + } + } + sv.Refs = refs +} + +// rebaseKey returns the refs key k, at or below from, moved to the same place below to. +func rebaseKey(k string, from, to *structpath.PathNode) (string, bool) { + p, err := structpath.ParsePath(k) + if err != nil { + return "", false + } + return structpath.Join(to, p.SkipPrefix(from.Len()).AsSlice()...).String(), true +} + +// isUnder reports whether the path string key is prefix or a path below it. +func isUnder(key, prefix string) bool { + if prefix == "" || key == prefix { + return true + } + if !strings.HasPrefix(key, prefix) { + return false + } + c := key[len(prefix)] + return c == '.' || c == '[' +} + +// withoutRefs returns a copy of refs without the references at and below path. +func withoutRefs(refs map[string]string, path *structpath.PathNode) map[string]string { + if len(refs) == 0 { + return refs + } + prefix := path.String() + out := make(map[string]string, len(refs)) + for k, v := range refs { + if !isUnder(k, prefix) { + out[k] = v + } + } + return out +} + +// SetReference sets the value at path to the reference ref. A string (or interface) +// field holds the reference itself; for other fields the typed value is set to zero, +// like decoding does, and the reference is recorded in Refs. +func (sv *StructVar) SetReference(path *structpath.PathNode, ref string) error { + isText := false + err := walkTo(reflect.ValueOf(sv.Value), components(path), func(dst reflect.Value) error { + switch dst.Kind() { + case reflect.String, reflect.Interface: + isText = true + case reflect.Pointer: + // The reference is recorded on an allocated zero value, so it is present. + dst.Set(reflect.New(dst.Type().Elem())) + default: + dst.SetZero() + } + return nil + }) + if err != nil { + return err + } + if isText { + return sv.Set(path, ref) + } + locs := sv.Locations.Exact(path) + // withoutRefs returns a fresh map unless there is nothing to filter. + refs := withoutRefs(sv.Refs, path) + if len(refs) == 0 { + refs = map[string]string{} + } + refs[path.String()] = ref + sv.Refs = refs + sv.Locations = sv.Locations.With(path, (*Locations)(nil).WithLocations(locs)) + return nil +} + +func addForceSend(v reflect.Value, info *StructInfo, k string) { + index, ok := info.ForceSendFieldsIndex[k] + if !ok { + return + } + fsf := GetOrNewFieldByIndex(v, index) + name := info.GolangNames[k] + if slices.Contains(fsf.Interface().([]string), name) { + return + } + fsf.Set(reflect.Append(fsf, reflect.ValueOf(name))) +} + +func removeForceSend(v reflect.Value, info *StructInfo, k string) { + index, ok := info.ForceSendFieldsIndex[k] + if !ok { + return + } + fsf := FieldByIndex(v, index) + if !fsf.IsValid() { + return + } + name := info.GolangNames[k] + names := fsf.Interface().([]string) + if i := slices.Index(names, name); i >= 0 { + fsf.Set(reflect.ValueOf(slices.Delete(slices.Clone(names), i, i+1))) + } +} + +// derefAlloc follows pointers, allocating nil ones, and returns the value they point to. +func derefAlloc(v reflect.Value) reflect.Value { + for v.Kind() == reflect.Pointer { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + return v +} + +// walkTo calls fn with the settable value at path. +func walkTo(v reflect.Value, path []pathComponent, fn func(reflect.Value) error) error { + if len(path) == 0 { + return fn(v) + } + return updateChild(derefAlloc(v), path[0], func(child reflect.Value) (bool, error) { + return false, walkTo(child, path[1:], fn) + }) +} + +// update calls fn with the settable value at path. If fn reports that it set a zero +// value into a struct field, the field is added to the governing ForceSendFields. +func update(v reflect.Value, path []pathComponent, fn func(reflect.Value) (bool, error)) error { + if len(path) == 0 { + if v.Kind() == reflect.Pointer && !v.CanSet() { + v = v.Elem() + } + _, err := fn(v) + return err + } + return walkTo(v, path[:len(path)-1], func(parent reflect.Value) error { + return updateChild(derefAlloc(parent), path[len(path)-1], fn) + }) +} + +// updateChild calls fn with the settable child c of v. Interfaces and map values are +// copied and written back; an index one past the end of a slice appends an element. +func updateChild(v reflect.Value, c pathComponent, fn func(reflect.Value) (bool, error)) error { + if v.Kind() == reflect.Interface { + if v.IsNil() { + return errors.New("cannot index nil value") + } + cp := reflect.New(v.Elem().Type()).Elem() + cp.Set(v.Elem()) + err := updateChild(derefAlloc(cp), c, fn) + v.Set(cp) + return err + } + + switch { + case c.isKey && v.Kind() == reflect.Struct: + info := GetStructInfo(v.Type()) + index, ok := info.Fields[c.key] + if !ok { + return fmt.Errorf("no field %q", c.key) + } + zero, err := fn(GetOrNewFieldByIndex(v, index)) + if err != nil { + return err + } + if zero { + addForceSend(v, info, c.key) + } + return nil + case c.isKey && v.Kind() == reflect.Map: + if v.IsNil() { + v.Set(reflect.MakeMap(v.Type())) + } + key := reflect.ValueOf(c.key).Convert(v.Type().Key()) + e := reflect.New(v.Type().Elem()).Elem() + if old := v.MapIndex(key); old.IsValid() { + e.Set(old) + } + _, err := fn(e) + v.SetMapIndex(key, e) + return err + case !c.isKey && v.Kind() == reflect.Slice: + if c.index == v.Len() { + v.Set(reflect.Append(v, reflect.New(v.Type().Elem()).Elem())) + } + if c.index < 0 || c.index >= v.Len() { + return fmt.Errorf("index %d out of range", c.index) + } + _, err := fn(v.Index(c.index)) + return err + default: + return fmt.Errorf("cannot index %s", v.Kind()) + } +} diff --git a/libs/structs/structvar/yaml_anchor_test.go b/libs/structs/structvar/yaml_anchor_test.go new file mode 100644 index 00000000000..f4fe8e9dfd1 --- /dev/null +++ b/libs/structs/structvar/yaml_anchor_test.go @@ -0,0 +1,115 @@ +package structvar_test + +import ( + "testing" + + "github.com/databricks/cli/libs/diag" + "github.com/stretchr/testify/assert" +) + +func loc(file string, line, column int) []diag.Location { + return []diag.Location{{File: file, Line: line, Column: column}} +} + +func TestYAMLAnchor01(t *testing.T) { + file := "testdata/yaml/anchor_01.yml" + v, sv := loadYAML(t, file) + + // The anchor is not reported as an unknown field, the other keys are. + type known struct{} + warnings := decodeFields[known](t, file) + assert.NotContains(t, warnings, "unknown field: defaults") + assert.Contains(t, warnings, "unknown field: shirt1") + assert.Contains(t, warnings, "unknown field: shirt2") + + assert.Equal(t, "striped", at(t, v, "shirt1", "pattern")) + assert.Equal(t, loc(file, 8, 12), locsAt(sv, "shirt1", "pattern")) +} + +func TestYAMLAnchor02(t *testing.T) { + file := "testdata/yaml/anchor_02.yml" + v, sv := loadYAML(t, file) + + assert.Equal(t, "red", at(t, v, "shirt", "color")) + assert.Equal(t, loc(file, 4, 10), locsAt(sv, "shirt", "color")) + + assert.Equal(t, "cotton", at(t, v, "shirt", "primary")) + assert.Equal(t, loc(file, 8, 12), locsAt(sv, "shirt", "primary")) + + assert.Equal(t, "striped", at(t, v, "shirt", "pattern")) + assert.Equal(t, loc(file, 13, 12), locsAt(sv, "shirt", "pattern")) +} + +func TestYAMLAnchor03(t *testing.T) { + file := "testdata/yaml/anchor_03.yml" + v, sv := loadYAML(t, file) + + // Assert the override took place. + assert.Equal(t, "blue", at(t, v, "shirt", "color")) + assert.Equal(t, loc(file, 10, 10), locsAt(sv, "shirt", "color")) +} + +func TestYAMLAnchor04(t *testing.T) { + file := "testdata/yaml/anchor_04.yml" + v, sv := loadYAML(t, file) + + assert.Equal(t, "San Francisco", at(t, v, "person1", "address", "city")) + assert.Equal(t, loc(file, 4, 9), locsAt(sv, "person1", "address", "city")) + + assert.Equal(t, "Los Angeles", at(t, v, "person2", "address", "city")) + assert.Equal(t, loc(file, 16, 11), locsAt(sv, "person2", "address", "city")) +} + +func TestYAMLAnchor05(t *testing.T) { + file := "testdata/yaml/anchor_05.yml" + v, sv := loadYAML(t, file) + + assert.Equal(t, "wifi", at(t, v, "phone1", "features", 0)) + assert.Equal(t, loc(file, 4, 5), locsAt(sv, "phone1", "features", 0)) + assert.Equal(t, "bluetooth", at(t, v, "phone1", "features", 1)) + assert.Equal(t, loc(file, 5, 5), locsAt(sv, "phone1", "features", 1)) +} + +func TestYAMLAnchor06(t *testing.T) { + file := "testdata/yaml/anchor_06.yml" + v, sv := loadYAML(t, file) + + assert.Equal(t, "Hello, World!", at(t, v, "greeting1")) + assert.Equal(t, loc(file, 2, 16), locsAt(sv, "greeting1")) +} + +func TestYAMLAnchor07(t *testing.T) { + file := "testdata/yaml/anchor_07.yml" + v, sv := loadYAML(t, file) + + assert.Equal(t, "Alice", at(t, v, "person1", "name")) + assert.Equal(t, loc(file, 5, 9), locsAt(sv, "person1", "name")) + + assert.Equal(t, 25, at(t, v, "person1", "age")) + assert.Equal(t, loc(file, 2, 13), locsAt(sv, "person1", "age")) +} + +func TestYAMLAnchor08(t *testing.T) { + file := "testdata/yaml/anchor_08.yml" + v, sv := loadYAML(t, file) + + assert.Equal(t, "user1", at(t, v, "user1", "username")) + assert.Equal(t, loc(file, 5, 13), locsAt(sv, "user1", "username")) + + assert.Equal(t, true, at(t, v, "user1", "active")) + assert.Equal(t, loc(file, 2, 11), locsAt(sv, "user1", "active")) +} + +func TestYAMLAnchor09(t *testing.T) { + file := "testdata/yaml/anchor_09.yml" + _, _, err := decodeFile(t, file) + assert.ErrorContains(t, err, `cyclic reference to anchor "x"`) +} + +func TestYAMLAnchor10(t *testing.T) { + file := "testdata/yaml/anchor_10.yml" + v, _ := loadYAML(t, file) + + assert.Equal(t, 1, at(t, v, "use1", "value")) + assert.Equal(t, 1, at(t, v, "use2", "value")) +} diff --git a/libs/dyn/yamlloader/yaml_error_test.go b/libs/structs/structvar/yaml_error_test.go similarity index 74% rename from libs/dyn/yamlloader/yaml_error_test.go rename to libs/structs/structvar/yaml_error_test.go index a18f9599b05..1e1954e8c0e 100644 --- a/libs/dyn/yamlloader/yaml_error_test.go +++ b/libs/structs/structvar/yaml_error_test.go @@ -1,11 +1,9 @@ -package yamlloader_test +package structvar_test import ( - "bytes" "os" "testing" - "github.com/databricks/cli/libs/dyn/yamlloader" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.yaml.in/yaml/v3" @@ -13,9 +11,9 @@ import ( func TestYAMLErrorMapMerge(t *testing.T) { for _, file := range []string{ - "testdata/error_01.yml", - "testdata/error_02.yml", - "testdata/error_03.yml", + "testdata/yaml/error_01.yml", + "testdata/yaml/error_02.yml", + "testdata/yaml/error_03.yml", } { input, err := os.ReadFile(file) require.NoError(t, err) @@ -28,7 +26,7 @@ func TestYAMLErrorMapMerge(t *testing.T) { }) t.Run("self", func(t *testing.T) { - _, err := yamlloader.LoadYAML(file, bytes.NewBuffer(input)) + _, _, err := decodeFile(t, file) assert.ErrorContains(t, err, "map merge requires map or sequence of maps as the value") }) }) diff --git a/libs/structs/structvar/yaml_mix_test.go b/libs/structs/structvar/yaml_mix_test.go new file mode 100644 index 00000000000..b9529424d20 --- /dev/null +++ b/libs/structs/structvar/yaml_mix_test.go @@ -0,0 +1,48 @@ +package structvar_test + +import ( + "bytes" + "os" + "testing" + + "github.com/databricks/cli/libs/structs/structvar" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// decodeFields decodes the file into a struct that has only the given fields and +// returns the names of the unknown fields reported. Anchors must not be reported. +func decodeFields[T any](t *testing.T, file string) []string { + input, err := os.ReadFile(file) + require.NoError(t, err) + var dst T + _, diags, err := structvar.DecodeYAML(file, bytes.NewBuffer(input), &dst) + require.NoError(t, err) + var out []string + for _, d := range diags { + out = append(out, d.Summary) + } + return out +} + +// The old tests checked dyn.Value.IsAnchor. The decoder exposes anchors only through +// the "unknown field" warning, which is not reported for anchors. +func TestYAMLMix01(t *testing.T) { + file := "testdata/yaml/mix_01.yml" + loadYAML(t, file) + + type known struct{} + warnings := decodeFields[known](t, file) + assert.Contains(t, warnings, "unknown field: office_address") + assert.NotContains(t, warnings, "unknown field: base_address") +} + +func TestYAMLMix02(t *testing.T) { + file := "testdata/yaml/mix_02.yml" + loadYAML(t, file) + + type known struct{} + warnings := decodeFields[known](t, file) + assert.Contains(t, warnings, "unknown field: theme") + assert.NotContains(t, warnings, "unknown field: base_colors") +} diff --git a/libs/structs/structvar/yaml_spec_test.go b/libs/structs/structvar/yaml_spec_test.go new file mode 100644 index 00000000000..16eeb7e22d4 --- /dev/null +++ b/libs/structs/structvar/yaml_spec_test.go @@ -0,0 +1,803 @@ +package structvar_test + +import ( + "math" + "testing" + + "github.com/databricks/cli/libs/diag" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const NL = "\n" + +func TestYAMLSpecExample_2_1(t *testing.T) { + file := "testdata/yaml/spec_example_2.1.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + []ev{ + nv("Mark McGwire", []diag.Location{{File: file, Line: 3, Column: 3}}), + nv("Sammy Sosa", []diag.Location{{File: file, Line: 4, Column: 3}}), + nv("Ken Griffey", []diag.Location{{File: file, Line: 5, Column: 3}}), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_2(t *testing.T) { + file := "testdata/yaml/spec_example_2.2.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "hr": nv(65, []diag.Location{{File: file, Line: 3, Column: 6}}), + "avg": nv(0.278, []diag.Location{{File: file, Line: 4, Column: 6}}), + "rbi": nv(147, []diag.Location{{File: file, Line: 5, Column: 6}}), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_3(t *testing.T) { + file := "testdata/yaml/spec_example_2.3.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "american": nv( + []ev{ + nv("Boston Red Sox", []diag.Location{{File: file, Line: 4, Column: 3}}), + nv("Detroit Tigers", []diag.Location{{File: file, Line: 5, Column: 3}}), + nv("New York Yankees", []diag.Location{{File: file, Line: 6, Column: 3}}), + }, + []diag.Location{{File: file, Line: 4, Column: 1}}, + ), + "national": nv( + []ev{ + nv("New York Mets", []diag.Location{{File: file, Line: 8, Column: 3}}), + nv("Chicago Cubs", []diag.Location{{File: file, Line: 9, Column: 3}}), + nv("Atlanta Braves", []diag.Location{{File: file, Line: 10, Column: 3}}), + }, + []diag.Location{{File: file, Line: 8, Column: 1}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_4(t *testing.T) { + file := "testdata/yaml/spec_example_2.4.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + []ev{ + nv( + map[string]ev{ + "name": nv("Mark McGwire", []diag.Location{{File: file, Line: 4, Column: 9}}), + "hr": nv(65, []diag.Location{{File: file, Line: 5, Column: 9}}), + "avg": nv(0.278, []diag.Location{{File: file, Line: 6, Column: 9}}), + }, + []diag.Location{{File: file, Line: 4, Column: 3}}, + ), + nv( + map[string]ev{ + "name": nv("Sammy Sosa", []diag.Location{{File: file, Line: 8, Column: 9}}), + "hr": nv(63, []diag.Location{{File: file, Line: 9, Column: 9}}), + "avg": nv(0.288, []diag.Location{{File: file, Line: 10, Column: 9}}), + }, + []diag.Location{{File: file, Line: 8, Column: 3}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_5(t *testing.T) { + file := "testdata/yaml/spec_example_2.5.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + []ev{ + nv( + []ev{ + nv("name", []diag.Location{{File: file, Line: 3, Column: 4}}), + nv("hr", []diag.Location{{File: file, Line: 3, Column: 18}}), + nv("avg", []diag.Location{{File: file, Line: 3, Column: 22}}), + }, + []diag.Location{{File: file, Line: 3, Column: 3}}, + ), + nv( + []ev{ + nv("Mark McGwire", []diag.Location{{File: file, Line: 4, Column: 4}}), + nv(65, []diag.Location{{File: file, Line: 4, Column: 18}}), + nv(0.278, []diag.Location{{File: file, Line: 4, Column: 22}}), + }, + []diag.Location{{File: file, Line: 4, Column: 3}}, + ), + nv( + []ev{ + nv("Sammy Sosa", []diag.Location{{File: file, Line: 5, Column: 4}}), + nv(63, []diag.Location{{File: file, Line: 5, Column: 18}}), + nv(0.288, []diag.Location{{File: file, Line: 5, Column: 22}}), + }, + []diag.Location{{File: file, Line: 5, Column: 3}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_6(t *testing.T) { + file := "testdata/yaml/spec_example_2.6.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "Mark McGwire": nv( + map[string]ev{ + "hr": nv(65, []diag.Location{{File: file, Line: 3, Column: 20}}), + "avg": nv(0.278, []diag.Location{{File: file, Line: 3, Column: 29}}), + }, + []diag.Location{{File: file, Line: 3, Column: 15}}, + ), + "Sammy Sosa": nv( + map[string]ev{ + "hr": nv(63, []diag.Location{{File: file, Line: 5, Column: 9}}), + "avg": nv(0.288, []diag.Location{{File: file, Line: 6, Column: 10}}), + }, + []diag.Location{{File: file, Line: 4, Column: 13}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_7(t *testing.T) { + file := "testdata/yaml/spec_example_2.7.yml" + v, sv := loadExample(t, file) + + // Note: we do not support multiple documents in a single YAML file. + + assertDecoded(t, nv( + []ev{ + nv( + "Mark McGwire", + []diag.Location{{File: file, Line: 5, Column: 3}}, + ), + nv( + "Sammy Sosa", + []diag.Location{{File: file, Line: 6, Column: 3}}, + ), + nv( + "Ken Griffey", + []diag.Location{{File: file, Line: 7, Column: 3}}, + ), + }, + []diag.Location{{File: file, Line: 5, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_8(t *testing.T) { + file := "testdata/yaml/spec_example_2.8.yml" + v, sv := loadExample(t, file) + + // Note: we do not support multiple documents in a single YAML file. + + assertDecoded(t, nv( + map[string]ev{ + "time": nv("20:03:20", []diag.Location{{File: file, Line: 4, Column: 7}}), + "player": nv("Sammy Sosa", []diag.Location{{File: file, Line: 5, Column: 9}}), + "action": nv("strike (miss)", []diag.Location{{File: file, Line: 6, Column: 9}}), + }, + []diag.Location{{File: file, Line: 4, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_9(t *testing.T) { + file := "testdata/yaml/spec_example_2.9.yml" + v, sv := loadExample(t, file) + + // Note: we do not support multiple documents in a single YAML file. + + assertDecoded(t, nv( + map[string]ev{ + "hr": nv( + []ev{ + nv("Mark McGwire", []diag.Location{{File: file, Line: 5, Column: 3}}), + nv("Sammy Sosa", []diag.Location{{File: file, Line: 6, Column: 3}}), + }, + []diag.Location{{File: file, Line: 5, Column: 1}}, + ), + "rbi": nv( + []ev{ + nv("Sammy Sosa", []diag.Location{{File: file, Line: 9, Column: 3}}), + nv("Ken Griffey", []diag.Location{{File: file, Line: 10, Column: 3}}), + }, + []diag.Location{{File: file, Line: 9, Column: 1}}, + ), + }, + []diag.Location{{File: file, Line: 4, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_10(t *testing.T) { + file := "testdata/yaml/spec_example_2.10.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "hr": nv( + []ev{ + nv("Mark McGwire", []diag.Location{{File: file, Line: 5, Column: 3}}), + nv("Sammy Sosa", []diag.Location{{File: file, Line: 7, Column: 3}}), + }, + []diag.Location{{File: file, Line: 5, Column: 1}}, + ), + "rbi": nv( + []ev{ + // The location for an anchored value refers to the anchor, not the reference. + // This is the same location as the anchor that appears in the "hr" mapping. + nv("Sammy Sosa", []diag.Location{{File: file, Line: 7, Column: 3}}), + nv("Ken Griffey", []diag.Location{{File: file, Line: 10, Column: 3}}), + }, + []diag.Location{{File: file, Line: 9, Column: 1}}, + ), + }, + []diag.Location{{File: file, Line: 4, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_11(t *testing.T) { + file := "testdata/yaml/spec_example_2.11.yml" + // Note: non-string mapping keys are not supported by "go.yaml.in/yaml/v3". + _, _, err := decodeFile(t, file) + assert.ErrorContains(t, err, `: key is not a scalar`) +} + +func TestYAMLSpecExample_2_12(t *testing.T) { + file := "testdata/yaml/spec_example_2.12.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + []ev{ + nv( + map[string]ev{ + "item": nv("Super Hoop", []diag.Location{{File: file, Line: 5, Column: 13}}), + "quantity": nv(1, []diag.Location{{File: file, Line: 6, Column: 13}}), + }, + []diag.Location{{File: file, Line: 5, Column: 3}}, + ), + nv( + map[string]ev{ + "item": nv("Basketball", []diag.Location{{File: file, Line: 7, Column: 13}}), + "quantity": nv(4, []diag.Location{{File: file, Line: 8, Column: 13}}), + }, + []diag.Location{{File: file, Line: 7, Column: 3}}, + ), + nv( + map[string]ev{ + "item": nv("Big Shoes", []diag.Location{{File: file, Line: 9, Column: 13}}), + "quantity": nv(1, []diag.Location{{File: file, Line: 10, Column: 13}}), + }, + []diag.Location{{File: file, Line: 9, Column: 3}}, + ), + }, + []diag.Location{{File: file, Line: 5, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_13(t *testing.T) { + file := "testdata/yaml/spec_example_2.13.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + ``+ + `\//||\/||`+NL+ + "// || ||__"+NL, + []diag.Location{{File: file, Line: 4, Column: 5}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_14(t *testing.T) { + file := "testdata/yaml/spec_example_2.14.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + `Mark McGwire's year was crippled by a knee injury.`+NL, + []diag.Location{{File: file, Line: 3, Column: 5}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_15(t *testing.T) { + file := "testdata/yaml/spec_example_2.15.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + ``+ + `Sammy Sosa completed another fine season with great stats.`+NL+ + NL+ + ` 63 Home Runs`+NL+ + ` 0.288 Batting Average`+NL+ + NL+ + `What a year!`+NL, + []diag.Location{{File: file, Line: 3, Column: 5}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_16(t *testing.T) { + file := "testdata/yaml/spec_example_2.16.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "name": nv( + "Mark McGwire", + []diag.Location{{File: file, Line: 3, Column: 7}}, + ), + "accomplishment": nv( + `Mark set a major league home run record in 1998.`+NL, + []diag.Location{{File: file, Line: 4, Column: 17}}, + ), + "stats": nv( + ``+ + `65 Home Runs`+NL+ + `0.278 Batting Average`+NL, + []diag.Location{{File: file, Line: 7, Column: 8}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_17(t *testing.T) { + file := "testdata/yaml/spec_example_2.17.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "unicode": nv( + `Sosa did fine.`+"\u263A", + []diag.Location{{File: file, Line: 3, Column: 10}}, + ), + "control": nv( + "\b1998\t1999\t2000\n", + []diag.Location{{File: file, Line: 4, Column: 10}}, + ), + "hex esc": nv( + "\x0d\x0a is \r\n", + []diag.Location{{File: file, Line: 5, Column: 10}}, + ), + "single": nv( + `"Howdy!" he cried.`, + []diag.Location{{File: file, Line: 7, Column: 9}}, + ), + "quoted": nv( + ` # Not a 'comment'.`, + []diag.Location{{File: file, Line: 8, Column: 9}}, + ), + "tie-fighter": nv( + `|\-*-/|`, + []diag.Location{{File: file, Line: 9, Column: 14}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_18(t *testing.T) { + file := "testdata/yaml/spec_example_2.18.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "plain": nv( + `This unquoted scalar spans many lines.`, + []diag.Location{{File: file, Line: 4, Column: 3}}, + ), + "quoted": nv( + `So does this quoted scalar.`+NL, + []diag.Location{{File: file, Line: 7, Column: 9}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_19(t *testing.T) { + file := "testdata/yaml/spec_example_2.19.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "canonical": nv( + 12345, + []diag.Location{{File: file, Line: 3, Column: 12}}, + ), + "decimal": nv( + 12345, + []diag.Location{{File: file, Line: 4, Column: 10}}, + ), + "octal": nv( + 12, + []diag.Location{{File: file, Line: 5, Column: 8}}, + ), + "hexadecimal": nv( + 12, + []diag.Location{{File: file, Line: 6, Column: 14}}, + ), + "octal11": nv( + 12345, + []diag.Location{{File: file, Line: 15, Column: 10}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_20(t *testing.T) { + file := "testdata/yaml/spec_example_2.20.yml" + v, sv := loadExample(t, file) + + // Equality assertion doesn't work with NaNs. + // See https://github.com/stretchr/testify/issues/624. + // + // Remove the NaN entry. + m := v.(map[string]any) + nan, ok := m["not a number"].(float64) + require.True(t, ok) + assert.True(t, math.IsNaN(nan)) + delete(m, "not a number") + + assertDecoded(t, nv( + map[string]ev{ + "canonical": nv( + 1230.15, + []diag.Location{{File: file, Line: 3, Column: 12}}, + ), + "exponential": nv( + 1230.15, + []diag.Location{{File: file, Line: 4, Column: 14}}, + ), + "fixed": nv( + 1230.15, + []diag.Location{{File: file, Line: 5, Column: 8}}, + ), + "negative infinity": nv( + math.Inf(-1), + []diag.Location{{File: file, Line: 6, Column: 20}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_21(t *testing.T) { + file := "testdata/yaml/spec_example_2.21.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "null": nv( + nil, + []diag.Location{{File: file, Line: 3, Column: 6}}, + ), + "booleans": nv( + []ev{ + nv(true, []diag.Location{{File: file, Line: 4, Column: 13}}), + nv(false, []diag.Location{{File: file, Line: 4, Column: 19}}), + }, + []diag.Location{{File: file, Line: 4, Column: 11}}, + ), + "string": nv( + "012345", + []diag.Location{{File: file, Line: 5, Column: 9}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_22(t *testing.T) { + file := "testdata/yaml/spec_example_2.22.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "canonical": nv( + "2001-12-15T02:59:43.1Z", + []diag.Location{{File: file, Line: 3, Column: 12}}, + ), + "iso8601": nv( + "2001-12-14t21:59:43.10-05:00", + []diag.Location{{File: file, Line: 4, Column: 10}}, + ), + "spaced": nv( + // This is parsed as a string, not a timestamp, + // both by "go.yaml.in/yaml/v3" and by our implementation. + "2001-12-14 21:59:43.10 -5", + []diag.Location{{File: file, Line: 5, Column: 9}}, + ), + "date": nv( + "2002-12-14", + []diag.Location{{File: file, Line: 6, Column: 7}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 1}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_23(t *testing.T) { + file := "testdata/yaml/spec_example_2.23.yml" + // Note: the !!binary tag is not supported by us. + _, _, err := decodeFile(t, file) + assert.ErrorContains(t, err, `: unknown tag: !!binary`) +} + +func TestYAMLSpecExample_2_24(t *testing.T) { + file := "testdata/yaml/spec_example_2.24.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + []ev{ + nv( + map[string]ev{ + "center": nv( + map[string]ev{ + "x": nv(73, []diag.Location{{File: file, Line: 8, Column: 23}}), + "y": nv(129, []diag.Location{{File: file, Line: 8, Column: 30}}), + }, + []diag.Location{{File: file, Line: 8, Column: 11}}, + ), + "radius": nv(7, []diag.Location{{File: file, Line: 9, Column: 11}}), + }, + []diag.Location{{File: file, Line: 7, Column: 3}}, + ), + nv( + map[string]ev{ + "start": nv( + map[string]ev{ + "x": nv(73, []diag.Location{{File: file, Line: 8, Column: 23}}), + "y": nv(129, []diag.Location{{File: file, Line: 8, Column: 30}}), + }, + []diag.Location{{File: file, Line: 8, Column: 11}}, + ), + "finish": nv( + map[string]ev{ + "x": nv(89, []diag.Location{{File: file, Line: 12, Column: 16}}), + "y": nv(102, []diag.Location{{File: file, Line: 12, Column: 23}}), + }, + []diag.Location{{File: file, Line: 12, Column: 11}}, + ), + }, + []diag.Location{{File: file, Line: 10, Column: 3}}, + ), + nv( + map[string]ev{ + "start": nv( + map[string]ev{ + "x": nv(73, []diag.Location{{File: file, Line: 8, Column: 23}}), + "y": nv(129, []diag.Location{{File: file, Line: 8, Column: 30}}), + }, + []diag.Location{{File: file, Line: 8, Column: 11}}, + ), + "color": nv(16772795, []diag.Location{{File: file, Line: 15, Column: 10}}), + "text": nv("Pretty vector drawing.", []diag.Location{{File: file, Line: 16, Column: 9}}), + }, + []diag.Location{{File: file, Line: 13, Column: 3}}, + ), + }, + []diag.Location{{File: file, Line: 4, Column: 5}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_25(t *testing.T) { + file := "testdata/yaml/spec_example_2.25.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "Mark McGwire": nv(nil, []diag.Location{{File: file, Line: 8, Column: 1}}), + "Sammy Sosa": nv(nil, []diag.Location{{File: file, Line: 9, Column: 1}}), + "Ken Griffey": nv(nil, []diag.Location{{File: file, Line: 10, Column: 1}}), + }, + []diag.Location{{File: file, Line: 6, Column: 5}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_26(t *testing.T) { + file := "testdata/yaml/spec_example_2.26.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + []ev{ + nv( + map[string]ev{ + "Mark McGwire": nv(65, []diag.Location{{File: file, Line: 7, Column: 17}}), + }, + []diag.Location{{File: file, Line: 7, Column: 3}}, + ), + nv( + map[string]ev{ + "Sammy Sosa": nv(63, []diag.Location{{File: file, Line: 8, Column: 15}}), + }, + []diag.Location{{File: file, Line: 8, Column: 3}}, + ), + nv( + map[string]ev{ + "Ken Griffey": nv(58, []diag.Location{{File: file, Line: 9, Column: 16}}), + }, + []diag.Location{{File: file, Line: 9, Column: 3}}, + ), + }, + []diag.Location{{File: file, Line: 6, Column: 5}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_27(t *testing.T) { + file := "testdata/yaml/spec_example_2.27.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "invoice": nv( + 34843, + []diag.Location{{File: file, Line: 4, Column: 10}}, + ), + "date": nv( + "2001-01-23", + []diag.Location{{File: file, Line: 5, Column: 10}}, + ), + "bill-to": nv( + map[string]ev{ + "given": nv( + "Chris", + []diag.Location{{File: file, Line: 7, Column: 12}}, + ), + "family": nv( + "Dumars", + []diag.Location{{File: file, Line: 8, Column: 12}}, + ), + "address": nv( + map[string]ev{ + "lines": nv( + "458 Walkman Dr.\nSuite #292\n", + []diag.Location{{File: file, Line: 10, Column: 12}}, + ), + "city": nv( + "Royal Oak", + []diag.Location{{File: file, Line: 13, Column: 15}}, + ), + "state": nv( + "MI", + []diag.Location{{File: file, Line: 14, Column: 15}}, + ), + "postal": nv( + 48046, + []diag.Location{{File: file, Line: 15, Column: 15}}, + ), + }, + []diag.Location{{File: file, Line: 10, Column: 5}}, + ), + }, + []diag.Location{{File: file, Line: 6, Column: 10}}, + ), + "ship-to": nv( + map[string]ev{ + "given": nv( + "Chris", + []diag.Location{{File: file, Line: 7, Column: 12}}, + ), + "family": nv( + "Dumars", + []diag.Location{{File: file, Line: 8, Column: 12}}, + ), + "address": nv( + map[string]ev{ + "lines": nv( + "458 Walkman Dr.\nSuite #292\n", + []diag.Location{{File: file, Line: 10, Column: 12}}, + ), + "city": nv( + "Royal Oak", + []diag.Location{{File: file, Line: 13, Column: 15}}, + ), + "state": nv( + "MI", + []diag.Location{{File: file, Line: 14, Column: 15}}, + ), + "postal": nv( + 48046, + []diag.Location{{File: file, Line: 15, Column: 15}}, + ), + }, + []diag.Location{{File: file, Line: 10, Column: 5}}, + ), + }, + []diag.Location{{File: file, Line: 6, Column: 10}}, + ), + "product": nv( + []ev{ + nv( + map[string]ev{ + "sku": nv( + "BL394D", + []diag.Location{{File: file, Line: 18, Column: 17}}, + ), + "quantity": nv( + 4, + []diag.Location{{File: file, Line: 19, Column: 17}}, + ), + "description": nv( + "Basketball", + []diag.Location{{File: file, Line: 20, Column: 17}}, + ), + "price": nv( + 450.0, + []diag.Location{{File: file, Line: 21, Column: 17}}, + ), + }, + []diag.Location{{File: file, Line: 18, Column: 3}}, + ), nv( + map[string]ev{ + "sku": nv( + "BL4438H", + []diag.Location{{File: file, Line: 22, Column: 17}}, + ), + "quantity": nv( + 1, + []diag.Location{{File: file, Line: 23, Column: 17}}, + ), + "description": nv( + "Super Hoop", + []diag.Location{{File: file, Line: 24, Column: 17}}, + ), + "price": nv( + 2392.0, + []diag.Location{{File: file, Line: 25, Column: 17}}, + ), + }, + []diag.Location{{File: file, Line: 22, Column: 3}}, + ), + }, + []diag.Location{{File: file, Line: 18, Column: 1}}, + ), + "tax": nv( + 251.42, + []diag.Location{{File: file, Line: 26, Column: 8}}, + ), + "total": nv( + 4443.52, + []diag.Location{{File: file, Line: 27, Column: 8}}, + ), + "comments": nv( + "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.", + []diag.Location{{File: file, Line: 29, Column: 3}}, + ), + }, + []diag.Location{{File: file, Line: 3, Column: 5}}, + ), v, sv) +} + +func TestYAMLSpecExample_2_28(t *testing.T) { + file := "testdata/yaml/spec_example_2.28.yml" + v, sv := loadExample(t, file) + + assertDecoded(t, nv( + map[string]ev{ + "Time": nv( + "2001-11-23 15:01:42 -5", + []diag.Location{{File: file, Line: 4, Column: 7}}, + ), + "User": nv( + "ed", + []diag.Location{{File: file, Line: 5, Column: 7}}, + ), + "Warning": nv( + "This is an error message for the log file", + []diag.Location{{File: file, Line: 7, Column: 3}}, + ), + }, + []diag.Location{{File: file, Line: 4, Column: 1}}, + ), v, sv) +} diff --git a/libs/structs/structvar/yaml_test.go b/libs/structs/structvar/yaml_test.go new file mode 100644 index 00000000000..d75410f6fc0 --- /dev/null +++ b/libs/structs/structvar/yaml_test.go @@ -0,0 +1,159 @@ +package structvar_test + +import ( + "bytes" + "fmt" + "os" + "testing" + "time" + + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/structs/structvar" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v3" +) + +// ev is an expected value with the locations it is expected to have. +// val is a scalar, a []ev or a map[string]ev. +type ev struct { + val any + locs []diag.Location +} + +func nv(val any, locs []diag.Location) ev { + return ev{val: val, locs: locs} +} + +// plain converts the expected value to the plain Go value the decoder produces. +func (e ev) plain() any { + switch v := e.val.(type) { + case []ev: + out := make([]any, len(v)) + for i, x := range v { + out[i] = x.plain() + } + return out + case map[string]ev: + out := make(map[string]any, len(v)) + for k, x := range v { + out[k] = x.plain() + } + return out + default: + return v + } +} + +// assertLocations checks the locations of every value in the expected tree. +func assertLocations(t *testing.T, l *structvar.Locations, e ev, where string) { + assert.Equal(t, e.locs, l.Get(), "locations of %s", where) + switch v := e.val.(type) { + case []ev: + for i, x := range v { + assertLocations(t, l.Index(i), x, fmt.Sprintf("%s[%d]", where, i)) + } + case map[string]ev: + for k, x := range v { + assertLocations(t, l.Key(k), x, where+"."+k) + } + } +} + +func decodeFile(t *testing.T, file string) (any, *structvar.StructVar, error) { + input, err := os.ReadFile(file) + require.NoError(t, err) + var v any + sv, _, err := structvar.DecodeYAML(file, bytes.NewBuffer(input), &v) + return v, sv, err +} + +func loadExample(t *testing.T, file string) (any, *structvar.StructVar) { + v, sv, err := decodeFile(t, file) + require.NoError(t, err) + return v, sv +} + +// assertDecoded checks the decoded value and the locations of all its parts. +func assertDecoded(t *testing.T, expected ev, v any, sv *structvar.StructVar) { + assert.Equal(t, expected.plain(), v) + assertLocations(t, sv.Locations, expected, "root") +} + +// equalToReference compares the decoded value to the one decoded by yaml.v3. +// Timestamps are kept as the string they were written as, so only their presence is compared. +func equalToReference(t *testing.T, ref, v any) { + switch r := ref.(type) { + case time.Time: + assert.IsType(t, "", v) + case map[string]any: + got, ok := v.(map[string]any) + require.True(t, ok, "expected a map, got %T", v) + require.Len(t, got, len(r)) + for k, x := range r { + equalToReference(t, x, got[k]) + } + case []any: + got, ok := v.([]any) + require.True(t, ok, "expected a sequence, got %T", v) + require.Len(t, got, len(r)) + for i, x := range r { + equalToReference(t, x, got[i]) + } + default: + assert.EqualValues(t, ref, v) + } +} + +// loadYAML decodes the file and checks that the result matches yaml.v3. +func loadYAML(t *testing.T, path string) (any, *structvar.StructVar) { + input, err := os.ReadFile(path) + require.NoError(t, err) + + var ref any + err = yaml.Unmarshal(input, &ref) + require.NoError(t, err) + + v, sv := loadExample(t, path) + require.NotNil(t, sv) + + equalToReference(t, ref, v) + return v, sv +} + +// at returns the value at the path of keys (strings) and indexes (ints). +func at(t *testing.T, v any, path ...any) any { + for _, p := range path { + switch p := p.(type) { + case string: + m, ok := v.(map[string]any) + require.True(t, ok, "expected a map at %q, got %T", p, v) + v = m[p] + case int: + s, ok := v.([]any) + require.True(t, ok, "expected a sequence at %d, got %T", p, v) + v = s[p] + } + } + return v +} + +// locsAt returns the locations of the value at the path of keys (strings) and indexes (ints). +func locsAt(sv *structvar.StructVar, path ...any) []diag.Location { + l := sv.Locations + for _, p := range path { + switch p := p.(type) { + case string: + l = l.Key(p) + case int: + l = l.Index(p) + } + } + return l.Get() +} + +func TestYAMLEmpty(t *testing.T) { + v, sv := loadYAML(t, "testdata/yaml/empty.yml") + assert.Nil(t, v) + assert.Empty(t, sv.Locations.Get()) +} diff --git a/libs/structs/structyaml/structyaml.go b/libs/structs/structyaml/structyaml.go new file mode 100644 index 00000000000..9c88f6fb21c --- /dev/null +++ b/libs/structs/structyaml/structyaml.go @@ -0,0 +1,187 @@ +// Package structyaml writes typed values as YAML with a controlled key order. +package structyaml + +import ( + "cmp" + "fmt" + "os" + "path/filepath" + "slices" + "strconv" + + "github.com/databricks/cli/libs/structs/structvar" + "go.yaml.in/yaml/v3" +) + +// Pair is an entry of a [Map]. +type Pair struct { + Key string + Value any +} + +// Map is a YAML mapping that keeps the order of its keys. Values are maps (Map or +// map[string]any, keys sorted), sequences ([]any or []string), strings, bools, ints, floats or nil. +type Map []Pair + +// M returns a Map of the given alternating keys (strings) and values. +func M(kv ...any) Map { + m := make(Map, 0, len(kv)/2) + for i := 0; i < len(kv); i += 2 { + m = append(m, Pair{Key: kv[i].(string), Value: kv[i+1]}) + } + return m +} + +// Add appends an entry. +func (m *Map) Add(key string, value any) { + *m = append(*m, Pair{Key: key, Value: value}) +} + +// Order moves the given keys to the front, in the given order. The other keys keep +// their relative order. +func (m *Map) Order(keys ...string) Map { + index := func(p Pair) int { + if i := slices.Index(keys, p.Key); i >= 0 { + return i + } + return len(keys) + } + slices.SortStableFunc(*m, func(a, b Pair) int { return cmp.Compare(index(a), index(b)) }) + return *m +} + +// Struct returns the fields of the struct (or map) v that are set, except skip, as a +// Map with the keys sorted alphabetically, and so are the keys of maps and structs +// nested in maps. Sequence elements keep the field order of their type. +func Struct(v any, skip ...string) (Map, error) { + x := structvar.NewView(v, nil, nil) + if x.Kind() != structvar.KindMap { + return nil, fmt.Errorf("expected map, got %s", x.Kind()) + } + m := fromView(x, true).(Map) + return slices.DeleteFunc(m, func(p Pair) bool { return slices.Contains(skip, p.Key) }), nil +} + +// Value returns the typed value v as a value for a Map, in field order. Unset +// fields are dropped. It returns nil if v is unset. +func Value(v any) any { + return fromView(structvar.NewView(v, nil, nil), false) +} + +func fromView(x structvar.View, sorted bool) any { + switch x.Kind() { + case structvar.KindMap: + var m Map + for k, c := range x.MapItems() { + m.Add(k, fromView(c, sorted)) + } + if sorted { + slices.SortStableFunc(m, func(a, b Pair) int { return cmp.Compare(a.Key, b.Key) }) + } + return m + case structvar.KindSequence: + s := []any{} //nolint:gocritic // an empty sequence is written as [], not null + for _, c := range x.Sequence() { + s = append(s, fromView(c, false)) + } + return s + default: + return x.AsAny() + } +} + +// Node returns v as a YAML node. Values of keys in styles (and everything below +// them) get that style. +func Node(v any, styles map[string]yaml.Style) *yaml.Node { + return toNode(v, 0, styles) +} + +func toNode(v any, style yaml.Style, styles map[string]yaml.Style) *yaml.Node { + switch v := v.(type) { + case Map: + n := &yaml.Node{Kind: yaml.MappingNode, Style: style} + for _, p := range v { + s, ok := styles[p.Key] + if !ok { + s = style + } + n.Content = append(n.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: p.Key, Style: style}, toNode(p.Value, s, styles)) + } + return n + case map[string]any: + m := make(Map, 0, len(v)) + for k, e := range v { + m.Add(k, e) + } + slices.SortFunc(m, func(a, b Pair) int { return cmp.Compare(a.Key, b.Key) }) + return toNode(m, style, styles) + case []any: + n := &yaml.Node{Kind: yaml.SequenceNode, Style: style} + for _, e := range v { + n.Content = append(n.Content, toNode(e, style, styles)) + } + return n + case []string: + n := &yaml.Node{Kind: yaml.SequenceNode, Style: style} + for _, e := range v { + n.Content = append(n.Content, toNode(e, style, styles)) + } + return n + case nil: + return &yaml.Node{Kind: yaml.ScalarNode, Value: "null", Style: style} + case string: + // A string that reads as another scalar (bool, number or empty) is quoted. + if v == "" || v == "true" || v == "false" || isNumber(v) { + style = yaml.DoubleQuotedStyle + } + return &yaml.Node{Kind: yaml.ScalarNode, Value: v, Style: style} + case bool: + return &yaml.Node{Kind: yaml.ScalarNode, Value: strconv.FormatBool(v), Style: style} + case int: + return &yaml.Node{Kind: yaml.ScalarNode, Value: strconv.Itoa(v), Style: style} + case int64: + return &yaml.Node{Kind: yaml.ScalarNode, Value: strconv.FormatInt(v, 10), Style: style} + case float64: + return &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprint(v), Style: style} + default: + // Panic because we only want to deal with known types. + panic(fmt.Sprintf("invalid type: %T", v)) + } +} + +func isNumber(s string) bool { + if _, err := strconv.ParseInt(s, 0, 64); err == nil { + return true + } + _, err := strconv.ParseFloat(s, 64) + return err == nil +} + +// Save writes v as YAML to path, creating parent directories. It fails if path exists +// and force is not set. See [Node] for styles. +func Save(path string, v any, force bool, styles map[string]yaml.Style) error { + err := os.MkdirAll(filepath.Dir(path), 0o755) + if err != nil { + return err + } + + info, err := os.Stat(path) + if err == nil { + if info.IsDir() { + return fmt.Errorf("%s is a directory", path) + } + if !force { + return fmt.Errorf("%s already exists. Use --force to overwrite", path) + } + } + + file, err := os.Create(path) + if err != nil { + return err + } + defer file.Close() + + enc := yaml.NewEncoder(file) + enc.SetIndent(2) + return enc.Encode(Node(v, styles)) +} diff --git a/libs/structs/structyaml/structyaml_test.go b/libs/structs/structyaml/structyaml_test.go new file mode 100644 index 00000000000..6c62fcfcac2 --- /dev/null +++ b/libs/structs/structyaml/structyaml_test.go @@ -0,0 +1,163 @@ +package structyaml + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v3" +) + +type testStruct struct { + Name string `json:"name"` + Map map[string]string `json:"map"` + List []string `json:"list"` + LongNameField string `json:"long_name_field"` + ForceSendFields []string `json:"-"` + Format string `json:"format"` +} + +func newTestStruct() *testStruct { + return &testStruct{ + Name: "test", + Map: map[string]string{"key2": "value2", "key1": "value1"}, + List: []string{"a", "b", "c"}, + ForceSendFields: []string{"Name"}, + LongNameField: "long name goes here", + } +} + +func TestStruct(t *testing.T) { + result, err := Struct(newTestStruct(), "format") + require.NoError(t, err) + assert.Equal(t, M( + "list", []any{"a", "b", "c"}, + "long_name_field", "long name goes here", + "map", M("key1", "value1", "key2", "value2"), + "name", "test", + ), result) +} + +func TestStructWithOrder(t *testing.T) { + result, err := Struct(newTestStruct(), "format") + require.NoError(t, err) + assert.Equal(t, M( + "list", []any{"a", "b", "c"}, + "name", "test", + "map", M("key1", "value1", "key2", "value2"), + "long_name_field", "long name goes here", + ), result.Order("list", "name", "map")) +} + +func TestStructNotAMap(t *testing.T) { + _, err := Struct([]string{"a"}) + assert.ErrorContains(t, err, "expected map") +} + +func TestOrderUnknownKeysKeepOrder(t *testing.T) { + m := M("d", 1, "c", 2, "b", 3, "a", 4) + assert.Equal(t, M("a", 4, "b", 3, "d", 1, "c", 2), m.Order("a", "x", "b")) +} + +func TestValueKeepsFieldOrder(t *testing.T) { + type inner struct { + Zeta string `json:"zeta"` + Alpha string `json:"alpha"` + } + assert.Equal(t, []any{M("zeta", "z", "alpha", "a")}, Value([]inner{{Zeta: "z", Alpha: "a"}})) + assert.Nil(t, Value([]inner(nil))) +} + +func TestNodeScalars(t *testing.T) { + tests := []struct { + value any + want string + style yaml.Style + }{ + {nil, "null", 0}, + {1, "1", 0}, + {int64(291), "291", 0}, + {1.0, "1", 0}, + {true, "true", 0}, + {"value", "value", 0}, + {"0x123", "0x123", yaml.DoubleQuotedStyle}, + {"0b101", "0b101", yaml.DoubleQuotedStyle}, + {"0123", "0123", yaml.DoubleQuotedStyle}, + {"1.0", "1.0", yaml.DoubleQuotedStyle}, + {"true", "true", yaml.DoubleQuotedStyle}, + {"", "", yaml.DoubleQuotedStyle}, + } + for _, tt := range tests { + n := Node(tt.value, nil) + assert.Equal(t, yaml.ScalarNode, n.Kind) + assert.Equal(t, tt.want, n.Value) + assert.Equal(t, tt.style, n.Style, "%v", tt.value) + } +} + +func TestNodeSequence(t *testing.T) { + for _, v := range []any{[]any{"value1", "value2"}, []string{"value1", "value2"}} { + n := Node(v, nil) + assert.Equal(t, yaml.SequenceNode, n.Kind) + assert.Equal(t, "value1", n.Content[0].Value) + assert.Equal(t, "value2", n.Content[1].Value) + } +} + +func TestNodeMapKeepsOrder(t *testing.T) { + n := Node(M("key3", "value3", "key1", "value1"), nil) + assert.Equal(t, yaml.MappingNode, n.Kind) + assert.Equal(t, "key3", n.Content[0].Value) + assert.Equal(t, "value3", n.Content[1].Value) + assert.Equal(t, "key1", n.Content[2].Value) + assert.Equal(t, "value1", n.Content[3].Value) +} + +func TestNodeGoMapSortsKeys(t *testing.T) { + n := Node(map[string]any{"b": 1, "a": M("y", 1, "x", 2)}, nil) + assert.Equal(t, "a", n.Content[0].Value) + assert.Equal(t, "y", n.Content[1].Content[0].Value) + assert.Equal(t, "b", n.Content[2].Value) +} + +func TestNodeStyle(t *testing.T) { + styles := map[string]yaml.Style{"styled": yaml.DoubleQuotedStyle} + n := Node(M( + "styled", M("key1", "value1", "key2", "value2"), + "unstyled", M("key3", "value3"), + ), styles) + + styled := n.Content[1] + assert.Equal(t, yaml.MappingNode, styled.Kind) + for _, c := range styled.Content { + assert.Equal(t, yaml.DoubleQuotedStyle, c.Style) + } + + unstyled := n.Content[3] + assert.Equal(t, yaml.MappingNode, unstyled.Kind) + for _, c := range unstyled.Content { + assert.Equal(t, yaml.Style(0), c.Style) + } +} + +func TestSave(t *testing.T) { + path := filepath.Join(t.TempDir(), "dir", "out.yml") + require.NoError(t, Save(path, M("b", "1", "a", M("c", []any{1, true})), false, nil)) + + data, err := os.ReadFile(path) + require.NoError(t, err) + assert.Equal(t, "b: \"1\"\na:\n c:\n - 1\n - true\n", string(data)) + + err = Save(path, M("a", 1), false, nil) + assert.ErrorContains(t, err, "already exists. Use --force to overwrite") + + require.NoError(t, Save(path, M("a", 1), true, nil)) + data, err = os.ReadFile(path) + require.NoError(t, err) + assert.Equal(t, "a: 1\n", string(data)) + + err = Save(filepath.Dir(path), M("a", 1), true, nil) + assert.ErrorContains(t, err, "is a directory") +} diff --git a/python/Taskfile.yml b/python/Taskfile.yml index a49778c9e72..4a99223150f 100644 --- a/python/Taskfile.yml +++ b/python/Taskfile.yml @@ -22,7 +22,7 @@ tasks: desc: Run pydabs tests sources: - "**/*.py" - - ../libs/dyn/dynvar/testdata/reference_vectors.json + - ../libs/structs/structvar/testdata/reference_vectors.json - pyproject.toml - uv.lock - README.md diff --git a/python/codegen/codegen/packages.py b/python/codegen/codegen/packages.py index 46674518dca..69d4944fa9a 100644 --- a/python/codegen/codegen/packages.py +++ b/python/codegen/codegen/packages.py @@ -63,7 +63,7 @@ def _load_resource_namespace() -> dict[str, str]: # pass through untyped. The Go config mutators validate the value. "interface": "Any", # time.Time is a scalar serialized as an RFC3339 string; the Go side models - # it as a string too (see libs/dyn/convert/sdk_native_types.go). + # it as a string too (see libs/structs/structvar/struct_info.go). "time.Time": "str", # duration.Duration is a scalar serialized as a seconds string (e.g. "3600s"). "duration.Duration": "str", diff --git a/python/databricks/bundles/core/_transform.py b/python/databricks/bundles/core/_transform.py index 8e3b4a6ec12..c39b3555869 100644 --- a/python/databricks/bundles/core/_transform.py +++ b/python/databricks/bundles/core/_transform.py @@ -19,7 +19,7 @@ _T = TypeVar("_T") -# Same strings as normalizeBool in libs/dyn/convert/normalize.go. +# Same strings as the bool decoding in libs/structs/structvar/decode.go. # See https://yaml.org/type/bool.html. _TRUE_STRINGS = [ "true", @@ -311,8 +311,8 @@ def _unwrap_variable(tpe: type) -> Optional[type]: # Regex for string corresponding to variables. # -# The source of truth is regex in libs/dyn/dynvar/ref.go. -# Behavioral parity is enforced by libs/dyn/dynvar/testdata/reference_vectors.json. +# The source of truth is regex in libs/structs/structvar/ref.go. +# Behavioral parity is enforced by libs/structs/structvar/testdata/reference_vectors.json. # # The (?