Files
showbridge-go/internal/module/http-server.go
2025-12-28 11:47:02 -06:00

161 lines
3.7 KiB
Go

package module
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
)
type HTTPServer struct {
config config.ModuleConfig
Port uint16
ctx context.Context
router route.RouteIO
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 {
IOErrors []ResponseIOError `json:"ioErrors"`
Message string `json:"message"`
Status string `json:"status"`
}
func init() {
RegisterModule(ModuleRegistration{
Type: "http.server",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) {
params := config.Params
port, ok := params["port"]
if !ok {
return nil, errors.New("http.server requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, errors.New("http.server port must be uint16")
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("http.server unable to get router from context")
}
return &HTTPServer{Port: uint16(portNum), config: config, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
},
})
}
func (hs *HTTPServer) Id() string {
return hs.config.Id
}
func (hs *HTTPServer) Type() string {
return hs.config.Type
}
func (hs *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
response := ResponseData{
Message: "routing successful",
Status: "ok",
}
if hs.router != nil {
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.StatusNotFound)
response.Status = "error"
response.Message = "no matching routes found"
}
} else {
w.WriteHeader(http.StatusInternalServerError)
response.Message = "no router registered"
response.Status = "error"
}
json.NewEncoder(w).Encode(response)
}
func (hs *HTTPServer) Run() error {
httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", hs.Port),
Handler: hs,
}
go func() {
<-hs.ctx.Done()
httpServer.Close()
}()
err := httpServer.ListenAndServe()
// TODO(jwetzell): handle server closed error differently
if err != nil {
if err.Error() != "http: Server closed" {
return err
}
}
<-hs.ctx.Done()
hs.logger.Debug("done")
return nil
}
func (hs *HTTPServer) Output(ctx context.Context, payload any) error {
return errors.New("http.server output is not implemented")
}