Files
regorus/bindings/csharp/Regorus/Regorus.cs
Anand Krishnamoorthi 130f9685fd feat: Updates for Policy Framework (#405)
- Documentation
  - Regorus Engine is intended to be used from a single thread
  - Clone the engine after adding policies and data to use from another thread

- Builtin errors strictness:
  - default to less strict for OPA compatibility
  - Provide API to change strictness

- Expose GetAstAsJson to C#,
  This can allow writing policy validations in C#.

- Use spectre mitigated msvc crt libs (binskim compliance)

- Update dependencies

fixes #404

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-30 15:33:40 -05:00

255 lines
8.4 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
using System.Text;
#nullable enable
namespace Regorus
{
/// <summary>
/// C# Wrapper for the Regorus engine.
/// This class is not thread-safe. For multithreaded use, prefer cloning after adding policies and data to an instance.
/// Cloning is cheap and involves only incrementing reference counts for shared immutable objects like parsed policies,
/// data etc. Mutable state is deep copied as needed.
/// </summary>
public unsafe sealed class Engine : System.IDisposable
{
private Regorus.Internal.RegorusEngine* E;
// Detect redundant Dispose() calls in a thread-safe manner.
// _isDisposed == 0 means Dispose(bool) has not been called yet.
// _isDisposed == 1 means Dispose(bool) has been already called.
private int isDisposed;
public Engine()
{
E = Regorus.Internal.API.regorus_engine_new();
}
public void Dispose()
{
Dispose(disposing: true);
// This object will be cleaned up by the Dispose method.
// Therefore, call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
void Dispose(bool disposing)
{
// In case _isDisposed is 0, atomically set it to 1.
// Enter the branch only if the original value is 0.
if (System.Threading.Interlocked.CompareExchange(ref isDisposed, 1, 0) == 0)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
// No managed resource to dispose.
}
// Call the appropriate methods to clean up
// unmanaged resources here.
// If disposing is false,
// only the following code is executed.
if (E != null)
{
Regorus.Internal.API.regorus_engine_drop(E);
E = null;
}
}
}
// Use C# finalizer syntax for finalization code.
// This finalizer will run only if the Dispose method
// does not get called.
~Engine() => Dispose(disposing: false);
// Helper for implementing Clone
private Engine(Internal.RegorusEngine* engine)
{
this.E = engine;
}
public Engine Clone() => new(Internal.API.regorus_engine_clone(E));
public void SetStrictBuiltinErrors(bool strict)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_strict_builtin_errors(E, strict));
}
byte[] NullTerminatedUTF8Bytes(string s)
{
return Encoding.UTF8.GetBytes(s + char.MinValue);
}
public string? AddPolicy(string path, string rego)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
var regoBytes = NullTerminatedUTF8Bytes(rego);
fixed (byte* pathPtr = pathBytes)
{
fixed (byte* regoPtr = regoBytes)
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
}
}
}
public void SetRegoV0(bool enable)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0(E, enable));
}
public string? AddPolicyFromFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
fixed (byte* pathPtr = pathBytes)
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file(E, pathPtr));
}
}
public void AddDataJson(string data)
{
var dataBytes = NullTerminatedUTF8Bytes(data);
fixed (byte* dataPtr = dataBytes)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json(E, dataPtr));
}
}
public void AddDataFromJsonFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file(E, pathPtr));
}
}
public void SetInputJson(string input)
{
var inputBytes = NullTerminatedUTF8Bytes(input);
fixed (byte* inputPtr = inputBytes)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json(E, inputPtr));
}
}
public void SetInputFromJsonFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file(E, pathPtr));
}
}
public string? EvalQuery(string query)
{
var queryBytes = NullTerminatedUTF8Bytes(query);
fixed (byte* queryPtr = queryBytes)
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query(E, queryPtr));
}
}
public string? EvalRule(string rule)
{
var ruleBytes = NullTerminatedUTF8Bytes(rule);
fixed (byte* rulePtr = ruleBytes)
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule(E, rulePtr));
}
}
public void SetEnableCoverage(bool enable)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage(E, enable));
}
public void ClearCoverageData()
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data(E));
}
public string? GetCoverageReport()
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report(E));
}
public string? GetCoverageReportPretty()
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty(E));
}
public void SetGatherPrints(bool enable)
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints(E, enable));
}
public string? TakePrints()
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints(E));
}
public string? GetAstAsJson()
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json(E));
}
string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
{
if (result.status != Regorus.Internal.RegorusStatus.RegorusStatusOk)
{
var message = StringFromUTF8((IntPtr)result.error_message);
var ex = new Exception(message);
Regorus.Internal.API.regorus_result_drop(result);
throw ex;
}
var resultString = "";
if (result.output is not null)
{
resultString = StringFromUTF8((IntPtr)result.output);
}
Regorus.Internal.API.regorus_result_drop(result);
return resultString;
}
}
}