Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/uniffi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,82 @@ jobs:
run: ./gradlew publishMavenKotlinPublicationToCentralPortal publishMavenJavaPublicationToCentralPortal


release-csharp:
runs-on: ubuntu-latest
needs: [ build ]
permissions:
id-token: write # Required for OIDC
if: "startsWith(github.event.head_commit.message, 'chore(release): publish uniffi')"
defaults:
run:
working-directory: ${{ env.UNIFFI_DIRECTORY }}

steps:
- uses: actions/checkout@v3

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'

- name: Setup Rust Toolchain
uses: dtolnay/rust-toolchain@stable

- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: build/generated/resources

- name: Move artifacts
run: mv ../../build ./

- name: Install uniffi-bindgen-cs
run: cargo install uniffi-bindgen-cs --git https://github.com/bcrevar-gorules/uniffi-bindgen-cs --branch fix/async-callback-interface-return-type

- name: Generate C# bindings
run: |
uniffi-bindgen-cs \
--library build/generated/resources/darwin-x86-64/libzen_uniffi.dylib \
--config uniffi.toml \
--out-dir build/generated/csharp

- name: Upload generated C# source
uses: actions/upload-artifact@v4
with:
name: csharp-generated-source
path: ${{ env.UNIFFI_DIRECTORY }}/build/generated/csharp/zen_uniffi.cs
if-no-files-found: error

- name: Extract version from Cargo.toml
id: version
run: |
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Detected version: $VERSION"

- name: Pack NuGet package
run: dotnet pack nuget/GoRules.ZenEngine.csproj -c Release -p:Version=$VERSION

- name: Upload NuGet package artifact
uses: actions/upload-artifact@v4
with:
name: nuget-package
path: ${{ env.UNIFFI_DIRECTORY }}/nuget/bin/Release/*.nupkg
if-no-files-found: error

- name: NuGet login (trusted publishing)
uses: NuGet/login@v1
id: nuget-login
with:
user: ${{ secrets.NUGET_USER }}

- name: Push to NuGet.org
run: |
dotnet nuget push nuget/bin/Release/*.nupkg \
--api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }} \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate

build-ios:
if: "!contains(github.event.head_commit.message, 'skip ci')"
env:
Expand Down
2 changes: 2 additions & 0 deletions bindings/uniffi/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.idea
.gradle
build/
build-android/
jniLibs/
23 changes: 23 additions & 0 deletions bindings/uniffi/lib/csharp/GoRules/ZenEngine/JsonBuffer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace GoRules.ZenEngine;

using System.Text;

public class JsonBuffer
{
public byte[] Value { get; }

public JsonBuffer(byte[] value)
{
Value = value;
}

public JsonBuffer(string json)
{
Value = Encoding.UTF8.GetBytes(json);
}

public override string ToString()
{
return Encoding.UTF8.GetString(Value);
}
}
66 changes: 66 additions & 0 deletions bindings/uniffi/nuget/GoRules.ZenEngine.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PackageId>GoRules.ZenEngine</PackageId>
<Authors>GoRules</Authors>
<Company>GoRules</Company>
<Description>ZEN Engine - Business Rules Engine for .NET. Execute JSON Decision Models (JDM) with native performance via Rust FFI bindings.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/gorules/zen</PackageProjectUrl>
<PackageIcon>icon.png</PackageIcon>
<RepositoryUrl>https://github.com/gorules/zen</RepositoryUrl>
<PackageTags>rules-engine;business-rules;decision-engine;jdm;gorules</PackageTags>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="/" />
<None Include="icon.png" Pack="true" PackagePath="" />
</ItemGroup>

<!-- C# source files (generated + custom) -->
<ItemGroup>
<Compile Include="../build/generated/csharp/zen_uniffi.cs" Link="zen_uniffi.cs" />
<Compile Include="../lib/csharp/GoRules/ZenEngine/JsonBuffer.cs" Link="JsonBuffer.cs" />
</ItemGroup>

<!-- Native libraries for each platform (NuGet RID layout) -->
<ItemGroup>
<!-- macOS x64 -->
<None Include="../build/generated/resources/darwin-x86-64/libzen_uniffi.dylib"
Pack="true"
PackagePath="runtimes/osx-x64/native/"
Visible="false" />

<!-- macOS arm64 -->
<None Include="../build/generated/resources/darwin-aarch64/libzen_uniffi.dylib"
Pack="true"
PackagePath="runtimes/osx-arm64/native/"
Visible="false" />

<!-- Linux x64 -->
<None Include="../build/generated/resources/linux-x86-64/libzen_uniffi.so"
Pack="true"
PackagePath="runtimes/linux-x64/native/"
Visible="false" />

<!-- Linux arm64 -->
<None Include="../build/generated/resources/linux-aarch64/libzen_uniffi.so"
Pack="true"
PackagePath="runtimes/linux-arm64/native/"
Visible="false" />

<!-- Windows x64 -->
<None Include="../build/generated/resources/win32-x86-64/zen_uniffi.dll"
Pack="true"
PackagePath="runtimes/win-x64/native/"
Visible="false" />
</ItemGroup>

</Project>
98 changes: 98 additions & 0 deletions bindings/uniffi/nuget/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# GoRules.ZenEngine

Open-source Business Rules Engine for .NET. Execute JSON Decision Models (JDM) with native performance powered by Rust.

## Installation

```bash
dotnet add package GoRules.ZenEngine
```

## Quick Start

```csharp
using GoRules.ZenEngine;

// Create an engine and evaluate
var engine = new ZenEngine(loader: null, customNode: null);
var decision = engine.CreateDecision(new JsonBuffer(File.ReadAllBytes("my-decision.json")));
var context = new JsonBuffer("""{"input": 42}""");
var response = await decision.Evaluate(context, null);
Console.WriteLine(response.result);

```

## Features

- **Decision Tables** - Rule tables with first/collect hit policies
- **Expression Language** - Built-in ZEN expression language with functions like `sum()`, `filter()`, `map()`
- **Custom Nodes** - Extend the engine with custom node handlers
- **Tracing** - Full execution trace for debugging and auditing
- **Cross-platform** - Native libraries for Windows (x64), macOS (x64/ARM), Linux (x64/ARM)

## Tracing

Enable tracing to inspect the execution of each node:

```csharp
var options = new ZenEvaluateOptions(maxDepth: null, trace: true);
var decided = await decision.Evaluate(context, options);

foreach (var (nodeId, trace) in decided.trace!)
{
Console.WriteLine($"{trace.name}: {trace.output}");
}
```

## Expression Evaluation

Evaluate expressions directly without a decision file:

```csharp
// One-off evaluation
var result = ZenUniffiMethods.EvaluateExpression(
"sum(items) * multiplier",
new JsonBuffer("{\"items\": [10, 20, 30], \"multiplier\": 2}")
);

// Compiled expression (reusable, better performance)
var expr = ZenExpression.Compile("a + b * 2");
var output = expr.Evaluate(new JsonBuffer("{\"a\": 1, \"b\": 10}"));
Console.WriteLine($"output: {output}");
expr.Dispose();
```

## Custom Nodes

Extend the engine with custom logic:

```csharp

using var myEngine = new ZenEngine(loader: new FileLoader(), customNode: new MyCustomNode());
var myResponse = await myEngine.Evaluate("custom.json", context, options);
Console.WriteLine(myResponse.result);

// Custom node handler
class MyCustomNode : ZenCustomNodeCallback
{
public Task<ZenEngineHandlerResponse> Handle(ZenEngineHandlerRequest request) =>
Task.FromResult(new ZenEngineHandlerResponse(
output: new JsonBuffer("""{"result": "custom"}"""),
traceData: null
));
}

// Implement a loader to resolve decision files
class FileLoader : ZenDecisionLoaderCallback
{
public Task<JsonBuffer?> Load(string key) =>
Task.FromResult<JsonBuffer?>(new JsonBuffer(File.ReadAllBytes(key)));
}

```

## Links

- [GitHub Repository](https://github.com/gorules/zen)
- [GoRules Documentation](https://docs.gorules.io/developers/sdks/csharp)
- [JDM Editor](https://editor.gorules.io)
Binary file added bindings/uniffi/nuget/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion bindings/uniffi/uniffi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ from_custom = "{}"
# C#
[bindings.csharp]
namespace = "GoRules.ZenEngine"
cdylib_name = "Libs/zen_uniffi"
cdylib_name = "zen_uniffi"
access_modifier = "public"

[bindings.csharp.custom_types.JsonBuffer]
into_custom = "new JsonBuffer({})"
Expand Down