Skip to content

Commit

Permalink
Added PtrTo and ConvertAll functions
Browse files Browse the repository at this point in the history
  • Loading branch information
jameshowe committed Aug 28, 2024
1 parent f3d003e commit d41ec88
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down
5 changes: 5 additions & 0 deletions ptr/ptr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ptr

func PtrTo[T any](v T) *T {
return &v
}
51 changes: 51 additions & 0 deletions ptr/ptr_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
19 changes: 19 additions & 0 deletions slice/slice.go
Original file line number Diff line number Diff line change
@@ -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
}
174 changes: 174 additions & 0 deletions slice/slice_test.go
Original file line number Diff line number Diff line change
@@ -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=<nil>)"
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())
}
}

0 comments on commit d41ec88

Please sign in to comment.