Skip to content

fix(azure_policy): quote-aware [*] detection in field paths - #875

Merged
Anand Krishnamoorthi (anakrish) merged 1 commit into
microsoft:mainfrom
snehabandla:split/azure-policy-field-fn-quoted-wildcard
Oct 29, 2026
Merged

Anand Krishnamoorthi (anakrish) merged 1 commit into
microsoft:mainfrom
snehabandla:split/azure-policy-field-fn-quoted-wildcard

Conversation

@snehabandla

Copy link
Copy Markdown
Contributor

Summary

Quote-aware [*] detection in Azure Policy field paths.

The compiler used plain substring checks (contains("[*]"), find("[*]"), split("[*]")) to look for [*] in field paths. So a quoted key containing [*], such as m['a[*]'] or items[*]['a[*]'], was treated as an array wildcard. Depending on where it appeared, field conditions, field() and count paths then iterated the wrong thing, split at the wrong place, or failed to compile with "unclosed bracket".

What changed:

  • One shared finder. find_unquoted_wildcard / has_unquoted_wildcard in compiler/utils.rs are a single linear scan. They are used at every [*] search on a field path in the compiler:

    • field conditions and field();
    • count bindings, nested-wildcard collection and binding_selects_single_value;
    • the alias array-path fallback and metadata.

    split_path_without_wildcards is quote-aware too.

  • Quoting rule (documented and pinned by tests). A bracket whose first non-whitespace character is ' or " is a quoted key. The key runs to the matching quote followed by ], and there is no escaping. So p['a][*].x[b'] is one quoted key and contains no wildcard. If the quote never closes, the bracket is read as unquoted text, which is main's behaviour.

  • strip_fq_prefix. Only a / before the first [ now ends the resource-type prefix, so aliases such as labels['a/b'] resolve. Before, rfind('/') landed inside the quoted key.

Behaviour changes

  1. A count field must end with an unquoted [*] (trailing whitespace is allowed), and it must not contain an unclosed quote.
    • items[*].x and items[*]['a[*]'] are now compile errors. On main they compiled and silently counted items.
    • This matches the Azure docs: count.field "must be an array alias".
  2. Counting over a tag field is a compile error ("count over a tag field is not supported").
    • On main, tag counts already failed with "must contain [*]", except when the tag name contained [*], as in tags['a[*]'] or tags.a[*]. In that case the quotes were lost and the tag name became a real wildcard over tags.a.
    • tags['x'][*] is an ordinary path and still counts.

Tests

  • New YAML suites: tests/azure_policy/cases/quoted_wildcard.yaml and aliases_quoted_keys.yaml (with aliases/quoted_keys_aliases.json). Every match case has a non-match twin. They cover:
    • quoted [*] after a real wildcard;
    • nested counts, including greater: 0;
    • field() under a count binding;
    • aliases with quoted keys;
    • ] and / inside quotes;
    • literal keys named a[*];
    • mixed, spaced and uppercase quoting;
    • unbalanced quotes;
    • the rejected count and tag shapes, each with a valid twin.
  • Unit tests cover the finder (including linear-time inputs), ensure_count_field_path, and the raw-fallback strip_fq_prefix site.
  • Pre-fix evidence: each new test fails on the pre-fix source, or kills a targeted mutant when it is an acceptance guard.

Deferred findings

  • Alias normalizer: it splits defaultPath on . and inserts literal keys, so a renamed alias with any bracketed key (renamed['ab'], not only quoted [*]) evaluates to Undefined. This is pre-existing and is tracked as follow-up item (bracket-aware alias normalizer/denormalizer).
  • [ * ] with spaces: this PR keeps main's reading, where it is a key, not a wildcard. An exact [*], a linear bracket parse, and nested-count prefix joining with a bracket collection are handled by the count-bracket-suffix work, and will be reconciled when the two are rebased together.
  • Parser tag check: whitespace-sensitive, naive [*] check in classify_field. It overlaps the tag-whitespace work and will be handled there or in the dynamic-tag-fields follow-up.
  • Runtime tokenize_path (dynamic concat() field paths) is not quote-aware. Deferred to the dynamic-tag-fields follow-up.

Dependencies: none; this is based on main.

Field paths were searched for `[*]` with plain substring checks, so a
quoted key that contains `[*]` (`m['a[*]']`, `items[*]['a[*]']`) was
treated as an array wildcard: field conditions, field() and count paths
iterated or split at the wrong place, or failed to compile.

Add one shared, linear, quote-aware finder (`find_unquoted_wildcard`,
`has_unquoted_wildcard` in compiler/utils.rs) and use it at every
field-path `[*]` search in the compiler (fields, field(), count
bindings, nested-wildcard collection, binding_selects_single_value,
alias array-path fallback, metadata).  Inside `['...']` / `["..."]` the
key runs to the matching quote followed by `]`, with no escaping; so
`p['a][*].x[b']` is one quoted key.  `split_path_without_wildcards` is
now quote-aware too.

Count fields must be array aliases: a count field must end with an
unquoted `[*]` and must not contain an unclosed quote.  Fields such as
`items[*].x` or `items[*]['a[*]']` used to be accepted and silently
counted `items`; they are now compile errors.  Counting over a tag field
is also rejected (a tag named `a[*]` used to become a real wildcard).

strip_fq_prefix only treats a `/` before the first `[` as the end of the
resource-type prefix, so aliases like `labels['a/b']` resolve.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues block the quoted-key wildcard handling changes.

No open issues 🎉

What changed in this PR

This PR fixes Azure Policy field-path compilation so quoted [*] text is treated as a literal key rather than an array wildcard.

Changes:

  • Shares quote-aware wildcard detection across field conditions, field(), counts, alias resolution, and metadata.
  • Validates count paths and preserves slashes inside bracketed keys when stripping resource-type prefixes.
  • Adds unit tests and paired match/non-match regression cases for quoted keys, aliases, and rejected count shapes.
File Description
tests/​azure_policy/​cases/​quoted_wildcard.yaml Covers quoted-key evaluation, count bindings, and invalid count paths.
tests/​azure_policy/​cases/​aliases_quoted_keys.yaml Covers alias resolution with quoted wildcard, bracket, and slash characters.
tests/​azure_policy/​aliases/​quoted_keys_aliases.json Supplies alias fixtures for quoted-key tests.
src/​languages/​azure_policy/​compiler/​utils.rs Adds shared quote-aware scanning, path validation, and unit tests.
src/​languages/​azure_policy/​compiler/​metadata.rs Flags only unquoted alias wildcards.
src/​languages/​azure_policy/​compiler/​fields.rs Uses quote-aware detection for field-value collection.
src/​languages/​azure_policy/​compiler/​expressions.rs Corrects field() result wrapping under count bindings.
src/​languages/​azure_policy/​compiler/​count.rs Updates wildcard handling and rejects unsupported count paths and tag counts.
src/​languages/​azure_policy/​compiler/​core.rs Updates alias fallback detection and preserves bracketed slashes.
src/​languages/​azure_policy/​compiler/​conditions_wildcard.rs Uses quote-aware detection for wildcard field conditions.

Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants