Compare commits

...

631 Commits

Author SHA1 Message Date
Joel Wetzell 848e644a9f add id field to route and processor 2026-08-19 09:30:20 -05:00
Joel Wetzell a3926d980b Merge pull request #226 from jwetzell/renovate/github.com-emiago-sipgo-1.x
Update module github.com/emiago/sipgo to v1.5.0
2026-08-19 08:08:30 -05:00
renovate[bot] 88d4f2665f Update module github.com/emiago/sipgo to v1.5.0 2026-08-19 02:49:59 +00:00
Joel Wetzell 0713d665bb Merge pull request #225 from jwetzell/renovate/modernc.org-quickjs-0.x
Update module modernc.org/quickjs to v0.24.0
2026-08-17 13:57:48 -05:00
Joel Wetzell 9991e44549 Merge pull request #224 from jwetzell/renovate/github.com-urfave-cli-v3-3.x
Update module github.com/urfave/cli/v3 to v3.11.0
2026-08-17 13:57:36 -05:00
Joel Wetzell d895c117dd Merge pull request #223 from jwetzell/renovate/github.com-emiago-diago-0.x
Update module github.com/emiago/diago to v0.32.0
2026-08-17 13:57:24 -05:00
renovate[bot] 5b4b91769a Update module modernc.org/quickjs to v0.24.0 2026-08-17 18:54:39 +00:00
renovate[bot] c1925a1056 Update module github.com/urfave/cli/v3 to v3.11.0 2026-08-17 18:54:32 +00:00
renovate[bot] 2fa95b35c7 Update module github.com/emiago/diago to v0.32.0 2026-08-17 18:54:26 +00:00
Joel Wetzell 58a4217469 Merge pull request #222 from jwetzell/renovate/github.com-nats-io-nats.go-1.x
Update module github.com/nats-io/nats.go to v1.53.1
2026-08-17 13:53:30 -05:00
renovate[bot] c6bc30791c Update module github.com/nats-io/nats.go to v1.53.1 2026-08-17 18:45:05 +00:00
Joel Wetzell 117e5cedab Merge pull request #220 from jwetzell/renovate/github.com-nats-io-nats-server-v2-2.x
Update module github.com/nats-io/nats-server/v2 to v2.14.5
2026-08-17 13:43:13 -05:00
Joel Wetzell e727509522 Merge pull request #219 from jwetzell/renovate/actions-checkout-7.x
Update actions/checkout action to v7.0.1
2026-08-17 13:41:49 -05:00
renovate[bot] 663b595f40 Update module github.com/nats-io/nats-server/v2 to v2.14.5 2026-08-17 18:40:50 +00:00
Joel Wetzell 762bf816d7 remove dependabot config 2026-08-17 13:40:25 -05:00
renovate[bot] d200966212 Update actions/checkout action to v7.0.1 2026-08-17 18:40:20 +00:00
Joel Wetzell 7e721dc263 Merge pull request #218 from jwetzell/renovate/configure
Add renovate.json
2026-08-17 13:39:42 -05:00
renovate[bot] e6a42c81d6 Add renovate.json 2026-08-17 18:20:30 +00:00
Joel Wetzell c07ce2de73 Merge pull request #214 from jwetzell/dependabot/go_modules/modernc.org/quickjs-0.23.0
Bump modernc.org/quickjs from 0.22.0 to 0.23.0
2026-08-11 20:07:38 -05:00
dependabot[bot] 2a2fd04b78 Bump modernc.org/quickjs from 0.22.0 to 0.23.0
Bumps [modernc.org/quickjs](https://gitlab.com/cznic/quickjs) from 0.22.0 to 0.23.0.
- [Commits](https://gitlab.com/cznic/quickjs/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: modernc.org/quickjs
  dependency-version: 0.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-12 01:05:15 +00:00
Joel Wetzell ad01ba62bb Merge pull request #215 from jwetzell/dependabot/go_modules/github.com/redis/go-redis/v9-9.22.0
Bump github.com/redis/go-redis/v9 from 9.21.0 to 9.22.0
2026-08-11 20:03:38 -05:00
Joel Wetzell c20bd9d448 Merge pull request #216 from jwetzell/dependabot/go_modules/modernc.org/sqlite-1.56.0
Bump modernc.org/sqlite from 1.55.0 to 1.56.0
2026-08-11 20:03:27 -05:00
Joel Wetzell 8f681b0cb6 Merge pull request #217 from jwetzell/fix/mqtt-client-locking
fix lock releasing in mqtt client on error
2026-08-11 20:02:58 -05:00
Joel Wetzell 7c0a504616 fix lock releasing in mqtt client on error 2026-08-11 19:58:33 -05:00
dependabot[bot] 9af0991a0d Bump modernc.org/sqlite from 1.55.0 to 1.56.0
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.55.0 to 1.56.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.55.0...v1.56.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.56.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-11 17:23:58 +00:00
dependabot[bot] b22fdbe6ef Bump github.com/redis/go-redis/v9 from 9.21.0 to 9.22.0
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.21.0 to 9.22.0.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.21.0...v9.22.0)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-11 17:23:51 +00:00
Joel Wetzell a1f7bca743 Merge pull request #212 from jwetzell/dependabot/github_actions/docker/login-action-4.6.0
Bump docker/login-action from 4.5.2 to 4.6.0
2026-08-04 14:28:35 -05:00
Joel Wetzell dbb1fed79f Merge pull request #213 from jwetzell/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.14.4
Bump github.com/nats-io/nats-server/v2 from 2.14.3 to 2.14.4
2026-08-04 14:28:25 -05:00
dependabot[bot] c4e338a517 Bump github.com/nats-io/nats-server/v2 from 2.14.3 to 2.14.4
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.14.3 to 2.14.4.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/RELEASES.md)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.14.3...v2.14.4)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-version: 2.14.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-04 17:23:29 +00:00
dependabot[bot] eddeb4655b Bump docker/login-action from 4.5.2 to 4.6.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.5.2 to 4.6.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/371161bbe7024a29a25c5e19bfcbc0804fe9ad2c...dbcb813823bdd20940b903addbd779551569679f)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-04 17:23:20 +00:00
Joel Wetzell 4f34de7fa8 Merge pull request #207 from jwetzell/dependabot/go_modules/go.bug.st/serial-1.8.0
Bump go.bug.st/serial from 1.7.1 to 1.8.0
2026-07-29 08:32:53 -05:00
dependabot[bot] e47a5fff5f Bump go.bug.st/serial from 1.7.1 to 1.8.0
Bumps [go.bug.st/serial](https://github.com/bugst/go-serial) from 1.7.1 to 1.8.0.
- [Release notes](https://github.com/bugst/go-serial/releases)
- [Commits](https://github.com/bugst/go-serial/compare/v1.7.1...v1.8.0)

---
updated-dependencies:
- dependency-name: go.bug.st/serial
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 13:17:39 +00:00
Joel Wetzell a1aa80c9ee Merge pull request #208 from jwetzell/dependabot/go_modules/modernc.org/sqlite-1.54.0
Bump modernc.org/sqlite from 1.53.0 to 1.55.0
2026-07-29 08:15:27 -05:00
Joel Wetzell d926bd3f7d Merge pull request #206 from jwetzell/dependabot/github_actions/actions/setup-go-7
Bump actions/setup-go from 6 to 7
2026-07-29 08:02:54 -05:00
Joel Wetzell aee67547cf Merge pull request #209 from jwetzell/dependabot/github_actions/docker/login-action-4.5.2
Bump docker/login-action from 4.4.0 to 4.5.2
2026-07-29 08:02:30 -05:00
dependabot[bot] 5e2c85244f Bump modernc.org/sqlite from 1.53.0 to 1.55.0
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.53.0 to 1.55.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.53.0...v1.55.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 13:02:20 +00:00
Joel Wetzell f314fed2d9 Merge pull request #210 from jwetzell/dependabot/go_modules/github.com/emiago/diago-0.31.0
Bump github.com/emiago/diago from 0.30.0 to 0.31.0
2026-07-29 08:02:04 -05:00
Joel Wetzell 32deb2032b Merge pull request #211 from jwetzell/dependabot/go_modules/modernc.org/quickjs-0.22.0
Bump modernc.org/quickjs from 0.21.0 to 0.22.0
2026-07-29 08:00:24 -05:00
dependabot[bot] 6852a3e917 Bump modernc.org/quickjs from 0.21.0 to 0.22.0
Bumps [modernc.org/quickjs](https://gitlab.com/cznic/quickjs) from 0.21.0 to 0.22.0.
- [Commits](https://gitlab.com/cznic/quickjs/compare/v0.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: modernc.org/quickjs
  dependency-version: 0.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-28 17:23:41 +00:00
dependabot[bot] 2874f2c7ff Bump github.com/emiago/diago from 0.30.0 to 0.31.0
Bumps [github.com/emiago/diago](https://github.com/emiago/diago) from 0.30.0 to 0.31.0.
- [Release notes](https://github.com/emiago/diago/releases)
- [Commits](https://github.com/emiago/diago/compare/v0.30.0...v0.31.0)

---
updated-dependencies:
- dependency-name: github.com/emiago/diago
  dependency-version: 0.31.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-28 17:23:33 +00:00
dependabot[bot] a9f35fa4f7 Bump docker/login-action from 4.4.0 to 4.5.2
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.4.0 to 4.5.2.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/af1e73f918a031802d376d3c8bbc3fe56130a9b0...371161bbe7024a29a25c5e19bfcbc0804fe9ad2c)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.5.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-28 17:23:28 +00:00
dependabot[bot] 5aa9961742 Bump actions/setup-go from 6 to 7
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6 to 7.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-21 17:23:15 +00:00
Joel Wetzell 4914db403e Merge pull request #205 from jwetzell/dependabot/go_modules/github.com/emiago/diago-0.30.0
Bump github.com/emiago/diago from 0.29.0 to 0.30.0
2026-07-18 13:16:27 -05:00
dependabot[bot] fd49fa2795 Bump github.com/emiago/diago from 0.29.0 to 0.30.0
Bumps [github.com/emiago/diago](https://github.com/emiago/diago) from 0.29.0 to 0.30.0.
- [Release notes](https://github.com/emiago/diago/releases)
- [Commits](https://github.com/emiago/diago/compare/v0.29.0...v0.30.0)

---
updated-dependencies:
- dependency-name: github.com/emiago/diago
  dependency-version: 0.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-14 17:23:39 +00:00
Joel Wetzell b86ca20592 syntax cleanup 2026-07-09 22:22:22 -05:00
Joel Wetzell 777f9c43de Merge pull request #196 from jwetzell/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.14.3
Bump github.com/nats-io/nats-server/v2 from 2.14.2 to 2.14.3
2026-07-07 14:55:03 -05:00
dependabot[bot] 8ebbed482d Bump github.com/nats-io/nats-server/v2 from 2.14.2 to 2.14.3
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.14.2 to 2.14.3.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/RELEASES.md)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.14.2...v2.14.3)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-version: 2.14.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 19:28:50 +00:00
Joel Wetzell 27ef3f7766 Merge pull request #201 from jwetzell/dependabot/go_modules/github.com/emiago/sipgo-1.4.3
Bump github.com/emiago/sipgo from 1.4.0 to 1.4.3
2026-07-07 14:28:46 -05:00
Joel Wetzell 2b71829d47 Merge pull request #198 from jwetzell/dependabot/github_actions/docker/setup-buildx-action-4.2.0
Bump docker/setup-buildx-action from 4.1.0 to 4.2.0
2026-07-07 14:28:29 -05:00
Joel Wetzell cf1dcd305f Merge pull request #197 from jwetzell/dependabot/github_actions/docker/setup-qemu-action-4.2.0
Bump docker/setup-qemu-action from 4.1.0 to 4.2.0
2026-07-07 14:27:35 -05:00
Joel Wetzell 2c05549edb Merge pull request #194 from jwetzell/dependabot/go_modules/github.com/urfave/cli/v3-3.10.1
Bump github.com/urfave/cli/v3 from 3.10.0 to 3.10.1
2026-07-07 14:27:19 -05:00
Joel Wetzell 10fc3584e1 Merge pull request #199 from jwetzell/dependabot/github_actions/docker/metadata-action-6.2.0
Bump docker/metadata-action from 6.1.0 to 6.2.0
2026-07-07 14:27:02 -05:00
Joel Wetzell 0b26835eb6 Merge pull request #200 from jwetzell/dependabot/github_actions/docker/build-push-action-7.3.0
Bump docker/build-push-action from 7.2.0 to 7.3.0
2026-07-07 14:26:40 -05:00
Joel Wetzell c47f6fedff Merge pull request #202 from jwetzell/dependabot/github_actions/docker/login-action-4.4.0
Bump docker/login-action from 4.2.0 to 4.4.0
2026-07-07 14:26:27 -05:00
Joel Wetzell 250e0f2b0b Merge pull request #203 from jwetzell/dependabot/go_modules/modernc.org/quickjs-0.21.0
Bump modernc.org/quickjs from 0.18.2 to 0.21.0
2026-07-07 14:26:17 -05:00
dependabot[bot] e02ddf4255 Bump modernc.org/quickjs from 0.18.2 to 0.21.0
Bumps [modernc.org/quickjs](https://gitlab.com/cznic/quickjs) from 0.18.2 to 0.21.0.
- [Commits](https://gitlab.com/cznic/quickjs/compare/v0.18.2...v0.21.0)

---
updated-dependencies:
- dependency-name: modernc.org/quickjs
  dependency-version: 0.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 17:23:54 +00:00
dependabot[bot] 5c8ab7a2db Bump github.com/emiago/sipgo from 1.4.0 to 1.4.3
Bumps [github.com/emiago/sipgo](https://github.com/emiago/sipgo) from 1.4.0 to 1.4.3.
- [Release notes](https://github.com/emiago/sipgo/releases)
- [Commits](https://github.com/emiago/sipgo/compare/v1.4.0...v1.4.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 17:23:46 +00:00
dependabot[bot] 58edcff9b4 Bump docker/login-action from 4.2.0 to 4.4.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.2.0 to 4.4.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/650006c6eb7dba73a995cc03b0b2d7f5ca915bee...af1e73f918a031802d376d3c8bbc3fe56130a9b0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 17:23:46 +00:00
dependabot[bot] 74dbb26911 Bump docker/build-push-action from 7.2.0 to 7.3.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 7.2.0 to 7.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/f9f3042f7e2789586610d6e8b85c8f03e5195baf...53b7df96c91f9c12dcc8a07bcb9ccacbed38856a)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 17:23:42 +00:00
dependabot[bot] ccc1b4ac34 Bump docker/metadata-action from 6.1.0 to 6.2.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9...dc802804100637a589fabce1cb79ff13a1411302)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 17:23:36 +00:00
dependabot[bot] 7e95b52774 Bump docker/setup-buildx-action from 4.1.0 to 4.2.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5...bb05f3f5519dd87d3ba754cc423b652a5edd6d2c)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 17:23:32 +00:00
dependabot[bot] 4eb78fa719 Bump docker/setup-qemu-action from 4.1.0 to 4.2.0
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/06116385d9baf250c9f4dcb4858b16962ea869c3...96fe6ef7f33517b61c61be40b68a1882f3264fb8)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 17:23:29 +00:00
dependabot[bot] 66d30e9faf Bump github.com/urfave/cli/v3 from 3.10.0 to 3.10.1
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.10.0 to 3.10.1.
- [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.10.0...v3.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-30 17:23:18 +00:00
Joel Wetzell 04ba102768 Merge pull request #191 from jwetzell/dependabot/github_actions/actions/checkout-7
Bump actions/checkout from 6 to 7
2026-06-29 10:26:10 -05:00
Joel Wetzell 49c1f90912 Merge pull request #192 from jwetzell/dependabot/go_modules/modernc.org/sqlite-1.53.0
Bump modernc.org/sqlite from 1.52.0 to 1.53.0
2026-06-29 10:25:23 -05:00
Joel Wetzell 80c145a541 Merge pull request #193 from jwetzell/dependabot/go_modules/github.com/redis/go-redis/v9-9.21.0
Bump github.com/redis/go-redis/v9 from 9.20.1 to 9.21.0
2026-06-29 10:24:53 -05:00
dependabot[bot] 4119312e9d Bump github.com/redis/go-redis/v9 from 9.20.1 to 9.21.0
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.20.1 to 9.21.0.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.20.1...v9.21.0)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-23 17:23:52 +00:00
dependabot[bot] 5b6cfc6849 Bump modernc.org/sqlite from 1.52.0 to 1.53.0
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.52.0 to 1.53.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.52.0...v1.53.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.53.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-23 17:23:44 +00:00
dependabot[bot] c48ca01b6d Bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-23 17:23:16 +00:00
Joel Wetzell c551499fc6 add test for common events 2026-06-21 13:09:09 -05:00
Joel Wetzell cf48d0e65e Merge pull request #188 from jwetzell/dependabot/go_modules/gitlab.com/gomidi/midi/v2-2.3.24
Bump gitlab.com/gomidi/midi/v2 from 2.3.23 to 2.3.24
2026-06-19 17:27:23 -05:00
Joel Wetzell e5578a4d5d Merge pull request #189 from jwetzell/dependabot/go_modules/github.com/redis/go-redis/v9-9.20.1
Bump github.com/redis/go-redis/v9 from 9.20.0 to 9.20.1
2026-06-19 17:26:18 -05:00
dependabot[bot] 83199650af Bump gitlab.com/gomidi/midi/v2 from 2.3.23 to 2.3.24
Bumps [gitlab.com/gomidi/midi/v2](https://gitlab.com/gomidi/midi) from 2.3.23 to 2.3.24.
- [Commits](https://gitlab.com/gomidi/midi/compare/v2.3.23...v2.3.24)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-19 22:24:02 +00:00
dependabot[bot] fcbc96c63b Bump github.com/redis/go-redis/v9 from 9.20.0 to 9.20.1
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.20.0 to 9.20.1.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.20.0...v9.20.1)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-19 22:24:01 +00:00
Joel Wetzell fd4fbbd66d Merge pull request #190 from jwetzell/dependabot/go_modules/github.com/urfave/cli/v3-3.10.0
Bump github.com/urfave/cli/v3 from 3.9.0 to 3.10.0
2026-06-19 17:21:54 -05:00
dependabot[bot] 1dddec502f Bump github.com/urfave/cli/v3 from 3.9.0 to 3.10.0
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.9.0 to 3.10.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.9.0...v3.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-16 17:24:32 +00:00
Joel Wetzell 28e8e4f8c7 Merge pull request #186 from jwetzell/dependabot/go_modules/github.com/emiago/diago-0.29.0
Bump github.com/emiago/diago from 0.28.0 to 0.29.0
2026-06-14 15:31:50 -05:00
Joel Wetzell 34dd6a0d31 update with new return signature 2026-06-14 15:29:38 -05:00
Joel Wetzell 2af9f348e7 Merge pull request #187 from jwetzell/dependabot/go_modules/modernc.org/sqlite-1.52.0
Bump modernc.org/sqlite from 1.51.0 to 1.52.0
2026-06-10 11:34:34 -05:00
dependabot[bot] b207643f1c Bump modernc.org/sqlite from 1.51.0 to 1.52.0
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.51.0 to 1.52.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.51.0...v1.52.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.52.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 17:24:03 +00:00
dependabot[bot] ae100ec567 Bump github.com/emiago/diago from 0.28.0 to 0.29.0
Bumps [github.com/emiago/diago](https://github.com/emiago/diago) from 0.28.0 to 0.29.0.
- [Release notes](https://github.com/emiago/diago/releases)
- [Commits](https://github.com/emiago/diago/compare/v0.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: github.com/emiago/diago
  dependency-version: 0.29.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 17:23:52 +00:00
Joel Wetzell 05c027dca7 Merge pull request #181 from jwetzell/dependabot/github_actions/docker/setup-qemu-action-4.1.0
Bump docker/setup-qemu-action from 4.0.0 to 4.1.0
2026-06-07 21:13:10 -05:00
Joel Wetzell 6ef4215248 Merge pull request #182 from jwetzell/dependabot/go_modules/github.com/redis/go-redis/v9-9.20.0
Bump github.com/redis/go-redis/v9 from 9.19.0 to 9.20.0
2026-06-07 21:12:58 -05:00
Joel Wetzell ac51d9bd3d Merge pull request #183 from jwetzell/dependabot/go_modules/modernc.org/sqlite-1.51.0
Bump modernc.org/sqlite from 1.50.1 to 1.51.0
2026-06-07 21:12:42 -05:00
Joel Wetzell c01a101214 Merge pull request #184 from jwetzell/dependabot/go_modules/github.com/emiago/sipgo-1.4.0
Bump github.com/emiago/sipgo from 1.3.1 to 1.4.0
2026-06-07 21:12:22 -05:00
Joel Wetzell 7d401b9570 Merge pull request #185 from jwetzell/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.14.2
Bump github.com/nats-io/nats-server/v2 from 2.14.1 to 2.14.2
2026-06-07 21:12:11 -05:00
dependabot[bot] 809c31847b Bump github.com/nats-io/nats-server/v2 from 2.14.1 to 2.14.2
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.14.1 to 2.14.2.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/RELEASES.md)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.14.1...v2.14.2)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-version: 2.14.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-04 00:43:00 +00:00
dependabot[bot] bdcf5a06ce Bump github.com/emiago/sipgo from 1.3.1 to 1.4.0
Bumps [github.com/emiago/sipgo](https://github.com/emiago/sipgo) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/emiago/sipgo/releases)
- [Commits](https://github.com/emiago/sipgo/compare/v1.3.1...v1.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-04 00:42:47 +00:00
dependabot[bot] 09a46d3634 Bump modernc.org/sqlite from 1.50.1 to 1.51.0
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.50.1 to 1.51.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.50.1...v1.51.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.51.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-04 00:42:42 +00:00
dependabot[bot] 7385f25b3d Bump github.com/redis/go-redis/v9 from 9.19.0 to 9.20.0
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.19.0 to 9.20.0.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.19.0...v9.20.0)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-04 00:42:37 +00:00
dependabot[bot] 94a59413a1 Bump docker/setup-qemu-action from 4.0.0 to 4.1.0
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/ce360397dd3f832beb865e1373c09c0e9f86d70a...06116385d9baf250c9f4dcb4858b16962ea869c3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-04 00:42:07 +00:00
Joel Wetzell b7e0d879bc Merge pull request #179 from jwetzell/fix/in-out-framer
make sure to use a distinct framer for both directions of communication
2026-06-01 21:35:58 -05:00
Joel Wetzell 9b76ee8195 make sure to use a distinct framer for both directions of communication 2026-06-01 21:32:14 -05:00
Joel Wetzell d48a89d1ef reword description 2026-06-01 18:30:19 -05:00
Joel Wetzell b01bebea54 Merge pull request #178 from jwetzell/processor-params-schema-descriptions
add description information to params for processors
2026-06-01 17:56:39 -05:00
Joel Wetzell 73905e1274 add description information to params for processors 2026-06-01 17:55:30 -05:00
Joel Wetzell 56566cf666 Merge pull request #177 from jwetzell/module-params-descriptions
add description information to params for modules
2026-06-01 17:39:30 -05:00
Joel Wetzell 5d1e6622cb add description information to params for modules 2026-06-01 17:39:00 -05:00
Joel Wetzell 81ae00c943 wrap config schema errors 2026-05-30 22:18:48 -05:00
Joel Wetzell 49e351c760 update artnet-go 2026-05-30 22:18:39 -05:00
Joel Wetzell e90356762c if tcp client is closed by stop, stop retrying 2026-05-30 22:18:28 -05:00
Joel Wetzell 68586cd588 some close work in websocket.client 2026-05-30 22:18:22 -05:00
Joel Wetzell 5e87cdcc5e Merge pull request #173 from jwetzell/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.14.1
Bump github.com/nats-io/nats-server/v2 from 2.14.0 to 2.14.1
2026-05-27 22:00:49 -05:00
Joel Wetzell 2d6fa2ad19 Merge pull request #174 from jwetzell/dependabot/go_modules/go.bug.st/serial-1.7.1
Bump go.bug.st/serial from 1.7.0 to 1.7.1
2026-05-27 22:00:24 -05:00
Joel Wetzell 3ab2cade81 add more to smoke config 2026-05-27 21:02:09 -05:00
Joel Wetzell 2078d8533f Merge pull request #176 from jwetzell/fix/http-response-schema
align http.response.create schema with reality
2026-05-27 20:59:17 -05:00
Joel Wetzell 9b5a9ce6d0 align http.response.create schema with reality 2026-05-27 20:54:11 -05:00
Joel Wetzell dbeb1e8c29 Merge pull request #175 from jwetzell/chore/shutdown-rework
rework module shutdown process to cancel module context in defer
2026-05-27 20:53:31 -05:00
Joel Wetzell 0b494fce91 rework module shutdown process to cancel module context in defer 2026-05-27 20:50:33 -05:00
Joel Wetzell 52ca801fbd add context to keyvaluemodule get/set 2026-05-27 18:30:23 -05:00
Joel Wetzell 3adbae8068 Merge pull request #172 from jwetzell/dependabot/github_actions/docker/login-action-4.2.0
Bump docker/login-action from 4.1.0 to 4.2.0
2026-05-27 15:35:46 -05:00
Joel Wetzell 4ba718e2c6 Merge pull request #171 from jwetzell/dependabot/github_actions/docker/build-push-action-7.2.0
Bump docker/build-push-action from 7.1.0 to 7.2.0
2026-05-27 15:35:37 -05:00
Joel Wetzell fa595a027b Merge pull request #170 from jwetzell/dependabot/github_actions/docker/metadata-action-6.1.0
Bump docker/metadata-action from 6.0.0 to 6.1.0
2026-05-27 15:35:28 -05:00
Joel Wetzell 5660acfca6 Merge pull request #169 from jwetzell/dependabot/github_actions/docker/setup-buildx-action-4.1.0
Bump docker/setup-buildx-action from 4.0.0 to 4.1.0
2026-05-27 15:35:19 -05:00
dependabot[bot] 3a1bc74fcf Bump go.bug.st/serial from 1.7.0 to 1.7.1
Bumps [go.bug.st/serial](https://github.com/bugst/go-serial) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/bugst/go-serial/releases)
- [Commits](https://github.com/bugst/go-serial/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: go.bug.st/serial
  dependency-version: 1.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-26 23:15:14 +00:00
dependabot[bot] 62ce5bf2f8 Bump github.com/nats-io/nats-server/v2 from 2.14.0 to 2.14.1
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.14.0 to 2.14.1.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/RELEASES.md)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.14.0...v2.14.1)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-version: 2.14.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-26 23:15:06 +00:00
dependabot[bot] 0f70596eb0 Bump docker/login-action from 4.1.0 to 4.2.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/4907a6ddec9925e35a0a9e82d7399ccc52663121...650006c6eb7dba73a995cc03b0b2d7f5ca915bee)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-26 23:14:42 +00:00
dependabot[bot] db3d0530a5 Bump docker/build-push-action from 7.1.0 to 7.2.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 7.1.0 to 7.2.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/bcafcacb16a39f128d818304e6c9c0c18556b85f...f9f3042f7e2789586610d6e8b85c8f03e5195baf)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 7.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-26 23:14:37 +00:00
dependabot[bot] 6b53211267 Bump docker/metadata-action from 6.0.0 to 6.1.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/030e881283bb7a6894de51c315a6bfe6a94e05cf...80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-26 23:14:34 +00:00
dependabot[bot] 821d192eb7 Bump docker/setup-buildx-action from 4.0.0 to 4.1.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd...d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-26 23:14:31 +00:00
Joel Wetzell b50882fc4a fix error message in test 2026-05-26 17:56:08 -05:00
Joel Wetzell 438c396aeb include processor index in processpayload error 2026-05-26 17:53:34 -05:00
Joel Wetzell f175bed084 go mod tidy 2026-05-26 17:27:47 -05:00
Joel Wetzell 80f8152dfb don't export module/processor/framer registrations 2026-05-26 17:27:35 -05:00
Joel Wetzell 0ff212742a Merge pull request #168 from jwetzell/feat/rate-limiter
add rate limiting filter
2026-05-24 14:24:38 -05:00
Joel Wetzell 8fe0f7e6a2 add rate limiting filter 2026-05-24 14:19:58 -05:00
Joel Wetzell 9db958b523 only run docker job on tag 2026-05-23 11:50:45 -05:00
Joel Wetzell ea427bd2b6 update artnet-go 2026-05-23 07:57:42 -05:00
Joel Wetzell 5a2aefbdd5 update free-d-decode 2026-05-23 07:57:34 -05:00
Joel Wetzell 4323a21015 move init functions to top of file 2026-05-22 17:45:03 -05:00
Joel Wetzell fb3c775765 go fix 2026-05-20 22:28:05 -05:00
Joel Wetzell cf41bcae85 align method receiver variable names 2026-05-20 21:25:30 -05:00
Joel Wetzell 4cedd58a76 switch modules to accept an InputHandler instead of the full router 2026-05-20 21:03:41 -05:00
Joel Wetzell be0b1c4a5f update test config with new pubsub.publish processor 2026-05-20 21:03:14 -05:00
Joel Wetzell 290a89d38e Merge pull request #167 from jwetzell/feat/kv-set-from-payload
switch kv.set to take value from incoming payload
2026-05-20 20:40:48 -05:00
Joel Wetzell b495446926 switch kv.set to take value from incoming payload 2026-05-20 20:38:45 -05:00
Joel Wetzell fa4eeddbbf Merge pull request #166 from jwetzell/fix/params-schema-required
don't make params schema required if all properties are optional
2026-05-20 17:30:28 -05:00
Joel Wetzell 1bbe0cc045 don't make params schema required if all properties are optional 2026-05-20 17:27:32 -05:00
Joel Wetzell 70c4aa172a add test for reading from sqlite file 2026-05-20 17:26:50 -05:00
Joel Wetzell 849a59a63b add ability to set test kv properties 2026-05-20 17:26:21 -05:00
Joel Wetzell 00b03197c2 Merge pull request #161 from jwetzell/dependabot/go_modules/modernc.org/quickjs-0.18.2
Bump modernc.org/quickjs from 0.18.1 to 0.18.2
2026-05-19 22:03:23 -05:00
Joel Wetzell bb44e3b813 fix rogue error check 2026-05-19 22:01:59 -05:00
dependabot[bot] 099a8af271 Bump modernc.org/quickjs from 0.18.1 to 0.18.2
Bumps [modernc.org/quickjs](https://gitlab.com/cznic/quickjs) from 0.18.1 to 0.18.2.
- [Commits](https://gitlab.com/cznic/quickjs/compare/v0.18.1...v0.18.2)

---
updated-dependencies:
- dependency-name: modernc.org/quickjs
  dependency-version: 0.18.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 03:01:34 +00:00
Joel Wetzell f640dac0ad Merge pull request #162 from jwetzell/dependabot/go_modules/go.bug.st/serial-1.7.0
Bump go.bug.st/serial from 1.6.4 to 1.7.0
2026-05-19 21:59:34 -05:00
Joel Wetzell 3c5b1cac4d fix processor type in configs 2026-05-19 21:56:50 -05:00
Joel Wetzell 7c8695b9fe start working on startup tests for modules 2026-05-19 21:56:04 -05:00
Joel Wetzell 5db23627d6 add constructor for test module type 2026-05-19 21:55:43 -05:00
dependabot[bot] 9f47318b7a Bump go.bug.st/serial from 1.6.4 to 1.7.0
Bumps [go.bug.st/serial](https://github.com/bugst/go-serial) from 1.6.4 to 1.7.0.
- [Release notes](https://github.com/bugst/go-serial/releases)
- [Commits](https://github.com/bugst/go-serial/compare/v1.6.4...v1.7.0)

---
updated-dependencies:
- dependency-name: go.bug.st/serial
  dependency-version: 1.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 02:54:58 +00:00
Joel Wetzell be1e01cc3f cleanup mock router 2026-05-19 21:53:04 -05:00
Joel Wetzell 434dcec25f Merge pull request #165 from jwetzell/remove-otel-tracing
remove otel tracing
2026-05-19 21:52:48 -05:00
Joel Wetzell d871eaaa16 remove otel tracing 2026-05-19 21:50:38 -05:00
Joel Wetzell 06a2a156c5 remove unused router level output 2026-05-19 21:46:48 -05:00
Joel Wetzell 7913f5febc Merge pull request #164 from jwetzell/router-output-rename
rename router.output to module.output
2026-05-19 21:38:48 -05:00
Joel Wetzell 99132c90c6 rename router.output to module.output 2026-05-19 21:31:14 -05:00
Joel Wetzell b2e07d7452 Merge pull request #163 from jwetzell/feat/pub-sub-module-type
convert nats and mqtt to a generic pub/sub module type
2026-05-19 21:28:49 -05:00
Joel Wetzell 5def922d93 add missing test module 2026-05-19 21:27:21 -05:00
Joel Wetzell a09249a047 convert nats and mqtt to a generic pub/sub module type 2026-05-19 21:25:29 -05:00
Joel Wetzell a991b264a6 Merge pull request #160 from jwetzell/fix/database-module-interface
rework database module to not expose raw DB
2026-05-19 12:38:13 -05:00
Joel Wetzell 11bbf1a703 rework database module to not expose raw DB 2026-05-19 12:33:54 -05:00
Joel Wetzell bd8368fbb1 add nats to smoke test config 2026-05-19 09:05:41 -05:00
Joel Wetzell 54ee4a2dde Merge pull request #158 from jwetzell/module-context-handling
rework context handling throughout router and modules
2026-05-18 18:44:03 -05:00
Joel Wetzell 4415b8e892 cleanup logging 2026-05-18 18:42:37 -05:00
Joel Wetzell 95d46d0acd rework context handling throughout router and modules 2026-05-18 18:25:36 -05:00
Joel Wetzell b1be2f8d26 Merge pull request #157 from jwetzell/fix/db-query-params-schema
add missing params schema to db.query processor
2026-05-18 18:20:50 -05:00
Joel Wetzell 0ec217a439 add a kind of all-in-one test config 2026-05-18 18:18:31 -05:00
Joel Wetzell 106d7fee10 move MustCompile regex out of constructor 2026-05-18 18:17:38 -05:00
Joel Wetzell 547bc60ed7 add TODO 2026-05-18 18:17:15 -05:00
Joel Wetzell 3b158185d6 add missing params schema to db.query processor 2026-05-18 18:17:02 -05:00
Joel Wetzell 507715b885 remove goreleaser things 2026-05-17 13:54:34 -05:00
Joel Wetzell 419bcf7ad4 fix extension on windows builds 2026-05-17 13:13:41 -05:00
Joel Wetzell c52c4299e9 add release creation and artificat upload 2026-05-17 13:06:36 -05:00
Joel Wetzell 8bfcbea992 remove zig setup from macos jobs 2026-05-17 12:53:46 -05:00
Joel Wetzell b31442b794 split macos and build natively 2026-05-17 12:53:05 -05:00
Joel Wetzell 6a324a41b5 add job to setup release properties 2026-05-17 12:43:15 -05:00
Joel Wetzell 49227f9a82 add macos build job 2026-05-17 12:32:57 -05:00
Joel Wetzell 2ba309636f set CGO enabled for native go builds 2026-05-17 12:19:33 -05:00
Joel Wetzell 1d8756508d split linux build into two 2026-05-17 12:17:19 -05:00
Joel Wetzell 69e839efb2 add step to install alsa 2026-05-17 11:44:38 -05:00
Joel Wetzell dd28c40b19 only docker build and push on tag 2026-05-17 11:40:03 -05:00
Joel Wetzell 410c2c4e1f start working on getting rid of gorelease and full cross platform builds 2026-05-17 11:39:00 -05:00
Joel Wetzell e9d5688852 Merge pull request #155 from jwetzell/config-file-permission-bits
tighten up permission bits on config file writes
2026-05-17 10:09:24 -05:00
Joel Wetzell df532d7677 Merge pull request #154 from jwetzell/fix/http-server-read-header-timeout
add read header timeout to http servers
2026-05-17 10:08:00 -05:00
Joel Wetzell 727d63cc32 tighten up permission bits on config file writes 2026-05-17 10:05:27 -05:00
Joel Wetzell 9125b323ff add read header timeout to http servers 2026-05-17 10:03:48 -05:00
Joel Wetzell ad4aa91e08 minor linting things 2026-05-17 09:50:08 -05:00
Joel Wetzell 4bdfe0b00a add error return to database module 2026-05-17 09:50:01 -05:00
Joel Wetzell 2fdf6d1a61 rearrange error return 2026-05-17 09:49:36 -05:00
Joel Wetzell b4d4ef3421 cleanup some error handling lint issues 2026-05-17 09:48:49 -05:00
Joel Wetzell 8752b35ea4 Merge pull request #153 from jwetzell/fix/serial-client-closing
close serial client more reliably
2026-05-17 09:06:36 -05:00
Joel Wetzell e5c8e01bf0 close serial client more reliably 2026-05-17 08:37:11 -05:00
Joel Wetzell 91063f50da better port error message for serial.client 2026-05-17 08:35:59 -05:00
Joel Wetzell 08941ceaa0 Merge pull request #152 from jwetzell/fix/serial-client-schema
fix params schema for serial.client
2026-05-17 07:54:59 -05:00
Joel Wetzell acb834405e fix params schema for serial.client 2026-05-17 07:53:20 -05:00
Joel Wetzell ac2930b91b take reference to module for future process calls 2026-05-14 22:35:31 -05:00
Joel Wetzell d16e247a3a update artnet-go 2026-05-14 20:50:40 -05:00
Joel Wetzell a134a8929f update free-d-go 2026-05-14 19:50:38 -05:00
Joel Wetzell ad91be50c4 pre-intialize and instance of the WASM script to use in process 2026-05-14 19:50:38 -05:00
Joel Wetzell d4abe7ed2d todo's 2026-05-14 19:50:38 -05:00
Joel Wetzell 0912949f31 add return early option for slice casting 2026-05-14 19:50:38 -05:00
Joel Wetzell b4b5e17265 Merge pull request #151 from jwetzell/benchmarks
add basic benchmark for most processors
2026-05-14 19:48:31 -05:00
Joel Wetzell 9eb05c8360 add basic benchmark for most processors 2026-05-14 19:44:19 -05:00
Joel Wetzell fa4997ba78 fix websocket connection disconnect and auto reconnect 2026-05-13 19:59:30 -05:00
Joel Wetzell dc0479c713 Merge pull request #149 from jwetzell/dependabot/go_modules/modernc.org/sqlite-1.50.1
Bump modernc.org/sqlite from 1.50.0 to 1.50.1
2026-05-13 19:24:53 -05:00
Joel Wetzell 4636ec0d44 Merge pull request #150 from jwetzell/websocket-client
add websocket.client module
2026-05-13 19:21:40 -05:00
Joel Wetzell 5d8df7ed2a replace sqlite version query 2026-05-13 19:21:22 -05:00
Joel Wetzell b58beb5568 Merge pull request #148 from jwetzell/dependabot/go_modules/github.com/urfave/cli/v3-3.9.0
Bump github.com/urfave/cli/v3 from 3.8.0 to 3.9.0
2026-05-13 19:18:21 -05:00
Joel Wetzell 58ffc6a12b add websocket.client module 2026-05-13 19:11:48 -05:00
dependabot[bot] 526e3afa5f Bump modernc.org/sqlite from 1.50.0 to 1.50.1
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.50.0 to 1.50.1.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.50.0...v1.50.1)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.50.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-12 22:17:30 +00:00
dependabot[bot] ea977f4634 Bump github.com/urfave/cli/v3 from 3.8.0 to 3.9.0
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.8.0 to 3.9.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.8.0...v3.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-12 22:17:19 +00:00
Joel Wetzell 9d22f9fc47 chore: updates 2026-05-09 13:49:48 -05:00
Joel Wetzell 7367f55fa9 chore: go fix 2026-05-09 13:37:02 -05:00
Joel Wetzell 1ac93ebe21 Merge pull request #147 from jwetzell/limit-context-values
pull all non-request scoped values out of context
2026-05-06 21:34:55 -05:00
Joel Wetzell 833bd529d6 pull all non-request scoped values out of context 2026-05-06 21:33:16 -05:00
Joel Wetzell eb25c73f3a Merge pull request #146 from jwetzell/api-decouple
work towards decoupling api from router
2026-05-06 17:18:31 -05:00
Joel Wetzell 984cb435d5 work towards decoupling api from router 2026-05-06 17:16:45 -05:00
Joel Wetzell 427d69d443 Merge pull request #144 from jwetzell/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.14.0
Bump github.com/nats-io/nats-server/v2 from 2.12.8 to 2.14.0
2026-05-05 17:16:09 -05:00
Joel Wetzell ecb5b1ce12 Merge pull request #145 from jwetzell/dependabot/go_modules/modernc.org/quickjs-0.18.1
Bump modernc.org/quickjs from 0.18.0 to 0.18.1
2026-05-05 17:15:54 -05:00
dependabot[bot] b81a1344b3 Bump modernc.org/quickjs from 0.18.0 to 0.18.1
Bumps [modernc.org/quickjs](https://gitlab.com/cznic/quickjs) from 0.18.0 to 0.18.1.
- [Commits](https://gitlab.com/cznic/quickjs/compare/v0.18.0...v0.18.1)

---
updated-dependencies:
- dependency-name: modernc.org/quickjs
  dependency-version: 0.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-05 17:28:46 +00:00
dependabot[bot] eca1db9605 Bump github.com/nats-io/nats-server/v2 from 2.12.8 to 2.14.0
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.12.8 to 2.14.0.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/RELEASES.md)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.12.8...v2.14.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-version: 2.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-05 17:28:38 +00:00
Joel Wetzell c1b3b6d553 Merge pull request #141 from jwetzell/dependabot/go_modules/github.com/redis/go-redis/v9-9.19.0
Bump github.com/redis/go-redis/v9 from 9.18.0 to 9.19.0
2026-04-29 13:33:37 -05:00
Joel Wetzell e7989bd950 Merge pull request #140 from jwetzell/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.12.8
Bump github.com/nats-io/nats-server/v2 from 2.12.7 to 2.12.8
2026-04-29 13:32:55 -05:00
Joel Wetzell bc23815062 Merge pull request #139 from jwetzell/dependabot/go_modules/modernc.org/sqlite-1.50.0
Bump modernc.org/sqlite from 1.49.1 to 1.50.0
2026-04-29 13:32:46 -05:00
Joel Wetzell cb34fdfb50 Merge pull request #142 from jwetzell/dependabot/go_modules/github.com/emiago/sipgo-1.3.1
Bump github.com/emiago/sipgo from 1.3.0 to 1.3.1
2026-04-29 13:32:29 -05:00
Joel Wetzell aa8adf704e Merge pull request #143 from jwetzell/dependabot/go_modules/github.com/google/jsonschema-go-0.4.3
Bump github.com/google/jsonschema-go from 0.4.2 to 0.4.3
2026-04-29 13:32:18 -05:00
dependabot[bot] dab8f20566 Bump github.com/google/jsonschema-go from 0.4.2 to 0.4.3
Bumps [github.com/google/jsonschema-go](https://github.com/google/jsonschema-go) from 0.4.2 to 0.4.3.
- [Release notes](https://github.com/google/jsonschema-go/releases)
- [Commits](https://github.com/google/jsonschema-go/compare/v0.4.2...0.4.3)

---
updated-dependencies:
- dependency-name: github.com/google/jsonschema-go
  dependency-version: 0.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-28 17:27:28 +00:00
dependabot[bot] 4ec2976dba Bump github.com/emiago/sipgo from 1.3.0 to 1.3.1
Bumps [github.com/emiago/sipgo](https://github.com/emiago/sipgo) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/emiago/sipgo/releases)
- [Commits](https://github.com/emiago/sipgo/compare/v1.3.0...v1.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-28 17:27:22 +00:00
dependabot[bot] 333e5d7563 Bump github.com/redis/go-redis/v9 from 9.18.0 to 9.19.0
Bumps [github.com/redis/go-redis/v9](https://github.com/redis/go-redis) from 9.18.0 to 9.19.0.
- [Release notes](https://github.com/redis/go-redis/releases)
- [Changelog](https://github.com/redis/go-redis/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/redis/go-redis/compare/v9.18.0...v9.19.0)

---
updated-dependencies:
- dependency-name: github.com/redis/go-redis/v9
  dependency-version: 9.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-28 17:27:18 +00:00
dependabot[bot] f9da46cc51 Bump github.com/nats-io/nats-server/v2 from 2.12.7 to 2.12.8
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.12.7 to 2.12.8.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/RELEASES.md)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.12.7...v2.12.8)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-version: 2.12.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-28 17:27:12 +00:00
dependabot[bot] 28b705240d Bump modernc.org/sqlite from 1.49.1 to 1.50.0
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.49.1 to 1.50.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.49.1...v1.50.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.50.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-28 17:26:55 +00:00
Joel Wetzell e1f1afb05a Merge pull request #138 from jwetzell/dependabot/go_modules/modernc.org/sqlite-1.49.1 2026-04-23 01:18:36 -05:00
jwetzell d3b3021fd5 update SQLite version in test 2026-04-23 01:15:55 -05:00
Joel Wetzell 190c03f5dc Merge pull request #135 from jwetzell/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.12.7 2026-04-23 01:12:48 -05:00
Joel Wetzell c7d4af4747 Merge pull request #134 from jwetzell/dependabot/github_actions/docker/build-push-action-7.1.0 2026-04-23 01:12:35 -05:00
dependabot[bot] 094933a34b Bump modernc.org/sqlite from 1.48.2 to 1.49.1
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.48.2 to 1.49.1.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.48.2...v1.49.1)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.49.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-23 03:24:59 +00:00
dependabot[bot] f40cd181e0 Bump github.com/nats-io/nats-server/v2 from 2.12.6 to 2.12.7
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.12.6 to 2.12.7.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/RELEASES.md)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.12.6...v2.12.7)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-version: 2.12.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-23 03:24:28 +00:00
Joel Wetzell b1a35f71d2 Merge pull request #136 from jwetzell/dependabot/go_modules/modernc.org/quickjs-0.18.0
Bump modernc.org/quickjs from 0.17.2 to 0.18.0
2026-04-22 22:23:36 -05:00
Joel Wetzell fbb16cd243 Merge pull request #137 from jwetzell/dependabot/go_modules/github.com/nats-io/nats.go-1.51.0
Bump github.com/nats-io/nats.go from 1.50.0 to 1.51.0
2026-04-22 22:23:08 -05:00
dependabot[bot] 6b9cabb976 Bump github.com/nats-io/nats.go from 1.50.0 to 1.51.0
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.50.0 to 1.51.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.50.0...v1.51.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 17:26:25 +00:00
dependabot[bot] 4671d41a50 Bump modernc.org/quickjs from 0.17.2 to 0.18.0
Bumps [modernc.org/quickjs](https://gitlab.com/cznic/quickjs) from 0.17.2 to 0.18.0.
- [Commits](https://gitlab.com/cznic/quickjs/compare/v0.17.2...v0.18.0)

---
updated-dependencies:
- dependency-name: modernc.org/quickjs
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 17:26:21 +00:00
dependabot[bot] c773dd4293 Bump docker/build-push-action from 7.0.0 to 7.1.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/d08e5c354a6adb9ed34480a06d141179aa583294...bcafcacb16a39f128d818304e6c9c0c18556b85f)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 17:25:49 +00:00
Joel Wetzell f96957c235 add git to builder image 2026-04-14 07:40:35 -05:00
Joel Wetzell 1218da0091 Merge pull request #133 from jwetzell/chore/osc-upgrade
upgrade osc-go library to v0.3.0 and handle new error cases
2026-04-14 07:30:29 -05:00
Joel Wetzell f969d3484d upgrade osc-go library to v0.3.0 and handle new error cases 2026-04-14 07:28:06 -05:00
Joel Wetzell 36b085ef5c Merge pull request #130 from jwetzell/dependabot/go_modules/modernc.org/sqlite-1.48.1
Bump modernc.org/sqlite from 1.48.0 to 1.48.1
2026-04-12 20:17:36 -05:00
dependabot[bot] 65f2259cf4 Bump modernc.org/sqlite from 1.48.0 to 1.48.1
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.48.0 to 1.48.1.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.48.0...v1.48.1)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.48.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-13 01:13:28 +00:00
Joel Wetzell 2b62c593e2 Merge pull request #131 from jwetzell/dependabot/go_modules/modernc.org/quickjs-0.17.2
Bump modernc.org/quickjs from 0.17.1 to 0.17.2
2026-04-12 20:12:16 -05:00
dependabot[bot] dbcbecbf11 Bump modernc.org/quickjs from 0.17.1 to 0.17.2
Bumps [modernc.org/quickjs](https://gitlab.com/cznic/quickjs) from 0.17.1 to 0.17.2.
- [Commits](https://gitlab.com/cznic/quickjs/compare/v0.17.1...v0.17.2)

---
updated-dependencies:
- dependency-name: modernc.org/quickjs
  dependency-version: 0.17.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-13 01:10:21 +00:00
Joel Wetzell 9f9f941d13 Merge pull request #127 from jwetzell/dependabot/github_actions/docker/login-action-4.1.0
Bump docker/login-action from 4.0.0 to 4.1.0
2026-04-12 20:09:59 -05:00
Joel Wetzell 7bb0b49459 Merge pull request #132 from jwetzell/dependabot/go_modules/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp-1.43.0
Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp from 1.42.0 to 1.43.0
2026-04-12 20:08:58 -05:00
Joel Wetzell 91a89379c7 update go version 2026-04-08 07:32:29 -05:00
dependabot[bot] ebba7cd8fc 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.42.0 to 1.43.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.42.0...v1.43.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-07 17:25:41 +00:00
dependabot[bot] 5eab29b3b8 Bump docker/login-action from 4.0.0 to 4.1.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/b45d80f862d83dbcd57f89517bcf500b2ab88fb2...4907a6ddec9925e35a0a9e82d7399ccc52663121)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-07 17:24:56 +00:00
Joel Wetzell b5df389cb2 Merge pull request #123 from jwetzell/dependabot/go_modules/modernc.org/sqlite-1.48.0
Bump modernc.org/sqlite from 1.47.0 to 1.48.0
2026-04-03 19:49:45 -05:00
Joel Wetzell c1692b291e Merge pull request #124 from jwetzell/dependabot/go_modules/github.com/nats-io/nats.go-1.50.0
Bump github.com/nats-io/nats.go from 1.49.0 to 1.50.0
2026-04-03 19:49:22 -05:00
Joel Wetzell 06d5dcf3e1 Merge pull request #125 from jwetzell/dependabot/go_modules/github.com/urfave/cli/v3-3.8.0
Bump github.com/urfave/cli/v3 from 3.7.0 to 3.8.0
2026-04-03 19:49:12 -05:00
Joel Wetzell 2222d09078 Merge pull request #126 from jwetzell/dependabot/go_modules/github.com/emiago/sipgo-1.3.0
Bump github.com/emiago/sipgo from 1.2.1 to 1.3.0
2026-04-03 19:48:53 -05:00
dependabot[bot] b2598ffede Bump github.com/emiago/sipgo from 1.2.1 to 1.3.0
Bumps [github.com/emiago/sipgo](https://github.com/emiago/sipgo) from 1.2.1 to 1.3.0.
- [Release notes](https://github.com/emiago/sipgo/releases)
- [Commits](https://github.com/emiago/sipgo/compare/v1.2.1...v1.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 17:26:39 +00:00
dependabot[bot] bab0c72d97 Bump github.com/urfave/cli/v3 from 3.7.0 to 3.8.0
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.7.0 to 3.8.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.7.0...v3.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 17:26:34 +00:00
dependabot[bot] 62d86bc79d Bump github.com/nats-io/nats.go from 1.49.0 to 1.50.0
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.49.0 to 1.50.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.49.0...v1.50.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 17:26:25 +00:00
dependabot[bot] 5fe6a35b5b Bump modernc.org/sqlite from 1.47.0 to 1.48.0
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.47.0 to 1.48.0.
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.47.0...v1.48.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-version: 1.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 17:26:14 +00:00
Joel Wetzell f57f9d8ce5 fix additionProperties in JSONSchema and add IDs to everything 2026-03-30 20:58:39 -05:00
Joel Wetzell 979addeff8 set default for whole api section of config 2026-03-30 20:17:36 -05:00
Joel Wetzell af92dbcce3 Merge pull request #122 from jwetzell/chore/separate-midi-message-create
convert midi.message.create into separate processors
2026-03-28 15:44:58 -05:00
Joel Wetzell 87947527d6 conver midi.message.create into separate processors 2026-03-28 15:43:13 -05:00
Joel Wetzell 882af2948a fix JSON schema of net.udp.server 2026-03-26 18:56:03 -05:00
Joel Wetzell e2e9fb5eb6 add other methods to http request schema 2026-03-26 18:20:02 -05:00
Joel Wetzell 411888d8db align schema of udp server with reality 2026-03-26 15:13:26 -05:00
Joel Wetzell e367d6eb5d Merge pull request #120 from jwetzell/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.12.6
Bump github.com/nats-io/nats-server/v2 from 2.12.5 to 2.12.6
2026-03-26 15:05:19 -05:00
Joel Wetzell 7659f412fb Delete .github/workflows/label-pr.yaml 2026-03-26 15:04:33 -05:00
Joel Wetzell 85964f5e25 Delete .github/labeler.yml 2026-03-26 15:04:25 -05:00
dependabot[bot] b9e8bb66c6 Bump github.com/nats-io/nats-server/v2 from 2.12.5 to 2.12.6
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.12.5 to 2.12.6.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/RELEASES.md)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.12.5...v2.12.6)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-version: 2.12.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-24 17:26:08 +00:00
Joel Wetzell 2f9ca82b81 add redis to protocols 2026-03-23 20:47:27 -05:00
Joel Wetzell 25579cb941 update cmd help 2026-03-23 20:42:02 -05:00
Joel Wetzell 9134f034c7 Merge pull request #119 from jwetzell/feat/env-vars-cmd-config
add support for sourcing flags from ENV vars
2026-03-23 20:40:25 -05:00
Joel Wetzell 204ccab683 add support for sourcing flags from ENV vars 2026-03-23 20:39:02 -05:00
Joel Wetzell 1361e16b28 Merge pull request #118 from jwetzell/fix/format-redis-client-logging
cleanup redis logging
2026-03-23 20:17:52 -05:00
Joel Wetzell 6c9d1b317d cleanup redis logging 2026-03-23 20:16:09 -05:00
Joel Wetzell 53e5e7db9e Merge pull request #117 from jwetzell/feat/validate-config-updates-with-schema
validate config updates via cmd and api with schema
2026-03-23 20:14:56 -05:00
Joel Wetzell 13f7b9e927 validate config updates via cmd and api with schema 2026-03-23 20:12:26 -05:00
Joel Wetzell 9a50ca8cfe move schema to an internal package 2026-03-23 12:51:26 -05:00
Joel Wetzell 256eeac6a8 Merge pull request #116 from jwetzell/feat/dynamic-json-schema
generate json-schema dynamically in Go
2026-03-23 12:15:45 -05:00
Joel Wetzell 842495f010 generate json-schema dynamically in Go 2026-03-23 12:11:10 -05:00
Joel Wetzell 0922ece656 rename filter.unique test as well 2026-03-23 10:25:48 -05:00
Joel Wetzell 4c7dd1b4d8 rename filter.unique to filter.change 2026-03-23 10:16:17 -05:00
Joel Wetzell 2fe2250a57 fix filter regex test names 2026-03-23 00:36:08 -05:00
Joel Wetzell c91f14185a Merge pull request #114 from jwetzell/feat/filter-unique-processor
add processor to filter out unique values
2026-03-23 00:35:39 -05:00
Joel Wetzell e32776b02c add processor to filter out unique values 2026-03-23 00:29:42 -05:00
Joel Wetzell 5bb3f08006 add more tests for bad GetAnyAs scenarios 2026-03-22 22:47:43 -05:00
Joel Wetzell a0f3ee3b05 add tests for wrapped payload 2026-03-22 22:39:41 -05:00
Joel Wetzell 9843c116b2 move test implementations to a shared internal package 2026-03-22 22:39:29 -05:00
Joel Wetzell 71b6a6d4a8 add test layout for router input/output processors 2026-03-22 21:58:17 -05:00
Joel Wetzell bb3d939bcd test script slice input and output 2026-03-22 21:44:55 -05:00
Joel Wetzell 97742d7e59 add test for no rows to db.query 2026-03-22 21:30:09 -05:00
Joel Wetzell 9646c3f2d3 add tests for correct module type from processors context 2026-03-22 21:27:20 -05:00
Joel Wetzell 26dc976565 add test for extended OSC types 2026-03-22 21:26:37 -05:00
Joel Wetzell 70f64e83c7 add tests to check getting modules from context 2026-03-22 21:00:11 -05:00
Joel Wetzell 13e5d4d85a add tests for db and kv processors 2026-03-22 20:41:39 -05:00
Joel Wetzell bfd0d3a90a add test module with kv and db capabilities to processor tests 2026-03-22 20:41:21 -05:00
Joel Wetzell 60e9253b51 add registry and config tests for db.sqlite and redis.client 2026-03-22 20:40:37 -05:00
Joel Wetzell 5f056496ce remove Output function from non-output modules 2026-03-22 20:03:50 -05:00
Joel Wetzell 279952f1ea add tests for comman GetAnyAs functions 2026-03-22 19:57:53 -05:00
Joel Wetzell 94bc22928b add tests for bad params Get scenarios 2026-03-22 19:39:24 -05:00
Joel Wetzell e5e1ea9f63 use http fileserver to serve schema directory 2026-03-21 11:51:49 -05:00
Joel Wetzell 1cdfe929b4 block config updates if one is already in progress 2026-03-21 11:32:11 -05:00
Joel Wetzell 012c416432 use GetAnyAsByteSlice whenever possible 2026-03-18 21:57:21 -05:00
Joel Wetzell 0763b4d720 Merge pull request #113 from jwetzell/feat/script-js-array-return
try processing return value of script.js as an array and a map
2026-03-18 21:54:47 -05:00
Joel Wetzell 14f3ae74a7 try processing return value of script.js as an array and a map 2026-03-18 21:51:25 -05:00
Joel Wetzell c2b73ac710 convert query to template 2026-03-18 20:54:51 -05:00
Joel Wetzell 57262c3377 actually use value template in Set 2026-03-18 20:54:42 -05:00
Joel Wetzell 1fb59cc947 type Modules property of WrappedPayload 2026-03-18 16:24:49 -05:00
Joel Wetzell 2fbca6209e make an OutputModule interface and remove output from modules that don't implement it 2026-03-18 16:24:19 -05:00
Joel Wetzell 3fedb7ac92 Merge pull request #112 from jwetzell/feat/sqlite-module
add module and processor for interacting with SQLite DB
2026-03-18 14:35:48 -05:00
Joel Wetzell 3f4271b5ef add module and processor for interacting with SQLite DB 2026-03-18 14:21:36 -05:00
Joel Wetzell 1e3b2571fb Merge pull request #111 from jwetzell/feat/kv-processors
more generic key value module things
2026-03-18 14:20:12 -05:00
Joel Wetzell 4559c6efbd add processors for key value operations 2026-03-18 14:18:07 -05:00
Joel Wetzell 7e75fdb758 shift Get method to only key/value modules add Set method to redis.client 2026-03-18 12:53:50 -05:00
Joel Wetzell 2127c6fd5b move module interface to common package 2026-03-18 12:47:28 -05:00
Joel Wetzell 2a40fa561c add test for multiple return values 2026-03-17 19:33:41 -05:00
Joel Wetzell 1467d9de16 add test for slice to struct get processors 2026-03-17 19:17:10 -05:00
Joel Wetzell 5fc7b7399f Merge pull request #110 from jwetzell/feat/api-server-enabled-option
add option to enable api server
2026-03-17 19:12:40 -05:00
Joel Wetzell 3d664fff67 add option to enable api server 2026-03-17 19:03:43 -05:00
Joel Wetzell 9c03ad2c14 test getting running config from router 2026-03-17 18:58:18 -05:00
Joel Wetzell 35262fd505 Merge pull request #109 from jwetzell/dependabot/go_modules/github.com/emiago/diago-0.28.0
Bump github.com/emiago/diago from 0.27.0 to 0.28.0
2026-03-17 13:28:19 -05:00
dependabot[bot] feccab91d5 Bump github.com/emiago/diago from 0.27.0 to 0.28.0
Bumps [github.com/emiago/diago](https://github.com/emiago/diago) from 0.27.0 to 0.28.0.
- [Release notes](https://github.com/emiago/diago/releases)
- [Commits](https://github.com/emiago/diago/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: github.com/emiago/diago
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-17 17:26:19 +00:00
Joel Wetzell b17e0489a5 switch test wasm plugin to version that does not require WASI 2026-03-17 12:06:12 -05:00
Joel Wetzell 97c1975c7f Merge pull request #108 from jwetzell/chore/remove-mqtt-message-encode
remove badly shaped mqtt.message.encode processor
2026-03-17 12:05:00 -05:00
Joel Wetzell 6336800641 remove badly shaped mqtt.message.encode processor 2026-03-17 12:04:01 -05:00
Joel Wetzell d9ac6d1a85 add good tests for sip processors 2026-03-16 22:00:25 -05:00
Joel Wetzell be3b9e3d5a add tests for sip processors 2026-03-16 21:58:10 -05:00
Joel Wetzell 44cc2e322c add missing test boilerplate for processors 2026-03-16 21:49:13 -05:00
Joel Wetzell 5f9547fddb add test for http.response.create 2026-03-16 21:32:30 -05:00
Joel Wetzell 8d8c5d4073 add test for http.requrest.do 2026-03-16 20:43:35 -05:00
Joel Wetzell 6d382ecd20 add test for midi.message.unpack 2026-03-16 20:38:43 -05:00
Joel Wetzell f0aa5a9eb0 add test for nats.message.create 2026-03-16 20:28:21 -05:00
Joel Wetzell e84bd6ead4 fix test error messages 2026-03-16 20:21:57 -05:00
Joel Wetzell 3d54ac7a0d add more bad test cases for freed.create 2026-03-16 20:13:19 -05:00
Joel Wetzell 2eae266903 fix template error handling in freed.create 2026-03-16 20:12:59 -05:00
Joel Wetzell c7d407d507 add bad test for freed.create 2026-03-16 19:59:02 -05:00
Joel Wetzell 2d74e14387 add tests for freed processors 2026-03-16 19:36:32 -05:00
Joel Wetzell e2a0f4ef90 Merge pull request #107 from jwetzell/fix/freed-processor-roll
fix roll value in freed.create
2026-03-16 19:32:04 -05:00
Joel Wetzell 54307bc817 fix roll value in freed.create 2026-03-16 19:30:55 -05:00
Joel Wetzell e95fb44064 add Source to wrapped payload 2026-03-16 18:54:54 -05:00
Joel Wetzell 36c0e9b23e explicitly set WrappedPayload.End to false 2026-03-16 18:54:44 -05:00
Joel Wetzell b57faaccb9 Merge pull request #106 from jwetzell/feat/redis-client-module
add redis client module
2026-03-16 17:21:27 -05:00
Joel Wetzell 814bb9c1dc add redis client module 2026-03-16 17:17:49 -05:00
Joel Wetzell 6fa2c1a746 Merge pull request #105 from jwetzell/chore/wrap-payload-in-processor
wrap payload for all processors
2026-03-16 17:09:02 -05:00
Joel Wetzell f273aedbc6 wrap payload for all processors 2026-03-16 17:05:49 -05:00
Joel Wetzell b6c1c5c600 fix schema for router.input 2026-03-14 09:05:22 -05:00
Joel Wetzell b6ec615e40 comment 2026-03-13 21:37:40 -05:00
Joel Wetzell 216775c9c6 switch to read lock in getter method 2026-03-13 21:32:59 -05:00
Joel Wetzell 1bfee2186b add payload type to span attributes 2026-03-13 21:18:47 -05:00
Joel Wetzell fa612231fb fix otel import path 2026-03-13 21:18:32 -05:00
Joel Wetzell c1161c5aa9 add processor to push input into router (#104) 2026-03-13 21:17:54 -05:00
Joel Wetzell b1076ce1e4 do some manual casting for script.js payload 2026-03-12 21:38:08 -05:00
Joel Wetzell 7f5204aca0 set minimum port above 1024 for server modules 2026-03-12 17:51:46 -05:00
Joel Wetzell f3d87e92f8 Merge pull request #103 from jwetzell/feat/http-api
add an HTTP/WS API for interacting with webui
2026-03-12 17:35:53 -05:00
Joel Wetzell e50e269697 pull from config changes channel to write out to config file 2026-03-12 17:34:12 -05:00
Joel Wetzell ca6aebadef add channel for router to communicate config changes out 2026-03-12 17:33:41 -05:00
Joel Wetzell 818ddc18f4 go mod tidy 2026-03-12 17:13:12 -05:00
Joel Wetzell 3e7b245536 add config reloading via API endpoint and more WS work 2026-03-12 17:04:21 -05:00
Joel Wetzell e996d84234 udpate module and route config error objects for JSON 2026-03-12 17:03:03 -05:00
Joel Wetzell 04c8ebbe06 add api config to example config 2026-03-11 21:07:30 -05:00
Joel Wetzell 0f57e123ce start work on http/ws api 2026-03-11 20:58:53 -05:00
Joel Wetzell 82ba1d5d10 log out errors running modules 2026-03-11 12:49:08 -05:00
Joel Wetzell 20c53e4996 Merge pull request #101 from jwetzell/dependabot/go_modules/github.com/nats-io/nats-server/v2-2.12.5
Bump github.com/nats-io/nats-server/v2 from 2.12.4 to 2.12.5
2026-03-11 10:03:20 -05:00
Joel Wetzell 8132faaba6 reuse quickjs objects in script.js processor 2026-03-10 18:14:50 -05:00
Joel Wetzell 5a0f21bd64 move any helper methods to common and reuse for params getter 2026-03-10 18:14:30 -05:00
dependabot[bot] fd0f78733c Bump github.com/nats-io/nats-server/v2 from 2.12.4 to 2.12.5
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.12.4 to 2.12.5.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/RELEASES.md)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.12.4...v2.12.5)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-version: 2.12.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 17:26:30 +00:00
Joel Wetzell 65476d5ecc rename files in common package 2026-03-09 14:13:17 -05:00
Joel Wetzell 536bc2af4e Merge pull request #96 from jwetzell/dependabot/go_modules/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp-1.42.0
Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp from 1.41.0 to 1.42.0
2026-03-08 17:35:41 -05:00
dependabot[bot] b89d907759 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.41.0 to 1.42.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.41.0...v1.42.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-08 22:34:42 +00:00
Joel Wetzell a3c48de2ad Merge pull request #97 from jwetzell/dependabot/go_modules/go.opentelemetry.io/otel/sdk-1.42.0
Bump go.opentelemetry.io/otel/sdk from 1.41.0 to 1.42.0
2026-03-08 17:33:34 -05:00
dependabot[bot] 8ca9d9940f Bump go.opentelemetry.io/otel/sdk from 1.41.0 to 1.42.0
Bumps [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) from 1.41.0 to 1.42.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.41.0...v1.42.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/sdk
  dependency-version: 1.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-08 22:27:05 +00:00
Joel Wetzell 53d0648774 Merge pull request #98 from jwetzell/dependabot/go_modules/go.opentelemetry.io/otel-1.42.0
Bump go.opentelemetry.io/otel from 1.41.0 to 1.42.0
2026-03-08 17:26:02 -05:00
Joel Wetzell cf2881e5c3 Merge pull request #94 from jwetzell/dependabot/go_modules/go.opentelemetry.io/otel/trace-1.42.0
Bump go.opentelemetry.io/otel/trace from 1.41.0 to 1.42.0
2026-03-08 17:25:46 -05:00
Joel Wetzell f437438dca Merge pull request #93 from jwetzell/dependabot/github_actions/docker/setup-qemu-action-4.0.0
Bump docker/setup-qemu-action from 3.7.0 to 4.0.0
2026-03-08 17:25:37 -05:00
Joel Wetzell 6b3c470534 Merge pull request #92 from jwetzell/dependabot/github_actions/docker/setup-buildx-action-4.0.0
Bump docker/setup-buildx-action from 3.12.0 to 4.0.0
2026-03-08 17:25:29 -05:00
Joel Wetzell 3bdff5309f Merge pull request #91 from jwetzell/dependabot/github_actions/docker/login-action-4.0.0
Bump docker/login-action from 3.7.0 to 4.0.0
2026-03-08 17:25:21 -05:00
Joel Wetzell 3792d40bb1 Merge pull request #90 from jwetzell/dependabot/github_actions/docker/metadata-action-6.0.0
Bump docker/metadata-action from 5.10.0 to 6.0.0
2026-03-08 17:25:10 -05:00
Joel Wetzell 0cca2e3be9 Merge pull request #95 from jwetzell/dependabot/github_actions/docker/build-push-action-7.0.0
Bump docker/build-push-action from 6.19.2 to 7.0.0
2026-03-08 17:24:41 -05:00
Joel Wetzell 5447781232 Merge pull request #99 from jwetzell/dependabot/go_modules/github.com/emiago/sipgo-1.2.1
Bump github.com/emiago/sipgo from 1.2.0 to 1.2.1
2026-03-08 17:24:25 -05:00
Joel Wetzell c760b220c0 Merge pull request #100 from jwetzell/feat/filter-regex
rename string.filter to filter.regex
2026-03-08 17:22:17 -05:00
Joel Wetzell cb71b5c5b8 rename string.filter to filter.regex 2026-03-08 17:19:49 -05:00
dependabot[bot] 84fd7c1028 Bump github.com/emiago/sipgo from 1.2.0 to 1.2.1
Bumps [github.com/emiago/sipgo](https://github.com/emiago/sipgo) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/emiago/sipgo/releases)
- [Commits](https://github.com/emiago/sipgo/compare/v1.2.0...v1.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-08 20:33:56 +00:00
dependabot[bot] 7bdf6e01b3 Bump go.opentelemetry.io/otel from 1.41.0 to 1.42.0
Bumps [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) from 1.41.0 to 1.42.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.41.0...v1.42.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel
  dependency-version: 1.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-08 20:33:51 +00:00
dependabot[bot] 1b607f64cd Bump go.opentelemetry.io/otel/trace from 1.41.0 to 1.42.0
Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.41.0 to 1.42.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.41.0...v1.42.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-version: 1.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-08 20:33:36 +00:00
dependabot[bot] 28e9141cbc Bump docker/build-push-action from 6.19.2 to 7.0.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.19.2 to 7.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/10e90e3645eae34f1e60eeb005ba3a3d33f178e8...d08e5c354a6adb9ed34480a06d141179aa583294)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-08 20:33:36 +00:00
dependabot[bot] 3b38110ffb Bump docker/setup-qemu-action from 3.7.0 to 4.0.0
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.7.0 to 4.0.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/c7c53464625b32c7a7e944ae62b3e17d2b600130...ce360397dd3f832beb865e1373c09c0e9f86d70a)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-08 20:33:32 +00:00
dependabot[bot] 25e8563cd1 Bump docker/setup-buildx-action from 3.12.0 to 4.0.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.12.0 to 4.0.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/8d2750c68a42422c14e847fe6c8ac0403b4cbd6f...4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-08 20:33:29 +00:00
dependabot[bot] 80969ac1ff Bump docker/login-action from 3.7.0 to 4.0.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.7.0 to 4.0.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/c94ce9fb468520275223c153574b00df6fe4bcc9...b45d80f862d83dbcd57f89517bcf500b2ab88fb2)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-08 20:33:24 +00:00
dependabot[bot] beb3664e3b Bump docker/metadata-action from 5.10.0 to 6.0.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.10.0 to 6.0.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/c299e40c65443455700f0fdfc63efafe5b349051...030e881283bb7a6894de51c315a6bfe6a94e05cf)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-08 20:33:21 +00:00
Joel Wetzell 6e53cbe8fd format README 2026-03-08 15:32:36 -05:00
Joel Wetzell 4ebaa48f93 yaml formatting 2026-03-08 15:32:27 -05:00
Joel Wetzell 79d535c283 json formatting 2026-03-08 15:31:04 -05:00
Joel Wetzell 85349f8540 Merge pull request #89 from jwetzell/feat/int-scale
add processor to scale int values
2026-03-08 15:29:27 -05:00
Joel Wetzell e49900a3a7 add processor to scale int values 2026-03-08 15:28:05 -05:00
Joel Wetzell 1f706becdc fix go version in dockerfile 2026-03-08 14:20:28 -05:00
Joel Wetzell 13a494239e Merge pull request #88 from jwetzell/remove-specific-filter-processors
remove specific filter processors
2026-03-08 14:13:11 -05:00
Joel Wetzell 153de944a2 remove filter processors 2026-03-08 14:06:28 -05:00
Joel Wetzell 42c75074fe update Go version 2026-03-08 13:38:52 -05:00
Joel Wetzell 361b07ec00 fix error message 2026-03-08 13:36:06 -05:00
Joel Wetzell 8c4f0591e1 Merge pull request #87 from jwetzell/feat/script-processor-env-data
consistent script processor environments
2026-03-08 13:34:01 -05:00
Joel Wetzell 09ddc40f1f consistent script processor environments 2026-03-08 13:32:34 -05:00
Joel Wetzell 6382cf6944 Merge pull request #86 from jwetzell/feat/struct-get-pointer
allow struct based processors to also operate on pointers to structs
2026-03-08 13:16:08 -05:00
Joel Wetzell 0732113a02 allow struct based processors to also operate on pointers to structs 2026-03-08 13:14:50 -05:00
Joel Wetzell e5db9a48a9 Merge pull request #85 from jwetzell/feat/json-decode-bytes
allow json.decode to take byte slice and string
2026-03-08 13:11:17 -05:00
Joel Wetzell 9502201261 allow json.decode to take byte slice and string 2026-03-08 13:09:22 -05:00
Joel Wetzell 9859aa8fd4 Merge pull request #84 from jwetzell/feat/http-client-processor
make http client into a processor instead of module
2026-03-08 13:08:26 -05:00
Joel Wetzell b7b05cbb77 make http client into a processor instead of module 2026-03-08 13:03:54 -05:00
Joel Wetzell 7b1fe47039 align variable names 2026-03-08 09:36:59 -05:00
Joel Wetzell 0848d8ab6e add descriptions to time module params 2026-03-08 09:36:31 -05:00
Joel Wetzell c1bb2ff4cb update schema descriptions 2026-03-08 09:22:57 -05:00
Joel Wetzell 50898b9130 add missing title for params property 2026-03-07 11:49:29 -06:00
Joel Wetzell 05e68e90af add title and descriptions for params schemas 2026-03-06 18:59:27 -06:00
Joel Wetzell cd4c3f59f2 fix missing properties on parse processors 2026-03-06 18:37:04 -06:00
Joel Wetzell 50399fa811 update titles on module schemas 2026-03-06 18:36:49 -06:00
Joel Wetzell 2dd44a11fd Merge pull request #83 from jwetzell/feat/router-output-as-processor
switch router output to be a processor instead of specific output per route
2026-03-04 21:34:10 -06:00
Joel Wetzell b7a8b04a72 switch router output to be a processor instead of specific output per route 2026-03-04 21:21:11 -06:00
Joel Wetzell 078e6ec68c more detailed error message 2026-03-04 19:42:48 -06:00
Joel Wetzell b58187d6a9 Merge pull request #82 from jwetzell/feat/sender-template-data
add sender to template data for relevant modules
2026-03-04 19:39:54 -06:00
Joel Wetzell 38b8e44f04 add sender to template data for relevant modules 2026-03-04 19:38:30 -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
Joel Wetzell fb599f1057 Merge pull request #72 from jwetzell/feat/struct-processors
add processors to pull data out of struct
2026-03-01 21:57:08 -06:00
Joel Wetzell a2efed0ee2 add processor to pull method out of struct 2026-03-01 21:47:37 -06:00
Joel Wetzell 533fe150c8 add processor to pull field out of struct 2026-03-01 21:47:31 -06:00
Joel Wetzell b653179e6d add functions to test struct 2026-03-01 21:44:41 -06:00
Joel Wetzell 695cabf15e move test struct to common place 2026-03-01 21:36:12 -06:00
Joel Wetzell ccac116f8d fix error message 2026-03-01 15:00:09 -06:00
Joel Wetzell c298f63ffc add convenience method to pull params from config 2026-03-01 14:57:19 -06:00
Joel Wetzell 183182e6cd use control instead of controller in midi.message.create 2026-03-01 14:21:36 -06:00
Joel Wetzell 01f172dbee Merge pull request #71 from jwetzell/feat/nats-server-module
add nats.server module
2026-03-01 13:18:28 -06:00
Joel Wetzell 20fd4170ed handle param values that aren't from loading in JSON 2026-03-01 13:16:13 -06:00
Joel Wetzell bcf9299505 remove nats message encode 2026-03-01 13:03:26 -06:00
Joel Wetzell 4f7820af5e add basic nats server module 2026-03-01 13:02:55 -06:00
Joel Wetzell 87fcc63068 Merge pull request #70 from jwetzell/dependabot/go_modules/github.com/nats-io/nats.go-1.49.0
Bump github.com/nats-io/nats.go from 1.48.0 to 1.49.0
2026-02-27 08:31:56 -06:00
dependabot[bot] 1c49776662 Bump github.com/nats-io/nats.go from 1.48.0 to 1.49.0
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.48.0 to 1.49.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.48.0...v1.49.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-27 14:21:09 +00:00
Joel Wetzell 1a7207ae4c Merge pull request #66 from jwetzell/dependabot/github_actions/docker/build-push-action-6.19.2
Bump docker/build-push-action from 6.18.0 to 6.19.2
2026-02-27 08:20:11 -06:00
Joel Wetzell f86a34cc92 Merge pull request #67 from jwetzell/dependabot/go_modules/github.com/expr-lang/expr-1.17.8
Bump github.com/expr-lang/expr from 1.17.7 to 1.17.8
2026-02-27 08:19:46 -06:00
Joel Wetzell d9d64bd248 Merge pull request #68 from jwetzell/dependabot/go_modules/gitlab.com/gomidi/midi/v2-2.3.22
Bump gitlab.com/gomidi/midi/v2 from 2.3.20 to 2.3.22
2026-02-27 08:19:24 -06:00
Joel Wetzell e828dc1b0b Merge pull request #69 from jwetzell/dependabot/github_actions/goreleaser/goreleaser-action-7
Bump goreleaser/goreleaser-action from 6 to 7
2026-02-27 08:19:08 -06:00
dependabot[bot] cc4d50bc17 Bump goreleaser/goreleaser-action from 6 to 7
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 6 to 7.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-23 19:22:05 +00:00
Joel Wetzell abd37439c5 rework Route struct 2026-02-16 19:13:04 -06:00
Joel Wetzell 8c3f93b601 update tracer name for processor 2026-02-16 18:57:44 -06:00
Joel Wetzell 2f7f6967e1 update tracer name for module 2026-02-16 18:25:30 -06:00
Joel Wetzell 09b030efa6 rework otel tracer providing 2026-02-16 17:11:43 -06:00
dependabot[bot] bb49dba22e Bump gitlab.com/gomidi/midi/v2 from 2.3.20 to 2.3.22
Bumps [gitlab.com/gomidi/midi/v2](https://gitlab.com/gomidi/midi) from 2.3.20 to 2.3.22.
- [Commits](https://gitlab.com/gomidi/midi/compare/v2.3.20...v2.3.22)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 18:21:08 +00:00
dependabot[bot] 4ed6866731 Bump github.com/expr-lang/expr from 1.17.7 to 1.17.8
Bumps [github.com/expr-lang/expr](https://github.com/expr-lang/expr) from 1.17.7 to 1.17.8.
- [Release notes](https://github.com/expr-lang/expr/releases)
- [Commits](https://github.com/expr-lang/expr/compare/v1.17.7...v1.17.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 18:21:00 +00:00
dependabot[bot] 2d392873ca Bump docker/build-push-action from 6.18.0 to 6.19.2
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.18.0 to 6.19.2.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/263435318d21b8e681c14492fe198d362a7d2c83...10e90e3645eae34f1e60eeb005ba3a3d33f178e8)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.19.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 18:20:37 +00:00
Joel Wetzell 11e25ab8f7 require id properties to be at least a one character 2026-02-13 21:46:46 -06:00
Joel Wetzell 87e6b09156 fix random processors schemas 2026-02-13 21:45:19 -06:00
Joel Wetzell 8cb2a0e9f0 add more tests for osc.message.create 2026-02-10 21:23:11 -06:00
Joel Wetzell 263943f4d6 add tests for osc.message.create and filter 2026-02-10 21:16:02 -06:00
Joel Wetzell f9865765c6 fix test names for script.wasm 2026-02-10 21:15:52 -06:00
Joel Wetzell e6aaffbb95 add error tests for script.wasm 2026-02-10 20:49:40 -06:00
Joel Wetzell 90a14b4d1b add more tests for router 2026-02-10 20:42:52 -06:00
Joel Wetzell 8bdd338a27 Merge pull request #59 from jwetzell/dependabot/go_modules/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp-1.40.0
Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp from 1.28.0 to 1.40.0
2026-02-09 23:23:18 -06:00
Joel Wetzell b20c16be4f Merge pull request #62 from jwetzell/dependabot/go_modules/github.com/emiago/diago-0.27.0
Bump github.com/emiago/diago from 0.26.2 to 0.27.0
2026-02-09 23:22:55 -06:00
Joel Wetzell de060057e6 Merge pull request #61 from jwetzell/dependabot/go_modules/gitlab.com/gomidi/midi/v2-2.3.20
Bump gitlab.com/gomidi/midi/v2 from 2.3.18 to 2.3.20
2026-02-09 23:22:44 -06:00
Joel Wetzell 4526e805af add basic tests for mqtt.message.encode 2026-02-09 23:18:19 -06:00
Joel Wetzell 1d8baa8c17 add function to create new MQTTMessage 2026-02-09 23:17:53 -06:00
Joel Wetzell 060c12512c generate coverage file manually to filter out cmd package 2026-02-09 23:09:57 -06:00
Joel Wetzell 444093d375 add tests for error json decode/encode error scenarios 2026-02-09 22:28:55 -06:00
Joel Wetzell a217b729cd add tests for error script.wasm error scenarios 2026-02-09 22:28:34 -06:00
Joel Wetzell ad76480008 add good/bad tests for time.sleep 2026-02-09 22:16:54 -06:00
Joel Wetzell a408e281a3 standardize processor instance creation in tests 2026-02-09 22:09:48 -06:00
Joel Wetzell f028634401 cleanup grammar on error messages 2026-02-09 21:29:12 -06:00
Joel Wetzell 961316d28b add tests for invalid addr to net servers 2026-02-09 21:23:09 -06:00
Joel Wetzell e22a941e4a fix non returning error in udp server 2026-02-09 21:19:29 -06:00
Joel Wetzell b0cffe819d add more bad test for net servers 2026-02-09 21:14:02 -06:00
Joel Wetzell 0cdd1f6e6a add bad creation tests for sip servers 2026-02-09 21:06:02 -06:00
Joel Wetzell a9245ab88c fix error message 2026-02-09 21:02:44 -06:00
Joel Wetzell d69d3a370d Merge pull request #64 from jwetzell/feat/sip-dtmf-server-useragent
add optional userAgent param to sip.dtmf.server
2026-02-09 20:58:48 -06:00
Joel Wetzell 019624f152 add optional userAgent param to sip.dtmf.server 2026-02-09 20:51:03 -06:00
Joel Wetzell 90a3119fb8 fix variable name 2026-02-09 20:48:07 -06:00
Joel Wetzell 54c14cbbae make nil payload a debug log 2026-02-09 19:57:17 -06:00
Joel Wetzell 969ac6e04b add test for module registraion 2026-02-09 19:45:04 -06:00
Joel Wetzell bfa63499c3 add basic tests for bad module creation 2026-02-09 19:44:54 -06:00
Joel Wetzell 6b178d1ae4 cleanup error messages 2026-02-09 19:15:34 -06:00
Joel Wetzell 988437fccf add test for loading from registry for time modules 2026-02-09 18:19:17 -06:00
Joel Wetzell dc19d18b81 add tests to ensure module id returns correctly 2026-02-09 18:17:42 -06:00
Joel Wetzell 86f1082159 add test for loading from registry for all modules 2026-02-09 18:11:08 -06:00
Joel Wetzell a46054c427 fix net.udp.server type return value 2026-02-09 18:10:28 -06:00
Joel Wetzell 9dc4706fd8 add test for loading from registry for rest of processors 2026-02-09 17:42:16 -06:00
Joel Wetzell f91cb9dbbf add basic tests for script.wasm 2026-02-09 15:07:18 -06:00
Joel Wetzell 10b5cbae6c add coverage badge 2026-02-09 15:06:04 -06:00
Joel Wetzell a9f9f1ba2c Merge pull request #63 from jwetzell/feat/script-wasm-wasi
add option to enable WASI for script.wasm processor
2026-02-09 14:55:18 -06:00
Joel Wetzell 8e27dc81a2 add option to enable WASI for script.wasm processor 2026-02-09 14:52:41 -06:00
dependabot[bot] 42bd5c46b5 Bump github.com/emiago/diago from 0.26.2 to 0.27.0
Bumps [github.com/emiago/diago](https://github.com/emiago/diago) from 0.26.2 to 0.27.0.
- [Release notes](https://github.com/emiago/diago/releases)
- [Commits](https://github.com/emiago/diago/compare/v0.26.2...v0.27.0)

---
updated-dependencies:
- dependency-name: github.com/emiago/diago
  dependency-version: 0.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 20:50:55 +00:00
dependabot[bot] 515a8d14fd Bump gitlab.com/gomidi/midi/v2 from 2.3.18 to 2.3.20
Bumps [gitlab.com/gomidi/midi/v2](https://gitlab.com/gomidi/midi) from 2.3.18 to 2.3.20.
- [Commits](https://gitlab.com/gomidi/midi/compare/v2.3.18...v2.3.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 20:50:51 +00:00
dependabot[bot] 1f3a2b9076 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.28.0 to 1.40.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.28.0...v1.40.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 20:50:36 +00:00
Joel Wetzell 5dc897c4b2 allow contents write permission 2026-02-09 14:49:44 -06:00
Joel Wetzell 7015a9d7a2 move tests to sub folders 2026-02-09 14:38:13 -06:00
Joel Wetzell 3c6d98638e add coverage report step to test workflow 2026-02-09 14:33:20 -06:00
Joel Wetzell 8ec8ad91a6 Merge pull request #58 from jwetzell/fix/sip-server-postwait
fix postwait on sip server for dtmf response
2026-02-09 12:05:44 -06:00
Joel Wetzell f919017ca6 fix postwait on sip server for dtmf response 2026-02-09 12:03:54 -06:00
Joel Wetzell 66f170047a remove arm images for now 2026-02-08 22:02:33 -06:00
Joel Wetzell 7ff20e0136 add more tests for string.split 2026-02-08 21:50:31 -06:00
Joel Wetzell b53494fddc add more error tests for string.filter 2026-02-08 21:49:30 -06:00
Joel Wetzell 3688510ded fix test names 2026-02-08 21:49:12 -06:00
Joel Wetzell d0bdc44c30 add test for string.filter 2026-02-08 21:40:53 -06:00
Joel Wetzell 1c49548a4c test cleanup 2026-02-08 21:38:44 -06:00
Joel Wetzell 4ea6c8f11f test cleanup 2026-02-08 21:31:08 -06:00
Joel Wetzell 0ef512eb28 add registry test for json.decode 2026-02-08 21:23:21 -06:00
Joel Wetzell ebbce3261c add registry test for json.encode 2026-02-08 21:21:28 -06:00
Joel Wetzell 79fa925823 clean up json.encode test 2026-02-08 21:21:14 -06:00
Joel Wetzell 659c95102b Merge pull request #57 from jwetzell/feat/json-decode-processor
add json.decode processor
2026-02-08 21:15:55 -06:00
Joel Wetzell e06c4195c7 add json.decode processor 2026-02-08 21:13:34 -06:00
Joel Wetzell 28e3c99ad8 add test loading from registry for time.sleep 2026-02-08 15:56:33 -06:00
Joel Wetzell 7312bfec20 add error tests for random processors 2026-02-08 15:56:14 -06:00
Joel Wetzell 4bbf69e644 add test for artnet.packet.filter 2026-02-08 15:55:48 -06:00
Joel Wetzell 1ba51446ad fix error messages in test 2026-02-08 15:55:32 -06:00
Joel Wetzell 2266d6a9b8 Merge pull request #56 from jwetzell/feat/json-schema
add json schema definitions for config
2026-02-08 15:04:25 -06:00
Joel Wetzell f1f340f963 add schema for rest of processors 2026-02-08 13:34:40 -06:00
Joel Wetzell 4ca989280c switch interval and timer to new namespace 2026-02-08 09:45:22 -06:00
Joel Wetzell ef83e84e84 add schema for time.sleep processor 2026-02-08 09:45:22 -06:00
Joel Wetzell b8ad912855 add JSON schema for config file 2026-02-08 09:45:22 -06:00
Joel Wetzell f766d0e2a4 Merge pull request #55 from jwetzell/feat/router-reload
reload router config on SIGHUP
2026-02-08 09:44:49 -06:00
Joel Wetzell 725e8ad670 add mutex for router operations 2026-02-07 17:07:18 -06:00
Joel Wetzell 7fa3b6d33d TODO 2026-02-07 16:29:49 -06:00
Joel Wetzell 5e957d7d03 expose running config from router 2026-02-07 16:28:06 -06:00
Joel Wetzell bd89df3da2 create app struct 2026-02-07 14:15:24 -06:00
Joel Wetzell 914f6aa833 mess around with being able to reload router 2026-02-07 13:42:55 -06:00
Joel Wetzell 2104d9f1a9 change to start/stop not run/stop 2026-02-07 12:51:01 -06:00
Joel Wetzell 457ff83c23 add more convenience methods for start/stopping modules on router 2026-02-07 12:48:27 -06:00
Joel Wetzell 37c7a23063 add debug log at beginning of running 2026-02-07 12:40:30 -06:00
Joel Wetzell 9ba3a88e8c add removeModule function to router 2026-02-07 09:53:48 -06:00
Joel Wetzell 33ecc94097 add Stop function to module 2026-02-07 09:53:38 -06:00
Joel Wetzell 8f5091cf9b load router from context in mock module 2026-02-05 20:20:00 -06:00
Joel Wetzell b095419b6e switch module instances to map on id 2026-02-05 20:02:03 -06:00
Joel Wetzell 1e872ba0b9 fix RouteIO interface 2026-02-05 19:51:09 -06:00
Joel Wetzell b959b88527 split out module and route creation in router and router output only does one module 2026-02-05 19:31:31 -06:00
Joel Wetzell 0dfbf30fa7 Merge pull request #54 from jwetzell/feat/opentelemetry
add option to export open telemetry tracing
2026-02-04 19:22:15 -06:00
Joel Wetzell abff674a75 fix router test 2026-02-04 19:17:57 -06:00
Joel Wetzell a920f34926 rework route and processing spans 2026-02-04 19:01:18 -06:00
Joel Wetzell 4dac1faae0 add basic opentelemetry 2026-02-04 19:01:18 -06:00
Joel Wetzell ab5b40ee72 remove osc.message.transform 2026-02-04 18:39:16 -06:00
Joel Wetzell 5a29139e63 Merge pull request #53 from jwetzell/chore/testing
add more testing
2026-02-04 18:38:18 -06:00
Joel Wetzell 1587f84981 add test for uint.random processor 2026-02-04 18:30:51 -06:00
Joel Wetzell 0cb13a30f9 add test for int.random processor 2026-02-04 18:30:41 -06:00
Joel Wetzell 8212d15a70 add test for module with no id 2026-02-04 18:30:25 -06:00
Joel Wetzell eee356225f Merge pull request #52 from jwetzell/fix/debug-log-payload-string
more compatible printing of payload in debug.log
2026-02-03 22:52:20 -06:00
Joel Wetzell 5cd02f9e3a more compatible printing of payload in debug.log 2026-02-03 22:50:30 -06:00
Joel Wetzell 100edab869 Merge pull request #51 from jwetzell/fix/no-default-framing-method
don't default framing method
2026-02-03 22:28:47 -06:00
Joel Wetzell fd4c2cb59b make framing parameter required in net.tcp.client 2026-02-03 22:27:58 -06:00
Joel Wetzell b6ee3c68f1 don't default framing method 2026-02-03 22:26:16 -06:00
Joel Wetzell 7e0c2b2560 Merge pull request #48 from jwetzell/dependabot/github_actions/docker/login-action-3.7.0
Bump docker/login-action from 3.6.0 to 3.7.0
2026-02-03 11:35:37 -06:00
Joel Wetzell c9a3eb80bc Merge pull request #50 from jwetzell/dependabot/go_modules/github.com/emiago/diago-0.26.2
Bump github.com/emiago/diago from 0.25.0 to 0.26.2
2026-02-03 11:35:13 -06:00
dependabot[bot] 2a5feec07a Bump github.com/emiago/diago from 0.25.0 to 0.26.2
Bumps [github.com/emiago/diago](https://github.com/emiago/diago) from 0.25.0 to 0.26.2.
- [Release notes](https://github.com/emiago/diago/releases)
- [Commits](https://github.com/emiago/diago/compare/v0.25.0...v0.26.2)

---
updated-dependencies:
- dependency-name: github.com/emiago/diago
  dependency-version: 0.26.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-02 23:54:12 +00:00
dependabot[bot] ced4bc839e Bump docker/login-action from 3.6.0 to 3.7.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.6.0 to 3.7.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/5e57cd118135c172c3672efd75eb46360885c0ef...c94ce9fb468520275223c153574b00df6fe4bcc9)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 3.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-02 23:53:47 +00:00
Joel Wetzell ce6a9ff4a8 update labeler 2026-02-02 13:14:11 -06:00
Joel Wetzell bd3b8a19d5 Merge pull request #47 from jwetzell/fix/module-id-required
add check to prevent empty module Id
2026-02-02 13:00:36 -06:00
Joel Wetzell cb738a9b8e combine route and router labels 2026-02-02 12:59:50 -06:00
Joel Wetzell 1b59dbc1e5 add router to labeler 2026-02-02 12:55:23 -06:00
Joel Wetzell c75a2327d5 add check to prevent empty module Id 2026-02-02 12:51:56 -06:00
Joel Wetzell a17be985e6 todo 2026-02-01 20:52:58 -06:00
Joel Wetzell 78737f57af Merge pull request #46 from jwetzell/chore/upgrade-artnet
update artnet dependency
2026-01-31 19:32:12 -06:00
Joel Wetzell 720c0cd52a update artnet dependency 2026-01-31 19:29:30 -06:00
Joel Wetzell 3e1d4f07f9 Merge pull request #43 from jwetzell/dependabot/go_modules/github.com/emiago/diago-0.25.0
Bump github.com/emiago/diago from 0.24.0 to 0.25.0
2026-01-20 11:47:43 -06:00
dependabot[bot] 452e308f8f Bump github.com/emiago/diago from 0.24.0 to 0.25.0
Bumps [github.com/emiago/diago](https://github.com/emiago/diago) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/emiago/diago/releases)
- [Commits](https://github.com/emiago/diago/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: github.com/emiago/diago
  dependency-version: 0.25.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-20 17:38:15 +00:00
Joel Wetzell d16d833520 Merge pull request #44 from jwetzell/dependabot/go_modules/github.com/emiago/sipgo-1.1.1
Bump github.com/emiago/sipgo from 1.1.0 to 1.1.1
2026-01-20 11:37:02 -06:00
Joel Wetzell f203b32261 Merge pull request #45 from jwetzell/dependabot/go_modules/github.com/urfave/cli/v3-3.6.2
Bump github.com/urfave/cli/v3 from 3.6.1 to 3.6.2
2026-01-20 11:36:51 -06:00
dependabot[bot] 361c4f4499 Bump github.com/urfave/cli/v3 from 3.6.1 to 3.6.2
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.6.1 to 3.6.2.
- [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.1...v3.6.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 19:15:13 +00:00
dependabot[bot] a76d12d799 Bump github.com/emiago/sipgo from 1.1.0 to 1.1.1
Bumps [github.com/emiago/sipgo](https://github.com/emiago/sipgo) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/emiago/sipgo/releases)
- [Commits](https://github.com/emiago/sipgo/compare/v1.1.0...v1.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 19:14:56 +00:00
Joel Wetzell 7d91e64ec4 add complete test coverage for string processors 2026-01-17 20:42:37 -06:00
Joel Wetzell 589c59c693 fix label for cli 2026-01-17 20:02:50 -06:00
Joel Wetzell 56c67c5445 Merge pull request #42 from jwetzell/feat/nats-message-create-subject-template
allow subject to be a template in nats.message.create
2026-01-17 19:31:53 -06:00
Joel Wetzell 13229215a6 allow subject to be a template in nats.message.create 2026-01-17 19:27:40 -06:00
Joel Wetzell 909b4d2337 add TODO 2026-01-17 19:24:21 -06:00
Joel Wetzell 3bcb9a6608 Merge pull request #41 from jwetzell/fix/dtmf-create-rune-check
return error if DTMF create results in invalid characters
2026-01-17 18:40:53 -06:00
Joel Wetzell 17dcffc09b return error if dtmf create results in invalid characters 2026-01-17 18:39:54 -06:00
Joel Wetzell 8318645eef Merge pull request #40 from jwetzell/chore/router-context-handling
move router context to run methods
2026-01-16 19:04:21 -06:00
Joel Wetzell 0c777a8874 move router context to run methods 2026-01-16 18:50:40 -06:00
Joel Wetzell a8f9894ff0 Merge pull request #39 from jwetzell/feat/wasm-module
add WASM processor
2026-01-14 17:56:33 -06:00
Joel Wetzell f0f74f50e5 allow specifying function name to call 2026-01-14 17:53:37 -06:00
Joel Wetzell bb3a8b982b add script.wasm via extism plugins 2026-01-14 17:50:16 -06:00
Joel Wetzell be9ce5a8aa update to versioned artnet library 2026-01-14 17:35:31 -06:00
Joel Wetzell ac2cf078a4 Remove debug print statement from SIPCallServer 2026-01-14 09:38:57 -06:00
Joel Wetzell 0fc264d0e9 Merge pull request #38 from jwetzell/dependabot/go_modules/github.com/emiago/sipgo-1.0.2
Bump github.com/emiago/sipgo from 1.0.1 to 1.0.2
2026-01-14 09:02:28 -06:00
dependabot[bot] 3c99eef999 Bump github.com/emiago/sipgo from 1.0.1 to 1.0.2
Bumps [github.com/emiago/sipgo](https://github.com/emiago/sipgo) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/emiago/sipgo/releases)
- [Commits](https://github.com/emiago/sipgo/compare/v1.0.1...v1.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-14 15:01:01 +00:00
Joel Wetzell f7c3a85649 Merge pull request #37 from jwetzell/dependabot/go_modules/github.com/emiago/diago-0.24.0
Bump github.com/emiago/diago from 0.23.1-0.20251211215055-e1d875617111 to 0.24.0
2026-01-14 08:59:30 -06:00
dependabot[bot] 76caccd9f8 Bump github.com/emiago/diago
Bumps [github.com/emiago/diago](https://github.com/emiago/diago) from 0.23.1-0.20251211215055-e1d875617111 to 0.24.0.
- [Release notes](https://github.com/emiago/diago/releases)
- [Commits](https://github.com/emiago/diago/commits/v0.24.0)

---
updated-dependencies:
- dependency-name: github.com/emiago/diago
  dependency-version: 0.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-12 21:31:51 +00:00
Joel Wetzell e8c71562a6 update artnet-go 2025-12-30 11:58:48 -06:00
Joel Wetzell 42551127ba Merge pull request #36 from jwetzell/dependabot/go_modules/modernc.org/quickjs-0.17.1
Bump modernc.org/quickjs from 0.17.0 to 0.17.1
2025-12-29 16:11:25 -06:00
Joel Wetzell a99fef9389 Merge pull request #35 from jwetzell/dependabot/github_actions/docker/setup-buildx-action-3.12.0
Bump docker/setup-buildx-action from 3.11.1 to 3.12.0
2025-12-29 16:11:16 -06:00
dependabot[bot] ac83b339a6 Bump modernc.org/quickjs from 0.17.0 to 0.17.1
Bumps [modernc.org/quickjs](https://gitlab.com/cznic/quickjs) from 0.17.0 to 0.17.1.
- [Commits](https://gitlab.com/cznic/quickjs/compare/v0.17.0...v0.17.1)

---
updated-dependencies:
- dependency-name: modernc.org/quickjs
  dependency-version: 0.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 17:28:41 +00:00
dependabot[bot] cb06e2dbb5 Bump docker/setup-buildx-action from 3.11.1 to 3.12.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.11.1 to 3.12.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/e468171a9de216ec08956ac3ada2f0791b6bd435...8d2750c68a42422c14e847fe6c8ac0403b4cbd6f)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 3.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 17:28:19 +00:00
225 changed files with 21430 additions and 5475 deletions
-10
View File
@@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
- package-ecosystem: gomod
directory: /
schedule:
interval: "weekly"
-23
View File
@@ -1,23 +0,0 @@
config:
- changed-files:
- any-glob-to-any-file: 'internal/config/**'
framer:
- changed-files:
- any-glob-to-any-file: 'internal/framer/**'
module:
- changed-files:
- any-glob-to-any-file: 'internal/module/**'
processor:
- changed-files:
- any-glob-to-any-file: 'internal/processor/**'
route:
- changed-files:
- any-glob-to-any-file: 'internal/route/**'
cli:
- changed-files:
- any-glob-to-any-file: 'cmd/showbridge/**'
+4 -3
View File
@@ -17,12 +17,13 @@ changelog:
- title: Processor 🏭 - title: Processor 🏭
labels: labels:
- processor - processor
- title: Route 🛣️ - title: Routing 🛣️
labels: labels:
- router
- route - route
- title: CLI ⌨️ - title: CLI ⌨️
labels: labels:
- cmd - cli
- title: Other Changes - title: Other Changes
labels: labels:
- '*' - "*"
-18
View File
@@ -1,18 +0,0 @@
# Taken from https://github.com/go-gitea/gitea
name: Add labels to PR
on:
pull_request_target:
types: [opened, synchronize, reopened]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
labeler:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
with:
sync-labels: true
+200 -19
View File
@@ -4,59 +4,240 @@ on:
push: push:
tags: tags:
- "v*" - "v*"
workflow_dispatch:
permissions: permissions:
contents: write contents: write
jobs: jobs:
goreleaser: setup_release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
version: ${{ steps.set_version.outputs.version }}
short_commit: ${{ steps.set_commit.outputs.short_commit }}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v6 uses: actions/checkout@v7
with:
fetch-depth: 0
- name: set environment variables
run: echo "SHOWBRIDGE_VERSION=${GITHUB_REF_NAME}" >> $GITHUB_ENV
- name: clean tag
if: startsWith(github.ref, 'refs/tags/v')
run: echo "SHOWBRIDGE_VERSION=${SHOWBRIDGE_VERSION#v}" >> $GITHUB_ENV
- name: set version
id: set_version
run: echo "version=${SHOWBRIDGE_VERSION}" >> $GITHUB_OUTPUT
- name: set commit
id: set_commit
run: echo "short_commit=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
- name: create release
uses: softprops/action-gh-release@v3
if: github.ref_type == 'tag' && github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v')
with:
draft: true
generate_release_notes: true
linux_amd64:
runs-on: ubuntu-24.04
needs: setup_release
env:
SHOWBRIDGE_VERSION: ${{ needs.setup_release.outputs.version }}
SHORT_COMMIT: ${{ needs.setup_release.outputs.short_commit }}
steps:
- name: checkout
uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
- name: setup go - name: setup go
uses: actions/setup-go@v6 uses: actions/setup-go@v7
with: with:
go-version-file: 'go.mod' go-version-file: "go.mod"
- name: release - name: install alsa lib
uses: goreleaser/goreleaser-action@v6 run: sudo apt-get install -y libasound2-dev
- name: build
run: |
CGO_ENABLED=1 go build -o showbridge -ldflags="-s -w -X main.version=${SHOWBRIDGE_VERSION}-${SHORT_COMMIT}" ./cmd/showbridge
tar -czf showbridge_${SHOWBRIDGE_VERSION}_linux-amd64.tar.gz showbridge
- name: upload linux artifacts
uses: actions/upload-artifact@v7
if: github.event_name == 'workflow_dispatch'
with: with:
workdir: cmd/showbridge name: showbridge-linux-amd64
distribution: goreleaser path: |
version: "v2.13.0" showbridge_*.tar.gz
args: release --clean - name: upload artifacts to release
uses: softprops/action-gh-release@v3
if: github.ref_type == 'tag' && github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v')
with:
draft: true
files: showbridge_*.tar.gz
linux_arm64:
runs-on: ubuntu-24.04-arm
needs: setup_release
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SHOWBRIDGE_VERSION: ${{ needs.setup_release.outputs.version }}
SHORT_COMMIT: ${{ needs.setup_release.outputs.short_commit }}
steps:
- name: checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: setup go
uses: actions/setup-go@v7
with:
go-version-file: "go.mod"
- name: install alsa lib
run: sudo apt-get install -y libasound2-dev
- name: build
run: |
CGO_ENABLED=1 go build -o showbridge -ldflags="-s -w -X main.version=${SHOWBRIDGE_VERSION}-${SHORT_COMMIT}" ./cmd/showbridge
tar -czf showbridge_${SHOWBRIDGE_VERSION}_linux-arm64.tar.gz showbridge
- name: upload linux artifacts
uses: actions/upload-artifact@v7
if: github.event_name == 'workflow_dispatch'
with:
name: showbridge-linux-arm64
path: |
showbridge_*.tar.gz
- name: upload artifacts to release
uses: softprops/action-gh-release@v3
if: github.ref_type == 'tag' && github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v')
with:
draft: true
files: showbridge_*.tar.gz
windows:
runs-on: ubuntu-latest
needs: setup_release
env:
SHOWBRIDGE_VERSION: ${{ needs.setup_release.outputs.version }}
SHORT_COMMIT: ${{ needs.setup_release.outputs.short_commit }}
steps:
- name: checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: setup go
uses: actions/setup-go@v7
with:
go-version-file: "go.mod"
- name: setup zig
uses: mlugg/setup-zig@v2
with:
version: 0.16.0
- name: build amd64
run: |
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC="zig cc -target x86_64-windows-gnu" CXX="zig c++ -target x86_64-windows-gnu" go build -o showbridge.exe -ldflags="-s -w -X main.version=${SHOWBRIDGE_VERSION}-${SHORT_COMMIT}" ./cmd/showbridge
zip showbridge_${SHOWBRIDGE_VERSION}_windows-amd64.zip showbridge.exe
- name: build arm64
run: |
CGO_ENABLED=1 GOOS=windows GOARCH=arm64 CC="zig cc -target aarch64-windows-gnu" CXX="zig c++ -target aarch64-windows-gnu" go build -o showbridge.exe -ldflags="-s -w -X main.version=${SHOWBRIDGE_VERSION}-${SHORT_COMMIT}" ./cmd/showbridge
zip showbridge_${SHOWBRIDGE_VERSION}_windows-arm64.zip showbridge.exe
- name: upload windows artifacts
uses: actions/upload-artifact@v7
if: github.event_name == 'workflow_dispatch'
with:
name: showbridge-windows
path: |
showbridge_*.zip
- name: upload artifacts to release
uses: softprops/action-gh-release@v3
if: github.ref_type == 'tag' && github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v')
with:
draft: true
files: showbridge_*.zip
macos_arm64:
runs-on: macos-26
needs: setup_release
env:
SHOWBRIDGE_VERSION: ${{ needs.setup_release.outputs.version }}
SHORT_COMMIT: ${{ needs.setup_release.outputs.short_commit }}
steps:
- name: checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: setup go
uses: actions/setup-go@v7
with:
go-version-file: "go.mod"
- name: build
run: |
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -o showbridge -ldflags="-s -w -X main.version=${SHOWBRIDGE_VERSION}-${SHORT_COMMIT}" ./cmd/showbridge
tar -czf showbridge_${SHOWBRIDGE_VERSION}_macOS-arm64.tar.gz showbridge
- name: upload macos artifacts
uses: actions/upload-artifact@v7
if: github.event_name == 'workflow_dispatch'
with:
name: showbridge-macos-arm64
path: |
showbridge_*.tar.gz
- name: upload artifacts to release
uses: softprops/action-gh-release@v3
if: github.ref_type == 'tag' && github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v')
with:
draft: true
files: showbridge_*.tar.gz
macos_amd64:
runs-on: macos-26-intel
needs: setup_release
env:
SHOWBRIDGE_VERSION: ${{ needs.setup_release.outputs.version }}
SHORT_COMMIT: ${{ needs.setup_release.outputs.short_commit }}
steps:
- name: checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: setup go
uses: actions/setup-go@v7
with:
go-version-file: "go.mod"
- name: build
run: |
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -o showbridge -ldflags="-s -w -X main.version=${SHOWBRIDGE_VERSION}-${SHORT_COMMIT}" ./cmd/showbridge
tar -czf showbridge_${SHOWBRIDGE_VERSION}_macOS-amd64.tar.gz showbridge
- name: upload macos artifacts
uses: actions/upload-artifact@v7
if: github.event_name == 'workflow_dispatch'
with:
name: showbridge-macos-amd64
path: |
showbridge_*.tar.gz
- name: upload artifacts to release
uses: softprops/action-gh-release@v3
if: github.ref_type == 'tag' && github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v')
with:
draft: true
files: showbridge_*.tar.gz
docker: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: goreleaser needs: setup_release
if: github.ref_type == 'tag' && github.event_name != 'workflow_dispatch'
steps: steps:
- name: Check out repository - name: Check out repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup Docker metadata - name: Setup Docker metadata
id: meta id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with: with:
images: | images: |
jwetzell/showbridge jwetzell/showbridge
- name: Build and push - name: Build and push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with: with:
push: true push: true
context: ./ context: ./
@@ -65,4 +246,4 @@ jobs:
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64
+23 -11
View File
@@ -5,19 +5,19 @@ on:
branches: branches:
- main - main
paths: paths:
- '**/*.go' - "**/*.go"
- 'go.mod' - "go.mod"
- 'go.sum' - "go.sum"
push: push:
branches: branches:
- main - main
paths: paths:
- '**/*.go' - "**/*.go"
- 'go.mod' - "go.mod"
- 'go.sum' - "go.sum"
permissions: permissions:
contents: read contents: write
jobs: jobs:
test: test:
@@ -26,12 +26,24 @@ jobs:
- name: Install alsa - name: Install alsa
run: sudo apt-get install -y libasound2-dev run: sudo apt-get install -y libasound2-dev
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-go@v6 - uses: actions/setup-go@v7
with: with:
go-version-file: 'go.mod' go-version-file: "go.mod"
- name: mod tidy - name: mod tidy
run: go mod tidy run: go mod tidy
- name: run tests - name: run tests
run: go test ./... 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
- name: Update coverage report
uses: ncruces/go-coverage-report@v0.3.2
with:
coverage-file: coverage.nocmd.out
report: true
chart: true
amend: true
if: github.event_name == 'push'
continue-on-error: true
-1
View File
@@ -4,7 +4,6 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Launch showbridge", "name": "Launch showbridge",
"type": "go", "type": "go",
+2 -2
View File
@@ -1,6 +1,6 @@
ARG GO_VERSION=1.25.5 ARG GO_VERSION=1.26.3
FROM golang:${GO_VERSION}-alpine AS build FROM golang:${GO_VERSION}-alpine AS build
RUN apk --no-cache add ca-certificates tzdata RUN apk --no-cache add ca-certificates tzdata git
WORKDIR /build WORKDIR /build
COPY go.mod go.sum ./ COPY go.mod go.sum ./
RUN go mod download RUN go mod download
+6 -3
View File
@@ -2,6 +2,7 @@
# showbridge (go edition) # showbridge (go edition)
[![Coverage](https://github.com/jwetzell/showbridge-go/wiki/coverage.svg)](https://raw.githack.com/wiki/jwetzell/showbridge-go/coverage.html)
Simple protocol router _/s_ Simple protocol router _/s_
</div> </div>
@@ -12,6 +13,7 @@ Simple protocol router _/s_
</p> </p>
### Supported Protocols ### Supported Protocols
- HTTP - HTTP
- UDP - UDP
- TCP - TCP
@@ -23,6 +25,7 @@ Simple protocol router _/s_
- [OSC](https://opensoundcontrol.stanford.edu/spec-1_0.html) - [OSC](https://opensoundcontrol.stanford.edu/spec-1_0.html)
- [FreeD](https://ptzoptics.com/freed/) - [FreeD](https://ptzoptics.com/freed/)
- [SIP](https://en.wikipedia.org/wiki/Session_Initiation_Protocol) - [SIP](https://en.wikipedia.org/wiki/Session_Initiation_Protocol)
- [Redis](https://redis.io/)
### CLI Usage ### CLI Usage
@@ -34,9 +37,9 @@ USAGE:
showbridge [global options] showbridge [global options]
GLOBAL OPTIONS: GLOBAL OPTIONS:
--config string path to config file (default: "./config.yaml") --config string path to config file (default: "./config.yaml") [$SHOWBRIDGE_CONFIG]
--log-level string set log level (default: "info") --log-level string set log level (default: "info") [$SHOWBRIDGE_LOG_LEVEL]
--log-format string log format to use (default: "text") --log-format string log format to use (default: "text") [$SHOWBRIDGE_LOG_FORMAT]
--help, -h show help --help, -h show help
--version, -v print the version --version, -v print the version
``` ```
-2
View File
@@ -1,2 +0,0 @@
# Added by goreleaser init:
dist/
-33
View File
@@ -1,33 +0,0 @@
version: 2
project_name: showbridge
before:
hooks:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
goarch:
- "amd64"
- "arm64"
ldflags:
- '-s -w -X main.version={{.RawVersion}}-{{.ShortCommit}}'
archives:
- formats: [tar.gz]
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}-{{ .Arch }}"
format_overrides:
- goos: windows
formats: [zip]
changelog:
sort: asc
use: github-native
release:
draft: true
replace_existing_draft: true
+128 -26
View File
@@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
@@ -9,15 +10,18 @@ import (
"os/signal" "os/signal"
"slices" "slices"
"sync" "sync"
"syscall"
"github.com/jwetzell/showbridge-go" "github.com/jwetzell/showbridge-go"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/schema"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
var ( var (
version = "dev" version = "dev"
sigHangup = make(chan os.Signal, 1)
) )
func main() { func main() {
@@ -30,6 +34,7 @@ func main() {
Name: "config", Name: "config",
Value: "./config.yaml", Value: "./config.yaml",
Usage: "path to config file", Usage: "path to config file",
Sources: cli.EnvVars("SHOWBRIDGE_CONFIG"),
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "log-level", Name: "log-level",
@@ -42,6 +47,7 @@ func main() {
} }
return nil return nil
}, },
Sources: cli.EnvVars("SHOWBRIDGE_LOG_LEVEL"),
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "log-format", Name: "log-format",
@@ -54,12 +60,15 @@ func main() {
} }
return nil return nil
}, },
Sources: cli.EnvVars("SHOWBRIDGE_LOG_FORMAT"),
}, },
}, },
Action: run, Action: run,
} }
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
signal.Notify(sigHangup, syscall.SIGHUP)
defer cancel() defer cancel()
err := cmd.Run(ctx, os.Args) err := cmd.Run(ctx, os.Args)
@@ -69,6 +78,14 @@ func main() {
} }
type showbridgeApp struct {
ctx context.Context
configPath string
logger *slog.Logger
router *showbridge.Router
routerMutex sync.Mutex
}
func readConfig(configPath string) (config.Config, error) { func readConfig(configPath string) (config.Config, error) {
cfg := config.Config{} cfg := config.Config{}
@@ -78,7 +95,31 @@ func readConfig(configPath string) (config.Config, error) {
return config.Config{}, err return config.Config{}, err
} }
err = yaml.Unmarshal(configBytes, &cfg) //TODO(jwetzell): this is an annoying amount of marshaling
yamlMap := make(map[string]any)
err = yaml.Unmarshal(configBytes, &yamlMap)
if err != nil {
return config.Config{}, err
}
err = schema.ApplyDefaults(&yamlMap)
if err != nil {
return config.Config{}, fmt.Errorf("failed to apply defaults: %w", err)
}
err = schema.ValidateConfig(yamlMap)
if err != nil {
return config.Config{}, fmt.Errorf("failed to validate config: %w", err)
}
validatedConfigBytes, err := json.Marshal(yamlMap)
if err != nil {
return config.Config{}, err
}
err = json.Unmarshal(validatedConfigBytes, &cfg)
if err != nil { if err != nil {
return config.Config{}, err return config.Config{}, err
} }
@@ -86,18 +127,27 @@ func readConfig(configPath string) (config.Config, error) {
return cfg, nil return cfg, nil
} }
func run(ctx context.Context, c *cli.Command) error { func writeConfig(configPath string, newConfig config.Config) error {
configPath := c.String("config") configBytes, err := yaml.Marshal(newConfig)
if configPath == "" {
return errors.New("config value cannot be empty")
}
config, err := readConfig(configPath)
if err != nil { if err != nil {
return err return err
} }
logLevel := slog.LevelInfo err = os.WriteFile(configPath, configBytes, 0600)
if err != nil {
return err
}
return nil
}
func run(ctx context.Context, c *cli.Command) error {
configPath := c.String("config")
if configPath == "" {
return errors.New("config path cannot be empty")
}
var logLevel slog.Level
logLevelFromFlag := c.String("log-level") logLevelFromFlag := c.String("log-level")
@@ -135,28 +185,80 @@ func run(ctx context.Context, c *cli.Command) error {
slog.SetDefault(slog.New(logHandler)) slog.SetDefault(slog.New(logHandler))
commandLogger := slog.Default().With("component", "cmd") showbridgeApp := &showbridgeApp{
ctx: ctx,
configPath: configPath,
logger: slog.Default().With("component", "cmd"),
}
router, moduleErrors, routeErrors := showbridge.NewRouter(context.Background(), config) config, err := readConfig(showbridgeApp.configPath)
if err != nil {
return err
}
router, moduleErrors, routeErrors := showbridge.NewRouter(config)
showbridgeApp.logConfigErrors(moduleErrors, routeErrors)
if moduleErrors != nil || routeErrors != nil {
return fmt.Errorf("errors initializing modules or routes")
}
showbridgeApp.routerMutex.Lock()
showbridgeApp.router = router
router.Start(context.Background())
showbridgeApp.routerMutex.Unlock()
go showbridgeApp.handleChannels()
<-showbridgeApp.ctx.Done()
showbridgeApp.logger.Debug("shutting down router")
showbridgeApp.router.Stop()
return nil
}
func (app *showbridgeApp) handleChannels() {
for {
select {
case <-sigHangup:
app.logger.Info("received SIGHUP, reloading configuration")
app.routerMutex.Lock()
config, err := readConfig(app.configPath)
if err != nil {
app.logger.Error("failed to read config file", "error", err)
app.routerMutex.Unlock()
continue
}
moduleErrors, routeErrors, err := app.router.UpdateConfig(config, false)
if err != nil {
app.logger.Error("failed to update router config", "error", err)
app.routerMutex.Unlock()
continue
}
app.logConfigErrors(moduleErrors, routeErrors)
app.logger.Info("configuration reloaded successfully")
app.routerMutex.Unlock()
case config := <-app.router.ConfigChange:
app.logger.Info("router config changed updating config file")
err := writeConfig(app.configPath, config)
if err != nil {
app.logger.Error("failed to write config file", "error", err)
continue
}
app.logger.Info("config file updated successfully")
case <-app.ctx.Done():
return
}
}
}
func (app *showbridgeApp) logConfigErrors(moduleErrors []config.ModuleError, routeErrors []config.RouteError) {
for _, moduleError := range moduleErrors { for _, moduleError := range moduleErrors {
commandLogger.Error("problem initializing module", "index", moduleError.Index, "error", moduleError.Error) app.logger.Error("problem initializing module", "index", moduleError.Index, "error", moduleError.Error)
} }
for _, routeError := range routeErrors { for _, routeError := range routeErrors {
commandLogger.Error("problem initializing route", "index", routeError.Index, "error", routeError.Error) app.logger.Error("problem initializing route", "index", routeError.Index, "error", routeError.Error)
} }
routerRunner := sync.WaitGroup{}
routerRunner.Go(func() {
router.Run()
})
<-ctx.Done()
commandLogger.Debug("shutting down router")
router.Stop()
commandLogger.Debug("waiting for router to exit")
routerRunner.Wait()
return nil
} }
+85
View File
@@ -0,0 +1,85 @@
package showbridge
import (
"errors"
"reflect"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
)
func (r *Router) GetRunningConfig() config.Config {
r.runningConfigMu.RLock()
defer r.runningConfigMu.RUnlock()
return r.runningConfig
}
func (r *Router) UpdateConfig(newConfig config.Config, triggerChangeChan bool) ([]config.ModuleError, []config.RouteError, error) {
if !r.runningConfigMu.TryLock() {
return nil, nil, errors.New("config update in progress")
}
defer r.runningConfigMu.Unlock()
oldConfig := r.runningConfig
r.logger.Debug("received config update", "oldConfig", oldConfig, "newConfig", newConfig)
if !reflect.DeepEqual(oldConfig.Api, newConfig.Api) {
r.logger.Info("applying new API config")
r.apiServer.Stop()
r.apiServer.Start(newConfig.Api)
r.runningConfig.Api = newConfig.Api
}
// TODO(jwetzell): handle config update errors better
for _, moduleInstance := range r.ModuleInstances {
moduleInstance.Stop()
}
r.logger.Debug("waiting for modules to exit")
r.moduleWait.Wait()
r.ModuleInstances = make(map[string]common.Module)
r.RouteInstances = []*route.Route{}
var moduleErrors []config.ModuleError
for moduleIndex, moduleDecl := range newConfig.Modules {
err := r.addModule(moduleDecl)
if err != nil {
if moduleErrors == nil {
moduleErrors = []config.ModuleError{}
}
moduleErrors = append(moduleErrors, config.ModuleError{
Index: moduleIndex,
Config: moduleDecl,
Error: err.Error(),
})
continue
}
}
var routeErrors []config.RouteError
for routeIndex, routeDecl := range newConfig.Routes {
err := r.addRoute(routeDecl)
if err != nil {
if routeErrors == nil {
routeErrors = []config.RouteError{}
}
routeErrors = append(routeErrors, config.RouteError{
Index: routeIndex,
Config: routeDecl,
Error: err.Error(),
})
continue
}
}
r.runningConfig = newConfig
r.startModules()
if triggerChangeChan {
r.ConfigChange <- newConfig
}
return moduleErrors, routeErrors, nil
}
+135
View File
@@ -0,0 +1,135 @@
api:
enabled: true
port: 8080
modules:
- id: sqlite
type: db.sqlite
params:
dsn: ":memory:"
- id: httpin
type: http.server
params:
port: 3000
- id: tcpout
type: net.tcp.client
params:
host: "localhost"
port: 9000
framing: "RAW"
- id: tcpin
type: net.tcp.server
params:
port: 9000
framing: "RAW"
- id: ticker
type: time.interval
params:
duration: 1000
- id: udpout
type: net.udp.client
params:
host: "localhost"
port: 9001
- id: udpin
type: net.udp.server
params:
port: 9001
- id: wsecho
type: websocket.client
params:
url: "wss://echo.websocket.org"
- id: natsserver
type: nats.server
params:
port: 4222
- id: natsout
type: nats.client
params:
url: "nats://localhost:4222"
subject: "hello"
- id: natsin
type: nats.client
params:
url: "nats://localhost:4222"
subject: "test"
routes:
- input: ticker
processors:
- type: string.create
params:
template: "wsecho: {{.Payload.UnixMilli}}"
- type: module.output
params:
module: wsecho
- input: ticker
processors:
- type: string.create
params:
template: "tcpout: {{.Payload.UnixMilli}}"
- type: string.encode
- type: module.output
params:
module: tcpout
- input: ticker
processors:
- type: string.create
params:
template: "udpout: {{.Payload.UnixMilli}}"
- type: string.encode
- type: module.output
params:
module: udpout
- input: ticker
processors:
- type: http.request.do
params:
url: "http://localhost:3000/echo"
method: "GET"
- type: struct.field.get
params:
name: "Body"
- type: string.decode
- type: debug.log
- input: tcpin
processors:
- type: string.decode
- type: debug.log
- input: udpin
processors:
- type: string.decode
- type: debug.log
- input: wsecho
processors:
- type: debug.log
- input: ticker
processors:
- type: db.query
params:
module: "sqlite"
query: "SELECT 1;"
- type: debug.log
- input: natsin
processors:
- type: struct.field.get
params:
name: "Data"
- type: string.decode
- type: debug.log
- input: httpin
processors:
- type: http.response.create
params:
status: 200
bodyTemplate: "Hello, World!"
- type: module.output
params:
module: httpin
- input: ticker
processors:
- type: string.create
params:
template: "natsout: {{.Payload}}"
- type: pubsub.publish
params:
module: natsout
topic: "test"
+13 -4
View File
@@ -1,3 +1,6 @@
api:
enabled: true
port: 8080
modules: modules:
- id: http - id: http
type: http.server type: http.server
@@ -10,9 +13,15 @@ modules:
port: 8000 port: 8000
routes: routes:
- input: http - input: http
id: http-to-osc
processors: processors:
- type: osc.message.create - id: create-osc
type: osc.message.create
params: params:
address: "{{.URL.Path}}" address: "{{.Payload.URL.Path}}"
- type: osc.message.encode - id: encode-osc
output: udp type: osc.message.encode
- id: output-osc
type: module.output
params:
module: udp
+53
View File
@@ -0,0 +1,53 @@
package showbridge
import (
"time"
"github.com/jwetzell/showbridge-go/internal/common"
)
func (r *Router) HandleEvent(event common.Event, sender common.EventDestination) {
switch event.Type {
case "ping":
r.unicastEvent(common.Event{Type: "pong", Data: map[string]any{
"timestamp": time.Now().UnixMilli(),
}}, sender)
default:
r.logger.Warn("unknown event type", "eventType", event.Type)
}
}
func (r *Router) AddEventDestination(dest common.EventDestination) {
r.eventDestinationsMu.Lock()
defer r.eventDestinationsMu.Unlock()
r.eventDestinations = append(r.eventDestinations, dest)
}
func (r *Router) RemoveEventDestination(dest common.EventDestination) {
r.eventDestinationsMu.Lock()
defer r.eventDestinationsMu.Unlock()
for i, d := range r.eventDestinations {
if d.Is(dest) {
r.eventDestinations = append(r.eventDestinations[:i], r.eventDestinations[i+1:]...)
break
}
}
}
func (r *Router) unicastEvent(event common.Event, dest common.EventDestination) {
err := dest.Send(event)
if err != nil {
r.logger.Error("failed to send event", "error", err)
}
}
func (r *Router) broadcastEvent(event common.Event) {
r.eventDestinationsMu.Lock()
defer r.eventDestinationsMu.Unlock()
for _, dest := range r.eventDestinations {
err := dest.Send(event)
if err != nil {
r.logger.Error("failed to send event", "error", err)
}
}
}
+49 -29
View File
@@ -1,56 +1,76 @@
module github.com/jwetzell/showbridge-go module github.com/jwetzell/showbridge-go
go 1.25.5 go 1.26.3
require ( require (
github.com/eclipse/paho.mqtt.golang v1.5.1 github.com/eclipse/paho.mqtt.golang v1.5.1
github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111 github.com/emiago/diago v0.32.0
github.com/emiago/sipgo v1.0.1 github.com/emiago/sipgo v1.5.0
github.com/expr-lang/expr v1.17.7 github.com/expr-lang/expr v1.17.8
github.com/jwetzell/artnet-go v0.0.0-20251223201031-5097901aa9db github.com/extism/go-sdk v1.7.1
github.com/jwetzell/free-d-go v0.1.0 github.com/google/jsonschema-go v0.4.3
github.com/jwetzell/osc-go v0.1.0 github.com/gorilla/websocket v1.5.3
github.com/jwetzell/artnet-go v0.3.0
github.com/jwetzell/free-d-go v0.2.0
github.com/jwetzell/osc-go v0.3.0
github.com/jwetzell/psn-go v0.3.0 github.com/jwetzell/psn-go v0.3.0
github.com/nats-io/nats.go v1.48.0 github.com/nats-io/nats-server/v2 v2.14.5
github.com/urfave/cli/v3 v3.6.1 github.com/nats-io/nats.go v1.53.1
gitlab.com/gomidi/midi/v2 v2.3.18 github.com/redis/go-redis/v9 v9.22.0
go.bug.st/serial v1.6.4 github.com/urfave/cli/v3 v3.11.0
modernc.org/quickjs v0.17.0 gitlab.com/gomidi/midi/v2 v2.3.24
go.bug.st/serial v1.8.0
golang.org/x/time v0.15.0
modernc.org/quickjs v0.24.0
modernc.org/sqlite v1.56.0
sigs.k8s.io/yaml v1.6.0 sigs.k8s.io/yaml v1.6.0
) )
require ( require (
github.com/creack/goselect v0.1.2 // indirect github.com/antithesishq/antithesis-sdk-go v0.7.2-default-no-op // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dylibso/observe-sdk/go v0.0.0-20240828172851-9145d8ad07e1 // indirect
github.com/emiago/dtls/v3 v3.0.0-20260122183559-8b8d23e359c0 // indirect
github.com/go-audio/riff v1.0.0 // indirect github.com/go-audio/riff v1.0.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect github.com/gobwas/ws v1.4.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/ianlancetaylor/demangle v0.0.0-20260505044615-1ff4bf46051f // indirect
github.com/icholy/digest v1.1.0 // indirect github.com/icholy/digest v1.1.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.19.2 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.24 // indirect
github.com/nats-io/nkeys v0.4.11 // indirect github.com/minio/highwayhash v1.0.4 // indirect
github.com/nats-io/jwt/v2 v2.8.2 // indirect
github.com/nats-io/nkeys v0.4.16 // indirect
github.com/nats-io/nuid v1.0.1 // indirect github.com/nats-io/nuid v1.0.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pion/logging v0.2.4 // indirect github.com/pion/logging v0.2.4 // indirect
github.com/pion/randutil v0.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.16 // indirect github.com/pion/rtcp v1.2.16 // indirect
github.com/pion/rtp v1.8.26 // indirect github.com/pion/rtp v1.10.2 // indirect
github.com/pion/srtp/v3 v3.0.9 // indirect github.com/pion/srtp/v3 v3.0.10 // indirect
github.com/pion/transport/v3 v3.1.1 // indirect github.com/pion/transport/v4 v4.0.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
github.com/tetratelabs/wazero v1.11.0 // indirect
github.com/zaf/g711 v1.4.0 // indirect github.com/zaf/g711 v1.4.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/crypto v0.42.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/crypto v0.55.0 // indirect
golang.org/x/net v0.44.0 // indirect golang.org/x/net v0.57.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.39.0 // indirect golang.org/x/sys v0.47.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 // indirect gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 // indirect
modernc.org/libc v1.66.10 // indirect modernc.org/libc v1.75.3 // indirect
modernc.org/libquickjs v0.12.2 // indirect modernc.org/libquickjs v0.13.1 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect modernc.org/memory v1.12.0 // indirect
) )
+141 -70
View File
@@ -1,19 +1,37 @@
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/antithesishq/antithesis-sdk-go v0.7.2-default-no-op h1:p2zFsAzvhIpFya8AIOHIbWf7NGvO34QpLGclyf7nXj8=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/antithesishq/antithesis-sdk-go v0.7.2-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dylibso/observe-sdk/go v0.0.0-20240828172851-9145d8ad07e1 h1:idfl8M8rPW93NehFw5H1qqH8yG158t5POr+LX9avbJY=
github.com/dylibso/observe-sdk/go v0.0.0-20240828172851-9145d8ad07e1/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q=
github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE= github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU= github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111 h1:jqhOZbH40pf2jiUhGaBNk334wOtNYvAaXg/mHOXhy/Y= github.com/emiago/diago v0.31.0 h1:w/GZQnMS60EBANKXMnf/MjFDlUfcfHLPOlEKiZkaNe0=
github.com/emiago/diago v0.23.1-0.20251211215055-e1d875617111/go.mod h1:3vLCCq8/G/Ei5I64IHtrmBTag+nPLcgXcKeN1KkLtuc= github.com/emiago/diago v0.31.0/go.mod h1:CP7p9/aBZG/fQEYdtGdQkCblr1jLr1zbxODcu8MryVU=
github.com/emiago/sipgo v1.0.1 h1:8eCZ6L/VX3isyByyv1RrBoQ5GyBoRXBHkNMYjwacRfk= github.com/emiago/diago v0.32.0 h1:U6urbJc/LD2HwrHVe1d/yT0lMgyDMRUFle7gMqJe0Jg=
github.com/emiago/sipgo v1.0.1/go.mod h1:DuwAxBZhKMqIzQFPGZb1MVAGU6Wuxj64oTOhd5dx/FY= github.com/emiago/diago v0.32.0/go.mod h1:CP7p9/aBZG/fQEYdtGdQkCblr1jLr1zbxODcu8MryVU=
github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8= github.com/emiago/dtls/v3 v3.0.0-20260122183559-8b8d23e359c0 h1:o4LxpUnZ1zxiQ+Qjc9kLwXcjz31NGAHmnZ7xoJto3VM=
github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/emiago/dtls/v3 v3.0.0-20260122183559-8b8d23e359c0/go.mod h1:ydcZ977eS1I6uOWodzMuw30BwvNAzT9su/xcNYSJqjA=
github.com/emiago/sipgo v1.4.3 h1:Ju1Ilp0LhTcSHCyp2jBYAQDQ8TAwuxQHzT1IhO6jSIY=
github.com/emiago/sipgo v1.4.3/go.mod h1:DuwAxBZhKMqIzQFPGZb1MVAGU6Wuxj64oTOhd5dx/FY=
github.com/emiago/sipgo v1.5.0 h1:YVq5Rw5eVN4DRsB5MoBBiiurpHXXCjJstaH9cTvrlyI=
github.com/emiago/sipgo v1.5.0/go.mod h1:DuwAxBZhKMqIzQFPGZb1MVAGU6Wuxj64oTOhd5dx/FY=
github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM=
github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/extism/go-sdk v1.7.1 h1:lWJos6uY+tRFdlIHR+SJjwFDApY7OypS/2nMhiVQ9Sw=
github.com/extism/go-sdk v1.7.1/go.mod h1:IT+Xdg5AZM9hVtpFUA+uZCJMge/hbvshl8bwzLtFyKA=
github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA= github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498= github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
@@ -22,85 +40,130 @@ github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0=
github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 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/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 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 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/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/ianlancetaylor/demangle v0.0.0-20260505044615-1ff4bf46051f h1:NW3E2QSchEk63/fjeEvWOa2cE02FSv9ox//VE/N4c8g=
github.com/ianlancetaylor/demangle v0.0.0-20260505044615-1ff4bf46051f/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4= github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y= github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
github.com/jwetzell/artnet-go v0.0.0-20251223201031-5097901aa9db h1:PR9sdDEWBufi8/3yiQHRXncLNKUHBMKiEBDtdNWsK/E= github.com/jwetzell/artnet-go v0.3.0 h1:MDHElOSbpgmIbYGHO9SoxsT7ynrYtV5/UHGT82geHk0=
github.com/jwetzell/artnet-go v0.0.0-20251223201031-5097901aa9db/go.mod h1:gli97Z32a0kMkZ6taoTiK7/lqHcF/dhiGjGJdx/PxqA= github.com/jwetzell/artnet-go v0.3.0/go.mod h1:zqRsSsrd6rPwJacwscPteDqGIqevjZ3Z6dpp092VJ1w=
github.com/jwetzell/free-d-go v0.1.0 h1:xHt6dvyit98X+OC3jVzV0aLidxbyzi3vI9QiYkteEtA= github.com/jwetzell/free-d-go v0.2.0 h1:8WQW4du8Sf3d18aUzk9n5jZtHE6WdDQHDFjkFffvycc=
github.com/jwetzell/free-d-go v0.1.0/go.mod h1:KmrkooRARRaxJTBSPvwt/6IMAIaHH1R8bSA8cwbbELw= github.com/jwetzell/free-d-go v0.2.0/go.mod h1:yHuRy51NeDfN26eJ6wzZgys8qyQiS7hkYILw8yF+UJ4=
github.com/jwetzell/osc-go v0.1.0 h1:EXxup5VWBErHot2Ri4MFToPf6KCzLDTbCt2x6GLfw8I= github.com/jwetzell/osc-go v0.3.0 h1:z75TxuQSEmdcmZ56OAepkDa3m88SdZh//3m4nBb/XZI=
github.com/jwetzell/osc-go v0.1.0/go.mod h1:xLz0jTwebSxtx1TkKN1YVdeRqvpFNweDhTut5TE393A= github.com/jwetzell/osc-go v0.3.0/go.mod h1:kCs329JxY6Qjga08tRQ/Gl0PqhgQzLIMpOhm6uszvIc=
github.com/jwetzell/psn-go v0.3.0 h1:WVpCEmExYE8a+I5hQak5jNJJp2x35VdGX/VuMUKPmhY= 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/jwetzell/psn-go v0.3.0/go.mod h1:bcEAeti4sQM375buujb3mIfmUstD4Aby18gq3ENb6+o=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8=
github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U= github.com/minio/highwayhash v1.0.4 h1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4=
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= github.com/minio/highwayhash v1.0.4/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= github.com/nats-io/jwt/v2 v2.8.2 h1:XXRgB60MSTnqsRwejQurVDs/hcv2dkt+86GjI+I/bMc=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/jwt/v2 v2.8.2/go.mod h1:Ag/56sq9OblL4JgdYufDd16Egb17Kr/8WwwuO/forVc=
github.com/nats-io/nats-server/v2 v2.14.4 h1:efgjZ8cdExAKRuqSg8UPJFprb+l7NlBtSDPhDlw3rO4=
github.com/nats-io/nats-server/v2 v2.14.4/go.mod h1:BltdpOYestjbtQSnVO2zGHdg5SGBZjt+GYTgB9LZq/I=
github.com/nats-io/nats-server/v2 v2.14.5 h1:M6yeo/Xb7khi97RSEVELof3DForDqmYza3P4tHCPFWw=
github.com/nats-io/nats-server/v2 v2.14.5/go.mod h1:1D3iocrisKvWaD1B/imqarTqmaGrWMqALMLbEDo3v7Q=
github.com/nats-io/nats.go v1.52.0 h1:n3avV4VBsCgsdwh71TppsTwtv+QdPs7ntSKM8qJLGsc=
github.com/nats-io/nats.go v1.52.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno=
github.com/nats-io/nats.go v1.53.1 h1:Otsq3uLc/kLdjmkNHkXH0jBqwUquwdKFoe3fq6/3/Xo=
github.com/nats-io/nats.go v1.53.1/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno=
github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg=
github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
github.com/pion/rtp v1.8.26 h1:VB+ESQFQhBXFytD+Gk8cxB6dXeVf2WQzg4aORvAvAAc= github.com/pion/rtp v1.10.2 h1:l+f6tTDcAH6xwepaAoW791ddhuYsJlqRATOzirO04Mo=
github.com/pion/rtp v1.8.26/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= github.com/pion/rtp v1.10.2/go.mod h1:Au8fc6cEByy8RLTwKTQTEeQqDB/SJDxwL4mZuxYA5Pk=
github.com/pion/srtp/v3 v3.0.9 h1:lRGF4G61xxj+m/YluB3ZnBpiALSri2lTzba0kGZMrQY= github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ=
github.com/pion/srtp/v3 v3.0.9/go.mod h1:E+AuWd7Ug2Fp5u38MKnhduvpVkveXJX6J4Lq4rxUYt8= github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M=
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0=
github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/urfave/cli/v3 v3.10.1 h1:7Kx9H50hrHbRbyxgO1KP6/BcbiGRz0uYh5YyQ30JEEY=
github.com/urfave/cli/v3 v3.10.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/urfave/cli/v3 v3.11.0 h1:P/euJp99kb9p0tlVY+iYTLYYTAQlfl0hR2gUO1Img1Q=
github.com/urfave/cli/v3 v3.11.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/zaf/g711 v1.4.0 h1:XZYkjjiAg9QTBnHqEg37m2I9q3IIDv5JRYXs2N8ma7c= github.com/zaf/g711 v1.4.0 h1:XZYkjjiAg9QTBnHqEg37m2I9q3IIDv5JRYXs2N8ma7c=
github.com/zaf/g711 v1.4.0/go.mod h1:eCDXt3dSp/kYYAoooba7ukD/Q75jvAaS4WOMr0l1Roo= github.com/zaf/g711 v1.4.0/go.mod h1:eCDXt3dSp/kYYAoooba7ukD/Q75jvAaS4WOMr0l1Roo=
gitlab.com/gomidi/midi/v2 v2.3.18 h1:sj2fOhtvOe+zI8YJe8qTxLw5zv0ntULLUDwcFOaZQbI= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
gitlab.com/gomidi/midi/v2 v2.3.18/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= gitlab.com/gomidi/midi/v2 v2.3.24 h1:afkq5nhlzKvZaj9QK80YbK8tH3lIlKLnPPP9HxxD7Do=
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= gitlab.com/gomidi/midi/v2 v2.3.24/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw=
go.bug.st/serial v1.8.0 h1:ZtnmN8aYXtPlTghwSvDWPHKBHL9TM6oFDa+KpSn4SQE=
go.bug.st/serial v1.8.0/go.mod h1:d0MmS16Qt9b1m06yoYRNUXhRRTJV5Qg2S5EKqQtnayQ=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= 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/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -110,32 +173,40 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
modernc.org/libquickjs v0.12.2 h1:XtF6iD+aoN/pEz1MAQjg1wMZT412pzmOlO11UGLW/wQ= modernc.org/libc v1.75.3 h1:vCqT5+R0jPXMnvMkGo0T2zXvFNth+lYXVCx5X7CCX/g=
modernc.org/libquickjs v0.12.2/go.mod h1:n+vyuJ4mXh1pQYt1bJttvoCE+2t+MqCM8BFlcSDg/70= modernc.org/libc v1.75.3/go.mod h1:MjAX68G+0oufI+hNuh0QXcK+Ap+sL8bNPPcIC6EqOfo=
modernc.org/libquickjs v0.13.1 h1:uCb9AYyEyL1JS2dnKtLZKWH/vDb36cx1Mr1SIdbzb8A=
modernc.org/libquickjs v0.13.1/go.mod h1:tCEsA1Zda1+C5JagFIJGqjYZOxDPFOUYjK3dGhEeq0g=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/memory v1.12.0 h1:twkmYNkGXCvtYWzoux02jtK6eovjZbdI0uHFUYp6kuU=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/memory v1.12.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/quickjs v0.17.0 h1:gYZRSCKVokyOiHhZEKwran/NoBaGjAaWk+xJq52SWfQ= modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/quickjs v0.17.0/go.mod h1:TE+wpAYX4V7Nvi/sHRUrd9l5L176ZIiS0as3MMObvC0= modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/quickjs v0.23.0 h1:k+px+rFrMqHm2LPMvrd+vCR+8m6eogiTkROz+YJHkj8=
modernc.org/quickjs v0.23.0/go.mod h1:bFvjVk4JGlbqLYXVFt72YvwdnHbwtNIE9nQ+eh4fdi0=
modernc.org/quickjs v0.24.0 h1:Vo7bkccDIstEmNf7umJPPVBaEmwoAo/8RX1ymVPmZQM=
modernc.org/quickjs v0.24.0/go.mod h1:KYT0ACuCE+4eFnNc3WEZBUWfsFeIfsdtyDSltZz21iE=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.56.0 h1:/D8e2RfFqoy/Zc6PuC76U28zFwmI/sYx1Kjm4yEn9e0=
modernc.org/sqlite v1.56.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+278
View File
@@ -0,0 +1,278 @@
package api
import (
"context"
_ "embed"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"sync"
"time"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/schema"
)
type ApiServer struct {
config config.ApiConfig
serverMu sync.Mutex
server *http.Server
logger *slog.Logger
configurableRouter config.Configurable
eventRouter common.EventRouter
}
func NewApiServer(configurableRouter config.Configurable, eventRouter common.EventRouter) *ApiServer {
return &ApiServer{
configurableRouter: configurableRouter,
eventRouter: eventRouter,
logger: slog.Default().With("component", "api"),
}
}
func (as *ApiServer) Start(config config.ApiConfig) {
as.config = config
if !as.config.Enabled {
as.logger.Warn("not enabled")
return
}
as.logger.Debug("starting", "port", as.config.Port)
mux := http.NewServeMux()
mux.HandleFunc("/ws", as.handleWebsocket)
mux.HandleFunc("/health", as.handleHealthHTTP)
mux.HandleFunc("/api/v1/config", as.handleConfigHTTP)
mux.HandleFunc("/schema/config.schema.json", handleConfigSchema)
mux.HandleFunc("/schema/routes.schema.json", handleRoutesSchema)
mux.HandleFunc("/schema/modules.schema.json", handleModulesSchema)
mux.HandleFunc("/schema/processors.schema.json", handleProcessorsSchema)
as.serverMu.Lock()
defer as.serverMu.Unlock()
as.server = &http.Server{
Addr: fmt.Sprintf(":%d", as.config.Port),
ReadHeaderTimeout: 5 * time.Second,
Handler: mux,
}
go func() {
err := as.server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
as.logger.Error("server error", "error", err)
}
}()
}
func (as *ApiServer) Stop() {
if as.server == nil {
return
}
as.serverMu.Lock()
defer as.serverMu.Unlock()
if as.server != nil {
apiShutdownCtx, apiShutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer apiShutdownCancel()
err := as.server.Shutdown(apiShutdownCtx)
if err != nil {
as.logger.Error("error shutting down server", "error", err)
}
as.server = nil
}
as.logger.Debug("done")
}
func (as *ApiServer) handleHealthHTTP(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodGet:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusOK)
case http.MethodOptions:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.WriteHeader(http.StatusOK)
default:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func (as *ApiServer) handleConfigHTTP(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodGet:
configJSON, err := json.Marshal(as.configurableRouter.GetRunningConfig())
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
w.Write(configJSON)
case http.MethodPut:
//TODO(jwetzell): again way too much marshaling
cfgBytes, err := io.ReadAll(req.Body)
if err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
cfgMap := make(map[string]any)
err = json.Unmarshal(cfgBytes, &cfgMap)
if err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
err = schema.ApplyDefaults(&cfgMap)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = schema.ValidateConfig(cfgMap)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
validCfgBytes, err := json.Marshal(cfgMap)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
var newConfig config.Config
err = json.Unmarshal(validCfgBytes, &newConfig)
if err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
moduleErrors, routeErrors, err := as.configurableRouter.UpdateConfig(newConfig, true)
if err != nil {
http.Error(w, err.Error(), http.StatusConflict)
return
}
if len(moduleErrors) > 0 || len(routeErrors) > 0 {
errorResponse := struct {
ModuleErrors []config.ModuleError `json:"moduleErrors,omitempty"`
RouteErrors []config.RouteError `json:"routeErrors,omitempty"`
}{
ModuleErrors: moduleErrors,
RouteErrors: routeErrors,
}
errorResponseJSON, err := json.Marshal(errorResponse)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
w.Write(errorResponseJSON)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusOK)
case http.MethodOptions:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.WriteHeader(http.StatusOK)
default:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func handleConfigSchema(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodGet:
schemaJSON, err := json.Marshal(schema.ConfigSchema)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
w.Write(schemaJSON)
case http.MethodOptions:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.WriteHeader(http.StatusOK)
default:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func handleRoutesSchema(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodGet:
schemaJSON, err := json.Marshal(schema.RoutesConfigSchema)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
w.Write(schemaJSON)
case http.MethodOptions:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.WriteHeader(http.StatusOK)
default:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func handleModulesSchema(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodGet:
schemaJSON, err := json.Marshal(schema.GetModulesSchema())
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
w.Write(schemaJSON)
case http.MethodOptions:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.WriteHeader(http.StatusOK)
default:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func handleProcessorsSchema(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodGet:
schemaJSON, err := json.Marshal(schema.GetProcessorsSchema())
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
w.Write(schemaJSON)
case http.MethodOptions:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.WriteHeader(http.StatusOK)
default:
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
+82
View File
@@ -0,0 +1,82 @@
package api
import (
"encoding/json"
"net/http"
"github.com/gorilla/websocket"
"github.com/jwetzell/showbridge-go/internal/common"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
type WebsocketEventDestination struct {
conn *websocket.Conn
}
func (d WebsocketEventDestination) Send(event common.Event) error {
eventJSON, err := event.ToJSON()
if err != nil {
return err
}
return d.conn.WriteMessage(websocket.TextMessage, eventJSON)
}
func (d WebsocketEventDestination) Is(dest common.EventDestination) bool {
other, ok := dest.(WebsocketEventDestination)
if !ok {
return false
}
return d.conn == other.conn
}
func (as *ApiServer) handleWebsocket(w http.ResponseWriter, req *http.Request) {
conn, err := upgrader.Upgrade(w, req, nil)
if err != nil {
as.logger.Error("websocket upgrade error", "error", err)
return
}
defer conn.Close()
eventDestination := WebsocketEventDestination{conn: conn}
as.eventRouter.AddEventDestination(eventDestination)
READ_LOOP:
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
_, ok := err.(*websocket.CloseError)
if ok {
break READ_LOOP
}
}
switch messageType {
case websocket.TextMessage, websocket.BinaryMessage:
event := common.Event{}
err = json.Unmarshal(message, &event)
if err != nil {
as.logger.Error("websocket message unmarshal error", "error", err)
continue
}
as.eventRouter.HandleEvent(event, WebsocketEventDestination{conn: conn})
case websocket.CloseMessage:
break READ_LOOP
case websocket.PingMessage:
err = conn.WriteMessage(websocket.PongMessage, nil)
if err != nil {
as.logger.Error("websocket pong error", "error", err)
}
default:
as.logger.Warn("unsupported websocket message type", "type", messageType)
continue
}
}
//NOTE(jwetzell): remove ws connection
as.eventRouter.RemoveEventDestination(eventDestination)
}
+191
View File
@@ -0,0 +1,191 @@
package common
import (
"math"
"reflect"
)
func GetAnyAs[T any](value any) (T, bool) {
typed, ok := value.(T)
return typed, ok
}
func GetAnyAsInt(value any) (int, bool) {
intValue, ok := value.(int)
if ok {
return intValue, true
}
uintValue, ok := value.(uint)
if ok {
return int(uintValue), true
}
byteValue, ok := value.(byte)
if ok {
return int(byteValue), true
}
float32Value, ok := value.(float32)
if ok {
if float64(float32Value) != math.Floor(float64(float32Value)) {
return 0, false
}
return int(float32Value), true
}
float64Value, ok := value.(float64)
if ok {
if float64Value != math.Floor(float64Value) {
return 0, false
}
return int(float64Value), true
}
return 0, false
}
func GetAnyAsByte(value any) (byte, bool) {
byteValue, ok := value.(byte)
if ok {
return byte(byteValue), true
}
intValue, ok := value.(int)
if ok {
return byte(intValue), true
}
uintValue, ok := value.(uint)
if ok {
return byte(uintValue), true
}
float32Value, ok := value.(float32)
if ok {
if float64(float32Value) != math.Floor(float64(float32Value)) {
return 0, false
}
return byte(float32Value), true
}
float64Value, ok := value.(float64)
if ok {
if float64Value != math.Floor(float64Value) {
return 0, false
}
return byte(float64Value), true
}
return 0, false
}
func GetAnyAsByteSlice(value any) ([]byte, bool) {
// already a []byte
byteSlice, ok := value.([]byte)
if ok {
return byteSlice, true
}
// check for a slice
v := reflect.ValueOf(value)
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
return nil, false
}
// try to convert each element to a byte
result := make([]byte, v.Len())
for i := 0; i < v.Len(); i++ {
elem := v.Index(i).Interface()
elemValue, ok := GetAnyAsByte(elem)
if !ok {
return nil, false
}
result[i] = elemValue
}
return result, true
}
func GetAnyAsIntSlice(value any) ([]int, bool) {
// already a []int
intSlice, ok := value.([]int)
if ok {
return intSlice, true
}
// check for a slice
v := reflect.ValueOf(value)
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
return nil, false
}
result := make([]int, v.Len())
for i := 0; i < v.Len(); i++ {
elem := v.Index(i).Interface()
elemInt, ok := GetAnyAsInt(elem)
if !ok {
return nil, false
}
result[i] = elemInt
}
return result, true
}
func GetAnyAsFloat32(value any) (float32, bool) {
float32Value, ok := value.(float32)
if ok {
return float32Value, true
}
float64Value, ok := value.(float64)
if ok {
return float32(float64Value), true
}
intValue, ok := value.(int)
if ok {
return float32(intValue), true
}
uintValue, ok := value.(uint)
if ok {
return float32(uintValue), true
}
byteValue, ok := value.(byte)
if ok {
return float32(byteValue), true
}
return 0, false
}
func GetAnyAsFloat64(value any) (float64, bool) {
float64Value, ok := value.(float64)
if ok {
return float64Value, true
}
float32Value, ok := value.(float32)
if ok {
return float64(float32Value), true
}
intValue, ok := value.(int)
if ok {
return float64(intValue), true
}
uintValue, ok := value.(uint)
if ok {
return float64(uintValue), true
}
byteValue, ok := value.(byte)
if ok {
return float64(byteValue), true
}
return 0, false
}
+424
View File
@@ -0,0 +1,424 @@
package common_test
import (
"slices"
"testing"
"github.com/jwetzell/showbridge-go/internal/common"
)
func TestGoodGetAnyAsInt(t *testing.T) {
testCases := []struct {
name string
value any
typedValue int
}{
{
name: "int",
value: int(42),
typedValue: 42,
},
{
name: "uint",
value: uint(42),
typedValue: 42,
},
{
name: "float32 without decimal",
value: float32(42.0),
typedValue: 42,
},
{
name: "float64 without decimal",
value: float64(42.0),
typedValue: 42,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsInt(testCase.value)
if !ok {
t.Fatalf("GetAnyAsInt expected to succeed but failed")
}
if value != testCase.typedValue {
t.Fatalf("GetAnyAsInt expected got %d, expected %d", value, testCase.typedValue)
}
})
}
}
func TestBadGetAnyAsInt(t *testing.T) {
testCases := []struct {
name string
value any
}{
{
name: "string",
value: "value",
},
{
name: "float32 with decimal",
value: float32(1.5),
},
{
name: "float64 with decimal",
value: float64(1.5),
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsInt(testCase.value)
if ok {
t.Fatalf("GetAnyAsInt expected to fail but succeeded, got: %v", value)
}
})
}
}
func TestGoodGetAnyAsByte(t *testing.T) {
testCases := []struct {
name string
value any
typedValue byte
}{
{
name: "int",
value: int(42),
typedValue: 42,
},
{
name: "uint",
value: uint(42),
typedValue: 42,
},
{
name: "float32 without decimal",
value: float32(42.0),
typedValue: 42,
},
{
name: "float64 without decimal",
value: float64(42.0),
typedValue: 42,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsByte(testCase.value)
if !ok {
t.Fatalf("GetAnyAsByte expected to succeed but failed")
}
if value != testCase.typedValue {
t.Fatalf("GetAnyAsByte expected got %d, expected %d", value, testCase.typedValue)
}
})
}
}
func TestBadGetAnyAsByte(t *testing.T) {
testCases := []struct {
name string
value any
}{
{
name: "string",
value: "value",
},
{
name: "float32 with decimal",
value: float32(1.5),
},
{
name: "float64 with decimal",
value: float64(1.5),
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsByte(testCase.value)
if ok {
t.Fatalf("GetAnyAsByte expected to fail but succeeded, got: %v", value)
}
})
}
}
func TestGoodGetAnyAsByteSlice(t *testing.T) {
testCases := []struct {
name string
value any
typedValue []byte
}{
{
name: "byte slice",
value: []byte{1, 2, 3},
typedValue: []byte{1, 2, 3},
},
{
name: "int slice",
value: []int{1, 2, 3},
typedValue: []byte{1, 2, 3},
},
{
name: "uint slice",
value: []uint{1, 2, 3},
typedValue: []byte{1, 2, 3},
},
{
name: "float32 without decimal slice",
value: []float32{1, 2, 3},
typedValue: []byte{1, 2, 3},
},
{
name: "float64 without decimal slice",
value: []float64{1, 2, 3},
typedValue: []byte{1, 2, 3},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsByteSlice(testCase.value)
if !ok {
t.Fatalf("GetAnyAsByteSlice expected to succeed but failed")
}
if !slices.Equal(value, testCase.typedValue) {
t.Fatalf("GetAnyAsByteSlice expected got %d, expected %d", value, testCase.typedValue)
}
})
}
}
func TestBadGetAnyAsByteSlice(t *testing.T) {
testCases := []struct {
name string
value any
}{
{
name: "not a slice",
value: "value",
},
{
name: "not a int slice",
value: []any{"value1", 2},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsByteSlice(testCase.value)
if ok {
t.Fatalf("GetAnyAsByteSlice expected to fail but succeeded, got: %v", value)
}
})
}
}
func TestGoodGetAnyAsIntSlice(t *testing.T) {
testCases := []struct {
name string
value any
typedValue []int
}{
{
name: "int slice",
value: []int{1, 2, 3},
typedValue: []int{1, 2, 3},
},
{
name: "byte slice",
value: []byte{1, 2, 3},
typedValue: []int{1, 2, 3},
},
{
name: "uint slice",
value: []uint{1, 2, 3},
typedValue: []int{1, 2, 3},
},
{
name: "float32 without decimal slice",
value: []float32{1, 2, 3},
typedValue: []int{1, 2, 3},
},
{
name: "float64 without decimal slice",
value: []float64{1, 2, 3},
typedValue: []int{1, 2, 3},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsIntSlice(testCase.value)
if !ok {
t.Fatalf("GetAnyAsIntSlice expected to succeed but failed")
}
if !slices.Equal(value, testCase.typedValue) {
t.Fatalf("GetAnyAsIntSlice expected got %d, expected %d", value, testCase.typedValue)
}
})
}
}
func TestBadGetAnyAsIntSlice(t *testing.T) {
testCases := []struct {
name string
value any
}{
{
name: "not a slice",
value: "value",
},
{
name: "not a int slice",
value: []any{"value1", 2},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsIntSlice(testCase.value)
if ok {
t.Fatalf("GetAnyAsIntSlice expected to fail but succeeded, got: %v", value)
}
})
}
}
func TestGoodGetAnyAsFloat32(t *testing.T) {
testCases := []struct {
name string
value any
typedValue float32
}{
{
name: "int",
value: int(42),
typedValue: 42,
},
{
name: "uint",
value: uint(42),
typedValue: 42,
},
{
name: "byte",
value: byte(42),
typedValue: 42,
},
{
name: "float32",
value: float32(42.3),
typedValue: 42.3,
},
{
name: "float64",
value: float64(42.3),
typedValue: 42.3,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsFloat32(testCase.value)
if !ok {
t.Fatalf("GetAnyAsFloat32 expected to succeed but failed")
}
if value != testCase.typedValue {
t.Fatalf("GetAnyAsFloat32 expected got %f, expected %f", value, testCase.typedValue)
}
})
}
}
func TestBadGetAnyAsFloat32(t *testing.T) {
testCases := []struct {
name string
value any
}{
{
name: "string",
value: "value",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsFloat32(testCase.value)
if ok {
t.Fatalf("GetAnyAsFloat32 expected to fail but succeeded, got: %v", value)
}
})
}
}
func TestGoodGetAnyAsFloat64(t *testing.T) {
testCases := []struct {
name string
value any
typedValue float64
}{
{
name: "int",
value: int(42),
typedValue: 42,
},
{
name: "uint",
value: uint(42),
typedValue: 42,
},
{
name: "byte",
value: byte(42),
typedValue: 42,
},
{
name: "float32",
value: float32(42.5),
typedValue: 42.5,
},
{
name: "float64",
value: float64(42.5),
typedValue: 42.5,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsFloat64(testCase.value)
if !ok {
t.Fatalf("GetAnyAsFloat64 expected to succeed but failed")
}
if value != testCase.typedValue {
t.Fatalf("GetAnyAsFloat64 expected got %f, expected %f", value, testCase.typedValue)
}
})
}
}
func TestBadGetAnyAsFloat64(t *testing.T) {
testCases := []struct {
name string
value any
}{
{
name: "string",
value: "value",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
value, ok := common.GetAnyAsFloat64(testCase.value)
if ok {
t.Fatalf("GetAnyAsFloat64 expected to fail but succeeded, got: %v", value)
}
})
}
}
+26
View File
@@ -0,0 +1,26 @@
package common
import (
"encoding/json"
)
type Event struct {
Type string `json:"type"`
Data any `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
func (e Event) ToJSON() ([]byte, error) {
return json.Marshal(e)
}
type EventDestination interface {
Send(event Event) error
Is(dest EventDestination) bool
}
type EventRouter interface {
HandleEvent(event Event, source EventDestination)
AddEventDestination(dest EventDestination)
RemoveEventDestination(dest EventDestination)
}
+24
View File
@@ -0,0 +1,24 @@
package common_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/common"
)
func TestEventToJson(t *testing.T) {
e := common.Event{
Type: "test.event",
Data: map[string]any{
"key": "value",
},
}
jsonData, err := e.ToJSON()
if err != nil {
t.Fatalf("Failed to convert event to JSON: %v", err)
}
expectedJson := `{"type":"test.event","data":{"key":"value"}}`
if string(jsonData) != expectedJson {
t.Errorf("Expected JSON: %s, got: %s", expectedJson, string(jsonData))
}
}
+30
View File
@@ -0,0 +1,30 @@
package common
import (
"context"
"database/sql"
)
type Module interface {
Id() string
Type() string
Start(context.Context, InputHandler) error
Stop()
}
type OutputModule interface {
Output(context.Context, any) error
}
type KeyValueModule interface {
Get(ctx context.Context, key string) (any, error)
Set(ctx context.Context, key string, value any) error
}
type DatabaseModule interface {
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
}
type PubSubModule interface {
Publish(ctx context.Context, topic string, payload any) error
}
+9
View File
@@ -0,0 +1,9 @@
package common
type WrappedPayload struct {
Payload any
InputHandler InputHandler
Modules map[string]Module
Source string
End bool
}
+12
View File
@@ -0,0 +1,12 @@
package common
import (
"context"
)
type InputHandler func(ctx context.Context, sourceId string, payload any) (bool, []RouteIOError)
type RouteIOError struct {
Index int `json:"index"`
ProcessError error `json:"processError"`
}
+6
View File
@@ -0,0 +1,6 @@
package config
type ApiConfig struct {
Enabled bool `json:"enabled"`
Port int `json:"port"`
}
+4 -15
View File
@@ -1,23 +1,12 @@
package config package config
type Config struct { type Config struct {
Api ApiConfig `json:"api"`
Modules []ModuleConfig `json:"modules"` Modules []ModuleConfig `json:"modules"`
Routes []RouteConfig `json:"routes"` Routes []RouteConfig `json:"routes"`
} }
type ModuleConfig struct { type Configurable interface {
Id string `json:"id"` UpdateConfig(newConfig Config, triggerChangeChannel bool) ([]ModuleError, []RouteError, error)
Type string `json:"type"` GetRunningConfig() Config
Params map[string]any `json:"params"`
}
type RouteConfig struct {
Input string `json:"input"`
Processors []ProcessorConfig `json:"processors"`
Output string `json:"output"`
}
type ProcessorConfig struct {
Type string `json:"type"`
Params map[string]any `json:"params"`
} }
+13
View File
@@ -0,0 +1,13 @@
package config
type ModuleConfig struct {
Id string `json:"id"`
Type string `json:"type"`
Params Params `json:"params,omitempty"`
}
type ModuleError struct {
Index int `json:"index"`
Config ModuleConfig `json:"config"`
Error string `json:"error"`
}
+140
View File
@@ -0,0 +1,140 @@
package config
import (
"errors"
"github.com/jwetzell/showbridge-go/internal/common"
)
type Params map[string]any
var (
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")
ErrParamNotStringSlice = errors.New("not a string slice")
ErrParamNotByteSlice = errors.New("not a byte slice")
ErrParamNotIntSlice = errors.New("not an int slice")
)
func (p Params) GetString(key string) (string, error) {
value, ok := p[key]
if !ok {
return "", ErrParamNotFound
}
stringValue, ok := value.(string)
if !ok {
return "", ErrParamNotString
}
return stringValue, nil
}
func (p Params) GetInt(key string) (int, error) {
value, ok := p[key]
if !ok {
return 0, ErrParamNotFound
}
intValue, ok := common.GetAnyAsInt(value)
if ok {
return intValue, nil
}
return 0, ErrParamNotNumber
}
func (p Params) GetFloat32(key string) (float32, error) {
value, ok := p[key]
if !ok {
return 0, ErrParamNotFound
}
floatValue, ok := common.GetAnyAsFloat32(value)
if ok {
return floatValue, nil
}
return 0, ErrParamNotNumber
}
func (p Params) GetFloat64(key string) (float64, error) {
value, ok := p[key]
if !ok {
return 0, ErrParamNotFound
}
floatValue, ok := common.GetAnyAsFloat64(value)
if ok {
return floatValue, nil
}
return 0, ErrParamNotNumber
}
func (p Params) GetBool(key string) (bool, error) {
value, ok := p[key]
if !ok {
return false, ErrParamNotFound
}
boolValue, ok := value.(bool)
if !ok {
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, ErrParamNotStringSlice
}
stringSlice[i] = str
}
return stringSlice, nil
}
func (p Params) GetIntSlice(key string) ([]int, error) {
value, ok := p[key]
if !ok {
return nil, ErrParamNotFound
}
intSlice, ok := common.GetAnyAsIntSlice(value)
if !ok {
return nil, ErrParamNotIntSlice
}
return intSlice, nil
}
func (p Params) GetByteSlice(key string) ([]byte, error) {
value, ok := p[key]
if !ok {
return nil, ErrParamNotFound
}
byteSlice, ok := common.GetAnyAsByteSlice(value)
if !ok {
return nil, ErrParamNotByteSlice
}
return byteSlice, nil
}
+628
View File
@@ -0,0 +1,628 @@
package config_test
import (
"encoding/json"
"errors"
"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 TestBadStringParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
returnError error
}{
{
name: "key not found",
paramsJSON: `{"key": "value"}`,
key: "test",
returnError: config.ErrParamNotFound,
},
{
name: "not a string",
paramsJSON: `{"key": 1}`,
key: "key",
returnError: config.ErrParamNotString,
},
}
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 expected to fail but succeeded, got: %v", value)
}
if !errors.Is(err, testCase.returnError) {
t.Fatalf("GetString got error '%s', expected '%s'", err, testCase.returnError)
}
})
}
}
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 TestBadIntParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
returnError error
}{
{
name: "key not found",
paramsJSON: `{"key": 1}`,
key: "test",
returnError: config.ErrParamNotFound,
},
{
name: "not a number",
paramsJSON: `{"key": "1"}`,
key: "key",
returnError: config.ErrParamNotNumber,
},
}
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 expected to fail but succeeded, got: %v", value)
}
if !errors.Is(err, testCase.returnError) {
t.Fatalf("GetInt got error '%s', expected '%s'", err, testCase.returnError)
}
})
}
}
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 TestBadFloat32ParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
returnError error
}{
{
name: "key not found",
paramsJSON: `{"key": 1}`,
key: "test",
returnError: config.ErrParamNotFound,
},
{
name: "not a number",
paramsJSON: `{"key": "1"}`,
key: "key",
returnError: config.ErrParamNotNumber,
},
}
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 expected to fail but succeeded, got: %v", value)
}
if !errors.Is(err, testCase.returnError) {
t.Fatalf("GetFloat32 got error '%s', expected '%s'", err, testCase.returnError)
}
})
}
}
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 TestBadFloat64ParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
returnError error
}{
{
name: "key not found",
paramsJSON: `{"key": 1}`,
key: "test",
returnError: config.ErrParamNotFound,
},
{
name: "not a number",
paramsJSON: `{"key": "1"}`,
key: "key",
returnError: config.ErrParamNotNumber,
},
}
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 expected to fail but succeeded, got: %v", value)
}
if !errors.Is(err, testCase.returnError) {
t.Fatalf("GetFloat64 got error '%s', expected '%s'", err, testCase.returnError)
}
})
}
}
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 TestBadBoolParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
returnError error
}{
{
name: "key not found",
paramsJSON: `{"key": 1}`,
key: "test",
returnError: config.ErrParamNotFound,
},
{
name: "not a bool",
paramsJSON: `{"key": "1"}`,
key: "key",
returnError: config.ErrParamNotBool,
},
}
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 expected to fail but succeeded, got: %v", value)
}
if !errors.Is(err, testCase.returnError) {
t.Fatalf("GetBool got error '%s', expected '%s'", err, testCase.returnError)
}
})
}
}
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 TestBadStringSliceParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
returnError error
}{
{
name: "key not found",
paramsJSON: `{"key": ["value1", "value2"]}`,
key: "test",
returnError: config.ErrParamNotFound,
},
{
name: "not a slice",
paramsJSON: `{"key": "value"}`,
key: "key",
returnError: config.ErrParamNotSlice,
},
{
name: "not a string slice",
paramsJSON: `{"key": ["value1", 2]}`,
key: "key",
returnError: config.ErrParamNotStringSlice,
},
}
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 expected to fail but succeeded, got: %v", value)
}
if !errors.Is(err, testCase.returnError) {
t.Fatalf("GetStringSlice got error '%s', expected '%s'", err, testCase.returnError)
}
})
}
}
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 TestBadIntSliceParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
returnError error
}{
{
name: "key not found",
paramsJSON: `{"key": ["value1", "value2"]}`,
key: "test",
returnError: config.ErrParamNotFound,
},
{
name: "not a slice",
paramsJSON: `{"key": "value"}`,
key: "key",
returnError: config.ErrParamNotIntSlice,
},
{
name: "not a int slice",
paramsJSON: `{"key": ["value1", 2]}`,
key: "key",
returnError: config.ErrParamNotIntSlice,
},
}
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 expected to fail but succeeded, got: %v", value)
}
if !errors.Is(err, testCase.returnError) {
t.Fatalf("GetIntSlice got error '%s', expected '%s'", err, testCase.returnError)
}
})
}
}
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)
}
})
}
}
func TestBadByteSliceParamsJSON(t *testing.T) {
testCases := []struct {
name string
paramsJSON string
key string
returnError error
}{
{
name: "key not found",
paramsJSON: `{"key": ["value1", "value2"]}`,
key: "test",
returnError: config.ErrParamNotFound,
},
{
name: "not a slice",
paramsJSON: `{"key": "value"}`,
key: "key",
returnError: config.ErrParamNotByteSlice,
},
{
name: "not a int slice",
paramsJSON: `{"key": ["value1", 2]}`,
key: "key",
returnError: config.ErrParamNotByteSlice,
},
}
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 expected to fail but succeeded, got: %v", value)
}
if !errors.Is(err, testCase.returnError) {
t.Fatalf("GetByteSlice got error '%s', expected '%s'", err, testCase.returnError)
}
})
}
}
+7
View File
@@ -0,0 +1,7 @@
package config
type ProcessorConfig struct {
Id string `json:"id"`
Type string `json:"type"`
Params Params `json:"params,omitempty"`
}
+13
View File
@@ -0,0 +1,13 @@
package config
type RouteConfig struct {
Id string `json:"id"`
Input string `json:"input"`
Processors []ProcessorConfig `json:"processors"`
}
type RouteError struct {
Index int `json:"index"`
Config RouteConfig `json:"config"`
Error string `json:"error"`
}
+5 -5
View File
@@ -10,15 +10,15 @@ type Framer interface {
func GetFramer(framingType string) Framer { func GetFramer(framingType string) Framer {
switch framingType { switch framingType {
case "CR": case "CR":
return NewByteSeparatorFramer([]byte{'\r'}) return newByteSeparatorFramer([]byte{'\r'})
case "LF": case "LF":
return NewByteSeparatorFramer([]byte{'\n'}) return newByteSeparatorFramer([]byte{'\n'})
case "CRLF": case "CRLF":
return NewByteSeparatorFramer([]byte{'\r', '\n'}) return newByteSeparatorFramer([]byte{'\r', '\n'})
case "SLIP": case "SLIP":
return NewSlipFramer() return newSlipFramer()
case "RAW": case "RAW":
return NewRawFramer() return newRawFramer()
default: default:
return nil return nil
} }
+7 -7
View File
@@ -1,23 +1,23 @@
package framer package framer
type RawFramer struct{} type rawFramer struct{}
func NewRawFramer() *RawFramer { func newRawFramer() *rawFramer {
return &RawFramer{} return &rawFramer{}
} }
func (rf *RawFramer) Decode(data []byte) [][]byte { func (rf *rawFramer) Decode(data []byte) [][]byte {
return [][]byte{data} return [][]byte{data}
} }
func (rf *RawFramer) Encode(data []byte) []byte { func (rf *rawFramer) Encode(data []byte) []byte {
return data return data
} }
func (rf *RawFramer) Clear() { func (rf *rawFramer) Clear() {
// NOTE(jwetzell): no internal state to clear // NOTE(jwetzell): no internal state to clear
} }
func (rf *RawFramer) Buffer() []byte { func (rf *rawFramer) Buffer() []byte {
return []byte{} return []byte{}
} }
+7 -7
View File
@@ -4,16 +4,16 @@ import (
"bytes" "bytes"
) )
type ByteSeparatorFramer struct { type byteSeparatorFramer struct {
buffer []byte buffer []byte
separator []byte separator []byte
} }
func NewByteSeparatorFramer(separator []byte) *ByteSeparatorFramer { func newByteSeparatorFramer(separator []byte) *byteSeparatorFramer {
return &ByteSeparatorFramer{separator: separator, buffer: []byte{}} return &byteSeparatorFramer{separator: separator, buffer: []byte{}}
} }
func (bsf *ByteSeparatorFramer) Decode(data []byte) [][]byte { func (bsf *byteSeparatorFramer) Decode(data []byte) [][]byte {
messages := [][]byte{} messages := [][]byte{}
bsf.buffer = append(bsf.buffer, data...) bsf.buffer = append(bsf.buffer, data...)
@@ -28,14 +28,14 @@ func (bsf *ByteSeparatorFramer) Decode(data []byte) [][]byte {
return messages return messages
} }
func (bsf *ByteSeparatorFramer) Encode(data []byte) []byte { func (bsf *byteSeparatorFramer) Encode(data []byte) []byte {
return append(data, bsf.separator...) return append(data, bsf.separator...)
} }
func (bsf *ByteSeparatorFramer) Clear() { func (bsf *byteSeparatorFramer) Clear() {
bsf.buffer = []byte{} bsf.buffer = []byte{}
} }
func (bsf *ByteSeparatorFramer) Buffer() []byte { func (bsf *byteSeparatorFramer) Buffer() []byte {
return bsf.buffer return bsf.buffer
} }
+10 -9
View File
@@ -1,14 +1,14 @@
package framer package framer
type SlipFramer struct { type slipFramer struct {
buffer []byte buffer []byte
} }
func NewSlipFramer() *SlipFramer { func newSlipFramer() *slipFramer {
return &SlipFramer{buffer: []byte{}} return &slipFramer{buffer: []byte{}}
} }
func (sf *SlipFramer) Decode(data []byte) [][]byte { func (sf *slipFramer) Decode(data []byte) [][]byte {
messages := [][]byte{} messages := [][]byte{}
END := byte(0xc0) END := byte(0xc0)
@@ -25,9 +25,10 @@ func (sf *SlipFramer) Decode(data []byte) [][]byte {
} }
if escapeNext { if escapeNext {
if packetByte == ESC_END { switch packetByte {
case ESC_END:
sf.buffer = append(sf.buffer, END) sf.buffer = append(sf.buffer, END)
} else if packetByte == ESC_ESC { case ESC_ESC:
sf.buffer = append(sf.buffer, ESC) sf.buffer = append(sf.buffer, ESC)
} }
escapeNext = false escapeNext = false
@@ -48,7 +49,7 @@ func (sf *SlipFramer) Decode(data []byte) [][]byte {
return messages return messages
} }
func (sf *SlipFramer) Encode(data []byte) []byte { func (sf *slipFramer) Encode(data []byte) []byte {
END := byte(0xc0) END := byte(0xc0)
ESC := byte(0xdb) ESC := byte(0xdb)
ESC_END := byte(0xdc) ESC_END := byte(0xdc)
@@ -71,10 +72,10 @@ func (sf *SlipFramer) Encode(data []byte) []byte {
return encodedBytes return encodedBytes
} }
func (sf *SlipFramer) Clear() { func (sf *slipFramer) Clear() {
sf.buffer = []byte{} sf.buffer = []byte{}
} }
func (sf *SlipFramer) Buffer() []byte { func (sf *slipFramer) Buffer() []byte {
return sf.buffer return sf.buffer
} }
+103
View File
@@ -0,0 +1,103 @@
package module
import (
"context"
"database/sql"
"fmt"
"log/slog"
"sync"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
_ "modernc.org/sqlite"
)
func init() {
RegisterModule(ModuleRegistration{
Type: "db.sqlite",
Title: "SQLite Database",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"dsn": {
Title: "Data Source Name",
Description: "the data source name (DSN) for the SQLite database",
Type: "string",
MinLength: new(1),
},
},
Required: []string{"dsn"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params
dsnString, err := params.GetString("dsn")
if err != nil {
return nil, fmt.Errorf("db.sqlite dsn error: %w", err)
}
return &DbSqlite{Dsn: dsnString, config: config, logger: CreateLogger(config)}, nil
},
})
}
type DbSqlite struct {
config config.ModuleConfig
Dsn string
ctx context.Context
inputHandler common.InputHandler
db *sql.DB
logger *slog.Logger
dbMu sync.Mutex
cancel context.CancelFunc
}
func (dbs *DbSqlite) Id() string {
return dbs.config.Id
}
func (dbs *DbSqlite) Type() string {
return dbs.config.Type
}
func (dbs *DbSqlite) Start(ctx context.Context, inputHandler common.InputHandler) error {
dbs.logger.Debug("running")
dbs.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
dbs.ctx = moduleContext
dbs.cancel = cancel
db, err := sql.Open("sqlite", dbs.Dsn)
if err != nil {
return fmt.Errorf("db.sqlite error opening database: %w", err)
}
dbs.dbMu.Lock()
dbs.db = db
dbs.dbMu.Unlock()
<-dbs.ctx.Done()
dbs.logger.Debug("done")
return nil
}
func (dbs *DbSqlite) Stop() {
if dbs.cancel != nil {
defer dbs.cancel()
}
dbs.dbMu.Lock()
defer dbs.dbMu.Unlock()
if dbs.db != nil {
dbs.db.Close()
}
}
func (dbs *DbSqlite) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
dbs.dbMu.Lock()
defer dbs.dbMu.Unlock()
if dbs.db == nil {
return nil, fmt.Errorf("database not initialized")
}
return dbs.db.QueryContext(ctx, query, args...)
}
-80
View File
@@ -1,80 +0,0 @@
package module
import (
"context"
"errors"
"log/slog"
"net/http"
"time"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
)
type HTTPClient struct {
config config.ModuleConfig
ctx context.Context
client *http.Client
router route.RouteIO
logger *slog.Logger
}
func init() {
RegisterModule(ModuleRegistration{
Type: "http.client",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) {
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("http.client unable to get router from context")
}
return &HTTPClient{config: config, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
},
})
}
func (hc *HTTPClient) Id() string {
return hc.config.Id
}
func (hc *HTTPClient) Type() string {
return hc.config.Type
}
func (hc *HTTPClient) Run() error {
hc.client = &http.Client{
Timeout: 10 * time.Second,
}
<-hc.ctx.Done()
hc.logger.Debug("done")
return nil
}
func (hc *HTTPClient) Output(ctx context.Context, payload any) error {
payloadRequest, ok := payload.(*http.Request)
if !ok {
return errors.New("http.client is only able to output an http.Request")
}
if hc.client == nil {
return errors.New("http.client client is nil")
}
response, err := hc.client.Do(payloadRequest)
if err != nil {
return err
}
if hc.router != nil {
hc.router.HandleInput(hc.ctx, hc.Id(), response)
}
return nil
}
+66 -55
View File
@@ -7,25 +7,58 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"net/http" "net/http"
"sync"
"time"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor" "github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
) )
func init() {
RegisterModule(ModuleRegistration{
Type: "http.server",
Title: "HTTP Server",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"port": {
Title: "Port",
Description: "the port for the HTTP server to listen on",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1024),
Maximum: jsonschema.Ptr[float64](65535),
},
},
Required: []string{"port"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params
portNum, err := params.GetInt("port")
if err != nil {
return nil, fmt.Errorf("http.server port error: %w", err)
}
return &HTTPServer{Port: uint16(portNum), config: config, logger: CreateLogger(config)}, nil
},
})
}
type HTTPServer struct { type HTTPServer struct {
config config.ModuleConfig config config.ModuleConfig
Port uint16 Port uint16
ctx context.Context ctx context.Context
router route.RouteIO inputHandler common.InputHandler
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
server *http.Server
serverMu sync.Mutex
} }
type ResponseIOError struct { type ResponseIOError struct {
Index int `json:"index"` Index int `json:"index"`
OutputErrors []string `json:"outputErrors"`
ProcessError *string `json:"processError"` ProcessError *string `json:"processError"`
InputError *string `json:"inputError"`
} }
type IOResponseData struct { type IOResponseData struct {
@@ -51,33 +84,6 @@ func (hsrw *HTTPServerResponseWriter) Write(data []byte) (int, error) {
return hsrw.ResponseWriter.Write(data) return hsrw.ResponseWriter.Write(data)
} }
func init() {
RegisterModule(ModuleRegistration{
Type: "http.server",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) {
params := config.Params
port, ok := params["port"]
if !ok {
return nil, errors.New("http.server requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, errors.New("http.server port must be uint16")
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("http.server unable to get router from context")
}
return &HTTPServer{Port: uint16(portNum), config: config, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
},
})
}
func (hs *HTTPServer) Id() string { func (hs *HTTPServer) Id() string {
return hs.config.Id return hs.config.Id
} }
@@ -93,9 +99,9 @@ func (hs *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Message: "routing successful", Message: "routing successful",
Status: "ok", Status: "ok",
} }
if hs.router != nil { if hs.inputHandler != nil {
inputContext := context.WithValue(hs.ctx, httpServerContextKey("responseWriter"), &responseWriter) inputContext := context.WithValue(hs.ctx, httpServerContextKey("responseWriter"), &responseWriter)
aRouteFound, routingErrors := hs.router.HandleInput(inputContext, hs.Id(), r) aRouteFound, routingErrors := hs.inputHandler(inputContext, hs.Id(), r)
if !responseWriter.done { if !responseWriter.done {
if aRouteFound { if aRouteFound {
if routingErrors != nil { if routingErrors != nil {
@@ -109,26 +115,11 @@ func (hs *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Index: responseIOError.Index, Index: responseIOError.Index,
} }
if responseIOError.InputError != nil {
errorMsg := responseIOError.InputError.Error()
errorToAdd.InputError = &errorMsg
}
if responseIOError.ProcessError != nil { if responseIOError.ProcessError != nil {
errorMsg := responseIOError.ProcessError.Error() errorMsg := responseIOError.ProcessError.Error()
errorToAdd.ProcessError = &errorMsg errorToAdd.ProcessError = &errorMsg
} }
if responseIOError.OutputErrors != nil {
outputErrorMsgs := []string{}
for _, outputError := range responseIOError.OutputErrors {
outputErrorMsgs = append(outputErrorMsgs, outputError.Error())
}
errorToAdd.OutputErrors = outputErrorMsgs
}
response.IOErrors = append(response.IOErrors, errorToAdd) response.IOErrors = append(response.IOErrors, errorToAdd)
} }
@@ -157,21 +148,27 @@ func (hs *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
} }
func (hs *HTTPServer) Run() error { func (hs *HTTPServer) Start(ctx context.Context, inputHandler common.InputHandler) error {
hs.logger.Debug("running")
hs.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
hs.ctx = moduleContext
hs.cancel = cancel
httpServer := &http.Server{ httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", hs.Port), Addr: fmt.Sprintf(":%d", hs.Port),
ReadHeaderTimeout: 5 * time.Second,
Handler: hs, Handler: hs,
} }
go func() { hs.serverMu.Lock()
<-hs.ctx.Done() hs.server = httpServer
httpServer.Close() hs.serverMu.Unlock()
}()
err := httpServer.ListenAndServe() err := httpServer.ListenAndServe()
// TODO(jwetzell): handle server closed error differently // TODO(jwetzell): handle server closed error differently
if err != nil { if err != nil {
if err.Error() != "http: Server closed" { if !errors.Is(err, http.ErrServerClosed) {
return err return err
} }
} }
@@ -188,7 +185,7 @@ func (hs *HTTPServer) Output(ctx context.Context, payload any) error {
return errors.New("http.server output must originate from an http.server input") return errors.New("http.server output must originate from an http.server input")
} }
payloadResponse, ok := payload.(processor.HTTPResponse) payloadResponse, ok := common.GetAnyAs[processor.HTTPResponse](payload)
if !ok { if !ok {
return errors.New("http.server is only able to output HTTPResponse") return errors.New("http.server is only able to output HTTPResponse")
@@ -202,3 +199,17 @@ func (hs *HTTPServer) Output(ctx context.Context, payload any) error {
responseWriter.Write(payloadResponse.Body) responseWriter.Write(payloadResponse.Body)
return nil return nil
} }
func (hs *HTTPServer) Stop() {
if hs.cancel != nil {
defer hs.cancel()
}
hs.serverMu.Lock()
defer hs.serverMu.Unlock()
if hs.server != nil {
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
hs.server.Shutdown(shutdownCtx)
shutdownCancel()
<-shutdownCtx.Done()
}
}
+47 -37
View File
@@ -4,53 +4,54 @@ package module
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
"gitlab.com/gomidi/midi/v2" "gitlab.com/gomidi/midi/v2"
_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv" _ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
) )
type MIDIInput struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
Port string
SendFunc func(midi.Message) error
logger *slog.Logger
}
func init() { func init() {
RegisterModule(ModuleRegistration{ RegisterModule(ModuleRegistration{
Type: "midi.input", Type: "midi.input",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) { Title: "MIDI Input",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"port": {
Title: "Port",
Description: "the name of the MIDI port to listen to",
Type: "string",
},
},
Required: []string{"port"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params params := config.Params
port, ok := params["port"] portString, err := params.GetString("port")
if err != nil {
if !ok { return nil, fmt.Errorf("midi.input port error: %w", err)
return nil, errors.New("midi.input requires a port parameter")
} }
portString, ok := port.(string) return &MIDIInput{config: config, Port: portString, logger: CreateLogger(config)}, nil
if !ok {
return nil, errors.New("midi.input port must be a string")
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("midi.input unable to get router from context")
}
return &MIDIInput{config: config, Port: portString, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
}, },
}) })
} }
type MIDIInput struct {
config config.ModuleConfig
ctx context.Context
inputHandler common.InputHandler
Port string
logger *slog.Logger
cancel context.CancelFunc
stop func()
}
func (mi *MIDIInput) Id() string { func (mi *MIDIInput) Id() string {
return mi.config.Id return mi.config.Id
} }
@@ -59,8 +60,12 @@ func (mi *MIDIInput) Type() string {
return mi.config.Type return mi.config.Type
} }
func (mi *MIDIInput) Run() error { func (mi *MIDIInput) Start(ctx context.Context, inputHandler common.InputHandler) error {
defer midi.CloseDriver() mi.logger.Debug("running")
mi.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
mi.ctx = moduleContext
mi.cancel = cancel
in, err := midi.FindInPort(mi.Port) in, err := midi.FindInPort(mi.Port)
if err != nil { if err != nil {
@@ -68,22 +73,27 @@ func (mi *MIDIInput) Run() error {
} }
stop, err := midi.ListenTo(in, func(msg midi.Message, timestampms int32) { stop, err := midi.ListenTo(in, func(msg midi.Message, timestampms int32) {
if mi.router != nil { if mi.inputHandler != nil {
mi.router.HandleInput(mi.ctx, mi.Id(), msg) mi.inputHandler(mi.ctx, mi.Id(), msg)
} }
}, midi.UseSysEx()) }, midi.UseSysEx())
if err != nil { if err != nil {
return err return err
} }
mi.stop = stop
defer stop()
<-mi.ctx.Done() <-mi.ctx.Done()
mi.logger.Debug("done") mi.logger.Debug("done")
return nil return nil
} }
func (mi *MIDIInput) Output(ctx context.Context, payload any) error { func (mi *MIDIInput) Stop() {
return errors.New("midi.input output is not implemented") if mi.cancel != nil {
defer mi.cancel()
}
if mi.stop != nil {
mi.stop()
}
midi.CloseDriver()
} }
+54 -35
View File
@@ -7,51 +7,55 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"sync"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
"gitlab.com/gomidi/midi/v2" "gitlab.com/gomidi/midi/v2"
_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv" _ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
) )
type MIDIOutput struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
Port string
SendFunc func(midi.Message) error
logger *slog.Logger
}
func init() { func init() {
RegisterModule(ModuleRegistration{ RegisterModule(ModuleRegistration{
Type: "midi.output", Type: "midi.output",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) { Title: "MIDI Output",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"port": {
Title: "Port",
Description: "the name of the MIDI port to send messages to",
Type: "string",
},
},
Required: []string{"port"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params params := config.Params
port, ok := params["port"] portString, err := params.GetString("port")
if err != nil {
if !ok { return nil, fmt.Errorf("midi.output port error: %w", err)
return nil, errors.New("midi.output requires a port parameter")
} }
portString, ok := port.(string) return &MIDIOutput{config: config, Port: portString, logger: CreateLogger(config)}, nil
if !ok {
return nil, errors.New("midi.output port must be a string")
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("midi.output unable to get router from context")
}
return &MIDIOutput{config: config, Port: portString, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
}, },
}) })
} }
type MIDIOutput struct {
config config.ModuleConfig
ctx context.Context
inputHandler common.InputHandler
Port string
sendFunc func(midi.Message) error
logger *slog.Logger
cancel context.CancelFunc
sendFuncMu sync.Mutex
}
func (mo *MIDIOutput) Id() string { func (mo *MIDIOutput) Id() string {
return mo.config.Id return mo.config.Id
} }
@@ -60,8 +64,12 @@ func (mo *MIDIOutput) Type() string {
return mo.config.Type return mo.config.Type
} }
func (mo *MIDIOutput) Run() error { func (mo *MIDIOutput) Start(ctx context.Context, inputHandler common.InputHandler) error {
defer midi.CloseDriver() mo.logger.Debug("running")
mo.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
mo.ctx = moduleContext
mo.cancel = cancel
out, err := midi.FindOutPort(mo.Port) out, err := midi.FindOutPort(mo.Port)
@@ -74,7 +82,9 @@ func (mo *MIDIOutput) Run() error {
return err return err
} }
mo.SendFunc = send mo.sendFuncMu.Lock()
mo.sendFunc = send
mo.sendFuncMu.Unlock()
<-mo.ctx.Done() <-mo.ctx.Done()
mo.logger.Debug("done") mo.logger.Debug("done")
@@ -82,15 +92,24 @@ func (mo *MIDIOutput) Run() error {
} }
func (mo *MIDIOutput) Output(ctx context.Context, payload any) error { func (mo *MIDIOutput) Output(ctx context.Context, payload any) error {
if mo.SendFunc == nil { mo.sendFuncMu.Lock()
defer mo.sendFuncMu.Unlock()
if mo.sendFunc == nil {
return errors.New("midi.output output is not setup") return errors.New("midi.output output is not setup")
} }
payloadMessage, ok := payload.(midi.Message) payloadMessage, ok := common.GetAnyAs[midi.Message](payload)
if !ok { if !ok {
return errors.New("midi.output can only ouptut midi.Message") return errors.New("midi.output can only output midi.Message")
} }
return mo.SendFunc(payloadMessage) return mo.sendFunc(payloadMessage)
}
func (mo *MIDIOutput) Stop() {
if mo.cancel != nil {
defer mo.cancel()
}
midi.CloseDriver()
} }
+31 -18
View File
@@ -1,30 +1,21 @@
package module package module
import ( import (
"context"
"fmt" "fmt"
"log/slog" "log/slog"
"sync" "sync"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
type ModuleError struct {
Index int
Config config.ModuleConfig
Error error
}
type Module interface {
Id() string
Type() string
Run() error
Output(context.Context, any) error
}
type ModuleRegistration struct { type ModuleRegistration struct {
Type string `json:"type"` Type string `json:"type"`
New func(context.Context, config.ModuleConfig) (Module, error) Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
ParamsSchema *jsonschema.Schema `json:"paramsSchema,omitempty"`
New func(config.ModuleConfig) (common.Module, error)
} }
func RegisterModule(mod ModuleRegistration) { func RegisterModule(mod ModuleRegistration) {
@@ -39,15 +30,37 @@ func RegisterModule(mod ModuleRegistration) {
moduleRegistryMu.Lock() moduleRegistryMu.Lock()
defer moduleRegistryMu.Unlock() defer moduleRegistryMu.Unlock()
if _, ok := ModuleRegistry[string(mod.Type)]; ok { _, exists := moduleRegistry[string(mod.Type)]
if exists {
panic(fmt.Sprintf("module already registered: %s", mod.Type)) panic(fmt.Sprintf("module already registered: %s", mod.Type))
} }
ModuleRegistry[string(mod.Type)] = mod moduleRegistry[string(mod.Type)] = mod
}
type ModuleRegistry map[string]ModuleRegistration
func GetModuleRegistration(moduleType string) (ModuleRegistration, bool) {
moduleRegistryMu.RLock()
defer moduleRegistryMu.RUnlock()
mod, ok := moduleRegistry[moduleType]
return mod, ok
}
func GetModuleRegistrations() []ModuleRegistration {
moduleRegistryMu.RLock()
defer moduleRegistryMu.RUnlock()
registrations := make([]ModuleRegistration, 0, len(moduleRegistry))
for _, mod := range moduleRegistry {
registrations = append(registrations, mod)
}
return registrations
} }
var ( var (
moduleRegistryMu sync.RWMutex moduleRegistryMu sync.RWMutex
ModuleRegistry = make(map[string]ModuleRegistration) moduleRegistry = make(ModuleRegistry)
) )
func CreateLogger(config config.ModuleConfig) *slog.Logger { func CreateLogger(config config.ModuleConfig) *slog.Logger {
+114 -48
View File
@@ -2,77 +2,118 @@ package module
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt"
"log/slog" "log/slog"
"sync"
mqtt "github.com/eclipse/paho.mqtt.golang" mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
) )
type MQTTClient struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
Broker string
ClientID string
Topic string
client mqtt.Client
logger *slog.Logger
}
func init() { func init() {
RegisterModule(ModuleRegistration{ RegisterModule(ModuleRegistration{
Type: "mqtt.client", Type: "mqtt.client",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) { Title: "MQTT Client",
params := config.Params ParamsSchema: &jsonschema.Schema{
broker, ok := params["broker"] Type: "object",
Properties: map[string]*jsonschema.Schema{
"broker": {
Title: "Broker URL",
Description: "the URL of the MQTT broker to connect to",
Type: "string",
},
"topic": {
Title: "Topic",
Description: "an MQTT topic to subscribe to",
Type: "string",
},
"clientId": {
Title: "Client ID",
Description: "the client ID to use when connecting to the MQTT broker",
Type: "string",
},
"qos": {
Title: "QoS",
Description: "the QoS level to use when publishing messages",
Type: "integer",
Minimum: jsonschema.Ptr[float64](0),
Maximum: jsonschema.Ptr[float64](2),
Default: json.RawMessage(`0`),
},
"retained": {
Title: "Retained",
Description: "whether to set the retained flag when publishing messages",
Type: "boolean",
Default: json.RawMessage(`false`),
},
},
Required: []string{"broker", "topic", "clientId"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(moduleConfig config.ModuleConfig) (common.Module, error) {
params := moduleConfig.Params
brokerString, err := params.GetString("broker")
if !ok { if err != nil {
return nil, errors.New("mqtt.client requires a broker parameter") return nil, fmt.Errorf("mqtt.client broker error: %w", err)
} }
brokerString, ok := broker.(string) topicString, err := params.GetString("topic")
if !ok { if err != nil {
return nil, errors.New("mqtt.client broker must be string") return nil, fmt.Errorf("mqtt.client topic error: %w", err)
} }
topic, ok := params["topic"] clientIdString, err := params.GetString("clientId")
if !ok { if err != nil {
return nil, errors.New("mqtt.client requires a topic parameter") return nil, fmt.Errorf("mqtt.client clientId error: %w", err)
} }
topicString, ok := topic.(string) qosString, err := params.GetInt("qos")
if !ok { if err != nil {
return nil, errors.New("mqtt.client topic must be string") if errors.Is(err, config.ErrParamNotFound) {
qosString = 0
} else {
return nil, fmt.Errorf("mqtt.client qos error: %w", err)
}
} }
clientId, ok := params["clientId"] retainedBool, err := params.GetBool("retained")
if !ok { if err != nil {
return nil, errors.New("mqtt.client requires a clientId parameter") if errors.Is(err, config.ErrParamNotFound) {
retainedBool = false
} else {
return nil, fmt.Errorf("mqtt.client retained error: %w", err)
}
} }
clientIdString, ok := clientId.(string) return &MQTTClient{config: moduleConfig, Broker: brokerString, Topic: topicString, ClientID: clientIdString, QoS: byte(qosString), Retained: retainedBool, logger: CreateLogger(moduleConfig)}, nil
if !ok {
return nil, errors.New("mqtt.client clientId must be string")
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("mqtt.client unable to get router from context")
}
return &MQTTClient{config: config, Broker: brokerString, Topic: topicString, ClientID: clientIdString, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
}, },
}) })
} }
type MQTTClient struct {
config config.ModuleConfig
ctx context.Context
inputHandler common.InputHandler
Broker string
ClientID string
Topic string
QoS byte
Retained bool
client mqtt.Client
logger *slog.Logger
cancel context.CancelFunc
clientMu sync.Mutex
}
func (mc *MQTTClient) Id() string { func (mc *MQTTClient) Id() string {
return mc.config.Id return mc.config.Id
} }
@@ -81,7 +122,13 @@ func (mc *MQTTClient) Type() string {
return mc.config.Type return mc.config.Type
} }
func (mc *MQTTClient) Run() error { func (mc *MQTTClient) Start(ctx context.Context, inputHandler common.InputHandler) error {
mc.logger.Debug("running")
mc.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
mc.ctx = moduleContext
mc.cancel = cancel
opts := mqtt.NewClientOptions() opts := mqtt.NewClientOptions()
opts.AddBroker(mc.Broker) opts.AddBroker(mc.Broker)
opts.SetClientID(mc.ClientID) opts.SetClientID(mc.ClientID)
@@ -90,14 +137,18 @@ func (mc *MQTTClient) Run() error {
opts.OnConnect = func(c mqtt.Client) { opts.OnConnect = func(c mqtt.Client) {
token := mc.client.Subscribe(mc.Topic, 1, func(c mqtt.Client, m mqtt.Message) { token := mc.client.Subscribe(mc.Topic, 1, func(c mqtt.Client, m mqtt.Message) {
mc.router.HandleInput(mc.ctx, mc.Id(), m) if mc.inputHandler != nil {
mc.inputHandler(mc.ctx, mc.Id(), m)
}
}) })
token.Wait() token.Wait()
} }
mc.clientMu.Lock()
mc.client = mqtt.NewClient(opts) mc.client = mqtt.NewClient(opts)
token := mc.client.Connect() token := mc.client.Connect()
mc.clientMu.Unlock()
token.Wait() token.Wait()
err := token.Error() err := token.Error()
@@ -110,11 +161,15 @@ func (mc *MQTTClient) Run() error {
return nil return nil
} }
func (mc *MQTTClient) Output(ctx context.Context, payload any) error { func (mc *MQTTClient) Publish(ctx context.Context, topic string, payload any) error {
payloadMessage, ok := payload.(mqtt.Message) payloadBytes, ok := common.GetAnyAsByteSlice(payload)
if !ok { if !ok {
return errors.New("mqtt.client is only able to output a MQTTMessage") payloadString, ok := common.GetAnyAs[string](payload)
if !ok {
return errors.New("mqtt.client is only able to publish bytes or string")
}
payloadBytes = []byte(payloadString)
} }
if mc.client == nil { if mc.client == nil {
@@ -125,9 +180,20 @@ func (mc *MQTTClient) Output(ctx context.Context, payload any) error {
return errors.New("mqtt.client is not connected") return errors.New("mqtt.client is not connected")
} }
token := mc.client.Publish(payloadMessage.Topic(), payloadMessage.Qos(), payloadMessage.Retained(), payloadMessage.Payload()) token := mc.client.Publish(topic, mc.QoS, mc.Retained, payloadBytes)
token.Wait() token.Wait()
return token.Error() return token.Error()
} }
func (mc *MQTTClient) Stop() {
if mc.cancel != nil {
mc.cancel()
}
mc.clientMu.Lock()
defer mc.clientMu.Unlock()
if mc.client != nil {
mc.client.Disconnect(250)
}
}
+87 -51
View File
@@ -4,63 +4,67 @@ import (
"context" "context"
"errors" "errors"
"log/slog" "log/slog"
"sync"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
) )
type NATSClient struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
URL string
Subject string
client *nats.Conn
logger *slog.Logger
}
func init() { func init() {
RegisterModule(ModuleRegistration{ RegisterModule(ModuleRegistration{
Type: "nats.client", Type: "nats.client",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) { Title: "NATS Client",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"url": {
Title: "NATS Server URL",
Description: "the URL of the NATS server to connect to",
Type: "string",
},
"subject": {
Title: "Subject",
Description: "the subject to subscribe to",
Type: "string",
},
},
Required: []string{"url", "subject"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params params := config.Params
url, ok := params["url"] urlString, err := params.GetString("url")
if err != nil {
if !ok { return nil, errors.New("nats.client url error: " + err.Error())
return nil, errors.New("nats.client requires a url parameter")
} }
urlString, ok := url.(string) subjectString, err := params.GetString("subject")
if !ok { if err != nil {
return nil, errors.New("nats.client url must be string") return nil, errors.New("nats.client subject error: " + err.Error())
} }
subject, ok := params["subject"] return &NATSClient{config: config, URL: urlString, Subject: subjectString, logger: CreateLogger(config)}, nil
if !ok {
return nil, errors.New("nats.client requires a subject parameter")
}
subjectString, ok := subject.(string)
if !ok {
return nil, errors.New("nats.client subject must be string")
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("nats.client unable to get router from context")
}
return &NATSClient{config: config, URL: urlString, Subject: subjectString, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
}, },
}) })
} }
type NATSClient struct {
config config.ModuleConfig
ctx context.Context
inputHandler common.InputHandler
URL string
Subject string
client *nats.Conn
logger *slog.Logger
cancel context.CancelFunc
sub *nats.Subscription
subMu sync.Mutex
clientMu sync.Mutex
}
func (nc *NATSClient) Id() string { func (nc *NATSClient) Id() string {
return nc.config.Id return nc.config.Id
} }
@@ -69,42 +73,55 @@ func (nc *NATSClient) Type() string {
return nc.config.Type return nc.config.Type
} }
func (nc *NATSClient) Run() error { func (nc *NATSClient) Start(ctx context.Context, inputHandler common.InputHandler) error {
nc.logger.Debug("running")
nc.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
nc.ctx = moduleContext
nc.cancel = cancel
client, err := nats.Connect(nc.URL, nats.RetryOnFailedConnect(true)) client, err := nats.Connect(nc.URL, nats.RetryOnFailedConnect(true))
if err != nil { if err != nil {
return err return err
} }
nc.clientMu.Lock()
nc.client = client nc.client = client
nc.clientMu.Unlock()
defer client.Drain()
defer client.Close()
sub, err := nc.client.Subscribe(nc.Subject, func(msg *nats.Msg) { sub, err := nc.client.Subscribe(nc.Subject, func(msg *nats.Msg) {
if nc.router != nil { if nc.inputHandler != nil {
nc.router.HandleInput(nc.ctx, nc.Id(), msg) nc.inputHandler(nc.ctx, nc.Id(), msg)
} }
}) })
if err != nil { if err != nil {
return err return err
} }
nc.subMu.Lock()
defer sub.Unsubscribe() nc.sub = sub
nc.subMu.Unlock()
<-nc.ctx.Done() <-nc.ctx.Done()
nc.logger.Debug("done") nc.logger.Debug("done")
return nil return nil
} }
func (nc *NATSClient) Output(ctx context.Context, payload any) error { func (nc *NATSClient) Publish(ctx context.Context, topic string, payload any) error {
payloadMessage, ok := payload.(processor.NATSMessage) payloadBytes, ok := common.GetAnyAsByteSlice(payload)
if !ok { if !ok {
return errors.New("nats.client is only able to output NATSMessage") payloadString, ok := common.GetAnyAs[string](payload)
if !ok {
return errors.New("nats.client is only able to publish bytes or string")
} }
payloadBytes = []byte(payloadString)
}
nc.clientMu.Lock()
defer nc.clientMu.Unlock()
if nc.client == nil { if nc.client == nil {
return errors.New("nats.client client is not setup") return errors.New("nats.client client is not setup")
@@ -114,7 +131,26 @@ func (nc *NATSClient) Output(ctx context.Context, payload any) error {
return errors.New("nats.client is not connected") return errors.New("nats.client is not connected")
} }
err := nc.client.Publish(payloadMessage.Subject, payloadMessage.Payload) err := nc.client.Publish(topic, payloadBytes)
return err return err
} }
func (nc *NATSClient) Stop() {
if nc.cancel != nil {
defer nc.cancel()
}
nc.subMu.Lock()
defer nc.subMu.Unlock()
if nc.sub != nil {
nc.sub.Unsubscribe()
}
nc.clientMu.Lock()
defer nc.clientMu.Unlock()
if nc.client != nil {
nc.client.Drain()
// TODO(jwetzell): setup closed callback to get when client is fully closed
nc.client.Close()
}
}
+135
View File
@@ -0,0 +1,135 @@
package module
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net"
"sync"
"time"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/nats-io/nats-server/v2/server"
)
func init() {
RegisterModule(ModuleRegistration{
Type: "nats.server",
Title: "NATS Server",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"ip": {
Title: "IP",
Description: "the IP address to bind the NATS server to",
Type: "string",
Default: json.RawMessage(`"0.0.0.0"`),
},
"port": {
Title: "Port",
Description: "the port for the NATS server to listen on",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1024),
Maximum: jsonschema.Ptr[float64](65535),
Default: json.RawMessage(`4222`),
},
},
Required: []string{},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(moduleConfig config.ModuleConfig) (common.Module, error) {
params := moduleConfig.Params
portNum, err := params.GetInt("port")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
portNum = 4222
} else {
return nil, fmt.Errorf("nats.server port error: %w", err)
}
}
ipString, err := params.GetString("ip")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
ipString = "0.0.0.0"
} else {
return nil, fmt.Errorf("nats.server ip error: %w", err)
}
}
_, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", ipString, uint16(portNum)))
if err != nil {
return nil, err
}
return &NATSServer{config: moduleConfig, logger: CreateLogger(moduleConfig), Ip: ipString, Port: portNum}, nil
},
})
}
type NATSServer struct {
config config.ModuleConfig
ctx context.Context
Ip string
Port int
inputHandler common.InputHandler
server *server.Server
logger *slog.Logger
cancel context.CancelFunc
serverMu sync.Mutex
}
func (ns *NATSServer) Id() string {
return ns.config.Id
}
func (ns *NATSServer) Type() string {
return ns.config.Type
}
func (ns *NATSServer) Start(ctx context.Context, inputHandler common.InputHandler) error {
ns.logger.Debug("running")
ns.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
ns.ctx = moduleContext
ns.cancel = cancel
natsServer, err := server.NewServer(&server.Options{
Host: ns.Ip,
Port: ns.Port,
NoLog: true,
NoSigs: true,
})
if err != nil {
return err
}
ns.serverMu.Lock()
ns.server = natsServer
natsServer.Start()
if !natsServer.ReadyForConnections(5 * time.Second) {
return errors.New("nats.server failed to start")
}
ns.serverMu.Unlock()
<-ns.ctx.Done()
ns.logger.Debug("done")
return nil
}
func (ns *NATSServer) Stop() {
if ns.cancel != nil {
defer ns.cancel()
}
ns.serverMu.Lock()
defer ns.serverMu.Unlock()
if ns.server != nil {
ns.server.Shutdown()
ns.server.WaitForShutdown()
}
}
+44 -29
View File
@@ -2,38 +2,35 @@ package module
import ( import (
"context" "context"
"errors"
"fmt"
"log/slog" "log/slog"
"net" "net"
"sync"
"time" "time"
"github.com/jwetzell/psn-go" "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/config"
"github.com/jwetzell/showbridge-go/internal/route"
) )
func init() {
RegisterModule(ModuleRegistration{
Type: "psn.client",
Title: "PosiStageNet Client",
New: func(config config.ModuleConfig) (common.Module, error) {
return &PSNClient{config: config, decoder: psn.NewDecoder(), logger: CreateLogger(config)}, nil
},
})
}
type PSNClient struct { type PSNClient struct {
config config.ModuleConfig config config.ModuleConfig
conn *net.UDPConn conn *net.UDPConn
ctx context.Context ctx context.Context
router route.RouteIO inputHandler common.InputHandler
decoder *psn.Decoder decoder *psn.Decoder
logger *slog.Logger logger *slog.Logger
} cancel context.CancelFunc
connMu sync.Mutex
func init() {
RegisterModule(ModuleRegistration{
Type: "psn.client",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) {
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("psn.client unable to get router from context")
}
return &PSNClient{config: config, decoder: psn.NewDecoder(), ctx: ctx, router: router, logger: CreateLogger(config)}, nil
},
})
} }
func (pc *PSNClient) Id() string { func (pc *PSNClient) Id() string {
@@ -44,7 +41,12 @@ func (pc *PSNClient) Type() string {
return pc.config.Type return pc.config.Type
} }
func (pc *PSNClient) Run() error { func (pc *PSNClient) Start(ctx context.Context, inputHandler common.InputHandler) error {
pc.logger.Debug("running")
pc.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
pc.ctx = moduleContext
pc.cancel = cancel
addr, err := net.ResolveUDPAddr("udp", "236.10.10.10:56565") addr, err := net.ResolveUDPAddr("udp", "236.10.10.10:56565")
if err != nil { if err != nil {
@@ -55,24 +57,26 @@ func (pc *PSNClient) Run() error {
if err != nil { if err != nil {
return err return err
} }
defer client.Close()
pc.connMu.Lock()
pc.conn = client pc.conn = client
pc.connMu.Unlock()
buffer := make([]byte, 2048) buffer := make([]byte, 2048)
for { for pc.ctx.Err() == nil {
select { select {
case <-pc.ctx.Done(): case <-pc.ctx.Done():
// TODO(jwetzell): cleanup?
pc.logger.Debug("done")
return nil return nil
default: default:
pc.connMu.Lock()
pc.conn.SetDeadline(time.Now().Add(time.Millisecond * 200)) pc.conn.SetDeadline(time.Now().Add(time.Millisecond * 200))
numBytes, _, err := pc.conn.ReadFromUDP(buffer) numBytes, _, err := pc.conn.ReadFromUDP(buffer)
pc.connMu.Unlock()
if err != nil { if err != nil {
//NOTE(jwetzell) we hit deadline //NOTE(jwetzell) we hit deadline
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { opErr, ok := err.(*net.OpError)
if ok && opErr.Timeout() {
continue continue
} }
return err return err
@@ -85,18 +89,29 @@ func (pc *PSNClient) Run() error {
pc.logger.Error("problem decoding psn traffic", "error", err) pc.logger.Error("problem decoding psn traffic", "error", err)
} }
if pc.router != nil { if pc.inputHandler != nil {
// TODO(jwetzell): better input handling
for _, tracker := range pc.decoder.Trackers { for _, tracker := range pc.decoder.Trackers {
pc.router.HandleInput(pc.ctx, pc.Id(), tracker) pc.inputHandler(pc.ctx, pc.Id(), tracker)
} }
} else { } else {
pc.logger.Error("has no router") pc.logger.Error("has no input handler")
} }
} }
} }
} }
<-pc.ctx.Done()
pc.logger.Debug("done")
return nil
} }
func (pc *PSNClient) Output(ctx context.Context, payload any) error { func (pc *PSNClient) Stop() {
return fmt.Errorf("psn.client output is not implemented") if pc.cancel != nil {
defer pc.cancel()
}
pc.connMu.Lock()
defer pc.connMu.Unlock()
if pc.conn != nil {
pc.conn.Close()
}
} }
+133
View File
@@ -0,0 +1,133 @@
package module
import (
"context"
"errors"
"fmt"
"log/slog"
"sync"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/redis/go-redis/v9"
)
func init() {
RegisterModule(ModuleRegistration{
Type: "redis.client",
Title: "Redis Client",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"host": {
Title: "Host",
Description: "the hostname or IP address of the Redis server to connect to",
Type: "string",
},
"port": {
Title: "Port",
Description: "the port to use when connecting to the Redis server",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1),
Maximum: jsonschema.Ptr[float64](65535),
},
},
Required: []string{"host", "port"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params
hostString, err := params.GetString("host")
if err != nil {
return nil, errors.New("redis.client host error: " + err.Error())
}
portInt, err := params.GetInt("port")
if err != nil {
return nil, errors.New("redis.client port error: " + err.Error())
}
return &RedisClient{config: config, Host: hostString, Port: uint16(portInt), logger: CreateLogger(config)}, nil
},
})
}
type RedisClient struct {
config config.ModuleConfig
ctx context.Context
inputHandler common.InputHandler
Host string
Port uint16
client *redis.Client
logger *slog.Logger
cancel context.CancelFunc
clientMu sync.Mutex
}
func (rc *RedisClient) Id() string {
return rc.config.Id
}
func (rc *RedisClient) Type() string {
return rc.config.Type
}
func (rc *RedisClient) Printf(ctx context.Context, format string, v ...any) {
msg := fmt.Sprintf(format, v...)
rc.logger.Debug(msg)
}
func (rc *RedisClient) Start(ctx context.Context, inputHandler common.InputHandler) error {
redis.SetLogger(rc)
rc.logger.Debug("running")
rc.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
rc.ctx = moduleContext
rc.cancel = cancel
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", rc.Host, rc.Port),
Password: "",
DB: 0,
})
rc.clientMu.Lock()
rc.client = client
rc.clientMu.Unlock()
<-rc.ctx.Done()
rc.logger.Debug("done")
return nil
}
func (rc *RedisClient) Stop() {
if rc.cancel != nil {
defer rc.cancel()
}
rc.clientMu.Lock()
defer rc.clientMu.Unlock()
if rc.client != nil {
rc.client.Close()
}
}
func (rc *RedisClient) Get(ctx context.Context, key string) (any, error) {
if rc.client != nil {
val, err := rc.client.Get(ctx, key).Result()
if err != nil {
return nil, err
}
return val, nil
}
return nil, errors.New("redis.client not setup")
}
func (rc *RedisClient) Set(ctx context.Context, key string, value any) error {
if rc.client != nil {
status := rc.client.Set(ctx, key, value, 0)
return status.Err()
}
return errors.New("redis.client not setup")
}
+99 -78
View File
@@ -7,86 +7,91 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"sync"
"time" "time"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/framer" "github.com/jwetzell/showbridge-go/internal/framer"
"github.com/jwetzell/showbridge-go/internal/route"
"go.bug.st/serial" "go.bug.st/serial"
) )
type SerialClient struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
Port string
Framer framer.Framer
Mode *serial.Mode
port serial.Port
logger *slog.Logger
}
func init() { func init() {
RegisterModule(ModuleRegistration{ RegisterModule(ModuleRegistration{
Type: "serial.client", Type: "serial.client",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) { Title: "Serial Client",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"port": {
Title: "Port",
Description: "the name of the serial port to connect to",
Type: "string",
},
"baudRate": {
Title: "Baud Rate",
Description: "the baud rate to use when connecting to the serial port",
Type: "integer",
},
"framing": {
Title: "Framing Method",
Description: "the method to use for framing messages on the serial port",
Type: "string",
Enum: []any{"LF", "CR", "CRLF", "SLIP", "RAW"},
},
},
Required: []string{"port", "baudRate", "framing"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params params := config.Params
port, ok := params["port"] portString, err := params.GetString("port")
if err != nil {
if !ok { return nil, fmt.Errorf("serial.client port error: %w", err)
return nil, errors.New("serial.client requires a port parameter")
} }
portString, ok := port.(string) framingMethodString, err := params.GetString("framing")
if err != nil {
if !ok { return nil, fmt.Errorf("serial.client framing error: %w", err)
return nil, errors.New("serial.client port must be a string")
} }
framingMethod := "RAW" inFramer := framer.GetFramer(framingMethodString)
framingMethodRaw, ok := params["framing"] if inFramer == nil {
return nil, fmt.Errorf("serial.client unknown framing method: %s", framingMethodString)
if ok {
framingMethodString, ok := framingMethodRaw.(string)
if !ok {
return nil, errors.New("serial.client framing method must be a string")
}
framingMethod = framingMethodString
} }
framer := framer.GetFramer(framingMethod) outFramer := framer.GetFramer(framingMethodString)
if framer == nil { baudRateInt, err := params.GetInt("baudRate")
return nil, fmt.Errorf("serial.client unknown framing method: %s", framingMethod) if err != nil {
} return nil, fmt.Errorf("serial.client baudRate error: %w", err)
buadRate, ok := params["baudRate"]
if !ok {
return nil, errors.New("serial.client requires a baudRate parameter")
}
baudRateNum, ok := buadRate.(float64)
if !ok {
return nil, errors.New("serial.client baudRate must be a number")
} }
mode := serial.Mode{ mode := serial.Mode{
BaudRate: int(baudRateNum), BaudRate: baudRateInt,
} }
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO) return &SerialClient{config: config, Port: portString, inFramer: inFramer, outFramer: outFramer, Mode: &mode, logger: CreateLogger(config)}, nil
if !ok {
return nil, errors.New("serial.client unable to get router from context")
}
return &SerialClient{config: config, Port: portString, Framer: framer, Mode: &mode, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
}, },
}) })
} }
type SerialClient struct {
config config.ModuleConfig
ctx context.Context
inputHandler common.InputHandler
Port string
inFramer framer.Framer
outFramer framer.Framer
Mode *serial.Mode
port serial.Port
logger *slog.Logger
cancel context.CancelFunc
portMu sync.Mutex
}
func (sc *SerialClient) Id() string { func (sc *SerialClient) Id() string {
return sc.config.Id return sc.config.Id
} }
@@ -96,10 +101,11 @@ func (sc *SerialClient) Type() string {
} }
func (sc *SerialClient) SetupPort() error { func (sc *SerialClient) SetupPort() error {
sc.portMu.Lock()
defer sc.portMu.Unlock()
port, err := serial.Open(sc.Port, sc.Mode) port, err := serial.Open(sc.Port, sc.Mode)
if err != nil { if err != nil {
return fmt.Errorf("serial.client can't open input port: %s", sc.Port) return err
} }
sc.port = port sc.port = port
@@ -107,25 +113,20 @@ func (sc *SerialClient) SetupPort() error {
return nil return nil
} }
func (sc *SerialClient) Run() error { func (sc *SerialClient) Start(ctx context.Context, inputHandler common.InputHandler) error {
sc.logger.Debug("running")
sc.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
sc.ctx = moduleContext
sc.cancel = cancel
// TODO(jwetzell): shutdown with router.Context properly for sc.ctx.Err() == nil {
go func() {
<-sc.ctx.Done()
sc.logger.Debug("done")
if sc.port != nil {
sc.port.Close()
}
}()
for {
err := sc.SetupPort() err := sc.SetupPort()
if err != nil { if err != nil {
if sc.ctx.Err() != nil { if sc.ctx.Err() != nil {
sc.logger.Debug("done")
return nil return nil
} }
sc.logger.Error("port setup error", "error", err.Error()) sc.logger.Error("port setup error", "port", sc.Port, "error", err.Error())
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
continue continue
} }
@@ -133,29 +134,27 @@ func (sc *SerialClient) Run() error {
buffer := make([]byte, 1024) buffer := make([]byte, 1024)
select { select {
case <-sc.ctx.Done(): case <-sc.ctx.Done():
sc.logger.Debug("done")
return nil return nil
default: default:
READ: READ:
for { for sc.ctx.Err() == nil {
select { select {
case <-sc.ctx.Done(): case <-sc.ctx.Done():
sc.logger.Debug("done")
return nil return nil
default: default:
byteCount, err := sc.port.Read(buffer) byteCount, err := sc.port.Read(buffer)
if err != nil { if err != nil {
sc.Framer.Clear() sc.inFramer.Clear()
break READ break READ
} }
if sc.Framer != nil { if sc.inFramer != nil {
if byteCount > 0 { if byteCount > 0 {
messages := sc.Framer.Decode(buffer[0:byteCount]) messages := sc.inFramer.Decode(buffer[0:byteCount])
for _, message := range messages { for _, message := range messages {
if sc.router != nil { if sc.inputHandler != nil {
sc.router.HandleInput(sc.ctx, sc.Id(), message) sc.inputHandler(sc.ctx, sc.Id(), message)
} else { } else {
sc.logger.Error("input received but no router is configured") sc.logger.Error("input received but no router is configured")
} }
@@ -166,16 +165,38 @@ func (sc *SerialClient) Run() error {
} }
} }
} }
<-sc.ctx.Done()
sc.logger.Debug("done")
return nil
} }
func (sc *SerialClient) Output(ctx context.Context, payload any) error { func (sc *SerialClient) Output(ctx context.Context, payload any) error {
payloadBytes, ok := payload.([]byte) payloadBytes, ok := common.GetAnyAsByteSlice(payload)
if !ok { if !ok {
return errors.New("serial.client can only ouptut bytes") return errors.New("serial.client can only output bytes")
} }
_, err := sc.port.Write(sc.Framer.Encode(payloadBytes)) _, err := sc.port.Write(sc.outFramer.Encode(payloadBytes))
return err return err
} }
func (sc *SerialClient) Stop() {
if sc.cancel != nil {
defer sc.cancel()
}
sc.portMu.Lock()
defer sc.portMu.Unlock()
if sc.port != nil {
sc.port.Close()
}
if sc.inFramer != nil {
sc.inFramer.Clear()
}
if sc.outFramer != nil {
sc.outFramer.Clear()
}
}
+124 -83
View File
@@ -2,6 +2,7 @@ package module
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -14,21 +15,106 @@ import (
"github.com/emiago/diago/media" "github.com/emiago/diago/media"
"github.com/emiago/sipgo" "github.com/emiago/sipgo"
"github.com/emiago/sipgo/sip" "github.com/emiago/sipgo/sip"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor" "github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
) )
func init() {
RegisterModule(ModuleRegistration{
Type: "sip.call.server",
Title: "SIP Call Server",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"ip": {
Title: "IP",
Description: "the IP address to bind the SIP server to",
Type: "string",
Default: json.RawMessage(`"0.0.0.0"`),
},
"port": {
Title: "Port",
Description: "the port for the SIP server to listen on",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1024),
Maximum: jsonschema.Ptr[float64](65535),
Default: json.RawMessage(`5060`),
},
"transport": {
Title: "Transport",
Description: "the transport protocol to use for the SIP server",
Type: "string",
Enum: []any{"udp", "tcp", "ws", "udp4", "tcp4"},
Default: json.RawMessage(`"udp"`),
},
"userAgent": {
Title: "User Agent",
Description: "the user agent string to use",
Type: "string",
Default: json.RawMessage(`"showbridge"`),
},
},
Required: []string{},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(moduleConfig config.ModuleConfig) (common.Module, error) {
params := moduleConfig.Params
portNum, err := params.GetInt("port")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
portNum = 5060
} else {
return nil, fmt.Errorf("sip.call.server port error: %w", err)
}
}
ipString, err := params.GetString("ip")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
ipString = "0.0.0.0"
} else {
return nil, fmt.Errorf("sip.call.server ip error: %w", err)
}
}
transportString, err := params.GetString("transport")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
transportString = "udp"
} else {
return nil, fmt.Errorf("sip.call.server transport error: %w", err)
}
}
userAgentString, err := params.GetString("userAgent")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
userAgentString = "showbridge"
} else {
return nil, fmt.Errorf("sip.call.server userAgent error: %w", err)
}
}
return &SIPCallServer{config: moduleConfig, IP: ipString, Port: int(portNum), Transport: transportString, UserAgent: userAgentString, logger: CreateLogger(moduleConfig)}, nil
},
})
}
type SIPCallServer struct { type SIPCallServer struct {
config config.ModuleConfig config config.ModuleConfig
ctx context.Context ctx context.Context
router route.RouteIO inputHandler common.InputHandler
IP string IP string
Port int Port int
Transport string Transport string
UserAgent string UserAgent string
dg *diago.Diago
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
ua *sipgo.UserAgent
uaMu sync.Mutex
} }
type SIPCallMessage struct { type SIPCallMessage struct {
@@ -42,72 +128,6 @@ type SIPCall struct {
type sipCallContextKey string type sipCallContextKey string
func init() {
RegisterModule(ModuleRegistration{
Type: "sip.call.server",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) {
params := config.Params
portNum := 5060
port, ok := params["port"]
if ok {
specificPortNum, ok := port.(float64)
if !ok {
return nil, errors.New("sip.call.server port must be a number")
}
portNum = int(specificPortNum)
}
ipString := "0.0.0.0"
ip, ok := params["ip"]
if ok {
specificIpString, ok := ip.(string)
if !ok {
return nil, errors.New("sip.call.server ip must be a string")
}
ipString = specificIpString
}
transportString := "udp"
transport, ok := params["transport"]
if ok {
specificTransportString, ok := transport.(string)
if !ok {
return nil, errors.New("sip.call.server transport must be a string")
}
transportString = specificTransportString
}
userAgentString := "showbridge"
userAgent, ok := params["userAgent"]
if ok {
specificTransportString, ok := userAgent.(string)
if !ok {
return nil, errors.New("sip.call.server userAgent must be a string")
}
userAgentString = specificTransportString
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("sip.call.server unable to get router from context")
}
return &SIPCallServer{config: config, ctx: ctx, router: router, IP: ipString, Port: int(portNum), Transport: transportString, UserAgent: userAgentString, logger: CreateLogger(config)}, nil
},
})
}
func (scs *SIPCallServer) Id() string { func (scs *SIPCallServer) Id() string {
return scs.config.Id return scs.config.Id
} }
@@ -116,7 +136,13 @@ func (scs *SIPCallServer) Type() string {
return scs.config.Type return scs.config.Type
} }
func (scs *SIPCallServer) Run() error { func (scs *SIPCallServer) Start(ctx context.Context, inputHandler common.InputHandler) error {
scs.logger.Debug("running")
scs.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
scs.ctx = moduleContext
scs.cancel = cancel
diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil)) diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil))
ua, _ := sipgo.NewUA( ua, _ := sipgo.NewUA(
@@ -124,7 +150,9 @@ func (scs *SIPCallServer) Run() error {
sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)), sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)),
sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)), sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)),
) )
defer ua.Close() scs.uaMu.Lock()
scs.ua = ua
scs.uaMu.Unlock()
sip.SetDefaultLogger(diagoLogger) sip.SetDefaultLogger(diagoLogger)
media.SetDefaultLogger(diagoLogger) media.SetDefaultLogger(diagoLogger)
@@ -136,13 +164,12 @@ func (scs *SIPCallServer) Run() error {
}, },
)) ))
go func() { err := dg.Serve(scs.ctx, func(inDialog *diago.DialogServerSession) {
dg.Serve(scs.ctx, func(inDialog *diago.DialogServerSession) {
scs.HandleCall(inDialog) scs.HandleCall(inDialog)
}) })
}() if err != nil {
scs.logger.Error("diago serve error", "error", err)
scs.dg = dg }
<-scs.ctx.Done() <-scs.ctx.Done()
scs.logger.Debug("done") scs.logger.Debug("done")
@@ -157,10 +184,11 @@ func (scs *SIPCallServer) HandleCall(inDialog *diago.DialogServerSession) {
dialogContext := context.WithValue(scs.ctx, sipCallContextKey("call"), &SIPCall{ dialogContext := context.WithValue(scs.ctx, sipCallContextKey("call"), &SIPCall{
inDialog: inDialog, inDialog: inDialog,
}) })
scs.router.HandleInput(dialogContext, scs.Id(), SIPCallMessage{ if scs.inputHandler != nil {
scs.inputHandler(dialogContext, scs.Id(), SIPCallMessage{
To: inDialog.ToUser(), To: inDialog.ToUser(),
}) })
fmt.Println(inDialog.LoadState()) }
} }
func (scs *SIPCallServer) Output(ctx context.Context, payload any) error { func (scs *SIPCallServer) Output(ctx context.Context, payload any) error {
@@ -181,11 +209,13 @@ func (scs *SIPCallServer) Output(ctx context.Context, payload any) error {
return errors.New("sip.call.server inDialog already ended") return errors.New("sip.call.server inDialog already ended")
} }
payloadDTMFResponse, ok := payload.(processor.SipDTMFResponse) payloadDTMFResponse, ok := common.GetAnyAs[processor.SipDTMFResponse](payload)
if ok { if ok {
dtmfWriter := call.inDialog.AudioWriterDTMF() dtmfWriter, err := call.inDialog.AudioWriterDTMF()
if err != nil {
return err
}
time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PreWait)) time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PreWait))
for i, dtmfRune := range payloadDTMFResponse.Digits { for i, dtmfRune := range payloadDTMFResponse.Digits {
err := dtmfWriter.WriteDTMF(dtmfRune) err := dtmfWriter.WriteDTMF(dtmfRune)
@@ -194,11 +224,11 @@ func (scs *SIPCallServer) Output(ctx context.Context, payload any) error {
return fmt.Errorf("sip.dtmf.server error output dtmf digit at index %d", i) return fmt.Errorf("sip.dtmf.server error output dtmf digit at index %d", i)
} }
} }
time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PreWait)) time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PostWait))
return nil return nil
} }
payloadAudioFileResponse, ok := payload.(processor.SipAudioFileResponse) payloadAudioFileResponse, ok := common.GetAnyAs[processor.SipAudioFileResponse](payload)
if ok { if ok {
audioFile, err := os.Open(payloadAudioFileResponse.AudioFile) audioFile, err := os.Open(payloadAudioFileResponse.AudioFile)
@@ -226,3 +256,14 @@ func (scs *SIPCallServer) Output(ctx context.Context, payload any) error {
} }
return errors.New("sip.dtmf.server can only output SipDTMFResponse or SipAudioFileResponse") return errors.New("sip.dtmf.server can only output SipDTMFResponse or SipAudioFileResponse")
} }
func (scs *SIPCallServer) Stop() {
if scs.cancel != nil {
defer scs.cancel()
}
scs.uaMu.Lock()
defer scs.uaMu.Unlock()
if scs.ua != nil {
scs.ua.Close()
}
}
+146 -80
View File
@@ -2,6 +2,7 @@ package module
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -15,20 +16,127 @@ import (
"github.com/emiago/diago/media" "github.com/emiago/diago/media"
"github.com/emiago/sipgo" "github.com/emiago/sipgo"
"github.com/emiago/sipgo/sip" "github.com/emiago/sipgo/sip"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor" "github.com/jwetzell/showbridge-go/internal/processor"
"github.com/jwetzell/showbridge-go/internal/route"
) )
func init() {
RegisterModule(ModuleRegistration{
Type: "sip.dtmf.server",
Title: "SIP DTMF Server",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"ip": {
Title: "IP",
Description: "the IP address to bind the SIP server to",
Type: "string",
Default: json.RawMessage(`"0.0.0.0"`),
},
"port": {
Title: "Port",
Description: "the port for the SIP server to listen on",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1024),
Maximum: jsonschema.Ptr[float64](65535),
Default: json.RawMessage(`5060`),
},
"transport": {
Title: "Transport",
Description: "the transport protocol to use for the SIP server",
Type: "string",
Enum: []any{"udp", "tcp", "ws", "udp4", "tcp4"},
Default: json.RawMessage(`"udp"`),
},
"userAgent": {
Title: "User Agent",
Description: "the user agent string to use",
Type: "string",
Default: json.RawMessage(`"showbridge"`),
},
"separator": {
Title: "DTMF Separator",
Description: "the DTMF character to use as a separator between DTMF digit groups",
Type: "string",
MinLength: new(1),
MaxLength: new(1),
},
},
Required: []string{"separator"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(moduleConfig config.ModuleConfig) (common.Module, error) {
params := moduleConfig.Params
portNum, err := params.GetInt("port")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
portNum = 5060
} else {
return nil, fmt.Errorf("sip.dtmf.server port error: %w", err)
}
}
ipString, err := params.GetString("ip")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
ipString = "0.0.0.0"
} else {
return nil, fmt.Errorf("sip.dtmf.server ip error: %w", err)
}
}
transportString, err := params.GetString("transport")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
transportString = "udp"
} else {
return nil, fmt.Errorf("sip.dtmf.server transport error: %w", err)
}
}
userAgentString, err := params.GetString("userAgent")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
userAgentString = "showbridge"
} else {
return nil, fmt.Errorf("sip.dtmf.server userAgent error: %w", err)
}
}
separatorString, err := params.GetString("separator")
if err != nil {
return nil, fmt.Errorf("sip.dtmf.server separator error: %w", err)
}
if len(separatorString) != 1 {
return nil, errors.New("sip.dtmf.server separator must be a single character")
}
if !strings.ContainsRune("0123456789*#ABCD", rune(separatorString[0])) {
return nil, errors.New("sip.dtmf.server separator must be a valid DTMF character")
}
return &SIPDTMFServer{config: moduleConfig, IP: ipString, Port: int(portNum), Transport: transportString, UserAgent: userAgentString, Separator: separatorString, logger: CreateLogger(moduleConfig)}, nil
},
})
}
type SIPDTMFServer struct { type SIPDTMFServer struct {
config config.ModuleConfig config config.ModuleConfig
ctx context.Context ctx context.Context
router route.RouteIO inputHandler common.InputHandler
IP string IP string
Port int Port int
Transport string Transport string
UserAgent string
Separator string Separator string
logger *slog.Logger logger *slog.Logger
cancel context.CancelFunc
ua *sipgo.UserAgent
uaMu sync.Mutex
} }
type SIPDTMFMessage struct { type SIPDTMFMessage struct {
@@ -41,75 +149,6 @@ type SIPDTMFCall struct {
lock sync.Mutex lock sync.Mutex
} }
func init() {
RegisterModule(ModuleRegistration{
Type: "sip.dtmf.server",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) {
params := config.Params
portNum := 5060
port, ok := params["port"]
if ok {
specificPortNum, ok := port.(float64)
if !ok {
return nil, errors.New("sip.dtmf.server port must be a number")
}
portNum = int(specificPortNum)
}
ipString := "0.0.0.0"
ip, ok := params["ip"]
if ok {
specificIpString, ok := ip.(string)
if !ok {
return nil, errors.New("sip.dtmf.server ip must be a string")
}
ipString = specificIpString
}
transportString := "udp"
transport, ok := params["transport"]
if ok {
specificTransportString, ok := transport.(string)
if !ok {
return nil, errors.New("sip.dtmf.server transport must be a string")
}
transportString = specificTransportString
}
separator, ok := params["separator"]
if !ok {
return nil, errors.New("sip.dtmf.server requires a separator parameter")
}
separatorString, ok := separator.(string)
if !ok {
return nil, errors.New("sip.dtmf.server separator must be a string")
}
if len(separatorString) != 1 {
return nil, errors.New("sip.dtmf.server separator must be a single character")
}
if !strings.ContainsRune("0123456789*#ABCD", rune(separatorString[0])) {
return nil, errors.New("sip.dtmf.server separator must be a valid DTMF character")
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("sip.dtmf.server unable to get router from context")
}
return &SIPDTMFServer{config: config, ctx: ctx, router: router, IP: ipString, Port: int(portNum), Transport: transportString, Separator: separatorString, logger: CreateLogger(config)}, nil
},
})
}
func (sds *SIPDTMFServer) Id() string { func (sds *SIPDTMFServer) Id() string {
return sds.config.Id return sds.config.Id
} }
@@ -118,14 +157,24 @@ func (sds *SIPDTMFServer) Type() string {
return sds.config.Type return sds.config.Type
} }
func (sds *SIPDTMFServer) Run() error { func (sds *SIPDTMFServer) Start(ctx context.Context, inputHandler common.InputHandler) error {
sds.logger.Debug("running")
sds.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
sds.ctx = moduleContext
sds.cancel = cancel
diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil)) diagoLogger := slog.New(slog.NewJSONHandler(io.Discard, nil))
ua, _ := sipgo.NewUA( ua, _ := sipgo.NewUA(
sipgo.WithUserAgent(sds.UserAgent),
sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)), sipgo.WithUserAgentTransportLayerOptions(sip.WithTransportLayerLogger(diagoLogger)),
sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)), sipgo.WithUserAgentTransactionLayerOptions(sip.WithTransactionLayerLogger(diagoLogger)),
) )
defer ua.Close()
sds.uaMu.Lock()
sds.ua = ua
sds.uaMu.Unlock()
sip.SetDefaultLogger(diagoLogger) sip.SetDefaultLogger(diagoLogger)
media.SetDefaultLogger(diagoLogger) media.SetDefaultLogger(diagoLogger)
@@ -155,16 +204,19 @@ func (sds *SIPDTMFServer) HandleCall(inDialog *diago.DialogServerSession) error
inDialog.Ringing() inDialog.Ringing()
inDialog.Answer() inDialog.Answer()
reader := inDialog.AudioReaderDTMF() reader, err := inDialog.AudioReaderDTMF()
if err != nil {
return err
}
userString := "" userString := ""
return reader.Listen(func(dtmf rune) error { return reader.Listen(func(dtmf rune) error {
if dtmf == rune(sds.Separator[0]) { if dtmf == rune(sds.Separator[0]) {
if sds.router != nil { if sds.inputHandler != nil {
dialogContext := context.WithValue(sds.ctx, sipCallContextKey("call"), &SIPDTMFCall{ dialogContext := context.WithValue(sds.ctx, sipCallContextKey("call"), &SIPDTMFCall{
inDialog: inDialog, inDialog: inDialog,
}) })
sds.router.HandleInput(dialogContext, sds.Id(), SIPDTMFMessage{ sds.inputHandler(dialogContext, sds.Id(), SIPDTMFMessage{
To: inDialog.ToUser(), To: inDialog.ToUser(),
Digits: userString, Digits: userString,
}) })
@@ -194,10 +246,13 @@ func (sds *SIPDTMFServer) Output(ctx context.Context, payload any) error {
return errors.New("sip.dtmf.server inDialog already ended") return errors.New("sip.dtmf.server inDialog already ended")
} }
payloadDTMFResponse, ok := payload.(processor.SipDTMFResponse) payloadDTMFResponse, ok := common.GetAnyAs[processor.SipDTMFResponse](payload)
if ok { if ok {
dtmfWriter := call.inDialog.AudioWriterDTMF() dtmfWriter, err := call.inDialog.AudioWriterDTMF()
if err != nil {
return err
}
time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PreWait)) time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PreWait))
for i, dtmfRune := range payloadDTMFResponse.Digits { for i, dtmfRune := range payloadDTMFResponse.Digits {
@@ -207,11 +262,11 @@ func (sds *SIPDTMFServer) Output(ctx context.Context, payload any) error {
return fmt.Errorf("sip.dtmf.server error output dtmf digit at index %d", i) return fmt.Errorf("sip.dtmf.server error output dtmf digit at index %d", i)
} }
} }
time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PreWait)) time.Sleep(time.Millisecond * time.Duration(payloadDTMFResponse.PostWait))
return nil return nil
} }
payloadAudioFileResponse, ok := payload.(processor.SipAudioFileResponse) payloadAudioFileResponse, ok := common.GetAnyAs[processor.SipAudioFileResponse](payload)
if ok { if ok {
audioFile, err := os.Open(payloadAudioFileResponse.AudioFile) audioFile, err := os.Open(payloadAudioFileResponse.AudioFile)
@@ -240,3 +295,14 @@ func (sds *SIPDTMFServer) Output(ctx context.Context, payload any) error {
return errors.New("sip.dtmf.server can only output SipDTMFResponse or SipAudioFileResponse") return errors.New("sip.dtmf.server can only output SipDTMFResponse or SipAudioFileResponse")
} }
func (sds *SIPDTMFServer) Stop() {
if sds.cancel != nil {
defer sds.cancel()
}
sds.uaMu.Lock()
defer sds.uaMu.Unlock()
if sds.ua != nil {
sds.ua.Close()
}
}
+113 -91
View File
@@ -6,49 +6,54 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"net" "net"
"sync"
"time" "time"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/framer" "github.com/jwetzell/showbridge-go/internal/framer"
"github.com/jwetzell/showbridge-go/internal/route"
) )
type TCPClient struct {
config config.ModuleConfig
framer framer.Framer
conn *net.TCPConn
ctx context.Context
router route.RouteIO
Addr *net.TCPAddr
logger *slog.Logger
}
func init() { func init() {
RegisterModule(ModuleRegistration{ RegisterModule(ModuleRegistration{
Type: "net.tcp.client", Type: "net.tcp.client",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) { Title: "TCP Client",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"host": {
Title: "Host",
Description: "the hostname or IP address of the TCP server to connect to",
Type: "string",
},
"port": {
Title: "Port",
Description: "the port of the TCP server to connect to",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1),
Maximum: jsonschema.Ptr[float64](65535),
},
"framing": {
Title: "Framing Method",
Description: "the method used to frame messages over the TCP connection",
Type: "string",
Enum: []any{"LF", "CR", "CRLF", "SLIP", "RAW"},
},
},
Required: []string{"host", "port", "framing"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params params := config.Params
host, ok := params["host"] hostString, err := params.GetString("host")
if err != nil {
if !ok { return nil, fmt.Errorf("net.tcp.client host error: %w", err)
return nil, errors.New("net.tcp.client requires a host parameter")
} }
hostString, ok := host.(string) portNum, err := params.GetInt("port")
if err != nil {
if !ok { return nil, fmt.Errorf("net.tcp.client port error: %w", err)
return nil, errors.New("net.tcp.client host must be string")
}
port, ok := params["port"]
if !ok {
return nil, errors.New("net.tcp.client requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, errors.New("net.tcp.client port must be a number")
} }
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", hostString, uint16(portNum))) addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", hostString, uint16(portNum)))
@@ -56,36 +61,36 @@ func init() {
return nil, err return nil, err
} }
framingMethod := "RAW" framingMethodString, err := params.GetString("framing")
if err != nil {
framingMethodRaw, ok := params["framing"] return nil, fmt.Errorf("net.tcp.client framing error: %w", err)
if ok {
framingMethodString, ok := framingMethodRaw.(string)
if !ok {
return nil, errors.New("net.tcp.client framing method must be a string")
}
framingMethod = framingMethodString
} }
framer := framer.GetFramer(framingMethod) inFramer := framer.GetFramer(framingMethodString)
if framer == nil { if inFramer == nil {
return nil, fmt.Errorf("net.tcp.client unknown framing method: %s", framingMethod) return nil, fmt.Errorf("net.tcp.client unknown framing method: %s", framingMethodString)
} }
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO) outFramer := framer.GetFramer(framingMethodString)
return &TCPClient{inFramer: inFramer, outFramer: outFramer, Addr: addr, config: config, logger: CreateLogger(config)}, nil
if !ok {
return nil, errors.New("net.tcp.client unable to get router from context")
}
return &TCPClient{framer: framer, Addr: addr, config: config, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
}, },
}) })
} }
type TCPClient struct {
config config.ModuleConfig
inFramer framer.Framer
outFramer framer.Framer
conn *net.TCPConn
ctx context.Context
inputHandler common.InputHandler
Addr *net.TCPAddr
logger *slog.Logger
cancel context.CancelFunc
connMu sync.Mutex
}
func (tc *TCPClient) Id() string { func (tc *TCPClient) Id() string {
return tc.config.Id return tc.config.Id
} }
@@ -94,23 +99,19 @@ func (tc *TCPClient) Type() string {
return tc.config.Type return tc.config.Type
} }
func (tc *TCPClient) Run() error { func (tc *TCPClient) Start(ctx context.Context, inputHandler common.InputHandler) error {
tc.logger.Debug("running")
tc.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
tc.ctx = moduleContext
tc.cancel = cancel
// TODO(jwetzell): shutdown with router.Context properly CONNECT_RETRY:
go func() { for tc.ctx.Err() == nil {
<-tc.ctx.Done()
tc.logger.Debug("done")
if tc.conn != nil {
tc.conn.Close()
}
}()
for {
err := tc.SetupConn() err := tc.SetupConn()
if err != nil { if err != nil {
if tc.ctx.Err() != nil { if tc.ctx.Err() != nil {
tc.logger.Debug("done") break CONNECT_RETRY
return nil
} }
tc.logger.Error("connection error", "error", err.Error()) tc.logger.Error("connection error", "error", err.Error())
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
@@ -118,61 +119,82 @@ func (tc *TCPClient) Run() error {
} }
buffer := make([]byte, 1024) buffer := make([]byte, 1024)
select {
case <-tc.ctx.Done():
tc.logger.Debug("done")
return nil
default:
READ: READ:
for { for tc.ctx.Err() == nil {
select { tc.conn.SetReadDeadline(time.Now().Add(time.Millisecond * 200))
case <-tc.ctx.Done():
tc.logger.Debug("done")
return nil
default:
byteCount, err := tc.conn.Read(buffer) byteCount, err := tc.conn.Read(buffer)
if err != nil { if err != nil {
tc.framer.Clear() opErr, ok := err.(*net.OpError)
if ok {
//NOTE(jwetzell) we hit deadline
if opErr.Timeout() {
continue
}
}
if errors.Is(err, net.ErrClosed) {
break CONNECT_RETRY
}
break READ break READ
} }
if tc.framer != nil { if tc.inFramer != nil {
if byteCount > 0 { if byteCount > 0 {
messages := tc.framer.Decode(buffer[0:byteCount]) messages := tc.inFramer.Decode(buffer[0:byteCount])
for _, message := range messages { for _, message := range messages {
if tc.router != nil { if tc.inputHandler != nil {
tc.router.HandleInput(tc.ctx, tc.Id(), message) tc.inputHandler(tc.ctx, tc.Id(), message)
} else { } else {
tc.logger.Error("input received but no router is configured") tc.logger.Error("input received but no input handler is configured")
}
}
} }
} }
} }
} }
} }
} }
<-tc.ctx.Done()
tc.logger.Debug("done")
return nil
} }
func (tc *TCPClient) SetupConn() error { func (tc *TCPClient) SetupConn() error {
tc.connMu.Lock()
defer tc.connMu.Unlock()
client, err := net.DialTCP("tcp", nil, tc.Addr) client, err := net.DialTCP("tcp", nil, tc.Addr)
tc.conn = client tc.conn = client
return err return err
} }
func (tc *TCPClient) Output(ctx context.Context, payload any) error { func (tc *TCPClient) Output(ctx context.Context, payload any) error {
// NOTE(jwetzell): not sure how this would occur but tc.connMu.Lock()
defer tc.connMu.Unlock()
if tc.conn == nil { if tc.conn == nil {
err := tc.SetupConn() return errors.New("net.tcp.client client is not setup")
if err != nil {
return err
} }
} payloadBytes, ok := common.GetAnyAsByteSlice(payload)
payloadBytes, ok := payload.([]byte)
if !ok { if !ok {
return errors.New("net.tcp.client is only able to output bytes") return errors.New("net.tcp.client is only able to output bytes")
} }
_, err := tc.conn.Write(tc.framer.Encode(payloadBytes)) _, err := tc.conn.Write(tc.inFramer.Encode(payloadBytes))
return err return err
} }
func (tc *TCPClient) Stop() {
if tc.cancel != nil {
defer tc.cancel()
}
tc.connMu.Lock()
defer tc.connMu.Unlock()
if tc.conn != nil {
tc.conn.Close()
}
if tc.inFramer != nil {
tc.inFramer.Clear()
}
if tc.outFramer != nil {
tc.outFramer.Clear()
}
}
+152 -121
View File
@@ -2,97 +2,114 @@ package module
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"net" "net"
"slices" "slices"
"strings"
"sync" "sync"
"syscall"
"time" "time"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/framer" "github.com/jwetzell/showbridge-go/internal/framer"
"github.com/jwetzell/showbridge-go/internal/route"
) )
type TCPServer struct {
config config.ModuleConfig
Addr *net.TCPAddr
Framer framer.Framer
ctx context.Context
router route.RouteIO
quit chan interface{}
wg sync.WaitGroup
connections []*net.TCPConn
connectionsMu sync.RWMutex
logger *slog.Logger
}
func init() { func init() {
RegisterModule(ModuleRegistration{ RegisterModule(ModuleRegistration{
Type: "net.tcp.server", Type: "net.tcp.server",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) { Title: "TCP Server",
params := config.Params ParamsSchema: &jsonschema.Schema{
port, ok := params["port"] Type: "object",
if !ok { Properties: map[string]*jsonschema.Schema{
return nil, errors.New("net.tcp.server requires a port parameter") "ip": {
Title: "IP",
Description: "the IP address to bind the TCP server to",
Type: "string",
Default: json.RawMessage(`"0.0.0.0"`),
},
"port": {
Title: "Port",
Description: "the port for the TCP server to listen on",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1024),
Maximum: jsonschema.Ptr[float64](65535),
},
"framing": {
Title: "Framing Method",
Description: "the method used to frame messages over the TCP connection",
Type: "string",
Enum: []any{"LF", "CR", "CRLF", "SLIP", "RAW"},
},
},
Required: []string{"port", "framing"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(moduleConfig config.ModuleConfig) (common.Module, error) {
params := moduleConfig.Params
portNum, err := params.GetInt("port")
if err != nil {
return nil, fmt.Errorf("net.tcp.server port error: %w", err)
} }
portNum, ok := port.(float64) framingMethodString, err := params.GetString("framing")
if err != nil {
if !ok { return nil, fmt.Errorf("net.tcp.server framing error: %w", err)
return nil, errors.New("net.tcp.server port must be a number")
} }
framingMethod := "RAW" inFramer := framer.GetFramer(framingMethodString)
framingMethodRaw, ok := params["framing"] if inFramer == nil {
return nil, fmt.Errorf("net.tcp.server unknown framing method: %s", framingMethodString)
if ok {
framingMethodString, ok := framingMethodRaw.(string)
if !ok {
return nil, errors.New("net.tcp.server framing method must be a string")
}
framingMethod = framingMethodString
} }
framer := framer.GetFramer(framingMethod) outFramer := framer.GetFramer(framingMethodString)
if framer == nil { ipString, err := params.GetString("ip")
return nil, fmt.Errorf("net.tcp.server unknown framing method: %s", framingMethod) if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
ipString = "0.0.0.0"
} else {
return nil, fmt.Errorf("net.tcp.server ip error: %w", err)
} }
ipString := "0.0.0.0"
ip, ok := params["ip"]
if ok {
specificIpString, ok := ip.(string)
if !ok {
return nil, errors.New("net.tcp.server ip must be a string")
}
ipString = specificIpString
} }
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", ipString, uint16(portNum))) addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", ipString, uint16(portNum)))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &TCPServer{inFramer: inFramer, outFramer: outFramer, framerType: framingMethodString, Addr: addr, config: moduleConfig, logger: CreateLogger(moduleConfig)}, nil
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("net.tcp.server unable to get router from context")
}
return &TCPServer{Framer: framer, Addr: addr, config: config, quit: make(chan interface{}), ctx: ctx, router: router, logger: CreateLogger(config)}, nil
}, },
}) })
} }
type tcpConnection struct {
conn *net.TCPConn
framer framer.Framer
}
type TCPServer struct {
config config.ModuleConfig
Addr *net.TCPAddr
inFramer framer.Framer
outFramer framer.Framer
framerType string
ctx context.Context
inputHandler common.InputHandler
wg sync.WaitGroup
connections []tcpConnection
connectionsMu sync.RWMutex
logger *slog.Logger
cancel context.CancelFunc
listener *net.TCPListener
listenerMu sync.Mutex
connectionShutdownCtx context.Context
connectionShutdown context.CancelFunc
}
func (ts *TCPServer) Id() string { func (ts *TCPServer) Id() string {
return ts.config.Id return ts.config.Id
} }
@@ -103,62 +120,44 @@ func (ts *TCPServer) Type() string {
func (ts *TCPServer) handleClient(client *net.TCPConn) { func (ts *TCPServer) handleClient(client *net.TCPConn) {
ts.connectionsMu.Lock() ts.connectionsMu.Lock()
ts.connections = append(ts.connections, client) ts.connections = append(ts.connections, tcpConnection{conn: client, framer: framer.GetFramer(ts.framerType)})
ts.connectionsMu.Unlock() ts.connectionsMu.Unlock()
ts.logger.Debug("net.tcp.server connection accepted", "remoteAddr", client.RemoteAddr().String()) ts.logger.Debug("connection accepted", "remoteAddr", client.RemoteAddr().String())
defer client.Close() defer func() {
client.Close()
ts.connectionsMu.Lock()
for i := 0; i < len(ts.connections); i++ {
if ts.connections[i].conn == client {
ts.connections = slices.Delete(ts.connections, i, i+1)
break
}
}
ts.connectionsMu.Unlock()
ts.logger.Debug("connection closed", "remoteAddr", client.RemoteAddr().String())
}()
buffer := make([]byte, 1024) buffer := make([]byte, 1024)
ClientRead: for ts.ctx.Err() == nil && ts.connectionShutdownCtx.Err() == nil {
for {
select {
case <-ts.quit:
return
default:
client.SetDeadline(time.Now().Add(time.Millisecond * 200)) client.SetDeadline(time.Now().Add(time.Millisecond * 200))
byteCount, err := client.Read(buffer) byteCount, err := client.Read(buffer)
if err != nil { if err != nil {
if opErr, ok := err.(*net.OpError); ok { opErr, ok := err.(*net.OpError)
if ok {
//NOTE(jwetzell) we hit deadline //NOTE(jwetzell) we hit deadline
if opErr.Timeout() { if opErr.Timeout() {
continue ClientRead continue
}
} }
if errors.Is(opErr, syscall.ECONNRESET) {
ts.connectionsMu.Lock()
for i := 0; i < len(ts.connections); i++ {
if ts.connections[i] == client {
ts.connections = slices.Delete(ts.connections, i, i+1)
break break
} }
} if ts.inFramer != nil {
ts.logger.Debug("connection reset", "remoteAddr", client.RemoteAddr().String())
ts.connectionsMu.Unlock()
}
}
if err.Error() == "EOF" {
ts.connectionsMu.Lock()
for i := 0; i < len(ts.connections); i++ {
if ts.connections[i] == client {
ts.connections = slices.Delete(ts.connections, i, i+1)
break
}
}
ts.logger.Debug("stream ended", "remoteAddr", client.RemoteAddr().String())
ts.connectionsMu.Unlock()
}
return
}
if ts.Framer != nil {
if byteCount > 0 { if byteCount > 0 {
messages := ts.Framer.Decode(buffer[0:byteCount]) messages := ts.inFramer.Decode(buffer[0:byteCount])
for _, message := range messages { for _, message := range messages {
if ts.router != nil { if ts.inputHandler != nil {
ts.router.HandleInput(ts.ctx, ts.Id(), message) ts.inputHandler(ts.ctx, ts.Id(), message)
} else { } else {
ts.logger.Error("input received but no router is configured") ts.logger.Error("input received but no input handler is configured")
}
} }
} }
} }
@@ -166,62 +165,94 @@ ClientRead:
} }
} }
func (ts *TCPServer) Run() error { func (ts *TCPServer) Start(ctx context.Context, inputHandler common.InputHandler) error {
ts.logger.Debug("running")
ts.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
ts.ctx = moduleContext
ts.cancel = cancel
shutdownCtx, shutdownCancel := context.WithCancel(context.Background())
ts.connectionShutdownCtx = shutdownCtx
ts.connectionShutdown = shutdownCancel
listener, err := net.ListenTCP("tcp", ts.Addr) listener, err := net.ListenTCP("tcp", ts.Addr)
if err != nil { if err != nil {
return err return err
} }
ts.listenerMu.Lock()
ts.listener = listener
ts.listenerMu.Unlock()
ts.wg.Add(1) ts.wg.Add(1)
go func() {
<-ts.ctx.Done()
close(ts.quit)
listener.Close()
ts.logger.Debug("done")
}()
AcceptLoop: AcceptLoop:
for { for ts.ctx.Err() == nil {
conn, err := listener.AcceptTCP() conn, err := listener.AcceptTCP()
if err != nil { if err != nil {
if errors.Is(err, net.ErrClosed) {
break AcceptLoop
}
select { select {
case <-ts.quit: case <-ts.ctx.Done():
break AcceptLoop break AcceptLoop
default: default:
ts.logger.Debug("problem with listener", "error", err) ts.logger.Debug("problem with listener", "error", err)
} }
} else { } else {
ts.wg.Add(1) ts.wg.Go(func() {
go func() {
ts.handleClient(conn) ts.handleClient(conn)
ts.wg.Done() })
}()
} }
} }
ts.wg.Done() ts.wg.Done()
ts.wg.Wait() <-ts.ctx.Done()
ts.logger.Debug("done")
return nil return nil
} }
func (ts *TCPServer) Output(ctx context.Context, payload any) error { func (ts *TCPServer) Output(ctx context.Context, payload any) error {
payloadBytes, ok := payload.([]byte) payloadBytes, ok := common.GetAnyAsByteSlice(payload)
if !ok { if !ok {
return errors.New("net.tcp.server is only able to output bytes") return errors.New("net.tcp.server is only able to output bytes")
} }
ts.connectionsMu.Lock() ts.connectionsMu.Lock()
errorString := "" defer ts.connectionsMu.Unlock()
var errorString strings.Builder
if ts.outFramer == nil {
return errors.New("no output framer configured")
}
outputBytes := ts.outFramer.Encode(payloadBytes)
for _, connection := range ts.connections { for _, connection := range ts.connections {
_, err := connection.Write(payloadBytes) _, err := connection.conn.Write(outputBytes)
if err != nil { if err != nil {
errorString += fmt.Sprintf("%s\n", err.Error()) fmt.Fprintf(&errorString, "%s\n", err.Error())
} }
} }
ts.connectionsMu.Unlock()
if errorString == "" { if errorString.String() == "" {
return nil return nil
} }
return fmt.Errorf("net.tcp.server error during output: %s", errorString) return fmt.Errorf("net.tcp.server error during output: %s", errorString.String())
}
func (ts *TCPServer) Stop() {
if ts.cancel != nil {
defer ts.cancel()
}
if ts.connectionShutdown != nil {
ts.connectionShutdown()
}
ts.listenerMu.Lock()
defer ts.listenerMu.Unlock()
if ts.listener != nil {
ts.listener.Close()
}
ts.logger.Debug("waiting for connections to close")
ts.wg.Wait()
ts.logger.Debug("all connections closed")
} }
+139
View File
@@ -0,0 +1,139 @@
package module_test
import (
"testing"
"time"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestDbSqliteFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("db.sqlite")
if !ok {
t.Fatalf("db.sqlite module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "db.sqlite",
Params: map[string]any{
"dsn": ":memory:",
},
})
if err != nil {
t.Fatalf("failed to create db.sqlite module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("db.sqlite module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "db.sqlite" {
t.Fatalf("db.sqlite module has wrong type: %s", moduleInstance.Type())
}
}
func TestGoodDbSqlite(t *testing.T) {
testCases := []struct {
name string
params map[string]any
}{
{
name: "in memory db",
params: map[string]any{
"dsn": ":memory:",
},
},
{
name: "file db",
params: map[string]any{
"dsn": "test.db",
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("db.sqlite")
if !ok {
t.Fatalf("db.sqlite module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "db.sqlite",
Params: test.params,
})
if err != nil {
t.Fatalf("db.sqlite failed to create module: %s", err)
}
// TODO(jwetzell) this is kind of hacky
go func() {
time.Sleep(1 * time.Second)
moduleInstance.Stop()
}()
err = moduleInstance.Start(t.Context(), nil)
if err != nil {
t.Fatalf("db.sqlite failed to start: %s", err)
}
})
}
}
func TestBadDbSqlite(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no dsn param",
params: map[string]any{},
errorString: "db.sqlite dsn error: not found",
},
{
name: "non-string dsn",
params: map[string]any{"dsn": 123},
errorString: "db.sqlite dsn error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("db.sqlite")
if !ok {
t.Fatalf("db.sqlite module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "db.sqlite",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("db.sqlite got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("db.sqlite expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("db.sqlite got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+134
View File
@@ -0,0 +1,134 @@
package module_test
import (
"testing"
"time"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestHTTPServerFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("http.server")
if !ok {
t.Fatalf("http.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "http.server",
Params: map[string]any{
"port": 3000,
},
})
if err != nil {
t.Fatalf("failed to create http.server module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("http.server module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "http.server" {
t.Fatalf("http.server module has wrong type: %s", moduleInstance.Type())
}
}
func TestGoodHTTPServer(t *testing.T) {
testCases := []struct {
name string
params map[string]any
}{
{
name: "minimal config",
params: map[string]any{
"port": 8000,
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("http.server")
if !ok {
t.Fatalf("http.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "http.server",
Params: test.params,
})
if err != nil {
t.Fatalf("http.server failed to create module: %s", err)
}
// TODO(jwetzell) this is kind of hacky
go func() {
time.Sleep(1 * time.Second)
moduleInstance.Stop()
}()
err = moduleInstance.Start(t.Context(), nil)
if err != nil {
t.Fatalf("http.server failed to start: %s", err)
}
})
}
}
func TestBadHTTPServer(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no port param",
params: map[string]any{},
errorString: "http.server port error: not found",
},
{
name: "non-numeric port",
params: map[string]any{"port": "3000"},
errorString: "http.server port error: not a number",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("http.server")
if !ok {
t.Fatalf("http.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "http.server",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("http.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("http.server expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("http.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+87
View File
@@ -0,0 +1,87 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestMIDIInputFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("midi.input")
if !ok {
t.Fatalf("midi.input module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "midi.input",
Params: map[string]any{
"port": "test",
},
})
if err != nil {
t.Fatalf("failed to create midi.input module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("midi.input module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "midi.input" {
t.Fatalf("midi.input module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadMIDIInput(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no port param",
params: map[string]any{},
errorString: "midi.input port error: not found",
},
{
name: "non-string port",
params: map[string]any{"port": 123},
errorString: "midi.input port error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("midi.input")
if !ok {
t.Fatalf("midi.input module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "midi.input",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("midi.input got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("midi.input expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("midi.input got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+87
View File
@@ -0,0 +1,87 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestMIDIOutputFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("midi.output")
if !ok {
t.Fatalf("midi.output module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "midi.output",
Params: map[string]any{
"port": "test",
},
})
if err != nil {
t.Fatalf("failed to create midi.output module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("midi.output module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "midi.output" {
t.Fatalf("midi.output module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadMIDIOutput(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no port param",
params: map[string]any{},
errorString: "midi.output port error: not found",
},
{
name: "non-string port",
params: map[string]any{"port": 123},
errorString: "midi.output port error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("midi.output")
if !ok {
t.Fatalf("midi.output module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "midi.output",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("midi.output got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("midi.output expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("midi.output got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+63
View File
@@ -0,0 +1,63 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
"github.com/jwetzell/showbridge-go/internal/test"
)
func TestModuleBadRegistrationNoType(t *testing.T) {
defer func() {
r := recover()
if r == nil {
t.Fatalf("module registration should have panicked but did not")
}
}()
module.RegisterModule(module.ModuleRegistration{
Type: "",
New: func(config config.ModuleConfig) (common.Module, error) {
return test.NewTestModule("test"), nil
},
})
}
func TestModuleBadRegistrationNoNew(t *testing.T) {
defer func() {
r := recover()
if r == nil {
t.Fatalf("processor registration should have panicked but did not")
}
}()
module.RegisterModule(module.ModuleRegistration{
Type: "module.test",
New: nil,
})
}
func TestModuleBadRegistrationExistingType(t *testing.T) {
defer func() {
r := recover()
if r == nil {
t.Fatalf("processor registration should have panicked but did not")
}
}()
module.RegisterModule(module.ModuleRegistration{
Type: "module.test",
New: func(config config.ModuleConfig) (common.Module, error) {
return test.NewTestModule("test"), nil
},
})
module.RegisterModule(module.ModuleRegistration{
Type: "module.test",
New: func(config config.ModuleConfig) (common.Module, error) {
return test.NewTestModule("test"), nil
},
})
}
+130
View File
@@ -0,0 +1,130 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestMQTTClientFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("mqtt.client")
if !ok {
t.Fatalf("mqtt.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "mqtt.client",
Params: map[string]any{
"broker": "mqtt://localhost:1883",
"topic": "test/topic",
"clientId": "test",
},
})
if err != nil {
t.Fatalf("failed to create mqtt.client module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("mqtt.client module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "mqtt.client" {
t.Fatalf("mqtt.client module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadMQTTClient(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no broker param",
params: map[string]any{
"topic": "test/topic",
"clientId": "test",
},
errorString: "mqtt.client broker error: not found",
},
{
name: "non-string broker",
params: map[string]any{
"broker": 123,
"topic": "test/topic",
"clientId": "test",
},
errorString: "mqtt.client broker error: not a string",
},
{
name: "no topic param",
params: map[string]any{
"broker": "mqtt://localhost:1883",
"clientId": "test",
},
errorString: "mqtt.client topic error: not found",
},
{
name: "non-string topic",
params: map[string]any{
"broker": "mqtt://localhost:1883",
"topic": 123,
"clientId": "test",
},
errorString: "mqtt.client topic error: not a string",
},
{
name: "no clientId param",
params: map[string]any{
"broker": "mqtt://localhost:1883",
"topic": "test/topic",
},
errorString: "mqtt.client clientId error: not found",
},
{
name: "non-string clientId",
params: map[string]any{
"broker": "mqtt://localhost:1883",
"topic": "test/topic",
"clientId": 123,
},
errorString: "mqtt.client clientId error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("mqtt.client")
if !ok {
t.Fatalf("mqtt.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "mqtt.client",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("mqtt.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("mqtt.client expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("mqtt.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+108
View File
@@ -0,0 +1,108 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestNATSClientFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("nats.client")
if !ok {
t.Fatalf("nats.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "nats.client",
Params: map[string]any{
"url": "nats://127.0.0.1:4222",
"subject": "test",
},
})
if err != nil {
t.Fatalf("failed to create nats.client module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("nats.client module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "nats.client" {
t.Fatalf("nats.client module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadNATSClient(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no url param",
params: map[string]any{
"subject": "test/subject",
},
errorString: "nats.client url error: not found",
},
{
name: "non-string url",
params: map[string]any{
"url": 123,
"subject": "test/subject",
},
errorString: "nats.client url error: not a string",
},
{
name: "no subject param",
params: map[string]any{
"url": "nats://127.0.0.1:4222",
},
errorString: "nats.client subject error: not found",
},
{
name: "non-string subject",
params: map[string]any{
"url": "nats://127.0.0.1:4222",
"subject": 123,
},
errorString: "nats.client subject error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("nats.client")
if !ok {
t.Fatalf("nats.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "nats.client",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("nats.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("nats.client expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("nats.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+132
View File
@@ -0,0 +1,132 @@
package module_test
import (
"testing"
"time"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestNATSServerFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("nats.server")
if !ok {
t.Fatalf("nats.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "nats.server",
Params: map[string]any{
"ip": "127.0.0.1",
"port": 4222,
},
})
if err != nil {
t.Fatalf("failed to create nats.server module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("nats.server module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "nats.server" {
t.Fatalf("nats.server module has wrong type: %s", moduleInstance.Type())
}
}
func TestGoodNATSServer(t *testing.T) {
testCases := []struct {
name string
params map[string]any
}{
{
name: "minimal config",
params: map[string]any{
"port": 4222,
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("nats.server")
if !ok {
t.Fatalf("nats.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "nats.server",
Params: test.params,
})
if err != nil {
t.Fatalf("nats.server failed to create module: %s", err)
}
// TODO(jwetzell) this is kind of hacky
go func() {
time.Sleep(1 * time.Second)
moduleInstance.Stop()
}()
err = moduleInstance.Start(t.Context(), nil)
if err != nil {
t.Fatalf("nats.server failed to start: %s", err)
}
})
}
}
func TestBadNATSServer(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "non-string ip",
params: map[string]any{
"ip": 123,
},
errorString: "nats.server ip error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("nats.server")
if !ok {
t.Fatalf("nats.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "nats.server",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("nats.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("nats.server expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("nats.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+73
View File
@@ -0,0 +1,73 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestPSNClientFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("psn.client")
if !ok {
t.Fatalf("psn.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "psn.client",
})
if err != nil {
t.Fatalf("failed to create psn.client module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("psn.client module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "psn.client" {
t.Fatalf("psn.client module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadPSNClient(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("psn.client")
if !ok {
t.Fatalf("psn.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "psn.client",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("psn.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("psn.client expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("psn.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+108
View File
@@ -0,0 +1,108 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestRedisClientFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("redis.client")
if !ok {
t.Fatalf("redis.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "redis.client",
Params: map[string]any{
"host": "localhost",
"port": 6379,
},
})
if err != nil {
t.Fatalf("failed to create redis.client module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("redis.client module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "redis.client" {
t.Fatalf("redis.client module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadRedisClient(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no host param",
params: map[string]any{
"port": 6379,
},
errorString: "redis.client host error: not found",
},
{
name: "non-string host",
params: map[string]any{
"host": 123,
"port": 6379,
},
errorString: "redis.client host error: not a string",
},
{
name: "no port param",
params: map[string]any{
"host": "localhost",
},
errorString: "redis.client port error: not found",
},
{
name: "non-number port",
params: map[string]any{
"host": "localhost",
"port": "6379",
},
errorString: "redis.client port error: not a number",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("redis.client")
if !ok {
t.Fatalf("redis.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "redis.client",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("redis.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("redis.client expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("redis.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+117
View File
@@ -0,0 +1,117 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestSerialClientFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("serial.client")
if !ok {
t.Fatalf("serial.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "serial.client",
Params: map[string]any{
"port": "/dev/ttyUSB0",
"framing": "LF",
"baudRate": 9600,
},
})
if err != nil {
t.Fatalf("failed to create serial.client module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("serial.client module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "serial.client" {
t.Fatalf("serial.client module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadSerialClient(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no port param",
params: map[string]any{
"framing": "LF",
},
errorString: "serial.client port error: not found",
},
{
name: "non-string port param",
params: map[string]any{
"port": 8000,
"framing": "LF",
},
errorString: "serial.client port error: not a string",
},
{
name: "no framing param",
params: map[string]any{
"port": "/dev/ttyUSB0",
},
errorString: "serial.client framing error: not found",
},
{
name: "non-string framing param",
params: map[string]any{
"port": "/dev/ttyUSB0",
"framing": 1,
},
errorString: "serial.client framing error: not a string",
},
{
name: "unkown framing method",
params: map[string]any{
"port": "/dev/ttyUSB0",
"framing": "asdfasdfasdfasdflkj",
},
errorString: "serial.client unknown framing method: asdfasdfasdfasdflkj",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("serial.client")
if !ok {
t.Fatalf("serial.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "serial.client",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("serial.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("serial.client expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("serial.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
@@ -0,0 +1,102 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestSIPCallServerFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("sip.call.server")
if !ok {
t.Fatalf("sip.call.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "sip.call.server",
})
if err != nil {
t.Fatalf("failed to create sip.call.server module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("sip.call.server module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "sip.call.server" {
t.Fatalf("sip.call.server module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadSIPCallServer(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "non-number port param",
params: map[string]any{
"port": "8000",
},
errorString: "sip.call.server port error: not a number",
},
{
name: "non-string ip param",
params: map[string]any{
"ip": 123,
},
errorString: "sip.call.server ip error: not a string",
},
{
name: "non-string transport param",
params: map[string]any{
"transport": 123,
},
errorString: "sip.call.server transport error: not a string",
},
{
name: "non-string userAgent param",
params: map[string]any{
"userAgent": 123,
},
errorString: "sip.call.server userAgent error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("sip.call.server")
if !ok {
t.Fatalf("sip.call.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "sip.call.server",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("sip.call.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("sip.call.server expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("sip.call.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
@@ -0,0 +1,121 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestSIPDTMFServerFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("sip.dtmf.server")
if !ok {
t.Fatalf("sip.dtmf.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "sip.dtmf.server",
Params: map[string]any{
"separator": "#",
},
})
if err != nil {
t.Fatalf("failed to create sip.dtmf.server module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("sip.dtmf.server module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "sip.dtmf.server" {
t.Fatalf("sip.dtmf.server module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadSIPDTMFServer(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no separator param",
params: map[string]any{},
errorString: "sip.dtmf.server separator error: not found",
},
{
name: "non-string separator param",
params: map[string]any{
"separator": 123,
},
errorString: "sip.dtmf.server separator error: not a string",
},
{
name: "non-number port param",
params: map[string]any{
"separator": "#",
"port": "8000",
},
errorString: "sip.dtmf.server port error: not a number",
},
{
name: "non-string ip param",
params: map[string]any{
"separator": "#",
"ip": 123,
},
errorString: "sip.dtmf.server ip error: not a string",
},
{
name: "non-string transport param",
params: map[string]any{
"separator": "#",
"transport": 123,
},
errorString: "sip.dtmf.server transport error: not a string",
},
{
name: "non-string userAgent param",
params: map[string]any{
"separator": "#",
"userAgent": 123,
},
errorString: "sip.dtmf.server userAgent error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("sip.dtmf.server")
if !ok {
t.Fatalf("sip.dtmf.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "sip.dtmf.server",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("sip.dtmf.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("sip.dtmf.server expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("sip.dtmf.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+109
View File
@@ -0,0 +1,109 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestTCPClientFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.tcp.client")
if !ok {
t.Fatalf("net.tcp.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.tcp.client",
Params: map[string]any{
"host": "localhost",
"port": 8000,
"framing": "LF",
},
})
if err != nil {
t.Fatalf("failed to create net.tcp.client module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("net.tcp.client module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "net.tcp.client" {
t.Fatalf("net.tcp.client module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadTCPClient(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no port param",
params: map[string]any{
"host": "localhost",
},
errorString: "net.tcp.client port error: not found",
},
{
name: "non-number port param",
params: map[string]any{
"host": "localhost",
"port": "8000",
},
errorString: "net.tcp.client port error: not a number",
},
{
name: "no host param",
params: map[string]any{
"port": 8000,
},
errorString: "net.tcp.client host error: not found",
},
{
name: "non-string host param",
params: map[string]any{
"host": 123,
"port": 8000,
},
errorString: "net.tcp.client host error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.tcp.client")
if !ok {
t.Fatalf("net.tcp.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.tcp.client",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("net.tcp.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("net.tcp.client expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("net.tcp.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+181
View File
@@ -0,0 +1,181 @@
package module_test
import (
"testing"
"time"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestTCPServerFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.tcp.server")
if !ok {
t.Fatalf("net.tcp.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.tcp.server",
Params: map[string]any{
"port": 8000,
"framing": "LF",
},
})
if err != nil {
t.Fatalf("failed to create net.tcp.server module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("net.tcp.server module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "net.tcp.server" {
t.Fatalf("net.tcp.server module has wrong type: %s", moduleInstance.Type())
}
}
func TestGoodTCPServer(t *testing.T) {
testCases := []struct {
name string
params map[string]any
}{
{
name: "minimal config",
params: map[string]any{
"port": 8000,
"framing": "LF",
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.tcp.server")
if !ok {
t.Fatalf("net.tcp.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.tcp.server",
Params: test.params,
})
if err != nil {
t.Fatalf("net.tcp.server failed to create module: %s", err)
}
// TODO(jwetzell) this is kind of hacky
go func() {
time.Sleep(1 * time.Second)
moduleInstance.Stop()
}()
err = moduleInstance.Start(t.Context(), nil)
if err != nil {
t.Fatalf("net.tcp.server failed to start: %s", err)
}
})
}
}
func TestBadTCPServer(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no port param",
params: map[string]any{
"framing": "LF",
},
errorString: "net.tcp.server port error: not found",
},
{
name: "non-number port param",
params: map[string]any{
"port": "8000",
"framing": "LF",
},
errorString: "net.tcp.server port error: not a number",
},
{
name: "no framing param",
params: map[string]any{
"port": 8000,
},
errorString: "net.tcp.server framing error: not found",
},
{
name: "non-string framing param",
params: map[string]any{
"port": 8000,
"framing": 1,
},
errorString: "net.tcp.server framing error: not a string",
},
{
name: "unkown framing method",
params: map[string]any{
"port": 8000,
"framing": "asdfasdfasdfasdflkj",
},
errorString: "net.tcp.server unknown framing method: asdfasdfasdfasdflkj",
},
{
name: "non-string ip param",
params: map[string]any{
"port": 8000,
"framing": "LF",
"ip": 123,
},
errorString: "net.tcp.server ip error: not a string",
},
{
name: "invalid addr",
params: map[string]any{
"ip": "127.0.0.",
"port": 8000,
"framing": "LF",
},
errorString: "lookup 127.0.0.: no such host",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.tcp.server")
if !ok {
t.Fatalf("net.tcp.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.tcp.server",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("net.tcp.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("net.tcp.server expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("net.tcp.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
Binary file not shown.
+135
View File
@@ -0,0 +1,135 @@
package module_test
import (
"testing"
"time"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestTimeIntervalFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("time.interval")
if !ok {
t.Fatalf("time.interval module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "time.interval",
Params: map[string]any{
"duration": 1000,
},
})
if err != nil {
t.Fatalf("failed to create time.interval module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("time.interval module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "time.interval" {
t.Fatalf("time.interval module has wrong type: %s", moduleInstance.Type())
}
}
func TestGoodTimeInterval(t *testing.T) {
testCases := []struct {
name string
params map[string]any
}{
{
name: "minimal config",
params: map[string]any{
"duration": 1000,
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("time.interval")
if !ok {
t.Fatalf("time.interval module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "time.interval",
Params: test.params,
})
if err != nil {
t.Fatalf("time.interval failed to create module: %s", err)
}
// TODO(jwetzell) this is kind of hacky
go func() {
time.Sleep(1 * time.Second)
moduleInstance.Stop()
}()
err = moduleInstance.Start(t.Context(), nil)
if err != nil {
t.Fatalf("time.interval failed to start: %s", err)
}
})
}
}
func TestBadTimeInterval(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no duration param",
params: map[string]any{},
errorString: "time.interval duration error: not found",
},
{
name: "non-number duration param",
params: map[string]any{
"duration": "8000",
},
errorString: "time.interval duration error: not a number",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("time.interval")
if !ok {
t.Fatalf("time.interval module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "time.interval",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("time.interval got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("time.interval expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("time.interval got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+135
View File
@@ -0,0 +1,135 @@
package module_test
import (
"testing"
"time"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestTimeTimerFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("time.timer")
if !ok {
t.Fatalf("time.timer module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "time.timer",
Params: map[string]any{
"duration": 1000,
},
})
if err != nil {
t.Fatalf("failed to create time.timer module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("time.timer module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "time.timer" {
t.Fatalf("time.timer module has wrong type: %s", moduleInstance.Type())
}
}
func TestGoodTimeTimer(t *testing.T) {
testCases := []struct {
name string
params map[string]any
}{
{
name: "minimal config",
params: map[string]any{
"duration": 2000,
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("time.timer")
if !ok {
t.Fatalf("time.timer module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "time.timer",
Params: test.params,
})
if err != nil {
t.Fatalf("time.timer failed to create module: %s", err)
}
// TODO(jwetzell) this is kind of hacky
go func() {
time.Sleep(1 * time.Second)
moduleInstance.Stop()
}()
err = moduleInstance.Start(t.Context(), nil)
if err != nil {
t.Fatalf("time.timer failed to start: %s", err)
}
})
}
}
func TestBadTimeTimer(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no duration param",
params: map[string]any{},
errorString: "time.timer duration error: not found",
},
{
name: "non-number duration param",
params: map[string]any{
"duration": "8000",
},
errorString: "time.timer duration error: not a number",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("time.timer")
if !ok {
t.Fatalf("time.timer module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "time.timer",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("time.timer got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("time.timer expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("time.timer got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+157
View File
@@ -0,0 +1,157 @@
package module_test
import (
"testing"
"time"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestUDPClientFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.udp.client")
if !ok {
t.Fatalf("udp.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.udp.client",
Params: map[string]any{
"host": "localhost",
"port": 8000,
"framing": "LF",
},
})
if err != nil {
t.Fatalf("failed to create net.udp.client module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("net.udp.client module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "net.udp.client" {
t.Fatalf("net.udp.client module has wrong type: %s", moduleInstance.Type())
}
}
func TestGoodUDPClient(t *testing.T) {
testCases := []struct {
name string
params map[string]any
}{
{
name: "minimal config",
params: map[string]any{
"host": "localhost",
"port": 8000,
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.udp.client")
if !ok {
t.Fatalf("net.udp.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.udp.client",
Params: test.params,
})
if err != nil {
t.Fatalf("net.udp.client failed to create module: %s", err)
}
// TODO(jwetzell) this is kind of hacky
go func() {
time.Sleep(1 * time.Second)
moduleInstance.Stop()
}()
err = moduleInstance.Start(t.Context(), nil)
if err != nil {
t.Fatalf("net.udp.client failed to start: %s", err)
}
})
}
}
func TestBadUDPClient(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no port param",
params: map[string]any{
"host": "localhost",
},
errorString: "net.udp.client port error: not found",
},
{
name: "non-number port param",
params: map[string]any{
"host": "localhost",
"port": "8000",
},
errorString: "net.udp.client port error: not a number",
},
{
name: "no host param",
params: map[string]any{
"port": 8000,
},
errorString: "net.udp.client host error: not found",
},
{
name: "non-string host param",
params: map[string]any{
"host": 123,
"port": 8000,
},
errorString: "net.udp.client host error: not a string",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.udp.client")
if !ok {
t.Fatalf("net.udp.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.udp.client",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("net.udp.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("net.udp.client expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("net.udp.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+116
View File
@@ -0,0 +1,116 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestUDPMulticastFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.udp.multicast")
if !ok {
t.Fatalf("udp.multicast module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.udp.multicast",
Params: map[string]any{
"ip": "236.10.10.10",
"port": 56565,
},
})
if err != nil {
t.Fatalf("failed to create net.udp.multicast module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("net.udp.multicast module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "net.udp.multicast" {
t.Fatalf("net.udp.multicast module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadUDPMulticast(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no port param",
params: map[string]any{
"ip": "localhost",
},
errorString: "net.udp.multicast port error: not found",
},
{
name: "non-number port param",
params: map[string]any{
"ip": "localhost",
"port": "8000",
},
errorString: "net.udp.multicast port error: not a number",
},
{
name: "no ip param",
params: map[string]any{
"port": 8000,
},
errorString: "net.udp.multicast ip error: not found",
},
{
name: "non-string ip param",
params: map[string]any{
"ip": 123,
"port": 8000,
},
errorString: "net.udp.multicast ip error: not a string",
},
{
name: "invalid addr",
params: map[string]any{
"ip": "127.0.0.",
"port": 8000,
},
errorString: "lookup 127.0.0.: no such host",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.udp.multicast")
if !ok {
t.Fatalf("net.udp.multicast module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.udp.multicast",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("net.udp.multicast got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("net.udp.multicast expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("net.udp.multicast got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+160
View File
@@ -0,0 +1,160 @@
package module_test
import (
"testing"
"time"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestUDPServerFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.udp.server")
if !ok {
t.Fatalf("net.udp.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.udp.server",
Params: map[string]any{
"port": 8000,
},
})
if err != nil {
t.Fatalf("failed to create udp.server module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("udp.server module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "net.udp.server" {
t.Fatalf("net.udp.server module has wrong type: %s", moduleInstance.Type())
}
}
func TestGoodUDPServer(t *testing.T) {
testCases := []struct {
name string
params map[string]any
}{
{
name: "minimal config",
params: map[string]any{
"port": 8000,
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.udp.server")
if !ok {
t.Fatalf("net.udp.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.udp.server",
Params: test.params,
})
if err != nil {
t.Fatalf("net.udp.server failed to create module: %s", err)
}
// TODO(jwetzell) this is kind of hacky
go func() {
time.Sleep(1 * time.Second)
moduleInstance.Stop()
}()
err = moduleInstance.Start(t.Context(), nil)
if err != nil {
t.Fatalf("net.udp.server failed to start: %s", err)
}
})
}
}
func TestBadUDPServer(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no port param",
params: map[string]any{},
errorString: "net.udp.server port error: not found",
},
{
name: "non-number port param",
params: map[string]any{
"port": "8000",
},
errorString: "net.udp.server port error: not a number",
},
{
name: "non-string ip param",
params: map[string]any{
"port": 8000,
"ip": 123,
},
errorString: "net.udp.server ip error: not a string",
},
{
name: "non-number bufferSize param",
params: map[string]any{
"port": 8000,
"bufferSize": "1024",
},
errorString: "net.udp.server bufferSize error: not a number",
},
{
name: "invalid addr",
params: map[string]any{
"ip": "127.0.0.",
"port": 8000,
},
errorString: "lookup 127.0.0.: no such host",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("net.udp.server")
if !ok {
t.Fatalf("net.udp.server module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "net.udp.server",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("net.udp.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("net.udp.server expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("net.udp.server got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
@@ -0,0 +1,96 @@
package module_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/module"
)
func TestWebSocketClientFromRegistry(t *testing.T) {
registration, ok := module.GetModuleRegistration("websocket.client")
if !ok {
t.Fatalf("websocket.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "websocket.client",
Params: map[string]any{
"url": "ws://localhost",
},
})
if err != nil {
t.Fatalf("failed to create websocket.client module: %s", err)
}
if moduleInstance.Id() != "test" {
t.Fatalf("websocket.client module has wrong id: %s", moduleInstance.Id())
}
if moduleInstance.Type() != "websocket.client" {
t.Fatalf("websocket.client module has wrong type: %s", moduleInstance.Type())
}
}
func TestBadWebSocketClient(t *testing.T) {
tests := []struct {
name string
params map[string]any
errorString string
}{
{
name: "no url param",
params: map[string]any{},
errorString: "websocket.client url error: not found",
},
{
name: "non-string url param",
params: map[string]any{
"url": 123,
},
errorString: "websocket.client url error: not a string",
},
{
name: "invalid url param",
params: map[string]any{
"url": "invalid-url",
},
errorString: "websocket.client url error: scheme must be ws or wss",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registration, ok := module.GetModuleRegistration("websocket.client")
if !ok {
t.Fatalf("websocket.client module not registered")
}
moduleInstance, err := registration.New(config.ModuleConfig{
Id: "test",
Type: "websocket.client",
Params: test.params,
})
if err != nil {
if test.errorString != err.Error() {
t.Fatalf("websocket.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
return
}
err = moduleInstance.Start(t.Context(), nil)
if err == nil {
t.Fatalf("websocket.client expected to fail")
}
if err.Error() != test.errorString {
t.Fatalf("websocket.client got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+56 -44
View File
@@ -2,49 +2,51 @@ package module
import ( import (
"context" "context"
"errors" "fmt"
"log/slog" "log/slog"
"time" "time"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
) )
func init() {
RegisterModule(ModuleRegistration{
Type: "time.interval",
Title: "Interval",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"duration": {
Title: "Duration",
Description: "time in milliseconds between emitted events",
Type: "integer",
},
},
Required: []string{"duration"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params
durationInt, err := params.GetInt("duration")
if err != nil {
return nil, fmt.Errorf("time.interval duration error: %w", err)
}
return &TimeInterval{Duration: uint32(durationInt), config: config, logger: CreateLogger(config)}, nil
},
})
}
type TimeInterval struct { type TimeInterval struct {
config config.ModuleConfig config config.ModuleConfig
Duration uint32 Duration uint32
ctx context.Context ctx context.Context
router route.RouteIO inputHandler common.InputHandler
ticker *time.Ticker ticker *time.Ticker
logger *slog.Logger logger *slog.Logger
} cancel context.CancelFunc
func init() {
RegisterModule(ModuleRegistration{
Type: "time.interval",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) {
params := config.Params
duration, ok := params["duration"]
if !ok {
return nil, errors.New("time.interval requires a duration parameter")
}
durationNum, ok := duration.(float64)
if !ok {
return nil, errors.New("time.interval duration must be number")
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("time.interval unable to get router from context")
}
return &TimeInterval{Duration: uint32(durationNum), config: config, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
},
})
} }
func (i *TimeInterval) Id() string { func (i *TimeInterval) Id() string {
@@ -55,26 +57,36 @@ func (i *TimeInterval) Type() string {
return i.config.Type return i.config.Type
} }
func (i *TimeInterval) Run() error { func (i *TimeInterval) Start(ctx context.Context, inputHandler common.InputHandler) error {
i.logger.Debug("running")
i.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
i.ctx = moduleContext
i.cancel = cancel
ticker := time.NewTicker(time.Millisecond * time.Duration(i.Duration)) ticker := time.NewTicker(time.Millisecond * time.Duration(i.Duration))
i.ticker = ticker i.ticker = ticker
defer ticker.Stop()
for { for i.ctx.Err() == nil {
select { select {
case <-i.ctx.Done(): case <-ticker.C:
if i.inputHandler != nil {
i.inputHandler(i.ctx, i.Id(), time.Now())
}
default:
continue
}
}
<-i.ctx.Done()
i.logger.Debug("done") i.logger.Debug("done")
return nil return nil
case <-ticker.C:
if i.router != nil {
i.router.HandleInput(i.ctx, i.Id(), time.Now())
}
}
} }
func (i *TimeInterval) Stop() {
if i.cancel != nil {
defer i.cancel()
}
if i.ticker != nil {
i.ticker.Stop()
} }
func (i *TimeInterval) Output(ctx context.Context, payload any) error {
i.ticker.Reset(time.Millisecond * time.Duration(i.Duration))
return nil
} }
+55 -42
View File
@@ -2,49 +2,52 @@ package module
import ( import (
"context" "context"
"errors" "fmt"
"log/slog" "log/slog"
"time" "time"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
) )
func init() {
RegisterModule(ModuleRegistration{
Type: "time.timer",
Title: "Timer",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"duration": {
Title: "Duration",
Description: "time in milliseconds before the timer fires",
Type: "integer",
},
},
Required: []string{"duration"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params
durationNum, err := params.GetInt("duration")
if err != nil {
return nil, fmt.Errorf("time.timer duration error: %w", err)
}
return &TimeTimer{Duration: uint32(durationNum), config: config, logger: CreateLogger(config)}, nil
},
})
}
type TimeTimer struct { type TimeTimer struct {
config config.ModuleConfig config config.ModuleConfig
Duration uint32 Duration uint32
ctx context.Context ctx context.Context
router route.RouteIO inputHandler common.InputHandler
timer *time.Timer timer *time.Timer
logger *slog.Logger logger *slog.Logger
} cancel context.CancelFunc
func init() {
RegisterModule(ModuleRegistration{
Type: "time.timer",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) {
params := config.Params
duration, ok := params["duration"]
if !ok {
return nil, errors.New("time.timer requires a duration parameter")
}
durationNum, ok := duration.(float64)
if !ok {
return nil, errors.New("time.timer duration must be a number")
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("net.tcp.client unable to get router from context")
}
return &TimeTimer{Duration: uint32(durationNum), config: config, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
},
})
} }
func (t *TimeTimer) Id() string { func (t *TimeTimer) Id() string {
@@ -55,24 +58,34 @@ func (t *TimeTimer) Type() string {
return t.config.Type return t.config.Type
} }
func (t *TimeTimer) Run() error { func (t *TimeTimer) Start(ctx context.Context, inputHandler common.InputHandler) error {
t.logger.Debug("running")
t.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
t.ctx = moduleContext
t.cancel = cancel
t.timer = time.NewTimer(time.Millisecond * time.Duration(t.Duration)) t.timer = time.NewTimer(time.Millisecond * time.Duration(t.Duration))
defer t.timer.Stop() for t.ctx.Err() == nil {
for {
select { select {
case <-t.ctx.Done(): case <-t.ctx.Done():
t.timer.Stop()
t.logger.Debug("done")
return nil return nil
case time := <-t.timer.C: case time := <-t.timer.C:
if t.router != nil { if t.inputHandler != nil {
t.router.HandleInput(t.ctx, t.Id(), time) t.inputHandler(t.ctx, t.Id(), time)
} }
} }
} }
} <-t.ctx.Done()
t.logger.Debug("done")
func (t *TimeTimer) Output(ctx context.Context, payload any) error {
t.timer.Reset(time.Millisecond * time.Duration(t.Duration))
return nil return nil
} }
func (t *TimeTimer) Stop() {
if t.cancel != nil {
defer t.cancel()
}
if t.timer != nil {
t.timer.Stop()
}
}
+72 -52
View File
@@ -6,63 +6,67 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"net" "net"
"sync"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
) )
func init() {
RegisterModule(ModuleRegistration{
Type: "net.udp.client",
Title: "UDP Client",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"host": {
Title: "Host",
Description: "the hostname or IP address of the UDP server to connect to",
Type: "string",
},
"port": {
Title: "Port",
Description: "the port of the UDP server to connect to",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1),
Maximum: jsonschema.Ptr[float64](65535),
},
},
Required: []string{"host", "port"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params
hostString, err := params.GetString("host")
if err != nil {
return nil, fmt.Errorf("net.udp.client host error: %w", err)
}
portNum, err := params.GetInt("port")
if err != nil {
return nil, fmt.Errorf("net.udp.client port error: %w", err)
}
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", hostString, uint16(portNum)))
if err != nil {
return nil, err
}
return &UDPClient{Addr: addr, config: config, logger: CreateLogger(config)}, nil
},
})
}
type UDPClient struct { type UDPClient struct {
config config.ModuleConfig config config.ModuleConfig
Addr *net.UDPAddr Addr *net.UDPAddr
Port uint16 Port uint16
conn *net.UDPConn conn *net.UDPConn
ctx context.Context ctx context.Context
router route.RouteIO inputHandler common.InputHandler
logger *slog.Logger logger *slog.Logger
} cancel context.CancelFunc
connMu sync.Mutex
func init() {
RegisterModule(ModuleRegistration{
Type: "net.udp.client",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) {
params := config.Params
host, ok := params["host"]
if !ok {
return nil, errors.New("net.udp.client requires a host parameter")
}
hostString, ok := host.(string)
if !ok {
return nil, errors.New("net.udp.client host must be a string")
}
port, ok := params["port"]
if !ok {
return nil, errors.New("net.udp.client requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, errors.New("net.udp.client port must be a number")
}
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", hostString, uint16(portNum)))
if err != nil {
return nil, err
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("net.udp.client unable to get router from context")
}
return &UDPClient{Addr: addr, config: config, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
},
})
} }
func (uc *UDPClient) Id() string { func (uc *UDPClient) Id() string {
@@ -74,12 +78,19 @@ func (uc *UDPClient) Type() string {
} }
func (uc *UDPClient) SetupConn() error { func (uc *UDPClient) SetupConn() error {
uc.connMu.Lock()
defer uc.connMu.Unlock()
client, err := net.DialUDP("udp", nil, uc.Addr) client, err := net.DialUDP("udp", nil, uc.Addr)
uc.conn = client uc.conn = client
return err return err
} }
func (uc *UDPClient) Run() error { func (uc *UDPClient) Start(ctx context.Context, inputHandler common.InputHandler) error {
uc.logger.Debug("running")
uc.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
uc.ctx = moduleContext
uc.cancel = cancel
err := uc.SetupConn() err := uc.SetupConn()
if err != nil { if err != nil {
@@ -88,15 +99,13 @@ func (uc *UDPClient) Run() error {
<-uc.ctx.Done() <-uc.ctx.Done()
uc.logger.Debug("done") uc.logger.Debug("done")
if uc.conn != nil {
uc.conn.Close()
}
return nil return nil
} }
func (uc *UDPClient) Output(ctx context.Context, payload any) error { func (uc *UDPClient) Output(ctx context.Context, payload any) error {
uc.connMu.Lock()
payloadBytes, ok := payload.([]byte) defer uc.connMu.Unlock()
payloadBytes, ok := common.GetAnyAsByteSlice(payload)
if !ok { if !ok {
return errors.New("net.udp.client is only able to output bytes") return errors.New("net.udp.client is only able to output bytes")
} }
@@ -111,3 +120,14 @@ func (uc *UDPClient) Output(ctx context.Context, payload any) error {
} }
return nil return nil
} }
func (uc *UDPClient) Stop() {
if uc.cancel != nil {
defer uc.cancel()
}
uc.connMu.Lock()
defer uc.connMu.Unlock()
if uc.conn != nil {
uc.conn.Close()
}
}
+75 -52
View File
@@ -6,64 +6,69 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"net" "net"
"sync"
"time" "time"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
) )
type UDPMulticast struct {
config config.ModuleConfig
conn *net.UDPConn
ctx context.Context
router route.RouteIO
Addr *net.UDPAddr
logger *slog.Logger
}
func init() { func init() {
RegisterModule(ModuleRegistration{ RegisterModule(ModuleRegistration{
Type: "net.udp.multicast", Type: "net.udp.multicast",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) { Title: "UDP Multicast",
params := config.Params ParamsSchema: &jsonschema.Schema{
ip, ok := params["ip"] Type: "object",
Properties: map[string]*jsonschema.Schema{
if !ok { "ip": {
return nil, errors.New("net.udp.multicast requires an ip parameter") Title: "IP",
Description: "the multicast address to listen on",
Type: "string",
},
"port": {
Title: "Port",
Description: "the port to listen on",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1024),
Maximum: jsonschema.Ptr[float64](65535),
},
},
Required: []string{"ip", "port"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(moduleConfig config.ModuleConfig) (common.Module, error) {
params := moduleConfig.Params
ipString, err := params.GetString("ip")
if err != nil {
return nil, fmt.Errorf("net.udp.multicast ip error: %w", err)
} }
ipString, ok := ip.(string) portNum, err := params.GetInt("port")
if err != nil {
if !ok { return nil, fmt.Errorf("net.udp.multicast port error: %w", err)
return nil, errors.New("net.udp.multicast ip must be a string")
}
port, ok := params["port"]
if !ok {
return nil, errors.New("net.udp.multicast requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, errors.New("net.udp.multicast port must be a number")
} }
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ipString, uint16(portNum))) addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ipString, uint16(portNum)))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &UDPMulticast{config: moduleConfig, Addr: addr, logger: CreateLogger(moduleConfig)}, nil
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("net.udp.multicast unable to get router from context")
}
return &UDPMulticast{config: config, Addr: addr, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
}, },
}) })
} }
type UDPMulticast struct {
config config.ModuleConfig
conn *net.UDPConn
ctx context.Context
inputHandler common.InputHandler
Addr *net.UDPAddr
logger *slog.Logger
cancel context.CancelFunc
connMu sync.Mutex
}
func (um *UDPMulticast) Id() string { func (um *UDPMulticast) Id() string {
return um.config.Id return um.config.Id
} }
@@ -72,7 +77,12 @@ func (um *UDPMulticast) Type() string {
return um.config.Type return um.config.Type
} }
func (um *UDPMulticast) Run() error { func (um *UDPMulticast) Start(ctx context.Context, inputHandler common.InputHandler) error {
um.logger.Debug("running")
um.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
um.ctx = moduleContext
um.cancel = cancel
client, err := net.ListenMulticastUDP("udp", nil, um.Addr) client, err := net.ListenMulticastUDP("udp", nil, um.Addr)
if err != nil { if err != nil {
@@ -80,22 +90,22 @@ func (um *UDPMulticast) Run() error {
} }
defer client.Close() defer client.Close()
um.connMu.Lock()
um.conn = client um.conn = client
um.connMu.Unlock()
buffer := make([]byte, 2048) buffer := make([]byte, 2048)
for { for um.ctx.Err() == nil {
select {
case <-um.ctx.Done():
// TODO(jwetzell): cleanup?
um.logger.Debug("done")
return nil
default:
um.conn.SetDeadline(time.Now().Add(time.Millisecond * 200)) um.conn.SetDeadline(time.Now().Add(time.Millisecond * 200))
numBytes, _, err := um.conn.ReadFromUDP(buffer) numBytes, _, err := um.conn.ReadFromUDP(buffer)
if err != nil { if err != nil {
if errors.Is(err, net.ErrClosed) {
break
}
//NOTE(jwetzell) we hit deadline //NOTE(jwetzell) we hit deadline
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { opErr, ok := err.(*net.OpError)
if ok && opErr.Timeout() {
continue continue
} }
return err return err
@@ -104,19 +114,21 @@ func (um *UDPMulticast) Run() error {
if numBytes > 0 { if numBytes > 0 {
message := buffer[:numBytes] message := buffer[:numBytes]
if um.router != nil { if um.inputHandler != nil {
um.router.HandleInput(um.ctx, um.Id(), message) um.inputHandler(um.ctx, um.Id(), message)
} else { } else {
um.logger.Error("input received but no router is configured") um.logger.Error("input received but no input handler is configured")
}
} }
} }
} }
<-um.ctx.Done()
um.logger.Debug("done")
return nil
} }
func (um *UDPMulticast) Output(ctx context.Context, payload any) error { func (um *UDPMulticast) Output(ctx context.Context, payload any) error {
payloadBytes, ok := payload.([]byte) payloadBytes, ok := common.GetAnyAsByteSlice(payload)
if !ok { if !ok {
return errors.New("net.udp.multicast can only output bytes") return errors.New("net.udp.multicast can only output bytes")
} }
@@ -128,3 +140,14 @@ func (um *UDPMulticast) Output(ctx context.Context, payload any) error {
_, err := um.conn.Write(payloadBytes) _, err := um.conn.Write(payloadBytes)
return err return err
} }
func (um *UDPMulticast) Stop() {
if um.cancel != nil {
defer um.cancel()
}
um.connMu.Lock()
defer um.connMu.Unlock()
if um.conn != nil {
um.conn.Close()
}
}
+105 -77
View File
@@ -2,80 +2,95 @@ package module
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"log"
"log/slog" "log/slog"
"net" "net"
"sync"
"time" "time"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
) )
func init() {
RegisterModule(ModuleRegistration{
Type: "net.udp.server",
Title: "UDP Server",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"ip": {
Title: "IP",
Description: "the IP address to bind the UDP server to",
Type: "string",
Default: json.RawMessage(`"0.0.0.0"`),
},
"port": {
Title: "Port",
Description: "the port for the UDP server to listen on",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1024),
Maximum: jsonschema.Ptr[float64](65535),
},
"bufferSize": {
Title: "Buffer Size",
Description: "the size of the buffer for incoming UDP messages",
Type: "integer",
Minimum: jsonschema.Ptr[float64](1),
Maximum: jsonschema.Ptr[float64](65535),
Default: json.RawMessage("2048"),
},
},
Required: []string{"port"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(moduleConfig config.ModuleConfig) (common.Module, error) {
params := moduleConfig.Params
portNum, err := params.GetInt("port")
if err != nil {
return nil, fmt.Errorf("net.udp.server port error: %w", err)
}
ipString, err := params.GetString("ip")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
ipString = "0.0.0.0"
} else {
return nil, fmt.Errorf("net.udp.server ip error: %w", err)
}
}
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ipString, uint16(portNum)))
if err != nil {
return nil, err
}
bufferSizeNum, err := params.GetInt("bufferSize")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
bufferSizeNum = 2048
} else {
return nil, fmt.Errorf("net.udp.server bufferSize error: %w", err)
}
}
return &UDPServer{Addr: addr, BufferSize: bufferSizeNum, config: moduleConfig, logger: CreateLogger(moduleConfig)}, nil
},
})
}
type UDPServer struct { type UDPServer struct {
Addr *net.UDPAddr Addr *net.UDPAddr
BufferSize int BufferSize int
config config.ModuleConfig config config.ModuleConfig
ctx context.Context ctx context.Context
router route.RouteIO inputHandler common.InputHandler
logger *slog.Logger logger *slog.Logger
} cancel context.CancelFunc
listener *net.UDPConn
func init() { listenerMu sync.Mutex
RegisterModule(ModuleRegistration{
Type: "net.udp.server",
New: func(ctx context.Context, config config.ModuleConfig) (Module, error) {
params := config.Params
port, ok := params["port"]
if !ok {
return nil, errors.New("net.udp.server requires a port parameter")
}
portNum, ok := port.(float64)
if !ok {
return nil, errors.New("net.udp.server port must be a number")
}
ipString := "0.0.0.0"
ip, ok := params["ip"]
if ok {
specificIpString, ok := ip.(string)
if !ok {
return nil, errors.New("net.udp.server ip must be a string")
}
ipString = specificIpString
}
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ipString, uint16(portNum)))
if err != nil {
log.Fatalf("error resolving UDP address: %v", err)
}
bufferSizeNum := 2048
bufferSize, ok := params["bufferSize"]
if ok {
bufferSizeFloat, ok := bufferSize.(float64)
if !ok {
return nil, errors.New("net.udp.server bufferSize must be a number")
}
bufferSizeNum = int(bufferSizeFloat)
}
router, ok := ctx.Value(route.RouterContextKey).(route.RouteIO)
if !ok {
return nil, errors.New("net.udp.server unable to get router from context")
}
return &UDPServer{Addr: addr, BufferSize: bufferSizeNum, config: config, ctx: ctx, router: router, logger: CreateLogger(config)}, nil
},
})
} }
func (us *UDPServer) Id() string { func (us *UDPServer) Id() string {
@@ -83,47 +98,60 @@ func (us *UDPServer) Id() string {
} }
func (us *UDPServer) Type() string { func (us *UDPServer) Type() string {
return us.config.Id return us.config.Type
} }
func (us *UDPServer) Run() error { func (us *UDPServer) Start(ctx context.Context, inputHandler common.InputHandler) error {
us.logger.Debug("running")
us.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
us.ctx = moduleContext
us.cancel = cancel
listener, err := net.ListenUDP("udp", us.Addr) listener, err := net.ListenUDP("udp", us.Addr)
if err != nil { if err != nil {
return err return err
} }
us.listenerMu.Lock()
defer listener.Close() us.listener = listener
us.listenerMu.Unlock()
buffer := make([]byte, us.BufferSize) buffer := make([]byte, us.BufferSize)
for { for us.ctx.Err() == nil {
select {
case <-us.ctx.Done():
// TODO(jwetzell): cleanup?
us.logger.Debug("done")
return nil
default:
listener.SetDeadline(time.Now().Add(time.Millisecond * 200)) listener.SetDeadline(time.Now().Add(time.Millisecond * 200))
numBytes, _, err := listener.ReadFromUDP(buffer) numBytes, _, err := listener.ReadFromUDP(buffer)
if err != nil { if err != nil {
//NOTE(jwetzell) we hit deadline //NOTE(jwetzell) we hit deadline
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { opErr, ok := err.(*net.OpError)
if ok && opErr.Timeout() {
continue continue
} }
return err break
} }
message := buffer[:numBytes] message := buffer[:numBytes]
if us.router != nil { if us.inputHandler != nil {
us.router.HandleInput(us.ctx, us.Id(), message) us.inputHandler(us.ctx, us.Id(), message)
} else { } else {
us.logger.Error("input received but no router is configured") us.logger.Error("input received but no input handler is configured")
} }
} }
} <-us.ctx.Done()
us.logger.Debug("done")
return nil
} }
func (us *UDPServer) Output(ctx context.Context, payload any) error { func (us *UDPServer) Output(ctx context.Context, payload any) error {
return errors.New("net.udp.server output is not implemented") return errors.New("net.udp.server output is not implemented")
} }
func (us *UDPServer) Stop() {
if us.cancel != nil {
defer us.cancel()
}
us.listenerMu.Lock()
defer us.listenerMu.Unlock()
if us.listener != nil {
us.listener.Close()
}
}
+217
View File
@@ -0,0 +1,217 @@
package module
import (
"context"
"errors"
"fmt"
"log/slog"
"net/url"
"sync"
"time"
"github.com/google/jsonschema-go/jsonschema"
"github.com/gorilla/websocket"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
)
func init() {
RegisterModule(ModuleRegistration{
Type: "websocket.client",
Title: "WebSocket Client",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"url": {
Title: "URL",
Description: "the URL of the WebSocket server to connect",
Type: "string",
},
},
Required: []string{"url"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params
urlString, err := params.GetString("url")
if err != nil {
return nil, fmt.Errorf("websocket.client url error: %w", err)
}
parsedURL, err := url.Parse(urlString)
if err != nil {
return nil, fmt.Errorf("websocket.client url error: %w", err)
}
if parsedURL.Scheme != "ws" && parsedURL.Scheme != "wss" {
return nil, fmt.Errorf("websocket.client url error: scheme must be ws or wss")
}
return &WebSocketClient{URL: *parsedURL, config: config, logger: CreateLogger(config)}, nil
},
})
}
type WebSocketClient struct {
config config.ModuleConfig
URL url.URL
ctx context.Context
conn *websocket.Conn
inputHandler common.InputHandler
logger *slog.Logger
cancel context.CancelFunc
connMu sync.Mutex
}
func (wc *WebSocketClient) Id() string {
return wc.config.Id
}
func (wc *WebSocketClient) Type() string {
return wc.config.Type
}
func (wc *WebSocketClient) SetupConn() error {
wc.connMu.Lock()
defer wc.connMu.Unlock()
if wc.conn != nil {
wc.conn.Close()
}
conn, _, err := websocket.DefaultDialer.Dial(wc.URL.String(), nil)
if err != nil {
return fmt.Errorf("websocket.client dial error: %w", err)
}
conn.SetCloseHandler(func(code int, text string) error {
// NOTE(jwetzell): attempt to send close message back to server before closing connection
err := wc.conn.WriteControl(
websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""),
time.Now().Add(time.Minute),
)
wc.connMu.Lock()
defer wc.connMu.Unlock()
if wc.conn != nil {
wc.conn.Close()
}
return err
})
wc.conn = conn
return err
}
func (wc *WebSocketClient) Start(ctx context.Context, inputHandler common.InputHandler) error {
wc.logger.Debug("running")
wc.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
wc.ctx = moduleContext
wc.cancel = cancel
for wc.ctx.Err() == nil {
err := wc.SetupConn()
if err != nil {
wc.logger.Error("connection error", "error", err)
} else {
// NOTE(jwetzell): enter read loop until an error occurs
wc.readLoop()
}
// NOTE(jwetzell): if connection is lost or read error wait before trying again
time.Sleep(2 * time.Second)
}
<-wc.ctx.Done()
wc.logger.Debug("done")
return nil
}
func (wc *WebSocketClient) readLoop() {
for wc.ctx.Err() == nil {
if wc.conn == nil {
wc.logger.Error("websocket connection is not established")
return
}
// TODO(jwetzell): other ways to timeout?
wc.conn.SetReadDeadline(time.Now().Add(5 * time.Second))
messageType, message, err := wc.conn.ReadMessage()
if err != nil {
return
}
if wc.inputHandler != nil {
switch messageType {
case websocket.CloseMessage:
return
case websocket.PingMessage:
err := wc.conn.WriteMessage(websocket.PongMessage, nil)
if err != nil {
wc.logger.Error("websocket pong error", "error", err)
return
}
case websocket.TextMessage:
wc.inputHandler(wc.ctx, wc.Id(), string(message))
case websocket.BinaryMessage:
wc.inputHandler(wc.ctx, wc.Id(), message)
default:
wc.logger.Warn("unsupported message type received", "messageType", messageType)
}
} else {
wc.logger.Error("input received but no input handler is configured")
continue
}
}
}
func (wc *WebSocketClient) outputBytes(ctx context.Context, payload []byte) error {
if wc.conn == nil {
return errors.New("websocket.client client is not setup")
}
err := wc.conn.WriteMessage(websocket.BinaryMessage, payload)
if err != nil {
return err
}
return nil
}
func (wc *WebSocketClient) outputString(ctx context.Context, payload string) error {
if wc.conn == nil {
return errors.New("websocket.client client is not setup")
}
err := wc.conn.WriteMessage(websocket.TextMessage, []byte(payload))
if err != nil {
return err
}
return nil
}
func (wc *WebSocketClient) Output(ctx context.Context, payload any) error {
wc.connMu.Lock()
defer wc.connMu.Unlock()
payloadBytes, ok := common.GetAnyAsByteSlice(payload)
if ok {
return wc.outputBytes(ctx, payloadBytes)
} else {
payloadString, ok := common.GetAnyAs[string](payload)
if ok {
return wc.outputString(ctx, payloadString)
} else {
return errors.New("websocket.client payload must be string or []byte")
}
}
}
func (wc *WebSocketClient) Stop() {
if wc.cancel != nil {
defer wc.cancel()
}
wc.connMu.Lock()
defer wc.connMu.Unlock()
if wc.conn != nil {
err := wc.conn.WriteControl(websocket.CloseMessage, nil, time.Now().Add(time.Minute))
if err != nil {
wc.logger.Error("websocket close error", "error", err)
}
wc.conn.Close()
}
}
+31 -24
View File
@@ -5,38 +5,45 @@ import (
"fmt" "fmt"
"github.com/jwetzell/artnet-go" "github.com/jwetzell/artnet-go"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
type ArtNetPacketDecode struct {
config config.ProcessorConfig
}
func (apd *ArtNetPacketDecode) Process(ctx context.Context, payload any) (any, error) {
payloadBytes, ok := payload.([]byte)
if !ok {
return nil, fmt.Errorf("artnet.packet.decode processor only accepts a []byte")
}
payloadMessage, err := artnet.Decode(payloadBytes)
if err != nil {
return nil, err
}
return payloadMessage, nil
}
func (apd *ArtNetPacketDecode) Type() string {
return apd.config.Type
}
func init() { func init() {
RegisterProcessor(ProcessorRegistration{ RegisterProcessor(ProcessorRegistration{
Type: "artnet.packet.decode", Type: "artnet.packet.decode",
Title: "Decode ArtNet Packet",
New: func(config config.ProcessorConfig) (Processor, error) { New: func(config config.ProcessorConfig) (Processor, error) {
return &ArtNetPacketDecode{config: config}, nil return &ArtNetPacketDecode{config: config}, nil
}, },
}) })
} }
type ArtNetPacketDecode struct {
config config.ProcessorConfig
}
func (apd *ArtNetPacketDecode) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
payload := wrappedPayload.Payload
payloadBytes, ok := common.GetAnyAsByteSlice(payload)
if !ok {
wrappedPayload.End = true
return wrappedPayload, fmt.Errorf("artnet.packet.decode processor only accepts a []byte")
}
payloadMessage, err := artnet.Decode(payloadBytes)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
wrappedPayload.Payload = payloadMessage
return wrappedPayload, nil
}
func (apd *ArtNetPacketDecode) Type() string {
return apd.config.Type
}
+30 -23
View File
@@ -5,37 +5,44 @@ import (
"fmt" "fmt"
"github.com/jwetzell/artnet-go" "github.com/jwetzell/artnet-go"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
type ArtNetPacketEncode struct {
config config.ProcessorConfig
}
func (ape *ArtNetPacketEncode) Process(ctx context.Context, payload any) (any, error) {
payloadPacket, ok := payload.(artnet.ArtNetPacket)
if !ok {
return nil, fmt.Errorf("artnet.packet.encode processor only accepts an ArtNetPacket")
}
payloadBytes, err := payloadPacket.MarshalBinary()
if err != nil {
return nil, err
}
return payloadBytes, nil
}
func (ape *ArtNetPacketEncode) Type() string {
return ape.config.Type
}
func init() { func init() {
RegisterProcessor(ProcessorRegistration{ RegisterProcessor(ProcessorRegistration{
Type: "artnet.packet.encode", Type: "artnet.packet.encode",
Title: "Encode ArtNet Packet",
New: func(config config.ProcessorConfig) (Processor, error) { New: func(config config.ProcessorConfig) (Processor, error) {
return &ArtNetPacketEncode{config: config}, nil return &ArtNetPacketEncode{config: config}, nil
}, },
}) })
} }
type ArtNetPacketEncode struct {
config config.ProcessorConfig
}
func (ape *ArtNetPacketEncode) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
payload := wrappedPayload.Payload
payloadPacket, ok := common.GetAnyAs[artnet.ArtNetPacket](payload)
if !ok {
wrappedPayload.End = true
return wrappedPayload, fmt.Errorf("artnet.packet.encode processor only accepts an ArtNetPacket")
}
payloadBytes, err := payloadPacket.MarshalBinary()
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
wrappedPayload.Payload = payloadBytes
return wrappedPayload, nil
}
func (ape *ArtNetPacketEncode) Type() string {
return ape.config.Type
}
@@ -1,52 +0,0 @@
package processor
import (
"context"
"fmt"
"github.com/jwetzell/artnet-go"
"github.com/jwetzell/showbridge-go/internal/config"
)
type ArtNetPacketFilter struct {
config config.ProcessorConfig
OpCode uint16
}
func (apf *ArtNetPacketFilter) Process(ctx context.Context, payload any) (any, error) {
payloadPacket, ok := payload.(artnet.ArtNetPacket)
if !ok {
return nil, fmt.Errorf("artnet.packet.filter processor only accepts an ArtNetPacket")
}
if payloadPacket.GetOpCode() != apf.OpCode {
return nil, nil
}
return payloadPacket, nil
}
func (apf *ArtNetPacketFilter) Type() string {
return apf.config.Type
}
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "artnet.packet.filter",
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
opCode, ok := params["opCode"]
if !ok {
return nil, fmt.Errorf("artnet.packet.filter requires an opCode parameter")
}
opCodeNum, ok := opCode.(float64)
if !ok {
return nil, fmt.Errorf("artnet.packet.filter opCode must be a number")
}
return &ArtNetPacketFilter{config: config, OpCode: uint16(opCodeNum)}, nil
},
})
}
+148
View File
@@ -0,0 +1,148 @@
package processor
import (
"bytes"
"context"
"errors"
"fmt"
"log/slog"
"text/template"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
)
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "db.query",
Title: "Query Database",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"module": {
Title: "Module ID",
Description: "ID of the database module to query",
Type: "string",
},
"query": {
Title: "Query",
Description: "SQL query to execute",
Type: "string",
},
},
Required: []string{"module", "query"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
moduleIdString, err := params.GetString("module")
if err != nil {
return nil, fmt.Errorf("db.query module error: %w", err)
}
queryString, err := params.GetString("query")
if err != nil {
return nil, fmt.Errorf("db.query query error: %w", err)
}
queryTemplate, err := template.New("query").Parse(queryString)
if err != nil {
return nil, err
}
return &DbQuery{config: config, ModuleId: moduleIdString, Query: queryTemplate, logger: slog.Default().With("component", "processor", "type", config.Type)}, nil
},
})
}
type DbQuery struct {
config config.ProcessorConfig
ModuleId string
Query *template.Template
logger *slog.Logger
module common.DatabaseModule
}
func (dq *DbQuery) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
if dq.module == nil {
if wrappedPayload.Modules == nil {
wrappedPayload.End = true
return wrappedPayload, errors.New("db.query wrapped payload has no modules")
}
module, ok := wrappedPayload.Modules[dq.ModuleId]
if !ok {
wrappedPayload.End = true
return wrappedPayload, fmt.Errorf("db.query unable to find module with id: %s", dq.ModuleId)
}
dbModule, ok := module.(common.DatabaseModule)
if !ok {
wrappedPayload.End = true
return wrappedPayload, fmt.Errorf("db.query module with id %s is not a DatabaseModule", dq.ModuleId)
}
dq.module = dbModule
}
var queryBuffer bytes.Buffer
err := dq.Query.Execute(&queryBuffer, wrappedPayload)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
// support proper parameterized queries
rows, err := dq.module.QueryContext(ctx, queryBuffer.String())
if err != nil {
wrappedPayload.End = true
return wrappedPayload, fmt.Errorf("db.query error executing query: %w", err)
}
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
wrappedPayload.End = true
return wrappedPayload, fmt.Errorf("db.query error getting columns: %w", err)
}
// TODO(jwetzell): optimize this
results := make([]map[string]any, 0)
for rows.Next() {
columnValues := make([]any, len(columns))
for i := range columnValues {
columnValues[i] = new(any)
}
if err := rows.Scan(columnValues...); err != nil {
wrappedPayload.End = true
return wrappedPayload, fmt.Errorf("db.query error scanning row: %w", err)
}
rowMap := make(map[string]any)
for i, colName := range columns {
value := *columnValues[i].(*any)
rowMap[colName] = value
}
results = append(results, rowMap)
}
if len(results) == 0 {
wrappedPayload.Payload = nil
return wrappedPayload, nil
} else if len(results) == 1 {
wrappedPayload.Payload = results[0]
return wrappedPayload, nil
}
wrappedPayload.Payload = results
return wrappedPayload, nil
}
func (dq *DbQuery) Type() string {
return dq.config.Type
}
+18 -11
View File
@@ -5,28 +5,35 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "debug.log",
Title: "Debug Log",
New: func(config config.ProcessorConfig) (Processor, error) {
return &DebugLog{config: config, logger: slog.Default().With("component", "processor", "type", config.Type)}, nil
},
})
}
type DebugLog struct { type DebugLog struct {
config config.ProcessorConfig config config.ProcessorConfig
logger *slog.Logger logger *slog.Logger
} }
func (dl *DebugLog) Process(ctx context.Context, payload any) (any, error) { // TODO(jwetzell): maybe make a more useful logging processor
dl.logger.Debug("", "payload", payload, "payloadType", fmt.Sprintf("%T", payload)) func (dl *DebugLog) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
return payload, nil payload := wrappedPayload.Payload
payloadString := fmt.Sprintf("%+v", payload)
payloadType := fmt.Sprintf("%T", payload)
dl.logger.Debug("", "payload", payloadString, "payloadType", payloadType)
return wrappedPayload, nil
} }
func (dl *DebugLog) Type() string { func (dl *DebugLog) Type() string {
return dl.config.Type return dl.config.Type
} }
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "debug.log",
New: func(config config.ProcessorConfig) (Processor, error) {
return &DebugLog{config: config, logger: slog.Default().With("component", "processor", "type", config.Type)}, nil
},
})
}
-39
View File
@@ -1,39 +0,0 @@
package processor_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestDebugLogFromRegistry(t *testing.T) {
registration, ok := processor.ProcessorRegistry["debug.log"]
if !ok {
t.Fatalf("debug.log processor not registered")
}
processorInstance, err := registration.New(config.ProcessorConfig{
Type: "debug.log",
})
if err != nil {
t.Fatalf("failed to create debug.log processor: %s", err)
}
if processorInstance.Type() != "debug.log" {
t.Fatalf("debug.log processor has wrong type: %s", processorInstance.Type())
}
payload := "test"
expected := "test"
got, err := processorInstance.Process(t.Context(), payload)
if err != nil {
t.Fatalf("debug.log processing failed: %s", err)
}
if got != expected {
t.Fatalf("debug.log got %+v, expected %+v", got, expected)
}
}
+40
View File
@@ -0,0 +1,40 @@
package processor
import (
"context"
"reflect"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
)
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "filter.change",
Title: "Filter On Change",
New: func(config config.ProcessorConfig) (Processor, error) {
return &FilterChange{config: config}, nil
},
})
}
type FilterChange struct {
config config.ProcessorConfig
previous any
}
func (fc *FilterChange) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
payload := wrappedPayload.Payload
if reflect.DeepEqual(payload, fc.previous) {
wrappedPayload.End = true
return wrappedPayload, nil
}
fc.previous = payload
return wrappedPayload, nil
}
func (fc *FilterChange) Type() string {
return fc.config.Type
}
+81
View File
@@ -0,0 +1,81 @@
package processor
import (
"context"
"fmt"
"github.com/expr-lang/expr"
"github.com/expr-lang/expr/vm"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
)
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "filter.expr",
Title: "Filter by Expr expression",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"expression": {
Title: "Expression",
Description: "Expr expression to evaluate, must return a boolean",
Type: "string",
},
},
Required: []string{"expression"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
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
},
})
}
// NOTE(jwetzell): see language definition https://expr-lang.org/docs/language-definition
type FilterExpr struct {
config config.ProcessorConfig
Program *vm.Program
}
func (fe *FilterExpr) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
exprEnv := wrappedPayload
output, err := expr.Run(fe.Program, exprEnv)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
outputBool, ok := output.(bool)
if !ok {
wrappedPayload.End = true
return wrappedPayload, fmt.Errorf("filter.expr expression did not return a boolean")
}
if !outputBool {
wrappedPayload.End = true
return wrappedPayload, nil
}
wrappedPayload.Payload = exprEnv.Payload
return wrappedPayload, nil
}
func (fe *FilterExpr) Type() string {
return fe.config.Type
}
+58
View File
@@ -0,0 +1,58 @@
package processor
import (
"context"
"fmt"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"golang.org/x/time/rate"
)
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "filter.rate",
Title: "Filter by Rate",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"rate": {
Title: "Rate",
Description: "number of events to allow per second",
Type: "integer",
},
},
Required: []string{"rate"},
},
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
rateInt, err := params.GetInt("rate")
if err != nil {
return nil, fmt.Errorf("filter.rate rate error: %w", err)
}
limiter := rate.NewLimiter(rate.Limit(rateInt), rateInt*2)
return &FilterRate{config: config, limiter: limiter}, nil
},
})
}
type FilterRate struct {
config config.ProcessorConfig
limiter *rate.Limiter
}
func (fc *FilterRate) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
err := fc.limiter.Wait(ctx)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
return wrappedPayload, nil
}
func (fc *FilterRate) Type() string {
return fc.config.Type
}
+74
View File
@@ -0,0 +1,74 @@
package processor
import (
"context"
"errors"
"fmt"
"regexp"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
)
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "filter.regex",
Title: "Filter by Regex",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"pattern": {
Title: "Pattern",
Description: "regex pattern to match against",
Type: "string",
},
},
Required: []string{"pattern"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
patternString, err := params.GetString("pattern")
if err != nil {
return nil, fmt.Errorf("filter.regex pattern error: %w", err)
}
patternRegexp, err := regexp.Compile(patternString)
if err != nil {
return nil, err
}
return &FilterRegex{config: config, Pattern: patternRegexp}, nil
},
})
}
type FilterRegex struct {
config config.ProcessorConfig
Pattern *regexp.Regexp
}
func (fr *FilterRegex) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
payload := wrappedPayload.Payload
payloadString, ok := common.GetAnyAs[string](payload)
if !ok {
wrappedPayload.End = true
return wrappedPayload, errors.New("filter.regex processor only accepts a string")
}
if !fr.Pattern.MatchString(payloadString) {
wrappedPayload.End = true
return wrappedPayload, nil
}
wrappedPayload.Payload = payloadString
return wrappedPayload, nil
}
func (fr *FilterRegex) Type() string {
return fr.config.Type
}
+46 -26
View File
@@ -2,52 +2,72 @@ package processor
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt"
"strconv" "strconv"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "float.parse",
Title: "Parse Float",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"bitSize": {
Title: "Bit Size",
Description: "bit size of the resulting float",
Type: "integer",
Enum: []any{32, 64},
Default: json.RawMessage("64"),
},
},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(moduleConfig config.ProcessorConfig) (Processor, error) {
params := moduleConfig.Params
bitSizeNum, err := params.GetInt("bitSize")
if err != nil {
if errors.Is(err, config.ErrParamNotFound) {
bitSizeNum = 64
} else {
return nil, fmt.Errorf("float.parse bitSize error: %w", err)
}
}
return &FloatParse{config: moduleConfig, BitSize: bitSizeNum}, nil
},
})
}
type FloatParse struct { type FloatParse struct {
BitSize int BitSize int
config config.ProcessorConfig config config.ProcessorConfig
} }
func (fp *FloatParse) Process(ctx context.Context, payload any) (any, error) { func (fp *FloatParse) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
payloadString, ok := payload.(string) payload := wrappedPayload.Payload
payloadString, ok := common.GetAnyAs[string](payload)
if !ok { if !ok {
return nil, errors.New("float.parse processor only accepts a string") wrappedPayload.End = true
return wrappedPayload, errors.New("float.parse processor only accepts a string")
} }
payloadFloat, err := strconv.ParseFloat(payloadString, fp.BitSize) payloadFloat, err := strconv.ParseFloat(payloadString, fp.BitSize)
if err != nil { if err != nil {
return nil, err wrappedPayload.End = true
return wrappedPayload, err
} }
return payloadFloat, nil wrappedPayload.Payload = payloadFloat
return wrappedPayload, nil
} }
func (fp *FloatParse) Type() string { func (fp *FloatParse) Type() string {
return fp.config.Type return fp.config.Type
} }
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "float.parse",
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
bitSizeNum := 64
bitSize, ok := params["bitSize"]
if ok {
bitSizeFloat, ok := bitSize.(float64)
if !ok {
return nil, errors.New("float.parse bitSize must be a number")
}
bitSizeNum = int(bitSizeFloat)
}
return &FloatParse{config: config, BitSize: bitSizeNum}, nil
},
})
}
-173
View File
@@ -1,173 +0,0 @@
package processor_test
import (
"testing"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/processor"
)
func TestFloatParseFromRegistry(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",
})
if err != nil {
t.Fatalf("failed to create float.parse processor: %s", err)
}
if processorInstance.Type() != "float.parse" {
t.Fatalf("float.parse processor has wrong type: %s", processorInstance.Type())
}
}
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) {
floatParser := processor.FloatParse{}
tests := []struct {
processor processor.Processor
name string
payload any
bitSize int
expected float64
}{
{
name: "positive number",
payload: "12345.67",
bitSize: 64,
expected: 12345.67,
},
{
name: "negative number",
payload: "-12345.67",
bitSize: 64,
expected: -12345.67,
},
{
name: "zero",
payload: "0",
bitSize: 64,
expected: 0,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := floatParser.Process(t.Context(), test.payload)
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)
}
})
}
}
func TestBadFloatParse(t *testing.T) {
tests := []struct {
processor processor.Processor
name string
payload any
bitSize int
errorString string
}{
{
name: "non-string input",
payload: []byte{0x01},
bitSize: 64,
errorString: "float.parse processor only accepts a string",
},
{
name: "not float string",
payload: "abcd",
bitSize: 64,
errorString: "strconv.ParseFloat: parsing \"abcd\": invalid syntax",
},
{
name: "bit size overflow",
payload: "1.79e+64",
bitSize: 32,
errorString: "strconv.ParseFloat: parsing \"1.79e+64\": value out of range",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
floatParser := processor.FloatParse{
BitSize: test.bitSize,
}
got, err := floatParser.Process(t.Context(), test.payload)
if err == nil {
t.Fatalf("float.parse expected to fail but succeeded, got: %v", got)
}
if err.Error() != test.errorString {
t.Fatalf("float.parse got error '%s', expected '%s'", err.Error(), test.errorString)
}
})
}
}
+102
View File
@@ -0,0 +1,102 @@
package processor
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/rand/v2"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
)
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "float.random",
Title: "Random Float",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"bitSize": {
Title: "Bit Size",
Description: "bit size of the resulting float",
Type: "integer",
Enum: []any{32, 64},
Default: json.RawMessage("32"),
},
"min": {
Title: "Minimum",
Description: "inclusive minimum value of the random float",
Type: "number",
},
"max": {
Title: "Maximum",
Description: "exclusive maximum value of the random float",
Type: "number",
},
},
Required: []string{"min", "max"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
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
},
})
}
type FloatRandom struct {
BitSize int
Min float64
Max float64
config config.ProcessorConfig
}
func (fr *FloatRandom) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
if fr.BitSize == 32 {
payloadFloat := rand.Float32()*(float32(fr.Max)-float32(fr.Min)) + float32(fr.Min)
wrappedPayload.Payload = payloadFloat
return wrappedPayload, nil
}
if fr.BitSize == 64 {
payloadFloat := rand.Float64()*(fr.Max-fr.Min) + fr.Min
wrappedPayload.Payload = payloadFloat
return wrappedPayload, nil
}
wrappedPayload.End = true
return wrappedPayload, errors.New("float.random bitSize error: must be 32 or 64")
}
func (fr *FloatRandom) Type() string {
return fr.config.Type
}
+293 -254
View File
@@ -3,200 +3,89 @@ package processor
import ( import (
"bytes" "bytes"
"context" "context"
"errors" "fmt"
"strconv" "strconv"
"text/template" "text/template"
"github.com/google/jsonschema-go/jsonschema"
freeD "github.com/jwetzell/free-d-go" freeD "github.com/jwetzell/free-d-go"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config" "github.com/jwetzell/showbridge-go/internal/config"
) )
type FreeDCreate struct {
config config.ProcessorConfig
Id *template.Template
Pan *template.Template
Tilt *template.Template
Roll *template.Template
PosX *template.Template
PosY *template.Template
PosZ *template.Template
Zoom *template.Template
Focus *template.Template
}
func (fc *FreeDCreate) Process(ctx context.Context, payload any) (any, error) {
var idBuffer bytes.Buffer
err := fc.Id.Execute(&idBuffer, payload)
if err != nil {
return nil, err
}
idString := idBuffer.String()
idNum, err := strconv.ParseUint(idString, 10, 8)
if err != nil {
return nil, err
}
var panBuffer bytes.Buffer
err = fc.Pan.Execute(&panBuffer, payload)
if err != nil {
return nil, err
}
panString := panBuffer.String()
panNum, err := strconv.ParseFloat(panString, 32)
if err != nil {
return nil, err
}
var tiltBuffer bytes.Buffer
err = fc.Tilt.Execute(&tiltBuffer, payload)
if err != nil {
return nil, err
}
tiltString := tiltBuffer.String()
tiltNum, err := strconv.ParseFloat(tiltString, 32)
if err != nil {
return nil, err
}
var rollBuffer bytes.Buffer
err = fc.Tilt.Execute(&rollBuffer, payload)
if err != nil {
return nil, err
}
rollString := rollBuffer.String()
rollNum, err := strconv.ParseFloat(rollString, 32)
if err != nil {
return nil, err
}
var posXBuffer bytes.Buffer
err = fc.PosX.Execute(&posXBuffer, payload)
if err != nil {
return nil, err
}
posXString := posXBuffer.String()
posXNum, err := strconv.ParseFloat(posXString, 32)
if err != nil {
return nil, err
}
var posYBuffer bytes.Buffer
err = fc.PosY.Execute(&posYBuffer, payload)
if err != nil {
return nil, err
}
posYString := posYBuffer.String()
posYNum, err := strconv.ParseFloat(posYString, 32)
if err != nil {
return nil, err
}
var posZBuffer bytes.Buffer
err = fc.PosZ.Execute(&posZBuffer, payload)
if err != nil {
return nil, err
}
posZString := posZBuffer.String()
posZNum, err := strconv.ParseFloat(posZString, 32)
if err != nil {
return nil, err
}
var zoomBuffer bytes.Buffer
err = fc.Zoom.Execute(&zoomBuffer, payload)
if err != nil {
return nil, err
}
zoomString := zoomBuffer.String()
zoomNum, err := strconv.ParseInt(zoomString, 10, 32)
if err != nil {
return nil, err
}
var focusBuffer bytes.Buffer
err = fc.Zoom.Execute(&focusBuffer, payload)
if err != nil {
return nil, err
}
focusString := focusBuffer.String()
focusNum, err := strconv.ParseInt(focusString, 10, 32)
if err != nil {
return nil, err
}
payloadMessage := freeD.FreeDPosition{
ID: uint8(idNum),
Pan: float32(panNum),
Tilt: float32(tiltNum),
Roll: float32(rollNum),
PosX: float32(posXNum),
PosY: float32(posYNum),
PosZ: float32(posZNum),
Zoom: int32(zoomNum),
Focus: int32(focusNum),
}
return payloadMessage, nil
}
func (fc *FreeDCreate) Type() string {
return fc.config.Type
}
func init() { func init() {
RegisterProcessor(ProcessorRegistration{ RegisterProcessor(ProcessorRegistration{
Type: "freed.create", Type: "freed.create",
Title: "Create FreeD",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"id": {
Title: "Camera ID",
Description: "ID of the camera this FreeD position corresponds to",
Type: "string",
},
"pan": {
Title: "Pan",
Description: "pan angle of the camera",
Type: "string",
},
"tilt": {
Title: "Tilt",
Description: "tilt angle of the camera",
Type: "string",
},
"roll": {
Title: "Roll",
Description: "roll angle of the camera",
Type: "string",
},
"posX": {
Title: "Position X",
Description: "X coordinate of the camera's position",
Type: "string",
},
"posY": {
Title: "Position Y",
Description: "Y coordinate of the camera's position",
Type: "string",
},
"posZ": {
Title: "Position Z",
Description: "Z coordinate of the camera's position",
Type: "string",
},
"zoom": {
Title: "Zoom",
Description: "zoom level of the camera",
Type: "string",
},
"focus": {
Title: "Focus",
Description: "focus level of the camera",
Type: "string",
},
},
Required: []string{
"id",
"pan",
"tilt",
"roll",
"posX",
"posY",
"posZ",
"zoom",
"focus",
},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ProcessorConfig) (Processor, error) { New: func(config config.ProcessorConfig) (Processor, error) {
// TODO(jwetzell): make some params optional // TODO(jwetzell): make some params optional
params := config.Params params := config.Params
id, ok := params["id"] idString, err := params.GetString("id")
if err != nil {
if !ok { return nil, fmt.Errorf("freed.create id error: %w", err)
return nil, errors.New("freed.create requires an id parameter")
}
idString, ok := id.(string)
if !ok {
return nil, errors.New("freed.create id must be a string")
} }
idTemplate, err := template.New("id").Parse(idString) idTemplate, err := template.New("id").Parse(idString)
@@ -205,44 +94,31 @@ func init() {
return nil, err return nil, err
} }
pan, ok := params["pan"] panString, err := params.GetString("pan")
if err != nil {
if !ok { return nil, fmt.Errorf("freed.create pan error: %w", err)
return nil, errors.New("freed.create requires a pan parameter")
}
panString, ok := pan.(string)
if !ok {
return nil, errors.New("freed.create pan must be a string")
} }
panTemplate, err := template.New("pan").Parse(panString) panTemplate, err := template.New("pan").Parse(panString)
tilt, ok := params["tilt"] if err != nil {
return nil, err
if !ok {
return nil, errors.New("freed.create requires a tilt parameter")
} }
tiltString, ok := tilt.(string) tiltString, err := params.GetString("tilt")
if err != nil {
if !ok { return nil, fmt.Errorf("freed.create tilt error: %w", err)
return nil, errors.New("freed.create tilt must be a string")
} }
tiltTemplate, err := template.New("tilt").Parse(tiltString) tiltTemplate, err := template.New("tilt").Parse(tiltString)
roll, ok := params["roll"] if err != nil {
return nil, err
if !ok {
return nil, errors.New("freed.create requires a roll parameter")
} }
rollString, ok := roll.(string) rollString, err := params.GetString("roll")
if err != nil {
if !ok { return nil, fmt.Errorf("freed.create roll error: %w", err)
return nil, errors.New("freed.create roll must be a string")
} }
rollTemplate, err := template.New("roll").Parse(rollString) rollTemplate, err := template.New("roll").Parse(rollString)
@@ -251,16 +127,9 @@ func init() {
return nil, err return nil, err
} }
posX, ok := params["posX"] posXString, err := params.GetString("posX")
if err != nil {
if !ok { return nil, fmt.Errorf("freed.create posX error: %w", err)
return nil, errors.New("freed.create requires a posX parameter")
}
posXString, ok := posX.(string)
if !ok {
return nil, errors.New("freed.create posX must be a string")
} }
posXTemplate, err := template.New("posX").Parse(posXString) posXTemplate, err := template.New("posX").Parse(posXString)
@@ -269,16 +138,9 @@ func init() {
return nil, err return nil, err
} }
posY, ok := params["posY"] posYString, err := params.GetString("posY")
if err != nil {
if !ok { return nil, fmt.Errorf("freed.create posY error: %w", err)
return nil, errors.New("freed.create requires a posY parameter")
}
posYString, ok := posY.(string)
if !ok {
return nil, errors.New("freed.create posY must be a string")
} }
posYTemplate, err := template.New("posY").Parse(posYString) posYTemplate, err := template.New("posY").Parse(posYString)
@@ -287,16 +149,9 @@ func init() {
return nil, err return nil, err
} }
posZ, ok := params["posZ"] posZString, err := params.GetString("posZ")
if err != nil {
if !ok { return nil, fmt.Errorf("freed.create posZ error: %w", err)
return nil, errors.New("freed.create requires a posZ parameter")
}
posZString, ok := posZ.(string)
if !ok {
return nil, errors.New("freed.create posZ must be a string")
} }
posZTemplate, err := template.New("posZ").Parse(posZString) posZTemplate, err := template.New("posZ").Parse(posZString)
@@ -305,33 +160,26 @@ func init() {
return nil, err return nil, err
} }
zoom, ok := params["zoom"] zoomString, err := params.GetString("zoom")
if err != nil {
if !ok { return nil, fmt.Errorf("freed.create zoom error: %w", err)
return nil, errors.New("freed.create requires a zoom parameter")
}
zoomString, ok := zoom.(string)
if !ok {
return nil, errors.New("freed.create zoom must be a string")
} }
zoomTemplate, err := template.New("zoom").Parse(zoomString) zoomTemplate, err := template.New("zoom").Parse(zoomString)
focus, ok := params["focus"] if err != nil {
return nil, err
if !ok {
return nil, errors.New("freed.create requires a focus parameter")
} }
focusString, ok := focus.(string) focusString, err := params.GetString("focus")
if err != nil {
if !ok { return nil, fmt.Errorf("freed.create focus error: %w", err)
return nil, errors.New("freed.create focus must be a string")
} }
focusTemplate, err := template.New("focus").Parse(focusString) focusTemplate, err := template.New("focus").Parse(focusString)
if err != nil {
return nil, err
}
return &FreeDCreate{ return &FreeDCreate{
config: config, config: config,
@@ -348,3 +196,194 @@ func init() {
}, },
}) })
} }
type FreeDCreate struct {
config config.ProcessorConfig
Id *template.Template
Pan *template.Template
Tilt *template.Template
Roll *template.Template
PosX *template.Template
PosY *template.Template
PosZ *template.Template
Zoom *template.Template
Focus *template.Template
}
func (fc *FreeDCreate) Process(ctx context.Context, wrappedPayload common.WrappedPayload) (common.WrappedPayload, error) {
templateData := wrappedPayload
var idBuffer bytes.Buffer
err := fc.Id.Execute(&idBuffer, templateData)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
idString := idBuffer.String()
idNum, err := strconv.ParseUint(idString, 10, 8)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
var panBuffer bytes.Buffer
err = fc.Pan.Execute(&panBuffer, templateData)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
panString := panBuffer.String()
panNum, err := strconv.ParseFloat(panString, 32)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
var tiltBuffer bytes.Buffer
err = fc.Tilt.Execute(&tiltBuffer, templateData)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
tiltString := tiltBuffer.String()
tiltNum, err := strconv.ParseFloat(tiltString, 32)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
var rollBuffer bytes.Buffer
err = fc.Roll.Execute(&rollBuffer, templateData)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
rollString := rollBuffer.String()
rollNum, err := strconv.ParseFloat(rollString, 32)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
var posXBuffer bytes.Buffer
err = fc.PosX.Execute(&posXBuffer, templateData)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
posXString := posXBuffer.String()
posXNum, err := strconv.ParseFloat(posXString, 32)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
var posYBuffer bytes.Buffer
err = fc.PosY.Execute(&posYBuffer, templateData)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
posYString := posYBuffer.String()
posYNum, err := strconv.ParseFloat(posYString, 32)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
var posZBuffer bytes.Buffer
err = fc.PosZ.Execute(&posZBuffer, templateData)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
posZString := posZBuffer.String()
posZNum, err := strconv.ParseFloat(posZString, 32)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
var zoomBuffer bytes.Buffer
err = fc.Zoom.Execute(&zoomBuffer, templateData)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
zoomString := zoomBuffer.String()
zoomNum, err := strconv.ParseInt(zoomString, 10, 32)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
var focusBuffer bytes.Buffer
err = fc.Focus.Execute(&focusBuffer, templateData)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
focusString := focusBuffer.String()
focusNum, err := strconv.ParseInt(focusString, 10, 32)
if err != nil {
wrappedPayload.End = true
return wrappedPayload, err
}
payloadMessage := freeD.FreeDPosition{
ID: uint8(idNum),
Pan: float32(panNum),
Tilt: float32(tiltNum),
Roll: float32(rollNum),
PosX: float32(posXNum),
PosY: float32(posYNum),
PosZ: float32(posZNum),
Zoom: int32(zoomNum),
Focus: int32(focusNum),
}
wrappedPayload.Payload = payloadMessage
return wrappedPayload, nil
}
func (fc *FreeDCreate) Type() string {
return fc.config.Type
}

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