Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
5c0dcb7
refactor(rust sdk): part 1
daflyinbed Jul 2, 2026
019ee38
fix(rust sdk): align grpc codec/consumer with server key spellings an…
daflyinbed Jul 2, 2026
ac13110
feat(rust sdk): add self-contained e2e test suite with bundled docker…
daflyinbed Jul 2, 2026
1b3d07b
fix(rust sdk): make rocketmq compose profile boot on selinux/low-mem …
daflyinbed Jul 2, 2026
74f969c
test(rust sdk): switch e2e suite to the rocketmq compose profile
daflyinbed Jul 2, 2026
95bd544
fix(rust sdk): create e2e topics as form-encoded, not JSON
daflyinbed Jul 2, 2026
a113d84
fix(rust sdk): use per-call gRPC timeouts, not a channel-wide cap
daflyinbed Jul 2, 2026
78094a0
fix(rust sdk): relocate rocketmq jars so the storage plugin's shadow …
daflyinbed Jul 3, 2026
b9a4a5f
fix(rust sdk): make request/reply replies correlate with the original…
daflyinbed Jul 3, 2026
6a616e9
fix(rust sdk): add graceful shutdown and axum-style stream driver
daflyinbed Jul 3, 2026
eca18c0
Merge pull request #2 from daflyinbed/feature/rust-sdk-http
daflyinbed Jul 4, 2026
4821675
feat(rust-sdk): tcp (#3)
daflyinbed Jul 6, 2026
e49b70d
feat(rust-sdk): TCP auto-reconnect and CloudEvents support
daflyinbed Jul 7, 2026
a5410c5
fix(rust sdk): route one-way publish to publishOneWay RPC and check s…
daflyinbed Jul 7, 2026
34604c7
chore: add asf license header and cleanup
Jul 8, 2026
11d0f5d
refactor: more idiomatic
Jul 8, 2026
28d52f2
refactor: interval
Jul 8, 2026
57454e9
refactor(rust-sdk): redesign consumer API — separate RPC from connect…
Jul 8, 2026
028e68f
test(rust-sdk): use eventmesh:jdk11-test image and harden e2e suite
daflyinbed Jul 8, 2026
d9fe3ac
fix(rust-sdk): set datacontenttype=application/cloudevents+json for T…
daflyinbed Jul 9, 2026
371c4d5
feat(rust-sdk): concurrent dispatch in gRPC stream receive loop
Jul 13, 2026
c5cd690
fix(rust-sdk): prevent silent hang of gRPC subscribe_stream on curren…
daflyinbed Jul 13, 2026
ffea4f6
fix(rust-sdk): address all 8 release-blocking review findings
daflyinbed Jul 13, 2026
74527e1
fix(rust-sdk): strict is_success, TCP CloudEvent validation, unsubscr…
daflyinbed Jul 15, 2026
17e1536
fix(rust-sdk): prevent TCP ghost writes, slow-consumer drops, driver-…
daflyinbed Jul 15, 2026
d823a80
fix(rust-sdk): redact secrets in Debug, strict HTTP retCode, (topic,u…
daflyinbed Jul 15, 2026
18410b3
fix(rust-sdk): bound gRPC resubscribe waits
daflyinbed Jul 15, 2026
5bf670e
feat(sdk-rust): add OpenMessaging and CloudEvents APIs
daflyinbed Jul 15, 2026
5e06c30
fix(rust-sdk): preserve request reply metadata
daflyinbed Jul 15, 2026
2478ce0
feat(rust): add catalog and workflow clients
daflyinbed Jul 15, 2026
2047e47
refactor: rust
daflyinbed Jul 16, 2026
7fd6ccf
refactor: remove v2
daflyinbed Jul 16, 2026
b721370
Refactor Rust SDK transports to preserve message dialects
daflyinbed Jul 16, 2026
c6c4093
Strengthen Rust E2E publish assertions
daflyinbed Jul 16, 2026
2db072b
Add Java/Rust interop E2E test wiring
daflyinbed Jul 16, 2026
45158f0
Add gRPC webhook consumers and per-call request timeouts
daflyinbed Jul 16, 2026
bf6577f
Polish Rust SDK APIs and release checks
daflyinbed Jul 16, 2026
fb12074
Fix request reply and TCP E2E readiness
daflyinbed Jul 16, 2026
a7219fa
Refactor event mesh components
daflyinbed Jul 17, 2026
f3f516f
Expand Rust SDK integration coverage
daflyinbed Jul 17, 2026
3517d2e
Document Rust SDK API, features, and verification workflow
daflyinbed Jul 17, 2026
ac4bc91
Make generated gRPC modules crate-private
daflyinbed Jul 22, 2026
03624aa
update
daflyinbed Jul 22, 2026
d60c101
Remove obsolete files and code
daflyinbed Jul 23, 2026
2b1b8ea
fix(rust-sdk): align runtime compatibility coverage
daflyinbed Jul 28, 2026
1ffa122
Refine request-reply transport support and webhook protocol detection
daflyinbed Jul 28, 2026
9151dc6
Refine event mesh application workflows
daflyinbed Jul 28, 2026
c819786
Make Rust SDK transport dependencies feature-specific
daflyinbed Jul 29, 2026
ed725f8
Expand Rust SDK cross-SDK interoperability tests
daflyinbed Aug 5, 2026
66d7a94
Normalize Apache license headers in Rust SDK interop files
daflyinbed Aug 5, 2026
3dd4d68
fix(rust-sdk): harden reconnects and CloudEvent conversion
daflyinbed Aug 9, 2026
fd6c1cf
docs(rust-sdk): consolidate project guidance
daflyinbed Aug 10, 2026
8703959
refactor: remove grpc legacy config
daflyinbed Aug 13, 2026
4b52cc7
refactor(rust-sdk): make gRPC channel explicit
daflyinbed Aug 13, 2026
1b9e506
docs: update comment
daflyinbed Aug 14, 2026
e80feb1
refactor(grpc): use tonic's timeout
daflyinbed Aug 14, 2026
91f6be9
refactor(grepc): use tonic's timeout
daflyinbed Aug 14, 2026
34a6f9c
Use unified subscription model across Rust SDK transports
daflyinbed Aug 17, 2026
fe22eaa
refactor(rust-sdk): remove HTTP/TCP legacy config adapters
daflyinbed Aug 18, 2026
e9d1c2b
Fix gRPC and TCP timeout handling
daflyinbed Sep 1, 2026
2de04ef
Preserve operation and shutdown errors during cleanup
daflyinbed Sep 1, 2026
8bf51da
Refactor implementation across affected modules
daflyinbed Sep 1, 2026
fad47c5
Ensure HTTP startup cleanup and TCP broadcast flush completion
daflyinbed Sep 9, 2026
278afcd
Simplify EventMesh implementation and remove obsolete code
daflyinbed Sep 11, 2026
471b1e3
Support HTTP webhook dialect decoding and current-thread gRPC runtimes
daflyinbed Sep 11, 2026
07eecbc
fix(rust-sdk): unify native message TTL handling
daflyinbed Sep 14, 2026
13f4484
fix(rust-sdk): isolate TCP consumer handler panics
daflyinbed Sep 14, 2026
01ba527
Separate delivery metadata from native message properties
daflyinbed Sep 17, 2026
1250329
Preserve consumer task ownership across cancelled joins
daflyinbed Sep 18, 2026
f2490ca
Document gRPC stream subscription limitations
daflyinbed Sep 18, 2026
a5dc640
Gate Rust SDK internals by transport features
daflyinbed Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,31 @@
dist/
build/
**/build/

# VCS and gradle caches (the wrapper is not used; system gradle lives in the
# builder image).
.git/
.gitignore
.gradle/
**/.gradle/

# Non-Java SDKs (not included in Gradle build)
eventmesh-sdks/eventmesh-sdk-rust/
eventmesh-sdks/eventmesh-sdk-go/
eventmesh-sdks/eventmesh-sdk-c/

# Rust / Node build artifacts. Keep the pattern slashless so Docker excludes
# both the target directory itself and everything below it in any SDK context.
**/target
**/node_modules/

# IDE / editor cruft.
.idea/
.vscode/
*.iml
*.ipr
*.iws

# Docs & misc
docs/
*.md
15 changes: 15 additions & 0 deletions .zed/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Folder-specific settings
//
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
{
"lsp": {
"rust-analyzer": {
"initialization_options": {
"cargo": {
"features": "all"
}
}
}
}
}
10 changes: 10 additions & 0 deletions eventmesh-sdks/eventmesh-sdk-rust/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Agent guidance for the EventMesh Rust SDK

This crate is independent of the repository's Gradle build. Before changing it, read the project-owned documentation instead of duplicating it here:

- [README.md](README.md) — supported features, installation, and public behavior.
- [CONTRIBUTING.md](CONTRIBUTING.md) — prerequisites, checks, end-to-end tests, documentation ownership, and code conventions.
- [ARCHITECTURE.md](ARCHITECTURE.md) — protocol boundaries, generated code, and transport-specific implementation constraints.
- [examples/README.md](examples/README.md) — runnable examples and exact feature flags.

Keep those files authoritative. Update this file only when instructions specific to coding agents cannot be expressed naturally in the contributor or architecture documentation.
83 changes: 83 additions & 0 deletions eventmesh-sdks/eventmesh-sdk-rust/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# EventMesh Rust SDK architecture

This document records implementation constraints and protocol boundaries. For public usage, see [README.md](README.md); for build and test commands, see [CONTRIBUTING.md](CONTRIBUTING.md).

## Public API boundaries

- `src/lib.rs` denies unsafe code.
- Consumers and webhook servers invoke the public `MessageHandler` directly with `Message`. Transport-private helpers decode and encode that envelope; there is no separate listener trait or handler adapter with an associated message type.
- Producers use concrete transport methods behind the public `GrpcProducer`, `HttpProducer`, and `TcpProducer` APIs. Message dialect selection remains in the public producer facade, while each transport owns its wire encoding and supported operations. There is no internal `Publisher` or `RequestReply` trait requiring unused or unsupported methods.
- Subscription is intentionally transport-specific. Each consumer owns its receive loop where applicable and exposes lifecycle methods suited to its protocol.
- `src/common/` contains protocol keys, status codes, constants, and the shared `LoadBalanceSelector`.
- Internal transport helpers and re-exports are gated by their consuming features; `common` and `model` have no module-wide unused-code exemptions. The shared Java wire-key and status/request-code catalogs retain documented `dead_code` exemptions on their constant-only implementations.

## Generated protobuf code

`build.rs` uses `tonic-build` to compile `proto/eventmesh-{service,cloudevents}.proto` into Cargo's `OUT_DIR`. It creates client stubs only and enables `--experimental_allow_proto3_optional`. The two `.proto` inputs and the hand-written `src/proto_gen.rs` wrapper are checked in. The generated Rust files remain in `OUT_DIR` and are loaded by `tonic::include_proto!`; under the current build setup, those generated files are not checked in. Add convenience aliases to `proto_gen.rs` rather than editing build output.

## Wire formats

`EventMeshMessage` is a business model, not a shared wire DTO. Each transport owns its serialization:

| Transport | Boundary | Encoding |
| --- | --- | --- |
| gRPC | `src/transport/grpc/codec.rs` | CloudEvents protobuf |
| HTTP | `src/transport/http/codec.rs` | Form URL encoding, with JSON in `content` |
| TCP | `src/transport/tcp/message.rs` | Length-prefixed binary frames with `EventMesh` magic |

Native messages separate business data from delivery context:

- `EventMeshMessage` owns topic, content, business/unique IDs, TTL, content type, and business properties.
- `DeliveryContext` in `src/model/delivery.rs` owns received protocol descriptors and known identity/routing attributes. Its public API is read-only; only SDK decoders can attach it. Credentials are redacted in Debug output.
- `decode_native_message` in `src/transport/mod.rs` separates wire attributes for every native decoder. HTTP form fields take precedence over duplicated IDs in `extFields`. Protocol-specific wire representations remain private.
- Property builders and setters reject reserved names using the shared classification in `src/model/delivery.rs`. Encoders retain the same guard as defense in depth and never serialize a delivery context during normal publish, broadcast, or a new request.
- TCP `RESPONSE_TO_SERVER` encoding restores known reply-routing attributes from the original request context, including Runtime `req0*`/`rsp0*`, cluster, and RocketMQ `correlation99id`/`reply99to99client`. The consumer attaches the original request context even if the handler returns a message received elsewhere. gRPC replies retain routing from their original protobuf request. ACK correlation continues to use the original wire frame.

TTL has one business source: its dedicated field. HTTP/gRPC retain their 4000 ms outbound default; TCP leaves an unset TTL to the Runtime. Content type is also a dedicated field, encoded as a gRPC attribute, TCP message header, or HTTP `extFields` entry. Decoders reject malformed or out-of-i64-range TTL while preserving numeric values until outbound validation. CloudEvents keep their standard attributes/extensions; native-to-CloudEvents reply conversion maps the dedicated fields and reply context explicitly.

TCP CloudEvents use `protocoltype=cloudevents` and raw `application/cloudevents+json` bytes, matching the Java runtime codec path.

## Configuration

- Every transport consumes the public configuration types directly:
`GrpcConfig`, `HttpConfig`, and `TcpConfig`, together with the role
options (`ProducerOptions`, `ConsumerOptions`) passed to each role
factory. There are no transport-private configuration adapters.
- `GrpcChannel::connect` creates the tonic channel on the current Tokio
runtime. Roles receive the channel explicitly and clones share its
multiplexed HTTP/2 connection. Applications using multiple Tokio runtimes
create a separate channel in each runtime.
- `HttpConfig` carries an `EndpointSet`; endpoint weights feed the shared
load balancer and identity/credentials ride as HTTP headers.
- `TcpConfig` keeps connect, protocol-control, business request, heartbeat,
and reconnect timeouts separate for Java compatibility. Heartbeats and
GOODBYE are fire-and-forget.

## Consumer lifecycle waits

Consumer lifecycle waits keep task ownership in `transport::task::BackgroundTask`. Waiting borrows the `JoinHandle`; completed results stay in the owner until the remaining cleanup has finished. Cancelling `join()` therefore preserves both unfinished tasks and pending failures for a later wait. Dropping the owner aborts any unfinished task.

## gRPC stream subscription limitations

Two known stream lifecycle limitations are retained without behavior changes in this SDK revision:

- `spawn_stream_driver` ignores frames without `seqnum`, including subscription responses with a non-success `statuscode`. Runtime `SubscribeStreamProcessor` uses `ServiceUtils.sendResponseCompleted` for validation errors and `sendStreamResponseCompleted` for ACL errors, followed by normal stream completion. Consequently, `open()` and `join()` can both succeed for a rejected subscription. In the Java SDK, `EventMeshCloudEventBuilder.buildMessageFromEventMeshCloudEvent` treats an event with neither `seqnum` nor `uniqueid` as subscription-list content; `SubStreamHandler.onNext` logs a resulting `Set` without checking the response status. Its stream `subscribe` returns `void` and does not await acceptance.
- `unsubscribe_stream_rpc` removes only the requested local topics. Runtime `ConsumerManager.deregisterClient` calls `closeEventStream` on the removed topic's emitter, which is shared by all topics on that subscription stream. The Rust driver treats EOF as terminal and cancels the heartbeat; it does not reconnect or replay remaining subscriptions. Java `EventMeshGrpcConsumer.unsubscribe` likewise retains the remaining topics, while `SubStreamHandler.onCompleted` only logs completion. Its `sender` is never reset, so subsequent subscriptions and heartbeat-triggered `resubscribe` reuse the closed stream. Java heartbeats can continue because Runtime `updateClientTime` checks client records, not emitter liveness.

The relevant Java sources are under `eventmesh-sdks/eventmesh-sdk-java/src/main/java/org/apache/eventmesh/client/grpc/` and `eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/grpc/` in this repository. These observations come from source review; they do not establish live Java end-to-end coverage. Public usage constraints are documented in the README and `GrpcStreamConsumer` rustdoc.

## HTTP lifecycle and routing

The managed `HttpConsumer` binds its axum callback server before registration, then owns registration, heartbeat, and shutdown. Applications that host their own endpoint use `WebhookRegistration` and the public codec helpers `parse_push_body`, `PushMessageRequestBody::to_message`, and `WebhookReply`. `to_message` resolves the dialect from the HTTP headers and `extFields`; the built-in handler calls the same decoder. `WebhookHandler` and `WebhookState` in `src/transport/http/webhook.rs` are internal implementation details.

The consumer owns the spawned server and heartbeat before awaiting registration, so dropping the startup future runs the same local cleanup as dropping an active consumer.

All SDK HTTP operations use code-header routing at `/`. The bodies are `application/x-www-form-urlencoded`, so sending them to a Runtime path-based handler can select an incompatible JSON model. The heartbeat runs every 30 seconds in a background Tokio task tied to a `CancellationToken`.

## TCP connection lifecycle

The consumer invokes each handler inside an asynchronous `catch_unwind` boundary covering both future construction and polling. An unwinding handler panic is logged with the delivery sequence, skips reply/ACK for that delivery, and keeps the receive loop running. The boundary does not restore application state. Explicit handler errors and reply encoding/enqueue failures still close the connection without ACK.

In `src/transport/tcp/connection.rs`, `establish()` performs the socket and HELLO handshake. `run()` wraps `io_loop()` in the reconnect loop. With reconnect enabled, I/O failures trigger exponential backoff and re-establishment. `take_reconnect_rx()` notifies consumers after successful reconnects so they can replay subscriptions.

Broadcasts use a driver completion channel to await `Framed::send`, including its socket flush, without waiting for a server ACK. Queue reservation and completion share one control-timeout deadline. A cancelled broadcast still waiting in the outbound queue is skipped; a write already in progress may have reached the server.
72 changes: 72 additions & 0 deletions eventmesh-sdks/eventmesh-sdk-rust/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Contributing to the EventMesh Rust SDK

This guide covers `eventmesh-sdks/eventmesh-sdk-rust`. Repository-wide Apache EventMesh contribution requirements still apply.

## Prerequisites

- Rust 1.86.0 or newer (the crate MSRV)
- `protoc` on `PATH` for builds that enable `grpc`, `full`, or `e2e`
- Docker and a compatible EventMesh runtime only for live end-to-end tests
- Java 8 or newer and Maven for the optional cross-SDK interop tests

`build.rs` invokes `tonic-build`; generated protobuf code lives in `OUT_DIR` and must not be edited or committed.

## Local checks

Run these before submitting a Rust SDK change:

```bash
cargo fmt --check
cargo clippy --no-default-features --lib -- -D warnings
cargo clippy --features full --all-targets -- -D warnings
cargo test --features full
cargo doc --features full --no-deps
```

Use `cargo test --features full --test codec_test` for the codec test binary. Examples are feature-gated in `Cargo.toml`; compile the one you changed with its documented `cargo run --example ... --features ...` command, or compile all supported paths with `cargo check --examples --features full`.

## End-to-end tests

The e2e suite is opt-in so a normal `cargo test` never requires Docker:

```bash
cargo test --features e2e
```

The harness starts the `rocketmq` docker-compose profile unless `EVENTMESH_E2E_EXTERNAL=1` points it at an already running runtime. An absent runtime is a failure by default. `EVENTMESH_E2E_ALLOW_SKIP=1` is only for an intentional local skip and must not be used for release verification.

The bundled compose file pins the Runtime to `apache/eventmesh:v1.12.0`. Run the bidirectional Rust/Java gRPC, HTTP, and TCP checks with:

```bash
cargo test --features interop_e2e --test e2e interop
```

Those tests build `interop/java-peer` with Maven on first use. The standalone peer depends on `org.apache.eventmesh:eventmesh-sdk-java:1.12.0-release`; it does not compile or load the Java SDK source tree from this repository.

The TCP reconnect test runs in the normal e2e suite. It uses a unique client subsystem and the Runtime admin API to disconnect only its own TCP sessions, so it does not restart or disrupt the shared Runtime container.

Each test uses a unique topic and consumer group. gRPC and HTTP cases may run in parallel; TCP cases are serialized because Runtime route refresh and RocketMQ rebalance state are shared. The harness creates and warms topics through the admin API before publishing.

The standalone in-memory broker requires a topic and subscription before the first publish and does not implement request/reply. Use a runtime profile with request/reply support for complete release verification. Topic creation uses form URL encoding at `POST /topic`.

## Documentation responsibilities

Keep each document in its intended layer.

| Change | Update |
| --- | --- |
| Installation, feature choice, or common behavior | `README.md` |
| Public type, method, feature-gated API, or behavior | rustdoc in `src/` |
| Runnable workflow or transport use | the matching file in `examples/` and `examples/README.md` |
| Validation, e2e, or contributor workflow | this file |
| Protocol boundary or internal architecture | `ARCHITECTURE.md` |

Public rustdoc should state feature requirements, ownership/lifecycle rules, and error or acknowledgement behavior where relevant. Prefer an executable doctest when it has no runtime dependency; otherwise mark the snippet `rust,ignore` and point users to a runnable example.

## Code conventions

- Add the Apache license header to every new `.rs` file.
- Mirror the established consuming builder style for configuration additions.
- Keep transport wire formats behind the public v2 API. Transports consume the public configuration types (`GrpcConfig`, `HttpConfig`, `TcpConfig`, role options) directly — do not reintroduce transport-private configuration adapters.

Follow the additional protocol boundaries and internal constraints in [ARCHITECTURE.md](ARCHITECTURE.md).
Loading
Loading