mq-bridge-py-basic 0.3.7__tar.gz → 0.3.8__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.
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/CLAUDE.md +1 -1
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/Cargo.lock +4 -4
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/Cargo.toml +1 -1
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/PKG-INFO +1 -1
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/REFERENCE.md +38 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq-bridge.schema.json +34 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq_bridge/config.py +2 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq_bridge/config.pyi +7 -0
- mq_bridge_py_basic-0.3.8/src/middleware/compression.rs +314 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/mod.rs +8 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/models.rs +129 -1
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/reference_docs_test.rs +2 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/benchmark.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/ci.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/ibm-mq.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/node.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/publish-node.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/publish-python.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/python.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/release.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/test-matrix.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.gitignore +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/ARCHITECTURE.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/CONFIGURATION.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/CONTRIBUTING.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/LICENSE +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/LICENSE.python +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/README.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/benches/ETL_BENCHMARKS.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/benches/performance_bench.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/benches/router_bench.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/benches/zeromq_backends.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/bindings-common/Cargo.toml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/bindings-common/src/lib.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/bindings-common/src/logging.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/build.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/examples/ipc_worker_queue.yaml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq_bridge/__init__.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq_bridge/__init__.pyi +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq_bridge/py.typed +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/package-lock.json +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/pyproject.toml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/Cargo.toml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/LICENSE.python +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/README.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/analysis/bench_http_native.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/example.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/bench_memory.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/bench_memory.yaml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/consume_pull.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/dlt_source.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/json_route.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/memory.yaml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/raw_route.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/pyproject-basic.toml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/pyproject-reduced.toml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/scripts/gen_config_types.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/src/lib.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_config_types.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_logging.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_packaging.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_performance_smoke.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_public_api.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_routes.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/uv.lock +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/benches/metadata_bench.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/debug_ibm_mq_tls.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/http/analyze_xctrace_time_profile.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/http/http_flamegraph.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/http/mq_bridge_http_profile.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/http/run_http_ladder.sh +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/ci/format_bench_output.sh +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/httparena/README.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/httparena/frameworks/mq-bridge-py/Dockerfile +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/httparena/frameworks/mq-bridge-py/meta.json +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/httparena/frameworks/mq-bridge-py/server.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/Python/mq-bridge-py/README.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/Python/mq-bridge-py/benchmark_config.json +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/Python/mq-bridge-py/mq-bridge-py.dockerfile +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/Python/mq-bridge-py/server.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/README.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/postgres.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/seed.sql +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/verify.sh +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/the-benchmarker/README.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/the-benchmarker/python/mq-bridge-py/config.yaml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/the-benchmarker/python/mq-bridge-py/pyproject.toml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/the-benchmarker/python/mq-bridge-py/server.py +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/canonical_message.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/checkpoint.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/command_handler.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/amqp.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/aws.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/clickhouse.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/file/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/file/tests.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/grpc.proto +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/grpc.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/http/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/http/publisher.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/http/stream.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/http/tests.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/ibm_mq.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/kafka.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/endpoint.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/framed.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/ipc_unix.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/ipc_windows.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/memory_transport.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/transport.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/consumer.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/dedup.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/publisher.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/readers.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/tests.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mqtt.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/nats.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/object_store.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/poll.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/VENDORED.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/pgoutput/decoder.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/pgoutput/messages.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/pgoutput/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/pgoutput/registry.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/pgoutput/values.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/replication.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/state.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/redis_streams.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/sled.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/sqlx/dedup.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/sqlx/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/sqlx/tests.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/fanout.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/null.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/reader.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/request.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/response.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/static_endpoint.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/stream_buffer.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/switch.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/websocket.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/zeromq/codec.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/zeromq/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/zeromq/omq.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/zeromq/zmq.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/errors.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/event_handler.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/event_store.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/extensions.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/lib.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/buffer.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/cookie_jar.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/deduplication.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/delay.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/dlq.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/encryption.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/limiter.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/metrics.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/random_panic.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/retry.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/coerce.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/compiled.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/error.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/path.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/schema.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/tests.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/weak_join.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/outcomes.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/publisher.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/response.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/route.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/support/compression.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/support/connection_registry.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/support/crypto.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/support/interpolation.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/support/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/test_utils.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/traits.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/type_handler.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/README.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/armature_integration.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/amqp.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/aws.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/clickhouse.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/config.toml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/amqp.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/aws.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/clickhouse.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm-mq-certs/client.crl +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm-mq-certs/client.kdb +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm-mq-certs/client.p12 +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm-mq-certs/client.rdb +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm-mq-certs/client.sth +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm_mq.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm_mq_tls.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/kafka-tls.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/kafka.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mariadb.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mongodb-replica.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mongodb-tls.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mongodb.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mosquitto.conf +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mosquitto_performance.conf +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mosquitto_persistence.conf +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mq_init.mqsc +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mq_init_tls.mqsc +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mqtt.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mqtt_performance.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mysql.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/nats.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/object_store.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/postgres.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/postgres_cdc.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/redis.yml +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/file.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/grpc.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/grpc_tls.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/http.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/http_tls.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/ibm_mq.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/ibm_mq_tls.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/ipc.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/kafka.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/kafka_tls.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/logic_test.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mariadb.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/memory.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mod.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mongodb.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mongodb_raw.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mongodb_tls.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mqtt.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mysql.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/nats.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/object_store.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/performance_static.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/postgres.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/postgres_cdc.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/redis_streams.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/route.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/scripts/gen_certs.sh +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/sqlite.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/tls_helpers.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/websocket.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/zeromq.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration_test.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/memory_leak_test.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/ref_test.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/sqlite_test.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/tls_example.rs +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/unit_tests.md +0 -0
- {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/websocket_test.rs +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **Looking for what middleware or structural endpoints exist, and how to configure them?**
|
|
4
4
|
> [REFERENCE.md](REFERENCE.md) is the complete, authoritative list — every middleware
|
|
5
5
|
> (`retry`, `dlq`, `transform`, `deduplication`, `weak_join`, `buffer`, `limiter`, `delay`,
|
|
6
|
-
> `cookie_jar`, `encryption`, `metrics`, `random_panic`, `custom`) and every structural endpoint (`ref`,
|
|
6
|
+
> `cookie_jar`, `encryption`, `compression`, `metrics`, `random_panic`, `custom`) and every structural endpoint (`ref`,
|
|
7
7
|
> `fanout`, `switch`, `request`, `response`, `reader`, `static`, `stream_buffer`, `null`,
|
|
8
8
|
> `custom`), each with its fields, defaults, and a working YAML example. Do not infer these
|
|
9
9
|
> from the enum definitions in `models.rs`; the reference records the behaviour and the
|
|
@@ -3330,7 +3330,7 @@ dependencies = [
|
|
|
3330
3330
|
|
|
3331
3331
|
[[package]]
|
|
3332
3332
|
name = "mq-bridge"
|
|
3333
|
-
version = "0.3.
|
|
3333
|
+
version = "0.3.8"
|
|
3334
3334
|
dependencies = [
|
|
3335
3335
|
"aes-gcm",
|
|
3336
3336
|
"anyhow",
|
|
@@ -3411,7 +3411,7 @@ dependencies = [
|
|
|
3411
3411
|
|
|
3412
3412
|
[[package]]
|
|
3413
3413
|
name = "mq-bridge-bindings-common"
|
|
3414
|
-
version = "0.3.
|
|
3414
|
+
version = "0.3.8"
|
|
3415
3415
|
dependencies = [
|
|
3416
3416
|
"anyhow",
|
|
3417
3417
|
"fast-uuid-v7",
|
|
@@ -3425,7 +3425,7 @@ dependencies = [
|
|
|
3425
3425
|
|
|
3426
3426
|
[[package]]
|
|
3427
3427
|
name = "mq-bridge-node"
|
|
3428
|
-
version = "0.3.
|
|
3428
|
+
version = "0.3.8"
|
|
3429
3429
|
dependencies = [
|
|
3430
3430
|
"anyhow",
|
|
3431
3431
|
"async-trait",
|
|
@@ -3446,7 +3446,7 @@ dependencies = [
|
|
|
3446
3446
|
|
|
3447
3447
|
[[package]]
|
|
3448
3448
|
name = "mq-bridge-py"
|
|
3449
|
-
version = "0.3.
|
|
3449
|
+
version = "0.3.8"
|
|
3450
3450
|
dependencies = [
|
|
3451
3451
|
"anyhow",
|
|
3452
3452
|
"async-trait",
|
|
@@ -65,10 +65,21 @@ middlewares:
|
|
|
65
65
|
| [`delay`](#delay) | ✅ | ✅ | – | Fixed delay per receive/send |
|
|
66
66
|
| [`cookie_jar`](#cookie_jar) | ✅ | ✅ | – | Persist HTTP cookies / session values across messages |
|
|
67
67
|
| [`encryption`](#encryption) | ✅ | ✅ | `encryption` | AEAD-encrypt payloads on send, decrypt on receive |
|
|
68
|
+
| [`compression`](#compression) | ✅ | ✅ | `compression` | Compress payloads on send, decompress on receive |
|
|
68
69
|
| [`metrics`](#metrics) | ✅ | ✅ | `metrics` | Emit throughput/latency/error metrics |
|
|
69
70
|
| [`random_panic`](#random_panic) | ✅ | ✅ | – | Fault injection for testing |
|
|
70
71
|
| [`custom`](#custom-middleware) | ✅ | ✅ | – | Your own middleware via a registered factory |
|
|
71
72
|
|
|
73
|
+
> **Two kinds of compression.** The [`compression`](#compression) *middleware* compresses
|
|
74
|
+
> each message **payload** on any transport and decompresses it on the far side. Separately,
|
|
75
|
+
> the batch `compression` *field* on the `file` and `object_store` endpoints (`none` / `gzip`
|
|
76
|
+
> / `lz4` / `zstd`, same `compression` feature) compresses whole write batches so the file
|
|
77
|
+
> stays decodable with `zcat` / `lz4 -d`. Use the field for CLI-readable data at rest, the
|
|
78
|
+
> middleware for over-the-wire payloads. Don't stack either compression with the
|
|
79
|
+
> [`encryption`](#encryption) middleware on the same route — ciphertext does not compress; for
|
|
80
|
+
> compressed-and-encrypted data at rest use the endpoints' own `compression`/`encryption`
|
|
81
|
+
> fields (compress-then-encrypt per batch).
|
|
82
|
+
|
|
72
83
|
**Putting a middleware on the wrong side behaves in two different ways**, so check the table
|
|
73
84
|
above rather than assuming:
|
|
74
85
|
|
|
@@ -421,6 +432,33 @@ sniffing the leading magic bytes, so raw compressed bytes are never emitted as m
|
|
|
421
432
|
> `double precision` column can change the last bit. Build with the `float-roundtrip` feature
|
|
422
433
|
> for bit-exact float parsing across every endpoint (it trades a little parse speed for it).
|
|
423
434
|
|
|
435
|
+
### `compression`
|
|
436
|
+
|
|
437
|
+
Compresses each message **payload** on the output side and decompresses it on the input
|
|
438
|
+
side. Metadata and routing keys are untouched. Input and output. Requires the `compression`
|
|
439
|
+
feature.
|
|
440
|
+
|
|
441
|
+
| Field | Type | Default |
|
|
442
|
+
|---|---|---|
|
|
443
|
+
| `algorithm` | `none` \| `gzip` \| `lz4` \| `zstd` | `zstd` |
|
|
444
|
+
| `max_decompressed_bytes` | integer — reject a payload that decompresses larger than this (bomb guard); consumer side only | unset (no limit) |
|
|
445
|
+
|
|
446
|
+
```yaml
|
|
447
|
+
- compression: { algorithm: zstd }
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Each payload is compressed independently into a single self-contained member, so this works
|
|
451
|
+
over any transport, not just files. `algorithm: none` is a passthrough. A truncated or
|
|
452
|
+
corrupt frame is a **permanent** consumer error (the poison message is not re-read
|
|
453
|
+
indefinitely), as is a payload that exceeds `max_decompressed_bytes`. Put the same
|
|
454
|
+
`algorithm` on both the input and output side of a route.
|
|
455
|
+
|
|
456
|
+
Unlike the `file` / `object_store` batch `compression` field — which keeps whole write
|
|
457
|
+
batches decodable with `zcat` / `lz4 -d` — this middleware frames per message and is only
|
|
458
|
+
readable through a matching consumer. Do not combine it with the [`encryption`](#encryption)
|
|
459
|
+
middleware (ciphertext does not compress); for compressed-and-encrypted data at rest, use the
|
|
460
|
+
endpoints' own `compression`/`encryption` fields instead.
|
|
461
|
+
|
|
424
462
|
### `metrics`
|
|
425
463
|
|
|
426
464
|
Emits throughput, latency and error metrics for the endpoint. Input and output. Requires the
|
|
@@ -389,6 +389,28 @@
|
|
|
389
389
|
}
|
|
390
390
|
]
|
|
391
391
|
},
|
|
392
|
+
"CompressionMiddleware": {
|
|
393
|
+
"description": "Payload-compression middleware configuration.\n\nCompresses each message payload on the output side and decompresses it on the input\nside; metadata and routing keys are left untouched. Requires the `compression` feature.\nDistinct from the `file`/`object_store` batch `compression` field, which stays\nCLI-decodable at rest — this operates per message on any transport.",
|
|
394
|
+
"type": "object",
|
|
395
|
+
"properties": {
|
|
396
|
+
"algorithm": {
|
|
397
|
+
"description": "Algorithm: `none`, `gzip`, `lz4`, or `zstd`. Defaults to `zstd`.",
|
|
398
|
+
"$ref": "#/$defs/Compression",
|
|
399
|
+
"default": "zstd"
|
|
400
|
+
},
|
|
401
|
+
"max_decompressed_bytes": {
|
|
402
|
+
"description": "Reject a decompressed payload larger than this many bytes (decompression-bomb guard).\nConsumer side only; unset means no limit.",
|
|
403
|
+
"type": [
|
|
404
|
+
"integer",
|
|
405
|
+
"null"
|
|
406
|
+
],
|
|
407
|
+
"format": "uint64",
|
|
408
|
+
"default": null,
|
|
409
|
+
"minimum": 0
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
"additionalProperties": false
|
|
413
|
+
},
|
|
392
414
|
"CookieJarMiddleware": {
|
|
393
415
|
"description": "Cookie/session jar middleware configuration.\n\nOptimized for HTTP by default: it can read `cookie` and `set-cookie` metadata,\npersist session cookies, and inject them into later outgoing requests.\n\nThe middleware can also capture arbitrary metadata values into the same session store\nand optionally expose stored values back into message metadata.",
|
|
394
416
|
"type": "object",
|
|
@@ -1926,6 +1948,18 @@
|
|
|
1926
1948
|
"encryption"
|
|
1927
1949
|
]
|
|
1928
1950
|
},
|
|
1951
|
+
{
|
|
1952
|
+
"type": "object",
|
|
1953
|
+
"properties": {
|
|
1954
|
+
"compression": {
|
|
1955
|
+
"$ref": "#/$defs/CompressionMiddleware"
|
|
1956
|
+
}
|
|
1957
|
+
},
|
|
1958
|
+
"additionalProperties": false,
|
|
1959
|
+
"required": [
|
|
1960
|
+
"compression"
|
|
1961
|
+
]
|
|
1962
|
+
},
|
|
1929
1963
|
{
|
|
1930
1964
|
"type": "object",
|
|
1931
1965
|
"properties": {
|
|
@@ -7,6 +7,7 @@ AmqpConfig = dict
|
|
|
7
7
|
AwsConfig = dict
|
|
8
8
|
BufferMiddleware = dict
|
|
9
9
|
ClickHouseConfig = dict
|
|
10
|
+
CompressionMiddleware = dict
|
|
10
11
|
CookieJarMiddleware = dict
|
|
11
12
|
DeadLetterQueueMiddleware = dict
|
|
12
13
|
DeduplicationMiddleware = dict
|
|
@@ -73,6 +74,7 @@ __all__ = [
|
|
|
73
74
|
"CipherKind",
|
|
74
75
|
"ClickHouseConfig",
|
|
75
76
|
"Compression",
|
|
77
|
+
"CompressionMiddleware",
|
|
76
78
|
"ConfigDocument",
|
|
77
79
|
"CookieJarMiddleware",
|
|
78
80
|
"DeadLetterQueueMiddleware",
|
|
@@ -63,6 +63,12 @@ class ClickHouseConfig(TypedDict, total=False):
|
|
|
63
63
|
wait_for_async_insert: Optional[bool]
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
class CompressionMiddleware(TypedDict, total=False):
|
|
67
|
+
"""Payload-compression middleware configuration."""
|
|
68
|
+
algorithm: Compression
|
|
69
|
+
max_decompressed_bytes: Optional[int]
|
|
70
|
+
|
|
71
|
+
|
|
66
72
|
class CookieJarMiddleware(TypedDict, total=False):
|
|
67
73
|
"""Cookie/session jar middleware configuration."""
|
|
68
74
|
capture_metadata_keys: List[str]
|
|
@@ -226,6 +232,7 @@ class MetricsMiddleware(TypedDict, total=False):
|
|
|
226
232
|
class Middleware(TypedDict, total=False):
|
|
227
233
|
"""An enumeration of all supported middleware types."""
|
|
228
234
|
buffer: BufferMiddleware
|
|
235
|
+
compression: CompressionMiddleware
|
|
229
236
|
cookie_jar: CookieJarMiddleware
|
|
230
237
|
custom: Dict[str, Any]
|
|
231
238
|
deduplication: DeduplicationMiddleware
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// mq-bridge
|
|
2
|
+
// © Copyright 2026, by Marco Mengelkoch
|
|
3
|
+
// Licensed under MIT License, see License file for more details
|
|
4
|
+
// git clone https://github.com/marcomq/mq-bridge
|
|
5
|
+
//
|
|
6
|
+
//! Two-way payload compression middleware: the publisher side compresses each
|
|
7
|
+
//! message payload into a single self-contained member, the consumer side
|
|
8
|
+
//! decompresses it. Metadata and routing keys stay untouched. `Compression::None`
|
|
9
|
+
//! is a passthrough.
|
|
10
|
+
|
|
11
|
+
use crate::models::{Compression, CompressionMiddleware};
|
|
12
|
+
use crate::support::compression::{compress_member, decompress_all};
|
|
13
|
+
use crate::traits::{
|
|
14
|
+
BoxFuture, ConsumerError, MessageConsumer, MessagePublisher, PublisherError, Received,
|
|
15
|
+
ReceivedBatch, Sent, SentBatch,
|
|
16
|
+
};
|
|
17
|
+
use crate::CanonicalMessage;
|
|
18
|
+
use async_trait::async_trait;
|
|
19
|
+
use std::any::Any;
|
|
20
|
+
|
|
21
|
+
pub struct CompressionPublisher {
|
|
22
|
+
inner: Box<dyn MessagePublisher>,
|
|
23
|
+
algo: Compression,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
impl CompressionPublisher {
|
|
27
|
+
pub fn new(inner: Box<dyn MessagePublisher>, config: &CompressionMiddleware) -> Self {
|
|
28
|
+
Self {
|
|
29
|
+
inner,
|
|
30
|
+
algo: config.algorithm,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fn compress_message(&self, message: &mut CanonicalMessage) -> Result<(), PublisherError> {
|
|
35
|
+
if self.algo == Compression::None {
|
|
36
|
+
return Ok(());
|
|
37
|
+
}
|
|
38
|
+
let out = compress_member(self.algo, &message.payload)
|
|
39
|
+
.map_err(|e| PublisherError::NonRetryable(e.into()))?;
|
|
40
|
+
message.payload = out.into();
|
|
41
|
+
Ok(())
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[async_trait]
|
|
46
|
+
impl MessagePublisher for CompressionPublisher {
|
|
47
|
+
fn on_connect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
|
|
48
|
+
self.inner.on_connect_hook()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn on_disconnect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
|
|
52
|
+
self.inner.on_disconnect_hook()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async fn send(&self, mut message: CanonicalMessage) -> Result<Sent, PublisherError> {
|
|
56
|
+
self.compress_message(&mut message)?;
|
|
57
|
+
self.inner.send(message).await
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async fn send_batch(
|
|
61
|
+
&self,
|
|
62
|
+
mut messages: Vec<CanonicalMessage>,
|
|
63
|
+
) -> Result<SentBatch, PublisherError> {
|
|
64
|
+
// Keep the uncompressed payloads: messages surfaced as failed must go back
|
|
65
|
+
// upstream (retry/dlq) in their original form, or an outer retry would
|
|
66
|
+
// double-compress them.
|
|
67
|
+
let originals: std::collections::HashMap<u128, bytes::Bytes> = messages
|
|
68
|
+
.iter()
|
|
69
|
+
.map(|m| (m.message_id, m.payload.clone()))
|
|
70
|
+
.collect();
|
|
71
|
+
for message in &mut messages {
|
|
72
|
+
self.compress_message(message)?;
|
|
73
|
+
}
|
|
74
|
+
match self.inner.send_batch(messages).await? {
|
|
75
|
+
SentBatch::Ack => Ok(SentBatch::Ack),
|
|
76
|
+
SentBatch::Partial {
|
|
77
|
+
responses,
|
|
78
|
+
mut failed,
|
|
79
|
+
} => {
|
|
80
|
+
for (msg, _) in &mut failed {
|
|
81
|
+
if let Some(original) = originals.get(&msg.message_id) {
|
|
82
|
+
msg.payload = original.clone();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
Ok(SentBatch::Partial { responses, failed })
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn as_any(&self) -> &dyn Any {
|
|
91
|
+
self
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pub struct CompressionConsumer {
|
|
96
|
+
inner: Box<dyn MessageConsumer>,
|
|
97
|
+
algo: Compression,
|
|
98
|
+
max_bytes: Option<u64>,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
impl CompressionConsumer {
|
|
102
|
+
pub fn new(inner: Box<dyn MessageConsumer>, config: &CompressionMiddleware) -> Self {
|
|
103
|
+
Self {
|
|
104
|
+
inner,
|
|
105
|
+
algo: config.algorithm,
|
|
106
|
+
max_bytes: config.max_decompressed_bytes,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn decompress_message(&self, message: &mut CanonicalMessage) -> Result<(), ConsumerError> {
|
|
111
|
+
if self.algo == Compression::None {
|
|
112
|
+
return Ok(());
|
|
113
|
+
}
|
|
114
|
+
// A malformed/truncated frame will never decode, so it is a permanent failure
|
|
115
|
+
// rather than a reconnectable one — otherwise the poison message would be
|
|
116
|
+
// re-read forever.
|
|
117
|
+
let out = decompress_all(self.algo, &message.payload, self.max_bytes)
|
|
118
|
+
.map_err(|e| ConsumerError::Permanent(e.into()))?;
|
|
119
|
+
message.payload = out.into();
|
|
120
|
+
Ok(())
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#[async_trait]
|
|
125
|
+
impl MessageConsumer for CompressionConsumer {
|
|
126
|
+
fn set_exit_on_empty(&mut self, exit_on_empty: bool) {
|
|
127
|
+
self.inner.set_exit_on_empty(exit_on_empty);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fn commit_requires_order(&self) -> bool {
|
|
131
|
+
self.inner.commit_requires_order()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn on_connect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
|
|
135
|
+
self.inner.on_connect_hook()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn on_disconnect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
|
|
139
|
+
self.inner.on_disconnect_hook()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async fn receive(&mut self) -> Result<Received, ConsumerError> {
|
|
143
|
+
let mut received = self.inner.receive().await?;
|
|
144
|
+
self.decompress_message(&mut received.message)?;
|
|
145
|
+
Ok(received)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async fn receive_batch(&mut self, max_messages: usize) -> Result<ReceivedBatch, ConsumerError> {
|
|
149
|
+
let mut batch = self.inner.receive_batch(max_messages).await?;
|
|
150
|
+
for message in &mut batch.messages {
|
|
151
|
+
self.decompress_message(message)?;
|
|
152
|
+
}
|
|
153
|
+
Ok(batch)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn as_any(&self) -> &dyn Any {
|
|
157
|
+
self
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[cfg(test)]
|
|
162
|
+
mod tests {
|
|
163
|
+
use super::*;
|
|
164
|
+
use std::sync::{Arc, Mutex};
|
|
165
|
+
|
|
166
|
+
fn config(algo: Compression) -> CompressionMiddleware {
|
|
167
|
+
CompressionMiddleware {
|
|
168
|
+
algorithm: algo,
|
|
169
|
+
..Default::default()
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#[derive(Clone)]
|
|
174
|
+
struct RecordingPublisher {
|
|
175
|
+
sent: Arc<Mutex<Vec<CanonicalMessage>>>,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
#[async_trait]
|
|
179
|
+
impl MessagePublisher for RecordingPublisher {
|
|
180
|
+
async fn send_batch(
|
|
181
|
+
&self,
|
|
182
|
+
messages: Vec<CanonicalMessage>,
|
|
183
|
+
) -> Result<SentBatch, PublisherError> {
|
|
184
|
+
self.sent.lock().unwrap().extend(messages);
|
|
185
|
+
Ok(SentBatch::Ack)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fn as_any(&self) -> &dyn Any {
|
|
189
|
+
self
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
struct MockConsumer {
|
|
194
|
+
messages: Option<Vec<CanonicalMessage>>,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#[async_trait]
|
|
198
|
+
impl MessageConsumer for MockConsumer {
|
|
199
|
+
async fn receive_batch(
|
|
200
|
+
&mut self,
|
|
201
|
+
_max_messages: usize,
|
|
202
|
+
) -> Result<ReceivedBatch, ConsumerError> {
|
|
203
|
+
Ok(ReceivedBatch {
|
|
204
|
+
messages: self.messages.take().expect("batch already consumed"),
|
|
205
|
+
commit: Box::new(|_| Box::pin(async { Ok(()) })),
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fn as_any(&self) -> &dyn Any {
|
|
210
|
+
self
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#[tokio::test]
|
|
215
|
+
async fn publisher_compresses_and_consumer_decompresses() {
|
|
216
|
+
for algo in [Compression::Gzip, Compression::Lz4, Compression::Zstd] {
|
|
217
|
+
let sent = Arc::new(Mutex::new(Vec::new()));
|
|
218
|
+
let publisher = CompressionPublisher::new(
|
|
219
|
+
Box::new(RecordingPublisher { sent: sent.clone() }),
|
|
220
|
+
&config(algo),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
let plaintext = "compress me ".repeat(64);
|
|
224
|
+
let mut msg = CanonicalMessage::from(plaintext.as_str());
|
|
225
|
+
msg.metadata.insert("kind".to_string(), "note".to_string());
|
|
226
|
+
publisher.send_batch(vec![msg]).await.unwrap();
|
|
227
|
+
|
|
228
|
+
// The wire payload is compressed (smaller) and metadata stays clear.
|
|
229
|
+
let wire = sent.lock().unwrap().clone();
|
|
230
|
+
assert_ne!(
|
|
231
|
+
wire[0].payload.as_ref(),
|
|
232
|
+
plaintext.as_bytes(),
|
|
233
|
+
"algo {algo:?}"
|
|
234
|
+
);
|
|
235
|
+
assert!(wire[0].payload.len() < plaintext.len(), "algo {algo:?}");
|
|
236
|
+
assert_eq!(
|
|
237
|
+
wire[0].metadata.get("kind").map(|s| s.as_str()),
|
|
238
|
+
Some("note")
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
let mut consumer = CompressionConsumer::new(
|
|
242
|
+
Box::new(MockConsumer {
|
|
243
|
+
messages: Some(wire),
|
|
244
|
+
}),
|
|
245
|
+
&config(algo),
|
|
246
|
+
);
|
|
247
|
+
let batch = consumer.receive_batch(10).await.unwrap();
|
|
248
|
+
assert_eq!(
|
|
249
|
+
batch.messages[0].payload.as_ref(),
|
|
250
|
+
plaintext.as_bytes(),
|
|
251
|
+
"algo {algo:?}"
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#[tokio::test]
|
|
257
|
+
async fn corrupt_frame_fails_consume() {
|
|
258
|
+
let sent = Arc::new(Mutex::new(Vec::new()));
|
|
259
|
+
let publisher = CompressionPublisher::new(
|
|
260
|
+
Box::new(RecordingPublisher { sent: sent.clone() }),
|
|
261
|
+
&config(Compression::Gzip),
|
|
262
|
+
);
|
|
263
|
+
publisher
|
|
264
|
+
.send_batch(vec![CanonicalMessage::from("payload payload payload")])
|
|
265
|
+
.await
|
|
266
|
+
.unwrap();
|
|
267
|
+
|
|
268
|
+
let mut wire = sent.lock().unwrap().clone();
|
|
269
|
+
let mut corrupt = wire[0].payload.to_vec();
|
|
270
|
+
*corrupt.last_mut().unwrap() ^= 0xff;
|
|
271
|
+
wire[0].payload = corrupt.into();
|
|
272
|
+
|
|
273
|
+
let mut consumer = CompressionConsumer::new(
|
|
274
|
+
Box::new(MockConsumer {
|
|
275
|
+
messages: Some(wire),
|
|
276
|
+
}),
|
|
277
|
+
&config(Compression::Gzip),
|
|
278
|
+
);
|
|
279
|
+
assert!(matches!(
|
|
280
|
+
consumer.receive_batch(10).await,
|
|
281
|
+
Err(ConsumerError::Permanent(_))
|
|
282
|
+
));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
#[tokio::test]
|
|
286
|
+
async fn decompression_bomb_guard_rejects_oversized() {
|
|
287
|
+
let sent = Arc::new(Mutex::new(Vec::new()));
|
|
288
|
+
let publisher = CompressionPublisher::new(
|
|
289
|
+
Box::new(RecordingPublisher { sent: sent.clone() }),
|
|
290
|
+
&config(Compression::Zstd),
|
|
291
|
+
);
|
|
292
|
+
let big = "x".repeat(64 * 1024);
|
|
293
|
+
publisher
|
|
294
|
+
.send_batch(vec![CanonicalMessage::from(big.as_str())])
|
|
295
|
+
.await
|
|
296
|
+
.unwrap();
|
|
297
|
+
let wire = sent.lock().unwrap().clone();
|
|
298
|
+
|
|
299
|
+
let cfg = CompressionMiddleware {
|
|
300
|
+
algorithm: Compression::Zstd,
|
|
301
|
+
max_decompressed_bytes: Some(1024),
|
|
302
|
+
};
|
|
303
|
+
let mut consumer = CompressionConsumer::new(
|
|
304
|
+
Box::new(MockConsumer {
|
|
305
|
+
messages: Some(wire),
|
|
306
|
+
}),
|
|
307
|
+
&cfg,
|
|
308
|
+
);
|
|
309
|
+
assert!(matches!(
|
|
310
|
+
consumer.receive_batch(10).await,
|
|
311
|
+
Err(ConsumerError::Permanent(_))
|
|
312
|
+
));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -10,6 +10,8 @@ use anyhow::Result;
|
|
|
10
10
|
use std::sync::Arc;
|
|
11
11
|
|
|
12
12
|
mod buffer;
|
|
13
|
+
#[cfg(feature = "compression")]
|
|
14
|
+
mod compression;
|
|
13
15
|
mod cookie_jar;
|
|
14
16
|
#[cfg(feature = "dedup")]
|
|
15
17
|
pub(crate) mod deduplication;
|
|
@@ -26,6 +28,8 @@ mod transform;
|
|
|
26
28
|
mod weak_join;
|
|
27
29
|
|
|
28
30
|
use buffer::{BufferConsumer, BufferPublisher};
|
|
31
|
+
#[cfg(feature = "compression")]
|
|
32
|
+
use compression::{CompressionConsumer, CompressionPublisher};
|
|
29
33
|
use cookie_jar::{CookieJarConsumer, CookieJarPublisher};
|
|
30
34
|
#[cfg(feature = "dedup")]
|
|
31
35
|
use deduplication::DeduplicationConsumer;
|
|
@@ -77,6 +81,8 @@ pub async fn apply_middlewares_to_consumer(
|
|
|
77
81
|
Middleware::Transform(cfg) => Box::new(TransformConsumer::new(consumer, cfg)?),
|
|
78
82
|
#[cfg(feature = "encryption")]
|
|
79
83
|
Middleware::Encryption(cfg) => Box::new(EncryptionConsumer::new(consumer, cfg)?),
|
|
84
|
+
#[cfg(feature = "compression")]
|
|
85
|
+
Middleware::Compression(cfg) => Box::new(CompressionConsumer::new(consumer, cfg)),
|
|
80
86
|
Middleware::Custom { name, config } => {
|
|
81
87
|
let factory = get_middleware_factory(name).ok_or_else(|| {
|
|
82
88
|
anyhow::anyhow!("Custom middleware factory '{}' not found", name)
|
|
@@ -140,6 +146,8 @@ pub async fn apply_middlewares_to_publisher(
|
|
|
140
146
|
Middleware::Transform(cfg) => Box::new(TransformPublisher::new(publisher, cfg)?),
|
|
141
147
|
#[cfg(feature = "encryption")]
|
|
142
148
|
Middleware::Encryption(cfg) => Box::new(EncryptionPublisher::new(publisher, cfg)?),
|
|
149
|
+
#[cfg(feature = "compression")]
|
|
150
|
+
Middleware::Compression(cfg) => Box::new(CompressionPublisher::new(publisher, cfg)),
|
|
143
151
|
Middleware::Custom { name, config } => {
|
|
144
152
|
let factory = get_middleware_factory(name).ok_or_else(|| {
|
|
145
153
|
anyhow::anyhow!("Custom middleware factory '{}' not found", name)
|