From c4dd4760f9fac2fb93ad65310550e479458fc0d1 Mon Sep 17 00:00:00 2001 From: Shulhan Date: Sat, 14 Dec 2024 21:11:11 +0700 Subject: lib/reflect: refactor the Equaler method Equal This changes the Equal signature from "Equal(v any) bool" to "Equal(v any) error". The reason for this changes is to force the method to return an error message that is understand-able by caller. While at it, simplify checking for Equaler interface in internal doEqualStruct function. --- lib/reflect/equaler.go | 15 +++++---- lib/reflect/equaler_example_test.go | 52 +++++++++++++++++++++++++++++++ lib/reflect/equaler_test.go | 61 +++++++++++++++++++++++++++++++++++++ lib/reflect/reflect.go | 40 +++++++++++------------- 4 files changed, 140 insertions(+), 28 deletions(-) create mode 100644 lib/reflect/equaler_example_test.go create mode 100644 lib/reflect/equaler_test.go diff --git a/lib/reflect/equaler.go b/lib/reflect/equaler.go index f4dc7268..8a0a8dd1 100644 --- a/lib/reflect/equaler.go +++ b/lib/reflect/equaler.go @@ -1,11 +1,14 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// SPDX-FileCopyrightText: 2020 M. Shulhan +// +// SPDX-License-Identifier: BSD-3-Clause package reflect -// Equaler is an interface that when implemented by a type, it will be used to -// compare the value in Assert. +// Equaler is an interface that when implemented by a struct type, it will +// be used to compare the value in [DoEqual] or [IsEqual]. type Equaler interface { - IsEqual(v interface{}) bool + // Equal compare the struct receiver with parameter v. + // The v value can be converted to struct type T using (*T). + // If both struct values are equal it should return nil. + Equal(v any) error } diff --git a/lib/reflect/equaler_example_test.go b/lib/reflect/equaler_example_test.go new file mode 100644 index 00000000..6f14d2ae --- /dev/null +++ b/lib/reflect/equaler_example_test.go @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2024 M. Shulhan +// +// SPDX-License-Identifier: BSD-3-Clause + +package reflect + +import ( + "fmt" + "log" +) + +type ADT struct { + vint int +} + +func (rnp *ADT) Equal(v any) (err error) { + var ( + logp = `Equal` + got *ADT + ok bool + ) + got, ok = v.(*ADT) + if !ok { + return fmt.Errorf(`%s: v type is %T, want %T`, logp, got, v) + } + if rnp.vint != got.vint { + return fmt.Errorf(`%s: vint: %d, want %d`, + logp, got.vint, rnp.vint) + } + return nil +} + +func ExampleEqualer() { + var ( + rp1 = ADT{ + vint: 1, + } + rp2 = ADT{ + vint: 2, + } + ) + var err = DoEqual(&rp1, &rp2) + if err == nil { + log.Fatal(`expecting error, got nil`) + } + + var exp = `Equal: vint: want 1, got 2` + var got = err.Error() + if exp != got { + log.Fatalf(`want %q, got %q`, exp, got) + } +} diff --git a/lib/reflect/equaler_test.go b/lib/reflect/equaler_test.go new file mode 100644 index 00000000..0477c955 --- /dev/null +++ b/lib/reflect/equaler_test.go @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2024 M. Shulhan +// +// SPDX-License-Identifier: BSD-3-Clause + +package reflect + +import ( + "fmt" + "testing" +) + +type recvNotPointer struct { + vint int +} + +func (rnp recvNotPointer) Equal(v any) (err error) { + var ( + logp = `Equal` + got *recvNotPointer + ok bool + ) + got, ok = v.(*recvNotPointer) + if !ok { + return fmt.Errorf(`%s: v type is %T, want %T`, logp, v, got) + } + if rnp.vint != got.vint { + return fmt.Errorf(`%s: vint: want %d, got %d`, + logp, rnp.vint, got.vint) + } + return nil +} + +func TestEqualerRecvNotPointer(t *testing.T) { + var ( + rnp1 = recvNotPointer{ + vint: 1, + } + rnp2 = recvNotPointer{ + vint: 2, + } + ) + + var err = DoEqual(&rnp1, &rnp2) + if err == nil { + t.Fatal(`expecting error, got nil`) + } + + var exp = `Equal: vint: want 1, got 2` + var got = err.Error() + if exp != got { + t.Fatalf(`want %q, got %q`, exp, got) + } + + var rnp3 = recvNotPointer{ + vint: 1, + } + err = DoEqual(&rnp1, &rnp3) + if err != nil { + t.Fatalf(`expecting no error, got %s`, err) + } +} diff --git a/lib/reflect/reflect.go b/lib/reflect/reflect.go index 41790c55..f5f917f5 100644 --- a/lib/reflect/reflect.go +++ b/lib/reflect/reflect.go @@ -1,6 +1,6 @@ -// Copyright 2020, Shulhan . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// SPDX-FileCopyrightText: 2020 M. Shulhan +// +// SPDX-License-Identifier: BSD-3-Clause // Package reflect extends the standard reflect package. package reflect @@ -17,9 +17,10 @@ const ( tagNoEqual = `noequal` ) -// DoEqual is a naive interfaces comparison that check and use Equaler -// interface and return an error if its not match. +// DoEqual is a naive interfaces comparison for two values. // +// If the type is a struct and implement [Equaler] interface it will use the +// [Equal] method in that struct to compare the values. // A struct's field tagged with `noequal:""` will be skipped from being // processed. func DoEqual(x, y interface{}) (err error) { @@ -38,9 +39,10 @@ func DoEqual(x, y interface{}) (err error) { return nil } -// IsEqual is a naive interfaces comparison that check and use Equaler -// interface. +// IsEqual is a naive interfaces comparison for two values. // +// If the type is a struct and implement [Equaler] interface it will use the +// [Equal] method in that struct to compare the values. // A struct's field tagged with `noequal:""` will be skipped from being // processed. func IsEqual(x, y interface{}) bool { @@ -767,22 +769,16 @@ func doEqualMap(v1, v2 reflect.Value) (err error) { // fields has equal value. // The type of both struct is already equal when this function called. func doEqualStruct(v1, v2 reflect.Value) (err error) { - var ( - m1 = v1.MethodByName(`IsEqual`) - - callIn []reflect.Value - callOut []reflect.Value - ) - - if m1.IsValid() { - callIn = append(callIn, v2.Addr()) - callOut = m1.Call(callIn) - if len(callOut) == 1 && callOut[0].Kind() == reflect.Bool { - if callOut[0].Bool() { - return nil - } - return fmt.Errorf("IsEqual: %s.IsEqual(%s) return false", v1.String(), v2.String()) + var equalerType = reflect.TypeOf((*Equaler)(nil)).Elem() + if v1.Type().Implements(equalerType) { + var m1 = v1.MethodByName(`Equal`) + var callIn = []reflect.Value{v2.Addr()} + var callOut []reflect.Value = m1.Call(callIn) + var val = callOut[0].Interface() + if val == nil { + return nil } + return val.(error) } var ( -- cgit v1.3