mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
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
This commit is contained in:
committed by
GitHub
parent
687be2850b
commit
8f740e2f6f
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Recursive-descent JSON parser for Azure Policy rule constraints.
|
||||
//! Custom recursive-descent JSON parser for Azure Policy rules.
|
||||
//!
|
||||
//! Parses Azure Policy JSON directly from [`Lexer`] tokens, building span-annotated
|
||||
//! AST nodes in a single pass. No intermediate `serde_json::Value` is created.
|
||||
@@ -9,19 +9,34 @@
|
||||
//! The parser is policy-aware: when parsing JSON objects, it dispatches on key names
|
||||
//! (`allOf`, `anyOf`, `not`, `field`, `value`, `count`, operator names) to build
|
||||
//! the appropriate AST nodes.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use regorus::Source;
|
||||
//! use regorus::languages::azure_policy::parser;
|
||||
//!
|
||||
//! let json = r#"{ "if": { "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
|
||||
//! "then": { "effect": "deny" } }"#;
|
||||
//! let source = Source::from_contents("policy.json".into(), json.into())?;
|
||||
//! let rule = parser::parse_policy_rule(&source)?;
|
||||
//! ```
|
||||
|
||||
mod constraint;
|
||||
mod core;
|
||||
mod error;
|
||||
mod policy_definition;
|
||||
mod policy_rule;
|
||||
|
||||
pub(super) use self::core::json_unescape;
|
||||
|
||||
pub use error::ParseError;
|
||||
|
||||
use alloc::string::ToString as _;
|
||||
|
||||
use crate::lexer::{Source, TokenKind};
|
||||
|
||||
use super::ast::{Constraint, FieldKind, OperatorKind};
|
||||
use super::ast::{Constraint, FieldKind, OperatorKind, PolicyDefinition, PolicyRule};
|
||||
use super::expr::ExprParser;
|
||||
|
||||
use self::core::Parser;
|
||||
@@ -30,12 +45,56 @@ use self::core::Parser;
|
||||
// Public API
|
||||
// ============================================================================
|
||||
|
||||
/// Parse an Azure Policy rule from a JSON source.
|
||||
///
|
||||
/// The source should contain a complete `policyRule` JSON object:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "if": { "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
|
||||
/// "then": { "effect": "deny" }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Returns a span-annotated [`PolicyRule`] AST.
|
||||
pub fn parse_policy_rule(source: &Source) -> Result<PolicyRule, ParseError> {
|
||||
let mut parser = Parser::new(source)?;
|
||||
let rule = parser.parse_policy_rule()?;
|
||||
|
||||
if parser.tok.0 != TokenKind::Eof {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
span: parser.tok.1.clone(),
|
||||
expected: "end of input",
|
||||
});
|
||||
}
|
||||
|
||||
Ok(rule)
|
||||
}
|
||||
|
||||
/// Parse a full Azure Policy definition from a JSON source.
|
||||
///
|
||||
/// Accepts two forms:
|
||||
/// 1. **Wrapped**: `{ "properties": { "policyRule": ..., ... }, "id": ..., ... }`
|
||||
/// 2. **Unwrapped**: `{ "displayName": ..., "policyRule": ..., ... }`
|
||||
///
|
||||
/// Returns a [`PolicyDefinition`] with typed fields for known properties
|
||||
/// and a catch-all list of `extra` entries for everything else.
|
||||
pub fn parse_policy_definition(source: &Source) -> Result<PolicyDefinition, ParseError> {
|
||||
let mut parser = Parser::new(source)?;
|
||||
let defn = parser.parse_policy_definition()?;
|
||||
|
||||
if parser.tok.0 != TokenKind::Eof {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
span: parser.tok.1.clone(),
|
||||
expected: "end of input",
|
||||
});
|
||||
}
|
||||
|
||||
Ok(defn)
|
||||
}
|
||||
|
||||
/// Parse a standalone constraint from a JSON source.
|
||||
///
|
||||
/// A constraint is one of:
|
||||
/// - Logical combinator: `{ "allOf": [...] }`, `{ "anyOf": [...] }`, `{ "not": {...} }`
|
||||
/// - Leaf condition: `{ "field": "...", "equals": "..." }`
|
||||
/// - Count condition: `{ "count": { "field": "..." }, "greater": 0 }`
|
||||
/// Useful for parsing just the `"if"` part of a policy rule.
|
||||
pub fn parse_constraint(source: &Source) -> Result<Constraint, ParseError> {
|
||||
let mut parser = Parser::new(source)?;
|
||||
let constraint = parser.parse_constraint()?;
|
||||
|
||||
Reference in New Issue
Block a user