From a9942864027bcb21a3ebfe4e1701cafa00391460 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sun, 28 Dec 2025 11:30:37 -0600 Subject: [PATCH] fix error handling/short-circuiting in multi route matching --- internal/module/http-server.go | 64 +++++++++++++++++++++++++++++----- internal/route/route.go | 10 +++--- router.go | 50 +++++++++++++++++++------- 3 files changed, 98 insertions(+), 26 deletions(-) diff --git a/internal/module/http-server.go b/internal/module/http-server.go index e6d2914..ba4ba13 100644 --- a/internal/module/http-server.go +++ b/internal/module/http-server.go @@ -20,9 +20,17 @@ type HTTPServer struct { logger *slog.Logger } +type ResponseIOError struct { + Index int `json:"index"` + OutputErrors []string `json:"outputErrors"` + ProcessError *string `json:"processError"` + InputError *string `json:"inputError"` +} + type ResponseData struct { - Message string `json:"message"` - Status string `json:"status"` + IOErrors []ResponseIOError `json:"ioErrors"` + Message string `json:"message"` + Status string `json:"status"` } func init() { @@ -43,6 +51,8 @@ func init() { router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO) + fmt.Printf("%+T", ctx.Value(route.RouterContextKey)) + if !ok { return nil, errors.New("http.server unable to get router from context") } @@ -69,14 +79,50 @@ func (hs *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if hs.router != nil { - routingErrors := hs.router.HandleInput(hs.Id(), r) - if routingErrors != nil { - w.WriteHeader(http.StatusInternalServerError) - response.Status = "error" - response.Message = "routing failed" + aRouteFound, routingErrors := hs.router.HandleInput(hs.Id(), r) + if aRouteFound { + if routingErrors != nil { + w.WriteHeader(http.StatusInternalServerError) + response.Status = "error" + response.Message = "routing failed" + + response.IOErrors = []ResponseIOError{} + for _, responseIOError := range routingErrors { + errorToAdd := ResponseIOError{ + Index: responseIOError.Index, + } + + if responseIOError.InputError != nil { + errorMsg := responseIOError.InputError.Error() + errorToAdd.InputError = &errorMsg + } + + if responseIOError.ProcessError != nil { + errorMsg := responseIOError.ProcessError.Error() + errorToAdd.ProcessError = &errorMsg + } + + if responseIOError.OutputErrors != nil { + outputErrorMsgs := []string{} + + for _, outputError := range responseIOError.OutputErrors { + outputErrorMsgs = append(outputErrorMsgs, outputError.Error()) + } + + errorToAdd.OutputErrors = outputErrorMsgs + } + + response.IOErrors = append(response.IOErrors, errorToAdd) + + } + } else { + w.WriteHeader(http.StatusOK) + response.Message = "routing successful" + } } else { - w.WriteHeader(http.StatusOK) - response.Message = "routing successful" + w.WriteHeader(http.StatusNotFound) + response.Status = "error" + response.Message = "no matching routes found" } } else { w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/route/route.go b/internal/route/route.go index e4f8fc6..06e1f7f 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -20,13 +20,15 @@ type RouteError struct { } type RouteIOError struct { - Index int - Error error + Index int + OutputErrors []error + ProcessError error + InputError error } type RouteIO interface { - HandleInput(sourceId string, payload any) []RouteIOError - HandleOutput(ctx context.Context, destinationId string, payload any) error + HandleInput(sourceId string, payload any) (bool, []RouteIOError) + HandleOutput(ctx context.Context, destinationId string, payload any) []error } type Route interface { diff --git a/router.go b/router.go index e66e3af..ed14488 100644 --- a/router.go +++ b/router.go @@ -129,34 +129,58 @@ func (r *Router) Stop() { r.contextCancel() } -func (r *Router) HandleInput(sourceId string, payload any) []route.RouteIOError { - var routingErrors []route.RouteIOError +func (r *Router) HandleInput(sourceId string, payload any) (bool, []route.RouteIOError) { + var routeIOErrors []route.RouteIOError + routeFound := false for routeIndex, routeInstance := range r.RouteInstances { if routeInstance.Input() == sourceId { + routeFound = true routeContext := context.WithValue(r.Context, route.SourceContextKey, sourceId) payload, err := routeInstance.ProcessPayload(routeContext, payload) if err != nil { - if routingErrors == nil { - routingErrors = []route.RouteIOError{} + if routeIOErrors == nil { + routeIOErrors = []route.RouteIOError{} } - routingErrors = append(routingErrors, route.RouteIOError{ - Index: routeIndex, - Error: err, + r.logger.Error("unable to process input", "route", routeIndex, "source", sourceId, "error", err) + routeIOErrors = append(routeIOErrors, route.RouteIOError{ + Index: routeIndex, + ProcessError: err, }) - r.logger.Error("unable to route input", "route", routeIndex, "source", sourceId, "error", err) + continue } - r.HandleOutput(routeContext, routeInstance.Output(), payload) + + outputErrors := r.HandleOutput(routeContext, routeInstance.Output(), payload) + if outputErrors != nil { + if routeIOErrors == nil { + routeIOErrors = []route.RouteIOError{} + } + routeIOErrors = append(routeIOErrors, route.RouteIOError{ + Index: routeIndex, + OutputErrors: outputErrors, + }) + } + } } - return routingErrors + return routeFound, routeIOErrors } -func (r *Router) HandleOutput(ctx context.Context, destinationId string, payload any) error { +func (r *Router) HandleOutput(ctx context.Context, destinationId string, payload any) []error { + + var outputErrors []error for _, moduleInstance := range r.ModuleInstances { if moduleInstance.Id() == destinationId { - return moduleInstance.Output(ctx, payload) + err := moduleInstance.Output(ctx, payload) + if err != nil { + if outputErrors == nil { + outputErrors = []error{} + } + outputErrors = append(outputErrors, err) + } + // r.logger.Error("unable to route output", "module", moduleInstance.Id(), "error", err) } } - return fmt.Errorf("router could not find module instance for destination %s", destinationId) + fmt.Println(len(outputErrors)) + return outputErrors }