From d41ec88670112d361f77b5daa8c1a669ec14b793 Mon Sep 17 00:00:00 2001 From: James Howe Date: Wed, 28 Aug 2024 12:33:59 +0100 Subject: [PATCH] Added PtrTo and ConvertAll functions --- CHANGELOG.md | 4 + ptr/ptr.go | 5 ++ ptr/ptr_test.go | 51 +++++++++++++ slice/slice.go | 19 +++++ slice/slice_test.go | 174 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 253 insertions(+) create mode 100644 ptr/ptr.go create mode 100644 ptr/ptr_test.go create mode 100644 slice/slice.go create mode 100644 slice/slice_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 301cd58..e5cd5c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.2.0 +* Added `PtrTo` function for creating a pointer to any type +* Added `ConvertAll` function for safely converting all types of a slice to another where compatible + ## v0.1.0 * Renamed from `utils` to `go-utils`. diff --git a/ptr/ptr.go b/ptr/ptr.go new file mode 100644 index 0000000..0b333b7 --- /dev/null +++ b/ptr/ptr.go @@ -0,0 +1,5 @@ +package ptr + +func PtrTo[T any](v T) *T { + return &v +} diff --git a/ptr/ptr_test.go b/ptr/ptr_test.go new file mode 100644 index 0000000..080b2b8 --- /dev/null +++ b/ptr/ptr_test.go @@ -0,0 +1,51 @@ +package ptr + +import ( + "testing" +) + +func TestPtrToReturnsStrPtr(t *testing.T) { + actual := PtrTo("Hello") + want := "Hello" + if want != *actual { + t.Fatalf(`Ptr(string) = *"%s", wanted *"%s"`, *actual, want) + } +} + +func TestPtrReturnsIntPtr(t *testing.T) { + actual := PtrTo(500) + want := 500 + if want != *actual { + t.Fatalf(`Ptr(int) = *%d, wanted *%d`, *actual, want) + } +} + +func TestPtrReturnsFloat32Ptr(t *testing.T) { + actual := PtrTo(float32(100.50)) + want := float32(100.50) + if want != *actual { + t.Fatalf(`Ptr(float32) = *%f, wanted *%f`, *actual, want) + } +} + +func TestPtrReturnsFloat64Ptr(t *testing.T) { + actual := PtrTo(float32(100.50)) + want := float32(100.50) + if want != *actual { + t.Fatalf(`Ptr(float64) = %v, wanted %v`, *actual, want) + } +} + +func TestPtrReturnsStructPtr(t *testing.T) { + actual := PtrTo(struct { + Str string + Num int + }{Str: "Hello", Num: 2}) + want := struct { + Str string + Num int + }{Str: "Hello", Num: 2} + if want != *actual { + t.Fatalf(`Ptr(struct) = %v, wanted %v`, actual, &want) + } +} diff --git a/slice/slice.go b/slice/slice.go new file mode 100644 index 0000000..e9a38e3 --- /dev/null +++ b/slice/slice.go @@ -0,0 +1,19 @@ +package slice + +import ( + "fmt" + "reflect" +) + +func ConvertAll[K interface{}, T interface{}](source []T) ([]K, error) { + converted := []K{} + for _, i := range source { + intf, ok := any(i).(K) + if !ok { + var zeroValue K + return nil, fmt.Errorf("failed to convert source item (sourceType=%+v, targetType=%+v)", reflect.TypeOf(i), reflect.TypeOf(zeroValue)) + } + converted = append(converted, intf) + } + return converted, nil +} diff --git a/slice/slice_test.go b/slice/slice_test.go new file mode 100644 index 0000000..f4fe73e --- /dev/null +++ b/slice/slice_test.go @@ -0,0 +1,174 @@ +package slice + +import ( + "reflect" + "testing" +) + +type Driveable interface { + Drive() +} + +type Chargeable interface { + Charge() +} + +type Car struct{} + +func (c Car) Drive() {} + +type ElectricCar struct { + Car +} + +func (c ElectricCar) Charge() {} + +type Motorbike struct{} + +func (b Motorbike) Drive() {} + +type ElectricMotorbike struct { + Motorbike +} + +func (b ElectricMotorbike) Charge() {} + +func TestConvertAllConvertsAnyToStrings(t *testing.T) { + input := []any{"1", "2", "3"} + actual, err := ConvertAll[string](input) + want := "[]string" + if err != nil { + t.Fatal(err) + } + if len(actual) != len(input) { + t.Fatalf("Expected %d results, got %d", len(actual), len(input)) + } + if reflect.TypeOf(actual).String() != want { + t.Fatalf("ConvertAll() = %+v, wanted %+v", reflect.TypeOf(actual).String(), want) + } +} + +func TestConvertAllConvertsAnyToInts(t *testing.T) { + input := []any{1, 2, 3} + actual, err := ConvertAll[int](input) + want := "[]int" + if err != nil { + t.Fatal(err) + } + if len(actual) != len(input) { + t.Fatalf("Expected %d results, got %d", len(actual), len(input)) + } + if reflect.TypeOf(actual).String() != want { + t.Fatalf("ConvertAll() = %+v, wanted %+v", reflect.TypeOf(actual).String(), want) + } +} + +func TestConvertAllConvertsAnyToFloats(t *testing.T) { + input := []any{1.0, 2.0, 3.0} + actual, err := ConvertAll[float64](input) + want := "[]float64" + if err != nil { + t.Fatal(err) + } + if len(actual) != len(input) { + t.Fatalf("Expected %d results, got %d", len(actual), len(input)) + } + if reflect.TypeOf(actual).String() != want { + t.Fatalf("ConvertAll() = %+v, wanted %+v", reflect.TypeOf(actual).String(), want) + } +} + +func TestConvertAllConvertsAnyToInterfaces(t *testing.T) { + input := []any{Car{}, Motorbike{}, ElectricCar{}} + actual, err := ConvertAll[Driveable](input) + want := "[]slice.Driveable" + if err != nil { + t.Fatal(err) + } + if len(actual) != len(input) { + t.Fatalf("Expected %d results, got %d", len(actual), len(input)) + } + if reflect.TypeOf(actual).String() != want { + t.Fatalf("ConvertAll() = %+v, wanted %+v", reflect.TypeOf(actual).String(), want) + } +} + +func TestConvertAllConvertsStructsToInterfaces(t *testing.T) { + input := []Car{{}, {}, {}} + actual, err := ConvertAll[Driveable](input) + want := "[]slice.Driveable" + if err != nil { + t.Fatal(err) + } + if len(actual) != len(input) { + t.Fatalf("Expected %d results, got %d", len(actual), len(input)) + } + if reflect.TypeOf(actual).String() != want { + t.Fatalf("ConvertAll() = %+v, wanted %+v", reflect.TypeOf(actual).String(), want) + } +} + +func TestConvertAllConvertsInterfacesToStructs(t *testing.T) { + input := []Driveable{Car{}, Car{}, Car{}} + actual, err := ConvertAll[Car](input) + want := "[]slice.Car" + if err != nil { + t.Fatal(err) + } + if len(actual) != len(input) { + t.Fatalf("Expected %d results, got %d", len(actual), len(input)) + } + if reflect.TypeOf(actual).String() != want { + t.Fatalf("ConvertAll() = %+v, wanted %+v", reflect.TypeOf(actual).String(), want) + } +} + +func TestConvertAllConvertsInterfacesToInterfaces(t *testing.T) { + input := []Driveable{ElectricCar{}, ElectricMotorbike{}, ElectricCar{}} + actual, err := ConvertAll[Chargeable](input) + want := "[]slice.Chargeable" + if err != nil { + t.Fatal(err) + } + if len(actual) != len(input) { + t.Fatalf("Expected %d results, got %d", len(actual), len(input)) + } + if reflect.TypeOf(actual).String() != want { + t.Fatalf("ConvertAll() = %+v, wanted %+v", reflect.TypeOf(actual).String(), want) + } +} + +func TestConvertAllReturnsNilResultErrorWhenConversionFails(t *testing.T) { + input := []any{"1", "2", 3} + actual, err := ConvertAll[string](input) + if err == nil { + t.Fatal("ConvertAll() should have failed with error") + } + if actual != nil { + t.Fatalf("Expected error nil, got '%+v'", actual) + } +} + +func TestConvertAllFailsWithErrorWhenConvertingIncompatibleBuiltinTypes(t *testing.T) { + input := []any{"1", "2", 3} + _, err := ConvertAll[string](input) + want := "failed to convert source item (sourceType=int, targetType=string)" + if err == nil { + t.Fatal("ConvertAll() should have failed with error") + } + if err.Error() != want { + t.Fatalf("Expected error '%s', got '%s'", want, err.Error()) + } +} + +func TestConvertAllFailsWithErrorWhenConvertingUnsupportedInterfaceTypes(t *testing.T) { + input := []Driveable{Car{}, ElectricMotorbike{}, ElectricCar{}} + _, err := ConvertAll[Chargeable](input) + want := "failed to convert source item (sourceType=slice.Car, targetType=)" + if err == nil { + t.Fatal("ConvertAll() should have failed with error") + } + if err.Error() != want { + t.Fatalf("Expected error '%s', got '%s'", want, err.Error()) + } +}