From 2c75ebc999da0305dc1e708a4561ef62976e09fb Mon Sep 17 00:00:00 2001 From: johnabass Date: Wed, 7 Aug 2024 19:47:49 -0700 Subject: [PATCH 1/3] defined a listener API for noteworth bascule events --- eventType_string.go | 27 +++++++++ listener.go | 140 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 eventType_string.go create mode 100644 listener.go diff --git a/eventType_string.go b/eventType_string.go new file mode 100644 index 0000000..ff915b8 --- /dev/null +++ b/eventType_string.go @@ -0,0 +1,27 @@ +// Code generated by "stringer -type=EventType -trimprefix=EventType -output=eventType_string.go"; DO NOT EDIT. + +package bascule + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[EventTypeSuccess-0] + _ = x[EventTypeMissingCredentials-1] + _ = x[EventTypeInvalidCredentials-2] + _ = x[EventTypeFailedAuthentication-3] + _ = x[EventTypeFailedAuthorization-4] +} + +const _EventType_name = "SuccessMissingCredentialsInvalidCredentialsFailedAuthenticationFailedAuthorization" + +var _EventType_index = [...]uint8{0, 7, 25, 43, 63, 82} + +func (i EventType) String() string { + if i < 0 || i >= EventType(len(_EventType_index)-1) { + return "EventType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _EventType_name[_EventType_index[i]:_EventType_index[i+1]] +} diff --git a/listener.go b/listener.go new file mode 100644 index 0000000..208978f --- /dev/null +++ b/listener.go @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + +package bascule + +//go:generate stringer -type=EventType -trimprefix=EventType -output=eventType_string.go + +// EventType describes the kind of event bascule has dispatched. +type EventType int + +const ( + // EventTypeSuccess represents a completely successful authorization. + // The event's Token field will be set to resulting token. + EventTypeSuccess EventType = iota + + // EventTypeMissingCredentials represents a source that missing any + // kind of credentials that are recognizable to the way bascule + // has been configured. + EventTypeMissingCredentials + + // EventTypeInvalidCredentials indicates that credentials were present + // in the source, but were unparseable. + EventTypeInvalidCredentials + + // EventTypeFailedAuthentication indicates that valid, parseable credentials + // were present in the source, but the token failed authentication. + // + // The Token field will be set in the Event. + EventTypeFailedAuthentication + + // EventTypeFailedAuthorization indicates that valid, parseable credentials + // were present in the source and that the resulting token was authentic. + // However, the token did not have access to the resource(s) being requested. + // + // The Token field will be set in the Event. + EventTypeFailedAuthorization +) + +// Event holds information about the result of a attempting to obtain a token. +type Event[S any] struct { + // Type is the kind of event. This field is always set. + Type EventType + + // Source is the source of credentials. This field is always set. + Source S + + // Token is the parsed token from the source. This field will always be set + // if the token successfully parsed, even if it wasn't authentic or authorized. + Token Token + + // Err is any error that occurred from the bascule infrastructure. This will + // always be nil for a successful Event. This field MAY BE set if the + // configured infrastructure gave more information about why the attempt to + // get a token failed. + Err error +} + +// Success is a convenience for checking if the event's Type field represents +// a successful token. Using this method ensures that client code will work +// in future versions. +func (e Event[S]) Success() bool { + return e.Type == EventTypeSuccess +} + +// Listener is a sink for bascule events. +type Listener[S any] interface { + // OnEvent receives a bascule event. This method must not block or panic. + OnEvent(Event[S]) +} + +// ListenerFunc is a closure that can act as a Listener. +type ListenerFunc[S any] func(Event[S]) + +// OnEvent satisfies the Listener interface. +func (lf ListenerFunc[S]) OnEvent(e Event[S]) { lf(e) } + +// Listeners is an aggregate Listener. +type Listeners[S any] []Listener[S] + +// Append adds more listeners to this aggregate. The (possibly new) +// aggregate Listeners is returned. This method has the same +// semantics as the built-in append. +func (ls Listeners[S]) Append(more ...Listener[S]) Listeners[S] { + return append(ls, more...) +} + +// AppendFunc is a more convenient version of Append when using +// closures as listeners. +func (ls Listeners[S]) AppendFunc(more ...ListenerFunc[S]) Listeners[S] { + for _, lf := range more { + if lf != nil { // handle the nil interface case + ls = ls.Append(lf) + } + } + + return ls +} + +// OnEvent dispatches the given event to all listeners +// contained by this aggregate. +func (ls Listeners[S]) OnEvent(e Event[S]) { + for _, l := range ls { + l.OnEvent(e) + } +} + +// filteredListener is the internal type returned by FilterEvents. +type filteredListener[S any] struct { + next Listener[S] + types map[EventType]bool +} + +func (fl filteredListener[S]) OnEvent(e Event[S]) { + if fl.types[e.Type] { + fl.next.OnEvent(e) + } +} + +// FilterEvents decorates a given Listener so that it only receives events of +// certain types. The decorated Listener is returned. This method simplifies +// client code by removing the need for if/else or switch/case blocks to check +// the event's Type field in certain situations. +// +// If types is empty, the Listener is returned as is. +func FilterEvents[S any](l Listener[S], types ...EventType) Listener[S] { + if len(types) == 0 { + return l + } + + fl := filteredListener[S]{ + next: l, + types: make(map[EventType]bool, len(types)), + } + + for _, t := range types { + fl.types[t] = true + } + + return fl +} From 4a33b88b9ecf59a47d4bc08f3270a19142a3fe6e Mon Sep 17 00:00:00 2001 From: johnabass Date: Mon, 12 Aug 2024 11:03:59 -0700 Subject: [PATCH 2/3] pruned event filtering for now --- listener.go | 35 ----------- listener_test.go | 153 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 35 deletions(-) create mode 100644 listener_test.go diff --git a/listener.go b/listener.go index 208978f..c64deca 100644 --- a/listener.go +++ b/listener.go @@ -103,38 +103,3 @@ func (ls Listeners[S]) OnEvent(e Event[S]) { l.OnEvent(e) } } - -// filteredListener is the internal type returned by FilterEvents. -type filteredListener[S any] struct { - next Listener[S] - types map[EventType]bool -} - -func (fl filteredListener[S]) OnEvent(e Event[S]) { - if fl.types[e.Type] { - fl.next.OnEvent(e) - } -} - -// FilterEvents decorates a given Listener so that it only receives events of -// certain types. The decorated Listener is returned. This method simplifies -// client code by removing the need for if/else or switch/case blocks to check -// the event's Type field in certain situations. -// -// If types is empty, the Listener is returned as is. -func FilterEvents[S any](l Listener[S], types ...EventType) Listener[S] { - if len(types) == 0 { - return l - } - - fl := filteredListener[S]{ - next: l, - types: make(map[EventType]bool, len(types)), - } - - for _, t := range types { - fl.types[t] = true - } - - return fl -} diff --git a/listener_test.go b/listener_test.go new file mode 100644 index 0000000..4471ae7 --- /dev/null +++ b/listener_test.go @@ -0,0 +1,153 @@ +package bascule + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/suite" +) + +type ListenerTestSuite struct { + suite.Suite +} + +func (suite *ListenerTestSuite) TestEvent() { + suite.Run("Success", func() { + testCases := []struct { + name string + event Event[int] + expected bool + }{ + { + name: "type success", + event: Event[int]{ + Type: EventTypeSuccess, + }, + expected: true, + }, + { + name: "type missing credentials", + event: Event[int]{ + Type: EventTypeMissingCredentials, + }, + expected: false, + }, + } + + for _, testCase := range testCases { + suite.Run(testCase.name, func() { + suite.Equal( + testCase.expected, + testCase.event.Success(), + ) + }) + } + }) + + // just check that strings are unique + suite.Run("String", func() { + var ( + eventTypes = []EventType{ + EventTypeSuccess, + EventTypeMissingCredentials, + EventTypeInvalidCredentials, + EventTypeFailedAuthentication, + EventTypeFailedAuthorization, + EventType(256), // random weird value should still work + } + + strings = make(map[string]bool) + ) + + for _, et := range eventTypes { + strings[et.String()] = true + } + + suite.Equal(len(eventTypes), len(strings)) + }) +} + +func (suite *ListenerTestSuite) TestListenerFunc() { + var ( + called bool + + l Listener[int] = ListenerFunc[int](func(e Event[int]) { + suite.Equal(EventTypeMissingCredentials, e.Type) + called = true + }) + ) + + l.OnEvent(Event[int]{ + Type: EventTypeMissingCredentials, + }) + + suite.True(called) +} + +func (suite *ListenerTestSuite) TestListeners() { + suite.Run("Empty", func() { + var ls Listeners[int] + ls.OnEvent(Event[int]{}) // should be fine + }) + + suite.Run("Append", func() { + for _, count := range []int{1, 2, 5} { + suite.Run(fmt.Sprintf("count=%d", count), func() { + var ( + called int + expectedEvent = Event[int]{ + Type: EventTypeInvalidCredentials, + Source: 1234, + Err: errors.New("expected"), + } + + ls Listeners[int] + ) + + for i := 0; i < count; i++ { + var l Listener[int] = ListenerFunc[int](func(e Event[int]) { + suite.Equal(expectedEvent, e) + called++ + }) + + ls = ls.Append(l) + } + + ls.OnEvent(expectedEvent) + suite.Equal(count, called) + }) + } + }) + + suite.Run("AppendFunc", func() { + for _, count := range []int{1, 2, 5} { + suite.Run(fmt.Sprintf("count=%d", count), func() { + var ( + called int + expectedEvent = Event[int]{ + Type: EventTypeInvalidCredentials, + Source: 1234, + Err: errors.New("expected"), + } + + ls Listeners[int] + ) + + for i := 0; i < count; i++ { + ls = ls.AppendFunc(func(e Event[int]) { + suite.Equal(expectedEvent, e) + called++ + }) + } + + ls.OnEvent(expectedEvent) + suite.Equal(count, called) + }) + } + }) +} + +func TestListener(t *testing.T) { + suite.Run(t, new(ListenerTestSuite)) +} From a56b55956a76d4020235b9311bddbfa693617234 Mon Sep 17 00:00:00 2001 From: johnabass Date: Mon, 12 Aug 2024 11:07:58 -0700 Subject: [PATCH 3/3] chore: copyright --- .reuse/dep5 | 32 ++------------------------------ listener_test.go | 3 +++ 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/.reuse/dep5 b/.reuse/dep5 index e8a496c..3a8095d 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -39,38 +39,10 @@ Files: README.md Copyright: SPDX-FileCopyrightText: 2019 Comcast Cable Communications Management, LLC License: Apache-2.0 -Files: basculehttp/README.md -Copyright: SPDX-FileCopyrightText: 2019 Comcast Cable Communications Management, LLC -License: Apache-2.0 - -Files: basculehttp/notfoundbehavior_string.go -Copyright: SPDX-FileCopyrightText: 2019 Comcast Cable Communications Management, LLC -License: Apache-2.0 - Files: .whitesource Copyright: SPDX-FileCopyrightText: 2020 Comcast Cable Communications Management, LLC License: Apache-2.0 -Files: examples/acquirer/go.mod -Copyright: SPDX-FileCopyrightText: 2019 Comcast Cable Communications Management, LLC -License: Apache-2.0 - -Files: examples/acquirer/go.sum -Copyright: SPDX-FileCopyrightText: 2019 Comcast Cable Communications Management, LLC -License: Apache-2.0 - -Files: examples/basculehttp/go.mod -Copyright: SPDX-FileCopyrightText: 2019 Comcast Cable Communications Management, LLC -License: Apache-2.0 - -Files: examples/basculehttp/go.sum -Copyright: SPDX-FileCopyrightText: 2019 Comcast Cable Communications Management, LLC -License: Apache-2.0 - -Files: v2/go.mod -Copyright: SPDX-FileCopyrightText: 2019 Comcast Cable Communications Management, LLC -License: Apache-2.0 - -Files: v2/go.sum -Copyright: SPDX-FileCopyrightText: 2019 Comcast Cable Communications Management, LLC +Files: eventType_string.go +Copyright: SPDX-FileCopyrightText: 2020 Comcast Cable Communications Management, LLC License: Apache-2.0 diff --git a/listener_test.go b/listener_test.go index 4471ae7..fca5a82 100644 --- a/listener_test.go +++ b/listener_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC +// SPDX-License-Identifier: Apache-2.0 + package bascule import (