From 3f4a72354177175eea7d5ef98c3e06d09ebd1bfd Mon Sep 17 00:00:00 2001 From: Timothy Messier Date: Mon, 9 Oct 2023 19:36:51 +0000 Subject: [PATCH] feat(action): Add registrar for valid actions by resource --- internal/types/action/registrar.go | 73 +++++++++++++++++++++ internal/types/action/registrar_ext_test.go | 71 ++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 internal/types/action/registrar.go create mode 100644 internal/types/action/registrar_ext_test.go diff --git a/internal/types/action/registrar.go b/internal/types/action/registrar.go new file mode 100644 index 0000000000..310079f4eb --- /dev/null +++ b/internal/types/action/registrar.go @@ -0,0 +1,73 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package action + +import ( + "fmt" + "sync" + + "github.com/hashicorp/boundary/internal/types/resource" +) + +type resourceActionSets struct { + individual ActionSet + collection ActionSet + valid ActionSet +} + +type byResource struct { + mu sync.RWMutex + m map[resource.Type]*resourceActionSets +} + +func (b *byResource) add(r resource.Type, individual ActionSet, collection ActionSet) error { + b.mu.Lock() + defer b.mu.Unlock() + + if b.m == nil { + b.m = make(map[resource.Type]*resourceActionSets) + } + + _, ok := b.m[r] + if ok { + return fmt.Errorf("%s already registered", r) + } + b.m[r] = &resourceActionSets{ + individual: individual, + collection: collection, + valid: Union(individual, collection), + } + + return nil +} + +func (b *byResource) get(r resource.Type) (*resourceActionSets, error) { + b.mu.RLock() + defer b.mu.RUnlock() + a, ok := b.m[r] + if !ok { + return nil, fmt.Errorf("resource not found: %s", r) + } + return a, nil +} + +var byResourceRegistrar = &byResource{} + +// RegisterResource registers ActionSets for r. If RegisterResource is called +// twice with the same resource, it panics. +func RegisterResource(r resource.Type, individual ActionSet, collection ActionSet) { + if err := byResourceRegistrar.add(r, individual, collection); err != nil { + panic(err) + } +} + +// ActionSetForResource returns the ActionSet registered for r +// or an error if r has not been registered. +func ActionSetForResource(r resource.Type) (ActionSet, error) { + a, err := byResourceRegistrar.get(r) + if err != nil { + return nil, err + } + return a.valid, nil +} diff --git a/internal/types/action/registrar_ext_test.go b/internal/types/action/registrar_ext_test.go new file mode 100644 index 0000000000..9c42f6a76e --- /dev/null +++ b/internal/types/action/registrar_ext_test.go @@ -0,0 +1,71 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package action_test + +import ( + "errors" + "testing" + + "github.com/hashicorp/boundary/internal/types/action" + "github.com/hashicorp/boundary/internal/types/resource" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRegisterResource(t *testing.T) { + t.Run("Pancis", func(t *testing.T) { + // Ensure the resource is not registered yet + _, err := action.ActionSetForResource(resource.Session) + require.EqualError(t, err, "resource not found: session") + + // Now register the resource + action.RegisterResource(resource.Session, action.NewActionSet(action.Read, action.Create), action.NewActionSet(action.List)) + got, err := action.ActionSetForResource(resource.Session) + require.NoError(t, err) + assert.Equal(t, action.NewActionSet(action.Read, action.Create, action.List), got) + + // Attempting to register the same resource again should panic + require.Panics( + t, + func() { + action.RegisterResource(resource.Session, action.NewActionSet(action.Update, action.Read, action.Create), action.NewActionSet(action.List)) + }, + ) + }) + + // Run these after the Pancis test rely on resource.Session already being registered. + t.Run("ActionSetForResource", func(t *testing.T) { + cases := []struct { + name string + res resource.Type + want action.ActionSet + wantErr error + }{ + { + "NotRegistered", + resource.Target, + nil, + errors.New("resource not found: target"), + }, + { + "Registered", + resource.Session, + action.NewActionSet(action.Read, action.Create, action.List), + nil, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := action.ActionSetForResource(tc.res) + if tc.wantErr != nil { + require.EqualError(t, err, tc.wantErr.Error()) + return + } + + require.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } + }) +}