diff --git a/.github/workflows/uniffi.yaml b/.github/workflows/uniffi.yaml index b4193941..8052c534 100644 --- a/.github/workflows/uniffi.yaml +++ b/.github/workflows/uniffi.yaml @@ -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: diff --git a/bindings/uniffi/.gitignore b/bindings/uniffi/.gitignore index 6eb8b6a7..dd20902a 100644 --- a/bindings/uniffi/.gitignore +++ b/bindings/uniffi/.gitignore @@ -1,3 +1,5 @@ .idea .gradle build/ +build-android/ +jniLibs/ \ No newline at end of file diff --git a/bindings/uniffi/lib/csharp/GoRules/ZenEngine/JsonBuffer.cs b/bindings/uniffi/lib/csharp/GoRules/ZenEngine/JsonBuffer.cs new file mode 100644 index 00000000..d63fc8d6 --- /dev/null +++ b/bindings/uniffi/lib/csharp/GoRules/ZenEngine/JsonBuffer.cs @@ -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); + } +} diff --git a/bindings/uniffi/nuget/GoRules.ZenEngine.csproj b/bindings/uniffi/nuget/GoRules.ZenEngine.csproj new file mode 100644 index 00000000..9698645c --- /dev/null +++ b/bindings/uniffi/nuget/GoRules.ZenEngine.csproj @@ -0,0 +1,66 @@ + + + + net8.0 + GoRules.ZenEngine + GoRules + GoRules + ZEN Engine - Business Rules Engine for .NET. Execute JSON Decision Models (JDM) with native performance via Rust FFI bindings. + MIT + https://github.com/gorules/zen + icon.png + https://github.com/gorules/zen + rules-engine;business-rules;decision-engine;jdm;gorules + enable + enable + true + true + $(NoWarn);CS1591 + README.md + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bindings/uniffi/nuget/README.md b/bindings/uniffi/nuget/README.md new file mode 100644 index 00000000..10c9a593 --- /dev/null +++ b/bindings/uniffi/nuget/README.md @@ -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 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 Load(string key) => + Task.FromResult(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) diff --git a/bindings/uniffi/nuget/icon.png b/bindings/uniffi/nuget/icon.png new file mode 100644 index 00000000..b3199c44 Binary files /dev/null and b/bindings/uniffi/nuget/icon.png differ diff --git a/bindings/uniffi/uniffi.toml b/bindings/uniffi/uniffi.toml index ed2a9997..c030c133 100644 --- a/bindings/uniffi/uniffi.toml +++ b/bindings/uniffi/uniffi.toml @@ -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({})"