Documentation
¶
Overview ¶
Package typemux is a type-safe multiplexer for Go. It enables routing values to handlers by type and creating typed values from envelopes.
Index ¶
- Variables
- func CreateType[KEY comparable, DATA any](reg factoryResolver, key KEY, data DATA) (any, error)
- func Dispatch(disp dispatcher, ctx context.Context, v any, middleware ...DispatchMiddleware) error
- func JSONFactory[T any]() func([]byte) (T, error)
- func RegisterDispatch[T any](reg dispatchRegistry, handler HandlerFunc[T], middleware ...Middleware[T])
- func RegisterFactory[KEY comparable, DATA any, T any](reg factoryRegistry, key KEY, factory func(DATA) (T, error))
- type DispatchMiddleware
- type DispatchRegistry
- type FactoryRegistry
- type HandlerFunc
- type Middleware
- type Registry
- type SealedDispatchRegistry
- type SealedFactoryRegistry
- type SealedRegistry
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrDataTypeNotSupported = errors.New("data type not supported")
ErrDataTypeNotSupported is returned when CreateType is called with not supported DATA type.
var ErrFactoryNotFound = errors.New("factory not found")
ErrFactoryNotFound is returned when no factory is found for the given key.
var ErrHandlerNotFound = errors.New("handler not found")
ErrHandlerNotFound is returned when no handler is found for the given value's type.
Functions ¶
func CreateType ¶
func CreateType[KEY comparable, DATA any](reg factoryResolver, key KEY, data DATA) (any, error)
CreateType looks up a factory by key and uses it to create a value from the provided data. It returns ErrFactoryNotFound if no factory is registered for the given key.
Example ¶
package main
import (
"context"
"fmt"
"github.com/struct0x/typemux"
)
func main() {
// Event types
type UserCreated struct {
ID string `json:"id"`
Name string `json:"name"`
}
type OrderPlaced struct {
OrderID string `json:"order_id"`
Amount int `json:"amount"`
}
// Create registry and register factories + handlers
reg := typemux.NewRegistry()
// Register factories using JSONFactory helper
typemux.RegisterFactory(reg, "user_created", typemux.JSONFactory[UserCreated]())
typemux.RegisterFactory(reg, "order_placed", typemux.JSONFactory[OrderPlaced]())
// Register handlers
typemux.RegisterDispatch(reg, func(ctx context.Context, e UserCreated) error {
fmt.Printf("User created: %s (ID: %s)\n", e.Name, e.ID)
return nil
})
typemux.RegisterDispatch(reg, func(ctx context.Context, e OrderPlaced) error {
fmt.Printf("Order placed: %s for $%d\n", e.OrderID, e.Amount)
return nil
})
// Seal for production use
sealed := reg.Seal()
ctx := context.Background()
// Simulate receiving envelopes (e.g., from a message queue)
envelopes := []struct {
Type string
Data []byte
}{
{"user_created", []byte(`{"id": "u1", "name": "Alice"}`)},
{"order_placed", []byte(`{"order_id": "ORD-001", "amount": 150}`)},
{"user_created", []byte(`{"id": "u2", "name": "Bob"}`)},
}
// Process each envelope: create typed value, then dispatch
for _, env := range envelopes {
value, err := typemux.CreateType(sealed, env.Type, env.Data)
if err != nil {
fmt.Printf("Failed to create type: %v\n", err)
continue
}
if err := typemux.Dispatch(sealed, ctx, value); err != nil {
fmt.Printf("Failed to dispatch: %v\n", err)
}
}
}
Output: User created: Alice (ID: u1) Order placed: ORD-001 for $150 User created: Bob (ID: u2)
func Dispatch ¶
func Dispatch(disp dispatcher, ctx context.Context, v any, middleware ...DispatchMiddleware) error
Dispatch dispatches the given value to a registered handler based on its concrete type. Optional generic middleware is applied outermost-first, wrapping the typed middleware chain. It returns ErrHandlerNotFound if no handler is registered for the value's type.
Example ¶
package main
import (
"context"
"fmt"
"github.com/struct0x/typemux"
)
func loggingMiddleware[T any]() typemux.Middleware[T] {
return func(next typemux.HandlerFunc[T]) typemux.HandlerFunc[T] {
return func(ctx context.Context, event T) error {
fmt.Printf("Processing event: %T\n", event)
err := next(ctx, event)
if err != nil {
fmt.Printf("Error processing event: %v\n", err)
return err
}
fmt.Printf("Successfully processed event: %T\n", event)
return nil
}
}
}
func main() {
reg := typemux.NewRegistry()
// Define event types
type UserCreated struct {
ID int
Name string
}
type OrderPlaced struct {
OrderID string
Amount float64
}
type Unknown struct {
Foo string
}
// Create middleware using the helper function
validationMiddleware := typemux.MiddlewareFunc(func(ctx context.Context, event UserCreated) (bool, error) {
if event.ID <= 0 {
return false, fmt.Errorf("invalid user ID: %d", event.ID)
}
if event.Name == "" {
return false, fmt.Errorf("user name cannot be empty")
}
return true, nil // Continue processing
})
// RegisterDispatch handlers for different types
typemux.RegisterDispatch(
reg,
func(ctx context.Context, event UserCreated) error {
fmt.Printf("User created: %s (ID: %d)\n", event.Name, event.ID)
return nil
},
validationMiddleware,
loggingMiddleware[UserCreated](),
)
typemux.RegisterDispatch(
reg,
func(ctx context.Context, event OrderPlaced) error {
fmt.Printf("Order placed: %s for $%.2f\n", event.OrderID, event.Amount)
return nil
},
loggingMiddleware[OrderPlaced](),
)
// Dispatch events
ctx := context.Background()
_ = typemux.Dispatch(reg, ctx, UserCreated{ID: 1, Name: "Alice"})
_ = typemux.Dispatch(reg, ctx, OrderPlaced{OrderID: "ORD-001", Amount: 99.99})
sealedReg := reg.Seal()
_ = typemux.Dispatch(sealedReg, ctx, UserCreated{ID: 2, Name: "Alice"})
_ = typemux.Dispatch(sealedReg, ctx, OrderPlaced{OrderID: "ORD-002", Amount: 99.99})
if err := typemux.Dispatch(reg, ctx, Unknown{Foo: "bar"}); err != nil {
fmt.Printf("Dispatch err: %v", err)
}
}
Output: Processing event: typemux_test.UserCreated User created: Alice (ID: 1) Successfully processed event: typemux_test.UserCreated Processing event: typemux_test.OrderPlaced Order placed: ORD-001 for $99.99 Successfully processed event: typemux_test.OrderPlaced Processing event: typemux_test.UserCreated User created: Alice (ID: 2) Successfully processed event: typemux_test.UserCreated Processing event: typemux_test.OrderPlaced Order placed: ORD-002 for $99.99 Successfully processed event: typemux_test.OrderPlaced Dispatch err: typemux: handler not found for type typemux_test.Unknown
func JSONFactory ¶
JSONFactory returns a factory function that unmarshals JSON data into type T. Use with RegisterFactory for convenient JSON-based type creation.
Example:
RegisterFactory(reg, "user_created", JSONFactory[UserCreated]())
func RegisterDispatch ¶
func RegisterDispatch[T any](reg dispatchRegistry, handler HandlerFunc[T], middleware ...Middleware[T])
RegisterDispatch adds a handler for values of type T, with optional middleware.
If a handler for the same type T has already been registered, it will be replaced by the new handler and middleware chain.
Middleware is applied outermost first (i.e., the last middleware wraps the others).
func RegisterFactory ¶
func RegisterFactory[KEY comparable, DATA any, T any](reg factoryRegistry, key KEY, factory func(DATA) (T, error))
RegisterFactory registers a factory function that creates values of type T from data of type DATA, associated with the given key.
The key can be any comparable type (string, int, custom enum, etc.). If a factory for the same key has already been registered, it will be replaced.
Types ¶
type DispatchMiddleware ¶
type DispatchMiddleware func(ctx context.Context, event any, next func(context.Context) error) error
DispatchMiddleware wraps a dispatch call with access to the event as any. Use for cross-cutting concerns like logging, timing, and tracing that don't need type-specific access to the event.
type DispatchRegistry ¶
type DispatchRegistry struct {
// contains filtered or unexported fields
}
DispatchRegistry holds registered type-safe handlers. Use NewDispatchRegistry() to create one, then RegisterDispatch() handlers.
func NewDispatchRegistry ¶
func NewDispatchRegistry() *DispatchRegistry
NewDispatchRegistry creates a new empty DispatchRegistry.
DispatchRegistry holds registered type-safe handlers.
func (*DispatchRegistry) Seal ¶
func (r *DispatchRegistry) Seal() *SealedDispatchRegistry
Seal finalizes the DispatchRegistry and returns a SealedDispatchRegistry.
type FactoryRegistry ¶
type FactoryRegistry struct {
// contains filtered or unexported fields
}
FactoryRegistry holds registered type factories. Use NewFactoryRegistry() to create one, then RegisterFactory().
func NewFactoryRegistry ¶
func NewFactoryRegistry() *FactoryRegistry
NewFactoryRegistry creates a new empty FactoryRegistry.
func (*FactoryRegistry) Seal ¶
func (r *FactoryRegistry) Seal() *SealedFactoryRegistry
Seal finalizes the FactoryRegistry and returns a SealedFactoryRegistry.
type HandlerFunc ¶
HandlerFunc is a type-safe handler for values of type T. It receives a context and a value, and may return an error.
type Middleware ¶
type Middleware[T any] func(next HandlerFunc[T]) HandlerFunc[T]
Middleware is a type-safe wrapper around a HandlerFunc. It allows injecting logic before/after the handler.
func MiddlewareFunc ¶
MiddlewareFunc is a simple convenience function to create middleware from a function that optionally short-circuits the call chain.
type Registry ¶
type Registry struct {
*DispatchRegistry
*FactoryRegistry
}
Registry is a composite registry that supports both handlers and factories. Use NewRegistry() to create one.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry creates a new composite Registry with both handler and factory support.
func (*Registry) Seal ¶
func (r *Registry) Seal() *SealedRegistry
Seal finalizes the Registry and returns a SealedRegistry.
The resulting SealedRegistry is immutable and safe for concurrent use with no mutex overhead.
type SealedDispatchRegistry ¶
type SealedDispatchRegistry struct {
// contains filtered or unexported fields
}
SealedDispatchRegistry is an immutable, thread-safe dispatcher.
type SealedFactoryRegistry ¶
type SealedFactoryRegistry struct {
// contains filtered or unexported fields
}
SealedFactoryRegistry is an immutable factory resolver.
type SealedRegistry ¶
type SealedRegistry struct {
*SealedDispatchRegistry
*SealedFactoryRegistry
}
SealedRegistry is an immutable composite registry for runtime use.