Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/event #273

Merged
merged 3 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 2 additions & 30 deletions .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -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
27 changes: 27 additions & 0 deletions eventType_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

105 changes: 105 additions & 0 deletions listener.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// 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)
}
}
156 changes: 156 additions & 0 deletions listener_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC
// SPDX-License-Identifier: Apache-2.0

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))
}
Loading