# connect2axum [](https://crates.io/crates/connect2axum) [](https://docs.rs/connect2axum) [](https://github.com/nu11ptr/connect2axum/actions) [](https://github.com/nu11ptr/connect2axum/blob/main/LICENSE)
Generate REST and/or WebSocket endpoint wrappers over ConnectRPC services.
Additionally, generate OpenAPI docs for REST endpoints and AsyncAPI docs for
WebSocket endpoints.
## Install
```sh
cargo install --locked connect2axum-codegen
```
This installs:
- `protoc-gen-connect2rest`
- `protoc-gen-connect2ws`
- `protoc-gen-connect2openapi`
- `protoc-gen-connect2asyncapi`
`connect2axum` 0.4 and `connect2axum-codegen` 0.5 target Buffa 0.9 and
ConnectRPC 0.9. When upgrading from upstream versions before 0.9, regenerate
the Buffa, ConnectRPC, and connect2axum bindings together. Service implementations receive
`ServiceRequest<'_, Request>` for unary and server-streaming calls, and
`InboundStream<Request>` (items of `StreamMessage<Request>`) for client and
bidirectional streaming calls. Read stream-item fields through `.view()` or
the generated accessor methods.
Version 0.4 removes `JsonCompatibleView` and `json_compatible_view`. Use
`json_view` for response views with a ProtoJSON `Serialize` implementation,
or return an owned message. `json_owned_view` remains the input helper for
decoding incoming JSON into an `OwnedView`.
## Domain-owned Responses
An application can keep its own response types and borrow their fields only
while encoding. Implement ConnectRPC's `Encodable<Message>` for the domain
type and use `connect2axum::json_view` around a temporary Buffa view:
```rust
use buffa::bytes::Bytes;
use connectrpc::{CodecFormat, ConnectError, Encodable};
use flexstr::SharedStr;
// HelloReply and HelloReplyView are the generated message and view types.
struct Greeting {
message: SharedStr,
}
impl Encodable<HelloReply> for Greeting {
fn encode(&self, codec: CodecFormat) -> Result<Bytes, ConnectError> {
connect2axum::json_view(HelloReplyView {
message: self.message.as_ref(),
..Default::default()
})
.encode(codec)
}
}
```
The stream owns each `Greeting`, so it can cross task/channel boundaries.
The view borrows its fields only during encoding; no generated owned
`HelloReply` or intermediate protobuf buffer is needed to produce JSON.
This does not require `SharedStr` to implement Buffa field traits because the
generated view uses `&str`.
`json_view` serializes JSON directly through the view's `Serialize` impl and
delegates other codecs to its `Encodable` impl. Enable Buffa JSON generation
for the view. Both implementations must represent the same protobuf message;
an arbitrary domain struct's derived serde is not necessarily ProtoJSON.
The same response item works with Connect/gRPC, REST, and JSON WebSocket
adapters. See the [WebSocket streaming example](examples/ws-streaming/src/lib.rs)
for a complete implementation and transport tests.
Return an owned message when registered protobuf extensions must appear in
JSON: Buffa's generated view serializer omits those extensions.
Direct view encoding can still allocate output buffers or temporary view
containers; it does not guarantee allocation-free serialization.
## Nested Fields
Path variables may bind a field inside singular messages, such as
`get: "/symbols/{datasource.symbol.ticker}"`. The rest of `datasource` still
comes from the body or query; the path value overrides only that field.
Query parameters accept dotted paths into nested messages, using protobuf or
JSON field names: `?datasource.symbol.exchange=NASDAQ&datasource.workspaceName=demo`.
## Plugin Options
Options are passed as comma-separated `name=value` pairs in `buf.gen.yaml`.
```yaml
plugins:
- local: protoc-gen-connect2rest
out: src/generated/connect2axum
opt:
- buffa_module=crate::proto
- connect_module=crate::connect
- local: protoc-gen-connect2ws
out: src/generated/connect2axum
opt:
- buffa_module=crate::proto
- connect_module=crate::connect
- local: protoc-gen-connect2openapi
out: src/generated/openapi
strategy: all
opt:
- config=connect2openapi.yaml
- local: protoc-gen-connect2asyncapi
out: src/generated/asyncapi
strategy: all
opt:
- config=connect2asyncapi.yaml
```
### Active Options
| Option | Default | Used By | Purpose |
| --- | --- | --- | --- |
| `buffa_module` | `crate::proto` | REST, WS | Rust module root where Buffa generated messages are available. |
| `connect_module` | `crate::connect` | REST, WS | Rust module root where Connect Rust generated service traits are available. |
| `runtime_module` | `::connect2axum` | REST, WS | Rust path to the runtime helper crate/module. |
| `streaming_content_type` | `application/x-ndjson` | REST | REST streaming response content type. |
| `suppress_pkg_prefix` | `true` | OpenAPI, AsyncAPI | Omit protobuf package prefixes from document component names and references. |
| `value_suffix` | `__` | REST, WS | Suffix for generated local bindings to avoid collisions with request fields. |
| `type_suffix` | `__` | REST | Suffix for generated DTO type names. |
| `body_message_suffix` | `Body` | REST | Suffix for generated body DTOs. |
| `query_message_suffix` | `Query` | REST | Suffix for generated query DTOs. |
### OpenAPI Generator
`protoc-gen-connect2openapi` wraps grpc-gateway's
`protoc-gen-openapiv3`, then patches the generated document for connect2axum
REST behavior. It supports `output`, `config`, `openapiv3_bin`,
`openapiv3_opt`, `streaming_content_type`, and `suppress_pkg_prefix` plugin
options.
See [docs/openapi-generator.md](docs/openapi-generator.md) for config format
and backend details.
### AsyncAPI Generator
`protoc-gen-connect2asyncapi` emits AsyncAPI v3.1 JSON for the JSON WebSocket
routes generated by `protoc-gen-connect2ws`. It supports `output`, `config`,
`server_url`, `default_content_type`, and `suppress_pkg_prefix` plugin options.
See [docs/asyncapi-generator.md](docs/asyncapi-generator.md) for document shape
and config details.
### WebSocket Notes
`protoc-gen-connect2ws` only generates JSON WebSocket routes for streaming RPCs.
Unary RPCs stay REST/Connect-only. Client and bidirectional request streams end
with an empty text frame so the socket can remain open for any response frames.