47 Commits

Author SHA1 Message Date
Joel Wetzell
68a943e141 add option to save recording of call for sip.call.server 2026-03-04 12:46:25 -06:00
Joel Wetzell
dff33f722d Merge pull request #81 from jwetzell/feat/richer-templating-data
use a struct to pass multiple pieces of data into templating context
2026-03-04 12:40:37 -06:00
Joel Wetzell
572a54d3b2 use a struct to pass multiple pieces of data into templating context 2026-03-04 12:38:51 -06:00
Joel Wetzell
6a21cc2639 make method to get data out of a module 2026-03-04 12:35:15 -06:00
Joel Wetzell
0d4ec24a9b move context keys to common package 2026-03-04 12:33:35 -06:00
Joel Wetzell
10c6e84682 fix template usage in freed.create 2026-03-04 09:14:09 -06:00
Joel Wetzell
7ac0ec7582 remove uint parse and random 2026-03-03 21:42:08 -06:00
Joel Wetzell
c25a36f09c Merge pull request #80 from jwetzell/feat/random-float
add processor to generator random float
2026-03-03 21:26:28 -06:00
Joel Wetzell
8d2c022baf fix variable name 2026-03-03 21:21:47 -06:00
Joel Wetzell
76318b4e10 add processor to generator random float 2026-03-03 21:20:36 -06:00
Joel Wetzell
633eaba0ec fix osc.message.create tests 2026-03-03 07:00:05 -06:00
Joel Wetzell
00b6f3660d fix osc.message.create 2026-03-03 06:58:46 -06:00
Joel Wetzell
a275cd2b78 upgrade osc library and switch to pointers 2026-03-02 21:17:49 -06:00
Joel Wetzell
5cb2f845a1 add more error case tests to osc.message.decode 2026-03-02 21:04:18 -06:00
Joel Wetzell
8f769f0a4a add OSC encode/decode test with arg 2026-03-02 20:59:29 -06:00
Joel Wetzell
fdd4b341cb fix osc message decode test 2026-03-02 20:56:16 -06:00
Joel Wetzell
9a9bf10078 add tests for osc message encode/decode 2026-03-02 20:54:03 -06:00
Joel Wetzell
ea46885f70 rework GetInt and GetIntSlice and more testing for params config 2026-03-02 20:53:53 -06:00
Joel Wetzell
fa8f0b7cfc add test for getting array and void from struct 2026-03-02 20:53:20 -06:00
Joel Wetzell
d316147411 align test err checking and logs 2026-03-02 20:53:00 -06:00
Joel Wetzell
30564523d7 Merge pull request #78 from jwetzell/dependabot/go_modules/github.com/urfave/cli/v3-3.7.0
Bump github.com/urfave/cli/v3 from 3.6.2 to 3.7.0
2026-03-02 14:07:05 -06:00
dependabot[bot]
f7231e54c2 Bump github.com/urfave/cli/v3 from 3.6.2 to 3.7.0
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.6.2 to 3.7.0.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v3.6.2...v3.7.0)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v3
  dependency-version: 3.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 20:05:27 +00:00
Joel Wetzell
14de829e7a Merge pull request #75 from jwetzell/dependabot/go_modules/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp-1.41.0
Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp from 1.40.0 to 1.41.0
2026-03-02 14:04:27 -06:00
Joel Wetzell
33a54eb24c Merge pull request #76 from jwetzell/dependabot/go_modules/gitlab.com/gomidi/midi/v2-2.3.23
Bump gitlab.com/gomidi/midi/v2 from 2.3.22 to 2.3.23
2026-03-02 14:04:12 -06:00
Joel Wetzell
3c6c50c914 add tests for midi and mqtt message create processors 2026-03-02 14:01:47 -06:00
Joel Wetzell
da516f5102 fix test wording 2026-03-02 14:01:32 -06:00
Joel Wetzell
1f88819428 use params methods iin mqtt.message.create 2026-03-02 14:01:20 -06:00
Joel Wetzell
df4f0f745d fix GetByteSlice for params 2026-03-02 14:00:47 -06:00
dependabot[bot]
d86bff30cd Bump gitlab.com/gomidi/midi/v2 from 2.3.22 to 2.3.23
Bumps [gitlab.com/gomidi/midi/v2](https://gitlab.com/gomidi/midi) from 2.3.22 to 2.3.23.
- [Commits](https://gitlab.com/gomidi/midi/compare/v2.3.22...v2.3.23)

---
updated-dependencies:
- dependency-name: gitlab.com/gomidi/midi/v2
  dependency-version: 2.3.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 19:21:14 +00:00
dependabot[bot]
22d8bbf88d Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go) from 1.40.0 to 1.41.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.40.0...v1.41.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
  dependency-version: 1.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 19:21:05 +00:00
Joel Wetzell
925873a124 add tests for midi.message.decode 2026-03-02 12:55:33 -06:00
Joel Wetzell
08308c1891 add count flag to hopefully avoid cache issues 2026-03-02 12:47:01 -06:00
Joel Wetzell
f68312ee84 standardize layout of bad tests 2026-03-02 12:34:46 -06:00
Joel Wetzell
cd1b5e1437 add tests for artnet packet encode/decode processors 2026-03-02 12:34:26 -06:00
Joel Wetzell
34af7d7aec cleanup unnecessary use of floats 2026-03-02 12:34:06 -06:00
Joel Wetzell
df14024012 add convenience method for casting payloads 2026-03-02 12:06:42 -06:00
Joel Wetzell
af39c16f30 add more convenience functions for getting slices from params and add tests 2026-03-02 12:03:35 -06:00
Joel Wetzell
7d2a3225a7 make more error const for params config 2026-03-02 12:03:05 -06:00
Joel Wetzell
b5ffee022a use GetStringSlice in osc.message.create 2026-03-02 12:02:27 -06:00
Joel Wetzell
cd264d9ed4 add function to get string slice from params 2026-03-02 09:07:31 -06:00
Joel Wetzell
9d14e5929f wrap all payload types for expr env 2026-03-01 23:49:53 -06:00
Joel Wetzell
8fe1463198 Merge pull request #74 from jwetzell/feat/expr-payload-wrapping
wrap literal payloads into a struct for expr processors
2026-03-01 23:36:28 -06:00
Joel Wetzell
28a5d1ea90 wrap literal payloads into a struct for expr processors 2026-03-01 23:35:08 -06:00
Joel Wetzell
e9d5931e9d Merge pull request #73 from jwetzell/feat/expr-filter
add processor to filter using expr expression
2026-03-01 23:15:02 -06:00
Joel Wetzell
d90f103d00 add processor to filter using expr expression 2026-03-01 23:04:04 -06:00
Joel Wetzell
05b0de1dfd remote http request/response encoding 2026-03-01 23:02:56 -06:00
Joel Wetzell
0d8dbab743 add title for all processor schemas 2026-03-01 22:12:45 -06:00
120 changed files with 2675 additions and 1284 deletions

View File

@@ -34,7 +34,7 @@ jobs:
run: go mod tidy
- name: run tests
run: go test ./... -coverpkg "github.com/jwetzell/showbridge-go/..." -coverprofile coverage.out
run: go test ./... -count=1 -coverpkg "github.com/jwetzell/showbridge-go/..." -coverprofile coverage.out
- name: clean up coverage report
run: cat coverage.out | grep -v "github.com/jwetzell/showbridge-go/cmd" > coverage.nocmd.out

34
go.mod
View File

@@ -10,17 +10,17 @@ require (
github.com/extism/go-sdk v1.7.1
github.com/jwetzell/artnet-go v0.2.1
github.com/jwetzell/free-d-go v0.1.0
github.com/jwetzell/osc-go v0.1.0
github.com/jwetzell/osc-go v0.2.0
github.com/jwetzell/psn-go v0.3.0
github.com/nats-io/nats-server/v2 v2.12.4
github.com/nats-io/nats.go v1.49.0
github.com/urfave/cli/v3 v3.6.2
gitlab.com/gomidi/midi/v2 v2.3.22
github.com/urfave/cli/v3 v3.7.0
gitlab.com/gomidi/midi/v2 v2.3.23
go.bug.st/serial v1.6.4
go.opentelemetry.io/otel v1.40.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0
go.opentelemetry.io/otel/sdk v1.40.0
go.opentelemetry.io/otel/trace v1.40.0
go.opentelemetry.io/otel v1.41.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0
go.opentelemetry.io/otel/sdk v1.41.0
go.opentelemetry.io/otel/trace v1.41.0
modernc.org/quickjs v0.17.1
sigs.k8s.io/yaml v1.6.0
)
@@ -43,7 +43,7 @@ require (
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect
github.com/icholy/digest v1.1.0 // indirect
github.com/klauspost/compress v1.18.3 // indirect
@@ -65,20 +65,20 @@ require (
github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/zaf/g711 v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/grpc v1.78.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/grpc v1.79.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 // indirect
modernc.org/libc v1.67.1 // indirect

80
go.sum
View File

@@ -49,8 +49,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca h1:T54Ema1DU8ngI+aef9ZhAhNGQhcRTrWxVeG07F+c/Rw=
@@ -61,8 +61,8 @@ github.com/jwetzell/artnet-go v0.2.1 h1:iYTKWcwYrF5kBkYfkw2UbWvoueeA23iKEn7fR27m
github.com/jwetzell/artnet-go v0.2.1/go.mod h1:gli97Z32a0kMkZ6taoTiK7/lqHcF/dhiGjGJdx/PxqA=
github.com/jwetzell/free-d-go v0.1.0 h1:xHt6dvyit98X+OC3jVzV0aLidxbyzi3vI9QiYkteEtA=
github.com/jwetzell/free-d-go v0.1.0/go.mod h1:KmrkooRARRaxJTBSPvwt/6IMAIaHH1R8bSA8cwbbELw=
github.com/jwetzell/osc-go v0.1.0 h1:EXxup5VWBErHot2Ri4MFToPf6KCzLDTbCt2x6GLfw8I=
github.com/jwetzell/osc-go v0.1.0/go.mod h1:xLz0jTwebSxtx1TkKN1YVdeRqvpFNweDhTut5TE393A=
github.com/jwetzell/osc-go v0.2.0 h1:4as+BYCeZhEddFczGveP5yZZxvY728Uavz+ZSLZfOII=
github.com/jwetzell/osc-go v0.2.0/go.mod h1:D3ZIXYB12bt4S35lKFUqgCFbF1Y+9Ld0sOhHA9mGZZM=
github.com/jwetzell/psn-go v0.3.0 h1:WVpCEmExYE8a+I5hQak5jNJJp2x35VdGX/VuMUKPmhY=
github.com/jwetzell/psn-go v0.3.0/go.mod h1:bcEAeti4sQM375buujb3mIfmUstD4Aby18gq3ENb6+o=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
@@ -113,30 +113,30 @@ github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZ
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/urfave/cli/v3 v3.7.0 h1:AGSnbUyjtLiM+WJUb4dzXKldl/gL+F8OwmRDtVr6g2U=
github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/zaf/g711 v1.4.0 h1:XZYkjjiAg9QTBnHqEg37m2I9q3IIDv5JRYXs2N8ma7c=
github.com/zaf/g711 v1.4.0/go.mod h1:eCDXt3dSp/kYYAoooba7ukD/Q75jvAaS4WOMr0l1Roo=
gitlab.com/gomidi/midi/v2 v2.3.22 h1:4Q20o6q4BDo7i/KGvnwASeytOlrPI7MwsS7F2hA7fOM=
gitlab.com/gomidi/midi/v2 v2.3.22/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw=
gitlab.com/gomidi/midi/v2 v2.3.23 h1:P8NxV4EzV9c+BjpwTeB+G/qa+Xdq/UTazS2fKxY0O0g=
gitlab.com/gomidi/midi/v2 v2.3.23/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw=
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0/go.mod h1:u3T6vz0gh/NVzgDgiwkgLxpsSF6PaPmo2il0apGJbls=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 h1:inYW9ZhgqiDqh6BioM7DVHHzEGVq76Db5897WLGZ5Go=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0/go.mod h1:Izur+Wt8gClgMJqO/cZ8wdeeMryJ/xxiOVgFSSfpDTY=
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y=
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -145,34 +145,34 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -0,0 +1,7 @@
package common
type contextKey string
const RouterContextKey contextKey = contextKey("router")
const SourceContextKey contextKey = contextKey("source")
const ModulesContextKey contextKey = contextKey("modules")

View File

@@ -2,6 +2,9 @@ package config
import (
"errors"
"fmt"
"math"
"reflect"
)
type Config struct {
@@ -12,9 +15,12 @@ type Config struct {
type Params map[string]any
var (
ErrParamNotFound = errors.New("not found")
ErrParamNotString = errors.New("not a string")
ErrParamNotNumber = errors.New("not a number")
ErrParamNotFound = errors.New("not found")
ErrParamNotString = errors.New("not a string")
ErrParamNotNumber = errors.New("not a number")
ErrParamNotInteger = errors.New("not an integer")
ErrParamNotBool = errors.New("not a boolean")
ErrParamNotSlice = errors.New("not a slice")
)
func (p Params) GetString(key string) (string, error) {
@@ -37,14 +43,97 @@ func (p Params) GetInt(key string) (int, error) {
}
intValue, ok := value.(int)
if !ok {
floatValue, ok := value.(float64)
if !ok {
return 0, ErrParamNotNumber
}
intValue = int(floatValue)
if ok {
return intValue, nil
}
return intValue, nil
uintValue, ok := value.(uint)
if ok {
return int(uintValue), nil
}
byteValue, ok := value.(byte)
if ok {
return int(byteValue), nil
}
floatValue, ok := value.(float64)
if ok {
if floatValue != math.Floor(floatValue) {
return 0, ErrParamNotInteger
}
return int(floatValue), nil
}
return 0, ErrParamNotNumber
}
func (p Params) GetFloat32(key string) (float32, error) {
value, ok := p[key]
if !ok {
return 0, ErrParamNotFound
}
float32Value, ok := value.(float32)
if ok {
return float32Value, nil
}
float64Value, ok := value.(float64)
if ok {
return float32(float64Value), nil
}
intValue, ok := value.(int)
if ok {
return float32(intValue), nil
}
uintValue, ok := value.(uint)
if ok {
return float32(uintValue), nil
}
byteValue, ok := value.(byte)
if ok {
return float32(byteValue), nil
}
return 0, ErrParamNotNumber
}
func (p Params) GetFloat64(key string) (float64, error) {
value, ok := p[key]
if !ok {
return 0, ErrParamNotFound
}
float64Value, ok := value.(float64)
if ok {
return float64Value, nil
}
float32Value, ok := value.(float32)
if ok {
return float64(float32Value), nil
}
intValue, ok := value.(int)
if ok {
return float64(intValue), nil
}
uintValue, ok := value.(uint)
if ok {
return float64(uintValue), nil
}
byteValue, ok := value.(byte)
if ok {
return float64(byteValue), nil
}
return 0, ErrParamNotNumber
}
func (p Params) GetBool(key string) (bool, error) {
@@ -55,11 +144,126 @@ func (p Params) GetBool(key string) (bool, error) {
boolValue, ok := value.(bool)
if !ok {
return false, errors.New("not a boolean")
return false, ErrParamNotBool
}
return boolValue, nil
}
func (p Params) GetStringSlice(key string) ([]string, error) {
value, ok := p[key]
if !ok {
return nil, ErrParamNotFound
}
interfaceSlice, ok := value.([]any)
if !ok {
return nil, ErrParamNotSlice
}
stringSlice := make([]string, len(interfaceSlice))
for i, v := range interfaceSlice {
str, ok := v.(string)
if !ok {
return nil, fmt.Errorf("element at index %d is not a string", i)
}
stringSlice[i] = str
}
return stringSlice, nil
}
func (p Params) GetIntSlice(key string) ([]int, error) {
value, ok := p[key]
if !ok {
return nil, ErrParamNotFound
}
v := reflect.ValueOf(value)
if v.Kind() != reflect.Slice {
return nil, ErrParamNotSlice
}
result := make([]int, v.Len())
for i := 0; i < v.Len(); i++ {
elem := v.Index(i).Interface()
byteValue, ok := elem.(byte)
if ok {
result[i] = int(byteValue)
continue
}
uintValue, ok := elem.(uint)
if ok {
result[i] = int(uintValue)
continue
}
intValue, ok := elem.(int)
if ok {
result[i] = int(intValue)
continue
}
floatValue, ok := elem.(float64)
if ok {
if floatValue != math.Floor(floatValue) {
return nil, fmt.Errorf("element at index %d is not an integer", i)
}
result[i] = int(floatValue)
continue
}
return nil, fmt.Errorf("element at index %d is not a number", i)
}
return result, nil
}
func (p Params) GetByteSlice(key string) ([]byte, error) {
value, ok := p[key]
if !ok {
return nil, ErrParamNotFound
}
v := reflect.ValueOf(value)
if v.Kind() != reflect.Slice {
return nil, ErrParamNotSlice
}
result := make([]byte, v.Len())
for i := 0; i < v.Len(); i++ {
elem := v.Index(i).Interface()
byteValue, ok := elem.(byte)
if ok {
result[i] = byteValue
continue
}
uintValue, ok := elem.(uint)
if ok {
if uintValue > 255 {
return nil, fmt.Errorf("element at index %d is out of byte range", i)
}
result[i] = byte(uintValue)
continue
}
intValue, ok := elem.(int)
if ok {
if intValue < 0 || intValue > 255 {
return nil, fmt.Errorf("element at index %d is out of byte range", i)
}
result[i] = byte(intValue)
continue
}
floatValue, ok := elem.(float64)
if ok {
if floatValue != math.Floor(floatValue) {
return nil, fmt.Errorf("element at index %d is not an integer", i)
}
if floatValue < 0 || floatValue > 255 {
return nil, fmt.Errorf("element at index %d is out of byte range", i)
}
result[i] = byte(floatValue)
continue
}
return nil, fmt.Errorf("element at index %d is not a number", i)
}
return result, nil
}
type ModuleConfig struct {
Id string `json:"id"`
Type string `json:"type"`

View File

@@ -0,0 +1,297 @@
package config_test
import (
"encoding/json"
"slices"
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
)
func TestGoodStringParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
expected string
}{
{
name: "string param",
paramsJSON: `{"key": "value"}`,
key: "key",
expected: "value",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
params := config.Params{}
err := json.Unmarshal([]byte(testCase.paramsJSON), &params)
if err != nil {
t.Fatalf("Failed to unmarshal params JSON: %v", err)
}
value, err := params.GetString(testCase.key)
if err != nil {
t.Fatalf("GetString returned error: %v", err)
}
if value != testCase.expected {
t.Fatalf("GetString got %s, expected %s", value, testCase.expected)
}
})
}
}
func TestGoodIntParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
expected int
}{
{
name: "int param",
paramsJSON: `{"key": 1}`,
key: "key",
expected: 1,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
params := config.Params{}
err := json.Unmarshal([]byte(testCase.paramsJSON), &params)
if err != nil {
t.Fatalf("Failed to unmarshal params JSON: %v", err)
}
value, err := params.GetInt(testCase.key)
if err != nil {
t.Fatalf("GetInt returned error: %v", err)
}
if value != testCase.expected {
t.Fatalf("GetInt got %d, expected %d", value, testCase.expected)
}
})
}
}
func TestGoodFloat32ParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
expected float32
}{
{
name: "no decimal param",
paramsJSON: `{"key": 1}`,
key: "key",
expected: 1,
},
{
name: "float param",
paramsJSON: `{"key": 1.23}`,
key: "key",
expected: 1.23,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
params := config.Params{}
err := json.Unmarshal([]byte(testCase.paramsJSON), &params)
if err != nil {
t.Fatalf("Failed to unmarshal params JSON: %v", err)
}
value, err := params.GetFloat32(testCase.key)
if err != nil {
t.Fatalf("GetFloat32 returned error: %v", err)
}
if value != testCase.expected {
t.Fatalf("GetFloat32 got %f, expected %f", value, testCase.expected)
}
})
}
}
func TestGoodFloat64ParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
expected float64
}{
{
name: "no decimal param",
paramsJSON: `{"key": 1}`,
key: "key",
expected: 1,
},
{
name: "float param",
paramsJSON: `{"key": 1.23}`,
key: "key",
expected: 1.23,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
params := config.Params{}
err := json.Unmarshal([]byte(testCase.paramsJSON), &params)
if err != nil {
t.Fatalf("Failed to unmarshal params JSON: %v", err)
}
value, err := params.GetFloat64(testCase.key)
if err != nil {
t.Fatalf("GetFloat64 returned error: %v", err)
}
if value != testCase.expected {
t.Fatalf("GetFloat64 got %f, expected %f", value, testCase.expected)
}
})
}
}
func TestGoodBoolParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
expected bool
}{
{
name: "bool param",
paramsJSON: `{"key": true}`,
key: "key",
expected: true,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
params := config.Params{}
err := json.Unmarshal([]byte(testCase.paramsJSON), &params)
if err != nil {
t.Fatalf("Failed to unmarshal params JSON: %v", err)
}
value, err := params.GetBool(testCase.key)
if err != nil {
t.Fatalf("GetBool returned error: %v", err)
}
if value != testCase.expected {
t.Fatalf("GetBool got %t, expected %t", value, testCase.expected)
}
})
}
}
func TestGoodStringSliceParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
expected []string
}{
{
name: "string array",
paramsJSON: `{"key": ["value1", "value2"]}`,
key: "key",
expected: []string{"value1", "value2"},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
params := config.Params{}
err := json.Unmarshal([]byte(testCase.paramsJSON), &params)
if err != nil {
t.Fatalf("Failed to unmarshal params JSON: %v", err)
}
value, err := params.GetStringSlice(testCase.key)
if err != nil {
t.Fatalf("GetStringSlice returned error: %v", err)
}
if !slices.Equal(value, testCase.expected) {
t.Fatalf("GetStringSlice got %v, expected %v", value, testCase.expected)
}
})
}
}
func TestGoodIntSliceParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
expected []int
}{
{
name: "int array",
paramsJSON: `{"key": [1, 2, 3]}`,
key: "key",
expected: []int{1, 2, 3},
},
{
name: "int array with floats",
paramsJSON: `{"key": [1.0, 2.0, 3.0]}`,
key: "key",
expected: []int{1, 2, 3},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
params := config.Params{}
err := json.Unmarshal([]byte(testCase.paramsJSON), &params)
if err != nil {
t.Fatalf("Failed to unmarshal params JSON: %v", err)
}
value, err := params.GetIntSlice(testCase.key)
if err != nil {
t.Fatalf("GetIntSlice returned error: %v", err)
}
if !slices.Equal(value, testCase.expected) {
t.Fatalf("GetIntSlice got %v, expected %v", value, testCase.expected)
}
})
}
}
func TestGoodByteSliceParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
expected []byte
}{
{
name: "byte array",
paramsJSON: `{"key": [1,2,3,4]}`,
key: "key",
expected: []byte{1, 2, 3, 4},
},
{
name: "byte array with floats",
paramsJSON: `{"key": [1.0,2.0,3.0,4.0]}`,
key: "key",
expected: []byte{1, 2, 3, 4},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
params := config.Params{}
err := json.Unmarshal([]byte(testCase.paramsJSON), &params)
if err != nil {
t.Fatalf("Failed to unmarshal params JSON: %v", err)
}
value, err := params.GetByteSlice(testCase.key)
if err != nil {
t.Fatalf("GetByteSlice returned error: %v", err)
}
if !slices.Equal(value, testCase.expected) {
t.Fatalf("GetByteSlice got %v, expected %v", value, testCase.expected)
}
})
}
}

View File

@@ -7,7 +7,9 @@ import (
"net/http"
"time"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
)
@@ -40,7 +42,7 @@ func (hc *HTTPClient) Type() string {
func (hc *HTTPClient) Start(ctx context.Context) error {
hc.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("http.client unable to get router from context")
@@ -61,7 +63,7 @@ func (hc *HTTPClient) Start(ctx context.Context) error {
func (hc *HTTPClient) Output(ctx context.Context, payload any) error {
payloadRequest, ok := payload.(*http.Request)
payloadRequest, ok := processor.GetAnyAs[*http.Request](payload)
if !ok {
return errors.New("http.client is only able to output an http.Request")
@@ -87,3 +89,7 @@ func (hc *HTTPClient) Output(ctx context.Context, payload any) error {
func (hc *HTTPClient) Stop() {
hc.cancel()
}
func (hc *HTTPClient) Get(key string) (any, error) {
return nil, errors.New("http.client does not support Get")
}

View File

@@ -8,6 +8,7 @@ import (
"log/slog"
"net/http"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
@@ -142,7 +143,7 @@ func (hs *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (hs *HTTPServer) Start(ctx context.Context) error {
hs.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("http.server unable to get router from context")
@@ -182,7 +183,7 @@ func (hs *HTTPServer) Output(ctx context.Context, payload any) error {
return errors.New("http.server output must originate from an http.server input")
}
payloadResponse, ok := payload.(processor.HTTPResponse)
payloadResponse, ok := processor.GetAnyAs[processor.HTTPResponse](payload)
if !ok {
return errors.New("http.server is only able to output HTTPResponse")
@@ -200,3 +201,7 @@ func (hs *HTTPServer) Output(ctx context.Context, payload any) error {
func (hs *HTTPServer) Stop() {
hs.cancel()
}
func (hs *HTTPServer) Get(key string) (any, error) {
return nil, errors.New("http.server does not support Get")
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"log/slog"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
"gitlab.com/gomidi/midi/v2"
@@ -50,7 +51,7 @@ func (mi *MIDIInput) Type() string {
func (mi *MIDIInput) Start(ctx context.Context) error {
mi.logger.Debug("running")
defer midi.CloseDriver()
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("midi.input unable to get router from context")
@@ -89,3 +90,12 @@ func (mi *MIDIInput) Output(ctx context.Context, payload any) error {
func (mi *MIDIInput) Stop() {
mi.cancel()
}
func (mi *MIDIInput) Get(key string) (any, error) {
switch key {
case "port":
return mi.Port, nil
default:
return nil, errors.New("midi.input key not found")
}
}

View File

@@ -8,7 +8,9 @@ import (
"fmt"
"log/slog"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
"gitlab.com/gomidi/midi/v2"
_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
@@ -51,7 +53,7 @@ func (mo *MIDIOutput) Type() string {
func (mo *MIDIOutput) Start(ctx context.Context) error {
mo.logger.Debug("running")
defer midi.CloseDriver()
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("midi.output unable to get router from context")
@@ -84,7 +86,7 @@ func (mo *MIDIOutput) Output(ctx context.Context, payload any) error {
return errors.New("midi.output output is not setup")
}
payloadMessage, ok := payload.(midi.Message)
payloadMessage, ok := processor.GetAnyAs[midi.Message](payload)
if !ok {
return errors.New("midi.output can only ouptut midi.Message")
@@ -96,3 +98,12 @@ func (mo *MIDIOutput) Output(ctx context.Context, payload any) error {
func (mo *MIDIOutput) Stop() {
mo.cancel()
}
func (mo *MIDIOutput) Get(key string) (any, error) {
switch key {
case "port":
return mo.Port, nil
default:
return nil, errors.New("midi.output key not found")
}
}

View File

@@ -21,6 +21,7 @@ type Module interface {
Start(context.Context) error
Stop()
Output(context.Context, any) error
Get(key string) (any, error)
}
type ModuleRegistration struct {

View File

@@ -7,7 +7,9 @@ import (
"log/slog"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
)
@@ -61,7 +63,7 @@ func (mc *MQTTClient) Type() string {
func (mc *MQTTClient) Start(ctx context.Context) error {
mc.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("mqtt.client unable to get router from context")
@@ -101,7 +103,7 @@ func (mc *MQTTClient) Start(ctx context.Context) error {
}
func (mc *MQTTClient) Output(ctx context.Context, payload any) error {
payloadMessage, ok := payload.(mqtt.Message)
payloadMessage, ok := processor.GetAnyAs[mqtt.Message](payload)
if !ok {
return errors.New("mqtt.client is only able to output a MQTTMessage")
@@ -125,3 +127,7 @@ func (mc *MQTTClient) Output(ctx context.Context, payload any) error {
func (mc *MQTTClient) Stop() {
mc.cancel()
}
func (mc *MQTTClient) Get(key string) (any, error) {
return nil, errors.New("mqtt.client does not support Get")
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"log/slog"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
@@ -53,7 +54,7 @@ func (nc *NATSClient) Type() string {
func (nc *NATSClient) Start(ctx context.Context) error {
nc.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("nats.client unable to get router from context")
@@ -94,7 +95,7 @@ func (nc *NATSClient) Start(ctx context.Context) error {
func (nc *NATSClient) Output(ctx context.Context, payload any) error {
payloadMessage, ok := payload.(processor.NATSMessage)
payloadMessage, ok := processor.GetAnyAs[processor.NATSMessage](payload)
if !ok {
return errors.New("nats.client is only able to output NATSMessage")
@@ -116,3 +117,7 @@ func (nc *NATSClient) Output(ctx context.Context, payload any) error {
func (nc *NATSClient) Stop() {
nc.cancel()
}
func (nc *NATSClient) Get(key string) (any, error) {
return nil, errors.New("nats.client does not support Get")
}

View File

@@ -8,6 +8,7 @@ import (
"net"
"time"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
"github.com/nats-io/nats-server/v2/server"
@@ -66,7 +67,7 @@ func (ns *NATSServer) Type() string {
func (ns *NATSServer) Start(ctx context.Context) error {
ns.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("nats.server unable to get router from context")
@@ -112,3 +113,7 @@ func (ns *NATSServer) Stop() {
ns.server.Shutdown()
}
}
func (ns *NATSServer) Get(key string) (any, error) {
return nil, errors.New("nats.server does not support Get")
}

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/jwetzell/psn-go"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
)
@@ -43,7 +44,7 @@ func (pc *PSNClient) Type() string {
func (pc *PSNClient) Start(ctx context.Context) error {
pc.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("psn.client unable to get router from context")
@@ -112,3 +113,14 @@ func (pc *PSNClient) Output(ctx context.Context, payload any) error {
func (pc *PSNClient) Stop() {
pc.cancel()
}
func (pc *PSNClient) Get(key string) (any, error) {
switch key {
case "trackers":
return pc.decoder.Trackers, nil
case "name":
return pc.decoder.SystemName, nil
default:
return nil, errors.New("psn.client key not found")
}
}

View File

@@ -9,8 +9,10 @@ import (
"log/slog"
"time"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/framer"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
"go.bug.st/serial"
)
@@ -84,7 +86,7 @@ func (sc *SerialClient) SetupPort() error {
func (sc *SerialClient) Start(ctx context.Context) error {
sc.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("serial.client unable to get router from context")
@@ -156,7 +158,7 @@ func (sc *SerialClient) Start(ctx context.Context) error {
func (sc *SerialClient) Output(ctx context.Context, payload any) error {
payloadBytes, ok := payload.([]byte)
payloadBytes, ok := processor.GetAnyAs[[]byte](payload)
if !ok {
return errors.New("serial.client can only ouptut bytes")
@@ -169,3 +171,12 @@ func (sc *SerialClient) Output(ctx context.Context, payload any) error {
func (sc *SerialClient) Stop() {
sc.cancel()
}
func (sc *SerialClient) Get(key string) (any, error) {
switch key {
case "port":
return sc.Port, nil
default:
return nil, errors.New("serial.client key not found")
}
}

View File

@@ -7,6 +7,7 @@ import (
"io"
"log/slog"
"os"
"path"
"sync"
"time"
@@ -14,22 +15,24 @@ import (
"github.com/emiago/diago/media"
"github.com/emiago/sipgo"
"github.com/emiago/sipgo/sip"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
)
type SIPCallServer struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
IP string
Port int
Transport string
UserAgent string
dg *diago.Diago
logger *slog.Logger
cancel context.CancelFunc
config config.ModuleConfig
ctx context.Context
router route.RouteIO
IP string
Port int
Transport string
UserAgent string
RecordingPath string
dg *diago.Diago
logger *slog.Logger
cancel context.CancelFunc
}
type SIPCallMessage struct {
@@ -85,7 +88,19 @@ func init() {
}
}
return &SIPCallServer{config: moduleConfig, IP: ipString, Port: int(portNum), Transport: transportString, UserAgent: userAgentString, logger: CreateLogger(moduleConfig)}, nil
recordingPathString := ""
recordingPath, ok := params["recordingPath"]
if ok {
specificRecordingPath, ok := recordingPath.(string)
if !ok {
return nil, errors.New("sip.call.server recordingPath must be a string")
}
recordingPathString = specificRecordingPath
}
return &SIPCallServer{config: moduleConfig, IP: ipString, Port: int(portNum), Transport: transportString, UserAgent: userAgentString, RecordingPath: recordingPathString, logger: CreateLogger(moduleConfig)}, nil
},
})
}
@@ -100,7 +115,7 @@ func (scs *SIPCallServer) Type() string {
func (scs *SIPCallServer) Start(ctx context.Context) error {
scs.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("sip.call.server unable to get router from context")
@@ -150,6 +165,41 @@ func (scs *SIPCallServer) HandleCall(inDialog *diago.DialogServerSession) {
dialogContext := context.WithValue(scs.ctx, sipCallContextKey("call"), &SIPCall{
inDialog: inDialog,
})
if scs.RecordingPath != "" {
filename := path.Join(scs.RecordingPath, fmt.Sprintf("%s-%d.wav", inDialog.ToUser(), time.Now().Unix()))
wavFile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
scs.logger.Error("error creating recording file", "error", err, "filename", filename)
} else {
recorder, err := inDialog.AudioStereoRecordingCreate(wavFile)
if err != nil {
scs.logger.Error("error creating recording", "error", err)
wavFile.Close()
} else {
go func() {
<-inDialog.Context().Done()
err := recorder.Close()
if err != nil {
scs.logger.Error("error closing recording", "error", err)
}
wavFile.Close()
scs.logger.Debug("finished recording", "filename", filename)
}()
go func() {
bytes, err := media.Copy(recorder.AudioReader(), recorder.AudioWriter())
fmt.Println("recorded bytes", bytes)
if err != nil {
if !errors.Is(err, io.EOF) {
scs.logger.Error("error while recording", "error", err)
}
}
}()
}
}
}
scs.router.HandleInput(dialogContext, scs.Id(), SIPCallMessage{
To: inDialog.ToUser(),
})
@@ -173,7 +223,7 @@ func (scs *SIPCallServer) Output(ctx context.Context, payload any) error {
return errors.New("sip.call.server inDialog already ended")
}
payloadDTMFResponse, ok := payload.(processor.SipDTMFResponse)
payloadDTMFResponse, ok := processor.GetAnyAs[processor.SipDTMFResponse](payload)
if ok {
dtmfWriter := call.inDialog.AudioWriterDTMF()
@@ -189,7 +239,7 @@ func (scs *SIPCallServer) Output(ctx context.Context, payload any) error {
return nil
}
payloadAudioFileResponse, ok := payload.(processor.SipAudioFileResponse)
payloadAudioFileResponse, ok := processor.GetAnyAs[processor.SipAudioFileResponse](payload)
if ok {
audioFile, err := os.Open(payloadAudioFileResponse.AudioFile)
@@ -221,3 +271,7 @@ func (scs *SIPCallServer) Output(ctx context.Context, payload any) error {
func (scs *SIPCallServer) Stop() {
scs.cancel()
}
func (scs *SIPCallServer) Get(key string) (any, error) {
return nil, errors.New("sip.call.server does not support Get")
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/emiago/diago/media"
"github.com/emiago/sipgo"
"github.com/emiago/sipgo/sip"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
@@ -113,7 +114,7 @@ func (sds *SIPDTMFServer) Type() string {
func (sds *SIPDTMFServer) Start(ctx context.Context) error {
sds.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("sip.dtmf.server unable to get router from context")
@@ -199,7 +200,7 @@ func (sds *SIPDTMFServer) Output(ctx context.Context, payload any) error {
return errors.New("sip.dtmf.server inDialog already ended")
}
payloadDTMFResponse, ok := payload.(processor.SipDTMFResponse)
payloadDTMFResponse, ok := processor.GetAnyAs[processor.SipDTMFResponse](payload)
if ok {
dtmfWriter := call.inDialog.AudioWriterDTMF()
@@ -216,7 +217,7 @@ func (sds *SIPDTMFServer) Output(ctx context.Context, payload any) error {
return nil
}
payloadAudioFileResponse, ok := payload.(processor.SipAudioFileResponse)
payloadAudioFileResponse, ok := processor.GetAnyAs[processor.SipAudioFileResponse](payload)
if ok {
audioFile, err := os.Open(payloadAudioFileResponse.AudioFile)
@@ -249,3 +250,7 @@ func (sds *SIPDTMFServer) Output(ctx context.Context, payload any) error {
func (sds *SIPDTMFServer) Stop() {
sds.cancel()
}
func (sds *SIPDTMFServer) Get(key string) (any, error) {
return nil, errors.New("sip.dtmf.server does not support Get")
}

View File

@@ -8,8 +8,10 @@ import (
"net"
"time"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/framer"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
)
@@ -69,7 +71,7 @@ func (tc *TCPClient) Type() string {
func (tc *TCPClient) Start(ctx context.Context) error {
tc.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("net.tcp.client unable to get router from context")
@@ -152,7 +154,7 @@ func (tc *TCPClient) Output(ctx context.Context, payload any) error {
return err
}
}
payloadBytes, ok := payload.([]byte)
payloadBytes, ok := processor.GetAnyAs[[]byte](payload)
if !ok {
return errors.New("net.tcp.client is only able to output bytes")
}
@@ -163,3 +165,20 @@ func (tc *TCPClient) Output(ctx context.Context, payload any) error {
func (tc *TCPClient) Stop() {
tc.cancel()
}
func (tc *TCPClient) Get(key string) (any, error) {
switch key {
case "host":
host, err := tc.config.Params.GetString("host")
if err != nil {
return nil, fmt.Errorf("net.tcp.client host error: %w", err)
}
return host, nil
case "ip":
return tc.Addr.IP.String(), nil
case "port":
return tc.Addr.Port, nil
default:
return nil, errors.New("net.tcp.client key not found")
}
}

View File

@@ -11,8 +11,10 @@ import (
"syscall"
"time"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/framer"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
)
@@ -153,7 +155,7 @@ ClientRead:
func (ts *TCPServer) Start(ctx context.Context) error {
ts.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("net.tcp.server unable to get router from context")
@@ -200,7 +202,7 @@ AcceptLoop:
}
func (ts *TCPServer) Output(ctx context.Context, payload any) error {
payloadBytes, ok := payload.([]byte)
payloadBytes, ok := processor.GetAnyAs[[]byte](payload)
if !ok {
return errors.New("net.tcp.server is only able to output bytes")
@@ -226,3 +228,14 @@ func (ts *TCPServer) Stop() {
ts.cancel()
ts.wg.Wait()
}
func (ts *TCPServer) Get(key string) (any, error) {
switch key {
case "ip":
return ts.Addr.IP.String(), nil
case "port":
return ts.Addr.Port, nil
default:
return nil, errors.New("net.tcp.server key not found")
}
}

View File

@@ -17,7 +17,7 @@ func TestHTTPServerFromRegistry(t *testing.T) {
Id: "test",
Type: "http.server",
Params: map[string]any{
"port": 3000.0,
"port": 3000,
},
})

View File

@@ -30,6 +30,10 @@ func (m *TestModule) Id() string {
return "test"
}
func (m *TestModule) Get(key string) (any, error) {
return nil, nil
}
func TestModuleBadRegistrationNoType(t *testing.T) {
defer func() {
if r := recover(); r == nil {

View File

@@ -19,7 +19,7 @@ func TestSerialClientFromRegistry(t *testing.T) {
Params: map[string]any{
"port": "/dev/ttyUSB0",
"framing": "LF",
"baudRate": 9600.0,
"baudRate": 9600,
},
})

View File

@@ -18,7 +18,7 @@ func TestTCPClientFromRegistry(t *testing.T) {
Type: "net.tcp.client",
Params: map[string]any{
"host": "localhost",
"port": 8000.0,
"port": 8000,
"framing": "LF",
},
})
@@ -60,7 +60,7 @@ func TestBadTCPClient(t *testing.T) {
{
name: "no host param",
params: map[string]any{
"port": 8000.0,
"port": 8000,
},
errorString: "net.tcp.client host error: not found",
},
@@ -68,7 +68,7 @@ func TestBadTCPClient(t *testing.T) {
name: "non-string host param",
params: map[string]any{
"host": 123,
"port": 8000.0,
"port": 8000,
},
errorString: "net.tcp.client host error: not a string",
},

View File

@@ -17,7 +17,7 @@ func TestTCPServerFromRegistry(t *testing.T) {
Id: "test",
Type: "net.tcp.server",
Params: map[string]any{
"port": 8000.0,
"port": 8000,
"framing": "LF",
},
})
@@ -59,14 +59,14 @@ func TestBadTCPServer(t *testing.T) {
{
name: "no framing param",
params: map[string]any{
"port": 8000.0,
"port": 8000,
},
errorString: "net.tcp.server framing error: not found",
},
{
name: "non-string framing param",
params: map[string]any{
"port": 8000.0,
"port": 8000,
"framing": 1,
},
errorString: "net.tcp.server framing error: not a string",
@@ -74,7 +74,7 @@ func TestBadTCPServer(t *testing.T) {
{
name: "unkown framing method",
params: map[string]any{
"port": 8000.0,
"port": 8000,
"framing": "asdfasdfasdfasdflkj",
},
errorString: "net.tcp.server unknown framing method: asdfasdfasdfasdflkj",
@@ -82,7 +82,7 @@ func TestBadTCPServer(t *testing.T) {
{
name: "non-string ip param",
params: map[string]any{
"port": 8000.0,
"port": 8000,
"framing": "LF",
"ip": 123,
},
@@ -92,7 +92,7 @@ func TestBadTCPServer(t *testing.T) {
name: "invalid addr",
params: map[string]any{
"ip": "127.0.0.",
"port": 8000.0,
"port": 8000,
"framing": "LF",
},
errorString: "lookup 127.0.0.: no such host",

View File

@@ -17,7 +17,7 @@ func TestTimeIntervalFromRegistry(t *testing.T) {
Id: "test",
Type: "time.interval",
Params: map[string]any{
"duration": 1000.0,
"duration": 1000,
},
})

View File

@@ -17,7 +17,7 @@ func TestTimeTimerFromRegistry(t *testing.T) {
Id: "test",
Type: "time.timer",
Params: map[string]any{
"duration": 1000.0,
"duration": 1000,
},
})

View File

@@ -18,7 +18,7 @@ func TestUDPClientFromRegistry(t *testing.T) {
Type: "net.udp.client",
Params: map[string]any{
"host": "localhost",
"port": 8000.0,
"port": 8000,
"framing": "LF",
},
})
@@ -60,7 +60,7 @@ func TestBadUDPClient(t *testing.T) {
{
name: "no host param",
params: map[string]any{
"port": 8000.0,
"port": 8000,
},
errorString: "net.udp.client host error: not found",
},
@@ -68,7 +68,7 @@ func TestBadUDPClient(t *testing.T) {
name: "non-string host param",
params: map[string]any{
"host": 123,
"port": 8000.0,
"port": 8000,
},
errorString: "net.udp.client host error: not a string",
},

View File

@@ -18,7 +18,7 @@ func TestUDPMulticastFromRegistry(t *testing.T) {
Type: "net.udp.multicast",
Params: map[string]any{
"ip": "236.10.10.10",
"port": 56565.0,
"port": 56565,
},
})
@@ -59,7 +59,7 @@ func TestBadUDPMulticast(t *testing.T) {
{
name: "no ip param",
params: map[string]any{
"port": 8000.0,
"port": 8000,
},
errorString: "net.udp.multicast ip error: not found",
},
@@ -67,7 +67,7 @@ func TestBadUDPMulticast(t *testing.T) {
name: "non-string ip param",
params: map[string]any{
"ip": 123,
"port": 8000.0,
"port": 8000,
},
errorString: "net.udp.multicast ip error: not a string",
},
@@ -75,7 +75,7 @@ func TestBadUDPMulticast(t *testing.T) {
name: "invalid addr",
params: map[string]any{
"ip": "127.0.0.",
"port": 8000.0,
"port": 8000,
},
errorString: "lookup 127.0.0.: no such host",
},

View File

@@ -17,7 +17,7 @@ func TestUDPServerFromRegistry(t *testing.T) {
Id: "test",
Type: "net.udp.server",
Params: map[string]any{
"port": 8000.0,
"port": 8000,
},
})
@@ -55,7 +55,7 @@ func TestBadUDPServer(t *testing.T) {
{
name: "non-string ip param",
params: map[string]any{
"port": 8000.0,
"port": 8000,
"ip": 123,
},
errorString: "net.udp.server ip error: not a string",
@@ -63,7 +63,7 @@ func TestBadUDPServer(t *testing.T) {
{
name: "non-number bufferSize param",
params: map[string]any{
"port": 8000.0,
"port": 8000,
"bufferSize": "1024",
},
errorString: "net.udp.server bufferSize error: not a number",
@@ -72,7 +72,7 @@ func TestBadUDPServer(t *testing.T) {
name: "invalid addr",
params: map[string]any{
"ip": "127.0.0.",
"port": 8000.0,
"port": 8000,
},
errorString: "lookup 127.0.0.: no such host",
},

View File

@@ -7,6 +7,7 @@ import (
"log/slog"
"time"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
)
@@ -46,7 +47,7 @@ func (i *TimeInterval) Type() string {
func (i *TimeInterval) Start(ctx context.Context) error {
i.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("time.interval unable to get router from context")
@@ -82,3 +83,12 @@ func (i *TimeInterval) Output(ctx context.Context, payload any) error {
func (i *TimeInterval) Stop() {
i.cancel()
}
func (i *TimeInterval) Get(key string) (any, error) {
switch key {
case "duration":
return i.Duration, nil
default:
return nil, errors.New("time.interval key not found")
}
}

View File

@@ -7,6 +7,7 @@ import (
"log/slog"
"time"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
)
@@ -47,7 +48,7 @@ func (t *TimeTimer) Type() string {
func (t *TimeTimer) Start(ctx context.Context) error {
t.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("net.tcp.client unable to get router from context")
@@ -81,3 +82,12 @@ func (t *TimeTimer) Output(ctx context.Context, payload any) error {
func (t *TimeTimer) Stop() {
t.cancel()
}
func (t *TimeTimer) Get(key string) (any, error) {
switch key {
case "duration":
return t.Duration, nil
default:
return nil, errors.New("time.timer key not found")
}
}

View File

@@ -7,7 +7,9 @@ import (
"log/slog"
"net"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
)
@@ -62,7 +64,7 @@ func (uc *UDPClient) SetupConn() error {
func (uc *UDPClient) Start(ctx context.Context) error {
uc.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("net.udp.client unable to get router from context")
@@ -87,7 +89,7 @@ func (uc *UDPClient) Start(ctx context.Context) error {
func (uc *UDPClient) Output(ctx context.Context, payload any) error {
payloadBytes, ok := payload.([]byte)
payloadBytes, ok := processor.GetAnyAs[[]byte](payload)
if !ok {
return errors.New("net.udp.client is only able to output bytes")
}
@@ -106,3 +108,20 @@ func (uc *UDPClient) Output(ctx context.Context, payload any) error {
func (uc *UDPClient) Stop() {
uc.cancel()
}
func (uc *UDPClient) Get(key string) (any, error) {
switch key {
case "host":
host, err := uc.config.Params.GetString("host")
if err != nil {
return nil, fmt.Errorf("net.udp.client host error: %w", err)
}
return host, nil
case "ip":
return uc.Addr.IP.String(), nil
case "port":
return uc.Addr.Port, nil
default:
return nil, errors.New("net.udp.client key not found")
}
}

View File

@@ -8,7 +8,9 @@ import (
"net"
"time"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
)
@@ -56,7 +58,7 @@ func (um *UDPMulticast) Type() string {
func (um *UDPMulticast) Start(ctx context.Context) error {
um.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("net.udp.multicast unable to get router from context")
@@ -108,7 +110,7 @@ func (um *UDPMulticast) Start(ctx context.Context) error {
func (um *UDPMulticast) Output(ctx context.Context, payload any) error {
payloadBytes, ok := payload.([]byte)
payloadBytes, ok := processor.GetAnyAs[[]byte](payload)
if !ok {
return errors.New("net.udp.multicast can only output bytes")
}
@@ -124,3 +126,14 @@ func (um *UDPMulticast) Output(ctx context.Context, payload any) error {
func (um *UDPMulticast) Stop() {
um.cancel()
}
func (um *UDPMulticast) Get(key string) (any, error) {
switch key {
case "ip":
return um.Addr.IP.String(), nil
case "port":
return um.Addr.Port, nil
default:
return nil, errors.New("net.udp.multicast key not found")
}
}

View File

@@ -8,6 +8,7 @@ import (
"net"
"time"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
)
@@ -69,7 +70,7 @@ func (us *UDPServer) Type() string {
func (us *UDPServer) Start(ctx context.Context) error {
us.logger.Debug("running")
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
router, ok := ctx.Value(common.RouterContextKey).(route.RouteIO)
if !ok {
return errors.New("net.udp.server unable to get router from context")
@@ -122,3 +123,14 @@ func (us *UDPServer) Output(ctx context.Context, payload any) error {
func (us *UDPServer) Stop() {
us.cancel()
}
func (us *UDPServer) Get(key string) (any, error) {
switch key {
case "ip":
return us.Addr.IP.String(), nil
case "port":
return us.Addr.Port, nil
default:
return nil, errors.New("net.udp.server key not found")
}
}

View File

@@ -13,7 +13,7 @@ type ArtNetPacketDecode struct {
}
func (apd *ArtNetPacketDecode) Process(ctx context.Context, payload any) (any, error) {
payloadBytes, ok := payload.([]byte)
payloadBytes, ok := GetAnyAs[[]byte](payload)
if !ok {
return nil, fmt.Errorf("artnet.packet.decode processor only accepts a []byte")

View File

@@ -13,7 +13,7 @@ type ArtNetPacketEncode struct {
}
func (ape *ArtNetPacketEncode) Process(ctx context.Context, payload any) (any, error) {
payloadPacket, ok := payload.(artnet.ArtNetPacket)
payloadPacket, ok := GetAnyAs[artnet.ArtNetPacket](payload)
if !ok {
return nil, fmt.Errorf("artnet.packet.encode processor only accepts an ArtNetPacket")

View File

@@ -14,7 +14,7 @@ type ArtNetPacketFilter struct {
}
func (apf *ArtNetPacketFilter) Process(ctx context.Context, payload any) (any, error) {
payloadPacket, ok := payload.(artnet.ArtNetPacket)
payloadPacket, ok := GetAnyAs[artnet.ArtNetPacket](payload)
if !ok {
return nil, fmt.Errorf("artnet.packet.filter processor only accepts an ArtNetPacket")

View File

@@ -0,0 +1,73 @@
package processor
import (
"context"
"fmt"
"github.com/expr-lang/expr"
"github.com/expr-lang/expr/vm"
"github.com/jwetzell/showbridge-go/internal/config"
)
// NOTE(jwetzell): see language definition https://expr-lang.org/docs/language-definition
type FilterExpr struct {
config config.ProcessorConfig
Program *vm.Program
}
func SafeExprEnv(payload any) any {
exprEnv := ExprEnv{
Payload: payload,
}
return exprEnv
}
type ExprEnv struct {
Payload any
}
func (se *FilterExpr) Process(ctx context.Context, payload any) (any, error) {
exprEnv := SafeExprEnv(payload)
output, err := expr.Run(se.Program, exprEnv)
if err != nil {
return nil, err
}
outputBool, ok := output.(bool)
if !ok {
return nil, fmt.Errorf("filter.expr expression did not return a boolean")
}
if !outputBool {
return nil, nil
}
return payload, nil
}
func (se *FilterExpr) Type() string {
return se.config.Type
}
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "filter.expr",
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
expressionString, err := params.GetString("expression")
if err != nil {
return nil, fmt.Errorf("filter.expr expression error: %w", err)
}
program, err := expr.Compile(expressionString)
if err != nil {
return nil, err
}
return &FilterExpr{config: config, Program: program}, nil
},
})
}

View File

@@ -15,7 +15,7 @@ type FloatParse struct {
}
func (fp *FloatParse) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
payloadString, ok := GetAnyAs[string](payload)
if !ok {
return nil, errors.New("float.parse processor only accepts a string")

View File

@@ -0,0 +1,71 @@
package processor
import (
"context"
"errors"
"fmt"
"math/rand/v2"
"github.com/jwetzell/showbridge-go/internal/config"
)
type FloatRandom struct {
BitSize int
Min float64
Max float64
config config.ProcessorConfig
}
func (fr *FloatRandom) Process(ctx context.Context, payload any) (any, error) {
if fr.BitSize == 32 {
payloadFloat := rand.Float32()*(float32(fr.Max)-float32(fr.Min)) + float32(fr.Min)
return payloadFloat, nil
}
if fr.BitSize == 64 {
payloadFloat := rand.Float64()*(fr.Max-fr.Min) + fr.Min
return payloadFloat, nil
}
return nil, errors.New("float.random bitSize error: must be 32 or 64")
}
func (fr *FloatRandom) Type() string {
return fr.config.Type
}
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "float.random",
New: func(processorConfig config.ProcessorConfig) (Processor, error) {
params := processorConfig.Params
bitSizeInt, err := params.GetInt("bitSize")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
bitSizeInt = 32
} else {
return nil, fmt.Errorf("float.random bitSize error: %w", err)
}
}
if bitSizeInt != 32 && bitSizeInt != 64 {
return nil, errors.New("float.random bitSize error: must be 32 or 64")
}
minFloat, err := params.GetFloat64("min")
if err != nil {
return nil, fmt.Errorf("float.random min error: %w", err)
}
maxFloat, err := params.GetFloat64("max")
if err != nil {
return nil, fmt.Errorf("float.random max error: %w", err)
}
if maxFloat < minFloat {
return nil, errors.New("float.random max must be greater than min")
}
return &FloatRandom{config: processorConfig, Min: minFloat, Max: maxFloat, BitSize: bitSizeInt}, nil
},
})
}

View File

@@ -26,8 +26,10 @@ type FreeDCreate struct {
func (fc *FreeDCreate) Process(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var idBuffer bytes.Buffer
err := fc.Id.Execute(&idBuffer, payload)
err := fc.Id.Execute(&idBuffer, templateData)
if err != nil {
return nil, err
@@ -42,7 +44,7 @@ func (fc *FreeDCreate) Process(ctx context.Context, payload any) (any, error) {
}
var panBuffer bytes.Buffer
err = fc.Pan.Execute(&panBuffer, payload)
err = fc.Pan.Execute(&panBuffer, templateData)
if err != nil {
return nil, err
@@ -57,7 +59,7 @@ func (fc *FreeDCreate) Process(ctx context.Context, payload any) (any, error) {
}
var tiltBuffer bytes.Buffer
err = fc.Tilt.Execute(&tiltBuffer, payload)
err = fc.Tilt.Execute(&tiltBuffer, templateData)
if err != nil {
return nil, err
@@ -72,7 +74,7 @@ func (fc *FreeDCreate) Process(ctx context.Context, payload any) (any, error) {
}
var rollBuffer bytes.Buffer
err = fc.Tilt.Execute(&rollBuffer, payload)
err = fc.Tilt.Execute(&rollBuffer, templateData)
if err != nil {
return nil, err
@@ -87,7 +89,7 @@ func (fc *FreeDCreate) Process(ctx context.Context, payload any) (any, error) {
}
var posXBuffer bytes.Buffer
err = fc.PosX.Execute(&posXBuffer, payload)
err = fc.PosX.Execute(&posXBuffer, templateData)
if err != nil {
return nil, err
@@ -102,7 +104,7 @@ func (fc *FreeDCreate) Process(ctx context.Context, payload any) (any, error) {
}
var posYBuffer bytes.Buffer
err = fc.PosY.Execute(&posYBuffer, payload)
err = fc.PosY.Execute(&posYBuffer, templateData)
if err != nil {
return nil, err
@@ -117,7 +119,7 @@ func (fc *FreeDCreate) Process(ctx context.Context, payload any) (any, error) {
}
var posZBuffer bytes.Buffer
err = fc.PosZ.Execute(&posZBuffer, payload)
err = fc.PosZ.Execute(&posZBuffer, templateData)
if err != nil {
return nil, err
@@ -132,7 +134,7 @@ func (fc *FreeDCreate) Process(ctx context.Context, payload any) (any, error) {
}
var zoomBuffer bytes.Buffer
err = fc.Zoom.Execute(&zoomBuffer, payload)
err = fc.Zoom.Execute(&zoomBuffer, templateData)
if err != nil {
return nil, err
@@ -147,7 +149,7 @@ func (fc *FreeDCreate) Process(ctx context.Context, payload any) (any, error) {
}
var focusBuffer bytes.Buffer
err = fc.Zoom.Execute(&focusBuffer, payload)
err = fc.Focus.Execute(&focusBuffer, templateData)
if err != nil {
return nil, err

View File

@@ -13,7 +13,7 @@ type FreeDDecode struct {
}
func (fdd *FreeDDecode) Process(ctx context.Context, payload any) (any, error) {
payloadBytes, ok := payload.([]byte)
payloadBytes, ok := GetAnyAs[[]byte](payload)
if !ok {
return nil, errors.New("freed.decode processor only accepts a []byte")

View File

@@ -13,7 +13,7 @@ type FreeDEncode struct {
}
func (fde *FreeDEncode) Process(ctx context.Context, payload any) (any, error) {
payloadPosition, ok := payload.(freeD.FreeDPosition)
payloadPosition, ok := GetAnyAs[freeD.FreeDPosition](payload)
if !ok {
return nil, errors.New("freed.decode processor only accepts a FreeDEncode")

View File

@@ -18,8 +18,10 @@ type HTTPRequestCreate struct {
func (hrc *HTTPRequestCreate) Process(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var urlBuffer bytes.Buffer
err := hrc.URL.Execute(&urlBuffer, payload)
err := hrc.URL.Execute(&urlBuffer, templateData)
if err != nil {
return nil, err

View File

@@ -1,42 +0,0 @@
package processor
import (
"context"
"errors"
"io"
"net/http"
"github.com/jwetzell/showbridge-go/internal/config"
)
type HTTPRequestEncode struct {
config config.ProcessorConfig
}
func (hre *HTTPRequestEncode) Process(ctx context.Context, payload any) (any, error) {
payloadRequest, ok := payload.(*http.Request)
if !ok {
return nil, errors.New("http.request.encode processor only accepts an http.Request")
}
bytes, err := io.ReadAll(payloadRequest.Body)
if err != nil {
return nil, err
}
return bytes, nil
}
func (hre *HTTPRequestEncode) Type() string {
return hre.config.Type
}
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "http.request.encode",
New: func(config config.ProcessorConfig) (Processor, error) {
return &HTTPRequestEncode{config: config}, nil
},
})
}

View File

@@ -18,7 +18,7 @@ type HTTPRequestFilter struct {
func (hrf *HTTPRequestFilter) Process(ctx context.Context, payload any) (any, error) {
payloadRequest, ok := payload.(*http.Request)
payloadRequest, ok := GetAnyAs[*http.Request](payload)
if !ok {
return nil, errors.New("http.request.filter can only operate on http.Request payloads")

View File

@@ -21,8 +21,10 @@ type HTTPResponse struct {
}
func (hre *HTTPResponseCreate) Process(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var bodyBuffer bytes.Buffer
err := hre.BodyTmpl.Execute(&bodyBuffer, payload)
err := hre.BodyTmpl.Execute(&bodyBuffer, templateData)
if err != nil {
return nil, err

View File

@@ -1,43 +0,0 @@
package processor
import (
"context"
"errors"
"io"
"net/http"
"github.com/jwetzell/showbridge-go/internal/config"
)
type HTTPResponseEncode struct {
config config.ProcessorConfig
}
func (hre *HTTPResponseEncode) Process(ctx context.Context, payload any) (any, error) {
payloadResponse, ok := payload.(*http.Response)
if !ok {
return nil, errors.New("http.response.encode processor only accepts an http.Response")
}
defer payloadResponse.Body.Close()
bytes, err := io.ReadAll(payloadResponse.Body)
if err != nil {
return nil, err
}
return bytes, nil
}
func (hre *HTTPResponseEncode) Type() string {
return hre.config.Type
}
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "http.response.encode",
New: func(config config.ProcessorConfig) (Processor, error) {
return &HTTPResponseEncode{config: config}, nil
},
})
}

View File

@@ -16,7 +16,7 @@ type IntParse struct {
}
func (ip *IntParse) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
payloadString, ok := GetAnyAs[string](payload)
if !ok {
return nil, errors.New("int.parse processor only accepts a string")

View File

@@ -15,13 +15,13 @@ type IntRandom struct {
config config.ProcessorConfig
}
func (up *IntRandom) Process(ctx context.Context, payload any) (any, error) {
payloadInt := rand.IntN(up.Max-up.Min+1) + up.Min
func (ir *IntRandom) Process(ctx context.Context, payload any) (any, error) {
payloadInt := rand.IntN(ir.Max-ir.Min+1) + ir.Min
return payloadInt, nil
}
func (up *IntRandom) Type() string {
return up.config.Type
func (ir *IntRandom) Type() string {
return ir.config.Type
}
func init() {

View File

@@ -13,7 +13,7 @@ type JsonDecode struct {
}
func (jd *JsonDecode) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
payloadString, ok := GetAnyAs[string](payload)
if !ok {
return nil, errors.New("json.decode processor only accepts a string")

View File

@@ -65,9 +65,10 @@ func newMidiNoteOnCreate(config config.ProcessorConfig) (Processor, error) {
}
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
err := channelTemplate.Execute(&channelBuffer, templateData)
if err != nil {
return nil, err
@@ -76,7 +77,7 @@ func newMidiNoteOnCreate(config config.ProcessorConfig) (Processor, error) {
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var noteBuffer bytes.Buffer
err = noteTemplate.Execute(&noteBuffer, payload)
err = noteTemplate.Execute(&noteBuffer, templateData)
if err != nil {
return nil, err
@@ -85,7 +86,7 @@ func newMidiNoteOnCreate(config config.ProcessorConfig) (Processor, error) {
noteValue, err := strconv.ParseUint(noteBuffer.String(), 10, 8)
var velocityBuffer bytes.Buffer
err = velocityTemplate.Execute(&velocityBuffer, payload)
err = velocityTemplate.Execute(&velocityBuffer, templateData)
if err != nil {
return nil, err
@@ -136,8 +137,10 @@ func newMidiNoteOffCreate(config config.ProcessorConfig) (Processor, error) {
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
err := channelTemplate.Execute(&channelBuffer, templateData)
if err != nil {
return nil, err
@@ -146,7 +149,7 @@ func newMidiNoteOffCreate(config config.ProcessorConfig) (Processor, error) {
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var noteBuffer bytes.Buffer
err = noteTemplate.Execute(&noteBuffer, payload)
err = noteTemplate.Execute(&noteBuffer, templateData)
if err != nil {
return nil, err
@@ -155,7 +158,7 @@ func newMidiNoteOffCreate(config config.ProcessorConfig) (Processor, error) {
noteValue, err := strconv.ParseUint(noteBuffer.String(), 10, 8)
var velocityBuffer bytes.Buffer
err = velocityTemplate.Execute(&velocityBuffer, payload)
err = velocityTemplate.Execute(&velocityBuffer, templateData)
if err != nil {
return nil, err
@@ -207,8 +210,10 @@ func newMidiControlChangeCreate(config config.ProcessorConfig) (Processor, error
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
err := channelTemplate.Execute(&channelBuffer, templateData)
if err != nil {
return nil, err
@@ -217,7 +222,7 @@ func newMidiControlChangeCreate(config config.ProcessorConfig) (Processor, error
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var controlBuffer bytes.Buffer
err = controlTemplate.Execute(&controlBuffer, payload)
err = controlTemplate.Execute(&controlBuffer, templateData)
if err != nil {
return nil, err
@@ -226,7 +231,7 @@ func newMidiControlChangeCreate(config config.ProcessorConfig) (Processor, error
controlValue, err := strconv.ParseUint(controlBuffer.String(), 10, 8)
var valueBuffer bytes.Buffer
err = valueTemplate.Execute(&valueBuffer, payload)
err = valueTemplate.Execute(&valueBuffer, templateData)
if err != nil {
return nil, err
@@ -266,9 +271,10 @@ func newMidiProgramChangeCreate(config config.ProcessorConfig) (Processor, error
}
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
err := channelTemplate.Execute(&channelBuffer, templateData)
if err != nil {
return nil, err
@@ -277,7 +283,7 @@ func newMidiProgramChangeCreate(config config.ProcessorConfig) (Processor, error
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var programBuffer bytes.Buffer
err = programTemplate.Execute(&programBuffer, payload)
err = programTemplate.Execute(&programBuffer, templateData)
if err != nil {
return nil, err

View File

@@ -15,7 +15,7 @@ type MIDIMessageDecode struct {
}
func (mmd *MIDIMessageDecode) Process(ctx context.Context, payload any) (any, error) {
payloadBytes, ok := payload.([]byte)
payloadBytes, ok := GetAnyAs[[]byte](payload)
if !ok {
return nil, errors.New("midi.message.decode processor only accepts a []byte")

View File

@@ -15,7 +15,7 @@ type MIDIMessageEncode struct {
}
func (mme *MIDIMessageEncode) Process(ctx context.Context, payload any) (any, error) {
payloadMessage, ok := payload.(midi.Message)
payloadMessage, ok := GetAnyAs[midi.Message](payload)
if !ok {
return nil, errors.New("midi.message.encode processor only accepts a midi.Message")

View File

@@ -17,7 +17,7 @@ type MIDIMessageFilter struct {
}
func (mmf *MIDIMessageFilter) Process(ctx context.Context, payload any) (any, error) {
payloadMessage, ok := payload.(midi.Message)
payloadMessage, ok := GetAnyAs[midi.Message](payload)
if !ok {
return nil, errors.New("midi.message.filter processor only accepts a midi.Message")

View File

@@ -45,7 +45,7 @@ type MIDIPitchBend struct {
}
func (mmu *MIDIMessageUnpack) Process(ctx context.Context, payload any) (any, error) {
payloadMidi, ok := payload.(midi.Message)
payloadMidi, ok := GetAnyAs[midi.Message](payload)
if !ok {
return nil, errors.New("midi.message.unpack processor only accepts a midi.Message")

View File

@@ -80,8 +80,8 @@ func (mmc *MQTTMessageCreate) Type() string {
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "mqtt.message.create",
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
New: func(processorConfig config.ProcessorConfig) (Processor, error) {
params := processorConfig.Params
topicString, err := params.GetString("topic")
if err != nil {
return nil, fmt.Errorf("mqtt.message.create topic error: %w", err)
@@ -98,25 +98,22 @@ func init() {
}
//TODO(jwetzell): convert payload into []byte or string for sending
payload, ok := params["payload"]
if !ok {
return nil, errors.New("mqtt.message.create payload error: not found")
}
if payloadBytes, ok := payload.([]byte); ok {
return &MQTTMessageCreate{config: config, Topic: topicString, QoS: byte(qosByte), Retained: retainedBool, Payload: payloadBytes}, nil
}
payloadString, ok := payload.(string)
if !ok {
return nil, errors.New("mqtt.message.create payload error: not a string or byte array")
payloadString, err := params.GetString("payload")
if err != nil {
if errors.Is(err, config.ErrParamNotString) {
payloadBytes, err := params.GetByteSlice("payload")
if err != nil {
return nil, fmt.Errorf("mqtt.message.create payload error: %w", err)
}
return &MQTTMessageCreate{config: processorConfig, Topic: topicString, QoS: byte(qosByte), Retained: retainedBool, Payload: payloadBytes}, nil
} else {
return nil, fmt.Errorf("mqtt.message.create payload error: %w", err)
}
}
payloadBytes := []byte(payloadString)
return &MQTTMessageCreate{config: config, Topic: topicString, QoS: byte(qosByte), Retained: retainedBool, Payload: payloadBytes}, nil
return &MQTTMessageCreate{config: processorConfig, Topic: topicString, QoS: byte(qosByte), Retained: retainedBool, Payload: payloadBytes}, nil
},
})
}

View File

@@ -13,7 +13,7 @@ type MQTTMessageEncode struct {
}
func (mme *MQTTMessageEncode) Process(ctx context.Context, payload any) (any, error) {
payloadMessage, ok := payload.(mqtt.Message)
payloadMessage, ok := GetAnyAs[mqtt.Message](payload)
if !ok {
return nil, errors.New("mqtt.message.encode processor only accepts an mqtt.Message")

View File

@@ -21,9 +21,10 @@ type NATSMessageCreate struct {
}
func (nmc *NATSMessageCreate) Process(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var payloadBuffer bytes.Buffer
err := nmc.Payload.Execute(&payloadBuffer, payload)
err := nmc.Payload.Execute(&payloadBuffer, templateData)
if err != nil {
return nil, err
@@ -32,7 +33,7 @@ func (nmc *NATSMessageCreate) Process(ctx context.Context, payload any) (any, er
payloadString := payloadBuffer.String()
var subjectBuffer bytes.Buffer
err = nmc.Subject.Execute(&subjectBuffer, payload)
err = nmc.Subject.Execute(&subjectBuffer, templateData)
if err != nil {
return nil, err

View File

@@ -22,8 +22,10 @@ type OSCMessageCreate struct {
func (omc *OSCMessageCreate) Process(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var addressBuffer bytes.Buffer
err := omc.Address.Execute(&addressBuffer, payload)
err := omc.Address.Execute(&addressBuffer, templateData)
if err != nil {
return nil, err
@@ -39,7 +41,7 @@ func (omc *OSCMessageCreate) Process(ctx context.Context, payload any) (any, err
return nil, errors.New("osc.message.create address must start with '/'")
}
payloadMessage := osc.OSCMessage{
payloadMessage := &osc.OSCMessage{
Address: addressString,
}
@@ -47,7 +49,7 @@ func (omc *OSCMessageCreate) Process(ctx context.Context, payload any) (any, err
for argIndex, argTemplate := range omc.Args {
var argBuffer bytes.Buffer
err := argTemplate.Execute(&argBuffer, payload)
err := argTemplate.Execute(&argBuffer, templateData)
if err != nil {
return nil, err
@@ -78,8 +80,8 @@ func (omc *OSCMessageCreate) Type() string {
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "osc.message.create",
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
New: func(processorConfig config.ProcessorConfig) (Processor, error) {
params := processorConfig.Params
addressString, err := params.GetString("address")
if err != nil {
return nil, fmt.Errorf("osc.message.create address error: %w", err)
@@ -91,43 +93,36 @@ func init() {
return nil, err
}
args, ok := params["args"]
if ok {
rawArgs, ok := args.([]interface{})
if !ok {
return nil, fmt.Errorf("osc.message.create address must be an array found %T", args)
argStrings, err := params.GetStringSlice("args")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
return &OSCMessageCreate{config: processorConfig, Address: addressTemplate}, nil
} else {
return nil, fmt.Errorf("osc.message.create args error: %w", err)
}
typesString, err := params.GetString("types")
if err != nil {
return nil, fmt.Errorf("osc.message.create types error: %w", err)
}
if len(rawArgs) != len(typesString) {
return nil, errors.New("osc.message.create args and types must be the same length")
}
argTemplates := []*template.Template{}
for _, rawArg := range rawArgs {
argString, ok := rawArg.(string)
if !ok {
return nil, errors.New("osc.message.create arg error: not a string")
}
argTemplate, err := template.New("arg").Parse(argString)
if err != nil {
return nil, err
}
argTemplates = append(argTemplates, argTemplate)
}
return &OSCMessageCreate{config: config, Address: addressTemplate, Args: argTemplates, Types: typesString}, nil
}
return &OSCMessageCreate{config: config, Address: addressTemplate}, nil
typesString, err := params.GetString("types")
if err != nil {
return nil, fmt.Errorf("osc.message.create types error: %w", err)
}
if len(argStrings) != len(typesString) {
return nil, errors.New("osc.message.create args and types must be the same length")
}
argTemplates := []*template.Template{}
for _, argString := range argStrings {
argTemplate, err := template.New("arg").Parse(argString)
if err != nil {
return nil, err
}
argTemplates = append(argTemplates, argTemplate)
}
return &OSCMessageCreate{config: processorConfig, Address: addressTemplate, Args: argTemplates, Types: typesString}, nil
},
})
}

View File

@@ -3,6 +3,7 @@ package processor
import (
"context"
"errors"
"fmt"
osc "github.com/jwetzell/osc-go"
"github.com/jwetzell/showbridge-go/internal/config"
@@ -13,7 +14,7 @@ type OSCMessageDecode struct {
}
func (omd *OSCMessageDecode) Process(ctx context.Context, payload any) (any, error) {
payloadBytes, ok := payload.([]byte)
payloadBytes, ok := GetAnyAs[[]byte](payload)
if !ok {
return nil, errors.New("osc.message.decode processor only accepts a []byte payload")
@@ -29,7 +30,7 @@ func (omd *OSCMessageDecode) Process(ctx context.Context, payload any) (any, err
message, err := osc.MessageFromBytes(payloadBytes)
if err != nil {
return nil, err
return nil, fmt.Errorf("osc.message.decode processor failed to decode OSC message: %w", err)
}
return message, nil
}

View File

@@ -13,10 +13,10 @@ type OSCMessageEncode struct {
}
func (ome *OSCMessageEncode) Process(ctx context.Context, payload any) (any, error) {
payloadMessage, ok := payload.(osc.OSCMessage)
payloadMessage, ok := GetAnyAs[*osc.OSCMessage](payload)
if !ok {
return nil, errors.New("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

@@ -18,7 +18,7 @@ type OSCMessageFilter struct {
func (omf *OSCMessageFilter) Process(ctx context.Context, payload any) (any, error) {
payloadMessage, ok := payload.(osc.OSCMessage)
payloadMessage, ok := GetAnyAs[osc.OSCMessage](payload)
if !ok {
return nil, errors.New("osc.message.filter can only operate on OSCMessage payloads")

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"sync"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
)
@@ -40,3 +41,22 @@ var (
processorRegistryMu sync.RWMutex
ProcessorRegistry = make(map[string]ProcessorRegistration)
)
func GetAnyAs[T any](p any) (T, bool) {
typed, ok := p.(T)
return typed, ok
}
type TemplateData struct {
Payload any
Modules any
}
func GetTemplateData(ctx context.Context, payload any) TemplateData {
templateData := TemplateData{Payload: payload}
modules := ctx.Value(common.ModulesContextKey)
if modules != nil {
templateData.Modules = modules
}
return templateData
}

View File

@@ -17,7 +17,9 @@ type ScriptExpr struct {
func (se *ScriptExpr) Process(ctx context.Context, payload any) (any, error) {
output, err := expr.Run(se.Program, payload)
exprEnv := SafeExprEnv(payload)
output, err := expr.Run(se.Program, exprEnv)
if err != nil {
return nil, err
}

View File

@@ -17,7 +17,7 @@ type ScriptWASM struct {
func (se *ScriptWASM) Process(ctx context.Context, payload any) (any, error) {
payloadBytes, ok := payload.([]byte)
payloadBytes, ok := GetAnyAs[[]byte](payload)
if !ok {
return nil, fmt.Errorf("script.wasm can only operator on byte array")

View File

@@ -24,8 +24,10 @@ type SipAudioFileResponse struct {
func (scc *SipResponseAudioCreate) Process(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var audioFileBuffer bytes.Buffer
err := scc.AudioFile.Execute(&audioFileBuffer, payload)
err := scc.AudioFile.Execute(&audioFileBuffer, templateData)
if err != nil {
return nil, err

View File

@@ -27,8 +27,10 @@ type SipDTMFResponse struct {
func (scc *SipResponseDTMFCreate) Process(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var digitsBuffer bytes.Buffer
err := scc.Digits.Execute(&digitsBuffer, payload)
err := scc.Digits.Execute(&digitsBuffer, templateData)
if err != nil {
return nil, err

View File

@@ -15,8 +15,10 @@ type StringCreate struct {
}
func (sc *StringCreate) Process(ctx context.Context, payload any) (any, error) {
templateData := GetTemplateData(ctx, payload)
var templateBuffer bytes.Buffer
err := sc.Template.Execute(&templateBuffer, payload)
err := sc.Template.Execute(&templateBuffer, templateData)
if err != nil {
return nil, err

View File

@@ -12,7 +12,7 @@ type StringDecode struct {
}
func (sd *StringDecode) Process(ctx context.Context, payload any) (any, error) {
payloadBytes, ok := payload.([]byte)
payloadBytes, ok := GetAnyAs[[]byte](payload)
if !ok {
return nil, errors.New("string.decode processor only accepts a []byte")

View File

@@ -12,7 +12,7 @@ type StringEncode struct {
}
func (se *StringEncode) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
payloadString, ok := GetAnyAs[string](payload)
if !ok {
return nil, errors.New("string.encode processor only accepts a string")

View File

@@ -15,7 +15,7 @@ type StringFilter struct {
}
func (sf *StringFilter) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
payloadString, ok := GetAnyAs[string](payload)
if !ok {
return nil, errors.New("string.filter processor only accepts a string")

View File

@@ -15,7 +15,7 @@ type StringSplit struct {
}
func (ss *StringSplit) Process(ctx context.Context, payload any) (any, error) {
payloadString, ok := payload.(string)
payloadString, ok := GetAnyAs[string](payload)
if !ok {
return nil, errors.New("string.split only accepts a string")

View File

@@ -1,13 +1,15 @@
package processor_test
import (
"reflect"
"testing"
"github.com/jwetzell/artnet-go"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestArtnetPacketCreateFromRegistry(t *testing.T) {
func TestArtnetPacketDecodeFromRegistry(t *testing.T) {
registration, ok := processor.ProcessorRegistry["artnet.packet.decode"]
if !ok {
t.Fatalf("artnet.packet.decode processor not registered")
@@ -25,3 +27,81 @@ func TestArtnetPacketCreateFromRegistry(t *testing.T) {
t.Fatalf("artnet.packet.decode processor has wrong type: %s", processorInstance.Type())
}
}
func TestGoodArtnetPacketDecode(t *testing.T) {
packetDecoder := processor.ArtNetPacketDecode{}
tests := []struct {
name string
payload any
expected artnet.ArtNetPacket
}{
{
name: "number",
payload: []byte{65, 114, 116, 45, 78, 101, 116, 0, 0, 80, 0, 14, 237, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
expected: &artnet.ArtDmx{
ID: []byte{'A', 'r', 't', '-', 'N', 'e', 't', 0x00},
OpCode: artnet.OpDmx,
ProtVerHi: 0,
ProtVerLo: 14,
Sequence: 237,
Physical: 0,
SubUni: 1,
Net: 0,
Length: 512,
Data: make([]uint8, 512),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := packetDecoder.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("artnet.packet.decode processing failed: %s", err)
}
//TODO(jwetzell): work out better way to compare the any/any
if !reflect.DeepEqual(got, test.expected) {
t.Fatalf("artnet.packet.decode got %+v (%T), expected %+v (%T)", got, got, test.expected, test.expected)
}
})
}
}
func TestBadArtnetPacketDecode(t *testing.T) {
packetDecoder := processor.ArtNetPacketDecode{}
tests := []struct {
name string
payload any
errorString string
}{
{
name: "not a byte slice",
payload: "not a byte slice",
errorString: "artnet.packet.decode processor only accepts a []byte",
},
{
name: "not enough bytes",
payload: []byte{1, 2, 3},
errorString: "ArtNet packet must be at least 12 bytes",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := packetDecoder.Process(t.Context(), test.payload)
if err == nil {
t.Fatalf("artnet.packet.decode expected to fail but succeeded, got: %v", got)
}
if err.Error() != test.errorString {
t.Fatalf("artnet.packet.decode got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}

View File

@@ -1,8 +1,10 @@
package processor_test
import (
"reflect"
"testing"
"github.com/jwetzell/artnet-go"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
@@ -25,3 +27,76 @@ func TestArtnetPacketEncodeFromRegistry(t *testing.T) {
t.Fatalf("artnet.packet.encode processor has wrong type: %s", processorInstance.Type())
}
}
func TestGoodArtnetPacketEncode(t *testing.T) {
packetEncoder := processor.ArtNetPacketEncode{}
tests := []struct {
name string
expected []byte
payload any
}{
{
name: "number",
expected: []byte{65, 114, 116, 45, 78, 101, 116, 0, 0, 80, 0, 14, 237, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
payload: &artnet.ArtDmx{
ID: []byte{'A', 'r', 't', '-', 'N', 'e', 't', 0x00},
OpCode: artnet.OpDmx,
ProtVerHi: 0,
ProtVerLo: 14,
Sequence: 237,
Physical: 0,
SubUni: 1,
Net: 0,
Length: 512,
Data: make([]uint8, 512),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := packetEncoder.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("artnet.packet.encode processing failed: %s", err)
}
//TODO(jwetzell): work out better way to compare the any/any
if !reflect.DeepEqual(got, test.expected) {
t.Fatalf("artnet.packet.encode got %+v (%T), expected %+v (%T)", got, got, test.expected, test.expected)
}
})
}
}
func TestBadArtnetPacketEncode(t *testing.T) {
packetEncoder := processor.ArtNetPacketEncode{}
tests := []struct {
name string
payload any
errorString string
}{
{
name: "not an ArtNet packet",
payload: "test",
errorString: "artnet.packet.encode processor only accepts an ArtNetPacket",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := packetEncoder.Process(t.Context(), test.payload)
if err == nil {
t.Fatalf("artnet.packet.encode expected to fail but succeeded, got: %v", got)
}
if err.Error() != test.errorString {
t.Fatalf("artnet.packet.encode got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}

View File

@@ -148,7 +148,7 @@ func TestGoodArtnetPacketFilter(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("artnet.packet.filter failed: %s", err)
t.Fatalf("artnet.packet.filter processing failed: %s", err)
}
if test.expected == nil {

View File

@@ -0,0 +1,176 @@
package processor_test
import (
"reflect"
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestFilterExprFromRegistry(t *testing.T) {
registration, ok := processor.ProcessorRegistry["filter.expr"]
if !ok {
t.Fatalf("filter.expr processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "filter.expr",
Params: map[string]any{
"expression": "foo + bar",
},
})
if err != nil {
t.Fatalf("failed to create filter.expr processor: %s", err)
}
if processorInstance.Type() != "filter.expr" {
t.Fatalf("filter.expr processor has wrong type: %s", processorInstance.Type())
}
}
func TestGoodFilterExpr(t *testing.T) {
tests := []struct {
name string
params map[string]any
payload any
expected any
}{
{
name: "number",
params: map[string]any{
"expression": "Payload.Int > 0",
},
payload: TestStruct{
Int: 1,
},
expected: TestStruct{
Int: 1,
},
},
{
name: "string",
params: map[string]any{
"expression": "Payload.String == 'hello'",
},
payload: TestStruct{
String: "hello",
},
expected: TestStruct{
String: "hello",
},
},
{
name: "not matching",
params: map[string]any{
"expression": "Payload.Int > 0",
},
payload: TestStruct{
Int: 0,
},
expected: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.ProcessorRegistry["filter.expr"]
if !ok {
t.Fatalf("filter.expr processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "filter.expr",
Params: test.params,
})
if err != nil {
t.Fatalf("filter.expr failed to create processor: %s", err)
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("filter.expr processing failed: %s", err)
}
//TODO(jwetzell): work out better way to compare the any/any
if !reflect.DeepEqual(got, test.expected) {
t.Fatalf("filter.expr got %+v (%T), expected %+v (%T)", got, got, test.expected, test.expected)
}
})
}
}
func TestBadFilterExpr(t *testing.T) {
tests := []struct {
name string
params map[string]any
payload any
errorString string
}{
{
name: "no expression parameter",
params: map[string]any{
// no expression parameter
},
payload: TestStruct{},
errorString: "filter.expr expression error: not found",
},
{
name: "non-string expression parameter",
params: map[string]any{
"expression": 12345,
},
payload: TestStruct{},
errorString: "filter.expr expression error: not a string",
},
{
name: "invalid expression",
params: map[string]any{
"expression": "foo +",
},
payload: TestStruct{},
errorString: "unexpected token EOF (1:5)\n | foo +\n | ....^",
},
{
name: "accessing missing field",
params: map[string]any{
"expression": "Payload.foo + Payload.bar",
},
payload: map[string]any{
"foo": 1,
},
errorString: "invalid operation: int + <nil> (1:13)\n | Payload.foo + Payload.bar\n | ............^",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.ProcessorRegistry["filter.expr"]
if !ok {
t.Fatalf("filter.expr processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "filter.expr",
Params: test.params,
})
if err != nil {
if err.Error() != test.errorString {
t.Fatalf("filter.expr got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err == nil {
t.Fatalf("filter.expr expected to fail but succeeded, got: %v", got)
}
if err.Error() != test.errorString {
t.Fatalf("filter.expr got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}

View File

@@ -26,59 +26,6 @@ func TestFloatParseFromRegistry(t *testing.T) {
}
}
func TestFloatParseBadConfigBitSizeString(t *testing.T) {
registration, ok := processor.ProcessorRegistry["float.parse"]
if !ok {
t.Fatalf("float.parse processor not registered")
}
_, err := registration.New(config.ProcessorConfig{
Type: "float.parse",
Params: map[string]any{
"bitSize": "64",
},
})
if err == nil {
t.Fatalf("float.parse should have returned an error for bad bitSize config")
}
}
func TestFloatParseGoodConfig(t *testing.T) {
registration, ok := processor.ProcessorRegistry["float.parse"]
if !ok {
t.Fatalf("float.parse processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "float.parse",
Params: map[string]any{
"bitSize": 64.0,
},
})
if err != nil {
t.Fatalf("float.parse should have created processor but got error: %s", err)
}
payload := "12345.0"
expected := float64(12345.0)
got, err := processorInstance.Process(t.Context(), payload)
if err != nil {
t.Fatalf("float.parse processing failed: %s", err)
}
gotFloat, ok := got.(float64)
if !ok {
t.Fatalf("float.parse returned a %T payload: %s", got, got)
}
if gotFloat != expected {
t.Fatalf("float.parse got %f, expected %f", gotFloat, expected)
}
}
func TestGoodFloatParse(t *testing.T) {
tests := []struct {
name string
@@ -89,7 +36,7 @@ func TestGoodFloatParse(t *testing.T) {
{
name: "positive number",
params: map[string]any{
"bitSize": 64.0,
"bitSize": 64,
},
payload: "12345.67",
expected: 12345.67,
@@ -97,7 +44,7 @@ func TestGoodFloatParse(t *testing.T) {
{
name: "negative number",
params: map[string]any{
"bitSize": 64.0,
"bitSize": 64,
},
payload: "-12345.67",
expected: -12345.67,
@@ -105,7 +52,7 @@ func TestGoodFloatParse(t *testing.T) {
{
name: "zero",
params: map[string]any{
"bitSize": 64.0,
"bitSize": 64,
},
payload: "0",
expected: 0,
@@ -129,14 +76,14 @@ func TestGoodFloatParse(t *testing.T) {
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("float.parse processing failed: %s", err)
}
gotFloat, ok := got.(float64)
if !ok {
t.Fatalf("float.parse returned a %T payload: %s", got, got)
}
if err != nil {
t.Fatalf("float.parse failed: %s", err)
}
if gotFloat != test.expected {
t.Fatalf("float.parse got %f, expected %f", gotFloat, test.expected)
}
@@ -151,10 +98,18 @@ func TestBadFloatParse(t *testing.T) {
payload any
errorString string
}{
{
name: "non-string bitSize",
params: map[string]any{
"bitSize": "32",
},
payload: "1.23",
errorString: "float.parse bitSize error: not a number",
},
{
name: "non-string input",
params: map[string]any{
"bitSize": 64.0,
"bitSize": 64,
},
payload: []byte{0x01},
errorString: "float.parse processor only accepts a string",
@@ -162,7 +117,7 @@ func TestBadFloatParse(t *testing.T) {
{
name: "not float string",
params: map[string]any{
"bitSize": 64.0,
"bitSize": 64,
},
payload: "abcd",
errorString: "strconv.ParseFloat: parsing \"abcd\": invalid syntax",
@@ -170,7 +125,7 @@ func TestBadFloatParse(t *testing.T) {
{
name: "bit size overflow",
params: map[string]any{
"bitSize": 32.0,
"bitSize": 32,
},
payload: "1.79e+64",
errorString: "strconv.ParseFloat: parsing \"1.79e+64\": value out of range",
@@ -189,6 +144,13 @@ func TestBadFloatParse(t *testing.T) {
Params: test.params,
})
if err != nil {
if err.Error() != test.errorString {
t.Fatalf("float.parse got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err == nil {

View File

@@ -0,0 +1,185 @@
package processor_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestFloatRandomFromRegistry(t *testing.T) {
registration, ok := processor.ProcessorRegistry["float.random"]
if !ok {
t.Fatalf("float.random processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "float.random",
Params: map[string]any{
"min": 1.0,
"max": 10.0,
},
})
if err != nil {
t.Fatalf("failed to create float.random processor: %s", err)
}
if processorInstance.Type() != "float.random" {
t.Fatalf("float.random processor has wrong type: %s", processorInstance.Type())
}
}
func TestGoodFloatRandom(t *testing.T) {
tests := []struct {
name string
payload any
params map[string]any
}{
{
name: "1-10",
params: map[string]any{
"min": 1.0,
"max": 10.0,
},
payload: "12345",
},
{
name: "1-1",
params: map[string]any{
"min": 1.0,
"max": 1.0,
},
payload: "12345",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.ProcessorRegistry["float.random"]
if !ok {
t.Fatalf("float.random processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "float.random",
Params: test.params,
})
if err != nil {
t.Fatalf("float.random failed to create processor: %s", err)
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("float.random processing failed: %s", err)
}
bitSize, ok := test.params["bitSize"].(int)
if !ok {
bitSize = 32
}
var gotFloat float64
if bitSize == 32 {
gotFloat32, ok := got.(float32)
if !ok {
t.Fatalf("float.random returned a %T payload: %s", got, got)
}
gotFloat = float64(gotFloat32)
}
if bitSize == 64 {
gotFloat64, ok := got.(float64)
if !ok {
t.Fatalf("float.random returned a %T payload: %s", got, got)
}
gotFloat = gotFloat64
}
minNum, ok := test.params["min"].(float64)
if !ok {
t.Fatalf("float.random test min param is not a number: %s", test.params["min"])
}
maxNum, ok := test.params["max"].(float64)
if !ok {
t.Fatalf("float.random test max param is not a number: %s", test.params["max"])
}
if gotFloat < minNum || gotFloat > maxNum {
t.Fatalf("float.random got %f, expected between %f and %f", gotFloat, minNum, maxNum)
}
})
}
}
func TestBadFloatRandom(t *testing.T) {
tests := []struct {
name string
params map[string]any
payload any
errorString string
}{
{
name: "no min param",
payload: "hello",
params: map[string]any{"max": 10.0},
errorString: "float.random min error: not found",
},
{
name: "no max param",
payload: "hello",
params: map[string]any{"min": 1.0},
errorString: "float.random max error: not found",
},
{
name: "min param not a number",
payload: "hello",
params: map[string]any{"min": "1", "max": 10.0},
errorString: "float.random min error: not a number",
},
{
name: "max param not a number",
payload: "hello",
params: map[string]any{"min": 1.0, "max": "10"},
errorString: "float.random max error: not a number",
},
{
name: "max less than min",
payload: "hello",
params: map[string]any{"min": 1.0, "max": 0.0},
errorString: "float.random max must be greater than min",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.ProcessorRegistry["float.random"]
if !ok {
t.Fatalf("float.random processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "float.random",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("float.random got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err == nil {
t.Fatalf("float.random expected to fail but got payload: %s", got)
}
if err.Error() != test.errorString {
t.Fatalf("float.random got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}

View File

@@ -1,27 +0,0 @@
package processor_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestHTTPRequestEncodeFromRegistry(t *testing.T) {
registration, ok := processor.ProcessorRegistry["http.request.encode"]
if !ok {
t.Fatalf("http.request.encode processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "http.request.encode",
})
if err != nil {
t.Fatalf("failed to create http.request.encode processor: %s", err)
}
if processorInstance.Type() != "http.request.encode" {
t.Fatalf("http.request.encode processor has wrong type: %s", processorInstance.Type())
}
}

View File

@@ -16,7 +16,7 @@ func TestHTTPResponseCreateFromRegistry(t *testing.T) {
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "http.response.create",
Params: map[string]any{
"status": 200.0,
"status": 200,
"bodyTemplate": "Hello, World!",
},
})

View File

@@ -1,27 +0,0 @@
package processor_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestHTTPResponseEncodeFromRegistry(t *testing.T) {
registration, ok := processor.ProcessorRegistry["http.response.encode"]
if !ok {
t.Fatalf("http.response.encode processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "http.response.encode",
})
if err != nil {
t.Fatalf("failed to create http.response.encode processor: %s", err)
}
if processorInstance.Type() != "http.response.encode" {
t.Fatalf("http.response.encode processor has wrong type: %s", processorInstance.Type())
}
}

View File

@@ -26,78 +26,6 @@ func TestIntParseFromRegistry(t *testing.T) {
}
}
func TestIntParseBadConfigBaseString(t *testing.T) {
registration, ok := processor.ProcessorRegistry["int.parse"]
if !ok {
t.Fatalf("int.parse processor not registered")
}
_, err := registration.New(config.ProcessorConfig{
Type: "int.parse",
Params: map[string]any{
"base": "10",
},
})
if err == nil {
t.Fatalf("int.parse should have returned an error for bad base config")
}
}
func TestIntParseBadConfigBitSizeString(t *testing.T) {
registration, ok := processor.ProcessorRegistry["int.parse"]
if !ok {
t.Fatalf("int.parse processor not registered")
}
_, err := registration.New(config.ProcessorConfig{
Type: "int.parse",
Params: map[string]any{
"bitSize": "64",
},
})
if err == nil {
t.Fatalf("int.parse should have returned an error for bad bitSize config")
}
}
func TestIntParseGoodConfig(t *testing.T) {
registration, ok := processor.ProcessorRegistry["int.parse"]
if !ok {
t.Fatalf("int.parse processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "int.parse",
Params: map[string]any{
"base": 10.0,
"bitSize": 64.0,
},
})
if err != nil {
t.Fatalf("int.parse should have created processor but got error: %s", err)
}
payload := "12345"
expected := int64(12345)
got, err := processorInstance.Process(t.Context(), payload)
if err != nil {
t.Fatalf("int.parse processing failed: %s", err)
}
gotInt, ok := got.(int64)
if !ok {
t.Fatalf("int.parse returned a %T payload: %s", got, got)
}
if gotInt != expected {
t.Fatalf("int.parse got %d, expected %d", gotInt, expected)
}
}
func TestGoodIntParse(t *testing.T) {
tests := []struct {
name string
@@ -108,8 +36,8 @@ func TestGoodIntParse(t *testing.T) {
{
name: "positive number",
params: map[string]any{
"base": 10.0,
"bitSize": 64.0,
"base": 10,
"bitSize": 64,
},
payload: "12345",
expected: 12345,
@@ -117,8 +45,8 @@ func TestGoodIntParse(t *testing.T) {
{
name: "negative number",
params: map[string]any{
"base": 10.0,
"bitSize": 64.0,
"base": 10,
"bitSize": 64,
},
payload: "-12345",
expected: -12345,
@@ -126,8 +54,8 @@ func TestGoodIntParse(t *testing.T) {
{
name: "zero",
params: map[string]any{
"base": 10.0,
"bitSize": 64.0,
"base": 10,
"bitSize": 64,
},
payload: "0",
expected: 0,
@@ -135,8 +63,8 @@ func TestGoodIntParse(t *testing.T) {
{
name: "binary",
params: map[string]any{
"base": 2.0,
"bitSize": 64.0,
"base": 2,
"bitSize": 64,
},
payload: "1010101",
expected: 85,
@@ -144,8 +72,8 @@ func TestGoodIntParse(t *testing.T) {
{
name: "hex",
params: map[string]any{
"base": 16.0,
"bitSize": 64.0,
"base": 16,
"bitSize": 64,
},
payload: "15F",
expected: 351,
@@ -169,14 +97,14 @@ func TestGoodIntParse(t *testing.T) {
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("int.parse processing failed: %s", err)
}
gotInt, ok := got.(int64)
if !ok {
t.Fatalf("int.parse returned a %T payload: %s", got, got)
}
if err != nil {
t.Fatalf("int.parse failed: %s", err)
}
if gotInt != test.expected {
t.Fatalf("int.parse got %d, expected %d", gotInt, test.expected)
}
@@ -191,11 +119,29 @@ func TestBadIntParse(t *testing.T) {
payload any
errorString string
}{
{
name: "non-string base",
params: map[string]any{
"base": "10",
"bitSize": 64,
},
payload: "12345",
errorString: "int.parse base error: not a number",
},
{
name: "non-string bitSize",
params: map[string]any{
"base": 10,
"bitSize": "64",
},
payload: "12345",
errorString: "int.parse bitSize error: not a number",
},
{
name: "non-string input",
params: map[string]any{
"base": 10.0,
"bitSize": 64.0,
"base": 10,
"bitSize": 64,
},
payload: []byte{0x01},
errorString: "int.parse processor only accepts a string",
@@ -203,8 +149,8 @@ func TestBadIntParse(t *testing.T) {
{
name: "not int string",
params: map[string]any{
"base": 10.0,
"bitSize": 64.0,
"base": 10,
"bitSize": 64,
},
payload: "123.46",
errorString: "strconv.ParseInt: parsing \"123.46\": invalid syntax",
@@ -212,8 +158,8 @@ func TestBadIntParse(t *testing.T) {
{
name: "bit overflow",
params: map[string]any{
"base": 10.0,
"bitSize": 32.0,
"base": 10,
"bitSize": 32,
},
payload: "12345678901234567890",
errorString: "strconv.ParseInt: parsing \"12345678901234567890\": value out of range",
@@ -232,6 +178,13 @@ func TestBadIntParse(t *testing.T) {
Params: test.params,
})
if err != nil {
if err.Error() != test.errorString {
t.Fatalf("int.parse got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err == nil {

View File

@@ -16,8 +16,8 @@ func TestIntRandomFromRegistry(t *testing.T) {
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "int.random",
Params: map[string]any{
"min": 1.0,
"max": 10.0,
"min": 1,
"max": 10,
},
})
@@ -39,8 +39,8 @@ func TestIntRandomGoodConfig(t *testing.T) {
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "int.random",
Params: map[string]any{
"min": 1.0,
"max": 10.0,
"min": 1,
"max": 10,
},
})
@@ -74,8 +74,8 @@ func TestGoodIntRandom(t *testing.T) {
{
name: "1-10",
params: map[string]any{
"min": 1.0,
"max": 10.0,
"min": 1,
"max": 10,
},
payload: "12345",
},
@@ -98,19 +98,20 @@ func TestGoodIntRandom(t *testing.T) {
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("int.random processing failed: %s", err)
}
gotInt, ok := got.(int)
if !ok {
t.Fatalf("int.random returned a %T payload: %s", got, got)
}
if err != nil {
t.Fatalf("int.random failed: %s", err)
}
minNum, ok := test.params["min"].(float64)
minNum, ok := test.params["min"].(int)
if !ok {
t.Fatalf("int.random test min param is not a number: %s", test.params["min"])
}
maxNum, ok := test.params["max"].(float64)
maxNum, ok := test.params["max"].(int)
if !ok {
t.Fatalf("int.random test max param is not a number: %s", test.params["max"])
}
@@ -132,31 +133,31 @@ func TestBadIntRandom(t *testing.T) {
{
name: "no min param",
payload: "hello",
params: map[string]any{"max": 10.0},
params: map[string]any{"max": 10},
errorString: "int.random min error: not found",
},
{
name: "no max param",
payload: "hello",
params: map[string]any{"min": 1.0},
params: map[string]any{"min": 1},
errorString: "int.random max error: not found",
},
{
name: "min param not a number",
payload: "hello",
params: map[string]any{"min": "1", "max": 10.0},
params: map[string]any{"min": "1", "max": 10},
errorString: "int.random min error: not a number",
},
{
name: "max param not a number",
payload: "hello",
params: map[string]any{"min": 1.0, "max": "10"},
params: map[string]any{"min": 1, "max": "10"},
errorString: "int.random max error: not a number",
},
{
name: "max less than min",
payload: "hello",
params: map[string]any{"min": 1.0, "max": 0.0},
params: map[string]any{"min": 1, "max": 0},
errorString: "int.random max must be greater than min",
},
}

View File

@@ -75,14 +75,15 @@ func TestGoodJsonDecode(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := jsonDecoder.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("json.decode processing failed: %s", err)
}
gotMap, ok := got.(map[string]any)
if !ok {
t.Fatalf("json.decode returned a %T payload: %s", got, got)
}
if err != nil {
t.Fatalf("json.decode failed: %s", err)
}
if !reflect.DeepEqual(gotMap, test.expected) {
t.Fatalf("json.decode got %x, expected %s", got, test.expected)
}

View File

@@ -69,14 +69,15 @@ func TestGoodJsonEncode(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := jsonEncoder.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("json.encode processing failed: %s", err)
}
gotBytes, ok := got.([]byte)
if !ok {
t.Fatalf("json.encode returned a %T payload: %s", got, got)
}
if err != nil {
t.Fatalf("json.encode failed: %s", err)
}
if !slices.Equal(gotBytes, test.expected) {
t.Fatalf("json.encode got %x, expected %s", got, test.expected)
}

View File

@@ -1,10 +1,12 @@
package processor_test
import (
"reflect"
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"gitlab.com/gomidi/midi/v2"
)
func TestMIDIMessageCreateFromRegistry(t *testing.T) {
@@ -31,3 +33,261 @@ func TestMIDIMessageCreateFromRegistry(t *testing.T) {
t.Fatalf("midi.message.create processor has wrong type: %s", processorInstance.Type())
}
}
func TestGoodMIDIMessageCreate(t *testing.T) {
tests := []struct {
name string
params map[string]any
payload any
expected any
}{
{
name: "note_on message",
params: map[string]any{
"type": "note_on",
"channel": "1",
"note": "60",
"velocity": "100",
},
payload: "test",
expected: midi.NoteOn(1, 60, 100),
},
{
name: "note_off message",
params: map[string]any{
"type": "note_off",
"channel": "1",
"note": "60",
"velocity": "100",
},
payload: "test",
expected: midi.NoteOffVelocity(1, 60, 100),
},
{
name: "control_change message",
params: map[string]any{
"type": "control_change",
"channel": "1",
"control": "64",
"value": "127",
},
payload: "test",
expected: midi.ControlChange(1, 64, 127),
},
{
name: "program_change message",
params: map[string]any{
"type": "program_change",
"channel": "1",
"program": "10",
},
payload: "test",
expected: midi.ProgramChange(1, 10),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.ProcessorRegistry["midi.message.create"]
if !ok {
t.Fatalf("midi.message.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "midi.message.create",
Params: test.params,
})
if err != nil {
t.Fatalf("midi.message.create failed to create processor: %s", err)
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("midi.message.create processing failed: %s", err)
}
gotMessage, ok := got.(midi.Message)
if !ok {
t.Fatalf("midi.message.create returned a %T payload: %s", got, got)
}
if !reflect.DeepEqual(gotMessage, test.expected) {
t.Fatalf("midi.message.create got %v, expected %v", gotMessage, test.expected)
}
})
}
}
func TestBadMIDIMessageCreate(t *testing.T) {
tests := []struct {
name string
params map[string]any
payload any
errorString string
}{
{
name: "no type parameter",
params: map[string]any{},
payload: "test",
errorString: "midi.message.create type error: not found",
},
{
name: "non-string type parameter",
params: map[string]any{
"type": 1,
},
payload: "test",
errorString: "midi.message.create type error: not a string",
},
{
name: "unknown type parameter",
params: map[string]any{
"type": "asdf",
},
payload: "test",
errorString: "midi.message.create does not support type asdf",
},
{
name: "note_on message no channel",
params: map[string]any{
"type": "note_on",
"note": "60",
"velocity": "100",
},
payload: "test",
errorString: "midi.message.create channel error: not found",
},
{
name: "note_on message no note",
params: map[string]any{
"type": "note_on",
"channel": "1",
"velocity": "100",
},
payload: "test",
errorString: "midi.message.create note error: not found",
},
{
name: "note_on message no velocity",
params: map[string]any{
"type": "note_on",
"channel": "1",
"note": "60",
},
payload: "test",
errorString: "midi.message.create velocity error: not found",
},
{
name: "note_off message no channel",
params: map[string]any{
"type": "note_off",
"note": "60",
"velocity": "100",
},
payload: "test",
errorString: "midi.message.create channel error: not found",
},
{
name: "note_off message no note",
params: map[string]any{
"type": "note_off",
"channel": "1",
"velocity": "100",
},
payload: "test",
errorString: "midi.message.create note error: not found",
},
{
name: "note_off message no velocity",
params: map[string]any{
"type": "note_off",
"channel": "1",
"note": "60",
},
payload: "test",
errorString: "midi.message.create velocity error: not found",
},
{
name: "control_change no channel",
params: map[string]any{
"type": "control_change",
"control": "64",
"value": "127",
},
payload: "test",
errorString: "midi.message.create channel error: not found",
},
{
name: "control_change no control",
params: map[string]any{
"type": "control_change",
"channel": "1",
"value": "127",
},
payload: "test",
errorString: "midi.message.create control error: not found",
},
{
name: "control_change no value",
params: map[string]any{
"type": "control_change",
"channel": "1",
"control": "64",
},
payload: "test",
errorString: "midi.message.create value error: not found",
},
{
name: "program_change no channel",
params: map[string]any{
"type": "program_change",
"program": "64",
},
payload: "test",
errorString: "midi.message.create channel error: not found",
},
{
name: "program_change no program",
params: map[string]any{
"type": "program_change",
"channel": "1",
},
payload: "test",
errorString: "midi.message.create program error: not found",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.ProcessorRegistry["midi.message.create"]
if !ok {
t.Fatalf("midi.message.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "midi.message.create",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("string.create got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err == nil {
t.Fatalf("midi.message.create expected to fail but succeeded, got: %v", got)
}
if err.Error() != test.errorString {
t.Fatalf("midi.message.create got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}

View File

@@ -1,10 +1,12 @@
package processor_test
import (
"reflect"
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"gitlab.com/gomidi/midi/v2"
)
func TestMIDIMessageDecodeFromRegistry(t *testing.T) {
@@ -25,3 +27,66 @@ func TestMIDIMessageDecodeFromRegistry(t *testing.T) {
t.Fatalf("midi.message.decode processor has wrong type: %s", processorInstance.Type())
}
}
func TestGoodMIDIMessageDecode(t *testing.T) {
processorInstance := &processor.MIDIMessageDecode{}
tests := []struct {
name string
payload any
expected any
}{
{
name: "note on message",
payload: []byte{0x90, 0x40, 0x7F},
expected: midi.NoteOn(0, 64, 127),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("midi.message.decode processing failed: %s", err)
}
gotMessage, ok := got.(midi.Message)
if !ok {
t.Fatalf("midi.message.decode returned a %T payload: %s", got, got)
}
if !reflect.DeepEqual(gotMessage, test.expected) {
t.Fatalf("midi.message.decode got %+v, expected %+v", gotMessage, test.expected)
}
})
}
}
func TestBadMIDIMessageDecode(t *testing.T) {
processorInstance := &processor.MIDIMessageDecode{}
tests := []struct {
name string
payload any
errorString string
}{
{
name: "non-byte slice payload",
payload: "12345",
errorString: "midi.message.decode processor only accepts a []byte",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err == nil {
t.Fatalf("midi.message.decode expected to fail but succeeded, got: %v", got)
}
if err.Error() != test.errorString {
t.Fatalf("midi.message.decode got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}

View File

@@ -45,14 +45,15 @@ func TestGoodMIDIMessageEncode(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := midiMessageEncoder.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("midi.message.encode processing failed: %s", err)
}
gotBytes, ok := got.([]byte)
if !ok {
t.Fatalf("midi.message.encode returned a %T payload: %s", got, got)
}
if err != nil {
t.Fatalf("midi.message.encode failed: %s", err)
}
if !slices.Equal(gotBytes, test.expected) {
t.Fatalf("midi.message.encode got %+v, expected %+v", got, test.expected)
}

View File

@@ -71,7 +71,7 @@ func TestGoodMIDIMessageFilter(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("midi.message.filter failed: %s", err)
t.Fatalf("midi.message.filter processing failed: %s", err)
}
if test.expected == nil {

View File

@@ -1,8 +1,10 @@
package processor_test
import (
"reflect"
"testing"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
@@ -18,7 +20,7 @@ func TestMQTTMessageCreateFromRegistry(t *testing.T) {
Params: map[string]any{
"topic": "test/topic",
"payload": "Hello, World!",
"qos": 1.0,
"qos": 1,
"retained": true,
},
})
@@ -31,3 +33,209 @@ func TestMQTTMessageCreateFromRegistry(t *testing.T) {
t.Fatalf("mqtt.message.create processor has wrong type: %s", processorInstance.Type())
}
}
func TestGoodMQTTMessageCreate(t *testing.T) {
tests := []struct {
name string
payload any
params map[string]any
expected any
}{
{
name: "basic topic and string payload",
params: map[string]any{
"topic": "test/topic",
"payload": "Hello, World!",
"qos": 1,
"retained": true,
},
payload: "test",
expected: processor.NewMQTTMessage("test/topic", 1, true, []byte("Hello, World!")),
},
{
name: "basic topic and []byte payload",
params: map[string]any{
"topic": "test/topic",
"payload": []byte{72, 101, 108, 108, 111},
"qos": 1,
"retained": true,
},
payload: "test",
expected: processor.NewMQTTMessage("test/topic", 1, true, []byte("Hello")),
},
{
name: "basic topic and []int payload",
params: map[string]any{
"topic": "test/topic",
"payload": []int{72, 101, 108, 108, 111},
"qos": 1,
"retained": true,
},
payload: "test",
expected: processor.NewMQTTMessage("test/topic", 1, true, []byte("Hello")),
},
{
name: "basic topic and []uint payload",
params: map[string]any{
"topic": "test/topic",
"payload": []uint{72, 101, 108, 108, 111},
"qos": 1,
"retained": true,
},
payload: "test",
expected: processor.NewMQTTMessage("test/topic", 1, true, []byte("Hello")),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.ProcessorRegistry["mqtt.message.create"]
if !ok {
t.Fatalf("mqtt.message.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "mqtt.message.create",
Params: test.params,
})
if err != nil {
t.Fatalf("mqtt.message.create failed to create processor: %s", err)
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("mqtt.message.create processing failed: %s", err)
}
if test.expected == nil {
if got != nil {
t.Fatalf("mqtt.message.create got %+v, expected nil", got)
}
return
}
gotMessage, ok := got.(mqtt.Message)
if !ok {
t.Fatalf("mqtt.message.create returned a %T payload: %s", got, got)
}
if !reflect.DeepEqual(gotMessage, test.expected) {
t.Fatalf("mqtt.message.create got %+v, expected %+v", gotMessage, test.expected)
}
})
}
}
func TestBadMQTTMessageCreate(t *testing.T) {
tests := []struct {
name string
params map[string]any
payload any
errorString string
}{
{
name: "no topic parameter",
params: map[string]any{},
payload: "test",
errorString: "mqtt.message.create topic error: not found",
},
{
name: "non-string topic parameter",
params: map[string]any{
"topic": 1,
},
payload: "test",
errorString: "mqtt.message.create topic error: not a string",
},
{
name: "no qos parameter",
params: map[string]any{
"topic": "test/topic",
},
payload: "test",
errorString: "mqtt.message.create qos error: not found",
},
{
name: "non-number qos parameter",
params: map[string]any{
"topic": "test/topic",
"qos": "1",
},
payload: "test",
errorString: "mqtt.message.create qos error: not a number",
},
{
name: "no retained parameter",
params: map[string]any{
"topic": "test/topic",
"qos": 1,
},
payload: "test",
errorString: "mqtt.message.create retained error: not found",
},
{
name: "non-bool retained parameter",
params: map[string]any{
"topic": "test/topic",
"qos": 1,
"retained": "1",
},
payload: "test",
errorString: "mqtt.message.create retained error: not a boolean",
},
{
name: "no payload parameter",
params: map[string]any{
"topic": "test/topic",
"qos": 1,
"retained": true,
},
payload: "test",
errorString: "mqtt.message.create payload error: not found",
},
{
name: "non-string payload parameter",
params: map[string]any{
"topic": "test/topic",
"qos": 1,
"retained": true,
"payload": 123,
},
payload: 1,
errorString: "mqtt.message.create payload error: not a slice",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := processor.ProcessorRegistry["mqtt.message.create"]
if !ok {
t.Fatalf("mqtt.message.create processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "mqtt.message.create",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("mqtt.message.create got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err == nil {
t.Fatalf("mqtt.message.create expected to fail but succeeded, got: %v", got)
}
if err.Error() != test.errorString {
t.Fatalf("mqtt.message.create got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}

View File

@@ -45,14 +45,15 @@ func TestGoodMQTTMessageEncode(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := stringEncoder.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("mqtt.message.encode processing failed: %s", err)
}
gotBytes, ok := got.([]byte)
if !ok {
t.Fatalf("mqtt.message.encode returned a %T payload: %s", got, got)
}
if err != nil {
t.Fatalf("mqtt.message.encode failed: %s", err)
}
if !slices.Equal(gotBytes, test.expected) {
t.Fatalf("mqtt.message.encode got %s, expected %s", got, test.expected)
}

View File

@@ -44,35 +44,35 @@ func TestGoodOSCMessageCreate(t *testing.T) {
"address": "/test",
},
payload: osc.OSCMessage{},
expected: osc.OSCMessage{Address: "/test"},
expected: &osc.OSCMessage{Address: "/test"},
},
{
name: "address with template and no args",
params: map[string]any{
"address": "/test/{{.Value}}",
"address": "/test/{{.Payload.Value}}",
},
payload: map[string]any{"Value": "value"},
expected: osc.OSCMessage{Address: "/test/value"},
expected: &osc.OSCMessage{Address: "/test/value"},
},
{
name: "address with template and string arg",
params: map[string]any{
"address": "/test/{{.Value}}",
"address": "/test/{{.Payload.Value}}",
"args": []interface{}{"arg1"},
"types": "s",
},
payload: map[string]any{"Value": "value"},
expected: osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: "arg1", Type: "s"}}},
expected: &osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: "arg1", Type: "s"}}},
},
{
name: "address with template and mixed args",
params: map[string]any{
"address": "/test/{{.Value}}",
"address": "/test/{{.Payload.Value}}",
"args": []interface{}{"arg1", "42", "3.14"},
"types": "sif",
},
payload: map[string]any{"Value": "value"},
expected: osc.OSCMessage{
expected: &osc.OSCMessage{
Address: "/test/value",
Args: []osc.OSCArg{
{Value: "arg1", Type: "s"},
@@ -84,52 +84,52 @@ func TestGoodOSCMessageCreate(t *testing.T) {
{
name: "address with template and int64 arg",
params: map[string]any{
"address": "/test/{{.Value}}",
"address": "/test/{{.Payload.Value}}",
"args": []interface{}{"42"},
"types": "h",
},
payload: map[string]any{"Value": "value"},
expected: osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: int64(42), Type: "h"}}},
expected: &osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: int64(42), Type: "h"}}},
},
{
name: "address with template and double arg",
params: map[string]any{
"address": "/test/{{.Value}}",
"address": "/test/{{.Payload.Value}}",
"args": []interface{}{"42"},
"types": "d",
},
payload: map[string]any{"Value": "value"},
expected: osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: float64(42), Type: "d"}}},
expected: &osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: float64(42), Type: "d"}}},
},
{
name: "address with template and true arg",
params: map[string]any{
"address": "/test/{{.Value}}",
"address": "/test/{{.Payload.Value}}",
"args": []interface{}{""},
"types": "T",
},
payload: map[string]any{"Value": "value"},
expected: osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: true, Type: "T"}}},
expected: &osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: true, Type: "T"}}},
},
{
name: "address with template and false arg",
params: map[string]any{
"address": "/test/{{.Value}}",
"address": "/test/{{.Payload.Value}}",
"args": []interface{}{""},
"types": "F",
},
payload: map[string]any{"Value": "value"},
expected: osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: false, Type: "F"}}},
expected: &osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: false, Type: "F"}}},
},
{
name: "address with template and nil arg",
params: map[string]any{
"address": "/test/{{.Value}}",
"address": "/test/{{.Payload.Value}}",
"args": []interface{}{""},
"types": "N",
},
payload: map[string]any{"Value": "value"},
expected: osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: nil, Type: "N"}}},
expected: &osc.OSCMessage{Address: "/test/value", Args: []osc.OSCArg{{Value: nil, Type: "N"}}},
},
}
@@ -152,7 +152,7 @@ func TestGoodOSCMessageCreate(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("osc.message.create process failed: %s", err)
t.Fatalf("osc.message.create processing failed: %s", err)
}
if test.expected == nil {
@@ -162,7 +162,7 @@ func TestGoodOSCMessageCreate(t *testing.T) {
return
}
gotMessage, ok := got.(osc.OSCMessage)
gotMessage, ok := got.(*osc.OSCMessage)
if !ok {
t.Fatalf("osc.message.create returned a %T payload: %s", got, got)
}
@@ -211,7 +211,7 @@ func TestBadOSCMessageCreate(t *testing.T) {
"types": "s",
},
payload: "test",
errorString: "osc.message.create address must be an array found string",
errorString: "osc.message.create args error: not a slice",
},
{
name: "args without types parameter",
@@ -240,7 +240,7 @@ func TestBadOSCMessageCreate(t *testing.T) {
"types": "ss",
},
payload: "test",
errorString: "osc.message.create arg error: not a string",
errorString: "osc.message.create args error: element at index 1 is not a string",
},
{
name: "bad arg template",
@@ -286,7 +286,7 @@ func TestBadOSCMessageCreate(t *testing.T) {
"address": "/test/{{.missing}}",
},
payload: "test",
errorString: "template: address:1:8: executing \"address\" at <.missing>: can't evaluate field missing in type string",
errorString: "template: address:1:8: executing \"address\" at <.missing>: can't evaluate field missing in type processor.TemplateData",
},
{
name: "address doesn't start with slash",
@@ -304,7 +304,7 @@ func TestBadOSCMessageCreate(t *testing.T) {
"types": "s",
},
payload: "test",
errorString: "template: arg:1:2: executing \"arg\" at <.missing>: can't evaluate field missing in type string",
errorString: "template: arg:1:2: executing \"arg\" at <.missing>: can't evaluate field missing in type processor.TemplateData",
},
}
@@ -322,7 +322,7 @@ func TestBadOSCMessageCreate(t *testing.T) {
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("string.create got error '%s', expected '%s'", err.Error(), test.errorString)
t.Fatalf("osc.message.create got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}

View File

@@ -1,8 +1,10 @@
package processor_test
import (
"reflect"
"testing"
"github.com/jwetzell/osc-go"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
@@ -25,3 +27,96 @@ func TestOSCMessageDecodeFromRegistry(t *testing.T) {
t.Fatalf("osc.message.decode processor has wrong type: %s", processorInstance.Type())
}
}
func TestGoodOSCMessageDecode(t *testing.T) {
processorInstance := processor.OSCMessageDecode{}
tests := []struct {
name string
payload []byte
expected *osc.OSCMessage
}{
{
name: "basic OSC message",
payload: []byte{47, 116, 101, 115, 116, 0, 0, 0, 44, 0, 0, 0},
expected: &osc.OSCMessage{
Address: "/test",
Args: []osc.OSCArg{},
},
},
{
name: "basic OSC message with argument",
payload: []byte{47, 116, 101, 115, 116, 0, 0, 0, 44, 105, 0, 0, 0, 0, 0, 42},
expected: &osc.OSCMessage{
Address: "/test",
Args: []osc.OSCArg{
{
Type: "i",
Value: int32(42),
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("osc.message.decode processing failed: %s", err)
}
gotMessage, ok := got.(*osc.OSCMessage)
if !ok {
t.Fatalf("osc.message.decode returned a %T payload: %s", got, got)
}
if !reflect.DeepEqual(gotMessage, test.expected) {
t.Fatalf("osc.message.decode got %+v, expected %+v", gotMessage, test.expected)
}
})
}
}
func TestBadOSCMessageDecode(t *testing.T) {
processorInstance := processor.OSCMessageDecode{}
tests := []struct {
name string
payload any
errorString string
}{
{
name: "non []byte payload",
payload: "test",
errorString: "osc.message.decode processor only accepts a []byte payload",
},
{
name: "empty []byte payload",
payload: []byte{},
errorString: "osc.message.decode processor can't work on empty []byte",
},
{
name: "wrong start byte in payload",
payload: []byte{48, 116, 101, 115, 116, 0, 0, 0, 44, 105, 0, 0, 0, 0, 0, 42},
errorString: "osc.message.decode processor needs an OSC looking []byte",
},
{
name: "invalid OSC payload",
payload: []byte{47, 116, 101, 115, 116, 0},
errorString: "osc.message.decode processor failed to decode OSC message: string data is not properly padded",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err == nil {
t.Fatalf("osc.message.decode expected to fail but got payload: %s", got)
}
if err.Error() != test.errorString {
t.Fatalf("osc.message.decode got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}

View File

@@ -1,8 +1,10 @@
package processor_test
import (
"slices"
"testing"
"github.com/jwetzell/osc-go"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
@@ -25,3 +27,79 @@ func TestOSCMessageEncodeFromRegistry(t *testing.T) {
t.Fatalf("osc.message.encode processor has wrong type: %s", processorInstance.Type())
}
}
func TestGoodOSCMessageEncode(t *testing.T) {
processorInstance := processor.OSCMessageEncode{}
tests := []struct {
name string
payload any
expected []byte
}{
{
name: "basic OSC message",
payload: &osc.OSCMessage{
Address: "/test",
},
expected: []byte{47, 116, 101, 115, 116, 0, 0, 0, 44, 0, 0, 0},
},
{
name: "basic OSC message with argument",
payload: &osc.OSCMessage{
Address: "/test",
Args: []osc.OSCArg{
{
Type: "i",
Value: int32(42),
},
},
},
expected: []byte{47, 116, 101, 115, 116, 0, 0, 0, 44, 105, 0, 0, 0, 0, 0, 42},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("osc.message.encode processing failed: %s", err)
}
gotBytes, ok := got.([]byte)
if !ok {
t.Fatalf("osc.message.encode returned a %T payload: %s", got, got)
}
if !slices.Equal(gotBytes, test.expected) {
t.Fatalf("osc.message.encode got %+v, expected %+v", got, test.expected)
}
})
}
}
func TestBadOSCMessageEncode(t *testing.T) {
processorInstance := processor.OSCMessageEncode{}
tests := []struct {
name string
payload any
errorString string
}{
{
name: "non-osc message input",
payload: "test",
errorString: "osc.message.encode processor only accepts an *OSCMessage",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err == nil {
t.Fatalf("osc.message.encode expected to fail but got payload: %s", got)
}
if err.Error() != test.errorString {
t.Fatalf("osc.message.encode got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}

View File

@@ -75,7 +75,7 @@ func TestGoodOSCMessageFilter(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("osc.message.filter process failed: %s", err)
t.Fatalf("osc.message.filter processing failed: %s", err)
}
if test.expected == nil {

View File

@@ -36,6 +36,8 @@ func (t TestStruct) GetData() any {
return t.Data
}
func (t TestStruct) Void() {}
type TestProcessor struct {
}

View File

@@ -28,58 +28,6 @@ func TestScriptExprFromRegistry(t *testing.T) {
}
}
func TestScriptExprNoProgram(t *testing.T) {
registration, ok := processor.ProcessorRegistry["script.expr"]
if !ok {
t.Fatalf("script.expr processor not registered")
}
_, err := registration.New(config.ProcessorConfig{
Type: "script.expr",
Params: map[string]any{},
})
if err == nil {
t.Fatalf("script.expr processor should have thrown an error when creating")
}
}
func TestScriptExprBadConfigWrongExpressionType(t *testing.T) {
registration, ok := processor.ProcessorRegistry["script.expr"]
if !ok {
t.Fatalf("script.expr processor not registered")
}
_, err := registration.New(config.ProcessorConfig{
Type: "script.expr",
Params: map[string]any{
"expression": 12345,
},
})
if err == nil {
t.Fatalf("script.expr processor should have thrown an error when creating with non-string expression")
}
}
func TestScriptExprBadConfigNonCompilingExpression(t *testing.T) {
registration, ok := processor.ProcessorRegistry["script.expr"]
if !ok {
t.Fatalf("script.expr processor not registered")
}
_, err := registration.New(config.ProcessorConfig{
Type: "script.expr",
Params: map[string]any{
"expression": "foo + ",
},
})
if err == nil {
t.Fatalf("script.expr processor should have thrown an error when creating with non-compiling expression")
}
}
func TestGoodScriptExpr(t *testing.T) {
tests := []struct {
name string
@@ -90,7 +38,7 @@ func TestGoodScriptExpr(t *testing.T) {
{
name: "number",
params: map[string]any{
"expression": "foo + bar",
"expression": "Payload.foo + Payload.bar",
},
payload: map[string]any{
"foo": 1,
@@ -101,7 +49,7 @@ func TestGoodScriptExpr(t *testing.T) {
{
name: "string",
params: map[string]any{
"expression": "foo + bar",
"expression": "Payload.foo + Payload.bar",
},
payload: map[string]any{
"foo": "1",
@@ -130,7 +78,7 @@ func TestGoodScriptExpr(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("script.expr failed: %s", err)
t.Fatalf("script.expr processing failed: %s", err)
}
//TODO(jwetzell): work out better way to compare the any/any
@@ -145,18 +93,24 @@ func TestBadScriptExpr(t *testing.T) {
tests := []struct {
name string
params map[string]any
payload map[string]any
payload any
errorString string
}{
{
name: "no expression parameter",
params: map[string]any{},
payload: map[string]any{"foo": 1, "bar": 1},
errorString: "script.expr expression error: not found",
},
{
name: "accessing missing field",
params: map[string]any{
"expression": "foo + bar",
"expression": "Payload.foo + Payload.bar",
},
payload: map[string]any{
"foo": 1,
},
errorString: "invalid operation: int + <nil> (1:5)\n | foo + bar\n | ....^",
errorString: "invalid operation: int + <nil> (1:13)\n | Payload.foo + Payload.bar\n | ............^",
},
}
@@ -172,6 +126,13 @@ func TestBadScriptExpr(t *testing.T) {
Params: test.params,
})
if err != nil {
if err.Error() != test.errorString {
t.Fatalf("script.expr got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
got, err := processorInstance.Process(t.Context(), test.payload)
if err == nil {

View File

@@ -145,7 +145,7 @@ func TestGoodScriptJS(t *testing.T) {
got, err := processorInstance.Process(t.Context(), test.payload)
if err != nil {
t.Fatalf("script.js process failed: %s", err)
t.Fatalf("script.js processing failed: %s", err)
}
//TODO(jwetzell): work out better way to compare the any/any

Some files were not shown because too many files have changed in this diff Show More