You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
443 lines
13 KiB
443 lines
13 KiB
package specifications |
|
|
|
import ( |
|
"fmt" |
|
"reflect" |
|
"strings" |
|
"time" |
|
) |
|
|
|
// CompositeSpecification implements logical operations between different specification types |
|
type CompositeSpecification[T any] struct { |
|
left Specification[T] |
|
right Specification[T] |
|
op string // "AND", "OR" |
|
} |
|
|
|
// IsSatisfiedBy checks if the candidate satisfies the composite specification |
|
func (c *CompositeSpecification[T]) IsSatisfiedBy(candidate T) bool { |
|
switch c.op { |
|
case "AND": |
|
return c.left.IsSatisfiedBy(candidate) && c.right.IsSatisfiedBy(candidate) |
|
case "OR": |
|
return c.left.IsSatisfiedBy(candidate) || c.right.IsSatisfiedBy(candidate) |
|
default: |
|
return false |
|
} |
|
} |
|
|
|
// And creates a new specification that is the logical AND of this and another specification |
|
func (c *CompositeSpecification[T]) And(other Specification[T]) Specification[T] { |
|
return &CompositeSpecification[T]{ |
|
left: c, |
|
right: other, |
|
op: "AND", |
|
} |
|
} |
|
|
|
// Or creates a new specification that is the logical OR of this and another specification |
|
func (c *CompositeSpecification[T]) Or(other Specification[T]) Specification[T] { |
|
return &CompositeSpecification[T]{ |
|
left: c, |
|
right: other, |
|
op: "OR", |
|
} |
|
} |
|
|
|
// Not creates a new specification that is the logical NOT of this specification |
|
func (c *CompositeSpecification[T]) Not() Specification[T] { |
|
// For composite specifications, we need to create a wrapper that negates the result |
|
return &NotSpecification[T]{spec: c} |
|
} |
|
|
|
// GetConditions returns all conditions from this specification |
|
func (c *CompositeSpecification[T]) GetConditions() []Condition { |
|
// Combine conditions from both sides |
|
leftConditions := c.left.GetConditions() |
|
rightConditions := c.right.GetConditions() |
|
return append(leftConditions, rightConditions...) |
|
} |
|
|
|
// GetSpec returns nil for composite specifications as they don't have a single spec structure |
|
func (c *CompositeSpecification[T]) GetSpec() *Spec { |
|
return nil |
|
} |
|
|
|
// NotSpecification implements the NOT operation for specifications |
|
type NotSpecification[T any] struct { |
|
spec Specification[T] |
|
} |
|
|
|
// IsSatisfiedBy checks if the candidate does NOT satisfy the wrapped specification |
|
func (n *NotSpecification[T]) IsSatisfiedBy(candidate T) bool { |
|
return !n.spec.IsSatisfiedBy(candidate) |
|
} |
|
|
|
// And creates a new specification that is the logical AND of this and another specification |
|
func (n *NotSpecification[T]) And(other Specification[T]) Specification[T] { |
|
return &CompositeSpecification[T]{ |
|
left: n, |
|
right: other, |
|
op: "AND", |
|
} |
|
} |
|
|
|
// Or creates a new specification that is the logical OR of this and another specification |
|
func (n *NotSpecification[T]) Or(other Specification[T]) Specification[T] { |
|
return &CompositeSpecification[T]{ |
|
left: n, |
|
right: other, |
|
op: "OR", |
|
} |
|
} |
|
|
|
// Not creates a new specification that is the logical NOT of this specification (double negation) |
|
func (n *NotSpecification[T]) Not() Specification[T] { |
|
return n.spec |
|
} |
|
|
|
// GetConditions returns all conditions from the wrapped specification |
|
func (n *NotSpecification[T]) GetConditions() []Condition { |
|
return n.spec.GetConditions() |
|
} |
|
|
|
// GetSpec returns nil for not specifications |
|
func (n *NotSpecification[T]) GetSpec() *Spec { |
|
return nil |
|
} |
|
|
|
// Specification defines the interface for specifications |
|
type Specification[T any] interface { |
|
IsSatisfiedBy(candidate T) bool |
|
And(other Specification[T]) Specification[T] |
|
Or(other Specification[T]) Specification[T] |
|
Not() Specification[T] |
|
GetConditions() []Condition |
|
GetSpec() *Spec |
|
} |
|
|
|
// SimpleSpecification implements the Specification interface using condition-based specs |
|
type SimpleSpecification[T any] struct { |
|
spec *Spec |
|
} |
|
|
|
// NewSimpleSpecification creates a new SimpleSpecification with the given spec |
|
func NewSimpleSpecification[T any](spec *Spec) *SimpleSpecification[T] { |
|
return &SimpleSpecification[T]{ |
|
spec: spec, |
|
} |
|
} |
|
|
|
// IsSatisfiedBy checks if the candidate satisfies the specification |
|
func (s *SimpleSpecification[T]) IsSatisfiedBy(candidate T) bool { |
|
return s.evaluateSpec(s.spec, candidate) |
|
} |
|
|
|
// And creates a new specification that is the logical AND of this and another specification |
|
func (s *SimpleSpecification[T]) And(other Specification[T]) Specification[T] { |
|
if otherSpec, ok := other.(*SimpleSpecification[T]); ok { |
|
return NewSimpleSpecification[T](SpecBuilder.And(s.spec, otherSpec.spec)) |
|
} |
|
panic("And operation not supported between different specification types") |
|
} |
|
|
|
// Or creates a new specification that is the logical OR of this and another specification |
|
func (s *SimpleSpecification[T]) Or(other Specification[T]) Specification[T] { |
|
if otherSpec, ok := other.(*SimpleSpecification[T]); ok { |
|
return NewSimpleSpecification[T](SpecBuilder.Or(s.spec, otherSpec.spec)) |
|
} |
|
panic("Or operation not supported between different specification types") |
|
} |
|
|
|
// Not creates a new specification that is the logical NOT of this specification |
|
func (s *SimpleSpecification[T]) Not() Specification[T] { |
|
return NewSimpleSpecification[T](SpecBuilder.Not(s.spec)) |
|
} |
|
|
|
// GetSpec returns the underlying specification structure |
|
func (s *SimpleSpecification[T]) GetSpec() *Spec { |
|
return s.spec |
|
} |
|
|
|
// GetConditions returns all conditions from this specification |
|
func (s *SimpleSpecification[T]) GetConditions() []Condition { |
|
return SpecBuilder.GetConditions(s.spec) |
|
} |
|
|
|
// evaluateSpec recursively evaluates a specification against a candidate |
|
func (s *SimpleSpecification[T]) evaluateSpec(spec *Spec, candidate T) bool { |
|
if spec == nil { |
|
return false |
|
} |
|
|
|
// Handle logical groups |
|
if spec.LogicalGroup != nil { |
|
switch spec.LogicalGroup.Operator { |
|
case GROUP_AND: |
|
return s.evaluateAndGroup(spec.LogicalGroup, candidate) |
|
case GROUP_OR: |
|
return s.evaluateOrGroup(spec.LogicalGroup, candidate) |
|
case GROUP_NOT: |
|
return s.evaluateNotGroup(spec.LogicalGroup, candidate) |
|
} |
|
} |
|
|
|
// Handle simple condition |
|
if spec.Condition != nil { |
|
return s.evaluateCondition(*spec.Condition, candidate) |
|
} |
|
|
|
return false |
|
} |
|
|
|
// evaluateAndGroup evaluates an AND group |
|
func (s *SimpleSpecification[T]) evaluateAndGroup(group *LogicalGroup, candidate T) bool { |
|
for _, cond := range group.Conditions { |
|
if !s.evaluateCondition(cond, candidate) { |
|
return false |
|
} |
|
} |
|
return true |
|
} |
|
|
|
// evaluateOrGroup evaluates an OR group |
|
func (s *SimpleSpecification[T]) evaluateOrGroup(group *LogicalGroup, candidate T) bool { |
|
for _, cond := range group.Conditions { |
|
if s.evaluateCondition(cond, candidate) { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// evaluateNotGroup evaluates a NOT group |
|
func (s *SimpleSpecification[T]) evaluateNotGroup(group *LogicalGroup, candidate T) bool { |
|
if group.Spec != nil { |
|
return !s.evaluateSpec(group.Spec, candidate) |
|
} |
|
return false |
|
} |
|
|
|
// evaluateCondition evaluates a single condition against a candidate |
|
func (s *SimpleSpecification[T]) evaluateCondition(condition Condition, candidate T) bool { |
|
// Get the field value from the candidate |
|
fieldValue, err := s.getFieldValue(candidate, condition.Field) |
|
if err != nil { |
|
return false |
|
} |
|
|
|
// Handle time comparison |
|
return s.compareValues(fieldValue, condition.Operator, condition.Value) |
|
} |
|
|
|
// getFieldValue retrieves the value of a field from an object using reflection |
|
func (s *SimpleSpecification[T]) getFieldValue(candidate T, fieldName string) (interface{}, error) { |
|
v := reflect.ValueOf(candidate) |
|
|
|
// If candidate is a pointer, get the underlying value |
|
if v.Kind() == reflect.Ptr { |
|
v = v.Elem() |
|
} |
|
|
|
if v.Kind() != reflect.Struct { |
|
return nil, fmt.Errorf("candidate is not a struct") |
|
} |
|
|
|
// Try to get the field by getter method first (preferred approach) |
|
getterName := "Get" + strings.Title(fieldName) |
|
method := v.MethodByName(getterName) |
|
if method.IsValid() && method.Type().NumIn() == 0 && method.Type().NumOut() > 0 { |
|
results := method.Call(nil) |
|
if len(results) > 0 { |
|
return results[0].Interface(), nil |
|
} |
|
} |
|
|
|
// Try alternative getter naming (e.g., ExpirationDate() instead of GetExpirationDate()) |
|
method = v.MethodByName(fieldName) |
|
if method.IsValid() && method.Type().NumIn() == 0 && method.Type().NumOut() > 0 { |
|
results := method.Call(nil) |
|
if len(results) > 0 { |
|
return results[0].Interface(), nil |
|
} |
|
} |
|
|
|
// Try to get the field by name (for exported fields only) |
|
field := v.FieldByName(fieldName) |
|
if field.IsValid() && field.CanInterface() { |
|
return field.Interface(), nil |
|
} |
|
|
|
return nil, fmt.Errorf("field %s not found or not accessible", fieldName) |
|
} |
|
|
|
// compareValues compares two values using the specified operator |
|
func (s *SimpleSpecification[T]) compareValues(fieldValue interface{}, operator string, compareValue interface{}) bool { |
|
// Handle time comparison |
|
if s.isTimeComparable(fieldValue, compareValue) { |
|
return s.compareTimes(fieldValue, operator, compareValue) |
|
} |
|
|
|
// Convert both values to the same type for comparison |
|
fieldVal := reflect.ValueOf(fieldValue) |
|
compareVal := reflect.ValueOf(compareValue) |
|
|
|
// Handle IN and NOT IN operators |
|
if operator == OP_IN || operator == OP_NIN { |
|
return s.compareIn(fieldValue, operator, compareValue) |
|
} |
|
|
|
// For other operators, try to convert to comparable types |
|
if fieldVal.Kind() != compareVal.Kind() { |
|
// Try to convert compareValue to the same type as fieldValue |
|
if compareVal.CanConvert(fieldVal.Type()) { |
|
compareVal = compareVal.Convert(fieldVal.Type()) |
|
} else if fieldVal.CanConvert(compareVal.Type()) { |
|
fieldVal = fieldVal.Convert(compareVal.Type()) |
|
} else { |
|
return false |
|
} |
|
} |
|
|
|
switch operator { |
|
case OP_EQ: |
|
return reflect.DeepEqual(fieldValue, compareValue) |
|
case OP_NEQ: |
|
return !reflect.DeepEqual(fieldValue, compareValue) |
|
case OP_GT: |
|
return s.compareGreater(fieldVal, compareVal) |
|
case OP_GTE: |
|
return s.compareGreater(fieldVal, compareVal) || reflect.DeepEqual(fieldValue, compareValue) |
|
case OP_LT: |
|
return s.compareLess(fieldVal, compareVal) |
|
case OP_LTE: |
|
return s.compareLess(fieldVal, compareVal) || reflect.DeepEqual(fieldValue, compareValue) |
|
default: |
|
return false |
|
} |
|
} |
|
|
|
// isTimeComparable checks if the values can be compared as times |
|
func (s *SimpleSpecification[T]) isTimeComparable(fieldValue, compareValue interface{}) bool { |
|
_, fieldIsTime := fieldValue.(time.Time) |
|
_, compareIsTime := compareValue.(time.Time) |
|
|
|
// If both are time.Time, they are time comparable |
|
if fieldIsTime && compareIsTime { |
|
return true |
|
} |
|
|
|
// If one is time.Time and the other is string, check if string is a valid time |
|
if fieldIsTime { |
|
if compareStr, ok := compareValue.(string); ok { |
|
_, err := time.Parse(time.RFC3339, compareStr) |
|
return err == nil |
|
} |
|
} |
|
|
|
if compareIsTime { |
|
if fieldStr, ok := fieldValue.(string); ok { |
|
_, err := time.Parse(time.RFC3339, fieldStr) |
|
return err == nil |
|
} |
|
} |
|
|
|
return false |
|
} |
|
|
|
// compareTimes compares time values |
|
func (s *SimpleSpecification[T]) compareTimes(fieldValue interface{}, operator string, compareValue interface{}) bool { |
|
var fieldTime, compareTime time.Time |
|
var err error |
|
|
|
// Convert fieldValue to time.Time |
|
switch v := fieldValue.(type) { |
|
case time.Time: |
|
fieldTime = v |
|
case string: |
|
fieldTime, err = time.Parse(time.RFC3339, v) |
|
if err != nil { |
|
return false |
|
} |
|
default: |
|
return false |
|
} |
|
|
|
// Convert compareValue to time.Time |
|
switch v := compareValue.(type) { |
|
case time.Time: |
|
compareTime = v |
|
case string: |
|
compareTime, err = time.Parse(time.RFC3339, v) |
|
if err != nil { |
|
return false |
|
} |
|
default: |
|
return false |
|
} |
|
|
|
switch operator { |
|
case OP_EQ: |
|
return fieldTime.Equal(compareTime) |
|
case OP_NEQ: |
|
return !fieldTime.Equal(compareTime) |
|
case OP_GT: |
|
return fieldTime.After(compareTime) |
|
case OP_GTE: |
|
return fieldTime.After(compareTime) || fieldTime.Equal(compareTime) |
|
case OP_LT: |
|
return fieldTime.Before(compareTime) |
|
case OP_LTE: |
|
return fieldTime.Before(compareTime) || fieldTime.Equal(compareTime) |
|
default: |
|
return false |
|
} |
|
} |
|
|
|
// compareIn handles IN and NOT IN operators |
|
func (s *SimpleSpecification[T]) compareIn(fieldValue interface{}, operator string, compareValue interface{}) bool { |
|
compareSlice, ok := compareValue.([]interface{}) |
|
if !ok { |
|
return false |
|
} |
|
|
|
for _, v := range compareSlice { |
|
if reflect.DeepEqual(fieldValue, v) { |
|
return operator == OP_IN |
|
} |
|
} |
|
|
|
return operator == OP_NIN |
|
} |
|
|
|
// compareGreater compares if fieldVal is greater than compareVal |
|
func (s *SimpleSpecification[T]) compareGreater(fieldVal, compareVal reflect.Value) bool { |
|
switch fieldVal.Kind() { |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
return fieldVal.Int() > compareVal.Int() |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
|
return fieldVal.Uint() > compareVal.Uint() |
|
case reflect.Float32, reflect.Float64: |
|
return fieldVal.Float() > compareVal.Float() |
|
case reflect.String: |
|
return fieldVal.String() > compareVal.String() |
|
default: |
|
return false |
|
} |
|
} |
|
|
|
// compareLess compares if fieldVal is less than compareVal |
|
func (s *SimpleSpecification[T]) compareLess(fieldVal, compareVal reflect.Value) bool { |
|
switch fieldVal.Kind() { |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
return fieldVal.Int() < compareVal.Int() |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
|
return fieldVal.Uint() < compareVal.Uint() |
|
case reflect.Float32, reflect.Float64: |
|
return fieldVal.Float() < compareVal.Float() |
|
case reflect.String: |
|
return fieldVal.String() < compareVal.String() |
|
default: |
|
return false |
|
} |
|
} |