Files
regorus/bindings/ffi/src/common.rs
Anand Krishnamoorthi d36f952133 feat(azure-policy): add alias normalization and denormalization (#635)
* 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
2026-03-30 18:44:36 -05:00

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);
}
}
}