Documentation
¶
Overview ¶
Package exprlang provides an expr-lang adapter for the sift filter library. It translates sift filter expressions into expr-lang expression syntax.
Example (BasicFilter) ¶
package main
import (
"context"
"fmt"
"github.com/expr-lang/expr"
"github.com/nisimpson/sift"
"github.com/nisimpson/sift/thru/exprlang"
)
// User represents a user in the system.
type User struct {
Name string
Email string
Age int
Role string
Status string
Verified bool
}
func main() {
// Create a sift filter: status = "active"
filter := &sift.Condition{
Name: "Status",
Operation: sift.OperationEQ,
Value: "active",
}
// Translate to expr-lang
adapter := exprlang.NewAdapter()
_ = sift.Thru(context.Background(), adapter, sift.WithFilter(filter))
// Compile and run with expr-lang
program, _ := expr.Compile(adapter.Expression(), expr.Env(User{}))
users := []User{
{Name: "Alice", Status: "active"},
{Name: "Bob", Status: "inactive"},
{Name: "Charlie", Status: "active"},
}
for _, user := range users {
output, _ := expr.Run(program, user)
if output.(bool) {
fmt.Println(user.Name)
}
}
}
Output: Alice Charlie
Example (BetweenOperation) ¶
package main
import (
"context"
"fmt"
"github.com/expr-lang/expr"
"github.com/nisimpson/sift"
"github.com/nisimpson/sift/thru/exprlang"
)
// User represents a user in the system.
type User struct {
Name string
Email string
Age int
Role string
Status string
Verified bool
}
func main() {
// Filter: age between 18 and 65
filter := &sift.Condition{
Name: "Age",
Operation: sift.OperationBetween,
Value: "18,65",
}
// Translate to expr-lang
adapter := exprlang.NewAdapter()
_ = sift.Thru(context.Background(), adapter, sift.WithFilter(filter))
fmt.Println("Expression:", adapter.Expression())
// Compile and run with expr-lang
program, _ := expr.Compile(adapter.Expression(), expr.Env(User{}))
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 16},
{Name: "Charlie", Age: 70},
{Name: "David", Age: 45},
}
fmt.Println("Matching users:")
for _, user := range users {
output, _ := expr.Run(program, user)
if output.(bool) {
fmt.Printf(" %s (age=%d)\n", user.Name, user.Age)
}
}
}
Output: Expression: Age >= 18 and Age <= 65 Matching users: Alice (age=25) David (age=45)
Example (ComplexFilter) ¶
package main
import (
"context"
"fmt"
"github.com/expr-lang/expr"
"github.com/nisimpson/sift"
"github.com/nisimpson/sift/thru/exprlang"
)
// User represents a user in the system.
type User struct {
Name string
Email string
Age int
Role string
Status string
Verified bool
}
func main() {
// Create a complex filter: (status = "active" AND age >= 18) OR role = "admin"
filter := &sift.OrOperation{
Left: &sift.AndOperation{
Left: &sift.Condition{
Name: "Status",
Operation: sift.OperationEQ,
Value: "active",
},
Right: &sift.Condition{
Name: "Age",
Operation: sift.OperationGTE,
Value: "18",
},
},
Right: &sift.Condition{
Name: "Role",
Operation: sift.OperationEQ,
Value: "admin",
},
}
// Translate to expr-lang
adapter := exprlang.NewAdapter()
_ = sift.Thru(context.Background(), adapter, sift.WithFilter(filter))
fmt.Println("Expression:", adapter.Expression())
// Compile and run with expr-lang
program, _ := expr.Compile(adapter.Expression(), expr.Env(User{}))
users := []User{
{Name: "Alice", Status: "active", Age: 25, Role: "user"},
{Name: "Bob", Status: "inactive", Age: 30, Role: "user"},
{Name: "Charlie", Status: "active", Age: 16, Role: "user"},
{Name: "David", Status: "inactive", Age: 40, Role: "admin"},
}
fmt.Println("Matching users:")
for _, user := range users {
output, _ := expr.Run(program, user)
if output.(bool) {
fmt.Printf(" %s (status=%s, age=%d, role=%s)\n",
user.Name, user.Status, user.Age, user.Role)
}
}
}
Output: Expression: ((Status == "active") && (Age >= 18)) || (Role == "admin") Matching users: Alice (status=active, age=25, role=user) David (status=inactive, age=40, role=admin)
Example (CustomExpression) ¶
package main
import (
"context"
"fmt"
"strings"
"github.com/expr-lang/expr"
"github.com/nisimpson/sift"
"github.com/nisimpson/sift/thru/exprlang"
)
func main() {
type Tweet struct {
Content string
Likes int
}
type UserWithTweets struct {
Name string
Tweets []Tweet
}
// Use custom expr-lang expression for complex filtering
// Filter users who have more than 5 tweets with content longer than 100 chars
filter := exprlang.RawExpression("len(Tweets) > 5 and any(Tweets, len(.Content) > 100)")
// Translate to expr-lang
adapter := exprlang.NewAdapter()
_ = sift.Thru(context.Background(), adapter, sift.WithFilter(filter))
fmt.Println("Expression:", adapter.Expression())
// Compile and run with expr-lang
program, _ := expr.Compile(adapter.Expression(), expr.Env(UserWithTweets{}))
users := []UserWithTweets{
{
Name: "Alice",
Tweets: []Tweet{
{Content: "Short", Likes: 10},
{Content: strings.Repeat("Long tweet content ", 10), Likes: 50},
{Content: "Another short", Likes: 5},
{Content: strings.Repeat("More long content ", 10), Likes: 30},
{Content: "Short again", Likes: 8},
{Content: strings.Repeat("Yet another long one ", 10), Likes: 40},
},
},
{
Name: "Bob",
Tweets: []Tweet{
{Content: "Short", Likes: 10},
{Content: "Also short", Likes: 5},
},
},
}
fmt.Println("Matching users:")
for _, user := range users {
output, _ := expr.Run(program, user)
if output.(bool) {
fmt.Printf(" %s (tweets=%d)\n", user.Name, len(user.Tweets))
}
}
}
Output: Expression: len(Tweets) > 5 and any(Tweets, len(.Content) > 100) Matching users: Alice (tweets=6)
Example (InOperation) ¶
package main
import (
"context"
"fmt"
"github.com/expr-lang/expr"
"github.com/nisimpson/sift"
"github.com/nisimpson/sift/thru/exprlang"
)
func main() {
// Create a type with a slice field for the 'in' operator
type Team struct {
Name string
Roles []string
}
// Filter: "admin" in Roles
filter := &sift.Condition{
Name: "Roles",
Operation: sift.OperationIn,
Value: "admin",
}
// Translate to expr-lang
adapter := exprlang.NewAdapter()
_ = sift.Thru(context.Background(), adapter, sift.WithFilter(filter))
fmt.Println("Expression:", adapter.Expression())
// Compile and run with expr-lang
program, _ := expr.Compile(adapter.Expression(), expr.Env(Team{}))
teams := []Team{
{Name: "Engineering", Roles: []string{"admin", "developer"}},
{Name: "Marketing", Roles: []string{"user", "editor"}},
{Name: "Operations", Roles: []string{"admin", "operator"}},
}
fmt.Println("Matching teams:")
for _, team := range teams {
output, _ := expr.Run(program, team)
if output.(bool) {
fmt.Printf(" %s\n", team.Name)
}
}
}
Output: Expression: "admin" in Roles Matching teams: Engineering Operations
Example (MixedStandardAndCustom) ¶
package main
import (
"context"
"fmt"
"github.com/expr-lang/expr"
"github.com/nisimpson/sift"
"github.com/nisimpson/sift/thru/exprlang"
)
func main() {
type Post struct {
Title string
Status string
Tags []string
Comments []string
}
// Combine standard Sift operations with custom expr-lang expressions
// Filter: status = "published" AND (has "golang" tag OR has more than 10 comments)
filter := &sift.AndOperation{
Left: &sift.Condition{
Name: "Status",
Operation: sift.OperationEQ,
Value: "published",
},
Right: &sift.OrOperation{
Left: &sift.Condition{
Name: "Tags",
Operation: sift.OperationIn,
Value: "golang",
},
Right: exprlang.RawExpression("len(Comments) > 10"),
},
}
// Translate to expr-lang
adapter := exprlang.NewAdapter()
_ = sift.Thru(context.Background(), adapter, sift.WithFilter(filter))
fmt.Println("Expression:", adapter.Expression())
// Compile and run with expr-lang
program, _ := expr.Compile(adapter.Expression(), expr.Env(Post{}))
posts := []Post{
{Title: "Go Tutorial", Status: "published", Tags: []string{"golang", "tutorial"}, Comments: make([]string, 5)},
{Title: "Python Guide", Status: "published", Tags: []string{"python"}, Comments: make([]string, 15)},
{Title: "Draft Post", Status: "draft", Tags: []string{"golang"}, Comments: make([]string, 20)},
}
fmt.Println("Matching posts:")
for _, post := range posts {
output, _ := expr.Run(program, post)
if output.(bool) {
fmt.Printf(" %s\n", post.Title)
}
}
}
Output: Expression: (Status == "published") && (("golang" in Tags) || (len(Comments) > 10)) Matching posts: Go Tutorial Python Guide
Example (Negation) ¶
package main
import (
"context"
"fmt"
"github.com/expr-lang/expr"
"github.com/nisimpson/sift"
"github.com/nisimpson/sift/thru/exprlang"
)
// User represents a user in the system.
type User struct {
Name string
Email string
Age int
Role string
Status string
Verified bool
}
func main() {
// Filter: NOT(status = "deleted")
filter := &sift.NotOperation{
Child: &sift.Condition{
Name: "Status",
Operation: sift.OperationEQ,
Value: "deleted",
},
}
// Translate to expr-lang
adapter := exprlang.NewAdapter()
_ = sift.Thru(context.Background(), adapter, sift.WithFilter(filter))
fmt.Println("Expression:", adapter.Expression())
// Compile and run with expr-lang
program, _ := expr.Compile(adapter.Expression(), expr.Env(User{}))
users := []User{
{Name: "Alice", Status: "active"},
{Name: "Bob", Status: "deleted"},
{Name: "Charlie", Status: "inactive"},
}
fmt.Println("Matching users:")
for _, user := range users {
output, _ := expr.Run(program, user)
if output.(bool) {
fmt.Println(" ", user.Name)
}
}
}
Output: Expression: !(Status == "deleted") Matching users: Alice Charlie
Example (Serialization) ¶
package main
import (
"context"
"fmt"
"github.com/nisimpson/sift"
"github.com/nisimpson/sift/thru/exprlang"
)
func main() {
// Custom expressions serialize as exprlang(...) in Sift format
// Simple custom expression
filter1 := exprlang.RawExpression("len(tweets) > 10")
fmt.Println("Custom expression:")
fmt.Println(" Sift format:", filter1.String())
adapter1 := exprlang.NewAdapter()
_ = sift.Thru(context.Background(), adapter1, sift.WithFilter(filter1))
fmt.Println(" Expr-lang:", adapter1.Expression())
// Mixed standard and custom
filter2 := &sift.AndOperation{
Left: &sift.Condition{
Name: "Status",
Operation: sift.OperationEQ,
Value: "active",
},
Right: exprlang.RawExpression("len(Comments) > 5"),
}
fmt.Println("\nMixed expression:")
fmt.Println(" Sift format:", filter2.String())
adapter2 := exprlang.NewAdapter()
_ = sift.Thru(context.Background(), adapter2, sift.WithFilter(filter2))
fmt.Println(" Expr-lang:", adapter2.Expression())
}
Output: Custom expression: Sift format: exprlang(len(tweets) > 10) Expr-lang: len(tweets) > 10 Mixed expression: Sift format: and(eq(Status,active),exprlang(len(Comments) > 5)) Expr-lang: (Status == "active") && (len(Comments) > 5)
Example (StringOperations) ¶
package main
import (
"context"
"fmt"
"github.com/expr-lang/expr"
"github.com/nisimpson/sift"
"github.com/nisimpson/sift/thru/exprlang"
)
// User represents a user in the system.
type User struct {
Name string
Email string
Age int
Role string
Status string
Verified bool
}
func main() {
// Filter: email contains "@example.com" AND name starts with "John"
filter := &sift.AndOperation{
Left: &sift.Condition{
Name: "Email",
Operation: sift.OperationContains,
Value: "@example.com",
},
Right: &sift.Condition{
Name: "Name",
Operation: sift.OperationBeginsWith,
Value: "John",
},
}
// Translate to expr-lang
adapter := exprlang.NewAdapter()
_ = sift.Thru(context.Background(), adapter, sift.WithFilter(filter))
fmt.Println("Expression:", adapter.Expression())
// Compile and run with expr-lang
program, _ := expr.Compile(adapter.Expression(), expr.Env(User{}))
users := []User{
{Name: "John Doe", Email: "john@example.com"},
{Name: "Jane Smith", Email: "jane@example.com"},
{Name: "Johnny Walker", Email: "johnny@example.com"},
{Name: "John Adams", Email: "john@other.com"},
}
fmt.Println("Matching users:")
for _, user := range users {
output, _ := expr.Run(program, user)
if output.(bool) {
fmt.Printf(" %s <%s>\n", user.Name, user.Email)
}
}
}
Output: Expression: (Email contains "@example.com") && (Name startsWith "John") Matching users: John Doe <john@example.com> Johnny Walker <johnny@example.com>
Index ¶
- func ArrayFunction(fn, array, predicate string) sift.Expression
- func DateFunction(fn string, args ...string) sift.Expression
- func NewCustomExpression(expr string) sift.Expression
- func NewRegistry() *sift.Registry
- func Parse(input string, opts ...ParseOption) (sift.Expression, error)
- func Predicate(expr string) sift.Expression
- func RawExpression(expr string) sift.Expression
- func StringFunction(fn string, args ...string) sift.Expression
- type Adapter
- func (a *Adapter) EvaluateAnd(ctx context.Context, node *sift.AndOperation) error
- func (a *Adapter) EvaluateCondition(ctx context.Context, node *sift.Condition) error
- func (a *Adapter) EvaluateCustom(ctx context.Context, node sift.CustomExpression) error
- func (a *Adapter) EvaluateNot(ctx context.Context, node *sift.NotOperation) error
- func (a *Adapter) EvaluateOr(ctx context.Context, node *sift.OrOperation) error
- func (a *Adapter) Evaluator(ctx context.Context) *sift.Evaluator
- func (a *Adapter) Expression() string
- type CustomExpression
- type Formatter
- type ParseOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ArrayFunction ¶
func ArrayFunction(fn, array, predicate string) sift.Expression
ArrayFunction creates a custom expression for array functions. Example: ArrayFunction("filter", "tweets", "len(.Content) > 240")
func DateFunction ¶
func DateFunction(fn string, args ...string) sift.Expression
DateFunction creates a custom expression for date functions. Example: DateFunction("now")
func NewCustomExpression ¶
func NewCustomExpression(expr string) sift.Expression
NewCustomExpression creates a new custom expr-lang expression. The expression string should be valid expr-lang syntax.
func NewRegistry ¶
NewRegistry creates a sift registry with expr-lang custom expressions registered. Use this when you need to serialize/deserialize expr-lang-specific expressions.
Example:
registry := exprlang.NewRegistry()
expr := exprlang.Predicate("len(.Content) > 240")
formatted, _ := sift.Format(expr, registry)
// Output: "exprlang(len\(.Content\) > 240)"
func Parse ¶ added in v0.2.0
func Parse(input string, opts ...ParseOption) (sift.Expression, error)
Parse parses an expr-lang expression string into a sift.Expression AST. It uses expr.Compile() to parse the string into an expr-lang AST, then walks the AST to produce sift nodes.
By default, the parser operates in strict mode where unsupported expr-lang constructs return descriptive errors. Use WithLenientMode() to wrap unsupported constructs as RawExpression nodes instead.
func Predicate ¶
func Predicate(expr string) sift.Expression
Predicate creates a custom expression using expr-lang predicate syntax. Example: Predicate("len(.Content) > 240")
func RawExpression ¶
func RawExpression(expr string) sift.Expression
RawExpression creates a custom expression from raw expr-lang syntax. Use this for any expr-lang expression that doesn't fit other helpers.
func StringFunction ¶
func StringFunction(fn string, args ...string) sift.Expression
StringFunction creates a custom expression for string functions. Example: StringFunction("upper", "name")
Types ¶
type Adapter ¶
type Adapter struct {
// contains filtered or unexported fields
}
Adapter translates sift filter expressions into expr-lang expression syntax. It accumulates the expression string during traversal.
func (*Adapter) EvaluateAnd ¶
EvaluateAnd combines two expressions with logical AND.
func (*Adapter) EvaluateCondition ¶
EvaluateCondition translates a sift condition into an expr-lang expression.
func (*Adapter) EvaluateCustom ¶
EvaluateCustom handles custom expr-lang expressions.
func (*Adapter) EvaluateNot ¶
EvaluateNot negates an expression.
func (*Adapter) EvaluateOr ¶
EvaluateOr combines two expressions with logical OR.
func (*Adapter) Expression ¶
Expression returns the accumulated expr-lang expression string.
type CustomExpression ¶
type CustomExpression struct {
// contains filtered or unexported fields
}
CustomExpression represents an expr-lang specific expression that doesn't map directly to standard Sift operations.
func (*CustomExpression) Expression ¶
func (c *CustomExpression) Expression() string
Expression returns the raw expr-lang expression string.
func (*CustomExpression) String ¶
func (c *CustomExpression) String() string
String returns the string representation of the custom expression.
func (*CustomExpression) Type ¶
func (c *CustomExpression) Type() string
Type returns the type identifier for this custom expression.
type Formatter ¶
type Formatter struct{}
Formatter implements sift.CustomFormatter for expr-lang custom expressions.
func (Formatter) FormatCustomExpression ¶
func (f Formatter) FormatCustomExpression(expression sift.CustomExpression) (string, error)
FormatCustomExpression serializes an expr-lang custom expression to a string.
func (Formatter) ParseCustomExpression ¶
ParseCustomExpression deserializes a string to an expr-lang custom expression.
type ParseOption ¶ added in v0.2.0
type ParseOption func(*parserConfig)
ParseOption configures the parser behavior.
func WithLenientMode ¶ added in v0.2.0
func WithLenientMode() ParseOption
WithLenientMode enables lenient parsing where unsupported expr-lang constructs are wrapped as exprlang.RawExpression custom sift nodes instead of returning errors.