regobrick

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 9, 2025 License: Apache-2.0 Imports: 8 Imported by: 0

README

RegoBrick

RegoBrick provides a straightforward way to parse and transform Rego modules without modifying the OPA engine. It applies certain transformations based on special import markers (for example, import data.regobrick.default_false) and also offers convenient helpers for custom builtins and Go↔Rego value conversion.

Overview

  • Default False
    If your Rego module imports data.regobrick.default_false, RegoBrick will automatically insert a default rule that evaluates to false for any “if” or boolean rules. This helps ensure you don’t forget to explicitly set them to false when not satisfied.

  • Custom Builtins
    Easily register builtins with typed arguments and return values. RegoBrick converts Rego AST terms to Go types and back, so you can write builtins in Go with minimal boilerplate.

  • Rego ↔ Go Conversion
    The convert package allows you to map Rego types (e.g. strings, numbers, arrays, objects) to typed Go structs, decimals, time.Time, etc., and vice versa.

  • Parse & Transform
    The ParseModule function (and other internal logic) reads a Rego module, looks for any RegoBrick import markers, and applies the corresponding AST transformations. A higher-level function, regobrick.New, provides a convenient way to load multiple modules, apply transforms, and inject them into OPA.

Installation

go get github.com/sky1core/regobrick

Make sure you also have OPA in your go.mod if you plan to work with the Rego engine.

Usage

Below is an example of how to use RegoBrick with decimal input data.
We create a decimal value from a string to avoid floating-point precision issues, then pass it to RegoBrick as input.

By including import data.regobrick.default_false in your policy, RegoBrick automatically inserts a default rule (for example, default allow = false), ensuring that if the condition isn’t met, the rule defaults to false.

package main

import (
    "context"
    "fmt"

    "github.com/shopspring/decimal"
    "github.com/open-policy-agent/opa/v1/rego"
    "github.com/sky1core/regobrick"
)

func main() {
    ctx := context.Background()

    // Example policy for the "sub" package
    subPolicy := `
        package sub

        some_rule {
            input.amount == 123.45
        }
    `
    // Example policy for the "main" package
    mainPolicy := `
        package example

        import data.regobrick.default_false

        allow if {
            input.user == "admin"
        }
    `

    // Build a rego.Rego object with your modules and input.
    query, err := rego.New(
        // Add Rego modules (which will apply "default_false" if that import is found):
        regobrick.Module("sub.rego", subPolicy, []string{"data.some.pkg"}),
        regobrick.Module("main.rego", mainPolicy, []string{"data.mycompany.util"}),

        // Specify the query we want to evaluate:
        rego.Query("data.example.allow"),
		
    ).PrepareForEval(ctx)

    if err != nil {
        panic(err)
    }

    // Create a decimal value from string to avoid floating-point issues.
    rawDec, err := decimal.NewFromString("123.45")
    if err != nil {
        panic(err)
    }

    // Convert the decimal to a RegoDecimal so it’s handled as a numeric literal.
    amount := regobrick.NewRegoDecimal(rawDec)

    // Build the input map, including our RegoDecimal.
    input := map[string]interface{}{
        "user":   "admin",
        "amount": amount,
    }

    // Evaluate using rego.EvalInput to pass input.
    rs, err := query.Eval(ctx, rego.EvalInput(input))
    if err != nil {
        panic(err)
    }

    // The result of 'data.example.allow' is in rs.
    // Because 'allow if ...' is accompanied by 'default allow = false',
    // if the condition is not met, it defaults to false.
    fmt.Println("Result:", rs)
}

Writing Custom Builtins

You can register a custom function that OPA calls within your policies. RegoBrick provides helper functions (like RegisterBuiltin1, RegisterBuiltin2, etc.) for builtins that accept typed Go arguments and return typed Go values.

package main

import (
    "context"
    "github.com/open-policy-agent/opa/v1/rego"
    "github.com/sky1core/regobrick"
)

// Example builtin that checks if a user is "admin"
func isAdmin(ctx rego.BuiltinContext, user string) (bool, error) {
    return user == "admin", nil
}

func main() {
    // RegisterBuiltin1[T1, R] has this signature:
    //   func RegisterBuiltin1[T1 any, R any](
    //       name string,
    //       fn func(rego.BuiltinContext, T1) (R, error),
    //       opts ...BuiltinRegisterOption,
    //   )
    //
    // So we pass:
    //   1) The builtin name ("is_admin")
    //   2) Our Go function (isAdmin)
    //   3) Any number of BuiltinRegisterOption values, such as categories or nondeterminism.

    regobrick.RegisterBuiltin1[string, bool](
        "is_admin",
        isAdmin,
        // We can set the categories (used in FilterCapabilities) or nondeterministic flag, etc.
        regobrick.WithCategories("my_custom_category"),
        // regobrick.WithNondeterministic() // If your builtin is nondeterministic
    )

    // Then in Rego, you can write:
    //    is_admin(input.user) => returns true if user == "admin"

    // ...
}

RegoBrick automatically converts the Rego argument to a Go string and converts the returned bool back to a Rego boolean. For more complex use cases, you can define builtins with multiple arguments, different Go types (e.g., []string, custom structs), and so on.

Filtering Builtins with FilterCapabilities

If you want to restrict which builtins are allowed when evaluating a policy, you can use the FilterCapabilities function to include or exclude builtins by name and category. This gives you a way to lock down OPA so that only certain operations are permitted.

package main

import (
    "context"
    "fmt"

    "github.com/open-policy-agent/opa/v1/rego"
    "github.com/sky1core/regobrick"
)

func main() {
    // Suppose you want to allow only a small subset of builtins:
    // by specific names or categories.
    allowedNames := []string{"is_admin", "concat"}          // e.g., our custom builtin, plus a standard OPA builtin
    allowedCats := []string{"my_custom_category", "strings"} // categories to allow

    // FilterCapabilities returns an *ast.Capabilities object
    // that includes only the builtins matching the allowed names/infixes/categories.
    caps := regobrick.FilterCapabilities(allowedNames, allowedCats)

    // Now you can build a Rego query with these restricted capabilities:
    ctx := context.Background()
    query, err := rego.New(
        rego.Query("data.example.allow"),
        rego.Capabilities(caps),
        // Possibly other Rego options...
    ).PrepareForEval(ctx)
    if err != nil {
        panic(err)
    }

    // Evaluate the query as usual:
    rs, err := query.Eval(ctx)
    if err != nil {
        panic(err)
    }

    fmt.Println("Query result:", rs)
}

In the snippet above, builtins that do not appear in either allowedNames or allowedCats (and are not in the coreInfixes set) will be excluded from the engine’s capabilities, resulting in errors if a policy tries to use them.

Documentation

Overview

Package regobrick provides high-level functions for applying RegoBrick transformations and adding modules to OPA rego.Rego objects.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FilterCapabilities added in v0.3.0

func FilterCapabilities(allowedNames []string, allowedCats []string) *ast.Capabilities

FilterCapabilities filters OPA built-in functions based on a list of allowed names and categories, along with built-ins whose infix is in coreInfixes.

  • If a built-in's infix is one of the core infixes, it is kept.
  • Otherwise, if the built-in name is in allowedNames or any of its categories are in allowedCats, it is kept.
  • If a built-in has custom category mappings, those are also checked against allowedCats.

The resulting capabilities object contains only the filtered built-ins.

func Module

func Module(filename, src string, imports []string) func(*rego.Rego)

Module returns a rego.Rego option that adds a single Rego module from the given filename, source, and optional imports. If you need to handle parse errors directly, parse the module yourself (for example, with regobrick.ParseModule or ast.ParseModule) and then pass the *ast.Module to rego.ParsedModule(...) in your own Rego configuration.

func Modules added in v0.5.0

func Modules(opts ...ModuleOption) func(*rego.Rego)

Modules returns a rego.Rego option that adds multiple Rego modules in one call, each specified via a ModuleOption.

func ParseModule

func ParseModule(filename, src string, imports []string) (*ast.Module, error)

ParseModule parses a Rego source file into an AST module, optionally appending additional imports. If the module includes "import data.regobrick.default_false", it applies the default_false transform.

func RegisterBuiltin0 added in v0.2.0

func RegisterBuiltin0[R any](name string, fn func(rego.BuiltinContext) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin0

func RegisterBuiltin0_ added in v0.2.0

func RegisterBuiltin0_(name string, fn func(rego.BuiltinContext) error, opts ...BuiltinRegisterOption)

RegisterBuiltin0_ has no arguments, returns error => null

func RegisterBuiltin1 added in v0.2.0

func RegisterBuiltin1[T1 any, R any](name string, fn func(rego.BuiltinContext, T1) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin1

func RegisterBuiltin1_ added in v0.2.0

func RegisterBuiltin1_[T1 any](name string, fn func(rego.BuiltinContext, T1) error, opts ...BuiltinRegisterOption)

RegisterBuiltin1_ has 1 argument, returns error => null

func RegisterBuiltin2 added in v0.2.0

func RegisterBuiltin2[T1 any, T2 any, R any](name string, fn func(rego.BuiltinContext, T1, T2) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin2

func RegisterBuiltin2_ added in v0.2.0

func RegisterBuiltin2_[T1 any, T2 any](name string, fn func(rego.BuiltinContext, T1, T2) error, opts ...BuiltinRegisterOption)

RegisterBuiltin2_

func RegisterBuiltin3 added in v0.2.0

func RegisterBuiltin3[T1 any, T2 any, T3 any, R any](name string, fn func(rego.BuiltinContext, T1, T2, T3) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin3

func RegisterBuiltin3_ added in v0.2.0

func RegisterBuiltin3_[T1 any, T2 any, T3 any](name string, fn func(rego.BuiltinContext, T1, T2, T3) error, opts ...BuiltinRegisterOption)

RegisterBuiltin3_

func RegisterBuiltin4 added in v0.2.0

func RegisterBuiltin4[T1 any, T2 any, T3 any, T4 any, R any](name string, fn func(rego.BuiltinContext, T1, T2, T3, T4) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin4

func RegisterBuiltin4_ added in v0.2.0

func RegisterBuiltin4_[T1 any, T2 any, T3 any, T4 any](name string, fn func(rego.BuiltinContext, T1, T2, T3, T4) error, opts ...BuiltinRegisterOption)

RegisterBuiltin4_

func RegisterBuiltin5 added in v0.2.0

func RegisterBuiltin5[T1 any, T2 any, T3 any, T4 any, T5 any, R any](name string, fn func(rego.BuiltinContext, T1, T2, T3, T4, T5) (R, error), opts ...BuiltinRegisterOption)

RegisterBuiltin5

func RegisterBuiltin5_ added in v0.2.0

func RegisterBuiltin5_[T1 any, T2 any, T3 any, T4 any, T5 any](name string, fn func(rego.BuiltinContext, T1, T2, T3, T4, T5) error, opts ...BuiltinRegisterOption)

RegisterBuiltin5_

Types

type BuiltinRegisterOption added in v0.4.0

type BuiltinRegisterOption func(*builtinRegisterConfig)

func WithCategories added in v0.4.0

func WithCategories(cats ...string) BuiltinRegisterOption

func WithDefaultDecimal added in v0.4.0

func WithDefaultDecimal() BuiltinRegisterOption

When set, RegoToGo will convert ast.Number to decimal.Decimal by default.

func WithNondeterministic added in v0.4.0

func WithNondeterministic() BuiltinRegisterOption

type ModuleOption added in v0.5.0

type ModuleOption = module.ModuleOption

ModuleOption is an alias for module.ModuleOption, used for specifying Rego module parameters or configuration.

type RegoDecimal added in v0.6.0

type RegoDecimal = types.RegoDecimal

RegoDecimal is an alias for types.RegoDecimal. It represents a numeric value that serializes to JSON as a numeric literal (e.g., 123.456) rather than a string (e.g., "123.456").

func NewRegoDecimal added in v0.6.0

func NewRegoDecimal(d decimal.Decimal) RegoDecimal

NewRegoDecimal creates a RegoDecimal from an existing decimal.Decimal value. The resulting RegoDecimal retains the same precision and scale.

func NewRegoDecimalFromInt added in v0.6.0

func NewRegoDecimalFromInt(i int64) RegoDecimal

NewRegoDecimalFromInt creates a RegoDecimal from an int64. This is a shortcut for decimal.NewFromInt(...) wrapped in a RegoDecimal.

type RegoSet added in v0.6.0

type RegoSet[T comparable] = types.RegoSet[T]

RegoSet is an alias for types.RegoSet, representing a generic set of comparable elements. It is serialized as a JSON array, removing duplicates on unmarshal.

Directories

Path Synopsis
internal
module
Package module provides utilities for parsing and transforming Rego modules, particularly for detecting and applying regobrick features like "default_false".
Package module provides utilities for parsing and transforming Rego modules, particularly for detecting and applying regobrick features like "default_false".
types
Package types provides wrappers around shopspring/decimal.
Package types provides wrappers around shopspring/decimal.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL