From de97d1a195558236e78bb03eaa9014d73a679a22 Mon Sep 17 00:00:00 2001 From: "taisuke.fujita" Date: Sun, 18 Aug 2024 23:03:28 +0900 Subject: [PATCH] Separated protobuf related functions --- README.md | 42 ++++---- copier.go | 126 +++++++----------------- copier_test.go | 251 +----------------------------------------------- example_test.go | 34 ++++--- go.mod | 4 +- go.sum | 10 -- new.go | 15 ++- 7 files changed, 93 insertions(+), 389 deletions(-) diff --git a/README.md b/README.md index 0434bb3..d9f77bb 100644 --- a/README.md +++ b/README.md @@ -45,30 +45,27 @@ import ( "fmt" "time" - "github.com/golang/protobuf/ptypes/timestamp" "github.com/glassonion1/xgo" ) -// It is a common, ordinary struct type FromModel struct { ID string `copier:"Id"` Name string CreatedAt time.Time UpdatedAt *time.Time } -// It is like a protobuf struct on gRPC type ToModel struct { Id string Name string - CreatedAt *timestamp.Timestamp - UpdatedAt *timestamp.Timestamp + CreatedAt time.Time + UpdatedAt *time.Time } func Example() { - now := time.Date(2020, 6, 1, 0, 0, 0, 0, time.UTC) + now := time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC) from := FromModel{ - ID: "xxxx", - Name: "R2D2", + ID: "xxxx", + Name: "R2D2", CreatedAt: now, UpdatedAt: &now, } @@ -78,8 +75,8 @@ func Example() { // handles error } fmt.Println("ToModel object:", to) - - // Output: ToModel object: &{xxxx R2D2 seconds:1590969600 seconds:1590969600} + + // Output: ToModel object: &{xxxx R2D2 2025-06-01 00:00:00 +0000 UTC 2025-06-01 00:00:00 +0000 UTC} } ``` @@ -100,7 +97,7 @@ func Example() { ID: "xxxx1", Name: "R2D2", }, - { + { ID: "xxxx2", Name: "C3PO", }, @@ -120,19 +117,19 @@ func Example() { Contains method for a slice. ```go // slice of int32 -containsInt32 := xgo.Contains[int32]([]int32{1, 2, 3, 4, 5}, 3) +containsInt32 := xgo.Contains([]int32{1, 2, 3, 4, 5}, 3) fmt.Println("contains int32:", containsInt32) // slice of int -containsInt := xgo.Contains[int]([]int{1, 2, 3, 4, 5}, 2) +containsInt := xgo.Contains([]int{1, 2, 3, 4, 5}, 2) fmt.Println("contains int:", containsInt) // slice of float64 -containsFloat64 := xgo.Contains[float64]([]float64{1.1, 2.2, 3.3, 4.4, 5.5}, 4.4) +containsFloat64 := xgo.Contains([]float64{1.1, 2.2, 3.3, 4.4, 5.5}, 4.4) fmt.Println("contains float64:", containsFloat64) // slice of string -containsString := Contains[string]([]string{"r2d2", "c3po", "bb8"}, "c3po") +containsString := xgo.Contains([]string{"r2d2", "c3po", "bb8"}, "c3po") fmt.Println(containsString) // -> true // slice of struct @@ -158,17 +155,18 @@ target := hero{ ID: "2", Name: "Han Solo", } -containsStruct := xgo.Contains[hero](list, target) +containsStruct := xgo.Contains(list, target) fmt.Println("contains struct:", containsStruct) // Output: // contains int32: true // contains int: true // contains float64: true +// contains string: true // contains struct: true ``` -### New +### ToPtr Obtain pointers to types ```go type Vegetables string @@ -186,12 +184,12 @@ type Model struct { CreatedAt *time.Time } -func ExampleNew() { +func ExampleToPtr() { obj := Model{ - ID: xgo.New(123), - Name: xgo.New("R2D2"), - Material: xgo.New(Pea), - CreatedAt: xgo.New(time.Date(2020, 6, 1, 0, 0, 0, 0, time.UTC)), + ID: xgo.ToPtr(123), + Name: xgo.ToPtr("R2D2"), + Material: xgo.ToPtr(Pea), + CreatedAt: xgo.ToPtr(time.Date(2020, 6, 1, 0, 0, 0, 0, time.UTC)), } fmt.Println("object:", obj) } diff --git a/copier.go b/copier.go index 1da4849..e9614d9 100644 --- a/copier.go +++ b/copier.go @@ -5,16 +5,26 @@ import ( "fmt" "reflect" "time" - - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" ) // tagCopier is tag for deep copy target const tagCopier = "copier" -// DeepCopy deepcopy a struct to struct. +type SetCustomField func(src, dst reflect.Value) (bool, error) + +// DeepCopy func DeepCopy(srcModel interface{}, dstModel interface{}) error { + f := func(src, dst reflect.Value) (bool, error) { return false, nil } + return DeepCopyWithCustomSetter(srcModel, dstModel, f) +} + +// DeepCopy with custom setter +func DeepCopyWithCustomSetter( + srcModel interface{}, + dstModel interface{}, + customSetter SetCustomField, +) error { + src := reflect.Indirect(reflect.ValueOf(srcModel)) dst := reflect.Indirect(reflect.ValueOf(dstModel)) @@ -65,12 +75,13 @@ func DeepCopy(srcModel interface{}, dstModel interface{}) error { continue } + // string, int, float if srcFieldType.Type.ConvertibleTo(dstFieldType.Type) { dstFieldValue.Set(srcFieldValue.Convert(dstFieldType.Type)) continue } - isSet, err := setTimeField(srcFieldValue, dstFieldValue) + isSet, err := customSetter(srcFieldValue, dstFieldValue) if err != nil { return fmt.Errorf("%v", err) } @@ -78,11 +89,17 @@ func DeepCopy(srcModel interface{}, dstModel interface{}) error { continue } - // from struct to pointer - switch srcFieldValue.Kind() { - case reflect.Int64, reflect.Int32: - dstFieldValue.SetInt(srcFieldValue.Int()) + // set the time.Time field + isSet, err = setTimeField(srcFieldValue, dstFieldValue) + if err != nil { + return fmt.Errorf("%v", err) + } + if isSet { continue + } + + // struct, pointer, slice + switch srcFieldValue.Kind() { case reflect.Struct: if !field.Anonymous { dv, vFunc := instantiate(dstFieldValue) @@ -109,13 +126,13 @@ func DeepCopy(srcModel interface{}, dstModel interface{}) error { } dstFieldValue.Set(vFunc()) continue - case reflect.Slice: if err := copySlice(srcFieldValue, dstFieldValue); err != nil { return fmt.Errorf("%v", err) } continue } + } return nil @@ -160,108 +177,35 @@ func setTimeField(src, dst reflect.Value) (bool, error) { case time.Time: // time.Time -> *timestampps.Timestamp or int64 switch dst.Interface().(type) { - case *timestamppb.Timestamp: - if t.Unix() > 0 { - ts := timestamppb.New(t) - if err := ts.CheckValid(); err != nil { - return false, err - } - dst.Set(reflect.ValueOf(ts)) - } case int64: dst.Set(reflect.ValueOf(t.Unix())) + return true, nil case *time.Time: dst.Set(reflect.ValueOf(&t)) + return true, nil } - return true, nil + case *time.Time: - if src.IsNil() { - dst.Set(reflect.Zero(dst.Type())) - return true, nil + if t == nil { + return false, nil } // *time.Time -> timestampps.Timestamp or int64 switch dst.Interface().(type) { - case *timestamppb.Timestamp: - if t.Unix() > 0 { - ts := timestamppb.New(*t) - if err := ts.CheckValid(); err != nil { - return false, err - } - dst.Set(reflect.ValueOf(ts)) - } case int64: dst.Set(reflect.ValueOf(t.Unix())) + return true, nil case time.Time: dst.Set(reflect.ValueOf(*t)) + return true, nil } - return true, nil - case *timestamppb.Timestamp: - // *timestamppb.Timestamp -> time.Time - switch dst.Interface().(type) { - case time.Time: - if t.GetSeconds() > 0 { - ts := timestamppb.New(t.AsTime()) - if err := ts.CheckValid(); err != nil { - return false, err - } - dst.Set(reflect.ValueOf(ts.AsTime())) - } - case *time.Time: - if t.GetSeconds() > 0 { - ts := timestamppb.New(t.AsTime()) - if err := ts.CheckValid(); err != nil { - return false, err - } - time := ts.AsTime() - dst.Set(reflect.ValueOf(&time)) - } - } - return true, nil - - case time.Duration: - // time.Duration -> *durationpb.Duration - switch dst.Interface().(type) { - case *durationpb.Duration: - if t.Nanoseconds() > 0 { - dst.Set(reflect.ValueOf(durationpb.New(t))) - } - } - return true, - nil - case *durationpb.Duration: - // *durationpb.Duration -> time.Duration - switch dst.Interface().(type) { - case time.Duration: - if t == nil { - return false, nil - } - d := durationpb.New(t.AsDuration()) - if err := d.CheckValid(); err != nil { - return false, err - } - dst.Set(reflect.ValueOf(d.AsDuration())) - } - return true, nil case int64: // int64 -> time.Time or *timestamppb.Timestamp or *durationpb.Duration switch dst.Interface().(type) { case time.Time: dst.Set(reflect.ValueOf(time.Unix(t, 0))) - case *timestamppb.Timestamp: - if t > 0 { - ts := timestamppb.New(time.Unix(t, 0)) - if err := ts.CheckValid(); err != nil { - return false, err - } - dst.Set(reflect.ValueOf(ts)) - } - case *durationpb.Duration: - if t > 0 { - dst.Set(reflect.ValueOf(durationpb.New(time.Duration(t)))) - } + return true, nil } - return true, nil } return false, nil } diff --git a/copier_test.go b/copier_test.go index 5a24dbc..b0018d9 100644 --- a/copier_test.go +++ b/copier_test.go @@ -6,9 +6,6 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" "github.com/glassonion1/xgo" ) @@ -211,7 +208,7 @@ func TestDeepCopy(t *testing.T) { } } -func TestDeepCopy_Time(t *testing.T) { +func TestDeepCopy_time(t *testing.T) { type ModelA struct { CreatedAt *time.Time @@ -275,7 +272,7 @@ func TestDeepCopy_Time(t *testing.T) { } } -func TestDeepCopy_CustomType(t *testing.T) { +func TestDeepCopy_customType(t *testing.T) { type StringField struct { Name string } @@ -339,245 +336,7 @@ func TestDeepCopy_CustomType(t *testing.T) { } } -func TestDeepCopy_Protobuf(t *testing.T) { - - // Model type - type SampleType int64 - // Protobuf type - type Sample_SampleType int32 - const ( - SampleTypeAllowStandby SampleType = 1 - Sample_ALLOW_STANDBY Sample_SampleType = 1 - ) - - type ModelSample struct { - ID string `copier:"Id"` - Order int64 - CreatedAt time.Time - UpdatedAt *time.Time - SampleType SampleType - Pos struct { - Lat float64 - Lon float64 - } - } - - type PbPos struct { - Lat float64 - Lon float64 - } - - // Protobuf struct - type PbSample struct { - Id string - Order int64 - CreatedAt *timestamppb.Timestamp - UpdatedAt *timestamppb.Timestamp - SampleType Sample_SampleType - Pos *PbPos - } - - type TimestampField struct { - FinishedAt *timestamppb.Timestamp - } - - type TimeField struct { - FinishedAt time.Time - } - - type DurationPbField struct { - Duration *durationpb.Duration - } - - type DurationField struct { - Duration time.Duration - } - - type args struct { - src interface{} - dest interface{} - } - - now := time.Unix(time.Now().Unix(), 0) - - tests := []struct { - name string - in args - want interface{} - err error - }{ - { - name: "model to pb", - in: args{ - src: ModelSample{ - ID: "ididididid", - Order: 1, - CreatedAt: now, - UpdatedAt: &now, - SampleType: SampleTypeAllowStandby, - Pos: struct { - Lat float64 - Lon float64 - }{ - Lat: 1234.56, - Lon: 6543.21, - }, - }, - dest: &PbSample{}, - }, - want: &PbSample{ - Id: "ididididid", - Order: 1, - CreatedAt: ×tamppb.Timestamp{Seconds: now.Unix(), Nanos: 0}, - UpdatedAt: ×tamppb.Timestamp{Seconds: now.Unix(), Nanos: 0}, - SampleType: Sample_ALLOW_STANDBY, - Pos: &PbPos{ - Lat: 1234.56, - Lon: 6543.21, - }, - }, - err: nil, - }, - { - name: "model to pb", - in: args{ - src: ModelSample{ - ID: "ididididid", - Order: 1, - CreatedAt: now, - UpdatedAt: nil, - SampleType: SampleTypeAllowStandby, - Pos: struct { - Lat float64 - Lon float64 - }{ - Lat: 1234.56, - Lon: 6543.21, - }, - }, - dest: &PbSample{}, - }, - want: &PbSample{ - Id: "ididididid", - Order: 1, - CreatedAt: ×tamppb.Timestamp{Seconds: now.Unix(), Nanos: 0}, - UpdatedAt: nil, - SampleType: Sample_ALLOW_STANDBY, - Pos: &PbPos{ - Lat: 1234.56, - Lon: 6543.21, - }, - }, - err: nil, - }, - { - name: "pb to model", - in: args{ - src: PbSample{ - Id: "ididididid", - Order: 1, - CreatedAt: ×tamppb.Timestamp{Seconds: now.Unix(), Nanos: 0}, - UpdatedAt: ×tamppb.Timestamp{Seconds: now.Unix(), Nanos: 0}, - SampleType: Sample_ALLOW_STANDBY, - Pos: &PbPos{ - Lat: 1234.56, - Lon: 6543.21, - }, - }, - dest: &ModelSample{}, - }, - want: &ModelSample{ - ID: "ididididid", - Order: 1, - CreatedAt: now, - UpdatedAt: &now, - SampleType: SampleTypeAllowStandby, - Pos: struct { - Lat float64 - Lon float64 - }{ - Lat: 1234.56, - Lon: 6543.21, - }, - }, - err: nil, - }, - { - name: "timestamp to time zero value", - in: args{ - src: TimestampField{}, - dest: &TimeField{}, - }, - want: &TimeField{}, - err: nil, - }, - { - name: "durationPbField to duration", - in: args{ - src: &DurationPbField{ - Duration: &durationpb.Duration{Seconds: 300}, - }, - dest: &DurationField{}, - }, - want: &DurationField{Duration: 300 * time.Second}, - err: nil, - }, - { - name: "duration to durationPbField", - in: args{ - src: &DurationField{Duration: 300 * time.Second}, - dest: &DurationPbField{}, - }, - want: &DurationPbField{ - Duration: &durationpb.Duration{Seconds: 300}, - }, - err: nil, - }, - { - name: "durationPb nil Field to duration", - in: args{ - src: &DurationPbField{}, - dest: &DurationField{}, - }, - want: &DurationField{}, - err: nil, - }, - { - name: "duration zero value to durationPbField", - in: args{ - src: &DurationField{}, - dest: &DurationPbField{}, - }, - want: &DurationPbField{}, - err: nil, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - err := xgo.DeepCopy(tt.in.src, tt.in.dest) - got := tt.in.dest - if tt.err == nil && err != nil { - t.Errorf("testing %s: should not be error for %#v but: %v", tt.name, tt.in, err) - } - if tt.err != nil && err == nil { - t.Errorf("testing %s: should be error for %#v but not:", tt.name, tt.in) - } - if tt.err != nil && err != tt.err { - t.Errorf("testing %s: should be error of %v but got: %v", tt.name, tt.err, err) - } - opt := cmpopts.IgnoreUnexported(timestamppb.Timestamp{}, - durationpb.Duration{}) - if diff := cmp.Diff(tt.want, got, opt); diff != "" { - t.Errorf("testing %s mismatch (-want +got):\n%s\n", tt.name, diff) - } - }) - } -} - -func TestDeepCopy_Private(t *testing.T) { +func TestDeepCopy_private(t *testing.T) { type PrivateField struct { ID string name string @@ -628,7 +387,7 @@ func TestDeepCopy_Private(t *testing.T) { } } -func TestDeepCopy_Slice(t *testing.T) { +func TestDeepCopy_slice(t *testing.T) { type Model1 struct { Foo string Bar int @@ -712,7 +471,7 @@ func TestDeepCopy_Slice(t *testing.T) { } } -func TestDeepCopy_Slice2(t *testing.T) { +func TestDeepCopy_slice2(t *testing.T) { type Model1 struct { Foo string Bar int diff --git a/example_test.go b/example_test.go index 9fedd23..91be31c 100644 --- a/example_test.go +++ b/example_test.go @@ -5,11 +5,9 @@ import ( "time" "github.com/glassonion1/xgo" - "google.golang.org/protobuf/types/known/timestamppb" ) func ExampleDeepCopy_struct() { - // It is a common, ordinary struct type FromModel struct { ID string `copier:"Id"` Name string @@ -17,15 +15,14 @@ func ExampleDeepCopy_struct() { UpdatedAt *time.Time } - // It is like a protobuf struct on gRPC type ToModel struct { Id string Name string - CreatedAt *timestamppb.Timestamp - UpdatedAt *timestamppb.Timestamp + CreatedAt time.Time + UpdatedAt *time.Time } - now := time.Date(2020, 6, 1, 0, 0, 0, 0, time.UTC) + now := time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC) from := FromModel{ ID: "xxxx", Name: "R2D2", @@ -39,7 +36,7 @@ func ExampleDeepCopy_struct() { } fmt.Println("ToModel object:", to) - // Output: ToModel object: &{xxxx R2D2 seconds:1590969600 seconds:1590969600} + // Output: ToModel object: &{xxxx R2D2 2025-06-01 00:00:00 +0000 UTC 2025-06-01 00:00:00 +0000 UTC} } func ExampleDeepCopy_slice() { @@ -73,17 +70,21 @@ func ExampleDeepCopy_slice() { func ExampleContains() { // slice of int32 - containsInt32 := xgo.Contains[int32]([]int32{1, 2, 3, 4, 5}, 3) + containsInt32 := xgo.Contains([]int32{1, 2, 3, 4, 5}, 3) fmt.Println("contains int32:", containsInt32) // slice of int - containsInt := xgo.Contains[int]([]int{1, 2, 3, 4, 5}, 2) + containsInt := xgo.Contains([]int{1, 2, 3, 4, 5}, 2) fmt.Println("contains int:", containsInt) // slice of float64 - containsFloat64 := xgo.Contains[float64]([]float64{1.1, 2.2, 3.3, 4.4, 5.5}, 4.4) + containsFloat64 := xgo.Contains([]float64{1.1, 2.2, 3.3, 4.4, 5.5}, 4.4) fmt.Println("contains float64:", containsFloat64) + // slice of string + containsString := xgo.Contains([]string{"r2d2", "c3po", "bb8"}, "c3po") + fmt.Println("contains string:", containsString) // -> true + // slice of struct type hero struct { ID string @@ -107,13 +108,14 @@ func ExampleContains() { ID: "2", Name: "Han Solo", } - containsStruct := xgo.Contains[hero](list, target) + containsStruct := xgo.Contains(list, target) fmt.Println("contains struct:", containsStruct) // Output: // contains int32: true // contains int: true // contains float64: true + // contains string: true // contains struct: true } @@ -132,12 +134,12 @@ type Model struct { CreatedAt *time.Time } -func ExampleNew() { +func ExampleToPtr() { obj := Model{ - ID: xgo.New(123), - Name: xgo.New("R2D2"), - Material: xgo.New(Pea), - CreatedAt: xgo.New(time.Date(2020, 6, 1, 0, 0, 0, 0, time.UTC)), + ID: xgo.ToPtr(123), + Name: xgo.ToPtr("R2D2"), + Material: xgo.ToPtr(Pea), + CreatedAt: xgo.ToPtr(time.Date(2020, 6, 1, 0, 0, 0, 0, time.UTC)), } fmt.Println("object:", obj) } diff --git a/go.mod b/go.mod index 52de4fa..487a4f0 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,5 @@ module github.com/glassonion1/xgo -go 1.20 +go 1.23 require github.com/google/go-cmp v0.6.0 - -require google.golang.org/protobuf v1.34.2 diff --git a/go.sum b/go.sum index 678260f..5a8d551 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,2 @@ -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= diff --git a/new.go b/new.go index e84a39c..edc6428 100644 --- a/new.go +++ b/new.go @@ -1,3 +1,16 @@ package xgo -func New[T any](value T) *T { return &value } +// Deprecated: Use ToPtr instead. +func New[T any](x T) *T { return &x } + +// ToPtr returns a pointer copy of value. +func ToPtr[T any](x T) *T { return &x } + +// FromPtr returns the pointer value or empty. +func FromPtr[T any](x *T) T { + if x == nil { + var zero T + return zero + } + return *x +}