mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
* feat: add Azure Policy alias normalization/denormalization Add normalizer and denormalizer for ARM JSON resources, enabling Azure Policy alias short names to become direct paths into a flat structure. - Normalizer: flattens properties wrappers, lowercases keys, resolves per-alias versioned ARM paths, handles sub-resource array flattening, element-level field remaps, and array base renames - Denormalizer: reverses all transformations with casing restoration - AliasRegistry: loads production alias catalogs and data policy manifests - Types: serde deserialization for ARM provider alias formats - YAML test suite: 13 test files covering normalize, denormalize, round-trip, data-plane, edge cases, malformed input, sub-resources, and registry API - Benchmark suite for normalization performance * feat: add FFI and C# bindings for alias normalization - FFI: alias_registry.rs with C-compatible API for loading catalogs, normalizing resources, and denormalizing back to ARM JSON - C#: AliasRegistry wrapper class with NativeMethods P/Invoke bindings and integration tests - Updated Cargo.lock files for new serde_json dependency
282 lines
7.5 KiB
Rust
282 lines
7.5 KiB
Rust
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
use alloc::boxed::Box;
|
|
use alloc::ffi::CString;
|
|
use alloc::format;
|
|
use alloc::string::{String, ToString};
|
|
use alloc::vec::Vec;
|
|
use anyhow::{anyhow, bail, Result};
|
|
use core::ffi::{c_char, c_longlong, c_void, CStr};
|
|
use core::{mem, ptr};
|
|
|
|
/// Status of a call on `RegorusEngine`.
|
|
#[derive(Debug, PartialEq)]
|
|
#[repr(C)]
|
|
pub enum RegorusStatus {
|
|
/// The operation was successful.
|
|
Ok,
|
|
|
|
/// The operation was unsuccessful.
|
|
Error,
|
|
|
|
/// Invalid data format provided.
|
|
InvalidDataFormat,
|
|
|
|
/// Invalid entrypoint rule specified.
|
|
InvalidEntrypoint,
|
|
|
|
/// Compilation failed.
|
|
CompilationFailed,
|
|
|
|
/// Invalid argument provided.
|
|
InvalidArgument,
|
|
|
|
/// Invalid module ID.
|
|
InvalidModuleId,
|
|
|
|
/// Invalid policy content.
|
|
InvalidPolicy,
|
|
|
|
/// The engine panicked and cannot be reused until reset.
|
|
Panic,
|
|
|
|
/// The engine remains poisoned because a previous panic was detected.
|
|
Poisoned,
|
|
}
|
|
|
|
/// Type of data contained in RegorusResult
|
|
#[repr(C)]
|
|
#[allow(unused)]
|
|
pub enum RegorusDataType {
|
|
/// No data / void
|
|
None,
|
|
/// String data (output field is valid)
|
|
String,
|
|
/// Boolean data (bool_value field is valid)
|
|
Boolean,
|
|
/// Integer data (int_value field is valid)
|
|
Integer,
|
|
/// Pointer data (pointer_value field is valid)
|
|
Pointer,
|
|
}
|
|
|
|
/// Result of a call on `RegorusEngine`.
|
|
///
|
|
/// Must be freed using `regorus_result_drop`.
|
|
#[repr(C)]
|
|
pub struct RegorusResult {
|
|
/// Status
|
|
pub(crate) status: RegorusStatus,
|
|
|
|
/// Type of data contained in this result
|
|
pub(crate) data_type: RegorusDataType,
|
|
|
|
/// String output produced by the call.
|
|
/// Valid when data_type is String. Owned by Rust.
|
|
pub(crate) output: *mut c_char,
|
|
|
|
/// Boolean value.
|
|
/// Valid when data_type is Boolean.
|
|
pub(crate) bool_value: bool,
|
|
|
|
/// Integer value.
|
|
/// Valid when data_type is Integer.
|
|
pub(crate) int_value: c_longlong,
|
|
|
|
/// Pointer value.
|
|
/// Valid when data_type is Pointer.
|
|
pub(crate) pointer_value: *mut c_void,
|
|
|
|
/// Errors produced by the call.
|
|
/// Owned by Rust.
|
|
pub(crate) error_message: *mut c_char,
|
|
}
|
|
|
|
/// Byte buffer returned from FFI for binary payloads.
|
|
///
|
|
/// Must be freed using `regorus_buffer_drop`.
|
|
#[repr(C)]
|
|
pub struct RegorusBuffer {
|
|
/// Pointer to byte buffer data.
|
|
pub data: *mut u8,
|
|
/// Number of bytes stored in `data`.
|
|
pub len: usize,
|
|
/// Capacity of the allocation backing `data`.
|
|
pub capacity: usize,
|
|
}
|
|
|
|
impl RegorusResult {
|
|
/// Create a successful result with no data.
|
|
pub(crate) fn ok_void() -> Self {
|
|
Self {
|
|
status: RegorusStatus::Ok,
|
|
data_type: RegorusDataType::None,
|
|
output: ptr::null_mut(),
|
|
bool_value: false,
|
|
int_value: 0,
|
|
pointer_value: ptr::null_mut(),
|
|
error_message: ptr::null_mut(),
|
|
}
|
|
}
|
|
|
|
/// Create a successful result with string output.
|
|
pub(crate) fn ok_string(output: String) -> Self {
|
|
Self {
|
|
status: RegorusStatus::Ok,
|
|
data_type: RegorusDataType::String,
|
|
output: to_c_str(output),
|
|
bool_value: false,
|
|
int_value: 0,
|
|
pointer_value: ptr::null_mut(),
|
|
error_message: ptr::null_mut(),
|
|
}
|
|
}
|
|
|
|
/// Create a successful result with boolean value.
|
|
#[allow(unused)]
|
|
pub(crate) fn ok_bool(value: bool) -> Self {
|
|
Self {
|
|
status: RegorusStatus::Ok,
|
|
data_type: RegorusDataType::Boolean,
|
|
output: ptr::null_mut(),
|
|
bool_value: value,
|
|
int_value: 0,
|
|
pointer_value: ptr::null_mut(),
|
|
error_message: ptr::null_mut(),
|
|
}
|
|
}
|
|
|
|
/// Create a successful result with integer value.
|
|
#[allow(unused)]
|
|
pub(crate) fn ok_int(value: i64) -> Self {
|
|
Self {
|
|
status: RegorusStatus::Ok,
|
|
data_type: RegorusDataType::Integer,
|
|
output: ptr::null_mut(),
|
|
bool_value: false,
|
|
int_value: value as c_longlong,
|
|
pointer_value: ptr::null_mut(),
|
|
error_message: ptr::null_mut(),
|
|
}
|
|
}
|
|
|
|
/// Create a successful result with pointer value.
|
|
pub(crate) fn ok_pointer(pointer: *mut c_void) -> Self {
|
|
Self {
|
|
status: RegorusStatus::Ok,
|
|
data_type: RegorusDataType::Pointer,
|
|
output: ptr::null_mut(),
|
|
bool_value: false,
|
|
int_value: 0,
|
|
pointer_value: pointer,
|
|
error_message: ptr::null_mut(),
|
|
}
|
|
}
|
|
|
|
/// Create an error result with specific status.
|
|
pub(crate) fn err(status: RegorusStatus) -> Self {
|
|
Self {
|
|
status,
|
|
data_type: RegorusDataType::None,
|
|
output: ptr::null_mut(),
|
|
bool_value: false,
|
|
int_value: 0,
|
|
pointer_value: ptr::null_mut(),
|
|
error_message: ptr::null_mut(),
|
|
}
|
|
}
|
|
|
|
/// Create an error result with status and message.
|
|
pub(crate) fn err_with_message(status: RegorusStatus, message: String) -> Self {
|
|
Self {
|
|
status,
|
|
data_type: RegorusDataType::None,
|
|
output: ptr::null_mut(),
|
|
bool_value: false,
|
|
int_value: 0,
|
|
pointer_value: ptr::null_mut(),
|
|
error_message: to_c_str(message),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RegorusBuffer {
|
|
pub(crate) fn from_vec(mut data: Vec<u8>) -> *mut RegorusBuffer {
|
|
let buffer = RegorusBuffer {
|
|
data: data.as_mut_ptr(),
|
|
len: data.len(),
|
|
capacity: data.capacity(),
|
|
};
|
|
mem::forget(data);
|
|
Box::into_raw(Box::new(buffer))
|
|
}
|
|
}
|
|
|
|
pub(crate) fn to_c_str(s: String) -> *mut c_char {
|
|
match CString::new(s) {
|
|
Ok(cs) => cs.into_raw(),
|
|
_ => to_c_str("binding error: failed to create c-style string".to_string()),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn from_c_str(s: *const c_char) -> Result<String> {
|
|
if s.is_null() {
|
|
bail!("null pointer");
|
|
}
|
|
unsafe {
|
|
CStr::from_ptr(s)
|
|
.to_str()
|
|
.map_err(|e| anyhow!("invalid utf8: {e}"))
|
|
.map(|s| s.to_string())
|
|
}
|
|
}
|
|
|
|
pub(crate) fn to_ref<'a, T>(t: *mut T) -> Result<&'a mut T> {
|
|
unsafe { t.as_mut().ok_or_else(|| anyhow!("null pointer")) }
|
|
}
|
|
|
|
pub(crate) fn to_regorus_result(r: Result<()>) -> RegorusResult {
|
|
match r {
|
|
Ok(()) => RegorusResult::ok_void(),
|
|
Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn to_regorus_string_result(r: Result<String>) -> RegorusResult {
|
|
match r {
|
|
Ok(s) => RegorusResult::ok_string(s),
|
|
Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
|
|
}
|
|
}
|
|
|
|
/// Drop a `RegorusBuffer`.
|
|
///
|
|
/// `data` is not valid after drop.
|
|
#[no_mangle]
|
|
pub extern "C" fn regorus_buffer_drop(buffer: *mut RegorusBuffer) {
|
|
if let Ok(buffer) = to_ref(buffer) {
|
|
unsafe {
|
|
if !buffer.data.is_null() {
|
|
let _ = Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity);
|
|
}
|
|
let _ = Box::from_raw(ptr::from_mut(buffer));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Drop a `RegorusResult`.
|
|
///
|
|
/// `output` and `error_message` strings are not valid after drop.
|
|
#[no_mangle]
|
|
pub extern "C" fn regorus_result_drop(r: RegorusResult) {
|
|
unsafe {
|
|
if !r.error_message.is_null() {
|
|
let _ = CString::from_raw(r.error_message);
|
|
}
|
|
if !r.output.is_null() {
|
|
let _ = CString::from_raw(r.output);
|
|
}
|
|
}
|
|
}
|