Appearance
Functions in Expression Language
Overview
Functions in the expression language provide a powerful way to extend the language's capabilities beyond basic comparisons and logical operations. They are prefixed with @ and can perform complex operations, transformations, and validations.
Implementation Details
Functions are implemented through an interface:
- Function Interface
go
type Function interface {
Evaluate(args []interface{}) (interface{}, error)
ArgCount() int
}
Function Types and Return Values
Functions can return three types of values, each with specific usage patterns:
1. Boolean Functions
- Return
true
orfalse
- Can be used directly in expressions
Example implementation:
go
type IsAdminFunction struct{}
func (f *IsAdminFunction) Evaluate(args []interface{}) (interface{}, error) {
return args[0] == "admin", nil
}
func (f *IsAdminFunction) ArgCount() int {
return 1
}
Usage:
@isAdmin(user.role) or @isAdmin(user.role) == true
2. Numeric Functions
- Return numeric values (integers or floats)
- Must be used with comparison operators
Example implementation:
go
type CountFunction struct{}
func (f *CountFunction) Evaluate(args []interface{}) (interface{}, error) {
if arr, ok := args[0].([]interface{}); ok {
return float64(len(arr)), nil
}
return 0.0, fmt.Errorf("argument must be an array")
}
func (f *CountFunction) ArgCount() int {
return 1
}
Usage:
@count(items) > 5
@length(array) >= 10
3. String Functions
- Return string values
- Must be used with comparison operators
Example implementation:
go
type ToLowerFunction struct{}
func (f *ToLowerFunction) Evaluate(args []interface{}) (interface{}, error) {
if str, ok := args[0].(string); ok {
return strings.ToLower(str), nil
}
return "", fmt.Errorf("argument must be a string")
}
func (f *ToLowerFunction) ArgCount() int {
return 1
}
Usage:
@toLowerCase(name) == "admin"
@format(user.id) starts "usr_"
Error Handling
The function system includes comprehensive error handling for various scenarios:
1. Registration Errors
go
// Attempting to register a duplicate function
err := registry.Register("existing", &SomeFunction{})
if err != nil {
// Error: "function existing already registered"
}
2. Argument Count Errors
js
# Too many arguments
@isAdmin(user.role, "extra")
// Error: "function isAdmin expects 1 arguments, got 2"
# Too few arguments
@compare()
// Error: "function compare expects 2 arguments, got 0"
3. Type Context Errors
js
# Using numeric function in boolean context
@count(items)
// Error: "function count returned non-boolean value when used in boolean context"
# Using string function in boolean context
@toLowerCase(name)
// Error: "function toLowerCase returned non-boolean value when used in boolean context"
4. Argument Evaluation Errors
js
# Invalid field access
@isAdmin(nonexistent.field)
// Error: "error evaluating argument 0: field 'nonexistent' not found"
# Type conversion errors
@count("not_an_array")
// Error: "argument must be an array"
Advanced Usage Patterns
1. Function Chaining
Functions can be used as arguments to other functions:
js
@validate(@transform(data))
@hasRole(@getUserType(user))
2. Complex Conditions
Functions can be combined with other expressions:
js
@isAdmin(user.role) and (@count(user.permissions) > 5)
(@length(items) > 0 and @hasAccess(user)) or @isOverride(context)
3. Array Operations
Functions working with arrays:
js
@contains(users, "admin")
@any(permissions, @hasAccess)
@filter(items, "status", "active")
Best Practices
Type Safety
- Always use comparison operators with numeric and string functions
- Use boolean functions directly in logical expressions
- Handle type conversions explicitly in function implementation
Error Handling
- Implement thorough argument validation in functions
- Provide clear error messages for invalid inputs
- Consider edge cases in function implementation
Performance
- Keep function implementations efficient
- Cache results for expensive operations when possible
- Consider argument evaluation costs
Naming Conventions
- Use clear, descriptive function names
- Follow consistent naming patterns
- Document expected arguments and return types
Testing
- Test functions with various input types
- Include edge cases in tests
- Verify error conditions
- Test in different contexts (boolean, comparison)
Common Patterns and Examples
1. User Authorization
js
@hasRole(user, "admin") and @isActive(user)
@canAccess(user, resource) or @isAdmin(user)
2. Data Validation
js
@isValid(data) and @matchesSchema(data, "user")
@notEmpty(field) and @format(field) match "^[A-Z].*$"
3. Date/Time Operations
js
@dateAfter(timestamp, "2023-01-01")
@dateBetween(event.date, "2023-01-01", "2023-12-31")
4. String Manipulation
js
@contains(@toLowerCase(text), "error")
@matches(@trim(input), "^[0-9]+$")
5. Numeric Operations
js
@sum(values) > 1000
@average(scores) >= 75.0