Files
regorus/src/registry.rs
Anand Krishnamoorthi cc917ea75d feat: Complete target system with C# bindings and resource inference (#458)
* feat: Add Schema Registry and Validation Framework

This commit introduces a comprehensive schema registry and validation framework, providing schema-based validation of resources and policy effects.

- Thread-safe, in-memory registry for schema storage and management
- Global registry patterns for effects and resources
- Concurrent access with proper error handling
- Unicode schema names support

- JSON Schema-compliant validation for all primitive types
- Advanced constraint validation (patterns, ranges, length limits)
- Discriminated union support with anyOf schemas
- Detailed error reporting with nested validation paths
- Discriminated subobject validation for polymorphic schemas

- **Registry Tests**: All registry operations
- **Effect Tests**: Policy effect validation
- **Resource Tests**: Resource validation
- **Validation Tests**: Core validation engine
- Thread-safety, error handling, integration scenarios, edge cases

- **Dependencies**: dashmap, once_cell, regex
- **Thread Safety**: Minimal locking with Rc<Schema> sharing
- **Error Types**: TypeMismatch, OutOfRange, PatternMismatch, etc.

- Complete schema registry and validation subsystem
- Comprehensive test coverage
- Foundation for policy validation in Regorus

Benchmarks:

- Criterion benchmarks for basic types, effects and Azure resources
- Performance range: 3.22ns (string) to 34.74µs (Azure VM resource schema validation)
- String withs patterns validation: 30.2µs. Need to explore whether regex caching helps
  bring this down.
- Azure policy effects: 188ns-1.4µs

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* feat: Complete target system with C# bindings and resource inference

- Add comprehensive target system with TargetRegistry and target-aware compilation
- Implement resource type inference from policy equality expressions
- Create modular C# bindings with separate wrapper classes for each concept
- Add thread-safe CompiledPolicy with reference counting for safe disposal
- Enhance FFI with detailed error propagation and target functionality
- Create TargetExampleApp demonstrating Azure Policy integration
- Add CI/CD pipeline testing for all C# applications
- Support target definitions with schema validation and resource selectors
- Implement PolicyModule struct and target-aware compilation methods
- Add comprehensive test coverage for target functionality

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-19 20:23:43 -05:00

346 lines
11 KiB
Rust

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(dead_code)]
use crate::*;
use core::fmt;
use dashmap::DashMap;
type String = Rc<str>;
#[cfg(test)]
mod tests {
mod core;
mod effect;
mod resource;
mod target;
}
/// Errors that can occur when interacting with a Registry.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RegistryError {
AlreadyExists { name: String, registry: String },
InvalidName { name: String, registry: String },
}
impl fmt::Display for RegistryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RegistryError::AlreadyExists { name, registry } => {
write!(
f,
"{} registration failed: An item with the name '{name}' is already registered.",
registry
)
}
RegistryError::InvalidName { name, registry } => {
write!(f, "{} registration failed: The name '{name}' is invalid (empty or whitespace-only names are not allowed).", registry)
}
}
}
}
impl core::error::Error for RegistryError {}
/// Validates that a name is not empty or whitespace-only.
pub fn validate_name(name: &str, registry_name: &str) -> Result<(), RegistryError> {
if name.is_empty() || name.trim().is_empty() {
Err(RegistryError::InvalidName {
name: String::from(name),
registry: String::from(registry_name),
})
} else {
Ok(())
}
}
/// Generic thread-safe registry for items of type T using DashMap.
///
/// This template can be used to create registries for any type T.
/// It provides thread-safe storage and retrieval operations with customizable registry names.
#[derive(Clone)]
pub struct Registry<T> {
inner: DashMap<String, Rc<T>>,
name: String,
}
impl<T> Registry<T> {
/// Create a new, empty registry with a given name.
pub fn new(registry_name: impl Into<String>) -> Self {
Self {
inner: DashMap::new(),
name: registry_name.into(),
}
}
/// Get the name of this registry.
pub fn name(&self) -> &str {
&self.name
}
/// Register an item with a given name. Returns Err if name already exists.
pub fn register(&self, name: impl Into<String>, item: Rc<T>) -> Result<(), RegistryError> {
let name = name.into();
// Validate the name first
validate_name(&name, &self.name)?;
use dashmap::mapref::entry::Entry;
match self.inner.entry(name.clone()) {
Entry::Occupied(e) => Err(RegistryError::AlreadyExists {
name: e.key().clone(),
registry: self.name.clone(),
}),
Entry::Vacant(e) => {
e.insert(item);
Ok(())
}
}
}
/// Retrieve an item by name, if it exists.
pub fn get(&self, name: &str) -> Option<Rc<T>> {
self.inner.get(name).map(|entry| Rc::clone(entry.value()))
}
/// Remove an item by name. Returns the removed item if it existed.
pub fn remove(&self, name: &str) -> Option<Rc<T>> {
self.inner.remove(name).map(|(_, v)| v)
}
/// List all registered item names.
pub fn list_names(&self) -> Vec<String> {
self.inner.iter().map(|entry| entry.key().clone()).collect()
}
/// Check if an item with the given name exists.
pub fn contains(&self, name: &str) -> bool {
self.inner.contains_key(name)
}
/// Get the number of registered items.
pub fn len(&self) -> usize {
self.inner.len()
}
/// Check if the registry is empty.
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// Clear all items from the registry.
pub fn clear(&self) {
self.inner.clear();
}
/// Get an iterator over all entries in the registry.
/// Returns an iterator of (name, item) pairs.
pub fn iter(&self) -> impl Iterator<Item = (String, Rc<T>)> + '_ {
self.inner
.iter()
.map(|entry| (entry.key().clone(), Rc::clone(entry.value())))
}
/// Get all registered items as a vector.
pub fn list_items(&self) -> Vec<Rc<T>> {
self.inner
.iter()
.map(|entry| Rc::clone(entry.value()))
.collect()
}
/// Try to register an item, but don't fail if the name already exists.
/// Returns Ok(true) if the item was registered, Ok(false) if the name already exists.
pub fn try_register(
&self,
name: impl Into<String>,
item: Rc<T>,
) -> Result<bool, RegistryError> {
match self.register(name, item) {
Ok(()) => Ok(true),
Err(RegistryError::AlreadyExists { .. }) => Ok(false),
Err(e) => Err(e),
}
}
}
/// Type alias for Schema registry
pub type SchemaRegistry = Registry<crate::Schema>;
/// Type alias for Target registry
pub type TargetRegistry = Registry<crate::target::Target>;
/// Global registry instances
pub mod instances {
use super::*;
lazy_static::lazy_static! {
/// Global singleton instance of resource schemas registry.
pub static ref RESOURCE_SCHEMA_REGISTRY: Registry<crate::Schema> = Registry::new("RESOURCE_SCHEMA_REGISTRY");
}
lazy_static::lazy_static! {
/// Global singleton instance of effect schemas registry.
pub static ref EFFECT_SCHEMA_REGISTRY: Registry<crate::Schema> = Registry::new("EFFECT_SCHEMA_REGISTRY");
}
lazy_static::lazy_static! {
/// Global singleton instance of targets registry.
pub static ref TARGET_REGISTRY: Registry<crate::target::Target> = Registry::new("TARGET_REGISTRY");
}
}
/// Macro to generate helper functions for registry operations.
///
/// This macro generates helper functions that wrap the registry operations.
/// It reduces code duplication and makes it easier to maintain registry interfaces.
///
/// # Arguments
/// * `$registry_var` - The static registry variable to wrap
/// * `$item_type` - The type of items stored in the registry (e.g., `crate::Schema`)
/// * `$item_description` - Human-readable description of the item type (e.g., "resource schema")
/// * `$item_description_plural` - Plural form of the item description (e.g., "resource schemas")
macro_rules! generate_registry_helpers {
($registry_var:ident, $item_type:ty, $item_description:literal, $item_description_plural:literal) => {
#[doc = concat!("Register a ", $item_description, " with a given name.")]
pub fn register(
name: impl Into<String>,
item: Rc<$item_type>,
) -> Result<(), RegistryError> {
$registry_var.register(name, item)
}
#[doc = concat!("Retrieve a ", $item_description, " by name.")]
pub fn get(name: &str) -> Option<Rc<$item_type>> {
$registry_var.get(name)
}
#[doc = concat!("Remove a ", $item_description, " by name.")]
pub fn remove(name: &str) -> Option<Rc<$item_type>> {
$registry_var.remove(name)
}
#[doc = concat!("List all registered ", $item_description, " names.")]
pub fn list_names() -> Vec<String> {
$registry_var.list_names()
}
#[doc = concat!("Check if a ", $item_description, " with the given name exists.")]
pub fn contains(name: &str) -> bool {
$registry_var.contains(name)
}
#[doc = concat!("Get the number of registered ", $item_description_plural, ".")]
pub fn len() -> usize {
$registry_var.len()
}
#[doc = concat!("Check if the ", $item_description, " registry is empty.")]
pub fn is_empty() -> bool {
$registry_var.is_empty()
}
#[doc = concat!("Clear all ", $item_description_plural, " from the registry.")]
pub fn clear() {
$registry_var.clear();
}
};
}
/// Macro to generate a module with helper functions for registry operations.
///
/// This macro generates a complete module with helper functions that wrap the registry operations.
/// It reduces code duplication and makes it easier to maintain registry interfaces.
///
/// # Arguments
/// * `$mod_name` - The name of the module to generate
/// * `$registry_var` - The static registry variable to wrap
/// * `$item_type` - The type of items stored in the registry (e.g., `crate::Schema`)
/// * `$item_description` - Human-readable description of the item type (e.g., "resource schema")
/// * `$item_description_plural` - Plural form of the item description (e.g., "resource schemas")
macro_rules! generate_registry_module {
($mod_name:ident, $registry_var:ident, $item_type:ty, $item_description:literal, $item_description_plural:literal) => {
#[doc = concat!("Helper functions for ", $item_description, " registry operations.")]
pub mod $mod_name {
use super::*;
generate_registry_helpers!(
$registry_var,
$item_type,
$item_description,
$item_description_plural
);
}
};
}
/// Helper functions for schema registry operations.
pub mod schemas {
use super::*;
use instances::*;
// Generate helper modules for schema registries
generate_registry_module!(
resource,
RESOURCE_SCHEMA_REGISTRY,
crate::Schema,
"resource schema",
"resource schemas"
);
generate_registry_module!(
effect,
EFFECT_SCHEMA_REGISTRY,
crate::Schema,
"effect schema",
"effect schemas"
);
}
/// Helper functions for target registry operations.
pub mod targets {
use super::*;
use instances::*;
/// Register a target using its name property.
pub fn register(item: Rc<crate::target::Target>) -> Result<(), RegistryError> {
let name = item.name.as_ref().to_string();
TARGET_REGISTRY.register(name, item)
}
/// Retrieve a target by name.
pub fn get(name: &str) -> Option<Rc<crate::target::Target>> {
TARGET_REGISTRY.get(name)
}
/// Remove a target by name.
pub fn remove(name: &str) -> Option<Rc<crate::target::Target>> {
TARGET_REGISTRY.remove(name)
}
/// List all registered target names.
pub fn list_names() -> Vec<String> {
TARGET_REGISTRY.list_names()
}
/// Check if a target with the given name exists.
pub fn contains(name: &str) -> bool {
TARGET_REGISTRY.contains(name)
}
/// Get the number of registered targets.
pub fn len() -> usize {
TARGET_REGISTRY.len()
}
/// Check if the target registry is empty.
pub fn is_empty() -> bool {
TARGET_REGISTRY.is_empty()
}
/// Clear all targets from the registry.
pub fn clear() {
TARGET_REGISTRY.clear();
}
}