From c8b3c7e3c0a07141f3f68cd3a49b7d6ca1b6449c Mon Sep 17 00:00:00 2001 From: Romain Tadiello <110475851+romain-tadiello@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:40:48 +0100 Subject: [PATCH] feat: Add Pointer Package (#411) ## What this PR does / why we need it This PR aims to add a pointer package to be used across our services and librairies. I had to do pointer conversion in API-Proxy [here](https://github.com/getoutreach/apiproxy/blob/f3707830d6d25f94057182027037c709d7a14c4d/internal/apiv2/openapi.go#L33) and saw `ptr.SomeType()` in several places (not using generic). As we are using gql federation in our services we will surely need more pointer conversion and I would like to have the possibility to use generics. - Added tests with different types including some that are not supported by [github.com/aws/smithy-go/blob/main/ptr](https://github.com/aws/smithy-go/blob/main/ptr) ## Jira ID No Jira, just an idea of package that would be nice to have ## Notes for your reviewers --- pkg/pointer/pointer.go | 64 +++++++++ pkg/pointer/pointer_test.go | 250 ++++++++++++++++++++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 pkg/pointer/pointer.go create mode 100644 pkg/pointer/pointer_test.go diff --git a/pkg/pointer/pointer.go b/pkg/pointer/pointer.go new file mode 100644 index 00000000..18a44849 --- /dev/null +++ b/pkg/pointer/pointer.go @@ -0,0 +1,64 @@ +// Copyright 2023 Outreach Corporation. All Rights Reserved. + +// Description: Implements the pointer package. + +// Package pointer is an attempt to provide functions to convert data to pointers using generics. +// inspired by https://pkg.go.dev/github.com/aws/smithy-go/ptr +// This is intended to replace the following patterns that we've seen across our codebase: +// - myVar := "value"; return &myVar +// - ptr.{SomeType}("value") +// Also Enables the possibility to use: var res *myStructType = ToPtr(myStructType{}) and any derivate +// with map / slice +package pointer + +// ToPtr returns a pointer to {object} of type *T +func ToPtr[T any](object T) *T { + return &object +} + +// ToSlicePtr returns a slice of pointers of type *T pointing to each element of {objects} +func ToSlicePtr[T any](objects []T) []*T { + ptrObj := make([]*T, 0, len(objects)) + for _, o := range objects { + ptrObj = append(ptrObj, ToPtr(o)) + } + return ptrObj +} + +// ToMapPtr returns a map of pointers of type *T pointing to each element of {objectMap} +func ToMapPtr[K comparable, T any](objectMap map[K]T) map[K]*T { + ptrObj := make(map[K]*T, len(objectMap)) + for k, o := range objectMap { + ptrObj[k] = ToPtr(o) + } + return ptrObj +} + +// ToValue returns the value of type T pointed by {ptr} +// if {ptr} is nil, return 0 value of T +func ToValue[T any](ptr *T) (res T) { + if ptr == nil { + return res + } + return *ptr +} + +// ToSliceValue returns a slice of values of type T pointed by each pointers in {ptrs} +// if a pointer in {ptrs} is nil the value at the corresponding index will be the 0 of T +func ToSliceValue[T any](ptrs []*T) []T { + res := make([]T, 0, len(ptrs)) + for _, ptr := range ptrs { + res = append(res, ToValue(ptr)) + } + return res +} + +// ToSliceValue returns a map of values of type T pointed by each pointers in {ptrs} +// if a pointer in {ptrs} is nil the value at the corresponding key will be the 0 of T +func ToMapValue[K comparable, T any](ptrs map[K]*T) map[K]T { + res := make(map[K]T, len(ptrs)) + for v, ptr := range ptrs { + res[v] = ToValue(ptr) + } + return res +} diff --git a/pkg/pointer/pointer_test.go b/pkg/pointer/pointer_test.go new file mode 100644 index 00000000..33443571 --- /dev/null +++ b/pkg/pointer/pointer_test.go @@ -0,0 +1,250 @@ +package pointer + +import ( + "testing" +) + +type customStruct struct { + name string +} + +func TestPtrStruct(t *testing.T) { + oPtr := ToPtr(customStruct{name: "value"}) + if oPtr == nil { + t.Error("expected a non-nil pointer") + } + s := *oPtr + if s.name != "value" { + t.Errorf("expected %s, but received %s", "value", s.name) + } + oValue := ToValue(oPtr) + if oValue.name != "value" { + t.Errorf("expected %s, but received %s", "value", oValue.name) + } +} + +func TestSlicePtrStruct(t *testing.T) { + value := []customStruct{{name: "one"}, {name: "two"}} + oSlice := ToSlicePtr(value) + if len(oSlice) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oSlice)) + } + for i, v := range value { + if oSlice[i] == nil { + t.Error("unexpected nil value in slice") + } + s := *oSlice[i] + if s.name != v.name { + t.Errorf("expected value of size %s, but got %s", v.name, s.name) + } + } + oSliceValue := ToSliceValue(oSlice) + if len(oSliceValue) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oSliceValue)) + } + for i, v := range value { + if oSliceValue[i].name != v.name { + t.Errorf("expected value of size %s, but got %s", v.name, oSliceValue[i].name) + } + } +} + +func TestMapPtrStruct(t *testing.T) { + value := map[string]customStruct{ + "1": {name: "One"}, + "2": {name: "Two"}, + "10": {name: "Three??"}, + } + oMap := ToMapPtr(value) + if len(oMap) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oMap)) + } + for i, v := range value { + if oMap[i] == nil { + t.Error("unexpected nil value in slice") + } + s := *oMap[i] + if s.name != v.name { + t.Errorf("expected value of size %s, but got %s", v.name, s.name) + } + } + oMapValue := ToMapValue(oMap) + if len(oMapValue) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oMapValue)) + } + for i, v := range value { + if oMapValue[i].name != v.name { + t.Errorf("expected value of size %s, but got %s", v.name, oMapValue[i].name) + } + } +} + +func TestPtrString(t *testing.T) { + oPtr := ToPtr("value") + if oPtr == nil { + t.Error("expected a non-nil pointer") + } + if *oPtr != "value" { + t.Errorf("expected %s, but received %s", "value", *oPtr) + } + oValue := ToValue(oPtr) + if oValue != "value" { + t.Errorf("expected %s, but received %s", "value", oValue) + } +} + +func TestSlicePtrString(t *testing.T) { + value := []string{"a", "b", "c"} + oSlice := ToSlicePtr(value) + if len(oSlice) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oSlice)) + } + for i, v := range value { + if oSlice[i] == nil { + t.Error("unexpected nil value in slice") + } + if *oSlice[i] != v { + t.Errorf("expected value of size %s, but got %s", v, *oSlice[i]) + } + } + oSliceValue := ToSliceValue(oSlice) + if len(oSliceValue) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oSliceValue)) + } + for i, v := range value { + if oSliceValue[i] != v { + t.Errorf("expected value of size %s, but got %s", v, oSliceValue[i]) + } + } +} + +func TestMapPtrString(t *testing.T) { + value := map[int]string{ + 1: "a", + 2: "b", + 10: "c", + } + oMap := ToMapPtr(value) + if len(oMap) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oMap)) + } + for i, v := range value { + if oMap[i] == nil { + t.Error("unexpected nil value in slice") + } + if *oMap[i] != v { + t.Errorf("expected value of size %s, but got %s", v, *oMap[i]) + } + } + oMapValue := ToMapValue(oMap) + if len(oMapValue) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oMapValue)) + } + for i, v := range value { + if oMapValue[i] != v { + t.Errorf("expected value of size %s, but got %s", v, oMapValue[i]) + } + } +} + +func TestPtrInterface(t *testing.T) { + var value interface{} = "some interface" + oPtr := ToPtr(value) + if oPtr == nil { + t.Error("expected a non-nil pointer") + } + if *oPtr != value { + t.Errorf("expected %s, but received %s", "value", *oPtr) + } + oValue := ToValue(oPtr) + if oValue != value { + t.Errorf("expected %s, but received %s", "value", oValue) + } +} + +func TestSlicePtrInterface(t *testing.T) { + value := []interface{}{"a", 2, true} + oSlice := ToSlicePtr(value) + if len(oSlice) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oSlice)) + } + for i, v := range value { + if oSlice[i] == nil { + t.Error("unexpected nil value in slice") + } + if *oSlice[i] != v { + t.Errorf("expected value of size %s, but got %s", v, *oSlice[i]) + } + } + oSliceValue := ToSliceValue(oSlice) + if len(oSliceValue) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oSliceValue)) + } + for i, v := range value { + if oSliceValue[i] != v { + t.Errorf("expected value of size %s, but got %s", v, oSliceValue[i]) + } + } +} + +func TestMapPtrInterface(t *testing.T) { + value := map[int]interface{}{ + 1: "a", + 2: 50, + 10: true, + } + oMap := ToMapPtr(value) + if len(oMap) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oMap)) + } + for i, v := range value { + if oMap[i] == nil { + t.Error("unexpected nil value in slice") + } + if *oMap[i] != v { + t.Errorf("expected value of size %s, but got %s", v, *oMap[i]) + } + } + oMapValue := ToMapValue(oMap) + if len(oMapValue) != len(value) { + t.Errorf("expected slice of size %d, but got %d", len(value), len(oMapValue)) + } + for i, v := range value { + if oMapValue[i] != v { + t.Errorf("expected value of size %s, but got %s", v, oMapValue[i]) + } + } +} + +func TestPtrFunc(t *testing.T) { + f := func() string { + return "value" + } + oPtr := ToPtr(f) + if oPtr == nil { + t.Error("expected a non-nil pointer") + } + s := *oPtr + if s() != "value" { + t.Errorf("expected %s, but received %s", "value", s()) + } + oValue := ToValue(oPtr) + if oValue() != "value" { + t.Errorf("expected %s, but received %s", "value", oValue()) + } +} + +func TestPtrOfPtr(t *testing.T) { + value := ToPtr("value") + oPtr := ToPtr(value) + if oPtr == nil { + t.Error("expected a non-nil pointer") + } + if *oPtr != value { + t.Errorf("expected %p, but received %p", *oPtr, value) + } + oValue := ToValue(oPtr) + if oValue != value { + t.Errorf("expected %p, but received %p", oValue, value) + } +}