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 } }