feat(azure-policy): implement condition, expression, field, and template dispatch compilation (#686)

Fill in the compiler stubs for the evaluation layer.

Condition and wildcard compilation:
- Compile allOf/anyOf/not constraints, operator conditions with
  value-condition guards, and implicit allOf for unbound [*] fields
  via recursive Every loops.
- Defensively lowercase prefix/suffix path segments in wildcard
  handling for consistency with the collect path.

Expression and field compilation:
- Parse ARM template expressions and dispatch calls to parameters,
  field, current, resourceGroup, subscription, and others.
- Compile all FieldKind variants (type, id, name, location, tags,
  aliases, dynamic if/concat), resolve resource paths, and collect
  wildcard values via ForEach loops.

Template function dispatch:
- Wire up 50+ ARM template functions covering string, numeric,
  encoding, collection, date/time, logical, and comparison categories.

Compiler infrastructure (core.rs):
- Add emit helpers: load_literal, emit_builtin_call,
  emit_chained_index_literal_path, load_input, load_context,
  emit_coalesce_undefined_to_null, add_literal_u16, and
  get_or_add_builtin_index.
- Add alias resolution via resolve_alias_path and strip_fq_prefix.

Misc cleanup:
- Handle ARM template `[[` escape sequences in json_value_to_runtime
  and add a test for it.
- Tighten module visibility (pub -> pub(crate)/pub(super)) where
  appropriate.
- Add span context to bail errors in stubs so diagnostics carry
  source locations.
- Take CountBinding by reference in compile_from_binding.
- Suppress clippy warnings on the no-op memory_check stub.
This commit is contained in:
Anand Krishnamoorthi
2026-04-21 16:51:08 -05:00
committed by GitHub
parent f727096a1d
commit f50a9744ff
12 changed files with 1600 additions and 86 deletions
+23 -6
View File
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(dead_code, clippy::pattern_type_mismatch, clippy::redundant_pub_crate)]
#![allow(dead_code, clippy::pattern_type_mismatch)]
//! Free helper functions used by the Azure Policy compiler.
@@ -27,17 +27,22 @@ pub(super) fn extract_string_literal(expr: &Expr) -> Result<String> {
pub(super) fn split_count_wildcard_path(path: &str) -> Result<(String, Option<String>)> {
let wildcard_index = path
.find("[*]")
.ok_or_else(|| anyhow!("count.field must contain [*]: {}", path))?;
.ok_or_else(|| anyhow!("wildcard path must contain [*]: {}", path))?;
let (prefix_str, rest) = path.split_at(wildcard_index);
let prefix = prefix_str.trim_end_matches('.');
if prefix.is_empty() {
bail!(
"count.field must have a non-empty prefix before [*]: {}",
"wildcard path must have a non-empty prefix before [*]: {}",
path
);
}
let after_wildcard = &rest[3..];
let after_wildcard = rest.strip_prefix("[*]").ok_or_else(|| {
anyhow!(
"wildcard path could not be parsed after [*] split: {}",
path
)
})?;
if after_wildcard.contains("[*]") {
bail!("nested [*] wildcards are not supported: {}", path);
}
@@ -145,14 +150,20 @@ pub(super) fn split_path_without_wildcards(path: &str) -> Result<Vec<String>> {
}
/// Convert a parsed JSON value from the Azure Policy AST into a runtime [`Value`].
pub(crate) fn json_value_to_runtime(value: &JsonValue) -> Result<Value> {
pub(super) fn json_value_to_runtime(value: &JsonValue) -> Result<Value> {
match value {
JsonValue::Null(_) => Ok(Value::Null),
JsonValue::Bool(_, b) => Ok(Value::Bool(*b)),
JsonValue::Number(_, raw) => {
Value::from_numeric_string(raw).map_err(|_| anyhow!("invalid number literal: {}", raw))
}
JsonValue::Str(_, s) => Ok(Value::from(s.clone())),
JsonValue::Str(_, s) => {
// Handle ARM template escape: `[[...` → `[...`
s.strip_prefix("[[").map_or_else(
|| Ok(Value::from(s.clone())),
|unescaped| Ok(Value::from(alloc::format!("[{unescaped}"))),
)
}
JsonValue::Array(_, items) => {
let mut out = Vec::with_capacity(items.len());
for item in items {
@@ -269,6 +280,12 @@ mod tests {
assert_eq!(v, Value::from("hello".to_string()));
}
#[test]
fn json_string_double_bracket_escape() {
let v = json_value_to_runtime(&JsonValue::Str(dummy_span(), "[[escaped]".into())).unwrap();
assert_eq!(v, Value::from("[escaped]".to_string()));
}
#[test]
fn json_array() {
let arr = JsonValue::Array(