connect2axum-codegen 0.5.2

Protoc generators for REST, WebSocket, OpenAPI, and AsyncAPI wrappers over ConnectRPC services
Documentation

connect2axum Crate Docs Build 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

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:

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 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.

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 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 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.