Multiple implementations of the same back-end application. The aim is to provide quick, side-by-side comparisons of different technologies (languages, frameworks, libraries) while preserving consistent business logic across all implementations.
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

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