Skip to content

Commit

Permalink
using generic rules to apply polymorphism
Browse files Browse the repository at this point in the history
  • Loading branch information
SoulKa committed Nov 25, 2023
1 parent f6b717d commit 19ebf25
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 32 deletions.
62 changes: 32 additions & 30 deletions polymorphism.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

}
4 changes: 4 additions & 0 deletions polymorphism_builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package golymorph

type PolymorphismBuilder struct {
}
6 changes: 4 additions & 2 deletions polymorphism_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)

Expand Down
40 changes: 40 additions & 0 deletions rule.go
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit 19ebf25

Please sign in to comment.