swift-opa

0.0.2

Swift package for evaluating OPA IR Plans compiled from Rego policies
open-policy-agent/swift-opa

What's New

0.0.2

2026-03-16T21:37:42Z

This release contains bugfixes, performance improvements for the IR evaluator, and several new builtins!

Performance improvements

The IR evaluator is now more efficient when creating aggregate Rego values. (#101, authored by @koponen) In particular, ArrayAppend, ObjectInsert/ObjectInsertOnce, and SetAdd IR instructions run around 7-11% faster and allocate less memory in our benchmarks.

We also now use a new RegoNumber type internally which allows the interpreter to swap out the representations of numeric types for better performance. (#51, authored by @koponen) Previously we used NSNumber everywhere, and this had a noticeable negative performance impact for some benchmarks. Using the RegoNumber type, we have seen improvements across all benchmarks, ranging from 1-35% speedups. Most testcases involving numeric literals see an 18% or greater speedup.

walk builtin (#93)

The walk builtin is now supported in Swift OPA. walk transforms any Rego aggregate datatype into a list of [path, value] tuples. It is often used to work around cases where one might use recursion in other programming languages.

Here is an example that sums the leaf nodes on a nested object using walk:

policy.rego:

package walk_example

# Sum up all "var": <number> leaves in the tree
var_leaves contains val if {
	some path, val
	walk(input, [path, val])

	# The last element of the path must be the key "var"
	path[count(path) - 1] == "var"

	# Ensure the value is a number
	is_number(val)
}

total := sum(var_leaves)

input.json:

{
  "a": { "b": { "c": { "var": 2 } } },
  "d": { "e": { "var": 1 } },
  "f": { "var": 3 },
  "g": { "var": "foo" }
}

Results:

{
    "total": 6,
    "var_leaves": [
        1,
        2,
        3
    ]
}

Authored by @philipaconrad

json encoding builtins (#98)

Swift OPA has recently added support for the following json builtins:

These builtins make working with JSON data much more convenient.

Authored by @philipaconrad

time.add_date builtin (#117)

The time.add_date builtin returns the nanoseconds since the epoch after adding years, months, and days to a given nanoseconds timestamp.

Example policy:

package time_add_example

ts_ns_1980 := 315532800000000000                            # Tue Jan 01 00:00:00 1980 UTC
ts_ns_nov_12_1990 := time.add_date(ts_ns_1980, 10, 10, 11)  # Wed Nov 12 00:00:00 1990 UTC

Authored by @DFrenkel

Miscellaneous

  • AST/RegoValue+Codable: Change decoding order for strings. (#97) authored by @philipaconrad
  • deps: Bump swift-crypto to pick up newer APIs. (#108) authored by @philipaconrad
  • ComplianceTests: Override package name for local Swift OPA dep. (#111) authored by @philipaconrad
  • ComplianceTests: Add make command to generate new compliance tests (#90) authored by @sspaink
  • gh: Add PR template. (#96) authored by @philipaconrad
  • ci: Harden CI + Add zizmor static analysis for GH Actions (#99) authored by @philipaconrad
  • ci: Add dependabot.yml config for GH Actions and Go version bumps. (#94) authored by @philipaconrad
  • ci: add Linux test runs (#92) authored by @srenatus
  • ci: Fix missing checkout step for post-tag workflow. (#88) authored by @philipaconrad
  • ci: Fix issue in release detection script. (#87) authored by @philipaconrad

Swift-OPA

Swift 6.0.3+

Swift-OPA is a Swift package for evaluating OPA IR Plans compiled from Rego policies.

Rego is a declarative language for expressing policy over structured data. A common use of Rego is for defining authorization policy. Swift-OPA allows for in-process evaluation of compiled Rego within a Swift-based service or application.

Prerequisites

A Rego Policy

For our example application, we'll use this simple policy:

policy.rego

package policy.main

default is_valid := false

# METADATA
# entrypoint: true
is_valid := true if {
    input.favorite_fruit == "apple"
}

The entrypoint annotation allows us to define in policy where plan evaluation should begin. This serves both as documentation as well as it lets us skip providing the --entrypoint flag in the next step, where we build a plan bundle.

Building a Plan Bundle

In order to evaluate a plan bundle, we first have to build one! For that we'll use the opa build command with a target set to plan:

opa build --bundle --target plan my_bundle_directory

This will provide us a bundle.tar.gz file in the directory in which the command is executed, or optionally where the --output argument points to. Note that at this point, swift-opa can't parse .tar.gz files directly. For now, we'll have to first extract its contents into a directory:

tar -xvf bundle.tar.gz

Adding Swift-OPA as a Dependency

Package.swift

let package = Package(
    // required minimum versions for using swift-opa
    platforms: [
        .macOS(.v13),
        .iOS(.v16),
    ],
    // name, platforms, products, etc.
    dependencies: [
        .package(url: "https://github.com/open-policy-agent/swift-opa", branch: "main"),
        // other dependencies
    ],
    targets: [
        // or libraryTarget
        .executableTarget(name: "<target-name>", dependencies: [
            .product(name:"SwiftOPA", package: "swift-opa"),
            // other dependencies
        ]),
        // other targets
    ]
)

Usage

The main entry point for policy evaluation is the OPA.Engine. An engine can evaluate policies packaged in one or more Bundles. An OPA.Engine can be initialized with an on-disk bundle using its constructor: init(bundlePaths:). Using a simple main.swift file for the purpose of demonstration:

main.swift

import Rego
import Foundation

// Prepare does as much pre-processing as possible to get ready to evaluate queries.
// This only needs to be done once when loading the engine and after updating it.
func prepare() async throws -> OPA.Engine.PreparedQuery {
    let bundlePath = OPA.Engine.BundlePath(
        name: "policyBundle", url: URL(fileURLWithPath: "path/to/bundle"))

    var regoEngine = OPA.Engine(bundlePaths: [bundlePath])

    // Prepare query for the 'is_valid' rule in the 'policy.main' package
    return try await regoEngine.prepareForEvaluation(query: "data.policy.main.is_valid")
}

// This prepared query can be reused
let preparedQuery = try await prepare()

Policies often expect an input document to be passed in. This can be parsed from JSON data, for example:

import AST

// ...

let rawInput = #"{"favorite_fruit": "apple"}"#.data(using: .utf8)!
let inputDocument = try AST.RegoValue(jsonData: rawInput)

Evaluation is performed using the prepared query. We used data.policy.main.is_valid above, which makes sense given our policy source. Putting it all together, we can now evaluate our query and interpret the results, in this case just printing them:

let resultSet = try await preparedQuery.evaluate(input: inputDocument)

print(try resultSet.jsonString)

Complete Example

For the copy-paste inclined.

main.swift

import AST
import Rego
import Foundation

// Prepare does as much pre-processing as possible to get ready to evaluate queries.
// This only needs to be done once when loading the engine and after updating it.
func prepare() async throws -> OPA.Engine.PreparedQuery {
    let bundlePath = OPA.Engine.BundlePath(
        name: "policyBundle", url: URL(fileURLWithPath: "path/to/bundle"))

    var regoEngine = OPA.Engine(bundlePaths: [bundlePath])

    // Prepare query for the 'is_valid' rule in the 'policy.main' package
    return try await regoEngine.prepareForEvaluation(query: "data.policy.main.is_valid")
}

// This prepared query can be reused
let preparedQuery = try await prepare()

let rawInput = #"{"favorite_fruit": "apple"}"#.data(using: .utf8)!
let inputDocument = try AST.RegoValue(jsonData: rawInput)

let resultSet = try await preparedQuery.evaluate(input: inputDocument)

print(try resultSet.jsonString)

Capabilities and Builtins

  • Builtins are the standard and custom functions available to Rego (e.g. count, concat, etc.). The engine comes with the default OPA builtins enabled.
  • You can optionally supply an OPA capabilities.json (from an OPA release) upon the OPA.Engine init via capabilities: .path(...). During prepareForEvaluation, the engine checks that all builtins required by your compiled policies are present in the capabilities file (including matching signatures). If no capabilities are specified, validation is skipped and execution proceeds normally.
  • The engine also checks that every required builtin by the compiled policy is present in the Swift implementation, matched by its name (default or custom builtin). This check runs independently of capabilities validation. Signature correctness is enforced by OPA’s capabilities (if specified); Swift builtin closures validate arguments only at runtime.

Adding Custom Builtins

One can also register custom builtins when creating the OPA.Engine, in addition to the default OPA builtins provided by swift-opa. Conflicts with default builtin names are validated during prepareForEvaluation(query:). If capabilities are provided, the custom builtins are validated against the specified capabilities file.

import AST

let customBuiltins: [String: Builtin] = [
    "my.slugify": { ctx, args in
        // Example: expect a single string argument and return a slug
        guard args.count == 1, case let .string(s) = args[0] else {
            throw BuiltinError.argumentCountMismatch(got: args.count, want: 1)
        }
        let slug = s.lowercased().replacingOccurrences(of: " ", with: "-")
        return .string(slug)
    }
]

var engine = OPA.Engine(
    bundlePaths: [bundlePath],
    // To enable builtin validation, provide a `capabilities.json`. Without it, builtins are not checked against capabilities.
    // capabilities: .path(capabilitiesURL),
    customBuiltins: customBuiltins
)

// Throws if a custom builtin name conflicts with a default or a builtin required by the compiled policy is not present.
// If capabilities are specified, this throws if a capabilities validation error against the builtins occurs.
let preparedQuery = try await engine.prepareForEvaluation(query: "<some_query>")

Community Support

Feel free to open and issue if you encounter any problems using swift-opa, or have ideas on how to make it even better. We are also happy to answer more general questions in the #swift-opa channel of the OPA Slack.

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

Last updated: Sun Apr 12 2026 07:52:49 GMT-0900 (Hawaii-Aleutian Daylight Time)