Skip to content

Commit a18a065

Browse files
Merge pull request #11 from kashifkhan0771/feat/compare-structs
added structs comparison support
2 parents c658437 + 34a656b commit a18a065

File tree

2 files changed

+157
-0
lines changed

2 files changed

+157
-0
lines changed

structs/structs.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package structs
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
)
7+
8+
// Result represents the comparison outcome of a field, including its name, old value, and new value.
9+
type Result struct {
10+
FieldName string
11+
OldValue interface{}
12+
NewValue interface{}
13+
}
14+
15+
/*
16+
CompareStructs compares two struct instances of the same type
17+
and returns a list of results with the old and new values of each field tagged with `updateable`.
18+
*/
19+
func CompareStructs(old, new interface{}) ([]Result, error) {
20+
if reflect.TypeOf(old) != reflect.TypeOf(new) {
21+
return nil, fmt.Errorf("both structs must be of the same type")
22+
}
23+
24+
oldValue := reflect.ValueOf(old)
25+
newValue := reflect.ValueOf(new)
26+
27+
if oldValue.Kind() != reflect.Struct || newValue.Kind() != reflect.Struct {
28+
return nil, fmt.Errorf("both parameters should be struct")
29+
}
30+
31+
var comparedResults = make([]Result, 0, oldValue.NumField())
32+
33+
for i := 0; i < oldValue.NumField(); i++ {
34+
field := oldValue.Type().Field(i)
35+
36+
if !field.IsExported() {
37+
continue // skip unexported fields
38+
}
39+
40+
oldFieldValue := oldValue.Field(i)
41+
newFieldValue := newValue.Field(i)
42+
43+
// check if the field has the `updateable` tag
44+
if tag, ok := field.Tag.Lookup("updateable"); ok && tag != "" {
45+
fieldName := field.Name // default to the struct field name
46+
if tag != "true" { // if a custom tag is provided, use that as the field name
47+
fieldName = tag
48+
}
49+
50+
if !reflect.DeepEqual(oldFieldValue.Interface(), newFieldValue.Interface()) {
51+
comparedResults = append(comparedResults, Result{
52+
FieldName: fieldName,
53+
OldValue: oldFieldValue.Interface(),
54+
NewValue: newFieldValue.Interface(),
55+
})
56+
}
57+
}
58+
59+
}
60+
61+
return comparedResults, nil
62+
}

structs/structs_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package structs
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
"time"
7+
)
8+
9+
type Test struct {
10+
Name string `updateable:"name"`
11+
Age int `updateable:"age"`
12+
IsAdult bool `updateable:"is_adult"`
13+
}
14+
15+
type ComplexTest struct {
16+
Data Test `updateable:"data"`
17+
UpdatedOn time.Time `updateable:"updated_on"`
18+
History []string `updateable:"history"`
19+
}
20+
21+
func TestCompareStructs(t *testing.T) {
22+
tests := []struct {
23+
name string
24+
old interface{}
25+
new interface{}
26+
want []Result
27+
wantErr bool
28+
}{
29+
{
30+
name: "success - compare two structs",
31+
old: Test{Name: "example", Age: 10, IsAdult: false},
32+
new: Test{Name: "example - updated", Age: 18, IsAdult: true},
33+
want: []Result{
34+
{
35+
FieldName: "name",
36+
OldValue: "example",
37+
NewValue: "example - updated",
38+
},
39+
{
40+
FieldName: "age",
41+
OldValue: 10,
42+
NewValue: 18,
43+
},
44+
{
45+
FieldName: "is_adult",
46+
OldValue: false,
47+
NewValue: true,
48+
},
49+
},
50+
wantErr: false,
51+
},
52+
{
53+
name: "success - compare two complex structs",
54+
old: ComplexTest{Data: Test{Name: "example1", Age: 25, IsAdult: true}, UpdatedOn: time.Date(2024, 10, 22, 00, 00, 00, 00, time.UTC), History: []string{"user1", "user2"}},
55+
new: ComplexTest{Data: Test{Name: "example1", Age: 26, IsAdult: true}, UpdatedOn: time.Date(2024, 10, 22, 00, 01, 00, 00, time.UTC), History: []string{"user1", "user2", "user3"}},
56+
want: []Result{
57+
{
58+
FieldName: "data",
59+
OldValue: Test{Name: "example1", Age: 25, IsAdult: true},
60+
NewValue: Test{Name: "example1", Age: 26, IsAdult: true},
61+
},
62+
{
63+
FieldName: "updated_on",
64+
OldValue: time.Date(2024, 10, 22, 00, 00, 00, 00, time.UTC),
65+
NewValue: time.Date(2024, 10, 22, 00, 01, 00, 00, time.UTC),
66+
},
67+
{
68+
FieldName: "history",
69+
OldValue: []string{"user1", "user2"},
70+
NewValue: []string{"user1", "user2", "user3"},
71+
},
72+
},
73+
wantErr: false,
74+
},
75+
{
76+
name: "fail - non struct parameters",
77+
old: map[string]string{"test": "example"},
78+
new: map[string]string{"test": "example-updated"},
79+
want: nil,
80+
wantErr: true,
81+
},
82+
}
83+
for _, tt := range tests {
84+
t.Run(tt.name, func(t *testing.T) {
85+
got, err := CompareStructs(tt.old, tt.new)
86+
if (err != nil) != tt.wantErr {
87+
t.Errorf("CompareStructs() error = %v, wantErr %v", err, tt.wantErr)
88+
return
89+
}
90+
if !reflect.DeepEqual(got, tt.want) {
91+
t.Errorf("CompareStructs() = %v, want %v", got, tt.want)
92+
}
93+
})
94+
}
95+
}

0 commit comments

Comments
 (0)