Compare commits

..

37 Commits

Author SHA1 Message Date
Joel Wetzell
168265f0a9 move docker building to main release workflow 2025-12-24 14:05:40 -06:00
Joel Wetzell
caee7b269c fix docker job name 2025-12-24 13:26:28 -06:00
Joel Wetzell
b0cc47236f add debug flag and working directory to launch.json 2025-12-24 13:26:09 -06:00
Joel Wetzell
f6c2b1d9ac Merge pull request #24 from jwetzell/feat/docker
add docker image
2025-12-24 13:24:29 -06:00
Joel Wetzell
f977b845be add github action to build docker image 2025-12-24 13:22:52 -06:00
Joel Wetzell
0166383978 set workdir 2025-12-24 13:22:45 -06:00
Joel Wetzell
66cfaa3091 update Go version in dockerfile 2025-12-24 13:22:34 -06:00
Joel Wetzell
0904d2fcb8 add more to dockerignore 2025-12-24 13:21:53 -06:00
Joel Wetzell
6a13c38e77 add dockerfile 2025-12-24 12:44:25 -06:00
Joel Wetzell
1a8ccfc64a align variable name 2025-12-24 10:54:30 -06:00
Joel Wetzell
2e3bb408c3 Merge pull request #23 from jwetzell/feat/configurable-number-parsing
make base and bitsize configurable for number parsers
2025-12-24 10:35:06 -06:00
Joel Wetzell
3d75165a61 cleanout TODO 2025-12-24 10:34:20 -06:00
Joel Wetzell
50f755f914 make base and bitsize configurable for number parsers 2025-12-24 10:34:06 -06:00
Joel Wetzell
b10e296d0a Merge pull request #22 from jwetzell/feat/udp-server-buffersize
allow configuring buffer size for udp server
2025-12-24 10:27:14 -06:00
Joel Wetzell
91c44420cb allow configuring buffer size for udp server 2025-12-24 10:24:24 -06:00
Joel Wetzell
c1b2fa714e update midi module section 2025-12-23 15:06:23 -06:00
Joel Wetzell
ff1949ce69 use errors.New when not formatting 2025-12-23 15:00:30 -06:00
Joel Wetzell
76583b1fb8 Merge pull request #20 from jwetzell/dependabot/go_modules/github.com/expr-lang/expr-1.17.7
Bump github.com/expr-lang/expr from 1.17.6 to 1.17.7
2025-12-23 14:29:05 -06:00
dependabot[bot]
e6433878b5 Bump github.com/expr-lang/expr from 1.17.6 to 1.17.7
Bumps [github.com/expr-lang/expr](https://github.com/expr-lang/expr) from 1.17.6 to 1.17.7.
- [Release notes](https://github.com/expr-lang/expr/releases)
- [Commits](https://github.com/expr-lang/expr/compare/v1.17.6...v1.17.7)

---
updated-dependencies:
- dependency-name: github.com/expr-lang/expr
  dependency-version: 1.17.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-23 20:27:34 +00:00
Joel Wetzell
3ad067cbb8 Merge pull request #19 from jwetzell/dependabot/go_modules/github.com/emiago/sipgo-1.0.1
Bump github.com/emiago/sipgo from 1.0.1-alpha.0.20251212165843-9c9bcdf9126f to 1.0.1
2025-12-23 14:26:28 -06:00
Joel Wetzell
30b406d601 Merge pull request #21 from jwetzell/dependabot/go_modules/github.com/nats-io/nats.go-1.48.0
Bump github.com/nats-io/nats.go from 1.47.0 to 1.48.0
2025-12-23 14:25:25 -06:00
dependabot[bot]
7315d40015 Bump github.com/nats-io/nats.go from 1.47.0 to 1.48.0
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.47.0 to 1.48.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.47.0...v1.48.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 17:33:17 +00:00
dependabot[bot]
e7105b8b39 Bump github.com/emiago/sipgo
Bumps [github.com/emiago/sipgo](https://github.com/emiago/sipgo) from 1.0.1-alpha.0.20251212165843-9c9bcdf9126f to 1.0.1.
- [Release notes](https://github.com/emiago/sipgo/releases)
- [Commits](https://github.com/emiago/sipgo/commits/v1.0.1)

---
updated-dependencies:
- dependency-name: github.com/emiago/sipgo
  dependency-version: 1.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 17:33:08 +00:00
Joel Wetzell
ec40194ecb add TODO 2025-12-21 15:13:06 -06:00
Joel Wetzell
85d8dc5787 cleanup error messages 2025-12-20 14:18:56 -06:00
Joel Wetzell
b27bfb1a6d don't wrap string.filter pattern 2025-12-20 10:59:33 -06:00
Joel Wetzell
fb9c0bc657 cleanup error messages 2025-12-20 07:50:22 -06:00
Joel Wetzell
d32d23041b add usage to cmd 2025-12-19 22:30:18 -06:00
Joel Wetzell
b4149df00a setup slog inside cmd with debug and json flag 2025-12-19 22:27:05 -06:00
Joel Wetzell
61bd4b64f5 setup loggers inside modules with attributes 2025-12-19 22:26:25 -06:00
Joel Wetzell
38d73881c9 return error don't just log it 2025-12-19 22:25:26 -06:00
Joel Wetzell
3138bdfcdb remove TODO's 2025-12-19 12:32:13 -06:00
Joel Wetzell
ba862300b2 fix config example 2025-12-19 09:02:05 -06:00
Joel Wetzell
e414bf336e Merge pull request #18 from jwetzell/test/script-processors
add basic tests for script processors
2025-12-18 20:06:21 -06:00
Joel Wetzell
da95fb5c75 add basic test for script.js 2025-12-18 20:04:52 -06:00
Joel Wetzell
1fdc30aa9e add basic test for script.expr 2025-12-18 20:04:41 -06:00
Joel Wetzell
128920dff9 fix typo in string.split test 2025-12-18 20:03:50 -06:00
62 changed files with 715 additions and 412 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
Dockerfile
.github
.vscode
README.md
LICENSE

View File

@@ -3,7 +3,7 @@ name: showbridge release
on:
push:
tags:
- "*"
- "v*"
permissions:
contents: write
@@ -29,3 +29,40 @@ jobs:
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
docker:
runs-on: ubuntu-latest
needs: goreleaser
steps:
- name: Check out repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup Docker metadata
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: |
jwetzell/showbridge
- name: Build and push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
push: true
context: ./
file: ./Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64,linux/arm/v7

4
.vscode/launch.json vendored
View File

@@ -10,7 +10,9 @@
"type": "go",
"request": "launch",
"mode": "auto",
"program": "cmd/showbridge"
"program": "cmd/showbridge",
"args": ["--debug"],
"cwd": "./"
}
]
}

15
Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
ARG GO_VERSION=1.25.5
FROM golang:${GO_VERSION}-alpine AS build
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build ./cmd/showbridge
FROM scratch
WORKDIR /app
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=build /build/showbridge /app/showbridge
ENTRYPOINT [ "/app/showbridge" ]

View File

@@ -22,8 +22,9 @@ Simple protocol router _/s_
- client
- [PosiStageNet](https://posistage.net/)
- client
- MIDI
- client (not included in pre-built binaries yet)
- MIDI (not included in pre-built binaries yet)
- input
- output
- [SIP](https://en.wikipedia.org/wiki/Session_Initiation_Protocol)
- call server
- [DTMF](https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling) server

View File

@@ -2,7 +2,7 @@ package main
import (
"context"
"fmt"
"errors"
"log/slog"
"os"
"os/signal"
@@ -20,30 +20,66 @@ var (
func main() {
cmd := &cli.Command{
Name: "showbridge",
Usage: "Simple protocol router /s",
Version: version,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Value: "./config.yaml",
Usage: "path to config file",
},
&cli.BoolFlag{
Name: "debug",
Value: false,
Usage: "set log level to DEBUG",
},
&cli.BoolFlag{
Name: "json",
Value: false,
Usage: "log using JSON",
},
},
Action: func(ctx context.Context, c *cli.Command) error {
configPath := c.String("config")
if configPath == "" {
return fmt.Errorf("config value cannot be empty")
return errors.New("config value cannot be empty")
}
config, err := readConfig(configPath)
if err != nil {
return err
}
logLevel := slog.LevelInfo
if c.Bool("debug") {
logLevel = slog.LevelDebug
}
logHandlerOptions := &slog.HandlerOptions{
Level: logLevel,
}
logOutput := os.Stderr
var logHandler slog.Handler = slog.NewTextHandler(logOutput, logHandlerOptions)
if c.Bool("json") {
logHandler = slog.NewJSONHandler(logOutput, logHandlerOptions)
}
logger := slog.New(logHandler)
slog.SetDefault(logger)
router, moduleErrors, routeErrors := showbridge.NewRouter(ctx, config)
for _, moduleError := range moduleErrors {
slog.Error("problem initializing module", "index", moduleError.Index, "error", moduleError.Error)
logger.Error("problem initializing module", "index", moduleError.Index, "error", moduleError.Error)
}
for _, routeError := range routeErrors {
slog.Error("problem initializing route", "index", routeError.Index, "error", routeError.Error)
logger.Error("problem initializing route", "index", routeError.Index, "error", routeError.Error)
}
router.Run()
return nil

View File

@@ -1,6 +1,6 @@
modules:
- id: http
type: net.http.server
type: http.server
params:
port: 3000
- id: udp

6
go.mod
View File

@@ -5,12 +5,12 @@ go 1.25.3
require (
github.com/eclipse/paho.mqtt.golang v1.5.1
github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111
github.com/emiago/sipgo v1.0.1-alpha.0.20251212165843-9c9bcdf9126f
github.com/expr-lang/expr v1.17.6
github.com/emiago/sipgo v1.0.1
github.com/expr-lang/expr v1.17.7
github.com/jwetzell/free-d-go v0.1.0
github.com/jwetzell/osc-go v0.1.0
github.com/jwetzell/psn-go v0.3.0
github.com/nats-io/nats.go v1.47.0
github.com/nats-io/nats.go v1.48.0
github.com/urfave/cli/v3 v3.6.1
gitlab.com/gomidi/midi/v2 v2.3.18
go.bug.st/serial v1.6.4

12
go.sum
View File

@@ -8,10 +8,10 @@ github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2I
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111 h1:jqhOZbH40pf2jiUhGaBNk334wOtNYvAaXg/mHOXhy/Y=
github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111/go.mod h1:3vLCCq8/G/Ei5I64IHtrmBTag+nPLcgXcKeN1KkLtuc=
github.com/emiago/sipgo v1.0.1-alpha.0.20251212165843-9c9bcdf9126f h1:n3wpx7ZyJBr0popXyATnTUZp/AdLIHjKbnKzr4ruF7g=
github.com/emiago/sipgo v1.0.1-alpha.0.20251212165843-9c9bcdf9126f/go.mod h1:DuwAxBZhKMqIzQFPGZb1MVAGU6Wuxj64oTOhd5dx/FY=
github.com/expr-lang/expr v1.17.6 h1:1h6i8ONk9cexhDmowO/A64VPxHScu7qfSl2k8OlINec=
github.com/expr-lang/expr v1.17.6/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/emiago/sipgo v1.0.1 h1:8eCZ6L/VX3isyByyv1RrBoQ5GyBoRXBHkNMYjwacRfk=
github.com/emiago/sipgo v1.0.1/go.mod h1:DuwAxBZhKMqIzQFPGZb1MVAGU6Wuxj64oTOhd5dx/FY=
github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8=
github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
@@ -44,8 +44,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=

View File

@@ -2,7 +2,7 @@ package module
import (
"context"
"fmt"
"errors"
"log/slog"
"net/http"
"time"
@@ -16,6 +16,7 @@ type HTTPClient struct {
ctx context.Context
client *http.Client
router route.RouteIO
logger *slog.Logger
}
func init() {
@@ -23,7 +24,7 @@ func init() {
Type: "http.client",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
return &HTTPClient{config: config, ctx: ctx, router: router}, nil
return &HTTPClient{config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -43,7 +44,7 @@ func (hc *HTTPClient) Run() error {
}
<-hc.ctx.Done()
slog.Debug("router context done in module", "id", hc.Id())
hc.logger.Debug("router context done in module")
return nil
}
@@ -52,11 +53,11 @@ func (hc *HTTPClient) Output(payload any) error {
payloadRequest, ok := payload.(*http.Request)
if !ok {
return fmt.Errorf("http.client is only able to output an http.Request")
return errors.New("http.client is only able to output an http.Request")
}
if hc.client == nil {
return fmt.Errorf("http.client client is nil")
return errors.New("http.client client is nil")
}
response, err := hc.client.Do(payloadRequest)

View File

@@ -3,6 +3,7 @@ package module
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
@@ -16,6 +17,7 @@ type HTTPServer struct {
Port uint16
ctx context.Context
router route.RouteIO
logger *slog.Logger
}
type ResponseData struct {
@@ -30,16 +32,16 @@ func init() {
params := config.Params
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("http.server requires a port parameter")
return nil, errors.New("http.server requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, fmt.Errorf("http.server port must be uint16")
return nil, errors.New("http.server port must be uint16")
}
return &HTTPServer{Port: uint16(portNum), config: config, ctx: ctx, router: router}, nil
return &HTTPServer{Port: uint16(portNum), config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -89,12 +91,11 @@ func (hs *HTTPServer) Run() error {
go func() {
<-hs.ctx.Done()
slog.Debug("router context done in module", "id", hs.Id())
hs.logger.Debug("router context done in module")
httpServer.Close()
}()
err := httpServer.ListenAndServe()
slog.Debug("http.server closed", "id", hs.Id())
// TODO(jwetzell): handle server closed error differently
if err != nil {
return err
@@ -105,5 +106,5 @@ func (hs *HTTPServer) Run() error {
}
func (hs *HTTPServer) Output(payload any) error {
return fmt.Errorf("http.server output is not implemented")
return errors.New("http.server output is not implemented")
}

View File

@@ -2,7 +2,7 @@ package module
import (
"context"
"fmt"
"errors"
"log/slog"
"time"
@@ -16,6 +16,7 @@ type Interval struct {
ctx context.Context
router route.RouteIO
ticker *time.Ticker
logger *slog.Logger
}
func init() {
@@ -26,16 +27,16 @@ func init() {
duration, ok := params["duration"]
if !ok {
return nil, fmt.Errorf("gen.interval requires a duration parameter")
return nil, errors.New("gen.interval requires a duration parameter")
}
durationNum, ok := duration.(float64)
if !ok {
return nil, fmt.Errorf("gen.interval duration must be number")
return nil, errors.New("gen.interval duration must be number")
}
return &Interval{Duration: uint32(durationNum), config: config, ctx: ctx, router: router}, nil
return &Interval{Duration: uint32(durationNum), config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -56,7 +57,7 @@ func (i *Interval) Run() error {
for {
select {
case <-i.ctx.Done():
slog.Debug("router context done in module", "id", i.Id())
i.logger.Debug("router context done in module")
return nil
case <-ticker.C:
if i.router != nil {

View File

@@ -4,6 +4,7 @@ package module
import (
"context"
"errors"
"fmt"
"log/slog"
@@ -19,6 +20,7 @@ type MIDIInput struct {
router route.RouteIO
Port string
SendFunc func(midi.Message) error
logger *slog.Logger
}
func init() {
@@ -29,39 +31,39 @@ func init() {
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("midi.input requires a port parameter")
return nil, errors.New("midi.input requires a port parameter")
}
portString, ok := port.(string)
if !ok {
return nil, fmt.Errorf("midi.input port must be a string")
return nil, errors.New("midi.input port must be a string")
}
return &MIDIInput{config: config, Port: portString, ctx: ctx, router: router}, nil
return &MIDIInput{config: config, Port: portString, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
func (mc *MIDIInput) Id() string {
return mc.config.Id
func (mi *MIDIInput) Id() string {
return mi.config.Id
}
func (mc *MIDIInput) Type() string {
return mc.config.Type
func (mi *MIDIInput) Type() string {
return mi.config.Type
}
func (mc *MIDIInput) Run() error {
func (mi *MIDIInput) Run() error {
defer midi.CloseDriver()
in, err := midi.FindInPort(mc.Port)
in, err := midi.FindInPort(mi.Port)
if err != nil {
return fmt.Errorf("midi.input can't find input port: %s", mc.Port)
return fmt.Errorf("midi.input can't find input port: %s", mi.Port)
}
stop, err := midi.ListenTo(in, func(msg midi.Message, timestampms int32) {
if mc.router != nil {
mc.router.HandleInput(mc.Id(), msg)
if mi.router != nil {
mi.router.HandleInput(mi.Id(), msg)
}
}, midi.UseSysEx())
@@ -71,11 +73,11 @@ func (mc *MIDIInput) Run() error {
defer stop()
<-mc.ctx.Done()
slog.Debug("router context done in module", "id", mc.Id())
<-mi.ctx.Done()
mi.logger.Debug("router context done in module")
return nil
}
func (mc *MIDIInput) Output(payload any) error {
return fmt.Errorf("midi.input output is not implemented")
func (mi *MIDIInput) Output(payload any) error {
return errors.New("midi.input output is not implemented")
}

View File

@@ -4,6 +4,7 @@ package module
import (
"context"
"errors"
"fmt"
"log/slog"
@@ -19,11 +20,11 @@ type MIDIOutput struct {
router route.RouteIO
Port string
SendFunc func(midi.Message) error
logger *slog.Logger
}
func init() {
RegisterModule(ModuleRegistration{
//TODO(jwetzell): find a better namespace than "misc"
Type: "midi.output",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
params := config.Params
@@ -31,35 +32,35 @@ func init() {
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("midi.output requires a port parameter")
return nil, errors.New("midi.output requires a port parameter")
}
portString, ok := port.(string)
if !ok {
return nil, fmt.Errorf("midi.output port must be a string")
return nil, errors.New("midi.output port must be a string")
}
return &MIDIOutput{config: config, Port: portString, ctx: ctx, router: router}, nil
return &MIDIOutput{config: config, Port: portString, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
func (mc *MIDIOutput) Id() string {
return mc.config.Id
func (mo *MIDIOutput) Id() string {
return mo.config.Id
}
func (mc *MIDIOutput) Type() string {
return mc.config.Type
func (mo *MIDIOutput) Type() string {
return mo.config.Type
}
func (mc *MIDIOutput) Run() error {
func (mo *MIDIOutput) Run() error {
defer midi.CloseDriver()
out, err := midi.FindOutPort(mc.Port)
out, err := midi.FindOutPort(mo.Port)
if err != nil {
return fmt.Errorf("midi.output can't find output port: %s", mc.Port)
return fmt.Errorf("midi.output can't find output port: %s", mo.Port)
}
send, err := midi.SendTo(out)
@@ -67,23 +68,23 @@ func (mc *MIDIOutput) Run() error {
return err
}
mc.SendFunc = send
mo.SendFunc = send
<-mc.ctx.Done()
slog.Debug("router context done in module", "id", mc.Id())
<-mo.ctx.Done()
mo.logger.Debug("router context done in module")
return nil
}
func (mc *MIDIOutput) Output(payload any) error {
if mc.SendFunc == nil {
return fmt.Errorf("midi.output output is not setup")
func (mo *MIDIOutput) Output(payload any) error {
if mo.SendFunc == nil {
return errors.New("midi.output output is not setup")
}
payloadMessage, ok := payload.(midi.Message)
if !ok {
return fmt.Errorf("midi.output can only ouptut midi.Message")
return errors.New("midi.output can only ouptut midi.Message")
}
return mc.SendFunc(payloadMessage)
return mo.SendFunc(payloadMessage)
}

View File

@@ -2,6 +2,7 @@ package module
import (
"context"
"errors"
"fmt"
"log/slog"
@@ -18,6 +19,7 @@ type MQTTClient struct {
ClientID string
Topic string
client mqtt.Client
logger *slog.Logger
}
func init() {
@@ -28,40 +30,40 @@ func init() {
broker, ok := params["broker"]
if !ok {
return nil, fmt.Errorf("mqtt.client requires a broker parameter")
return nil, errors.New("mqtt.client requires a broker parameter")
}
brokerString, ok := broker.(string)
if !ok {
return nil, fmt.Errorf("mqtt.client broker must be string")
return nil, errors.New("mqtt.client broker must be string")
}
topic, ok := params["topic"]
if !ok {
return nil, fmt.Errorf("mqtt.client requires a topic parameter")
return nil, errors.New("mqtt.client requires a topic parameter")
}
topicString, ok := topic.(string)
if !ok {
return nil, fmt.Errorf("mqtt.client topic must be string")
return nil, errors.New("mqtt.client topic must be string")
}
clientId, ok := params["clientId"]
if !ok {
return nil, fmt.Errorf("mqtt.client requires a clientId parameter")
return nil, errors.New("mqtt.client requires a clientId parameter")
}
clientIdString, ok := clientId.(string)
if !ok {
return nil, fmt.Errorf("mqtt.client clientId must be string")
return nil, errors.New("mqtt.client clientId must be string")
}
return &MQTTClient{config: config, Broker: brokerString, Topic: topicString, ClientID: clientIdString, ctx: ctx, router: router}, nil
return &MQTTClient{config: config, Broker: brokerString, Topic: topicString, ClientID: clientIdString, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -99,7 +101,7 @@ func (mc *MQTTClient) Run() error {
}
<-mc.ctx.Done()
slog.Debug("router context done in module", "id", mc.Id())
mc.logger.Debug("router context done in module")
return nil
}
@@ -109,15 +111,15 @@ func (mc *MQTTClient) Output(payload any) error {
fmt.Printf("payload type: %T\n", payload)
if !ok {
return fmt.Errorf("mqtt.client is only able to output a MQTTMessage")
return errors.New("mqtt.client is only able to output a MQTTMessage")
}
if mc.client == nil {
return fmt.Errorf("mqtt.client client is not setup")
return errors.New("mqtt.client client is not setup")
}
if !mc.client.IsConnected() {
return fmt.Errorf("mqtt.client is not connected")
return errors.New("mqtt.client is not connected")
}
token := mc.client.Publish(payloadMessage.Topic(), payloadMessage.Qos(), payloadMessage.Retained(), payloadMessage.Payload())

View File

@@ -2,7 +2,7 @@ package module
import (
"context"
"fmt"
"errors"
"log/slog"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -18,6 +18,7 @@ type NATSClient struct {
URL string
Subject string
client *nats.Conn
logger *slog.Logger
}
func init() {
@@ -28,28 +29,28 @@ func init() {
url, ok := params["url"]
if !ok {
return nil, fmt.Errorf("nats.client requires a url parameter")
return nil, errors.New("nats.client requires a url parameter")
}
urlString, ok := url.(string)
if !ok {
return nil, fmt.Errorf("nats.client url must be string")
return nil, errors.New("nats.client url must be string")
}
subject, ok := params["subject"]
if !ok {
return nil, fmt.Errorf("nats.client requires a subject parameter")
return nil, errors.New("nats.client requires a subject parameter")
}
subjectString, ok := subject.(string)
if !ok {
return nil, fmt.Errorf("nats.client subject must be string")
return nil, errors.New("nats.client subject must be string")
}
return &NATSClient{config: config, URL: urlString, Subject: subjectString, ctx: ctx, router: router}, nil
return &NATSClient{config: config, URL: urlString, Subject: subjectString, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -87,7 +88,7 @@ func (nc *NATSClient) Run() error {
defer sub.Unsubscribe()
<-nc.ctx.Done()
slog.Debug("router context done in module", "id", nc.Id())
nc.logger.Debug("router context done in module")
return nil
}
@@ -96,15 +97,15 @@ func (nc *NATSClient) Output(payload any) error {
payloadMessage, ok := payload.(processor.NATSMessage)
if !ok {
return fmt.Errorf("nats.client is only able to output NATSMessage")
return errors.New("nats.client is only able to output NATSMessage")
}
if nc.client == nil {
return fmt.Errorf("nats.client client is not setup")
return errors.New("nats.client client is not setup")
}
if !nc.client.IsConnected() {
return fmt.Errorf("nats.client is not connected")
return errors.New("nats.client is not connected")
}
err := nc.client.Publish(payloadMessage.Subject, payloadMessage.Payload)

View File

@@ -18,6 +18,7 @@ type PSNClient struct {
ctx context.Context
router route.RouteIO
decoder *psn.Decoder
logger *slog.Logger
}
func init() {
@@ -25,7 +26,7 @@ func init() {
Type: "psn.client",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
return &PSNClient{config: config, decoder: psn.NewDecoder(), ctx: ctx, router: router}, nil
return &PSNClient{config: config, decoder: psn.NewDecoder(), ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -58,7 +59,7 @@ func (pc *PSNClient) Run() error {
select {
case <-pc.ctx.Done():
// TODO(jwetzell): cleanup?
slog.Debug("router context done in module", "id", pc.Id())
pc.logger.Debug("router context done in module")
return nil
default:
pc.conn.SetDeadline(time.Now().Add(time.Millisecond * 200))
@@ -76,7 +77,7 @@ func (pc *PSNClient) Run() error {
message := buffer[:numBytes]
err := pc.decoder.Decode(message)
if err != nil {
slog.Error("psn.client problem decoding psn traffic", "id", pc.Id(), "error", err)
pc.logger.Error("psn.client problem decoding psn traffic", "error", err)
}
if pc.router != nil {
@@ -84,7 +85,7 @@ func (pc *PSNClient) Run() error {
pc.router.HandleInput(pc.Id(), tracker)
}
} else {
slog.Error("psn.client has no router", "id", pc.Id())
pc.logger.Error("psn.client has no router")
}
}
}

View File

@@ -4,6 +4,7 @@ package module
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
@@ -22,24 +23,24 @@ type SerialClient struct {
Framer framer.Framer
Mode *serial.Mode
port serial.Port
logger *slog.Logger
}
func init() {
RegisterModule(ModuleRegistration{
//TODO(jwetzell): find a better namespace than "misc"
Type: "serial.client",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
params := config.Params
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("serial.client requires a port parameter")
return nil, errors.New("serial.client requires a port parameter")
}
portString, ok := port.(string)
if !ok {
return nil, fmt.Errorf("serial.client port must be a string")
return nil, errors.New("serial.client port must be a string")
}
framingMethod := "RAW"
@@ -50,7 +51,7 @@ func init() {
framingMethodString, ok := framingMethodRaw.(string)
if !ok {
return nil, fmt.Errorf("serial.client framing method must be a string")
return nil, errors.New("serial.client framing method must be a string")
}
framingMethod = framingMethodString
}
@@ -63,94 +64,94 @@ func init() {
buadRate, ok := params["baudRate"]
if !ok {
return nil, fmt.Errorf("serial.client requires a baudRate parameter")
return nil, errors.New("serial.client requires a baudRate parameter")
}
baudRateNum, ok := buadRate.(float64)
if !ok {
return nil, fmt.Errorf("serial.client baudRate must be a number")
return nil, errors.New("serial.client baudRate must be a number")
}
mode := serial.Mode{
BaudRate: int(baudRateNum),
}
return &SerialClient{config: config, Port: portString, Framer: framer, Mode: &mode, ctx: ctx, router: router}, nil
return &SerialClient{config: config, Port: portString, Framer: framer, Mode: &mode, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
func (mc *SerialClient) Id() string {
return mc.config.Id
func (sc *SerialClient) Id() string {
return sc.config.Id
}
func (mc *SerialClient) Type() string {
return mc.config.Type
func (sc *SerialClient) Type() string {
return sc.config.Type
}
func (mc *SerialClient) SetupPort() error {
func (sc *SerialClient) SetupPort() error {
port, err := serial.Open(mc.Port, mc.Mode)
port, err := serial.Open(sc.Port, sc.Mode)
if err != nil {
return fmt.Errorf("serial.client can't open input port: %s", mc.Port)
return fmt.Errorf("serial.client can't open input port: %s", sc.Port)
}
mc.port = port
sc.port = port
return nil
}
func (mc *SerialClient) Run() error {
func (sc *SerialClient) Run() error {
// TODO(jwetzell): shutdown with router.Context properly
go func() {
<-mc.ctx.Done()
slog.Debug("router context done in module", "id", mc.Id())
if mc.port != nil {
mc.port.Close()
<-sc.ctx.Done()
sc.logger.Debug("router context done in module")
if sc.port != nil {
sc.port.Close()
}
}()
for {
err := mc.SetupPort()
err := sc.SetupPort()
if err != nil {
if mc.ctx.Err() != nil {
slog.Debug("router context done in module", "id", mc.Id())
if sc.ctx.Err() != nil {
sc.logger.Debug("router context done in module")
return nil
}
slog.Error("serial.client", "id", mc.Id(), "error", err.Error())
sc.logger.Error("serial.client", "error", err.Error())
time.Sleep(time.Second * 2)
continue
}
buffer := make([]byte, 1024)
select {
case <-mc.ctx.Done():
slog.Debug("router context done in module", "id", mc.Id())
case <-sc.ctx.Done():
sc.logger.Debug("router context done in module")
return nil
default:
READ:
for {
select {
case <-mc.ctx.Done():
slog.Debug("router context done in module", "id", mc.Id())
case <-sc.ctx.Done():
sc.logger.Debug("router context done in module")
return nil
default:
byteCount, err := mc.port.Read(buffer)
byteCount, err := sc.port.Read(buffer)
if err != nil {
mc.Framer.Clear()
sc.Framer.Clear()
break READ
}
if mc.Framer != nil {
if sc.Framer != nil {
if byteCount > 0 {
messages := mc.Framer.Decode(buffer[0:byteCount])
messages := sc.Framer.Decode(buffer[0:byteCount])
for _, message := range messages {
if mc.router != nil {
mc.router.HandleInput(mc.Id(), message)
if sc.router != nil {
sc.router.HandleInput(sc.Id(), message)
} else {
slog.Error("serial.client has no router", "id", mc.Id())
sc.logger.Error("serial.client has no router")
}
}
}
@@ -161,14 +162,14 @@ func (mc *SerialClient) Run() error {
}
}
func (mc *SerialClient) Output(payload any) error {
func (sc *SerialClient) Output(payload any) error {
payloadBytes, ok := payload.([]byte)
if !ok {
return fmt.Errorf("serial.client can only ouptut bytes")
return errors.New("serial.client can only ouptut bytes")
}
_, err := mc.port.Write(mc.Framer.Encode(payloadBytes))
_, err := sc.port.Write(sc.Framer.Encode(payloadBytes))
return err
}

View File

@@ -2,6 +2,7 @@ package module
import (
"context"
"errors"
"fmt"
"io"
"log/slog"
@@ -24,6 +25,7 @@ type SIPCallServer struct {
Transport string
UserAgent string
dg *diago.Diago
logger *slog.Logger
}
type SIPCallMessage struct {
@@ -42,7 +44,7 @@ func init() {
specificPortNum, ok := port.(float64)
if !ok {
return nil, fmt.Errorf("sip.call.server port must be a number")
return nil, errors.New("sip.call.server port must be a number")
}
portNum = int(specificPortNum)
}
@@ -55,7 +57,7 @@ func init() {
specificIpString, ok := ip.(string)
if !ok {
return nil, fmt.Errorf("sip.call.server ip must be a string")
return nil, errors.New("sip.call.server ip must be a string")
}
ipString = specificIpString
}
@@ -68,7 +70,7 @@ func init() {
specificTransportString, ok := transport.(string)
if !ok {
return nil, fmt.Errorf("sip.call.server transport must be a string")
return nil, errors.New("sip.call.server transport must be a string")
}
transportString = specificTransportString
}
@@ -81,28 +83,28 @@ func init() {
specificTransportString, ok := userAgent.(string)
if !ok {
return nil, fmt.Errorf("sip.call.server userAgent must be a string")
return nil, errors.New("sip.call.server userAgent must be a string")
}
userAgentString = specificTransportString
}
return &SIPCallServer{config: config, ctx: ctx, router: router, IP: ipString, Port: int(portNum), Transport: transportString, UserAgent: userAgentString}, nil
return &SIPCallServer{config: config, ctx: ctx, router: router, IP: ipString, Port: int(portNum), Transport: transportString, UserAgent: userAgentString, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
func (sds *SIPCallServer) Id() string {
return sds.config.Id
func (scs *SIPCallServer) Id() string {
return scs.config.Id
}
func (sds *SIPCallServer) Type() string {
return sds.config.Type
func (scs *SIPCallServer) Type() string {
return scs.config.Type
}
func (sds *SIPCallServer) Run() error {
func (scs *SIPCallServer) Run() error {
diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil))
ua, _ := sipgo.NewUA(
sipgo.WithUserAgent(sds.UserAgent),
sipgo.WithUserAgent(scs.UserAgent),
sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)),
sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)),
)
@@ -112,74 +114,74 @@ func (sds *SIPCallServer) Run() error {
media.SetDefaultLogger(diagoLogger)
dg := diago.NewDiago(ua, diago.WithLogger(diagoLogger), diago.WithTransport(
diago.Transport{
Transport: sds.Transport,
BindHost: sds.IP,
BindPort: sds.Port,
Transport: scs.Transport,
BindHost: scs.IP,
BindPort: scs.Port,
},
))
go func() {
dg.Serve(sds.ctx, func(inDialog *diago.DialogServerSession) {
sds.HandleCall(inDialog)
dg.Serve(scs.ctx, func(inDialog *diago.DialogServerSession) {
scs.HandleCall(inDialog)
})
}()
sds.dg = dg
scs.dg = dg
<-sds.ctx.Done()
slog.Debug("router context done in module", "id", sds.Id())
<-scs.ctx.Done()
scs.logger.Debug("router context done in module")
return nil
}
func (sds *SIPCallServer) HandleCall(inDialog *diago.DialogServerSession) {
func (scs *SIPCallServer) HandleCall(inDialog *diago.DialogServerSession) {
inDialog.Trying()
inDialog.Ringing()
inDialog.Answer()
sds.router.HandleInput(sds.Id(), SIPCallMessage{
scs.router.HandleInput(scs.Id(), SIPCallMessage{
To: inDialog.ToUser(),
})
<-inDialog.Context().Done()
}
func (sds *SIPCallServer) Output(payload any) error {
func (scs *SIPCallServer) Output(payload any) error {
payloadMsg, ok := payload.(string)
if !ok {
return fmt.Errorf("sip.call.server output payload must be of type string")
return errors.New("sip.call.server output payload must be of type string")
}
if sds.dg == nil {
return fmt.Errorf("sip.call.server diago is not initialized")
if scs.dg == nil {
return errors.New("sip.call.server diago is not initialized")
}
var uri sip.Uri
err := sip.ParseUri(payloadMsg, &uri)
if err != nil {
return fmt.Errorf("sip.call.server output payload is not a valid SIP URI: %v", err)
return fmt.Errorf("sip.call.server output payload is not a valid SIP URI: %s", err)
}
outDialog, err := sds.dg.NewDialog(uri, diago.NewDialogOptions{
Transport: sds.Transport,
outDialog, err := scs.dg.NewDialog(uri, diago.NewDialogOptions{
Transport: scs.Transport,
})
if err != nil {
return fmt.Errorf("sip.call.server failed to create new dialog: %v", err)
return fmt.Errorf("sip.call.server failed to create new dialog: %s", err)
}
err = outDialog.Invite(sds.ctx, diago.InviteClientOptions{})
err = outDialog.Invite(scs.ctx, diago.InviteClientOptions{})
if err != nil {
return fmt.Errorf("sip.call.server failed to send invite: %v", err)
return fmt.Errorf("sip.call.server failed to send invite: %s", err)
}
err = outDialog.Ack(sds.ctx)
err = outDialog.Ack(scs.ctx)
if err != nil {
return fmt.Errorf("sip.call.server failed to send ack: %v", err)
return fmt.Errorf("sip.call.server failed to send ack: %s", err)
}
// TODO(jwetzell): make this configurable
// NOTE(jwetzell): wait 5 seconds before hanging up the call
time.Sleep(5 * time.Second)
err = outDialog.Hangup(sds.ctx)
err = outDialog.Hangup(scs.ctx)
if err != nil {
return fmt.Errorf("sip.call.server failed to hangup call: %v", err)
return fmt.Errorf("sip.call.server failed to hangup call: %s", err)
}
return nil
}

View File

@@ -2,7 +2,7 @@ package module
import (
"context"
"fmt"
"errors"
"io"
"log/slog"
"strings"
@@ -24,6 +24,7 @@ type SIPDTMFServer struct {
Port int
Transport string
Separator string
logger *slog.Logger
}
type SIPDTMFMessage struct {
@@ -43,7 +44,7 @@ func init() {
specificPortNum, ok := port.(float64)
if !ok {
return nil, fmt.Errorf("sip.dtmf.server port must be a number")
return nil, errors.New("sip.dtmf.server port must be a number")
}
portNum = int(specificPortNum)
}
@@ -56,7 +57,7 @@ func init() {
specificIpString, ok := ip.(string)
if !ok {
return nil, fmt.Errorf("sip.dtmf.server ip must be a string")
return nil, errors.New("sip.dtmf.server ip must be a string")
}
ipString = specificIpString
}
@@ -69,28 +70,28 @@ func init() {
specificTransportString, ok := transport.(string)
if !ok {
return nil, fmt.Errorf("sip.dtmf.server transport must be a string")
return nil, errors.New("sip.dtmf.server transport must be a string")
}
transportString = specificTransportString
}
separator, ok := params["separator"]
if !ok {
return nil, fmt.Errorf("sip.dtmf.server requires a separator parameter")
return nil, errors.New("sip.dtmf.server requires a separator parameter")
}
separatorString, ok := separator.(string)
if !ok {
return nil, fmt.Errorf("sip.dtmf.server separator must be a string")
return nil, errors.New("sip.dtmf.server separator must be a string")
}
if len(separatorString) != 1 {
return nil, fmt.Errorf("sip.dtmf.server separator must be a single character")
return nil, errors.New("sip.dtmf.server separator must be a single character")
}
if !strings.ContainsRune("0123456789*#ABCD", rune(separatorString[0])) {
return nil, fmt.Errorf("sip.dtmf.server separator must be a valid DTMF character")
return nil, errors.New("sip.dtmf.server separator must be a valid DTMF character")
}
return &SIPDTMFServer{config: config, ctx: ctx, router: router, IP: ipString, Port: int(portNum), Transport: transportString, Separator: separatorString}, nil
return &SIPDTMFServer{config: config, ctx: ctx, router: router, IP: ipString, Port: int(portNum), Transport: transportString, Separator: separatorString, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -131,7 +132,7 @@ func (sds *SIPDTMFServer) Run() error {
}
<-sds.ctx.Done()
slog.Debug("router context done in module", "id", sds.Id())
sds.logger.Debug("router context done in module")
return nil
}
@@ -159,5 +160,5 @@ func (sds *SIPDTMFServer) HandleCall(inDialog *diago.DialogServerSession) error
}
func (sds *SIPDTMFServer) Output(payload any) error {
return fmt.Errorf("sip.dtmf.server output is not implemented")
return errors.New("sip.dtmf.server output is not implemented")
}

View File

@@ -2,6 +2,7 @@ package module
import (
"context"
"errors"
"fmt"
"log/slog"
"net"
@@ -19,6 +20,7 @@ type TCPClient struct {
ctx context.Context
router route.RouteIO
Addr *net.TCPAddr
logger *slog.Logger
}
func init() {
@@ -29,24 +31,24 @@ func init() {
host, ok := params["host"]
if !ok {
return nil, fmt.Errorf("net.tcp.client requires a host parameter")
return nil, errors.New("net.tcp.client requires a host parameter")
}
hostString, ok := host.(string)
if !ok {
return nil, fmt.Errorf("net.tcp.client host must be string")
return nil, errors.New("net.tcp.client host must be string")
}
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("net.tcp.client requires a port parameter")
return nil, errors.New("net.tcp.client requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, fmt.Errorf("net.tcp.client port must be a number")
return nil, errors.New("net.tcp.client port must be a number")
}
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", hostString, uint16(portNum)))
@@ -62,7 +64,7 @@ func init() {
framingMethodString, ok := framingMethodRaw.(string)
if !ok {
return nil, fmt.Errorf("misc.serial.client framing method must be a string")
return nil, errors.New("misc.serial.client framing method must be a string")
}
framingMethod = framingMethodString
}
@@ -73,7 +75,7 @@ func init() {
return nil, err
}
return &TCPClient{framer: framer, Addr: addr, config: config, ctx: ctx, router: router}, nil
return &TCPClient{framer: framer, Addr: addr, config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -91,7 +93,7 @@ func (tc *TCPClient) Run() error {
// TODO(jwetzell): shutdown with router.Context properly
go func() {
<-tc.ctx.Done()
slog.Debug("router context done in module", "id", tc.Id())
tc.logger.Debug("router context done in module")
if tc.conn != nil {
tc.conn.Close()
}
@@ -101,10 +103,10 @@ func (tc *TCPClient) Run() error {
err := tc.SetupConn()
if err != nil {
if tc.ctx.Err() != nil {
slog.Debug("router context done in module", "id", tc.Id())
tc.logger.Debug("router context done in module")
return nil
}
slog.Error("net.tcp.client", "id", tc.Id(), "error", err.Error())
tc.logger.Error("net.tcp.client", "error", err.Error())
time.Sleep(time.Second * 2)
continue
}
@@ -112,14 +114,14 @@ func (tc *TCPClient) Run() error {
buffer := make([]byte, 1024)
select {
case <-tc.ctx.Done():
slog.Debug("router context done in module", "id", tc.Id())
tc.logger.Debug("router context done in module")
return nil
default:
READ:
for {
select {
case <-tc.ctx.Done():
slog.Debug("router context done in module", "id", tc.Id())
tc.logger.Debug("router context done in module")
return nil
default:
byteCount, err := tc.conn.Read(buffer)
@@ -136,7 +138,7 @@ func (tc *TCPClient) Run() error {
if tc.router != nil {
tc.router.HandleInput(tc.Id(), message)
} else {
slog.Error("net.tcp.client has no router", "id", tc.Id())
tc.logger.Error("net.tcp.client has no router")
}
}
}
@@ -163,7 +165,7 @@ func (tc *TCPClient) Output(payload any) error {
}
payloadBytes, ok := payload.([]byte)
if !ok {
return fmt.Errorf("net.tcp.client is only able to output bytes")
return errors.New("net.tcp.client is only able to output bytes")
}
_, err := tc.conn.Write(tc.framer.Encode(payloadBytes))
return err

View File

@@ -26,6 +26,7 @@ type TCPServer struct {
wg sync.WaitGroup
connections []*net.TCPConn
connectionsMu sync.RWMutex
logger *slog.Logger
}
func init() {
@@ -35,13 +36,13 @@ func init() {
params := config.Params
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("net.tcp.server requires a port parameter")
return nil, errors.New("net.tcp.server requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, fmt.Errorf("net.tcp.server port must be a number")
return nil, errors.New("net.tcp.server port must be a number")
}
framingMethod := "RAW"
@@ -52,7 +53,7 @@ func init() {
framingMethodString, ok := framingMethodRaw.(string)
if !ok {
return nil, fmt.Errorf("misc.serial.client framing method must be a string")
return nil, errors.New("misc.serial.client framing method must be a string")
}
framingMethod = framingMethodString
}
@@ -71,7 +72,7 @@ func init() {
specificIpString, ok := ip.(string)
if !ok {
return nil, fmt.Errorf("net.tcp.server ip must be a string")
return nil, errors.New("net.tcp.server ip must be a string")
}
ipString = specificIpString
}
@@ -81,7 +82,7 @@ func init() {
return nil, err
}
return &TCPServer{Framer: framer, Addr: addr, config: config, quit: make(chan interface{}), ctx: ctx, router: router}, nil
return &TCPServer{Framer: framer, Addr: addr, config: config, quit: make(chan interface{}), ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -98,7 +99,7 @@ func (ts *TCPServer) handleClient(client *net.TCPConn) {
ts.connectionsMu.Lock()
ts.connections = append(ts.connections, client)
ts.connectionsMu.Unlock()
slog.Debug("net.tcp.server connection accepted", "id", ts.Id(), "remoteAddr", client.RemoteAddr().String())
ts.logger.Debug("net.tcp.server connection accepted", "remoteAddr", client.RemoteAddr().String())
defer client.Close()
buffer := make([]byte, 1024)
@@ -125,7 +126,7 @@ ClientRead:
break
}
}
slog.Debug("net.tcp.server connection reset", "id", ts.Id(), "remoteAddr", client.RemoteAddr().String())
ts.logger.Debug("net.tcp.server connection reset", "remoteAddr", client.RemoteAddr().String())
ts.connectionsMu.Unlock()
}
}
@@ -138,7 +139,7 @@ ClientRead:
break
}
}
slog.Debug("net.tcp.server stream ended", "id", ts.Id(), "remoteAddr", client.RemoteAddr().String())
ts.logger.Debug("net.tcp.server stream ended", "remoteAddr", client.RemoteAddr().String())
ts.connectionsMu.Unlock()
}
return
@@ -150,7 +151,7 @@ ClientRead:
if ts.router != nil {
ts.router.HandleInput(ts.Id(), message)
} else {
slog.Error("net.tcp.server has no router", "id", ts.Id())
ts.logger.Error("net.tcp.server has no router")
}
}
}
@@ -170,7 +171,7 @@ func (ts *TCPServer) Run() error {
<-ts.ctx.Done()
close(ts.quit)
listener.Close()
slog.Debug("router context done in module", "id", ts.Id())
ts.logger.Debug("router context done in module")
}()
AcceptLoop:
@@ -181,7 +182,7 @@ AcceptLoop:
case <-ts.quit:
break AcceptLoop
default:
slog.Debug("net.tcp.server problem with listener", "error", err)
ts.logger.Debug("net.tcp.server problem with listener", "error", err)
}
} else {
ts.wg.Add(1)
@@ -200,7 +201,7 @@ func (ts *TCPServer) Output(payload any) error {
payloadBytes, ok := payload.([]byte)
if !ok {
return fmt.Errorf("net.tcp.server is only able to output bytes")
return errors.New("net.tcp.server is only able to output bytes")
}
ts.connectionsMu.Lock()
errorString := ""
@@ -216,5 +217,5 @@ func (ts *TCPServer) Output(payload any) error {
if errorString == "" {
return nil
}
return fmt.Errorf("%s", errorString)
return fmt.Errorf("net.tcp.server error during output: %s", errorString)
}

View File

@@ -2,7 +2,7 @@ package module
import (
"context"
"fmt"
"errors"
"log/slog"
"time"
@@ -16,6 +16,7 @@ type Timer struct {
ctx context.Context
router route.RouteIO
timer *time.Timer
logger *slog.Logger
}
func init() {
@@ -26,16 +27,16 @@ func init() {
duration, ok := params["duration"]
if !ok {
return nil, fmt.Errorf("gen.timer requires a duration parameter")
return nil, errors.New("gen.timer requires a duration parameter")
}
durationNum, ok := duration.(float64)
if !ok {
return nil, fmt.Errorf("gen.timer duration must be a number")
return nil, errors.New("gen.timer duration must be a number")
}
return &Timer{Duration: uint32(durationNum), config: config, ctx: ctx, router: router}, nil
return &Timer{Duration: uint32(durationNum), config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -55,7 +56,7 @@ func (t *Timer) Run() error {
select {
case <-t.ctx.Done():
t.timer.Stop()
slog.Debug("router context done in module", "id", t.Id())
t.logger.Debug("router context done in module")
return nil
case time := <-t.timer.C:
if t.router != nil {

View File

@@ -2,6 +2,7 @@ package module
import (
"context"
"errors"
"fmt"
"log/slog"
"net"
@@ -17,6 +18,7 @@ type UDPClient struct {
conn *net.UDPConn
ctx context.Context
router route.RouteIO
logger *slog.Logger
}
func init() {
@@ -27,24 +29,24 @@ func init() {
host, ok := params["host"]
if !ok {
return nil, fmt.Errorf("net.udp.client requires a host parameter")
return nil, errors.New("net.udp.client requires a host parameter")
}
hostString, ok := host.(string)
if !ok {
return nil, fmt.Errorf("net.udp.client host must be a string")
return nil, errors.New("net.udp.client host must be a string")
}
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("net.udp.client requires a port parameter")
return nil, errors.New("net.udp.client requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, fmt.Errorf("net.udp.client port must be a number")
return nil, errors.New("net.udp.client port must be a number")
}
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", hostString, uint16(portNum)))
@@ -52,7 +54,7 @@ func init() {
return nil, err
}
return &UDPClient{Addr: addr, config: config, ctx: ctx, router: router}, nil
return &UDPClient{Addr: addr, config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -79,7 +81,7 @@ func (uc *UDPClient) Run() error {
}
<-uc.ctx.Done()
slog.Debug("router context done in module", "id", uc.Id())
uc.logger.Debug("router context done in module")
if uc.conn != nil {
uc.conn.Close()
}
@@ -90,7 +92,7 @@ func (uc *UDPClient) Output(payload any) error {
payloadBytes, ok := payload.([]byte)
if !ok {
return fmt.Errorf("net.udp.client is only able to output bytes")
return errors.New("net.udp.client is only able to output bytes")
}
if uc.conn != nil {
_, err := uc.conn.Write(payloadBytes)
@@ -99,7 +101,7 @@ func (uc *UDPClient) Output(payload any) error {
return err
}
} else {
return fmt.Errorf("net.udp.client client is not setup")
return errors.New("net.udp.client client is not setup")
}
return nil
}

View File

@@ -2,6 +2,7 @@ package module
import (
"context"
"errors"
"fmt"
"log/slog"
"net"
@@ -17,6 +18,7 @@ type UDPMulticast struct {
ctx context.Context
router route.RouteIO
Addr *net.UDPAddr
logger *slog.Logger
}
func init() {
@@ -27,31 +29,31 @@ func init() {
ip, ok := params["ip"]
if !ok {
return nil, fmt.Errorf("net.udp.client requires am ip parameter")
return nil, errors.New("net.udp.multicast requires an ip parameter")
}
ipString, ok := ip.(string)
if !ok {
return nil, fmt.Errorf("net.udp.client ip must be a string")
return nil, errors.New("net.udp.multicast ip must be a string")
}
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("net.udp.client requires a port parameter")
return nil, errors.New("net.udp.multicast requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, fmt.Errorf("net.udp.client port must be a number")
return nil, errors.New("net.udp.multicast port must be a number")
}
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ipString, uint16(portNum)))
if err != nil {
return nil, err
}
return &UDPMulticast{config: config, Addr: addr, ctx: ctx, router: router}, nil
return &UDPMulticast{config: config, Addr: addr, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -79,7 +81,7 @@ func (um *UDPMulticast) Run() error {
select {
case <-um.ctx.Done():
// TODO(jwetzell): cleanup?
slog.Debug("router context done in module", "id", um.Id())
um.logger.Debug("router context done in module")
return nil
default:
um.conn.SetDeadline(time.Now().Add(time.Millisecond * 200))
@@ -99,7 +101,7 @@ func (um *UDPMulticast) Run() error {
if um.router != nil {
um.router.HandleInput(um.Id(), message)
} else {
slog.Error("net.udp.multicast has no router", "id", um.Id())
um.logger.Error("net.udp.multicast has no router")
}
}
}
@@ -110,11 +112,11 @@ func (um *UDPMulticast) Output(payload any) error {
payloadBytes, ok := payload.([]byte)
if !ok {
return fmt.Errorf("net.udp.multicast can only output bytes")
return errors.New("net.udp.multicast can only output bytes")
}
if um.conn == nil {
return fmt.Errorf("net.udp.multicast connection is not setup")
return errors.New("net.udp.multicast connection is not setup")
}
_, err := um.conn.Write(payloadBytes)

View File

@@ -2,6 +2,7 @@ package module
import (
"context"
"errors"
"fmt"
"log"
"log/slog"
@@ -13,10 +14,12 @@ import (
)
type UDPServer struct {
Addr *net.UDPAddr
config config.ModuleConfig
ctx context.Context
router route.RouteIO
Addr *net.UDPAddr
BufferSize int
config config.ModuleConfig
ctx context.Context
router route.RouteIO
logger *slog.Logger
}
func init() {
@@ -26,13 +29,13 @@ func init() {
params := config.Params
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("net.udp.server requires a port parameter")
return nil, errors.New("net.udp.server requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, fmt.Errorf("net.udp.server port must be a number")
return nil, errors.New("net.udp.server port must be a number")
}
ipString := "0.0.0.0"
@@ -43,7 +46,7 @@ func init() {
specificIpString, ok := ip.(string)
if !ok {
return nil, fmt.Errorf("net.udp.server ip must be a string")
return nil, errors.New("net.udp.server ip must be a string")
}
ipString = specificIpString
}
@@ -53,7 +56,19 @@ func init() {
log.Fatalf("error resolving UDP address: %v", err)
}
return &UDPServer{Addr: addr, config: config, ctx: ctx, router: router}, nil
bufferSizeNum := 2048
bufferSize, ok := params["bufferSize"]
if ok {
bufferSizeFloat, ok := bufferSize.(float64)
if !ok {
return nil, errors.New("net.udp.server bufferSize must be a number")
}
bufferSizeNum = int(bufferSizeFloat)
}
return &UDPServer{Addr: addr, BufferSize: bufferSizeNum, config: config, ctx: ctx, router: router, logger: slog.Default().With("component", "module", "id", config.Id)}, nil
},
})
}
@@ -75,13 +90,12 @@ func (us *UDPServer) Run() error {
defer listener.Close()
// TODO(jwetzell): make buffer size configurable
buffer := make([]byte, 65535)
buffer := make([]byte, us.BufferSize)
for {
select {
case <-us.ctx.Done():
// TODO(jwetzell): cleanup?
slog.Debug("router context done in module", "id", us.Id())
us.logger.Debug("router context done in module")
return nil
default:
listener.SetDeadline(time.Now().Add(time.Millisecond * 200))
@@ -98,7 +112,7 @@ func (us *UDPServer) Run() error {
if us.router != nil {
us.router.HandleInput(us.Id(), message)
} else {
slog.Error("net.udp.server has no router", "id", us.Id())
us.logger.Error("net.udp.server has no router")
}
}
}
@@ -106,5 +120,5 @@ func (us *UDPServer) Run() error {
}
func (us *UDPServer) Output(payload any) error {
return fmt.Errorf("net.udp.server output is not implemented")
return errors.New("net.udp.server output is not implemented")
}

View File

@@ -10,10 +10,11 @@ import (
type DebugLog struct {
config config.ProcessorConfig
logger *slog.Logger
}
func (dl *DebugLog) Process(ctx context.Context, payload any) (any, error) {
slog.Debug("debug.log", "payload", payload, "payloadType", fmt.Sprintf("%T", payload))
dl.logger.Debug("debug.log", "payload", payload, "payloadType", fmt.Sprintf("%T", payload))
return payload, nil
}
@@ -25,7 +26,7 @@ func init() {
RegisterProcessor(ProcessorRegistration{
Type: "debug.log",
New: func(config config.ProcessorConfig) (Processor, error) {
return &DebugLog{config: config}, nil
return &DebugLog{config: config, logger: slog.Default().With("component", "processor")}, nil
},
})
}

View File

@@ -2,25 +2,25 @@ package processor
import (
"context"
"fmt"
"errors"
"strconv"
"github.com/jwetzell/showbridge-go/internal/config"
)
type FloatParse struct {
config config.ProcessorConfig
BitSize int
config config.ProcessorConfig
}
func (fp *FloatParse) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
if !ok {
return nil, fmt.Errorf("float.parse processor only accepts a string")
return nil, errors.New("float.parse processor only accepts a string")
}
// TODO(jwetzell): make bitSize configurable
payloadFloat, err := strconv.ParseFloat(payloadString, 64)
payloadFloat, err := strconv.ParseFloat(payloadString, fp.BitSize)
if err != nil {
return nil, err
}
@@ -35,7 +35,19 @@ func init() {
RegisterProcessor(ProcessorRegistration{
Type: "float.parse",
New: func(config config.ProcessorConfig) (Processor, error) {
return &FloatParse{config: config}, nil
params := config.Params
bitSizeNum := 64
bitSize, ok := params["bitSize"]
if ok {
bitSizeFloat, ok := bitSize.(float64)
if !ok {
return nil, errors.New("float.parse bitSize must be a number")
}
bitSizeNum = int(bitSizeFloat)
}
return &FloatParse{config: config, BitSize: bitSizeNum}, nil
},
})
}

View File

@@ -3,7 +3,7 @@ package processor
import (
"bytes"
"context"
"fmt"
"errors"
"strconv"
"text/template"
@@ -190,13 +190,13 @@ func init() {
id, ok := params["id"]
if !ok {
return nil, fmt.Errorf("freed.create requires an id parameter")
return nil, errors.New("freed.create requires an id parameter")
}
idString, ok := id.(string)
if !ok {
return nil, fmt.Errorf("freed.create id must be a string")
return nil, errors.New("freed.create id must be a string")
}
idTemplate, err := template.New("id").Parse(idString)
@@ -208,13 +208,13 @@ func init() {
pan, ok := params["pan"]
if !ok {
return nil, fmt.Errorf("freed.create requires an pan parameter")
return nil, errors.New("freed.create requires a pan parameter")
}
panString, ok := pan.(string)
if !ok {
return nil, fmt.Errorf("freed.create pan must be a string")
return nil, errors.New("freed.create pan must be a string")
}
panTemplate, err := template.New("pan").Parse(panString)
@@ -222,13 +222,13 @@ func init() {
tilt, ok := params["tilt"]
if !ok {
return nil, fmt.Errorf("freed.create requires an tilt parameter")
return nil, errors.New("freed.create requires a tilt parameter")
}
tiltString, ok := tilt.(string)
if !ok {
return nil, fmt.Errorf("freed.create tilt must be a string")
return nil, errors.New("freed.create tilt must be a string")
}
tiltTemplate, err := template.New("tilt").Parse(tiltString)
@@ -236,13 +236,13 @@ func init() {
roll, ok := params["roll"]
if !ok {
return nil, fmt.Errorf("freed.create requires an roll parameter")
return nil, errors.New("freed.create requires a roll parameter")
}
rollString, ok := roll.(string)
if !ok {
return nil, fmt.Errorf("freed.create roll must be a string")
return nil, errors.New("freed.create roll must be a string")
}
rollTemplate, err := template.New("roll").Parse(rollString)
@@ -254,13 +254,13 @@ func init() {
posX, ok := params["posX"]
if !ok {
return nil, fmt.Errorf("freed.create requires a posX parameter")
return nil, errors.New("freed.create requires a posX parameter")
}
posXString, ok := posX.(string)
if !ok {
return nil, fmt.Errorf("freed.create posX must be a string")
return nil, errors.New("freed.create posX must be a string")
}
posXTemplate, err := template.New("posX").Parse(posXString)
@@ -272,13 +272,13 @@ func init() {
posY, ok := params["posY"]
if !ok {
return nil, fmt.Errorf("freed.create requires a posY parameter")
return nil, errors.New("freed.create requires a posY parameter")
}
posYString, ok := posY.(string)
if !ok {
return nil, fmt.Errorf("freed.create posY must be a string")
return nil, errors.New("freed.create posY must be a string")
}
posYTemplate, err := template.New("posY").Parse(posYString)
@@ -290,13 +290,13 @@ func init() {
posZ, ok := params["posZ"]
if !ok {
return nil, fmt.Errorf("freed.create requires a posZ parameter")
return nil, errors.New("freed.create requires a posZ parameter")
}
posZString, ok := posZ.(string)
if !ok {
return nil, fmt.Errorf("freed.create posZ must be a string")
return nil, errors.New("freed.create posZ must be a string")
}
posZTemplate, err := template.New("posZ").Parse(posZString)
@@ -308,13 +308,13 @@ func init() {
zoom, ok := params["zoom"]
if !ok {
return nil, fmt.Errorf("freed.create requires an zoom parameter")
return nil, errors.New("freed.create requires a zoom parameter")
}
zoomString, ok := zoom.(string)
if !ok {
return nil, fmt.Errorf("freed.create zoom must be a string")
return nil, errors.New("freed.create zoom must be a string")
}
zoomTemplate, err := template.New("zoom").Parse(zoomString)
@@ -322,13 +322,13 @@ func init() {
focus, ok := params["focus"]
if !ok {
return nil, fmt.Errorf("freed.create requires an focus parameter")
return nil, errors.New("freed.create requires a focus parameter")
}
focusString, ok := focus.(string)
if !ok {
return nil, fmt.Errorf("freed.create focus must be a string")
return nil, errors.New("freed.create focus must be a string")
}
focusTemplate, err := template.New("focus").Parse(focusString)

View File

@@ -2,8 +2,7 @@ package processor
import (
"context"
"fmt"
"log/slog"
"errors"
freeD "github.com/jwetzell/free-d-go"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -17,12 +16,12 @@ func (fdd *FreeDDecode) Process(ctx context.Context, payload any) (any, error) {
payloadBytes, ok := payload.([]byte)
if !ok {
return nil, fmt.Errorf("freed.decode processor only accepts a []byte")
return nil, errors.New("freed.decode processor only accepts a []byte")
}
payloadMessage, err := freeD.Decode(payloadBytes)
if err != nil {
slog.Error("error decoding", "err", err)
return nil, err
}
return payloadMessage, nil
}

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
freeD "github.com/jwetzell/free-d-go"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -16,7 +16,7 @@ func (fde *FreeDEncode) Process(ctx context.Context, payload any) (any, error) {
payloadPosition, ok := payload.(freeD.FreeDPosition)
if !ok {
return nil, fmt.Errorf("freed.decode processor only accepts a FreeDEncode")
return nil, errors.New("freed.decode processor only accepts a FreeDEncode")
}
payloadBytes := freeD.Encode(payloadPosition)

View File

@@ -3,7 +3,7 @@ package processor
import (
"bytes"
"context"
"fmt"
"errors"
"net/http"
"text/template"
@@ -16,10 +16,10 @@ type HTTPRequestCreate struct {
URL *template.Template
}
func (hre *HTTPRequestCreate) Process(ctx context.Context, payload any) (any, error) {
func (hrc *HTTPRequestCreate) Process(ctx context.Context, payload any) (any, error) {
var urlBuffer bytes.Buffer
err := hre.URL.Execute(&urlBuffer, payload)
err := hrc.URL.Execute(&urlBuffer, payload)
if err != nil {
return nil, err
@@ -28,7 +28,7 @@ func (hre *HTTPRequestCreate) Process(ctx context.Context, payload any) (any, er
urlString := urlBuffer.String()
//TODO(jwetzell): support body
request, err := http.NewRequest(hre.Method, urlString, bytes.NewBuffer([]byte{}))
request, err := http.NewRequest(hrc.Method, urlString, bytes.NewBuffer([]byte{}))
if err != nil {
return nil, err
@@ -37,8 +37,8 @@ func (hre *HTTPRequestCreate) Process(ctx context.Context, payload any) (any, er
return request, nil
}
func (hre *HTTPRequestCreate) Type() string {
return hre.config.Type
func (hrc *HTTPRequestCreate) Type() string {
return hrc.config.Type
}
func init() {
@@ -50,25 +50,25 @@ func init() {
method, ok := params["method"]
if !ok {
return nil, fmt.Errorf("http.request.create requires an method parameter")
return nil, errors.New("http.request.create requires a method parameter")
}
methodString, ok := method.(string)
if !ok {
return nil, fmt.Errorf("http.request.create url must be a string")
return nil, errors.New("http.request.create url must be a string")
}
url, ok := params["url"]
if !ok {
return nil, fmt.Errorf("http.request.create requires an url parameter")
return nil, errors.New("http.request.create requires a url parameter")
}
urlString, ok := url.(string)
if !ok {
return nil, fmt.Errorf("http.request.create url must be a string")
return nil, errors.New("http.request.create url must be a string")
}
urlTemplate, err := template.New("url").Parse(urlString)

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
"io"
"net/http"
@@ -17,7 +17,7 @@ func (hre *HTTPRequestEncode) Process(ctx context.Context, payload any) (any, er
payloadRequest, ok := payload.(*http.Request)
if !ok {
return nil, fmt.Errorf("http.request.encode processor only accepts an http.Request")
return nil, errors.New("http.request.encode processor only accepts an http.Request")
}
bytes, err := io.ReadAll(payloadRequest.Body)

View File

@@ -2,6 +2,7 @@ package processor
import (
"context"
"errors"
"fmt"
"net/http"
"regexp"
@@ -20,7 +21,7 @@ func (hrf *HTTPRequestFilter) Process(ctx context.Context, payload any) (any, er
payloadRequest, ok := payload.(*http.Request)
if !ok {
return nil, fmt.Errorf("http.request.filter can only operate on http.Request payloads")
return nil, errors.New("http.request.filter can only operate on http.Request payloads")
}
if hrf.Method != "" {
@@ -48,13 +49,13 @@ func init() {
path, ok := params["path"]
if !ok {
return nil, fmt.Errorf("http.request.filter requires an path parameter")
return nil, errors.New("http.request.filter requires a path parameter")
}
pathString, ok := path.(string)
if !ok {
return nil, fmt.Errorf("http.request.filter path must be a string")
return nil, errors.New("http.request.filter path must be a string")
}
pathRegexp, err := regexp.Compile(fmt.Sprintf("^%s$", pathString))
@@ -69,7 +70,7 @@ func init() {
methodString, ok := method.(string)
if !ok {
return nil, fmt.Errorf("http.request.filter method must be a string")
return nil, errors.New("http.request.filter method must be a string")
}
return &HTTPRequestFilter{config: config, Path: pathRegexp, Method: methodString}, nil
}

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
"io"
"net/http"
@@ -17,7 +17,7 @@ func (hre *HTTPResponseEncode) Process(ctx context.Context, payload any) (any, e
payloadResponse, ok := payload.(*http.Response)
if !ok {
return nil, fmt.Errorf("http.response.encode processor only accepts an http.Response")
return nil, errors.New("http.response.encode processor only accepts an http.Response")
}
defer payloadResponse.Body.Close()

View File

@@ -2,25 +2,26 @@ package processor
import (
"context"
"fmt"
"errors"
"strconv"
"github.com/jwetzell/showbridge-go/internal/config"
)
type IntParse struct {
config config.ProcessorConfig
Base int
BitSize int
config config.ProcessorConfig
}
func (ip *IntParse) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
if !ok {
return nil, fmt.Errorf("int.parse processor only accepts a string")
return nil, errors.New("int.parse processor only accepts a string")
}
// TODO(jwetzell): make base and bitSize configurable
payloadInt, err := strconv.ParseInt(payloadString, 10, 64)
payloadInt, err := strconv.ParseInt(payloadString, ip.Base, ip.BitSize)
if err != nil {
return nil, err
}
@@ -35,7 +36,32 @@ func init() {
RegisterProcessor(ProcessorRegistration{
Type: "int.parse",
New: func(config config.ProcessorConfig) (Processor, error) {
return &IntParse{config: config}, nil
params := config.Params
baseNum := 10
base, ok := params["base"]
if ok {
baseFloat, ok := base.(float64)
if !ok {
return nil, errors.New("int.parse base must be a number")
}
baseNum = int(baseFloat)
}
bitSizeNum := 64
bitSize, ok := params["bitSize"]
if ok {
bitSizeFloat, ok := bitSize.(float64)
if !ok {
return nil, errors.New("int.parse bitSize must be a number")
}
bitSizeNum = int(bitSizeFloat)
}
return &IntParse{config: config, Base: baseNum, BitSize: bitSizeNum}, nil
},
})
}

View File

@@ -5,6 +5,7 @@ package processor
import (
"bytes"
"context"
"errors"
"fmt"
"strconv"
"text/template"
@@ -13,6 +14,7 @@ import (
"gitlab.com/gomidi/midi/v2"
)
// TODO(jwetzell): support using numbers in config file treated as hardcoded values
type MIDIMessageCreate struct {
config config.ProcessorConfig
ProcessFunc func(ctx context.Context, payload any) (any, error)
@@ -33,13 +35,13 @@ func newMidiNoteOnCreate(config config.ProcessorConfig) (Processor, error) {
channel, ok := params["channel"]
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn requires a channel parameter")
return nil, errors.New("midi.message.create NoteOn requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn channel must be a string")
return nil, errors.New("midi.message.create NoteOn channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
@@ -51,13 +53,13 @@ func newMidiNoteOnCreate(config config.ProcessorConfig) (Processor, error) {
note, ok := params["note"]
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn requires a note parameter")
return nil, errors.New("midi.message.create NoteOn requires a note parameter")
}
noteString, ok := note.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn note must be a string")
return nil, errors.New("midi.message.create NoteOn note must be a string")
}
noteTemplate, err := template.New("note").Parse(noteString)
@@ -69,13 +71,13 @@ func newMidiNoteOnCreate(config config.ProcessorConfig) (Processor, error) {
velocity, ok := params["velocity"]
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn requires a velocity parameter")
return nil, errors.New("midi.message.create NoteOn requires a velocity parameter")
}
velocityString, ok := velocity.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn velocity must be a string")
return nil, errors.New("midi.message.create NoteOn velocity must be a string")
}
velocityTemplate, err := template.New("velocity").Parse(velocityString)
@@ -124,13 +126,13 @@ func newMidiNoteOffCreate(config config.ProcessorConfig) (Processor, error) {
channel, ok := params["channel"]
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn requires a channel parameter")
return nil, errors.New("midi.message.create NoteOn requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn channel must be a string")
return nil, errors.New("midi.message.create NoteOn channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
@@ -142,13 +144,13 @@ func newMidiNoteOffCreate(config config.ProcessorConfig) (Processor, error) {
note, ok := params["note"]
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn requires a note parameter")
return nil, errors.New("midi.message.create NoteOn requires a note parameter")
}
noteString, ok := note.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn note must be a string")
return nil, errors.New("midi.message.create NoteOn note must be a string")
}
noteTemplate, err := template.New("note").Parse(noteString)
@@ -189,13 +191,13 @@ func newMidiControlChangeCreate(config config.ProcessorConfig) (Processor, error
channel, ok := params["channel"]
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange requires a channel parameter")
return nil, errors.New("midi.message.create ControlChange requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange channel must be a string")
return nil, errors.New("midi.message.create ControlChange channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
@@ -207,13 +209,13 @@ func newMidiControlChangeCreate(config config.ProcessorConfig) (Processor, error
controller, ok := params["controller"]
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange requires a controller parameter")
return nil, errors.New("midi.message.create ControlChange requires a controller parameter")
}
controllerString, ok := controller.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange controller must be a string")
return nil, errors.New("midi.message.create ControlChange controller must be a string")
}
controllerTemplate, err := template.New("controller").Parse(controllerString)
@@ -225,13 +227,13 @@ func newMidiControlChangeCreate(config config.ProcessorConfig) (Processor, error
value, ok := params["value"]
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange requires a value parameter")
return nil, errors.New("midi.message.create ControlChange requires a value parameter")
}
valueString, ok := value.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange value must be a string")
return nil, errors.New("midi.message.create ControlChange value must be a string")
}
valueTemplate, err := template.New("value").Parse(valueString)
@@ -281,13 +283,13 @@ func newMidiProgramChangeCreate(config config.ProcessorConfig) (Processor, error
channel, ok := params["channel"]
if !ok {
return nil, fmt.Errorf("midi.message.create ProgramChange requires a channel parameter")
return nil, errors.New("midi.message.create ProgramChange requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create ProgramChange channel must be a string")
return nil, errors.New("midi.message.create ProgramChange channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
@@ -299,13 +301,13 @@ func newMidiProgramChangeCreate(config config.ProcessorConfig) (Processor, error
program, ok := params["program"]
if !ok {
return nil, fmt.Errorf("midi.message.create ProgramChange requires a program parameter")
return nil, errors.New("midi.message.create ProgramChange requires a program parameter")
}
programString, ok := program.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create ProgramChange program must be a string")
return nil, errors.New("midi.message.create ProgramChange program must be a string")
}
programTemplate, err := template.New("program").Parse(programString)
@@ -348,13 +350,13 @@ func init() {
msgType, ok := params["type"]
if !ok {
return nil, fmt.Errorf("midi.message.create requires a type parameter")
return nil, errors.New("midi.message.create requires a type parameter")
}
msgTypeString, ok := msgType.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create type parameter must be a string")
return nil, errors.New("midi.message.create type parameter must be a string")
}
switch msgTypeString {

View File

@@ -4,7 +4,7 @@ package processor
import (
"context"
"fmt"
"errors"
"github.com/jwetzell/showbridge-go/internal/config"
"gitlab.com/gomidi/midi/v2"
@@ -18,7 +18,7 @@ func (mmd *MIDIMessageDecode) Process(ctx context.Context, payload any) (any, er
payloadBytes, ok := payload.([]byte)
if !ok {
return nil, fmt.Errorf("midi.message.decode processor only accepts a []byte")
return nil, errors.New("midi.message.decode processor only accepts a []byte")
}
payloadMessage := midi.Message(payloadBytes)

View File

@@ -4,7 +4,7 @@ package processor
import (
"context"
"fmt"
"errors"
"github.com/jwetzell/showbridge-go/internal/config"
"gitlab.com/gomidi/midi/v2"
@@ -18,7 +18,7 @@ func (mme *MIDIMessageEncode) Process(ctx context.Context, payload any) (any, er
payloadMessage, ok := payload.(midi.Message)
if !ok {
return nil, fmt.Errorf("midi.message.encode processor only accepts an midi.Message")
return nil, errors.New("midi.message.encode processor only accepts an midi.Message")
}
return payloadMessage.Bytes(), nil

View File

@@ -4,7 +4,7 @@ package processor
import (
"context"
"fmt"
"errors"
"github.com/jwetzell/showbridge-go/internal/config"
"gitlab.com/gomidi/midi/v2"
@@ -19,7 +19,7 @@ func (mmf *MIDIMessageFilter) Process(ctx context.Context, payload any) (any, er
payloadMessage, ok := payload.(midi.Message)
if !ok {
return nil, fmt.Errorf("midi.message.filter processor only accepts an midi.Message")
return nil, errors.New("midi.message.filter processor only accepts an midi.Message")
}
if payloadMessage.Type().String() != mmf.MIDIType {
@@ -41,12 +41,12 @@ func init() {
midiType, ok := params["type"]
if !ok {
return nil, fmt.Errorf("midi.message.filter requires a type parameter")
return nil, errors.New("midi.message.filter requires a type parameter")
}
midiTypeString, ok := midiType.(string)
if !ok {
return nil, fmt.Errorf("midi.message.filter type must be a string")
return nil, errors.New("midi.message.filter type must be a string")
}
return &MIDIMessageFilter{config: config, MIDIType: midiTypeString}, nil

View File

@@ -4,6 +4,7 @@ package processor
import (
"context"
"errors"
"fmt"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -47,7 +48,7 @@ func (mmu *MIDIMessageUnpack) Process(ctx context.Context, payload any) (any, er
payloadMidi, ok := payload.(midi.Message)
if !ok {
return nil, fmt.Errorf("midi.message.unpack processor only accepts a midi.Message")
return nil, errors.New("midi.message.unpack processor only accepts a midi.Message")
}
switch payloadMidi.Type() {

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
"github.com/jwetzell/showbridge-go/internal/config"
)
@@ -74,44 +74,44 @@ func init() {
topic, ok := params["topic"]
if !ok {
return nil, fmt.Errorf("mqtt.message.create requires an topic parameter")
return nil, errors.New("mqtt.message.create requires a topic parameter")
}
topicString, ok := topic.(string)
if !ok {
return nil, fmt.Errorf("mqtt.message.create topic must be a string")
return nil, errors.New("mqtt.message.create topic must be a string")
}
qos, ok := params["qos"]
if !ok {
return nil, fmt.Errorf("mqtt.message.create requires an qos parameter")
return nil, errors.New("mqtt.message.create requires a qos parameter")
}
qosByte, ok := qos.(float64)
if !ok {
return nil, fmt.Errorf("mqtt.message.create qos must be a number")
return nil, errors.New("mqtt.message.create qos must be a number")
}
retained, ok := params["retained"]
if !ok {
return nil, fmt.Errorf("mqtt.message.create requires an retained parameter")
return nil, errors.New("mqtt.message.create requires a retained parameter")
}
retainedBool, ok := retained.(bool)
if !ok {
return nil, fmt.Errorf("mqtt.message.create retained must be a boolean")
return nil, errors.New("mqtt.message.create retained must be a boolean")
}
//TODO(jwetzell): convert payload into []byte or string for sending
payload, ok := params["payload"]
if !ok {
return nil, fmt.Errorf("mqtt.message.create requires an payload parameter")
return nil, errors.New("mqtt.message.create requires a payload parameter")
}
if payloadBytes, ok := payload.([]byte); ok {
@@ -121,7 +121,7 @@ func init() {
payloadString, ok := payload.(string)
if !ok {
return nil, fmt.Errorf("mqtt.message.create payload must be a string or byte array")
return nil, errors.New("mqtt.message.create payload must be a string or byte array")
}
payloadBytes := []byte(payloadString)

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -16,7 +16,7 @@ func (mme *MQTTMessageEncode) Process(ctx context.Context, payload any) (any, er
payloadMessage, ok := payload.(mqtt.Message)
if !ok {
return nil, fmt.Errorf("mqtt.message.encode processor only accepts an mqtt.Message")
return nil, errors.New("mqtt.message.encode processor only accepts an mqtt.Message")
}
return payloadMessage.Payload(), nil

View File

@@ -3,7 +3,7 @@ package processor
import (
"bytes"
"context"
"fmt"
"errors"
"text/template"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -52,25 +52,25 @@ func init() {
subject, ok := params["subject"]
if !ok {
return nil, fmt.Errorf("nats.message.create requires a subject parameter")
return nil, errors.New("nats.message.create requires a subject parameter")
}
subjectString, ok := subject.(string)
if !ok {
return nil, fmt.Errorf("nats.message.create subject must be a string")
return nil, errors.New("nats.message.create subject must be a string")
}
payload, ok := params["payload"]
if !ok {
return nil, fmt.Errorf("osc.message.create requires a payload parameter")
return nil, errors.New("nats.message.create requires a payload parameter")
}
payloadString, ok := payload.(string)
if !ok {
return nil, fmt.Errorf("osc.message.create payload must be a string")
return nil, errors.New("nats.message.create payload must be a string")
}
payloadTemplate, err := template.New("payload").Parse(payloadString)

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/nats-io/nats.go"
@@ -16,7 +16,7 @@ func (nme *NATSMessageEncode) Process(ctx context.Context, payload any) (any, er
payloadMessage, ok := payload.(*nats.Msg)
if !ok {
return nil, fmt.Errorf("nats.message.encode processor only accepts an nats.Msg")
return nil, errors.New("nats.message.encode processor only accepts an nats.Msg")
}
return payloadMessage.Data, nil

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"strconv"
"text/template"
@@ -31,11 +32,11 @@ func (o *OSCMessageCreate) Process(ctx context.Context, payload any) (any, error
addressString := addressBuffer.String()
if len(addressString) == 0 {
return nil, fmt.Errorf("osc.message.create address must not be empty")
return nil, errors.New("osc.message.create address must not be empty")
}
if addressString[0] != '/' {
return nil, fmt.Errorf("osc.message.create address must start with '/'")
return nil, errors.New("osc.message.create address must start with '/'")
}
payloadMessage := osc.OSCMessage{
@@ -82,13 +83,13 @@ func init() {
address, ok := params["address"]
if !ok {
return nil, fmt.Errorf("osc.message.create requires an address parameter")
return nil, errors.New("osc.message.create requires an address parameter")
}
addressString, ok := address.(string)
if !ok {
return nil, fmt.Errorf("osc.message.create address must be a string")
return nil, errors.New("osc.message.create address must be a string")
}
addressTemplate, err := template.New("address").Parse(addressString)
@@ -109,17 +110,17 @@ func init() {
types, ok := params["types"]
if !ok {
return nil, fmt.Errorf("osc.message.create requires a types parameter with args")
return nil, errors.New("osc.message.create requires a types parameter with args")
}
typesString, ok := types.(string)
if !ok {
return nil, fmt.Errorf("osc.message.create types must be a string")
return nil, errors.New("osc.message.create types must be a string")
}
if len(rawArgs) != len(typesString) {
return nil, fmt.Errorf("osc.message.create args and types must be the same length")
return nil, errors.New("osc.message.create args and types must be the same length")
}
argTemplates := []*template.Template{}
@@ -128,7 +129,7 @@ func init() {
argString, ok := rawArg.(string)
if !ok {
return nil, fmt.Errorf("osc.message.create arg must be a string")
return nil, errors.New("osc.message.create arg must be a string")
}
argTemplate, err := template.New("arg").Parse(argString)

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
osc "github.com/jwetzell/osc-go"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -16,15 +16,15 @@ func (o *OSCMessageDecode) Process(ctx context.Context, payload any) (any, error
payloadBytes, ok := payload.([]byte)
if !ok {
return nil, fmt.Errorf("osc.message.decode processor only accepts a []byte payload")
return nil, errors.New("osc.message.decode processor only accepts a []byte payload")
}
if len(payloadBytes) == 0 {
return nil, fmt.Errorf("osc.message.decode processor can't work on empty []byte")
return nil, errors.New("osc.message.decode processor can't work on empty []byte")
}
if payloadBytes[0] != '/' {
return nil, fmt.Errorf("osc.message.decode processor needs an OSC looking []byte")
return nil, errors.New("osc.message.decode processor needs an OSC looking []byte")
}
message, err := osc.MessageFromBytes(payloadBytes)

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
osc "github.com/jwetzell/osc-go"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -16,7 +16,7 @@ func (o *OSCMessageEncode) Process(ctx context.Context, payload any) (any, error
payloadMessage, ok := payload.(osc.OSCMessage)
if !ok {
return nil, fmt.Errorf("osc.message.encode processor only accepts an OSCMessage")
return nil, errors.New("osc.message.encode processor only accepts an OSCMessage")
}
bytes := payloadMessage.ToBytes()

View File

@@ -2,6 +2,7 @@ package processor
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
@@ -20,7 +21,7 @@ func (o *OSCMessageFilter) Process(ctx context.Context, payload any) (any, error
payloadMessage, ok := payload.(osc.OSCMessage)
if !ok {
return nil, fmt.Errorf("osc.message.filter can only operate on OSCMessage payloads")
return nil, errors.New("osc.message.filter can only operate on OSCMessage payloads")
}
if !o.Address.MatchString(payloadMessage.Address) {
@@ -42,13 +43,13 @@ func init() {
address, ok := params["address"]
if !ok {
return nil, fmt.Errorf("osc.message.filter requires an address parameter")
return nil, errors.New("osc.message.filter requires an address parameter")
}
addressString, ok := address.(string)
if !ok {
return nil, fmt.Errorf("osc.message.filter address must be a string")
return nil, errors.New("osc.message.filter address must be a string")
}
addressPattern := strings.ReplaceAll(addressString, "?", ".")

View File

@@ -3,7 +3,7 @@ package processor
import (
"bytes"
"context"
"fmt"
"errors"
"text/template"
osc "github.com/jwetzell/osc-go"
@@ -19,11 +19,10 @@ func (o *OSCMessageTransform) Process(ctx context.Context, payload any) (any, er
payloadMessage, ok := payload.(osc.OSCMessage)
if !ok {
return nil, fmt.Errorf("osc.message.transform processor only accepts an OSCMessage")
return nil, errors.New("osc.message.transform processor only accepts an OSCMessage")
}
var addressBuffer bytes.Buffer
//TODO(jwetzell): actually inject data into template
err := o.Address.Execute(&addressBuffer, payloadMessage)
if err != nil {
@@ -33,11 +32,11 @@ func (o *OSCMessageTransform) Process(ctx context.Context, payload any) (any, er
addressString := addressBuffer.String()
if len(addressString) == 0 {
return nil, fmt.Errorf("osc.message.transform address must not be empty")
return nil, errors.New("osc.message.transform address must not be empty")
}
if addressString[0] != '/' {
return nil, fmt.Errorf("osc.message.transform address must start with '/'")
return nil, errors.New("osc.message.transform address must start with '/'")
}
payloadMessage.Address = addressString
@@ -57,13 +56,13 @@ func init() {
address, ok := params["address"]
if !ok {
return nil, fmt.Errorf("osc.message.transform requires an address parameter")
return nil, errors.New("osc.message.transform requires an address parameter")
}
addressString, ok := address.(string)
if !ok {
return nil, fmt.Errorf("osc.message.transform address must be a string")
return nil, errors.New("osc.message.transform address must be a string")
}
addressTemplate, err := template.New("address").Parse(addressString)

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
"github.com/expr-lang/expr"
"github.com/expr-lang/expr/vm"
@@ -38,13 +38,13 @@ func init() {
expression, ok := params["expression"]
if !ok {
return nil, fmt.Errorf("script.expr requires an expression parameter")
return nil, errors.New("script.expr requires an expression parameter")
}
expressionString, ok := expression.(string)
if !ok {
return nil, fmt.Errorf("script.expr expression must be a string")
return nil, errors.New("script.expr expression must be a string")
}
program, err := expr.Compile(expressionString)

View File

@@ -0,0 +1,58 @@
package processor_test
import (
"testing"
"github.com/expr-lang/expr"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestGoodScriptExpr(t *testing.T) {
tests := []struct {
program string
name string
payload map[string]any
expected any
}{
{
program: "foo + bar",
name: "number",
payload: map[string]any{
"foo": 1,
"bar": 1,
},
expected: 2,
},
{
program: "foo + bar",
name: "string",
payload: map[string]any{
"foo": "1",
"bar": "1",
},
expected: "11",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
program, err := expr.Compile(test.program)
if err != nil {
t.Errorf("script.expr failed to compile program: %s", err)
}
exprProcessor := &processor.ScriptExpr{Program: program}
got, err := exprProcessor.Process(t.Context(), test.payload)
if err != nil {
t.Errorf("script.expr failed: %s", err)
}
//TODO(jwetzell): work out better way to compare the any/any
if got != test.expected {
t.Errorf("script.expr got %+v (%T), expected %+v (%T)", got, got, test.expected, test.expected)
}
})
}
}

View File

@@ -3,7 +3,7 @@ package processor
import (
"context"
"encoding/json"
"fmt"
"errors"
"github.com/jwetzell/showbridge-go/internal/config"
"modernc.org/quickjs"
@@ -74,13 +74,13 @@ func init() {
program, ok := params["program"]
if !ok {
return nil, fmt.Errorf("script.js requires a program parameter")
return nil, errors.New("script.js requires a program parameter")
}
programString, ok := program.(string)
if !ok {
return nil, fmt.Errorf("script.js program must be a string")
return nil, errors.New("script.js program must be a string")
}
return &ScriptJS{config: config, Program: programString}, nil

View File

@@ -0,0 +1,48 @@
package processor_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestGoodScriptJS(t *testing.T) {
tests := []struct {
processor processor.Processor
name string
payload any
expected any
}{
{
processor: &processor.ScriptJS{Program: `
payload = payload + 1
`},
name: "number",
payload: 1,
expected: 2,
},
{
processor: &processor.ScriptJS{Program: `
payload = payload + "1"
`},
name: "string",
payload: "1",
expected: "11",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := test.processor.Process(t.Context(), test.payload)
if err != nil {
t.Errorf("script.js failed: %s", err)
}
//TODO(jwetzell): work out better way to compare the any/any
if got != test.expected {
t.Errorf("script.js got %+v, expected %+v", got, test.expected)
}
})
}
}

View File

@@ -3,7 +3,7 @@ package processor
import (
"bytes"
"context"
"fmt"
"errors"
"text/template"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -39,13 +39,13 @@ func init() {
tmpl, ok := params["template"]
if !ok {
return nil, fmt.Errorf("string.create requires a template parameter")
return nil, errors.New("string.create requires a template parameter")
}
templateString, ok := tmpl.(string)
if !ok {
return nil, fmt.Errorf("string.create template must be a string")
return nil, errors.New("string.create template must be a string")
}
templateTemplate, err := template.New("template").Parse(templateString)

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
"github.com/jwetzell/showbridge-go/internal/config"
)
@@ -15,7 +15,7 @@ func (sd *StringDecode) Process(ctx context.Context, payload any) (any, error) {
payloadBytes, ok := payload.([]byte)
if !ok {
return nil, fmt.Errorf("string.decode processor only accepts a []byte")
return nil, errors.New("string.decode processor only accepts a []byte")
}
payloadMessage := string(payloadBytes)

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
"github.com/jwetzell/showbridge-go/internal/config"
)
@@ -15,7 +15,7 @@ func (se *StringEncode) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
if !ok {
return nil, fmt.Errorf("string.encode processor only accepts a string")
return nil, errors.New("string.encode processor only accepts a string")
}
payloadBytes := []byte(payloadString)

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
"regexp"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -17,7 +17,7 @@ func (se *StringFilter) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
if !ok {
return nil, fmt.Errorf("string.filter processor only accepts a string")
return nil, errors.New("string.filter processor only accepts a string")
}
if !se.Pattern.MatchString(payloadString) {
@@ -40,16 +40,16 @@ func init() {
pattern, ok := params["pattern"]
if !ok {
return nil, fmt.Errorf("http.request.filter requires an pattern parameter")
return nil, errors.New("string.filter requires a pattern parameter")
}
patternString, ok := pattern.(string)
if !ok {
return nil, fmt.Errorf("http.request.filter pattern must be a string")
return nil, errors.New("string.filter pattern must be a string")
}
patternRegexp, err := regexp.Compile(fmt.Sprintf("^%s$", patternString))
patternRegexp, err := regexp.Compile(patternString)
if err != nil {
return nil, err

View File

@@ -2,7 +2,7 @@ package processor
import (
"context"
"fmt"
"errors"
"strings"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -17,7 +17,7 @@ func (se *StringSplit) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
if !ok {
return nil, fmt.Errorf("string.split only accepts a string")
return nil, errors.New("string.split only accepts a string")
}
payloadParts := strings.Split(payloadString, se.Separator)
@@ -38,13 +38,13 @@ func init() {
separator, ok := params["separator"]
if !ok {
return nil, fmt.Errorf("string.split requires a separator")
return nil, errors.New("string.split requires a separator")
}
separatorString, ok := separator.(string)
if !ok {
return nil, fmt.Errorf("string.split separator must be a string")
return nil, errors.New("string.split separator must be a string")
}
return &StringSplit{config: config, Separator: separatorString}, nil

View File

@@ -40,7 +40,7 @@ func TestGoodStringSplit(t *testing.T) {
}
}
func TestBasStringSplit(t *testing.T) {
func TestBadStringSplit(t *testing.T) {
tests := []struct {
processor processor.Processor
name string

View File

@@ -2,25 +2,26 @@ package processor
import (
"context"
"fmt"
"errors"
"strconv"
"github.com/jwetzell/showbridge-go/internal/config"
)
type UintParse struct {
config config.ProcessorConfig
Base int
BitSize int
config config.ProcessorConfig
}
func (up *UintParse) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
if !ok {
return nil, fmt.Errorf("uint.parse processor only accepts a string")
return nil, errors.New("uint.parse processor only accepts a string")
}
// TODO(jwetzell): make base and bitSize configurable
payloadUint, err := strconv.ParseUint(payloadString, 10, 64)
payloadUint, err := strconv.ParseUint(payloadString, up.Base, up.BitSize)
if err != nil {
return nil, err
}
@@ -35,7 +36,31 @@ func init() {
RegisterProcessor(ProcessorRegistration{
Type: "uint.parse",
New: func(config config.ProcessorConfig) (Processor, error) {
return &UintParse{config: config}, nil
params := config.Params
baseNum := 10
base, ok := params["base"]
if ok {
baseFloat, ok := base.(float64)
if !ok {
return nil, errors.New("uint.parse base must be a number")
}
baseNum = int(baseFloat)
}
bitSizeNum := 64
bitSize, ok := params["bitSize"]
if ok {
bitSizeFloat, ok := bitSize.(float64)
if !ok {
return nil, errors.New("uint.parse bitSize must be a number")
}
bitSizeNum = int(bitSizeFloat)
}
return &UintParse{config: config, Base: baseNum, BitSize: bitSizeNum}, nil
},
})
}

View File

@@ -2,9 +2,9 @@ package showbridge
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"sync"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -18,16 +18,10 @@ type Router struct {
ModuleInstances []module.Module
RouteInstances []route.Route
moduleWait sync.WaitGroup
logger *slog.Logger
}
func NewRouter(ctx context.Context, config config.Config) (*Router, []module.ModuleError, []route.RouteError) {
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
slog.Debug("creating router")
routerContext, cancel := context.WithCancel(ctx)
router := Router{
@@ -35,8 +29,11 @@ func NewRouter(ctx context.Context, config config.Config) (*Router, []module.Mod
contextCancel: cancel,
ModuleInstances: []module.Module{},
RouteInstances: []route.Route{},
logger: slog.Default().With("component", "router"),
}
router.logger.Debug("creating router")
var moduleErrors []module.ModuleError
for moduleIndex, moduleDecl := range config.Modules {
@@ -49,7 +46,7 @@ func NewRouter(ctx context.Context, config config.Config) (*Router, []module.Mod
moduleErrors = append(moduleErrors, module.ModuleError{
Index: moduleIndex,
Config: moduleDecl,
Error: fmt.Errorf("module type not defined"),
Error: errors.New("module type not defined"),
})
continue
}
@@ -64,7 +61,7 @@ func NewRouter(ctx context.Context, config config.Config) (*Router, []module.Mod
moduleErrors = append(moduleErrors, module.ModuleError{
Index: moduleIndex,
Config: moduleDecl,
Error: fmt.Errorf("duplicate module id"),
Error: errors.New("duplicate module id"),
})
break
}
@@ -110,20 +107,20 @@ func NewRouter(ctx context.Context, config config.Config) (*Router, []module.Mod
}
func (r *Router) Run() {
slog.Info("running router")
r.logger.Info("running router")
for _, moduleInstance := range r.ModuleInstances {
r.moduleWait.Add(1)
go func() {
err := moduleInstance.Run()
if err != nil {
slog.Error("error encountered running module", "id", moduleInstance.Id(), "error", err)
r.logger.Error("error encountered running module", "error", err)
}
r.moduleWait.Done()
}()
}
<-r.Context.Done()
r.moduleWait.Wait()
slog.Info("router done")
r.logger.Info("router done")
}
func (r *Router) Stop() {
@@ -143,7 +140,7 @@ func (r *Router) HandleInput(sourceId string, payload any) []route.RouteIOError
Index: routeIndex,
Error: err,
})
slog.Error("router unable to route input", "route", routeIndex, "source", sourceId, "error", err)
r.logger.Error("router unable to route input", "route", routeIndex, "source", sourceId, "error", err)
}
}
}