diff --git a/nup/sliceUpdate_test.go b/nup/sliceUpdate_test.go index 9fd4538..0447add 100644 --- a/nup/sliceUpdate_test.go +++ b/nup/sliceUpdate_test.go @@ -51,7 +51,7 @@ func TestSliceUpdate_UnmarshalJSON(t *testing.T) { if err := json.Unmarshal([]byte(testCase.json), &dst); err != nil { t.Errorf("Error unmarshaling JSON: %s", err) } - if !reflect.DeepEqual(dst.Update, testCase.expected) { + if !dst.Update.Equal(testCase.expected) { t.Errorf("Expected: %v. Actual: %v", testCase.expected, dst.Update) } }) @@ -84,7 +84,7 @@ func TestSliceRemoveOrSet(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { actual := SliceRemoveOrSet(testCase.value) - if !reflect.DeepEqual(actual, testCase.expected) { + if !actual.Equal(testCase.expected) { t.Errorf("Expected: %v. Actual: %v", testCase.expected, actual) } }) diff --git a/nup/update.go b/nup/update.go index c9a9ade..9c1c6fa 100644 --- a/nup/update.go +++ b/nup/update.go @@ -116,6 +116,22 @@ func (u Update[T]) Apply(value T) T { } } +// ApplyPtr returns the result of applying the update to the given pointer +// value. The result is the given value if the update is a no-op, nil if it's a +// removal, or a copy of the update's contained value if it's a set operation. +func (u Update[T]) ApplyPtr(value *T) *T { + switch u.op { + case OpNoop: + return value + case OpRemove: + return nil + default: // Set + // Copy the update value so it can't be mutated via the returned pointer. + value := u.value + return &value + } +} + // Diff returns the update itself if Apply(value) != value; otherwise it returns // a no-op update. Diff can be used to omit extraneous updates when applying // them would have no effect. @@ -126,6 +142,22 @@ func (u Update[T]) Diff(value T) Update[T] { return u } +// DiffPtr returns the update itself if ApplyPtr(value) does not contain a value +// equal to the given value; otherwise it returns a no-op update. DiffPtr can be +// used to omit extraneous updates when applying them would have no effect. +func (u Update[T]) DiffPtr(value *T) Update[T] { + applied := u.ApplyPtr(value) + switch { + case applied == nil && value == nil: + return Noop[T]() + case applied == nil || value == nil: + return u + default: + return u.Diff(*value) + } + +} + // UnmarshalJSON implements json.Unmarshaler. func (u *Update[T]) UnmarshalJSON(data []byte) error { if string(data) == "null" { diff --git a/nup/update_test.go b/nup/update_test.go index e958e4c..e2482ed 100644 --- a/nup/update_test.go +++ b/nup/update_test.go @@ -43,7 +43,7 @@ func TestUpdate_UnmarshalJSON(t *testing.T) { if err := json.Unmarshal([]byte(testCase.json), &dst); err != nil { t.Errorf("Error unmarshaling JSON: %s", err) } - if !reflect.DeepEqual(dst.Update, testCase.expected) { + if dst.Update != testCase.expected { t.Errorf("Expected: %v. Actual: %v", testCase.expected, dst.Update) } }) @@ -293,6 +293,42 @@ func TestUpdate_Apply(t *testing.T) { } } +func TestUpdate_ApplyPtr(t *testing.T) { + var ( + value1 = 1 + value2 = 2 + ) + testCases := []struct { + name string + update Update[int] + expected *int + }{ + { + name: "Noop", + update: Noop[int](), + expected: &value1, + }, + { + name: "Remove", + update: Remove[int](), + expected: nil, + }, + { + name: "Set", + update: Set(value2), + expected: &value2, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + if actual := testCase.update.ApplyPtr(&value1); !reflect.DeepEqual(actual, testCase.expected) { + t.Errorf("Expected: %v. Actual: %v", testCase.expected, actual) + } + }) + } +} + func TestUpdate_Diff(t *testing.T) { var ( value1 = 1 @@ -319,7 +355,7 @@ func TestUpdate_Diff(t *testing.T) { { name: "Remove/ZeroValue", update: Remove[int](), - value: 0.0, + value: 0, expected: Noop[int](), }, { @@ -338,7 +374,66 @@ func TestUpdate_Diff(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - if actual := testCase.update.Diff(testCase.value); !reflect.DeepEqual(actual, testCase.expected) { + if actual := testCase.update.Diff(testCase.value); actual != testCase.expected { + t.Errorf("Expected: %v. Actual: %v", testCase.expected, actual) + } + }) + } +} + +func TestUpdate_DiffPtr(t *testing.T) { + var ( + zero = 0 + value1 = 1 + value2 = 2 + ) + testCases := []struct { + name string + update Update[int] + value *int + expected Update[int] + }{ + { + name: "Noop", + update: Noop[int](), + value: &value1, + expected: Noop[int](), + }, + { + name: "Remove/ZeroValue", + update: Remove[int](), + value: &zero, + expected: Remove[int](), + }, + { + name: "Remove/NonZeroValue", + update: Remove[int](), + value: &value1, + expected: Remove[int](), + }, + { + name: "Remove/Nil", + update: Remove[int](), + value: nil, + expected: Noop[int](), + }, + { + name: "Set/Equal", + update: Set(value1), + value: &value1, + expected: Noop[int](), + }, + { + name: "Set/NotEqual", + update: Set(value2), + value: &value1, + expected: Set(value2), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + if actual := testCase.update.DiffPtr(testCase.value); actual != testCase.expected { t.Errorf("Expected: %v. Actual: %v", testCase.expected, actual) } })