feat: Type System (#452)

Details:

- Implement complete Type enum with 12 variants: Any, Integer, Number, Boolean,
  Null, String, Array, Set, Object, Enum, Const, AnyOf
- Add Schema wrapper struct with reference counting for efficient sharing
- Support JSON Schema-compatible deserialization with serde
- Implement discriminated subobjects for polymorphic type definitions
- Add comprehensive test suite covering all type variants
- Include Azure resource schema examples (Storage, VM, Key Vault, App Service)
- Create meta-schema validation system with lazy static validator
- Add extensive edge case and corner case test coverage
- Implement custom deserializers for complex schema patterns

This establishes the foundation for type checking and validation of Rego
policies, particularly useful for cloud resource schemas and policy validation.

Regorus's type system is a first of many features intended to
enable type checking and various other constraints on Rego policies.

The type system is inspired from:
   - JSON schema
   - Bicep

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2025-08-11 15:40:45 -05:00
committed by GitHub
parent 2fcd5e3eb9
commit 77f8544868
11 changed files with 5780 additions and 28 deletions

View File

@@ -44,3 +44,6 @@ jobs:
- name: Run tests (OPA Conformance)
run: >-
cargo test -r --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)
- name: Run tests (Azure Policy)
run: >-
cargo test --frozen --features azure_policy

128
Cargo.lock generated
View File

@@ -48,9 +48,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstream"
version = "0.6.19"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -78,22 +78,22 @@ dependencies = [
[[package]]
name = "anstyle-query"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys",
"windows-sys 0.60.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.9"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
"windows-sys 0.60.2",
]
[[package]]
@@ -171,9 +171,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.31"
version = "1.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
dependencies = [
"shlex",
]
@@ -480,9 +480,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.4"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
[[package]]
name = "heck"
@@ -869,7 +869,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
@@ -1145,9 +1145,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.21"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
@@ -1511,7 +1511,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
@@ -1579,7 +1579,16 @@ version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.3",
]
[[package]]
@@ -1588,14 +1597,31 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]]
@@ -1604,48 +1630,96 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
@@ -1739,9 +1813,9 @@ dependencies = [
[[package]]
name = "zerovec"
version = "0.11.2"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
dependencies = [
"yoke",
"zerofrom",

View File

@@ -23,7 +23,7 @@ default = ["full-opa", "arc"]
arc = ["scientific/arc"]
ast = []
azure_policy = []
azure_policy = ["dep:jsonschema"]
base64 = ["dep:data-encoding"]
base64url = ["dep:data-encoding"]
coverage = []

View File

@@ -28,6 +28,8 @@ mod lexer;
mod number;
mod parser;
mod scheduler;
#[cfg(feature = "azure_policy")]
mod schema;
mod utils;
mod value;

1033
src/schema.rs Normal file

File diff suppressed because it is too large Load Diff

58
src/schema/meta.rs Normal file
View File

@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(dead_code)]
use crate::*;
use lazy_static::lazy_static;
const META_SCHEMA: &str = include_str!("meta.schema.json");
lazy_static! {
/// Lazy static JSON Schema validator for the Regorus meta-schema.
/// This validator is initialized once and can be used to validate
/// any schema definition against the Regorus meta-schema format.
static ref META_SCHEMA_VALIDATOR: jsonschema::Validator = {
let meta_schema_json: serde_json::Value = serde_json::from_str(META_SCHEMA)
.expect("META_SCHEMA should be valid JSON");
jsonschema::validator_for(&meta_schema_json)
.expect("META_SCHEMA should be a valid JSON Schema")
};
}
pub fn get_meta_schema() -> &'static str {
META_SCHEMA
}
/// Validates a schema definition against the Regorus meta-schema.
/// Returns true if the schema is valid, false otherwise.
pub fn validate_schema(schema: &serde_json::Value) -> bool {
META_SCHEMA_VALIDATOR.is_valid(schema)
}
/// Validates a schema definition against the Regorus meta-schema.
/// Returns Ok(()) if valid, or Err with validation errors if invalid.
pub fn validate_schema_detailed(schema: &serde_json::Value) -> Result<(), Vec<String>> {
if let jsonschema::BasicOutput::Invalid(errors) = META_SCHEMA_VALIDATOR.apply(schema).basic() {
let msgs: alloc::collections::BTreeSet<String> = errors
.iter()
.map(|e| format!("{}: {}", e.instance_location(), e.error_description()))
.collect();
let msgs: Vec<String> = msgs.into_iter().collect();
return Err(msgs);
}
Ok(())
}
/// Validates a schema definition from a JSON string.
/// Returns true if the schema is valid, false otherwise.
pub fn validate_schema_str(schema_str: &str) -> bool {
match serde_json::from_str::<serde_json::Value>(schema_str) {
Ok(schema) => validate_schema(&schema),
Err(_) => false, // Invalid JSON
}
}
#[cfg(test)]
mod tests;

376
src/schema/meta.schema.json Normal file
View File

@@ -0,0 +1,376 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/microsoft/regorus/schema/meta-schema",
"title": "Regorus Schema Meta-Schema",
"description": "JSON Schema describing the structure of Regorus schema definitions",
"type": "object",
"$defs": {
"schemaCore": {
"oneOf": [
{
"description": "Union type schema using anyOf",
"type": "object",
"properties": {
"anyOf": {
"type": "array",
"description": "Array of schemas where at least one must match",
"items": {
"$ref": "#/$defs/schemaCore"
},
"minItems": 0
}
},
"required": [
"anyOf"
],
"additionalProperties": false
},
{
"description": "Constant value schema",
"type": "object",
"properties": {
"const": {
"description": "The exact value that must match"
},
"description": {
"type": "string",
"description": "Human-readable description of the schema"
}
},
"required": [
"const"
],
"additionalProperties": false
},
{
"description": "Enumeration schema",
"type": "object",
"properties": {
"enum": {
"type": "array",
"description": "Array of allowed values",
"minItems": 0
},
"description": {
"type": "string",
"description": "Human-readable description of the schema"
}
},
"required": [
"enum"
],
"additionalProperties": false
},
{
"description": "Any type schema",
"type": "object",
"properties": {
"type": {
"const": "any"
},
"description": {
"type": "string"
},
"default": true
},
"required": [
"type"
],
"additionalProperties": false
},
{
"description": "Integer type schema",
"type": "object",
"properties": {
"type": {
"const": "integer"
},
"description": {
"type": "string"
},
"minimum": {
"type": "integer"
},
"maximum": {
"type": "integer"
},
"default": true
},
"required": [
"type"
],
"additionalProperties": false
},
{
"description": "Number type schema",
"type": "object",
"properties": {
"type": {
"const": "number"
},
"description": {
"type": "string"
},
"minimum": {
"type": "number"
},
"maximum": {
"type": "number"
},
"default": true
},
"required": [
"type"
],
"additionalProperties": false
},
{
"description": "Boolean type schema",
"type": "object",
"properties": {
"type": {
"const": "boolean"
},
"description": {
"type": "string"
},
"default": true
},
"required": [
"type"
],
"additionalProperties": false
},
{
"description": "Null type schema",
"type": "object",
"properties": {
"type": {
"const": "null"
},
"description": {
"type": "string"
}
},
"required": [
"type"
],
"additionalProperties": false
},
{
"description": "String type schema",
"type": "object",
"properties": {
"type": {
"const": "string"
},
"description": {
"type": "string"
},
"minLength": {
"type": "integer",
"minimum": 0
},
"maxLength": {
"type": "integer",
"minimum": 0
},
"pattern": {
"type": "string"
},
"default": true
},
"required": [
"type"
],
"additionalProperties": false
},
{
"description": "Array type schema",
"type": "object",
"properties": {
"type": {
"const": "array"
},
"description": {
"type": "string"
},
"items": {
"$ref": "#/$defs/schemaCore",
"description": "Schema for array items"
},
"minItems": {
"type": "integer",
"minimum": 0
},
"maxItems": {
"type": "integer",
"minimum": 0
},
"default": true
},
"required": [
"type",
"items"
],
"additionalProperties": false
},
{
"description": "Set type schema",
"type": "object",
"properties": {
"type": {
"const": "set"
},
"description": {
"type": "string"
},
"items": {
"$ref": "#/$defs/schemaCore",
"description": "Schema for set items"
},
"default": true
},
"required": [
"type",
"items"
],
"additionalProperties": false
},
{
"description": "Object type schema",
"type": "object",
"properties": {
"type": {
"const": "object"
},
"description": {
"type": "string"
},
"properties": {
"type": "object",
"description": "Object property definitions",
"additionalProperties": {
"$ref": "#/$defs/schemaCore"
},
"default": {}
},
"required": {
"type": "array",
"description": "Required property names",
"items": {
"type": "string"
},
"uniqueItems": true
},
"additionalProperties": {
"oneOf": [
{
"type": "boolean",
"description": "Whether additional properties are allowed (true) or not (false)"
},
{
"$ref": "#/$defs/schemaCore",
"description": "Schema for additional properties"
}
]
},
"name": {
"type": "string",
"description": "Optional name for the object type"
},
"default": true,
"allOf": {
"type": "array",
"description": "Discriminated subobject definitions",
"items": {
"$ref": "#/$defs/discriminatedSubobject"
}
}
},
"required": [
"type"
],
"additionalProperties": false
}
]
},
"discriminatedSubobject": {
"type": "object",
"properties": {
"if": {
"type": "object",
"properties": {
"properties": {
"type": "object",
"patternProperties": {
".*": {
"type": "object",
"properties": {
"const": {
"type": "string"
}
},
"required": [
"const"
],
"additionalProperties": false
}
},
"minProperties": 1,
"maxProperties": 1
}
},
"required": [
"properties"
],
"additionalProperties": false
},
"then": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/schemaCore"
},
"default": {}
},
"required": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"additionalProperties": {
"oneOf": [
{
"type": "boolean",
"description": "Whether additional properties are allowed (true) or not (false)"
},
{
"$ref": "#/$defs/schemaCore",
"description": "Schema for additional properties"
}
]
},
"name": {
"type": "string"
}
},
"additionalProperties": false
}
},
"required": [
"if",
"then"
],
"additionalProperties": false
}
},
"$ref": "#/$defs/schemaCore"
}

160
src/schema/meta/tests.rs Normal file
View File

@@ -0,0 +1,160 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use super::*;
use serde_json::json;
#[test]
fn test_get_meta_schema() {
let meta_schema = get_meta_schema();
assert!(!meta_schema.is_empty());
assert!(meta_schema.contains("$schema"));
}
#[test]
fn test_validate_valid_schema() {
let valid_schema = json!({
"type": "string",
"description": "A simple string"
});
assert!(validate_schema(&valid_schema));
assert!(validate_schema_detailed(&valid_schema).is_ok());
}
#[test]
fn test_validate_valid_anyof_schema() {
let valid_anyof_schema = json!({
"anyOf": [
{ "type": "string" },
{ "type": "integer" }
]
});
assert!(validate_schema(&valid_anyof_schema));
assert!(validate_schema_detailed(&valid_anyof_schema).is_ok());
}
#[test]
fn test_validate_valid_const_schema() {
let valid_const_schema = json!({
"const": "hello",
"description": "A constant value"
});
assert!(validate_schema(&valid_const_schema));
assert!(validate_schema_detailed(&valid_const_schema).is_ok());
}
#[test]
fn test_validate_valid_enum_schema() {
let valid_enum_schema = json!({
"enum": ["red", "green", "blue"],
"description": "Color enumeration"
});
assert!(validate_schema(&valid_enum_schema));
assert!(validate_schema_detailed(&valid_enum_schema).is_ok());
}
#[test]
fn test_validate_invalid_schema() {
let invalid_schema = json!({
"invalidField": "this should not be allowed"
});
assert!(!validate_schema(&invalid_schema));
assert!(validate_schema_detailed(&invalid_schema).is_err());
}
#[test]
fn test_validate_schema_str() {
let valid_schema_str = r#"{"type": "string"}"#;
let invalid_schema_str = r#"{"invalidField": true}"#;
let malformed_json = r#"{"invalid": json"#;
assert!(validate_schema_str(valid_schema_str));
assert!(!validate_schema_str(invalid_schema_str));
assert!(!validate_schema_str(malformed_json));
}
#[test]
fn test_validate_complex_object_schema() {
let complex_schema = json!({
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 }
},
"required": ["name"],
});
assert!(validate_schema(&complex_schema));
assert!(validate_schema_detailed(&complex_schema).is_ok());
}
#[test]
fn test_validate_detailed_error_messages() {
let invalid_schema = json!({
"type": "object",
"unknownProperty": true
});
let result = validate_schema_detailed(&invalid_schema);
assert!(result.is_err());
let errors = result.unwrap_err();
assert!(!errors.is_empty());
// The error should mention the unknown property
assert!(errors.iter().any(|err| err.contains("unknownProperty")));
}
#[test]
fn test_validator_is_lazy_initialized() {
// This test ensures the lazy static validator is working
// Multiple calls should use the same validator instance
let schema1 = json!({"type": "string"});
let schema2 = json!({"type": "integer"});
assert!(validate_schema(&schema1));
assert!(validate_schema(&schema2));
}
#[test]
fn test_validate_array_schema() {
let array_schema = json!({
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"maxItems": 10
});
assert!(validate_schema(&array_schema));
assert!(validate_schema_detailed(&array_schema).is_ok());
}
#[test]
fn test_validate_discriminated_subobject_schema() {
let discriminated_schema = json!({
"type": "object",
"properties": {
"kind": { "type": "string" }
},
"allOf": [
{
"if": {
"properties": { "kind": { "const": "user" } }
},
"then": {
"properties": {
"username": { "type": "string" }
},
"required": ["username"]
}
}
]
});
assert!(validate_schema(&discriminated_schema));
assert!(validate_schema_detailed(&discriminated_schema).is_ok());
}

5
src/schema/tests.rs Normal file
View File

@@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
mod azure;
mod suite;

1266
src/schema/tests/azure.rs Normal file

File diff suppressed because it is too large Load Diff

2775
src/schema/tests/suite.rs Normal file

File diff suppressed because it is too large Load Diff