/mutableware

Mutable middleware with generics

Primary LanguageGoMIT LicenseMIT

mutableware

mutableware is a middleware package that allows for the modification of middleware. It uses generics to define Request and Response types. All handlers have a Handle[RequestType, ResponseType](ctx context.Context, request RequestType) (ResponseType, error) function.

Example

package mutableware_test

import (
	"context"
	"fmt"
	"strings"
	"testing"

	"github.com/erinpentecost/mutableware"
	"github.com/stretchr/testify/require"
)

// Build a new Handler that will tell us which sounds that animals make.
type Animal string
type Sound string

type AnimalSoundHandler struct {
	animal Animal
	sound  Sound
}

func (ash *AnimalSoundHandler) Handle(ctx context.Context, request Animal, next mutableware.CurriedHandlerFunc[Animal, Sound]) (Sound, error) {
	if request == ash.animal {
		return ash.sound, nil
	}
	// We don't handle this type of animal, so just pass along execution to the next handler in the chain.
	return next(ctx, request)
}

func TestExample(t *testing.T) {

	hc := mutableware.NewHandlerContainer[Animal, Sound]()
	// The first handler to be added will act as a sentinel to catch unknown Animal requests.
	hc.AddAnonymousHandler(
		func(ctx context.Context, request Animal, next mutableware.CurriedHandlerFunc[Animal, Sound]) (Sound, error) {
			return "", fmt.Errorf("unknown animal")
		},
	)
	// Now we'll deal with ducks.
	duckHandlerID := hc.Add(&AnimalSoundHandler{
		animal: Animal("duck"),
		sound:  Sound("quack"),
	})
	// And now cows.
	hc.Add(&AnimalSoundHandler{
		animal: Animal("cow"),
		sound:  Sound("moo"),
	})
	// This handler will make the sounds really loud.
	hc.AddAnonymousHandler(
		func(ctx context.Context, request Animal, next mutableware.CurriedHandlerFunc[Animal, Sound]) (Sound, error) {
			response, err := next(ctx, request)
			if err != nil {
				return response, err
			}
			return Sound(fmt.Sprintf("%s!", strings.ToUpper(string(response)))), nil
		},
	)

	// Now lets try sending in requests to the handler container.
	duckSound, err := hc.Handle(context.Background(), "duck")
	require.NoError(t, err)
	require.Equal(t, Sound("QUACK!"), duckSound)
	cowSound, err := hc.Handle(context.Background(), "cow")
	require.NoError(t, err)
	require.Equal(t, Sound("MOO!"), cowSound)
	dogSound, err := hc.Handle(context.Background(), "dog")
	require.Error(t, err)
	require.Zero(t, dogSound)

	// Let's make ducks go "bark" instead.
	hc.Add(&AnimalSoundHandler{
		animal: Animal("duck"),
		sound:  Sound("bark"),
	},
		mutableware.AddOptionSwap(duckHandlerID), // Swap out an existing handler for this new one.
	)

	// Give it another try.
	duckSound, err = hc.Handle(context.Background(), "duck")
	require.NoError(t, err)
	require.Equal(t, Sound("BARK!"), duckSound)
}