Files
regorus/docs/azure-policy/azurepolicy.ebnf
Anand Krishnamoorthi 8f740e2f6f feat(azure-policy): add policy rule and policy definition parsers (#660)
Extend the Azure Policy parser to handle complete policyRule and
policyDefinition JSON structures, not just standalone constraints.

Policy rule parser (policy_rule.rs):
- Parse top-level { "if": ..., "then": ... } objects
- Extract effect kind (deny, audit, append, modify, etc.) into typed AST
- Parse "details" structurally when it is an object to pull out
  existenceCondition as a first-class Constraint; fall back to opaque
  JSON for non-object details (e.g. append array form)
- Detect duplicate/missing keys for "if", "then", "effect", "details"

Policy definition parser (policy_definition.rs):
- Handle both wrapped ARM envelope ({ "properties": { ... } }) and
  unwrapped (properties-level keys at top level) forms
- Type-extract displayName, description, mode, metadata, parameters,
  and policyRule; everything else goes into extra
- Parse parameter definitions with type, defaultValue, allowedValues,
  and metadata; detect duplicate parameter names
- Duplicate key detection throughout

Grammar documentation (docs/azure-policy/azurepolicy.ebnf):
- Add formal EBNF grammar covering policy-rule, then-block,
  constraints, conditions, all 19 operators, count expressions,
  JSON values, and ARM template expressions

Test harness changes:
- Add parse_level field to YAML test cases: "constraint" (default),
  "policy_rule", or "policy_definition"
- Un-skip three parse_errors cases that needed policy_rule-level parsing
- Add policy_rule.yaml with 12 cases covering all 9 effect kinds,
  existenceCondition, parameterized effects, complex conditions, and
  extra key handling
- Add policy_definition.yaml with wrapped, unwrapped, parameterized,
  missing-policyRule, and duplicate-key error cases
2026-04-06 11:36:24 -05:00

77 lines
3.2 KiB
EBNF

(* Azure Policy grammar.
*
* All key matching is case-insensitive. JSON object keys are unordered,
* so the ordering shown below is for readability only.
*)
(* ================================================================
* Policy rule & then block
* NOTE: Keys may appear in any order; extra keys may appear between
* the recognized ones. The ordering below is illustrative.
* ================================================================ *)
policy-rule ::= '{' '"if"' ':' constraint ',' '"then"' ':' then-block
(',' STRING ':' json-value)* '}'
then-block ::= '{' '"effect"' ':' STRING
(',' '"details"' ':' json-value)? '}'
(* ================================================================
* Constraints
* ================================================================ *)
constraint ::= allOf | anyOf | not | condition
allOf ::= '{' '"allOf"' ':' '[' (constraint (',' constraint)*)? ']' '}'
anyOf ::= '{' '"anyOf"' ':' '[' (constraint (',' constraint)*)? ']' '}'
not ::= '{' '"not"' ':' constraint '}'
(* Keys within a condition are unordered; exactly one lhs-entry and one
* op-entry are required. *)
condition ::= '{' lhs-entry ',' op-entry '}'
lhs-entry ::= field | value-lhs | count
field ::= '"field"' ':' string-value
value-lhs ::= '"value"' ':' json-value
op-entry ::= operator ':' json-value
operator ::= '"contains"' | '"containsKey"' | '"equals"' | '"notEquals"'
| '"greater"' | '"greaterOrEquals"' | '"less"' | '"lessOrEquals"'
| '"exists"' | '"in"' | '"notIn"'
| '"like"' | '"notLike"'
| '"match"' | '"matchInsensitively"'
| '"notMatch"' | '"notMatchInsensitively"'
| '"notContains"' | '"notContainsKey"'
(* ================================================================
* Count expressions
* ================================================================ *)
count ::= '"count"' ':' count-inner
count-inner ::= count-field | count-value
count-field ::= '{' field (',' where)? '}'
count-value ::= '{' value-lhs (',' '"name"' ':' STRING)? (',' where)? '}'
where ::= '"where"' ':' constraint
(* ================================================================
* JSON values & template expressions
* ================================================================ *)
string-value ::= STRING | '"[' string-expr ']"'
json-value ::= STRING | NUMBER | BOOL | NULL
| array | object
| '"[' string-expr ']"'
array ::= '[' (json-value (',' json-value)*)? ']'
object ::= '{' (STRING ':' json-value (',' STRING ':' json-value)*)? '}'
(* ================================================================
* ARM template expression sub-grammar
* ================================================================ *)
string-expr ::= NUMBER | STRING | '-' string-expr | complex-expr
complex-expr ::= IDENT
| complex-expr '.' IDENT
| complex-expr '(' (string-expr (',' string-expr)*)? ')'
| complex-expr '[' string-expr ']'