mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat: add Azure Policy AST types (#653)
Add span-annotated AST types for Azure Policy conditions and rules. - PolicyDefinition, PolicyRule with if/then/details structure - Condition enum: field conditions, value conditions, logical combinators (allOf, anyOf, not), and count expressions - Operator enums for all 19 Azure Policy constraint operators (equals, contains, greater, matchInsensitively, etc.) - Expr enum for field references, literal values (number, string, bool), template function calls, and policy function invocations - Value types with span tracking for error reporting
This commit is contained in:
committed by
GitHub
parent
126cc12eb5
commit
648ba40126
331
src/languages/azure_policy/ast/mod.rs
Normal file
331
src/languages/azure_policy/ast/mod.rs
Normal file
@@ -0,0 +1,331 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Span-annotated AST types for Azure Policy rule conditions.
|
||||
//!
|
||||
//! AST nodes carry [`Span`] information pointing back into the original JSON
|
||||
//! source, enabling precise error messages during compilation and validation.
|
||||
//!
|
||||
//! The type hierarchy mirrors the Azure Policy JSON structure:
|
||||
//! - [`PolicyDefinition`] — full policy definition wrapper
|
||||
//! - [`PolicyRule`] — top-level `{ "if": constraint, "then": { "effect": ... } }`
|
||||
//! - [`Constraint`] — logical combinators (`allOf`, `anyOf`, `not`) or leaf [`Condition`]
|
||||
//! - [`Condition`] — `{ lhs, operator, rhs }` triple
|
||||
//! - [`FieldNode`] / [`FieldKind`] — field reference classification
|
||||
//! - [`Expr`] — ARM template expression (`"[concat(...)]"`)
|
||||
//! - [`CountNode`] — `count` with optional `where` clause
|
||||
|
||||
mod value;
|
||||
|
||||
pub use value::*;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pub use crate::lexer::Span;
|
||||
|
||||
// ============================================================================
|
||||
// Top-level
|
||||
// ============================================================================
|
||||
|
||||
/// A parsed Azure Policy rule.
|
||||
///
|
||||
/// Represents the `policyRule` object:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "if": { /* constraint */ },
|
||||
/// "then": { "effect": "deny" }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PolicyRule {
|
||||
/// Span covering the entire `policyRule` JSON object.
|
||||
pub span: Span,
|
||||
/// The `"if"` condition.
|
||||
pub condition: Constraint,
|
||||
/// The `"then"` block containing the effect.
|
||||
pub then_block: ThenBlock,
|
||||
}
|
||||
|
||||
/// The `"then"` block of a policy rule.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ThenBlock {
|
||||
/// Span covering the `"then"` JSON object.
|
||||
pub span: Span,
|
||||
/// The effect (e.g., "deny", "audit", "modify").
|
||||
pub effect: EffectNode,
|
||||
/// Optional details block (for modify/append/deployIfNotExists effects).
|
||||
pub details: Option<JsonValue>,
|
||||
/// Parsed `existenceCondition` from `details` (for auditIfNotExists /
|
||||
/// deployIfNotExists). This is extracted from the `details` JSON and
|
||||
/// parsed as a `Constraint` (same grammar as `policyRule.if`).
|
||||
pub existence_condition: Option<Constraint>,
|
||||
}
|
||||
|
||||
/// The `"effect"` value in the then block.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EffectNode {
|
||||
/// Span of the effect value string.
|
||||
pub span: Span,
|
||||
/// The effect kind.
|
||||
pub kind: EffectKind,
|
||||
/// The original effect text as written (preserves casing).
|
||||
pub raw: String,
|
||||
}
|
||||
|
||||
/// Known Azure Policy effect types.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum EffectKind {
|
||||
Deny,
|
||||
Audit,
|
||||
Append,
|
||||
AuditIfNotExists,
|
||||
DeployIfNotExists,
|
||||
Disabled,
|
||||
Modify,
|
||||
DenyAction,
|
||||
Manual,
|
||||
/// An effect value that wasn't recognized (may be a parameterized expression).
|
||||
/// Use [`EffectNode::raw`] to get the original text.
|
||||
Other,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Constraints
|
||||
// ============================================================================
|
||||
|
||||
/// A logical or leaf constraint node.
|
||||
///
|
||||
/// Maps directly to the Azure Policy JSON structure:
|
||||
/// - `{ "allOf": [...] }` → `AllOf`
|
||||
/// - `{ "anyOf": [...] }` → `AnyOf`
|
||||
/// - `{ "not": {...} }` → `Not`
|
||||
/// - `{ "field": "...", "equals": "..." }` → `Condition`
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Constraint {
|
||||
AllOf {
|
||||
/// Span covering the entire JSON object `{ "allOf": [...] }`.
|
||||
span: Span,
|
||||
/// The child constraints.
|
||||
constraints: Vec<Constraint>,
|
||||
},
|
||||
AnyOf {
|
||||
/// Span covering the entire JSON object `{ "anyOf": [...] }`.
|
||||
span: Span,
|
||||
/// The child constraints.
|
||||
constraints: Vec<Constraint>,
|
||||
},
|
||||
Not {
|
||||
/// Span covering the entire JSON object `{ "not": {...} }`.
|
||||
span: Span,
|
||||
/// The negated constraint.
|
||||
constraint: Box<Constraint>,
|
||||
},
|
||||
/// A leaf condition (field/value/count + operator + rhs).
|
||||
Condition(Box<Condition>),
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Conditions
|
||||
// ============================================================================
|
||||
|
||||
/// A leaf condition: `{ lhs, operator, rhs }`.
|
||||
///
|
||||
/// Example: `{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" }`
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Condition {
|
||||
/// Span covering the entire condition JSON object.
|
||||
pub span: Span,
|
||||
/// The left-hand operand (field, value, or count).
|
||||
pub lhs: Lhs,
|
||||
/// The operator (equals, contains, etc.) with its span.
|
||||
pub operator: OperatorNode,
|
||||
/// The right-hand value or expression.
|
||||
pub rhs: ValueOrExpr,
|
||||
}
|
||||
|
||||
/// The left-hand side of a condition.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Lhs {
|
||||
/// `"field": "..."` — a resource field reference.
|
||||
Field(FieldNode),
|
||||
/// `"value": ...` — a literal value or expression.
|
||||
Value {
|
||||
/// Span of the `"value"` key.
|
||||
key_span: Span,
|
||||
/// The value or expression.
|
||||
value: ValueOrExpr,
|
||||
},
|
||||
/// `"count": { ... }` — a count expression.
|
||||
Count(CountNode),
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Fields
|
||||
// ============================================================================
|
||||
|
||||
/// A field reference with its source span.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FieldNode {
|
||||
/// Span of the field string value in the JSON.
|
||||
pub span: Span,
|
||||
/// The classified field kind.
|
||||
pub kind: FieldKind,
|
||||
}
|
||||
|
||||
/// Classification of a `"field"` string value.
|
||||
///
|
||||
/// Built-in fields are mapped to specific variants; everything else is either
|
||||
/// an alias or an ARM template expression.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FieldKind {
|
||||
/// `"type"`
|
||||
Type,
|
||||
/// `"id"`
|
||||
Id,
|
||||
/// `"kind"`
|
||||
Kind,
|
||||
/// `"name"`
|
||||
Name,
|
||||
/// `"location"`
|
||||
Location,
|
||||
/// `"fullName"`
|
||||
FullName,
|
||||
/// `"tags"` (the entire tags object)
|
||||
Tags,
|
||||
/// `"identity.type"`
|
||||
IdentityType,
|
||||
/// `"identity.<subpath>"` — any identity sub-field other than `type`
|
||||
/// (e.g., `"identity.userAssignedIdentities"`, `"identity.principalId"`).
|
||||
IdentityField(String),
|
||||
/// `"apiVersion"`
|
||||
ApiVersion,
|
||||
/// `"tags.tagName"` or `"tags['tagName']"`
|
||||
Tag(String),
|
||||
/// An alias string (e.g., `"Microsoft.Compute/virtualMachines/imagePublisher"`)
|
||||
Alias(String),
|
||||
/// An ARM template expression (e.g., `"[concat('Microsoft.Network/', ...)]"`)
|
||||
Expr(Expr),
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Operators
|
||||
// ============================================================================
|
||||
|
||||
/// An operator node with span information.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OperatorNode {
|
||||
/// Span of the operator key string in the JSON (e.g., the `"equals"` key).
|
||||
pub span: Span,
|
||||
/// The operator kind.
|
||||
pub kind: OperatorKind,
|
||||
}
|
||||
|
||||
/// The 19 Azure Policy condition operators.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum OperatorKind {
|
||||
Contains,
|
||||
ContainsKey,
|
||||
Equals,
|
||||
Greater,
|
||||
GreaterOrEquals,
|
||||
Exists,
|
||||
In,
|
||||
Less,
|
||||
LessOrEquals,
|
||||
Like,
|
||||
Match,
|
||||
MatchInsensitively,
|
||||
NotContains,
|
||||
NotContainsKey,
|
||||
NotEquals,
|
||||
NotIn,
|
||||
NotLike,
|
||||
NotMatch,
|
||||
NotMatchInsensitively,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Policy Definition (full envelope)
|
||||
// ============================================================================
|
||||
|
||||
/// A fully parsed Azure Policy definition.
|
||||
///
|
||||
/// Wraps the `properties` section of a policy definition JSON:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "properties": {
|
||||
/// "displayName": "...",
|
||||
/// "description": "...",
|
||||
/// "mode": "All",
|
||||
/// "parameters": { ... },
|
||||
/// "policyRule": { "if": ..., "then": ... }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Fields that we don't parse into typed members are stored in `extra`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PolicyDefinition {
|
||||
/// Span covering the entire definition JSON object.
|
||||
pub span: Span,
|
||||
|
||||
/// Optional `displayName`.
|
||||
pub display_name: Option<String>,
|
||||
|
||||
/// Optional `description`.
|
||||
pub description: Option<String>,
|
||||
|
||||
/// Optional `mode` (e.g., `"All"`, `"Indexed"`, `"Microsoft.KeyVault.Data"`).
|
||||
pub mode: Option<String>,
|
||||
|
||||
/// Optional `metadata` (kept as raw JSON).
|
||||
pub metadata: Option<JsonValue>,
|
||||
|
||||
/// Parameter definitions as an ordered list; lookups should match `ParameterDefinition::name`.
|
||||
pub parameters: Vec<ParameterDefinition>,
|
||||
|
||||
/// The parsed `policyRule`.
|
||||
pub policy_rule: PolicyRule,
|
||||
|
||||
/// Any other top-level fields not handled above (e.g., `id`, `name`, `type`, `policyType`).
|
||||
pub extra: Vec<ObjectEntry>,
|
||||
}
|
||||
|
||||
/// A single parameter definition within `properties.parameters`.
|
||||
///
|
||||
/// ```json
|
||||
/// "paramName": {
|
||||
/// "type": "String",
|
||||
/// "defaultValue": "...",
|
||||
/// "allowedValues": [...],
|
||||
/// "metadata": { "displayName": "...", "description": "..." }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParameterDefinition {
|
||||
/// Span covering this parameter's JSON object.
|
||||
pub span: Span,
|
||||
|
||||
/// The parameter name (the key in the `parameters` object).
|
||||
pub name: String,
|
||||
|
||||
/// Span of the parameter name key.
|
||||
pub name_span: Span,
|
||||
|
||||
/// The `type` field (e.g., `"String"`, `"Integer"`, `"Boolean"`, `"Array"`, `"Object"`).
|
||||
pub param_type: Option<String>,
|
||||
|
||||
/// Optional default value.
|
||||
pub default_value: Option<JsonValue>,
|
||||
|
||||
/// Optional list of allowed values.
|
||||
pub allowed_values: Option<Vec<JsonValue>>,
|
||||
|
||||
/// Optional metadata (kept as raw JSON).
|
||||
pub metadata: Option<JsonValue>,
|
||||
|
||||
/// Any extra fields not handled above.
|
||||
pub extra: Vec<ObjectEntry>,
|
||||
}
|
||||
189
src/languages/azure_policy/ast/value.rs
Normal file
189
src/languages/azure_policy/ast/value.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! JSON value types, ARM template expressions, and count nodes.
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::lexer::Span;
|
||||
|
||||
use super::{Constraint, FieldNode};
|
||||
|
||||
// ============================================================================
|
||||
// Values and Expressions
|
||||
// ============================================================================
|
||||
|
||||
/// A value that may be a literal JSON value or an ARM template expression.
|
||||
///
|
||||
/// Strings of the form `"[expr]"` are parsed as expressions; everything else
|
||||
/// remains a plain JSON value.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ValueOrExpr {
|
||||
/// A plain JSON value.
|
||||
Value(JsonValue),
|
||||
/// An ARM template expression parsed from a `"[...]"` string.
|
||||
Expr {
|
||||
/// Span of the entire string (including the `[` and `]`).
|
||||
span: Span,
|
||||
/// The raw string content (between the quotes, including `[` and `]`).
|
||||
raw: String,
|
||||
/// The parsed expression AST.
|
||||
expr: Expr,
|
||||
},
|
||||
}
|
||||
|
||||
/// A JSON value with span information.
|
||||
///
|
||||
/// This is our own representation (not `serde_json::Value`) because we need
|
||||
/// exact source spans for every token.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum JsonValue {
|
||||
/// JSON `null`.
|
||||
Null(Span),
|
||||
/// JSON boolean.
|
||||
Bool(Span, bool),
|
||||
/// JSON number (stored as raw string for lossless round-trip).
|
||||
Number(Span, String),
|
||||
/// JSON string.
|
||||
Str(Span, String),
|
||||
/// JSON array.
|
||||
Array(Span, Vec<JsonValue>),
|
||||
/// JSON object.
|
||||
Object(Span, Vec<ObjectEntry>),
|
||||
}
|
||||
|
||||
/// A key-value entry in a JSON object.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ObjectEntry {
|
||||
/// Span of the key string.
|
||||
pub key_span: Span,
|
||||
/// The key text.
|
||||
pub key: String,
|
||||
/// The value.
|
||||
pub value: JsonValue,
|
||||
}
|
||||
|
||||
impl JsonValue {
|
||||
/// Returns the span of this JSON value.
|
||||
pub const fn span(&self) -> &Span {
|
||||
match *self {
|
||||
Self::Null(ref s)
|
||||
| Self::Bool(ref s, _)
|
||||
| Self::Number(ref s, _)
|
||||
| Self::Str(ref s, _)
|
||||
| Self::Array(ref s, _)
|
||||
| Self::Object(ref s, _) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ARM Template Expressions
|
||||
// ============================================================================
|
||||
|
||||
/// An ARM template expression AST node.
|
||||
///
|
||||
/// Represents expressions inside `"[...]"` strings, following the grammar:
|
||||
/// ```text
|
||||
/// expr ::= IDENT
|
||||
/// | NUMBER
|
||||
/// | STRING
|
||||
/// | BOOL
|
||||
/// | expr '.' IDENT
|
||||
/// | expr '(' args ')'
|
||||
/// | expr '[' expr ']'
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Expr {
|
||||
/// A literal value (number, string, or bool).
|
||||
Literal { span: Span, value: ExprLiteral },
|
||||
/// An identifier (e.g., function name or parameter reference).
|
||||
Ident { span: Span, name: String },
|
||||
/// A function call: `func(arg1, arg2, ...)`.
|
||||
Call {
|
||||
span: Span,
|
||||
func: Box<Expr>,
|
||||
args: Vec<Expr>,
|
||||
},
|
||||
/// A dot access: `expr.field`.
|
||||
Dot {
|
||||
span: Span,
|
||||
object: Box<Expr>,
|
||||
field_span: Span,
|
||||
field: String,
|
||||
},
|
||||
/// An index access: `expr[index]`.
|
||||
Index {
|
||||
span: Span,
|
||||
object: Box<Expr>,
|
||||
index: Box<Expr>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
/// Returns a reference to this expression's span.
|
||||
pub const fn span(&self) -> &Span {
|
||||
match *self {
|
||||
Self::Literal { ref span, .. }
|
||||
| Self::Ident { ref span, .. }
|
||||
| Self::Call { ref span, .. }
|
||||
| Self::Dot { ref span, .. }
|
||||
| Self::Index { ref span, .. } => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A literal value inside an ARM template expression.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ExprLiteral {
|
||||
/// Numeric value (raw string).
|
||||
Number(String),
|
||||
/// String value.
|
||||
String(String),
|
||||
/// Boolean value (from string `"true"`/`"false"` in expression context).
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Count
|
||||
// ============================================================================
|
||||
|
||||
/// A `"count"` expression node.
|
||||
///
|
||||
/// Two forms:
|
||||
/// - **Field count**: `{ "count": { "field": "alias[*]", "where": {...} } }`
|
||||
/// - **Value count**: `{ "count": { "value": [...], "name": "x", "where": {...} } }`
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CountNode {
|
||||
/// Count over an alias array field.
|
||||
Field {
|
||||
/// Span covering the `"count"` value object.
|
||||
span: Span,
|
||||
/// The field being counted (must be an alias or template expression).
|
||||
field: FieldNode,
|
||||
/// Optional filter constraint.
|
||||
where_: Option<Box<Constraint>>,
|
||||
},
|
||||
/// Count over an inline value.
|
||||
Value {
|
||||
/// Span covering the `"count"` value object.
|
||||
span: Span,
|
||||
/// The JSON value or expression being counted (typically an array).
|
||||
value: ValueOrExpr,
|
||||
/// Optional iteration variable name.
|
||||
name: Option<NameNode>,
|
||||
/// Optional filter constraint.
|
||||
where_: Option<Box<Constraint>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// A `"name"` entry in a count-value block.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NameNode {
|
||||
/// Span of the name string value.
|
||||
pub span: Span,
|
||||
/// The name text.
|
||||
pub name: String,
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Azure Policy language support.
|
||||
//! Azure Policy language support: AST types, aliases, and string utilities.
|
||||
|
||||
#[allow(clippy::pattern_type_mismatch)]
|
||||
pub mod aliases;
|
||||
pub mod ast;
|
||||
pub mod strings;
|
||||
|
||||
Reference in New Issue
Block a user