mq-bridge-py-basic 0.3.9__tar.gz → 0.3.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. mq_bridge_py_basic-0.3.10/.github/dependabot.yml +11 -0
  2. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/workflows/ci.yml +27 -0
  3. mq_bridge_py_basic-0.3.10/CHANGELOG.md +76 -0
  4. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/Cargo.lock +4 -4
  5. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/Cargo.toml +15 -6
  6. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/PKG-INFO +3 -3
  7. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/README.md +71 -2
  8. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/REFERENCE.md +61 -8
  9. mq_bridge_py_basic-0.3.10/SECURITY.md +104 -0
  10. mq_bridge_py_basic-0.3.10/deny.toml +97 -0
  11. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/mq-bridge.schema.json +50 -3
  12. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/mq_bridge/__init__.pyi +8 -0
  13. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/mq_bridge/config.pyi +9 -0
  14. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/README.md +2 -2
  15. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/examples/dlt_source.py +4 -8
  16. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/src/lib.rs +6 -0
  17. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/tests/test_public_api.py +10 -7
  18. mq_bridge_py_basic-0.3.10/src/canonical_message.rs +854 -0
  19. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/command_handler.rs +9 -1
  20. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/amqp.rs +20 -5
  21. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/file/mod.rs +493 -146
  22. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/file/tests.rs +268 -3
  23. mq_bridge_py_basic-0.3.10/src/endpoints/kafka.rs +1927 -0
  24. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/mod.rs +229 -27
  25. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/mongodb/readers.rs +81 -3
  26. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/mongodb/tests.rs +67 -0
  27. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/nats.rs +44 -17
  28. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/object_store.rs +233 -23
  29. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/postgres/mod.rs +56 -0
  30. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/sqlx/mod.rs +252 -116
  31. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/sqlx/tests.rs +262 -0
  32. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/errors.rs +66 -6
  33. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/cookie_jar.rs +73 -5
  34. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/deduplication.rs +518 -145
  35. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/limiter.rs +31 -1
  36. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/mod.rs +43 -3
  37. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/transform/compiled.rs +133 -15
  38. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/transform/mod.rs +7 -0
  39. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/transform/path.rs +26 -0
  40. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/transform/schema.rs +15 -1
  41. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/transform/tests.rs +333 -7
  42. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/models.rs +46 -2
  43. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/route.rs +729 -5
  44. mq_bridge_py_basic-0.3.10/src/support/base64_engine.rs +101 -0
  45. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/support/interpolation.rs +260 -17
  46. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/support/mod.rs +2 -0
  47. mq_bridge_py_basic-0.3.10/src/support/source_ranges.rs +448 -0
  48. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/test_utils.rs +30 -1
  49. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/armature_integration.rs +16 -0
  50. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/mqtt.rs +9 -3
  51. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/postgres_cdc.rs +1 -0
  52. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/route.rs +25 -23
  53. mq_bridge_py_basic-0.3.9/src/canonical_message.rs +0 -471
  54. mq_bridge_py_basic-0.3.9/src/endpoints/kafka.rs +0 -986
  55. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  56. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  57. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  58. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/workflows/benchmark.yml +0 -0
  59. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/workflows/ibm-mq.yml +0 -0
  60. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/workflows/node.yml +0 -0
  61. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/workflows/publish-node.yml +0 -0
  62. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/workflows/publish-python.yml +0 -0
  63. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/workflows/python.yml +0 -0
  64. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/workflows/release.yml +0 -0
  65. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.github/workflows/test-matrix.yml +0 -0
  66. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/.gitignore +0 -0
  67. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/ARCHITECTURE.md +0 -0
  68. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/CLAUDE.md +0 -0
  69. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/CONFIGURATION.md +0 -0
  70. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/CONTRIBUTING.md +0 -0
  71. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/LICENSE +0 -0
  72. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/LICENSE.python +0 -0
  73. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/benches/ETL_BENCHMARKS.md +0 -0
  74. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/benches/performance_bench.rs +0 -0
  75. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/benches/router_bench.rs +0 -0
  76. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/benches/zeromq_backends.rs +0 -0
  77. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/bindings-common/Cargo.toml +0 -0
  78. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/bindings-common/src/lib.rs +0 -0
  79. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/bindings-common/src/logging.rs +0 -0
  80. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/build.rs +0 -0
  81. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/examples/ipc_worker_queue.yaml +0 -0
  82. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/mq_bridge/__init__.py +0 -0
  83. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/mq_bridge/config.py +0 -0
  84. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/mq_bridge/py.typed +0 -0
  85. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/package-lock.json +0 -0
  86. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/pyproject.toml +0 -0
  87. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/Cargo.toml +0 -0
  88. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/LICENSE.python +0 -0
  89. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/analysis/bench_http_native.py +0 -0
  90. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/example.py +0 -0
  91. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/examples/bench_memory.py +0 -0
  92. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/examples/bench_memory.yaml +0 -0
  93. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/examples/consume_pull.py +0 -0
  94. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/examples/json_route.py +0 -0
  95. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/examples/memory.yaml +0 -0
  96. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/examples/raw_route.py +0 -0
  97. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/pyproject-basic.toml +0 -0
  98. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/pyproject-reduced.toml +0 -0
  99. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/scripts/gen_config_types.py +0 -0
  100. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/tests/test_config_types.py +0 -0
  101. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/tests/test_logging.py +0 -0
  102. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/tests/test_packaging.py +0 -0
  103. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/tests/test_performance_smoke.py +0 -0
  104. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/tests/test_routes.py +0 -0
  105. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/python/mq-bridge-py/uv.lock +0 -0
  106. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/analysis/benches/metadata_bench.rs +0 -0
  107. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/analysis/debug_ibm_mq_tls.rs +0 -0
  108. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/analysis/http/analyze_xctrace_time_profile.py +0 -0
  109. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/analysis/http/http_flamegraph.py +0 -0
  110. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/analysis/http/mq_bridge_http_profile.rs +0 -0
  111. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/analysis/http/run_http_ladder.sh +0 -0
  112. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/ci/format_bench_output.sh +0 -0
  113. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/httparena/README.md +0 -0
  114. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/httparena/frameworks/mq-bridge-py/Dockerfile +0 -0
  115. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/httparena/frameworks/mq-bridge-py/meta.json +0 -0
  116. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/httparena/frameworks/mq-bridge-py/server.py +0 -0
  117. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/techempower/Python/mq-bridge-py/README.md +0 -0
  118. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/techempower/Python/mq-bridge-py/benchmark_config.json +0 -0
  119. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/techempower/Python/mq-bridge-py/mq-bridge-py.dockerfile +0 -0
  120. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/techempower/Python/mq-bridge-py/server.py +0 -0
  121. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/techempower/README.md +0 -0
  122. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/techempower/postgres.yml +0 -0
  123. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/techempower/seed.sql +0 -0
  124. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/techempower/verify.sh +0 -0
  125. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/the-benchmarker/README.md +0 -0
  126. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/the-benchmarker/python/mq-bridge-py/config.yaml +0 -0
  127. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/the-benchmarker/python/mq-bridge-py/pyproject.toml +0 -0
  128. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/scripts/the-benchmarker/python/mq-bridge-py/server.py +0 -0
  129. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/checkpoint.rs +0 -0
  130. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/aws.rs +0 -0
  131. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/clickhouse.rs +0 -0
  132. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/grpc.proto +0 -0
  133. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/grpc.rs +0 -0
  134. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/http/mod.rs +0 -0
  135. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/http/publisher.rs +0 -0
  136. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/http/stream.rs +0 -0
  137. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/http/tests.rs +0 -0
  138. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/ibm_mq.rs +0 -0
  139. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/memory/endpoint.rs +0 -0
  140. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/memory/framed.rs +0 -0
  141. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/memory/ipc_unix.rs +0 -0
  142. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/memory/ipc_windows.rs +0 -0
  143. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/memory/memory_transport.rs +0 -0
  144. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/memory/mod.rs +0 -0
  145. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/memory/transport.rs +0 -0
  146. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/mongodb/consumer.rs +0 -0
  147. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/mongodb/dedup.rs +0 -0
  148. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/mongodb/mod.rs +0 -0
  149. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/mongodb/publisher.rs +0 -0
  150. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/mqtt.rs +0 -0
  151. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/poll.rs +0 -0
  152. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/postgres/VENDORED.md +0 -0
  153. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/postgres/pgoutput/decoder.rs +0 -0
  154. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/postgres/pgoutput/messages.rs +0 -0
  155. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/postgres/pgoutput/mod.rs +0 -0
  156. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/postgres/pgoutput/registry.rs +0 -0
  157. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/postgres/pgoutput/values.rs +0 -0
  158. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/postgres/replication.rs +0 -0
  159. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/postgres/state.rs +0 -0
  160. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/redis_streams.rs +0 -0
  161. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/sled.rs +0 -0
  162. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/sqlx/dedup.rs +0 -0
  163. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/structural/fanout.rs +0 -0
  164. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/structural/mod.rs +0 -0
  165. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/structural/null.rs +0 -0
  166. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/structural/reader.rs +0 -0
  167. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/structural/request.rs +0 -0
  168. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/structural/response.rs +0 -0
  169. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/structural/static_endpoint.rs +0 -0
  170. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/structural/stream_buffer.rs +0 -0
  171. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/structural/switch.rs +0 -0
  172. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/websocket.rs +0 -0
  173. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/zeromq/codec.rs +0 -0
  174. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/zeromq/mod.rs +0 -0
  175. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/zeromq/omq.rs +0 -0
  176. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/endpoints/zeromq/zmq.rs +0 -0
  177. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/event_handler.rs +0 -0
  178. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/event_store.rs +0 -0
  179. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/extensions.rs +0 -0
  180. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/lib.rs +0 -0
  181. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/buffer.rs +0 -0
  182. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/compression.rs +0 -0
  183. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/delay.rs +0 -0
  184. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/dlq.rs +0 -0
  185. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/encryption.rs +0 -0
  186. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/metrics.rs +0 -0
  187. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/random_panic.rs +0 -0
  188. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/retry.rs +0 -0
  189. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/transform/coerce.rs +0 -0
  190. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/transform/error.rs +0 -0
  191. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/middleware/weak_join.rs +0 -0
  192. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/outcomes.rs +0 -0
  193. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/publisher.rs +0 -0
  194. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/response.rs +0 -0
  195. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/support/compression.rs +0 -0
  196. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/support/connection_registry.rs +0 -0
  197. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/support/crypto.rs +0 -0
  198. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/support/crypto_envelope.rs +0 -0
  199. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/traits.rs +0 -0
  200. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/src/type_handler.rs +0 -0
  201. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/README.md +0 -0
  202. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/amqp.rs +0 -0
  203. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/aws.rs +0 -0
  204. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/clickhouse.rs +0 -0
  205. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/config.toml +0 -0
  206. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/amqp.yml +0 -0
  207. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/aws.yml +0 -0
  208. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/clickhouse.yml +0 -0
  209. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/ibm-mq-certs/client.crl +0 -0
  210. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/ibm-mq-certs/client.kdb +0 -0
  211. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/ibm-mq-certs/client.p12 +0 -0
  212. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/ibm-mq-certs/client.rdb +0 -0
  213. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/ibm-mq-certs/client.sth +0 -0
  214. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/ibm_mq.yml +0 -0
  215. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/ibm_mq_tls.yml +0 -0
  216. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/kafka-tls.yml +0 -0
  217. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/kafka.yml +0 -0
  218. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mariadb.yml +0 -0
  219. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mongodb-replica.yml +0 -0
  220. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mongodb-tls.yml +0 -0
  221. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mongodb.yml +0 -0
  222. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mosquitto.conf +0 -0
  223. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mosquitto_performance.conf +0 -0
  224. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mosquitto_persistence.conf +0 -0
  225. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mq_init.mqsc +0 -0
  226. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mq_init_tls.mqsc +0 -0
  227. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mqtt.yml +0 -0
  228. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mqtt_performance.yml +0 -0
  229. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/mysql.yml +0 -0
  230. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/nats.yml +0 -0
  231. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/object_store.yml +0 -0
  232. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/postgres.yml +0 -0
  233. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/postgres_cdc.yml +0 -0
  234. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/docker-compose/redis.yml +0 -0
  235. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/file.rs +0 -0
  236. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/grpc.rs +0 -0
  237. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/grpc_tls.rs +0 -0
  238. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/http.rs +0 -0
  239. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/http_tls.rs +0 -0
  240. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/ibm_mq.rs +0 -0
  241. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/ibm_mq_tls.rs +0 -0
  242. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/ipc.rs +0 -0
  243. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/kafka.rs +0 -0
  244. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/kafka_tls.rs +0 -0
  245. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/logic_test.rs +0 -0
  246. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/mariadb.rs +0 -0
  247. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/memory.rs +0 -0
  248. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/mod.rs +0 -0
  249. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/mongodb.rs +0 -0
  250. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/mongodb_raw.rs +0 -0
  251. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/mongodb_tls.rs +0 -0
  252. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/mysql.rs +0 -0
  253. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/nats.rs +0 -0
  254. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/object_store.rs +0 -0
  255. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/performance_static.rs +0 -0
  256. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/postgres.rs +0 -0
  257. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/redis_streams.rs +0 -0
  258. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/scripts/gen_certs.sh +0 -0
  259. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/sqlite.rs +0 -0
  260. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/tls_helpers.rs +0 -0
  261. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/websocket.rs +0 -0
  262. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration/zeromq.rs +0 -0
  263. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/integration_test.rs +0 -0
  264. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/memory_leak_test.rs +0 -0
  265. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/ref_test.rs +0 -0
  266. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/reference_docs_test.rs +0 -0
  267. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/sqlite_test.rs +0 -0
  268. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/tls_example.rs +0 -0
  269. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/unit_tests.md +0 -0
  270. {mq_bridge_py_basic-0.3.9 → mq_bridge_py_basic-0.3.10}/tests/websocket_test.rs +0 -0
@@ -0,0 +1,11 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "cargo" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
@@ -305,3 +305,30 @@ jobs:
305
305
 
306
306
  - name: Build documentation (deny warnings)
307
307
  run: RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-deps
308
+
309
+ # Supply chain: licenses, banned crates, source registries, RustSec advisories
310
+ supply-chain:
311
+ name: Supply Chain
312
+ runs-on: ubuntu-latest
313
+ permissions:
314
+ contents: read
315
+ steps:
316
+ - uses: actions/checkout@v4
317
+ with:
318
+ persist-credentials: false
319
+
320
+ # Hard gate: an unapproved license, a banned crate or a non-crates.io
321
+ # source is always a real, actionable problem in this repo.
322
+ - name: Licenses / bans / sources
323
+ uses: EmbarkStudios/cargo-deny-action@v2
324
+ with:
325
+ command: check bans licenses sources
326
+
327
+ # Informational: every current advisory is in a transitive dependency we
328
+ # cannot upgrade ourselves, and RustSec updates daily. SECURITY.md records
329
+ # the per-feature reachability analysis; deny.toml carries the reasoning.
330
+ - name: Advisories (informational)
331
+ continue-on-error: true
332
+ uses: EmbarkStudios/cargo-deny-action@v2
333
+ with:
334
+ command: check advisories
@@ -0,0 +1,76 @@
1
+ # Changelog
2
+
3
+ All notable changes to `mq-bridge`. Newest first.
4
+
5
+ ## 0.3.10
6
+
7
+
8
+ ### Changed
9
+
10
+ - DLQ and output middleware now run around the publish step, not inside the handler, so a
11
+ handler failure is no longer retried by the output chain. The tradeoff: a handler failure
12
+ now skips the output middleware entirely, so `dlq` on the output endpoint cannot capture
13
+ it — only publish failures reach the DLQ. Put a `dlq` on the input endpoint to catch
14
+ handler failures.
15
+ - Errors carry their full cause chain instead of only the outermost context.
16
+ - `deduplication` on an **output** endpoint is now a startup error instead of a warning and a
17
+ silent no-op. Move it to the route's input endpoint.
18
+ - `limiter` paces a single `send_batch` by its own message count, so one large batch is no
19
+ longer a free burst. Sustained throughput is unchanged.
20
+ - A `message_id` that is neither a UUID nor a `u128` is hashed to a stable id rather than
21
+ making the whole JSON envelope unparseable.
22
+
23
+ ### Performance
24
+
25
+ - **Kafka consumer**: a long-lived prefetch task reads librdkafka continuously into a bounded
26
+ channel, instead of rebuilding the stream inside every `receive_batch`. librdkafka only keeps
27
+ requesting records while its queue is drained, so every pause the pipeline took — a transform,
28
+ a slow sink — was also a pause in fetching, and the fetch rate collapsed to well under what the
29
+ broker could serve. Batch offsets are also recorded once per partition rather than once per
30
+ message, which was O(n²) in the batch and allocated two `CString`s per record.
31
+ Kafka → transform → file: **192,854 → 824,983 rows/s** on the 1M-row, four-partition
32
+ benchmark, from 0.35x to 1.51x Arroyo on identical output. A 16,384-message passthrough
33
+ batch went from 11.5s to 2.2s, and from 10.4s to 0.9s of CPU.
34
+ - **Postgres / sqlx**: `test_before_acquire(false)` on the pool, zero-copy row encoding via
35
+ a prebuilt `JsonRowSchema`, and prebuilt first/next page queries on the cursor path.
36
+ - **Command handler**: `send_batch` no longer loops per-message `send()` (≈8x on batched routes).
37
+ - **File**: single-pass byte-array decode, faster CSV writes, compression sniffing.
38
+ - **Deduplication**: the two-phase reserve/commit no longer writes to the store twice per
39
+ message. Reservations are held in memory — sled takes an exclusive file lock on its directory,
40
+ so a claim only ever has to be visible to this process — leaving one write per message, on
41
+ commit.
42
+
43
+ ### Fixed
44
+
45
+ - `insert_query` batch inserts dropped anything but a bare token from the `VALUES` tuple, so
46
+ `decode(${payload:x}, 'base64')` and casts were silently discarded — a binary (`bytea`)
47
+ column could not be written. The batch path now keeps the user's SQL and falls back to
48
+ iterative inserts when the tuple contains an expression.
49
+ - A payload string containing an embedded NUL byte was dropped by the driver and stored as
50
+ SQL `NULL` while the route reported success. It is now rejected as non-retryable.
51
+ - Database errors were rendered twice, because `sqlx` errors already display their own
52
+ source. Cause chains now skip a link an earlier one already contains.
53
+ - `cookie_jar`'s `inject_metadata` resolved only a bare stored name, so the namespaced
54
+ `cookie.<name>` / `value.<name>` spelling that `export_metadata_prefix` reports back
55
+ injected nothing. Both spellings now work.
56
+ - MQTT publishes are confirmed by PUBACK/PUBCOMP and guarded against session resets, which
57
+ took chaos-test message loss to zero.
58
+ - Postgres CDC advances its replication slot durably on shutdown instead of leaving the
59
+ feedback unflushed.
60
+ - `deduplication` could silently drop a message after a crash. The reservation was written to
61
+ the store *before* the message was processed, so a redelivery arriving within the 5s pending
62
+ TTL was classified as a duplicate and acked — without ever having been written to the sink.
63
+ Reservations are now in-memory and die with the process, so the redelivery is reprocessed.
64
+ - A Kafka record with no value — a tombstone, which is ordinary traffic on a compacted topic —
65
+ failed the whole batch. Its offset was never committed, so the route reconnected onto the same
66
+ record forever: one tombstone wedged the consumer permanently, and a compacted topic could not
67
+ be consumed at all. Tombstones now arrive as an empty payload flagged `mqb.kafka.tombstone`.
68
+ - `--drain` on a Kafka source could report success having copied only part of a topic, or none
69
+ of it. An idle wait was taken to mean "source exhausted", but an idle channel means nothing on
70
+ its own: before the first fetch lands, or in an ordinary gap between fetches, it looks exactly
71
+ the same as the end of the data. The shorter the idle timeout the more went missing — at 1ms a
72
+ 1,000,000-row topic landed 0 rows, and the copy still exited successfully. A drain now
73
+ completes only once every assigned partition has reached the offset it held when the drain
74
+ began, and lands all 1,000,000 rows at every idle timeout including 0.
75
+ - Drain no longer hangs on an empty source, and reconnect attempts are bounded.
76
+ - CSV reader handles quoted newlines; file endpoints fail fast on an unopenable path.
@@ -3348,7 +3348,7 @@ dependencies = [
3348
3348
 
3349
3349
  [[package]]
3350
3350
  name = "mq-bridge"
3351
- version = "0.3.9"
3351
+ version = "0.3.10"
3352
3352
  dependencies = [
3353
3353
  "aes-gcm",
3354
3354
  "anyhow",
@@ -3429,7 +3429,7 @@ dependencies = [
3429
3429
 
3430
3430
  [[package]]
3431
3431
  name = "mq-bridge-bindings-common"
3432
- version = "0.3.9"
3432
+ version = "0.3.10"
3433
3433
  dependencies = [
3434
3434
  "anyhow",
3435
3435
  "fast-uuid-v7",
@@ -3443,7 +3443,7 @@ dependencies = [
3443
3443
 
3444
3444
  [[package]]
3445
3445
  name = "mq-bridge-node"
3446
- version = "0.3.9"
3446
+ version = "0.3.10"
3447
3447
  dependencies = [
3448
3448
  "anyhow",
3449
3449
  "async-trait",
@@ -3465,7 +3465,7 @@ dependencies = [
3465
3465
 
3466
3466
  [[package]]
3467
3467
  name = "mq-bridge-py"
3468
- version = "0.3.9"
3468
+ version = "0.3.10"
3469
3469
  dependencies = [
3470
3470
  "anyhow",
3471
3471
  "async-trait",
@@ -16,7 +16,7 @@ members = ["bindings-common", "python/mq-bridge-py"]
16
16
  resolver = "2"
17
17
 
18
18
  [workspace.package]
19
- version = "0.3.9"
19
+ version = "0.3.10"
20
20
 
21
21
  [workspace.dependencies]
22
22
  anyhow = "1.0"
@@ -29,6 +29,12 @@ serde_yaml_ng = "0.10"
29
29
  tokio = "1.34" # `Receiver::recv_many` (HTTP source batch drain) needs 1.34+.
30
30
  tracing = "0.1"
31
31
 
32
+ [profile.dev.package."*"]
33
+ debug = false
34
+
35
+ [profile.dev.build-override]
36
+ debug = false
37
+
32
38
  [profile.release]
33
39
  lto = "thin"
34
40
  strip = "symbols"
@@ -138,7 +144,8 @@ flate2 = { version = "1.1", optional = true }
138
144
  lz4_flex = { version = "0.14", optional = true, default-features = false, features = ["frame"] }
139
145
  # zstd (bundled libzstd via zstd-sys); Decoder handles concatenated frames like gzip/lz4.
140
146
  zstd = { version = "0.13", optional = true }
141
- base64 = { version = "0.23", optional = true }
147
+ # Non-optional: `CanonicalMessage`'s JSON form encodes binary payloads as base64.
148
+ base64 = { version = "0.23" }
142
149
 
143
150
  # AEAD ciphers for the `encryption` feature (payload middleware + at-rest file/object).
144
151
  chacha20poly1305 = { version = "0.11", optional = true }
@@ -229,7 +236,7 @@ dedup = ["sled"]
229
236
  compression = ["dep:flate2", "dep:lz4_flex", "dep:zstd"]
230
237
  # AEAD payload encryption: the `encryption` middleware and the at-rest
231
238
  # `encryption` field on the file/object_store endpoints.
232
- encryption = ["dep:chacha20poly1305", "dep:aes-gcm", "dep:base64"]
239
+ encryption = ["dep:chacha20poly1305", "dep:aes-gcm"]
233
240
  metrics = ["dep:metrics"]
234
241
  test-utils = ["dep:tracing-appender", "dep:tracing-subscriber", "dep:serde_yaml_ng"]
235
242
  # Opt-in produce/consume-only isolation benchmarks (throwaway diagnostics, off by default)
@@ -254,9 +261,9 @@ amqp = ["lapin", "rustls", "rustls-pemfile", "url"]
254
261
  nats = ["async-nats", "rustls", "rustls-pemfile"]
255
262
  mongodb = ["dep:mongodb", "url"]
256
263
  mqtt = ["rumqttc", "rustls", "tokio-rustls", "rustls-pemfile", "url"]
257
- http = ["dep:arc-swap", "hyper", "hyper-util", "hyper-rustls", "http-body-util", "dep:http-body", "rustls", "tokio-rustls", "rustls-pemfile", "webpki-roots", "h2", "dep:flate2", "dep:lz4_flex", "dep:zstd", "dep:base64"]
264
+ http = ["dep:arc-swap", "hyper", "hyper-util", "hyper-rustls", "http-body-util", "dep:http-body", "rustls", "tokio-rustls", "rustls-pemfile", "webpki-roots", "h2", "dep:flate2", "dep:lz4_flex", "dep:zstd"]
258
265
  websocket = ["dep:tokio-websockets"]
259
- aws = ["aws-config", "aws-sdk-sqs", "aws-sdk-sns", "dep:base64"]
266
+ aws = ["aws-config", "aws-sdk-sqs", "aws-sdk-sns"]
260
267
  # Runtime-loaded IBM MQ client via dlopen: builds with no SDK present; the IBM
261
268
  # redistributable client is only needed at runtime, and only if an ibm-mq
262
269
  # endpoint is actually used.
@@ -283,10 +290,12 @@ object-store = ["dep:object_store", "url"]
283
290
  # Postgres logical-replication CDC (pgoutput). Reuses the sqlx dependency for the
284
291
  # slot-lifecycle control plane; the streaming wire protocol is pgwire-replication.
285
292
  # Needs Rust 1.88 (pgwire-replication MSRV), like the amqp feature gate.
286
- postgres-cdc = ["dep:pgwire-replication", "dep:byteorder", "sqlx", "dep:base64", "url", "dep:percent-encoding"]
293
+ postgres-cdc = ["dep:pgwire-replication", "dep:byteorder", "sqlx", "url", "dep:percent-encoding"]
287
294
 
288
295
  [dev-dependencies]
289
296
  config = { version = "0.15" }
297
+ # `start_paused` — lets a test cross a 30s health window without waiting 30s.
298
+ tokio = { workspace = true, features = ["test-util"] }
290
299
  serde_yaml_ng = "0.10"
291
300
  tempfile = "3.27"
292
301
  chrono = "0.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mq-bridge-py-basic
3
- Version: 0.3.9
3
+ Version: 0.3.10
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Programming Language :: Python :: 3
@@ -232,8 +232,8 @@ that ack each batch individually (NATS JetStream, AMQP, MQTT) accept any order.
232
232
  > per source position (Kafka `partition:offset`, NATS `stream_sequence`, AMQP
233
233
  > delivery tag) and makes a natural primary key. Source cursor fields are also
234
234
  > available in `message.metadata` (`mqb.src.kafka_topic`/`mqb.src.kafka_offset`, `mqb.src.nats_subject`/`mqb.src.nats_stream_sequence`,
235
- > `mqb.src.amqp_routing_key`/`mqb.src.amqp_delivery_tag`) when you opt in by setting
236
- > the `MQB_SOURCE_METADATA=1` environment variable (off by default).
235
+ > `mqb.src.amqp_routing_key`/`mqb.src.amqp_delivery_tag`) when its Kafka, NATS, or
236
+ > AMQP source config sets `source_metadata: true` (off by default).
237
237
  >
238
238
  > **Ack deadlines vs slow loads.** JetStream `AckWait` (default 30s), AMQP
239
239
  > prefetch/consumer-timeout and MQTT inflight windows each bound how long a batch
@@ -6,6 +6,8 @@
6
6
  ![Linux](https://img.shields.io/badge/Linux-supported-green?logo=linux)
7
7
  ![Windows](https://img.shields.io/badge/Windows-supported-green?logo=windows)
8
8
  ![macOS](https://img.shields.io/badge/macOS-supported-green?logo=apple)
9
+ [![Supply chain](https://img.shields.io/badge/supply%20chain-cargo--deny-blue)](deny.toml)
10
+ [![Security](https://img.shields.io/badge/security-advisory%20analysis-brightgreen)](SECURITY.md)
9
11
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
10
12
 
11
13
 
@@ -21,7 +23,7 @@ It is not only a forwarder. A route can transform, filter, fan out, retry, rate-
21
23
 
22
24
  ## Move data from A to B — inside your own service
23
25
 
24
- If you need to move data reliably between systems and you write code (Rust, Python, or Node), `mq-bridge` is a strong default. It is a **library you embed**, not a daemon or control plane you operate.
26
+ If you need to move data or events reliably between systems and you write code (Rust, Python, or Node), `mq-bridge` is a strong default. It is a **library you embed**, not a daemon or control plane you operate.
25
27
 
26
28
  **Prefer not to write code?** [`mq-bridge-app`](https://github.com/marcomq/mq-bridge-app) runs the exact same engine as a **standalone, zero-code ETL service** configured entirely by **YAML or environment variables** — move data from A to B without writing a line. It ships a **Postman-style UI** to build, send, and inspect messages against a route, and can **import Postman collections and AsyncAPI documents** to scaffold routes and endpoints for you.
27
29
 
@@ -35,6 +37,8 @@ If you need to move data reliably between systems and you write code (Rust, Pyth
35
37
 
36
38
  > **Throughput & footprint.** In our own benchmarks, the same engine — driven the zero-code way through [`mq-bridge-app`](https://github.com/marcomq/mq-bridge-app) — on a CSV→JSONL file conversion (1,000,000 mixed-type rows, ~116 MiB) sustained **1,133,786 rows/s** at ~22 MiB, about **~58x faster** and **~20x leaner in memory** than Meltano (`tap-csv` → `target-jsonl`, ~19,500 rows/s / ~444 MiB). Full setup, methodology, and the exact parameters are in [`benches/ETL_BENCHMARKS.md`](benches/ETL_BENCHMARKS.md).
37
39
 
40
+ > **Kafka → file.** In a 1,000,000-row relay with the default file format and no transform, the engine was up to **80% faster than Sea Streamer**. The [`mq-bridge-app` benchmark](https://github.com/marcomq/mq-bridge-app/tree/dev/benches/etl) contains the reproducible helper and native file-format caveats.
41
+
38
42
 
39
43
  ## Language Bindings
40
44
 
@@ -364,11 +368,56 @@ orders_upsert_branch:
364
368
  duplicate to detect. Left unwrapped by `request`, the tagged message is returned as the route's
365
369
  response as usual.
366
370
 
371
+ **Files & object storage — `idempotency`.** A filesystem has no unique constraint, so the `file` and
372
+ `object_store` sinks get replay safety a different way: **deterministic names plus covered-range
373
+ recovery**. Set `idempotency: true` and the sink stops writing UUID-named objects. Instead it groups
374
+ each batch into runs of consecutive source positions and writes one immutable part per run, named
375
+ for the range it covers:
376
+
377
+ ```yaml
378
+ kafka_to_s3:
379
+ input:
380
+ kafka: { topic: "orders", url: "localhost:9092", source_metadata: true }
381
+ output:
382
+ object_store:
383
+ url: "s3://my-bucket/orders"
384
+ idempotency: true # → s3://my-bucket/orders/part-orders-0-0-1023.jsonl
385
+ ```
386
+
387
+ On startup the sink lists what is already there, parses the ranges out of the part names, and drops
388
+ any incoming record whose position falls inside one. Filtering is **per record, not per batch**, so
389
+ batch boundaries are free to differ across restarts — that is what makes it work without a
390
+ checkpoint protocol. Local files stage to a temp name, `fsync`, then `rename` (atomic within a
391
+ filesystem); object stores have no atomic rename, so a single PUT under the final name *is* the
392
+ commit.
393
+
394
+ This needs a replayable source position, which today means **Kafka** (topic/partition/offset) or
395
+ **`postgres_cdc`** (commit LSN plus in-transaction ordinal). A route with an idempotent output turns
396
+ `source_metadata` on for its input automatically; set it explicitly only when you want the
397
+ `mqb.src.*` keys for something else. Any other input is rejected when the route starts. NATS, AMQP
398
+ and MongoDB CDC also accept `source_metadata` and emit provenance keys, but a subject or routing key
399
+ is not a replayable offset, so they cannot drive an idempotent sink.
400
+
401
+ For the `file` sink, `idempotency: true` changes what `path` means: it is the directory that receives
402
+ the part files, not the file that is appended to. The sink creates it on startup, so pointing it at an
403
+ existing regular file fails there with "Failed to create idempotent file sink directory".
404
+
405
+ `compression` and `encryption` work as usual.
406
+
407
+ Current limits, all of which the sink rejects or logs rather than silently mishandling:
408
+
409
+ * No `csv` (each part would need its own header row).
410
+ * `date_partition` is ignored — parts are written flat under the prefix, since the name already
411
+ carries the range. Logged at startup.
412
+ * **One part file per contiguous run per batch.** There is no size-based rolling yet, so a large
413
+ backfill produces many small files (~1000 per 1M rows at `batch_size: 1024`). For `postgres_cdc`
414
+ this is per-transaction, so a high-commit-rate stream produces one small file per commit. Rolling
415
+ would require buffering across batches the route has already acked, which is not safe today.
367
416
 
368
417
  ### Cloud Object Storage (S3 / GCS / Azure)
369
418
  The `object_store` endpoint (alias `s3`) reads and writes cloud object stores — Amazon S3, Google Cloud Storage, Azure Blob, Cloudflare R2, and anything else the [`object_store`](https://crates.io/crates/object_store) crate speaks — behind the same `receive_batch` / `send_batch` API. Enable it with the `object-store` feature. Credentials and backend options are read from the process environment (`AWS_ACCESS_KEY_ID`, `AWS_ENDPOINT`, `AWS_REGION`, `GOOGLE_SERVICE_ACCOUNT`, `AZURE_STORAGE_ACCOUNT`, ...); the URL scheme picks the backend (`s3://`, `gs://`, `az://`).
370
419
 
371
- * **As a sink**, each flushed batch is encoded with the same file endpoint formats (`normal` JSONL, `json`, `text`, `raw`) and written as **one immutable object** at `<prefix>/[YYYY/MM/DD/]<uuidv7>.<ext>`. Objects are write-once — nothing is appended or mutated. The uuidv7 name already sorts by write time; the optional `date_partition` prefix (on by default, derived from that same id's timestamp) is a readability / lifecycle-rule convenience.
420
+ * **As a sink**, each flushed batch is encoded with the same file endpoint formats (`normal` JSONL, `json`, `text`, `raw`) and written as **one immutable object** at `<prefix>/[YYYY/MM/DD/]<uuidv7>.<ext>`. Objects are write-once — nothing is appended or mutated. The uuidv7 name already sorts by write time; the optional `date_partition` prefix (on by default, derived from that same id's timestamp) is a readability / lifecycle-rule convenience. This naming applies only while `idempotency` is off — with `idempotency: true` the parts are instead named for the source range they cover and written flat under the prefix, and `date_partition` is ignored (see "Files & object storage — `idempotency`" above).
372
421
  * **As a source**, objects under the prefix are listed in key order, fetched whole, split on the delimiter, and emitted. Progress is a durable cursor holding the last fully-acked object key: set `cursor_id` and an external `checkpoint_store` (`file://`, `s3://`, `postgres://`, `mongodb://`) so a restart resumes without re-emitting. Objects are **never deleted or rewritten** — resume is non-destructive and at-least-once at object granularity (a nacked batch is redelivered; the cursor only advances once an object is fully acked). `csv` is supported on the source only.
373
422
 
374
423
  ```yaml
@@ -396,6 +445,26 @@ replay_from_s3:
396
445
  > always write the `{message_id, payload, metadata}` wrapper, so the message id survives the round
397
446
  > trip. Use `format: raw` to write payloads verbatim (bare documents, no wrapper). Applies to both
398
447
  > `file` and `object_store`.
448
+ >
449
+ > A **source** on those formats expects that same wrapper, `message_id` included. A line that is
450
+ > valid JSON but not the wrapper — a hand-written fixture with only `payload` and `metadata`, say —
451
+ > is not decomposed: the whole line becomes the payload and the line's own `metadata` is discarded.
452
+ > The reader logs a warning naming the wrapper when this happens. For plain JSON lines, use
453
+ > `format: raw`.
454
+ >
455
+ > **Payload encoding in the wrapper.** A UTF-8 payload is written as a plain JSON string under
456
+ > `payload`. A binary one (compressed, encrypted, Protobuf, …) is base64-encoded under a separate
457
+ > `payload_base64` field; the two are mutually exclusive, as in the
458
+ > [CloudEvents JSON format](https://github.com/cloudevents/spec/blob/main/cloudevents/formats/json-format.md).
459
+ >
460
+ > ```json
461
+ > {"message_id":"019f9b12-d786-7ebe-a7ec-a1aa71bc47ae","payload":"{\"order_id\":7}"}
462
+ > {"message_id":"019f9b12-d78a-7c01-b0f4-2f0f4d6a1c33","payload_base64":"KLUv/SBOAQAA"}
463
+ > ```
464
+ >
465
+ > Sources still read the older byte-array form (`"payload":[123,34,…]`), so existing files keep
466
+ > working. Only the reverse is a break: a **binary** record written by this version is not readable
467
+ > by an older mq-bridge. Text records are compatible in both directions.
399
468
 
400
469
  ### Response Endpoint
401
470
  The `response` output endpoint sends a reply back to the original requester. This is useful for synchronous request-reply flows, for example HTTP-to-NATS-to-HTTP. Use `response: {}` as the output endpoint configuration.
@@ -43,7 +43,15 @@ Writing with `[compression, encryption]` produces `compress(encrypt(payload))`;
43
43
  given that same list would try to decrypt first and fail. The reading route must say
44
44
  `[encryption, compression]`. The lists mirror — they are not copied.
45
45
 
46
- > This is asserted by `route::tests::test_dlq_and_retry_batch_integration`,
46
+ **A route handler sits outside every output middleware**, so it runs **once** per message
47
+ and the middlewares act on what it returned. In particular `retry` re-attempts only the
48
+ publish, never the handler — a handler with a side effect fires once however many times the
49
+ sink is retried. The trade: a `dlq` cannot capture a handler failure, only a send failure;
50
+ a handler error propagates to the route and is reported there.
51
+
52
+ > This is asserted by `route::tests::test_retryable_handler_error_is_not_retried_by_output_middleware`
53
+ > (the handler runs once and `retry` does not re-run it),
54
+ > `route::tests::test_dlq_and_retry_batch_integration`,
47
55
  > `middleware::transform::tests::test_rejected_message_reaches_the_dlq_through_the_config_wiring`,
48
56
  > and `reference_docs_test::publisher_middleware_wraps_last_entry_outermost`, and is
49
57
  > documented on `apply_middlewares_to_publisher` in `src/middleware/mod.rs`.
@@ -88,9 +96,10 @@ middlewares:
88
96
  **Putting a middleware on the wrong side behaves in two different ways**, so check the table
89
97
  above rather than assuming:
90
98
 
91
- - `deduplication` on an output, and `dlq` / `retry` on an input, log a warning and are
92
- skipped. The route still starts.
93
- - `weak_join` on an output is a **hard startup error** (`Unsupported publisher middleware`).
99
+ - `dlq` / `retry` on an input log a warning and are skipped. The route still starts.
100
+ - `deduplication` and `weak_join` on an output are **hard startup errors**. Deduplication
101
+ cannot work on the publish side, and silently starting an un-deduplicated route is worse
102
+ than refusing to start.
94
103
 
95
104
  A middleware whose feature is not compiled in (`deduplication` without `dedup`, `metrics`
96
105
  without `metrics`) is likewise a startup error, not a silent no-op.
@@ -131,16 +140,20 @@ Sends permanently-failed messages to a separate endpoint instead of failing the
131
140
  ```
132
141
 
133
142
  Captures `NonRetryable` failures and `Retryable` ones whose retries are exhausted. Connection
134
- errors are **not** dead-lettered — they propagate so the route can reconnect. The DLQ endpoint
143
+ errors are **not** dead-lettered — they propagate so the route can reconnect. Nor are handler
144
+ failures: the handler runs outside the middlewares (see [Ordering](#ordering--read-this-before-combining-middleware)),
145
+ so a `dlq` only ever sees what failed on the way to the sink. The DLQ endpoint
135
146
  is a full endpoint, so it can itself have middleware. If the DLQ send fails with a connection
136
147
  error that error propagates rather than silently dropping the message.
137
148
 
138
149
  **Without a `dlq` middleware**, a message that fails permanently — a data/type
139
- error the sink rejects, a poison payload a handler rejects — is logged at `error` level and
150
+ error the sink rejects — is logged at `error` level and
140
151
  **dropped**, and the route keeps processing the rest of the batch. `dlq` is the only retention
141
152
  mechanism: `retry` alone does not retain a permanently-failed message nor prevent it from being
142
- dropped — it only re-attempts retryable/connection errors, then hands a still-failing message on
143
- to be dropped (or to a following `dlq`). This tolerate-and-continue
153
+ dropped — it only re-attempts `Retryable` errors (a connection error is passed straight through
154
+ for the route to reconnect on, not retried), then hands a still-failing message on to be dropped
155
+ (or to a following `dlq`). This is why a sink that fails with a *connection* error never reaches
156
+ its `dlq`, whether it is a route's sole output or one leg of a `fanout`. This tolerate-and-continue
144
157
  policy keeps one bad message from halting the whole stream, but it means a *systematic* failure
145
158
  (e.g. every row hitting a column-type mismatch) drains the input while committing nothing and
146
159
  still ends `completed`. Add a `dlq` to capture the failures for inspection/replay, or watch the
@@ -161,11 +174,17 @@ validation — over a single parse. Input and output.
161
174
  | `schema_file` | path to a schema file | – |
162
175
  | `coerce` | bool | `true` |
163
176
  | `apply_defaults` | bool | `true` |
177
+ | `coerce_empty_as_null` | bool | `false` |
164
178
  | `on_error` | `reject` \| `pass_through` | `reject` |
165
179
 
166
180
  `schema` and `schema_file` are mutually exclusive. A mapping rule is either a bare path
167
181
  string or `{ path, default, required }`.
168
182
 
183
+ `schema` must be a JSON *object*, not a string containing one. A flat `key=value` middleware
184
+ syntax (such as the `|transform?schema=…` form in a connection URI) can only pass strings, so
185
+ it cannot express `schema` or any mapping rule beyond a bare path — use `schema_file`, or move
186
+ the route into a config file.
187
+
169
188
  ```yaml middleware
170
189
  - transform:
171
190
  mapping:
@@ -185,6 +204,26 @@ else is ignored, so an existing fuller schema can be used as-is. Coercions are l
185
204
  lossless ones: `string → integer`, `string → number`, `string → boolean` (`true`/`false`/`1`/`0`),
186
205
  `number → string`.
187
206
 
207
+ #### Empty strings
208
+
209
+ CSV and many SQL exports spell "no value" as an empty string. `coerce_empty_as_null: true`
210
+ reads every `""` the schema visits as `null`, which is then handled like any other null —
211
+ a `nullable` field keeps it, a `default` replaces it:
212
+
213
+ ```yaml middleware
214
+ - transform:
215
+ coerce_empty_as_null: true
216
+ schema:
217
+ type: object
218
+ properties:
219
+ note: { type: string, nullable: true }
220
+ tier: { type: string, default: standard }
221
+ ```
222
+
223
+ `note: ""` arrives as `null` and `tier: ""` as `"standard"`. A field that is neither
224
+ nullable nor defaulted is rejected, naming the coercion. Only fields the schema declares are
225
+ affected; `" "` is not empty.
226
+
188
227
  #### Embedded JSON
189
228
 
190
229
  A field carrying a JSON document as a string is decoded by `contentMediaType`, following
@@ -311,6 +350,11 @@ Correlates messages by a metadata key and emits them as one joined message. Inpu
311
350
  on_timeout: discard
312
351
  ```
313
352
 
353
+ `group_by` reads message **metadata** only — never the payload. A message that lacks the key
354
+ falls into a shared `"default"` group, so a mistyped key or a source that never sets it joins
355
+ unrelated messages instead of failing. If the value lives in the payload, lift it into metadata
356
+ first (a `transform` mapping, or the source's own metadata options).
357
+
314
358
  Setting `branch_by` switches to branch mode, where `required` overrides `expected_count`.
315
359
  On timeout an incomplete group is either emitted partially (`fire`) or dropped (`discard`).
316
360
  Messages are acknowledged on receipt, so a crash before the group completes loses the
@@ -506,6 +550,15 @@ Deliberate fault injection for testing recovery paths. Input and output.
506
550
  non-retryable one — useful for exercising a `dlq`. Keep `enabled: false` in committed configs
507
551
  rather than deleting the block.
508
552
 
553
+ On the **input** side, `json_format_error`/`nack` never call the real consumer at all — they
554
+ substitute a synthetic message (or error) on every triggered `receive`. Leaving
555
+ `trigger_on_message` unset means *every* poll is faulted, so the real source is never read and
556
+ `exit_on_empty`/`--drain` never sees the empty batch it waits for — the route runs forever,
557
+ manufacturing synthetic messages. Always set `trigger_on_message` to a specific count when
558
+ testing a drain-mode route with input-side fault injection. And since `dlq`/`retry` on an input
559
+ are no-ops (see above), pair an input-side fault with a real assertion on the *consumer's*
560
+ recovery, not a `dlq`.
561
+
509
562
  The middleware block alone is **not** enough: fault injection is gated per route by
510
563
  `allow_fault_injection`, which defaults to `false`. Copying only the snippet above leaves the
511
564
  middleware inert (the route logs that it is disabled). A complete, working configuration:
@@ -0,0 +1,104 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please report suspected vulnerabilities privately via
6
+ [GitHub Security Advisories](https://github.com/marcomq/mq-bridge/security/advisories/new)
7
+ rather than opening a public issue.
8
+
9
+ ## Supply-chain checks
10
+
11
+ `cargo deny check` runs in CI ([deny.toml](deny.toml)). Licenses, bans, and
12
+ sources are hard-gated; advisories run as an informational step, for the reasons
13
+ explained below.
14
+
15
+ ## Known advisories and why they do not (mostly) affect mq-bridge
16
+
17
+ Running `cargo audit` against this repository reports findings. **Most of them
18
+ are not reachable in mq-bridge**, and the rest are narrower than the raw output
19
+ suggests. This section records the analysis so you do not have to redo it.
20
+
21
+ Two structural points come first, because they explain most of the gap between
22
+ the tool output and the real exposure:
23
+
24
+ 1. **`cargo audit` and `cargo deny` read `Cargo.lock`, which is feature-blind.**
25
+ Almost every transport in this crate sits behind an optional Cargo feature.
26
+ A finding attributed to, say, the AWS SDK is not compiled at all in a
27
+ Kafka-only or Postgres-only build. The table below therefore states status
28
+ *per feature*.
29
+ 2. **A crate appearing in the tree is not the same as its vulnerable code being
30
+ used.** In one case below, a dependency pulls in an affected crate purely to
31
+ name a type in an error enum, while the actual cryptographic work happens in
32
+ a different, patched version.
33
+
34
+ ### Current status
35
+
36
+ | Advisory | Crate / path | Feature | Status |
37
+ | --- | --- | --- | --- |
38
+ | [RUSTSEC-2026-0104](https://rustsec.org/advisories/RUSTSEC-2026-0104) | `rustls-webpki` via `rumqttc`, `aws-sdk-*` | `mqtt`, `aws` | **Not affected** — CRL code path never invoked |
39
+ | [RUSTSEC-2026-0049](https://rustsec.org/advisories/RUSTSEC-2026-0049) | `rustls-webpki 0.102.8` via `rumqttc` | `mqtt` | **Not affected** — CRL code path never invoked |
40
+ | [RUSTSEC-2026-0098](https://rustsec.org/advisories/RUSTSEC-2026-0098) | `rustls-webpki 0.102.8` via `rumqttc` | `mqtt` | **Not affected** — validation runs on patched `0.103.13` |
41
+ | [RUSTSEC-2026-0099](https://rustsec.org/advisories/RUSTSEC-2026-0099) | `rustls-webpki 0.102.8` via `rumqttc` | `mqtt` | **Not affected** — validation runs on patched `0.103.13` |
42
+ | [RUSTSEC-2026-0098](https://rustsec.org/advisories/RUSTSEC-2026-0098) | `rustls-webpki 0.101.7` via `aws-sdk-*` | `aws` | **Reachable**, narrow preconditions — see below |
43
+ | [RUSTSEC-2026-0099](https://rustsec.org/advisories/RUSTSEC-2026-0099) | `rustls-webpki 0.101.7` via `aws-sdk-*` | `aws` | **Reachable**, narrow preconditions — see below |
44
+ | [RUSTSEC-2025-0057](https://rustsec.org/advisories/RUSTSEC-2025-0057) | `fxhash` via `sled 0.34` | `sled`, `dedup` | Unmaintained only, no vulnerability |
45
+ | [RUSTSEC-2024-0384](https://rustsec.org/advisories/RUSTSEC-2024-0384) | `instant` via `sled 0.34` → `parking_lot 0.11` | `sled`, `dedup` | Unmaintained only, no vulnerability |
46
+ | [RUSTSEC-2025-0134](https://rustsec.org/advisories/RUSTSEC-2025-0134) | `rustls-pemfile` (direct + `rumqttc`) | `amqp`, `nats`, `mqtt`, `http` | Unmaintained only, no vulnerability |
47
+
48
+ Each row applies only to the features listed for it: a build that enables none of
49
+ a row's features does not compile that finding's code. `rustls-pemfile` is the
50
+ broadest — it is pulled in by `amqp`, `nats`, `mqtt`, and `http` alike.
51
+
52
+ ### Detail: the `rustls-webpki` advisories
53
+
54
+ These reach the dependency tree by two independent paths.
55
+
56
+ **Path A — `rumqttc 0.25` → `rustls-webpki 0.102.8` (feature `mqtt`).**
57
+ `rumqttc` declares this dependency but uses exactly one item from it: the
58
+ `webpki::Error` variant in its own error enum (`src/tls.rs:42` is the only
59
+ reference to `webpki` in the crate). All certificate validation on the MQTT path
60
+ runs through `tokio-rustls 0.26` → `rustls 0.23` → `rustls-webpki 0.103.13`,
61
+ which is patched for all four advisories. **Not affected.**
62
+
63
+ **Path B — `aws-config` / `aws-sdk-sqs` / `aws-sdk-sns` →
64
+ `aws-smithy-http-client` → legacy `rustls 0.21` → `rustls-webpki 0.101.7`
65
+ (feature `aws`).** `aws-smithy-http-client` still selects its
66
+ `legacy-rustls-ring` connector, so this is live code on the SQS/SNS path. It
67
+ splits two ways:
68
+
69
+ - **RUSTSEC-2026-0104 and RUSTSEC-2026-0049 (CRL handling): not affected.** This
70
+ code executes only when the application supplies a certificate revocation list
71
+ to a client-certificate verifier. The AWS SDK is an outbound HTTPS client and
72
+ never configures one. mq-bridge's only `WebPkiClientVerifier` — used for the
73
+ optional mTLS HTTP server in `src/endpoints/http/mod.rs` — runs on
74
+ `rustls 0.23` / `rustls-webpki 0.103.13` and is never given a CRL.
75
+ - **RUSTSEC-2026-0098 and RUSTSEC-2026-0099 (name constraints): reachable.**
76
+ These fire during ordinary server-certificate chain validation, which the SQS
77
+ and SNS clients perform on every call. Exploitation requires a
78
+ name-constrained CA present in the trust store that issues certificates
79
+ outside its constraints — narrow, and not something mq-bridge can trigger or
80
+ mitigate itself. The fix depends on the AWS SDK migrating off its legacy
81
+ `rustls 0.21` connector. We do not claim to be unaffected here.
82
+
83
+ ### Detail: the unmaintained crates
84
+
85
+ None of these carry a known vulnerability; RustSec flags them as unmaintained.
86
+
87
+ - **`fxhash`, `instant`** arrive through `sled 0.34`, used by the deduplication
88
+ store. They resolve when `sled` updates its own dependencies.
89
+ - **`rustls-pemfile`** is a direct dependency of mq-bridge as well as of
90
+ `rumqttc`. It is superseded by pemfile support in `rustls-pki-types`.
91
+ Migrating the direct dependency is tracked as a follow-up.
92
+
93
+ ### Keeping this current
94
+
95
+ [deny.toml](deny.toml) carries these advisories as `[advisories].ignore`
96
+ entries, each with its reasoning inline, so suppressions and justifications
97
+ cannot drift apart. When an advisory is added, removed, or changes status,
98
+ update both files together.
99
+
100
+ The two tools do not report identically. `cargo deny` is configured with
101
+ `unmaintained = "workspace"`, which surfaces unmaintained crates only when we
102
+ depend on them directly — so the transitive `fxhash` and `instant` advisories
103
+ need no entry there. `cargo audit` applies no such filter and does report them.
104
+ Both are covered in the table above so either tool leads to the same answer.