Skip to content

Commit

Permalink
Handler middlewares (#1019)
Browse files Browse the repository at this point in the history
Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
  • Loading branch information
marefr and wbrowne authored Sep 24, 2024
1 parent dffaaeb commit d44c599
Show file tree
Hide file tree
Showing 7 changed files with 821 additions and 19 deletions.
4 changes: 2 additions & 2 deletions backend/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
)

const (
// EndpointConvertObject friendly name for the convert object endpoint/handler.
EndpointConvertObject Endpoint = "convertObject"
// EndpointConvertObjects friendly name for the convert objects endpoint/handler.
EndpointConvertObjects Endpoint = "convertObjects"
)

// ConversionHandler is an EXPERIMENTAL service that allows converting objects between versions
Expand Down
2 changes: 1 addition & 1 deletion backend/conversion_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (a *conversionSDKAdapter) convertQueryDataRequest(ctx context.Context, requ
}

func (a *conversionSDKAdapter) ConvertObjects(ctx context.Context, req *pluginv2.ConversionRequest) (*pluginv2.ConversionResponse, error) {
ctx = setupContext(ctx, EndpointConvertObject)
ctx = setupContext(ctx, EndpointConvertObjects)
parsedReq := FromProto().ConversionRequest(req)

resp := &ConversionResponse{}
Expand Down
70 changes: 70 additions & 0 deletions backend/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package backend

import "context"

// Handler interface for all handlers.
type Handler interface {
QueryDataHandler
CheckHealthHandler
CallResourceHandler
CollectMetricsHandler
StreamHandler
AdmissionHandler
ConversionHandler
}

var _ = Handler(&BaseHandler{})

// BaseHandler base handler provides a base implementation of Handler interface
// passing the request down the chain to next Handler.
// This allows handlers to avoid implementing the full Handler interface.
type BaseHandler struct {
next Handler
}

// NewBaseHandler creates a new BaseHandler.
func NewBaseHandler(next Handler) BaseHandler {
return BaseHandler{
next: next,
}
}

func (m BaseHandler) QueryData(ctx context.Context, req *QueryDataRequest) (*QueryDataResponse, error) {
return m.next.QueryData(ctx, req)
}

func (m BaseHandler) CallResource(ctx context.Context, req *CallResourceRequest, sender CallResourceResponseSender) error {
return m.next.CallResource(ctx, req, sender)
}

func (m BaseHandler) CheckHealth(ctx context.Context, req *CheckHealthRequest) (*CheckHealthResult, error) {
return m.next.CheckHealth(ctx, req)
}

func (m BaseHandler) CollectMetrics(ctx context.Context, req *CollectMetricsRequest) (*CollectMetricsResult, error) {
return m.next.CollectMetrics(ctx, req)
}

func (m BaseHandler) SubscribeStream(ctx context.Context, req *SubscribeStreamRequest) (*SubscribeStreamResponse, error) {
return m.next.SubscribeStream(ctx, req)
}

func (m BaseHandler) PublishStream(ctx context.Context, req *PublishStreamRequest) (*PublishStreamResponse, error) {
return m.next.PublishStream(ctx, req)
}

func (m BaseHandler) RunStream(ctx context.Context, req *RunStreamRequest, sender *StreamSender) error {
return m.next.RunStream(ctx, req, sender)
}

func (m BaseHandler) ValidateAdmission(ctx context.Context, req *AdmissionRequest) (*ValidationResponse, error) {
return m.next.ValidateAdmission(ctx, req)
}

func (m *BaseHandler) MutateAdmission(ctx context.Context, req *AdmissionRequest) (*MutationResponse, error) {
return m.next.MutateAdmission(ctx, req)
}

func (m *BaseHandler) ConvertObjects(ctx context.Context, req *ConversionRequest) (*ConversionResponse, error) {
return m.next.ConvertObjects(ctx, req)
}
181 changes: 181 additions & 0 deletions backend/handler_middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package backend

import (
"context"
"errors"
"slices"
)

var (
errNilRequest = errors.New("req cannot be nil")
errNilSender = errors.New("sender cannot be nil")
)

// HandlerMiddleware is an interface representing the ability to create a middleware
// that implements the Handler interface.
type HandlerMiddleware interface {
// CreateHandlerMiddleware creates a new Handler by decorating next Handler.
CreateHandlerMiddleware(next Handler) Handler
}

// The HandlerMiddlewareFunc type is an adapter to allow the use of ordinary
// functions as HandlerMiddleware's. If f is a function with the appropriate
// signature, HandlerMiddlewareFunc(f) is a HandlerMiddleware that calls f.
type HandlerMiddlewareFunc func(next Handler) Handler

// CreateHandlerMiddleware implements the HandlerMiddleware interface.
func (fn HandlerMiddlewareFunc) CreateHandlerMiddleware(next Handler) Handler {
return fn(next)
}

// MiddlewareHandler decorates a Handler with HandlerMiddleware's.
type MiddlewareHandler struct {
middlewares []HandlerMiddleware
finalHandler Handler
}

// HandlerFromMiddlewares creates a new MiddlewareHandler implementing Handler that decorates finalHandler with middlewares.
func HandlerFromMiddlewares(finalHandler Handler, middlewares ...HandlerMiddleware) (*MiddlewareHandler, error) {
if finalHandler == nil {
return nil, errors.New("finalHandler cannot be nil")
}

return &MiddlewareHandler{
middlewares: middlewares,
finalHandler: finalHandler,
}, nil
}

func (h *MiddlewareHandler) setupContext(ctx context.Context, pluginCtx PluginContext, endpoint Endpoint) context.Context {
ctx = initErrorSource(ctx)
ctx = WithEndpoint(ctx, endpoint)
ctx = WithPluginContext(ctx, pluginCtx)
ctx = WithGrafanaConfig(ctx, pluginCtx.GrafanaConfig)
ctx = WithUser(ctx, pluginCtx.User)
ctx = WithUserAgent(ctx, pluginCtx.UserAgent)
return ctx
}

func (h *MiddlewareHandler) QueryData(ctx context.Context, req *QueryDataRequest) (*QueryDataResponse, error) {
if req == nil {
return nil, errNilRequest
}

ctx = h.setupContext(ctx, req.PluginContext, EndpointQueryData)
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler)
return handler.QueryData(ctx, req)
}

func (h MiddlewareHandler) CallResource(ctx context.Context, req *CallResourceRequest, sender CallResourceResponseSender) error {
if req == nil {
return errNilRequest
}

if sender == nil {
return errNilSender
}

ctx = h.setupContext(ctx, req.PluginContext, EndpointCallResource)
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler)
return handler.CallResource(ctx, req, sender)
}

func (h MiddlewareHandler) CollectMetrics(ctx context.Context, req *CollectMetricsRequest) (*CollectMetricsResult, error) {
if req == nil {
return nil, errNilRequest
}

ctx = h.setupContext(ctx, req.PluginContext, EndpointCollectMetrics)
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler)
return handler.CollectMetrics(ctx, req)
}

func (h MiddlewareHandler) CheckHealth(ctx context.Context, req *CheckHealthRequest) (*CheckHealthResult, error) {
if req == nil {
return nil, errNilRequest
}

ctx = h.setupContext(ctx, req.PluginContext, EndpointCheckHealth)
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler)
return handler.CheckHealth(ctx, req)
}

func (h MiddlewareHandler) SubscribeStream(ctx context.Context, req *SubscribeStreamRequest) (*SubscribeStreamResponse, error) {
if req == nil {
return nil, errNilRequest
}

ctx = h.setupContext(ctx, req.PluginContext, EndpointSubscribeStream)
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler)
return handler.SubscribeStream(ctx, req)
}

func (h MiddlewareHandler) PublishStream(ctx context.Context, req *PublishStreamRequest) (*PublishStreamResponse, error) {
if req == nil {
return nil, errNilRequest
}

ctx = h.setupContext(ctx, req.PluginContext, EndpointPublishStream)
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler)
return handler.PublishStream(ctx, req)
}

func (h MiddlewareHandler) RunStream(ctx context.Context, req *RunStreamRequest, sender *StreamSender) error {
if req == nil {
return errNilRequest
}

if sender == nil {
return errors.New("sender cannot be nil")
}

ctx = h.setupContext(ctx, req.PluginContext, EndpointRunStream)
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler)
return handler.RunStream(ctx, req, sender)
}

func (h MiddlewareHandler) ValidateAdmission(ctx context.Context, req *AdmissionRequest) (*ValidationResponse, error) {
if req == nil {
return nil, errNilRequest
}

ctx = h.setupContext(ctx, req.PluginContext, EndpointValidateAdmission)
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler)
return handler.ValidateAdmission(ctx, req)
}

func (h MiddlewareHandler) MutateAdmission(ctx context.Context, req *AdmissionRequest) (*MutationResponse, error) {
if req == nil {
return nil, errNilRequest
}

ctx = h.setupContext(ctx, req.PluginContext, EndpointMutateAdmission)
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler)
return handler.MutateAdmission(ctx, req)
}

func (h MiddlewareHandler) ConvertObjects(ctx context.Context, req *ConversionRequest) (*ConversionResponse, error) {
if req == nil {
return nil, errNilRequest
}

ctx = h.setupContext(ctx, req.PluginContext, EndpointConvertObjects)
handler := handlerFromMiddlewares(h.middlewares, h.finalHandler)
return handler.ConvertObjects(ctx, req)
}

func handlerFromMiddlewares(middlewares []HandlerMiddleware, finalHandler Handler) Handler {
if len(middlewares) == 0 {
return finalHandler
}

clonedMws := slices.Clone(middlewares)
slices.Reverse(clonedMws)
next := finalHandler

for _, m := range clonedMws {
next = m.CreateHandlerMiddleware(next)
}

return next
}
Loading

0 comments on commit d44c599

Please sign in to comment.