cel2squirrel

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2025 License: MIT Imports: 6 Imported by: 0

README

cel2squirrel

Go Reference Go Report Card License

A Go package that converts Common Expression Language (CEL) expressions to SQL WHERE clauses using the Squirrel SQL builder.

Overview

cel2squirrel enables filtering using CEL expressions (as specified in AIP-160) and converts them to SQL queries that can be executed against a database. It validates that expressions are boolean and provides type-safe conversion to Squirrel SQL builder objects.

Features

  • CEL Expression Parsing: Parse and validate CEL expressions
  • Boolean Validation: Ensure expressions return boolean values
  • Type-Safe Conversion: Convert CEL AST to Squirrel SQL builder
  • Field Mapping: Map CEL field names to SQL column names
  • Operator Support:
    • Comparison: ==, !=, <, <=, >, >=
    • Logical: && (AND), || (OR), ! (NOT)
    • String: contains(), startsWith(), endsWith()
    • Membership: in operator
    • Null: == null, != null
  • PostgreSQL Compatible: Works with PostgreSQL placeholder format ($1, $2, etc.)
  • 🔒 Security Hardened: SQL injection protection, DoS prevention, field-level authorization

Installation

go get zntr.io/cel2squirrel

Usage

Basic Example
package main

import (
    "fmt"
    "log"
    
    "github.com/Masterminds/squirrel"
    "github.com/google/cel-go/cel"
    "zntr.io/cel2squirrel"
)

func main() {
    // Define field declarations (CEL variables and their types)
    config := cel2squirrel.Config{
        FieldDeclarations: map[string]*cel.Type{
            "status": cel.StringType,
            "age":    cel.IntType,
            "rating": cel.DoubleType,
        },
    }
    
    // Create converter
    converter, err := cel2squirrel.NewConverter(config)
    if err != nil {
        log.Fatal(err)
    }
    
    // Convert CEL expression to SQL
    celExpr := `status == "published" && age >= 18`
    result, err := converter.Convert(celExpr, nil)
    if err != nil {
        log.Fatal(err)
    }
    
    // Build complete query with Squirrel
    query := squirrel.Select("*").
        From("users").
        Where(result.Where)
    
    sql, args, _ := query.ToSql()
    fmt.Println(sql)
    // Output: SELECT * FROM users WHERE (status = ? AND age >= ?)
    fmt.Println(args)
    // Output: [published 18]
}
With Field Mappings

Map CEL field names to different SQL column names:

config := cel2squirrel.Config{
    FieldDeclarations: map[string]*cel.Type{
        "isDraft": cel.BoolType,
        "ownerId": cel.StringType,
    },
}

converter, _ := cel2squirrel.NewConverter(config)

// Map camelCase CEL fields to snake_case SQL columns
fieldMappings := map[string]string{
    "isDraft": "is_draft",
    "ownerId": "owner_id",
}

celExpr := `isDraft == false && ownerId == "user123"`
result, _ := converter.Convert(celExpr, fieldMappings)

sql, args, _ := result.Where.ToSql()
// SQL: (is_draft = ? AND owner_id = ?)
// Args: [false user123]
PostgreSQL Placeholders

Use PostgreSQL-style numbered placeholders:

query := squirrel.Select("*").
    From("users").
    Where(result.Where).
    PlaceholderFormat(squirrel.Dollar)

sql, args, _ := query.ToSql()
// SQL: SELECT * FROM users WHERE (status = $1 AND age >= $2)
Complex Expressions

Handle complex nested boolean expressions:

celExpr := `(status == "published" || status == "featured") && age >= 18 && rating > 4.0`
result, _ := converter.Convert(celExpr, nil)

query := squirrel.Select("id", "name", "rating").
    From("users").
    Where(result.Where).
    OrderBy("rating DESC").
    Limit(10)

sql, args, _ := query.ToSql()
// SQL: SELECT id, name, rating FROM users 
//      WHERE ((status = ? OR status = ?) AND age >= ? AND rating > ?) 
//      ORDER BY rating DESC LIMIT 10
// Args: [published featured 18 4.0]
String Operations

Use CEL string methods:

// Contains
celExpr := `name.contains("john")`
// SQL: name LIKE ?
// Args: [%john%]

// Starts with
celExpr := `name.startsWith("Dr. ")`
// SQL: name LIKE ?
// Args: [Dr. %]

// Ends with
celExpr := `email.endsWith("@example.com")`
// SQL: email LIKE ?
// Args: [%@example.com]
Null Comparisons

Handle NULL values:

// IS NULL
celExpr := `deletedAt == null`
// SQL: deletedAt IS NULL

// IS NOT NULL
celExpr := `deletedAt != null`
// SQL: deletedAt IS NOT NULL
IN Operator

Filter with multiple values:

celExpr := `status in ["published", "featured", "archived"]`
result, _ := converter.Convert(celExpr, nil)

sql, args, _ := result.Where.ToSql()
// SQL: status IN (?,?,?)
// Args: [published featured archived]

Real-World Example

Example implementation of a database repository with CEL filtering (AIP-160 compliant):

package repository

import (
    "context"
    "fmt"
    
    "github.com/Masterminds/squirrel"
    "github.com/google/cel-go/cel"
    "zntr.io/cel2squirrel"
)

type UserRepository struct {
    db        *sql.DB
    converter *cel2squirrel.Converter
}

func NewUserRepository(db *sql.DB) (*UserRepository, error) {
    // Setup CEL converter
    config := cel2squirrel.Config{
        FieldDeclarations: map[string]*cel.Type{
            "status":     cel.StringType,
            "isActive":   cel.BoolType,
            "rating":     cel.DoubleType,
            "createTime": cel.TimestampType,
        },
    }
    
    converter, err := cel2squirrel.NewConverter(config)
    if err != nil {
        return nil, fmt.Errorf("failed to create CEL converter: %w", err)
    }
    
    return &UserRepository{
        db:        db,
        converter: converter,
    }, nil
}

func (r *UserRepository) Search(ctx context.Context, filter string, pageSize int) ([]User, error) {
    // Map CEL fields to SQL columns
    fieldMappings := map[string]string{
        "isActive":   "is_active",
        "createTime": "create_time",
    }
    
    // Convert CEL filter to SQL
    result, err := r.converter.Convert(filter, fieldMappings)
    if err != nil {
        return nil, fmt.Errorf("invalid filter expression: %w", err)
    }
    
    // Build query with Squirrel
    query := squirrel.Select("*").
        From("users").
        Where(result.Where).
        PlaceholderFormat(squirrel.Dollar).
        Limit(uint64(pageSize))
    
    sql, args, err := query.ToSql()
    if err != nil {
        return nil, err
    }
    
    // Execute query
    rows, err := r.db.QueryContext(ctx, sql, args...)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    // Scan results...
    var users []User
    for rows.Next() {
        var u User
        // Scan into user struct...
        users = append(users, u)
    }
    
    return users, rows.Err()
}
API Usage Example
GET /v1/users?filter=status == "active" && rating > 4.0&page_size=20

Supported CEL Operations

Comparison Operators
CEL Operator SQL Equivalent Example
== = status == "published"
!= <> or IS NOT status != "draft"
< < age < 18
<= <= age <= 21
> > age > 65
>= >= rating >= 4.5
Logical Operators
CEL Operator SQL Equivalent Example
&& AND status == "published" && age >= 18
|| OR status == "draft" || status == "published"
! NOT !(isDraft)
String Operations
CEL Function SQL Equivalent Example
contains(x) LIKE '%x%' label.contains("test")
startsWith(x) LIKE 'x%' label.startsWith("prod")
endsWith(x) LIKE '%x' label.endsWith("v2")
Membership Operators
CEL Operator SQL Equivalent Example
in IN (...) status in ["published", "featured"]
Null Comparisons
CEL Expression SQL Equivalent Example
field == null IS NULL deletedAt == null
field != null IS NOT NULL deletedAt != null

Error Handling

The converter validates expressions and returns descriptive errors:

// Non-boolean expression
celExpr := `age + 5`  // Returns: "CEL expression must return a boolean, got int"

// Undefined field
celExpr := `unknownField == "value"`  // Returns: "failed to compile CEL expression: undeclared reference..."

// Syntax error
celExpr := `status == `  // Returns: "failed to compile CEL expression: Syntax error..."

Type Declarations

Use CEL types directly to define field types:

import "github.com/google/cel-go/cel"

FieldDeclarations: map[string]*cel.Type{
    "stringField":    cel.StringType,
    "intField":       cel.IntType,
    "doubleField":    cel.DoubleType,
    "boolField":      cel.BoolType,
    "uintField":      cel.UintType,
    "timestampField": cel.TimestampType,
    "durationField":  cel.DurationType,
    "bytesField":     cel.BytesType,
    
    // Complex types
    "stringList":     cel.ListType(cel.StringType),
    "stringMap":      cel.MapType(cel.StringType, cel.StringType),
}

Limitations

  • No Function Calls: Custom CEL functions are not supported (only built-in string methods)
  • Simple Expressions: Complex nested member access is limited
  • List Literals Only: The in operator requires constant list literals
  • No Arithmetic in Filters: Expressions like age + 5 > 30 are not supported

Performance Considerations

  • Prepared Statements: The generated SQL is parameterized and suitable for prepared statements
  • Validation: CEL expressions are validated before conversion, preventing SQL injection
  • Caching: Consider caching converter instances for frequently used field declarations

Security

Overview

The cel2squirrel package implements multiple layers of security to protect against common vulnerabilities when converting user-supplied filter expressions to SQL queries. All security features are enabled by default with secure defaults.

SQL Injection Protection

LIKE Pattern Escaping: The package automatically escapes SQL special characters (%, _, \, [, ]) in string operations to prevent LIKE pattern injection:

// User input: name.contains("%")
// Unsafe: LIKE '%%%'  (matches all records!)
// Safe:   LIKE '%\%%' (matches literal % character)

celExpr := `name.contains("%admin")`
result, _ := converter.Convert(celExpr)
// Generates: name LIKE ?
// Args: [%\%admin%]  // Special chars properly escaped

Parameterized Queries: All values are passed as query parameters, never concatenated into SQL strings:

celExpr := `status == "published" && age > 18`
// Generates parameterized SQL: status = ? AND age > ?
// Args: ["published", 18]
Denial of Service Prevention

Configure expression complexity limits to prevent resource exhaustion:

config := cel2squirrel.Config{
    FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
        "status": {Type: cel.StringType, Column: "status"},
    },

    // Security limits (default values shown)
    MaxExpressionLength: 10000,  // Max 10KB expression
    MaxExpressionDepth:  50,     // Max 50 levels of nesting
    MaxInClauseSize:     1000,   // Max 1000 values in IN clause
}

converter, _ := cel2squirrel.NewConverter(config)

// These will be rejected:
_, err := converter.Convert(strings.Repeat("a", 20000))  // Too long
_, err := converter.Convert(deeplyNestedExpression)       // Too deep
_, err := converter.Convert(`status in [...]`)            // Too many values
Field-Level Authorization

Restrict which fields users can filter by based on their roles:

config := cel2squirrel.Config{
    FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
        "status":   {Type: cel.StringType, Column: "status"},
        "owner_id": {Type: cel.StringType, Column: "owner_id"},
        "salary":   {Type: cel.IntType, Column: "salary"},
    },

    // Public fields accessible to all users
    PublicFields: []string{"status"},

    // Role-based access control
    FieldACL: map[string][]string{
        "owner_id": {"admin", "manager"},
        "salary":   {"admin", "hr"},
    },
}

converter, _ := cel2squirrel.NewConverter(config)

// Check authorization before conversion
userRoles := []string{"user"}
_, err := converter.ConvertWithAuth(`owner_id == "user123"`, userRoles)
// Returns: "access denied: insufficient permissions"

adminRoles := []string{"admin"}
result, _ := converter.ConvertWithAuth(`owner_id == "user123"`, adminRoles)
// Success: admin can filter by owner_id
Error Message Sanitization

The package sanitizes error messages to prevent information disclosure:

// Internal error with details (logged server-side)
convErr := err.(*cel2squirrel.ConversionError)
log.Printf("Conversion failed: %v", convErr.InternalError)
// Logs: "undeclared reference to 'secretField'"

// Public error message (returned to user)
fmt.Println(convErr.Error())
// Returns: "invalid filter expression syntax"
// Does NOT reveal field names or internal structure
Runtime Type Validation

Defense-in-depth type checking validates that values match declared field types:

config := cel2squirrel.Config{
    FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
        "age": {Type: cel.IntType, Column: "age"},
    },
}

converter, _ := cel2squirrel.NewConverter(config)

// CEL's type system catches this at compile time
_, err := converter.Convert(`age == "not a number"`)
// Returns type mismatch error

// Runtime validation provides additional protection
Secure Configuration Example

A production-ready secure configuration:

func NewSecureConverter() (*cel2squirrel.Converter, error) {
    config := cel2squirrel.DefaultConfig() // Start with secure defaults

    config.FieldDeclarations = map[string]cel2squirrel.ColumnMapping{
        "status":     {Type: cel.StringType, Column: "status"},
        "created_at": {Type: cel.TimestampType, Column: "created_at"},
        "owner_id":   {Type: cel.StringType, Column: "owner_id"},
        "is_private": {Type: cel.BoolType, Column: "is_private"},
    }

    // Define public fields
    config.PublicFields = []string{"status", "created_at"}

    // Restrict sensitive fields
    config.FieldACL = map[string][]string{
        "owner_id":   {"admin", "manager"},
        "is_private": {"admin"},
    }

    // Adjust limits for your use case
    config.MaxExpressionLength = 1000  // Shorter limit for API
    config.MaxExpressionDepth = 10     // Prevent deeply nested attacks
    config.MaxInClauseSize = 100       // Reasonable batch size

    return cel2squirrel.NewConverter(config)
}

// Usage in HTTP handler
func (h *Handler) SearchRecords(w http.ResponseWriter, r *http.Request) {
    filter := r.URL.Query().Get("filter")

    // Get user roles from authentication context
    userRoles := h.getUserRoles(r.Context())

    // Convert with authorization check
    result, err := h.converter.ConvertWithAuth(filter, userRoles)
    if err != nil {
        if convErr, ok := err.(*cel2squirrel.ConversionError); ok {
            // Return sanitized error to user
            http.Error(w, convErr.PublicMessage, http.StatusBadRequest)

            // Log detailed error server-side
            log.Printf("Filter conversion failed: %v (code=%s)",
                convErr.InternalError, convErr.ErrorCode)
            return
        }
        http.Error(w, "Invalid filter", http.StatusBadRequest)
        return
    }

    // Build and execute query...
}
Security Best Practices
  1. Always use field-level authorization for multi-tenant or multi-user systems
  2. Log security events to detect attack patterns
  3. Set conservative limits for expression complexity based on your use case
  4. Never expose internal errors to end users
  5. Use ConvertWithAuth() instead of Convert() when authorization is configured
  6. Validate user roles from trusted authentication context, never from user input
  7. Monitor for unusual patterns like deeply nested expressions or large IN clauses
  8. Test security controls regularly with security-focused test cases
  9. Keep dependencies updated (CEL, Squirrel) for security patches

Testing

Run tests:

go test -v ./...

Run with coverage:

go test -cover ./...

Run fuzz tests:

go test -fuzz=Fuzz -fuzztime=30s

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Development Setup
  1. Clone the repository
  2. Install dependencies: go mod download
  3. Run tests: go test ./...
  4. Run linter: go vet ./...
Guidelines
  • Write tests for new features
  • Follow Go best practices and idioms
  • Update documentation for API changes
  • Ensure all tests pass before submitting PR

Use Cases

This library is ideal for:

  • REST APIs: Implement AIP-160 compliant filtering in your API endpoints
  • Multi-tenant Applications: Safe, field-level authorization for database queries
  • Admin Dashboards: Flexible search and filtering capabilities
  • Data Export Tools: User-defined filtering without SQL injection risks
  • GraphQL Backends: Convert filter arguments to SQL queries

References

License

MIT License - see LICENSE file for details.

Support

Acknowledgments

Built with:

Documentation

Overview

Package cel2squirrel provides a converter for transforming Common Expression Language (CEL) expressions into SQL WHERE clauses using the Squirrel SQL builder.

Example

Example demonstrates basic usage of the CEL to SQL converter

package main

import (
	"fmt"
	"log"

	"github.com/Masterminds/squirrel"
	"github.com/google/cel-go/cel"
	"zntr.io/cel2squirrel"
)

func main() {
	// Define field declarations (CEL variables and their types)
	config := cel2squirrel.Config{
		FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
			"status": {Type: cel.StringType, Column: "status"},
			"age":    {Type: cel.IntType, Column: "age"},
		},
	}

	// Create converter
	converter, err := cel2squirrel.NewConverter(config)
	if err != nil {
		log.Fatal(err)
	}

	// Convert CEL expression to SQL
	celExpr := `status == "published" && age >= 18`
	result, err := converter.Convert(celExpr)
	if err != nil {
		log.Fatal(err)
	}

	// Build complete query with Squirrel
	query := squirrel.Select("*").
		From("prompts").
		Where(result.Where)

	sql, args, err := query.ToSql()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(sql)
	fmt.Printf("Args: %v\n", args)
}
Output:

SELECT * FROM prompts WHERE (status = ? AND age >= ?)
Args: [published 18]
Example (ComplexExpression)

Example_complexExpression demonstrates nested boolean logic

package main

import (
	"fmt"
	"log"

	"github.com/Masterminds/squirrel"
	"github.com/google/cel-go/cel"
	"zntr.io/cel2squirrel"
)

func main() {
	config := cel2squirrel.Config{
		FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
			"status": {Type: cel.StringType, Column: "status"},
			"age":    {Type: cel.IntType, Column: "age"},
			"rating": {Type: cel.DoubleType, Column: "rating"},
		},
	}

	converter, err := cel2squirrel.NewConverter(config)
	if err != nil {
		log.Fatal(err)
	}

	celExpr := `(status == "published" || status == "featured") && age >= 18 && rating > 4.0`
	result, err := converter.Convert(celExpr)
	if err != nil {
		log.Fatal(err)
	}

	query := squirrel.Select("id", "label", "rating").
		From("prompts").
		Where(result.Where).
		OrderBy("rating DESC").
		Limit(10)

	sql, args, _ := query.ToSql()
	fmt.Println(sql)
	fmt.Printf("Args: %v\n", args)
}
Output:

SELECT id, label, rating FROM prompts WHERE (((status = ? OR status = ?) AND age >= ?) AND rating > ?) ORDER BY rating DESC LIMIT 10
Args: [published featured 18 4]
Example (FieldMappings)

Example_fieldMappings demonstrates mapping CEL field names to SQL column names

package main

import (
	"fmt"
	"log"

	"github.com/google/cel-go/cel"
	"zntr.io/cel2squirrel"
)

func main() {
	config := cel2squirrel.Config{
		FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
			"isDraft": {Type: cel.BoolType, Column: "is_draft"},
			"ownerId": {Type: cel.StringType, Column: "owner_id"},
		},
	}

	converter, err := cel2squirrel.NewConverter(config)
	if err != nil {
		log.Fatal(err)
	}

	// Column mappings are now part of the config
	celExpr := `isDraft == false && ownerId == "user123"`
	result, err := converter.Convert(celExpr)
	if err != nil {
		log.Fatal(err)
	}

	sql, args, _ := result.Where.ToSql()
	fmt.Println(sql)
	fmt.Printf("Args: %v\n", args)
}
Output:

(is_draft = ? AND owner_id = ?)
Args: [false user123]
Example (PostgreSQL)

Example_postgreSQL demonstrates using PostgreSQL-style placeholders

package main

import (
	"fmt"
	"log"

	"github.com/Masterminds/squirrel"
	"github.com/google/cel-go/cel"
	"zntr.io/cel2squirrel"
)

func main() {
	config := cel2squirrel.Config{
		FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
			"status": {Type: cel.StringType, Column: "status"},
			"age":    {Type: cel.IntType, Column: "age"},
		},
	}

	converter, err := cel2squirrel.NewConverter(config)
	if err != nil {
		log.Fatal(err)
	}

	celExpr := `status == "published" && age >= 18`
	result, err := converter.Convert(celExpr)
	if err != nil {
		log.Fatal(err)
	}

	// Use PostgreSQL placeholder format
	query := squirrel.Select("*").
		From("prompts").
		Where(result.Where).
		PlaceholderFormat(squirrel.Dollar)

	sql, args, _ := query.ToSql()
	fmt.Println(sql)
	fmt.Printf("Args: %v\n", args)
}
Output:

SELECT * FROM prompts WHERE (status = $1 AND age >= $2)
Args: [published 18]
Example (StringOperations)

Example_stringOperations demonstrates CEL string methods

package main

import (
	"fmt"
	"log"

	"github.com/google/cel-go/cel"
	"zntr.io/cel2squirrel"
)

func main() {
	config := cel2squirrel.Config{
		FieldDeclarations: map[string]cel2squirrel.ColumnMapping{
			"label": {Type: cel.StringType, Column: "label"},
		},
	}

	converter, err := cel2squirrel.NewConverter(config)
	if err != nil {
		log.Fatal(err)
	}

	// Contains
	result, err := converter.Convert(`label.contains("gpt")`)
	if err != nil {
		log.Fatal(err)
	}
	sql, args, _ := result.Where.ToSql()
	fmt.Println("Contains:", sql)
	fmt.Printf("Args: %v\n", args)

	// Starts with
	result, err = converter.Convert(`label.startsWith("prod-")`)
	if err != nil {
		log.Fatal(err)
	}
	sql, args, _ = result.Where.ToSql()
	fmt.Println("Starts with:", sql)
	fmt.Printf("Args: %v\n", args)

	// Ends with
	result, err = converter.Convert(`label.endsWith("-v2")`)
	if err != nil {
		log.Fatal(err)
	}
	sql, args, _ = result.Where.ToSql()
	fmt.Println("Ends with:", sql)
	fmt.Printf("Args: %v\n", args)

}
Output:

Contains: label LIKE ?
Args: [%gpt%]
Starts with: label LIKE ?
Args: [prod-%]
Ends with: label LIKE ?
Args: [%-v2]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func QuoteIdentifier

func QuoteIdentifier(name string) string

QuoteIdentifier quotes a SQL identifier to prevent SQL injection.

Types

type ColumnMapping

type ColumnMapping struct {
	// Type is the type of the CEL field.
	Type *cel.Type
	// Column is the name of the SQL column.
	Column string
}

ColumnMapping is a mapping of a CEL field name to a SQL column name.

type Config

type Config struct {
	// FieldDeclarations maps CEL variable names to their types and SQL columns.
	// Example: map[string]ColumnMapping{
	//   "age": {Type: cel.IntType, Column: "user_age"},
	//   "name": {Type: cel.StringType, Column: "user_name"},
	//   "tags": {Type: cel.ListType(cel.StringType), Column: "user_tags"},
	// }
	FieldDeclarations map[string]ColumnMapping

	// Security limits to prevent DoS attacks
	// MaxExpressionLength is the maximum allowed length of a CEL expression in characters.
	// Default: 10000. Set to 0 to apply default.
	MaxExpressionLength int

	// MaxExpressionDepth is the maximum nesting depth of boolean operators.
	// Default: 50. Set to 0 to apply default.
	MaxExpressionDepth int

	// MaxInClauseSize is the maximum number of values allowed in an IN clause.
	// Default: 1000. Set to 0 to apply default.
	MaxInClauseSize int

	// Authorization settings for field-level access control
	// PublicFields is a list of field names that any user can filter by.
	// If empty, authorization checks are disabled.
	PublicFields []string

	// FieldACL maps field names to lists of roles that can access them.
	// Only checked if PublicFields is not empty.
	FieldACL map[string][]string
}

Config contains configuration for the CEL to SQL converter.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a Config with secure default values.

type ConversionError

type ConversionError struct {
	// PublicMessage is a sanitized error message safe to return to end users.
	PublicMessage string
	// InternalError contains the detailed error for internal logging.
	InternalError error
	// ErrorCode is a machine-readable error code.
	ErrorCode string
}

ConversionError represents an error that occurred during CEL to SQL conversion. It provides both a user-safe public message and detailed internal error for logging.

func (*ConversionError) Error

func (e *ConversionError) Error() string

Error implements the error interface, returning the public message.

func (*ConversionError) Unwrap

func (e *ConversionError) Unwrap() error

Unwrap returns the internal error for error chain unwrapping.

type ConvertResult

type ConvertResult struct {
	// Where is the Squirrel Sqlizer that can be used in WHERE clauses
	Where squirrel.Sqlizer

	// Args contains any arguments that need to be bound to the query
	Args []interface{}
}

ConvertResult contains the result of converting a CEL expression to SQL.

type Converter

type Converter struct {
	// contains filtered or unexported fields
}

Converter converts CEL expressions to Squirrel SQL builder objects.

func NewConverter

func NewConverter(config Config) (*Converter, error)

NewConverter creates a new CEL to SQL converter with the given configuration.

func (*Converter) Convert

func (c *Converter) Convert(celExpr string) (*ConvertResult, error)

Convert parses a CEL expression and converts it to a Squirrel SQL builder object. It validates that the expression is boolean and returns a Sqlizer that can be used in WHERE clauses. Column mappings are automatically applied based on the converter's configuration.

func (*Converter) ConvertWithAuth

func (c *Converter) ConvertWithAuth(celExpr string, userRoles []string) (*ConvertResult, error)

ConvertWithAuth converts a CEL expression to SQL with field-level authorization. It checks that the user (identified by their roles) is authorized to filter by all fields referenced in the expression. If authorization is not configured (PublicFields is empty), this behaves the same as Convert().

type SecurityLogger

type SecurityLogger interface {
	// LogConversionAttempt logs an attempt to convert a CEL expression to SQL.
	LogConversionAttempt(expr string, success bool, err error, duration time.Duration)

	// LogComplexExpression logs when an expression is unusually complex.
	LogComplexExpression(expr string, depth int, length int)

	// LogUnauthorizedField logs when a user attempts to access a restricted field.
	LogUnauthorizedField(expr string, field string, userRoles []string)

	// LogUnsupportedOperation logs when an unsupported CEL function is used.
	LogUnsupportedOperation(expr string, operation string)
}

SecurityLogger is an interface for logging security-relevant events. Implementations should log these events to a security audit log for monitoring.

Jump to

Keyboard shortcuts

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