Skip to content

Commit

Permalink
feat: add FillEntityID function
Browse files Browse the repository at this point in the history
  • Loading branch information
czeslavo committed Mar 23, 2023
1 parent 2576497 commit 5df24bc
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.19
replace github.com/imdario/mergo v0.3.12 => github.com/Kong/mergo v0.3.13

require (
github.com/gofrs/uuid v4.4.0+incompatible
github.com/google/go-cmp v0.5.9
github.com/google/go-querystring v1.1.0
github.com/google/uuid v1.3.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXym
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down
68 changes: 68 additions & 0 deletions kong/ids.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package kong

import (
"fmt"

"github.com/gofrs/uuid"
)

// IDFillableEntitiesT is a type constraint for entities that can be filled with an ID.
type IDFillableEntitiesT interface {
Service | Route
}

// FillEntityID fills the ID of an entity. It is a no-op if the entity already has an ID.
// ID is generated in a deterministic way using UUIDv5. The UUIDv5 namespace is different for each entity type.
// The name used to generate the UUIDv5 is:
// - for Service: Service.Name
// - for Route: Route.Name
func FillEntityID[T IDFillableEntitiesT](entity *T) error {
switch e := any(entity).(type) {
case *Service:
return fillIDForService(e)
case *Route:
return fillIDForRoute(e)
default:
return fmt.Errorf("unsupported entity: '%T'", entity)
}
}

// The following variables are UUIDv5 namespaces used to generate IDs for entities, one per each entity type.
var (
serviceIDsNamespace = uuid.Must(uuid.FromString("497d0cb2-5630-40a7-9858-8f210e6295f4"))
routeIDsNamespace = uuid.Must(uuid.FromString("6324c31d-0568-480f-8d8c-a5c1f4c8eb6c"))
)

func fillIDForService(svc *Service) error {
if svc == nil {
return fmt.Errorf("service is nil")
}
if svc.ID != nil {
// ID already set, do nothing.
return nil
}
if svc.Name == nil || *svc.Name == "" {
return fmt.Errorf("service name is required")
}

id := uuid.NewV5(serviceIDsNamespace, *svc.Name)
svc.ID = String(id.String())
return nil
}

func fillIDForRoute(route *Route) error {
if route == nil {
return fmt.Errorf("route is nil")
}
if route.ID != nil {
// ID already set, do nothing.
return nil
}
if route.Name == nil || *route.Name == "" {
return fmt.Errorf("route name is required")
}

id := uuid.NewV5(routeIDsNamespace, *route.Name)
route.ID = String(id.String())
return nil
}
111 changes: 111 additions & 0 deletions kong/ids_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package kong_test

import (
"testing"

"github.com/kong/go-kong/kong"
"github.com/stretchr/testify/require"
)

type fillEntityIDTestCase[T kong.IDFillableEntitiesT] struct {
name string
entity *T

assertEntity func(t *testing.T, entity *T)
expectErr bool
}

func (tc fillEntityIDTestCase[T]) run(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
err := kong.FillEntityID(tc.entity)
if tc.expectErr {
require.Error(t, err)
return
}
require.NoError(t, err)
tc.assertEntity(t, tc.entity)
})
}

func TestFillEntityID(t *testing.T) {
t.Run("Service", func(t *testing.T) {
testCases := []fillEntityIDTestCase[kong.Service]{
{
name: "service nil pointer",
entity: (*kong.Service)(nil),
expectErr: true,
},
{
name: "service with nil name",
entity: &kong.Service{},
assertEntity: func(t *testing.T, svc *kong.Service) {
require.Nil(t, svc.ID, "ID should not be set when name is nil")
},
expectErr: true,
},
{
name: "service with empty name",
entity: &kong.Service{Name: kong.String("")},
assertEntity: func(t *testing.T, svc *kong.Service) {
require.Nil(t, svc.ID, "ID should not be set when name is empty")
},
expectErr: true,
},
{
name: "service with name",
entity: &kong.Service{
Name: kong.String("some.service.name"),
},
assertEntity: func(t *testing.T, svc *kong.Service) {
require.NotNil(t, svc.ID)
const expectedID = "14a84f13-96ef-5628-b300-8bcd5a509f9b"
require.Equal(t, expectedID, *svc.ID, "ID should be deterministic")
},
},
}
for _, tc := range testCases {
tc.run(t)
}
})

t.Run("Route", func(t *testing.T) {
testCases := []fillEntityIDTestCase[kong.Route]{
{
name: "route nil pointer",
entity: (*kong.Route)(nil),
expectErr: true,
},
{
name: "route with nil name",
entity: &kong.Route{},
assertEntity: func(t *testing.T, route *kong.Route) {
require.Nil(t, route.ID, "ID should not be set when name is nil")
},
expectErr: true,
},
{
name: "route with empty name",
entity: &kong.Route{Name: kong.String("")},
assertEntity: func(t *testing.T, route *kong.Route) {
require.Nil(t, route.ID, "ID should not be set when name is empty")
},
expectErr: true,
},
{
name: "route with name",
entity: &kong.Route{
Name: kong.String("some.service.name"),
},
assertEntity: func(t *testing.T, route *kong.Route) {
require.NotNil(t, route.ID)

const expectedID = "9279da23-17de-5cca-b1da-a60d7cec6802"
require.Equal(t, expectedID, *route.ID, "ID should be deterministic")
},
},
}
for _, tc := range testCases {
tc.run(t)
}
})
}

0 comments on commit 5df24bc

Please sign in to comment.