From 19ebf25222ccf6b29aeb86660b9b5b2a02bb5a5c Mon Sep 17 00:00:00 2001 From: SoulKa Date: Sat, 25 Nov 2023 17:30:13 +0100 Subject: [PATCH] using generic rules to apply polymorphism --- polymorphism.go | 62 +++++++++++++++++++++-------------------- polymorphism_builder.go | 4 +++ polymorphism_test.go | 6 ++-- rule.go | 40 ++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 32 deletions(-) create mode 100644 polymorphism_builder.go create mode 100644 rule.go diff --git a/polymorphism.go b/polymorphism.go index 8de29a7..e2dd2e5 100644 --- a/polymorphism.go +++ b/polymorphism.go @@ -16,18 +16,16 @@ type TypeMap map[any]reflect.Type // Polymorphism is a mapper that assigns a target type based on a discriminator value type Polymorphism struct { - discriminatorKey objectpath.ObjectPath - targetPath objectpath.ObjectPath - mapping TypeMap + targetPath objectpath.ObjectPath + rules []Rule } -// NewPolymorphism creates a new Polymorphism mapper -func NewPolymorphism(discriminatorKey string, mapping TypeMap) (error, *Polymorphism) { - return NewPolymorphismAtPath(discriminatorKey, "/", mapping) +type Polymorpher interface { + AssignTargetType(source any, target any) error } -// NewPolymorphismAtPath creates a new Polymorphism mapper. -func NewPolymorphismAtPath(discriminatorKey string, targetPath string, mapping TypeMap) (error, *Polymorphism) { +// NewDiscriminatingPolymorphism creates a new Polymorphism mapper. +func NewDiscriminatingPolymorphism(discriminatorKey string, targetPath string, mapping TypeMap) (error, *Polymorphism) { // parse discriminator key err, discriminatorKeyObjectPath := objectpath.NewObjectPathFromString(discriminatorKey) @@ -49,34 +47,38 @@ func NewPolymorphismAtPath(discriminatorKey string, targetPath string, mapping T return err, nil } + // create rules + var rules []Rule + for discriminatorValue, targetType := range mapping { + rules = append(rules, Rule{ + *discriminatorKeyObjectPath, + ComparatorTypeEquality, + discriminatorValue, + targetType, + }) + } + return nil, &Polymorphism{ - *discriminatorKeyObjectPath, *targetObjectPath, - mapping, + rules, } } -// AssignTargetType assigns the target type based on the discriminator value in the source. -// The source and target must be pointers. -func (polymorphism *Polymorphism) AssignTargetType(source any, target any) error { - - // get discriminator value - var discriminatorVal reflect.Value - if err := objectpath.GetValueAtPath(source, polymorphism.discriminatorKey, &discriminatorVal); err != nil { - return errors.Join(errors.New("error getting discriminator value"), err) - } - discriminatorValue := discriminatorVal.Interface() - - // get type for discriminator value - targetType, ok := polymorphism.mapping[discriminatorValue] - if !ok { - return fmt.Errorf("no target type found for discriminator value %s", discriminatorValue) - } +// AssignTargetType assigns the determined type to target based on the polymorphism rules. The matching rule with the +// highest priority is used. If no rule matches, the target type is not changed. The source and target must be pointers. +func (polymorphism *Polymorphism) AssignTargetType(source any, target any) (error, bool) { - // create target with type - if err := objectpath.AssignTypeAtPath(target, polymorphism.targetPath, targetType); err != nil { - return errors.Join(errors.New("error assigning type to target"), err) + // check for each rule if it matches and assign type if it does + for _, rule := range polymorphism.rules { + if err, matches := rule.Matches(source); err != nil { + return errors.Join(errors.New("error applying rule"), err), false + } else if matches { + if err := objectpath.AssignTypeAtPath(target, polymorphism.targetPath, rule.NewType); err != nil { + return errors.Join(errors.New("error assigning type to target"), err), false + } + return nil, true + } } - return nil + return nil, false } diff --git a/polymorphism_builder.go b/polymorphism_builder.go new file mode 100644 index 0000000..b2b498c --- /dev/null +++ b/polymorphism_builder.go @@ -0,0 +1,4 @@ +package golymorph + +type PolymorphismBuilder struct { +} diff --git a/polymorphism_test.go b/polymorphism_test.go index 9a4bef2..3575f09 100644 --- a/polymorphism_test.go +++ b/polymorphism_test.go @@ -38,7 +38,7 @@ var testCases = []TestCase{ func TestPolymorphism_AssignTargetType(t *testing.T) { // Arrange - err, polymorphism := NewPolymorphismAtPath("type", "/specifics", animalTypeMap) + err, polymorphism := NewDiscriminatingPolymorphism("type", "/specifics", animalTypeMap) if err != nil { t.Fatalf("error creating polymorphism: %s", err) } @@ -55,8 +55,10 @@ func TestPolymorphism_AssignTargetType(t *testing.T) { // Act var actualAnimal Animal - if err := polymorphism.AssignTargetType(&actualAnimalJson, &actualAnimal); err != nil { + if err, applied := polymorphism.AssignTargetType(&actualAnimalJson, &actualAnimal); err != nil { t.Fatalf("error assigning target type to horse: %s", err) + } else if !applied { + t.Fatalf("expected polymorphism to be applied") } t.Logf("actualAnimal: %+v\n", actualAnimal) diff --git a/rule.go b/rule.go new file mode 100644 index 0000000..0837048 --- /dev/null +++ b/rule.go @@ -0,0 +1,40 @@ +package golymorph + +import ( + "fmt" + "github.com/SoulKa/golymorph/objectpath" + "reflect" +) + +type ComparatorType int + +const ( + ComparatorTypeEquality ComparatorType = iota +) + +// Rule is a rule for a polymorphism mapper. +type Rule struct { + // ValuePath is the path to the value in the source to compare. + ValuePath objectpath.ObjectPath + // ComparatorType is the type of comparison to perform. + ComparatorType ComparatorType + // ComparatorValue is the value to compare against. + ComparatorValue any + // NewType is the type to assign to the target if the rule matches. + NewType reflect.Type +} + +// Matches returns true if the source matches the rule. +func (r *Rule) Matches(source any) (error, bool) { + var sourceValue reflect.Value + if err := objectpath.GetValueAtPath(source, r.ValuePath, &sourceValue); err != nil { + return err, false + } + + switch r.ComparatorType { + case ComparatorTypeEquality: + return nil, sourceValue.Interface() == r.ComparatorValue + default: + return fmt.Errorf("unknown comparator type %d", r.ComparatorType), false + } +}