Skip to content

Commit

Permalink
feat: Add Pointer Package (#411)
Browse files Browse the repository at this point in the history
<!--
  !!!! README !!!! Please fill this out.

  Please follow the PR naming conventions: 

https://outreach-io.atlassian.net/wiki/spaces/EN/pages/1902444645/Conventional+Commits
-->


<!-- A short description of what your PR does and what it solves. -->
## 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)

<!-- <<Stencil::Block(jiraPrefix)>> -->

## Jira ID

No Jira, just an idea of package that would be nice to have

<!-- <</Stencil::Block>> -->

<!-- Notes that may be helpful for anyone reviewing this PR -->
## Notes for your reviewers



<!-- <<Stencil::Block(custom)>> -->

<!-- <</Stencil::Block>> -->
  • Loading branch information
romain-tadiello authored Nov 20, 2023
1 parent 7280625 commit c8b3c7e
Show file tree
Hide file tree
Showing 2 changed files with 314 additions and 0 deletions.
64 changes: 64 additions & 0 deletions pkg/pointer/pointer.go
Original file line number Diff line number Diff line change
@@ -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
}
250 changes: 250 additions & 0 deletions pkg/pointer/pointer_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit c8b3c7e

Please sign in to comment.