Skip to main content

connectrpc_reflection/
lib.rs

1//! gRPC server reflection for `connectrpc`.
2//!
3//! Wire-compatible with [`grpc.reflection.v1.ServerReflection`] and its
4//! `v1alpha` predecessor, so `grpcurl`, `buf curl`, Postman, `grpcui`,
5//! and every other reflection-aware client just works — over gRPC,
6//! gRPC-Web, and the Connect protocol alike.
7//!
8//! # Quick start
9//!
10//! Emit a descriptor set from your build script alongside code
11//! generation:
12//!
13//! ```ignore
14//! // build.rs
15//! connectrpc_build::Config::new()
16//!     .files(&["proto/app.proto"])
17//!     .includes(&["proto/"])
18//!     .emit_descriptor_set("app.fds.bin")
19//!     .compile()
20//!     .unwrap();
21//! ```
22//!
23//! then embed it and mount the service:
24//!
25//! ```no_run
26//! use connectrpc::Router;
27//! use connectrpc_reflection::{Reflector, install};
28//!
29//! // In real code: include_bytes!(concat!(env!("OUT_DIR"), "/app.fds.bin"))
30//! # fn descriptor_set_bytes() -> &'static [u8] { &[] }
31//! let reflector = Reflector::from_descriptor_set_bytes(descriptor_set_bytes()).unwrap();
32//! let router = install(Router::new(), reflector);
33//! ```
34//!
35//! [`install`] registers both protocol versions; use the generated
36//! extension traits directly if you want only one.
37//!
38//! Alternatively, when your buffa codegen has reflection enabled, skip
39//! the build-script step and serve straight from the generated package's
40//! descriptor pool:
41//!
42//! ```ignore
43//! let reflector =
44//!     Reflector::from_descriptor_pool(myapp::proto::descriptor_pool().clone()).unwrap();
45//! ```
46//!
47//! The bytes path needs only `emit_descriptor_set` — reflection codegen
48//! is **not** required — and answers with the compiler's original
49//! per-file descriptor bytes; the pool path re-encodes (semantically
50//! faithful, unknown fields preserved). See [`Reflector`] for the
51//! trade-off.
52//!
53//! # What gets exposed
54//!
55//! Everything in the descriptor set: all files, their transitive
56//! imports, and every service compiled into it — whether or not the
57//! corresponding handlers are mounted on the router. Use
58//! [`Reflector::with_services`] to curate the advertised service list,
59//! and [`Reflector::service_names`] to inspect it. Build the set from
60//! the same protos you serve, and remember that reflection
61//! intentionally publishes your schema: gate or omit the service on
62//! deployments where that is not wanted.
63//!
64//! The reflection service is **self-describing**: queries about
65//! `grpc.reflection.*` fall back to the crate's own descriptors, and
66//! `ListServices` advertises the reflection services alongside yours.
67//! This matches grpc-go (where the reflection proto is always
68//! registered) and is what schema-free callers like `buf curl` need to
69//! invoke `ServerReflectionInfo` directly. Use
70//! [`Reflector::with_services`] to advertise a different list — the
71//! override is verbatim, so omitting the reflection names de-lists them
72//! (they stay resolvable as symbols).
73//!
74//! # Cargo features
75//!
76//! * **`client`** (on by default) — re-exports the generated
77//!   `ServerReflectionClient` for querying a reflection server
78//!   (integration tests, CLI tooling). Pulls in `connectrpc`'s `client`
79//!   feature; server-only deployments opt out with
80//!   `default-features = false`.
81//!
82//! [`grpc.reflection.v1.ServerReflection`]: https://github.com/grpc/grpc-proto/blob/master/grpc/reflection/v1/reflection.proto
83#![cfg_attr(docsrs, feature(doc_cfg))]
84
85mod reflector;
86mod service;
87
88#[path = "generated/connect/mod.rs"]
89mod connect;
90// `message_response`'s variants all end in `Response` (proto field names);
91// buffa 0.7's generated allow-list does not yet cover this lint firing on
92// oneofs.
93#[allow(clippy::enum_variant_names)]
94#[path = "generated/buffa/mod.rs"]
95mod proto;
96
97pub use reflector::{ReflectionError, Reflector};
98pub use service::{ReflectionService, install};
99
100/// The wire-format `FileDescriptorSet` for this crate's protos
101/// (`grpc.reflection.v1` and `v1alpha`, from the public Buf Schema
102/// Registry's `buf.build/grpc/grpc` module).
103///
104/// Every [`Reflector`] already consults these descriptors as a built-in
105/// fallback, so the reflection service describes and lists itself with
106/// no setup. The constant is exposed for other uses — e.g. registering
107/// the reflection schema with a different protobuf runtime, the way
108/// tonic-reflection's constant of the same name is consumed.
109pub const FILE_DESCRIPTOR_SET: &[u8] = include_bytes!("../descriptor/reflection.fds.bin");
110
111/// Fully-qualified name of the v1 reflection service.
112pub use connect::grpc::reflection::v1::SERVER_REFLECTION_SERVICE_NAME;
113/// Generated v1 service trait and registration extension, for callers
114/// that mount a single protocol version by hand:
115///
116/// ```no_run
117/// use std::sync::Arc;
118/// use connectrpc::Router;
119/// use connectrpc_reflection::{Reflector, ReflectionService, ServerReflectionExt};
120///
121/// # fn descriptor_set_bytes() -> &'static [u8] { &[] }
122/// let reflector = Reflector::from_descriptor_set_bytes(descriptor_set_bytes()).unwrap();
123/// let service = Arc::new(ReflectionService::new(reflector));
124/// let router = service.register(Router::new()); // v1 only
125/// ```
126pub use connect::grpc::reflection::v1::{ServerReflection, ServerReflectionExt};
127
128/// Fully-qualified name of the v1alpha reflection service.
129pub use connect::grpc::reflection::v1alpha::SERVER_REFLECTION_SERVICE_NAME as SERVER_REFLECTION_V1ALPHA_SERVICE_NAME;
130/// Generated v1alpha service trait and registration extension, renamed to
131/// avoid colliding with the v1 items, for callers that mount the legacy
132/// protocol version by hand.
133pub use connect::grpc::reflection::v1alpha::{
134    ServerReflection as ServerReflectionV1alpha, ServerReflectionExt as ServerReflectionV1alphaExt,
135};
136
137/// Generated client for querying a `grpc.reflection.v1.ServerReflection`
138/// server.
139#[cfg(feature = "client")]
140#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
141pub use connect::grpc::reflection::v1::ServerReflectionClient;
142
143/// Re-exports of the generated `grpc.reflection.*` wire types — request
144/// and response messages, their oneof modules, and the method `Spec`
145/// constants. Everything a downstream crate needs to drive
146/// `ServerReflectionClient` (gated on the `client` feature) or inspect
147/// responses without regenerating the protos.
148pub mod wire {
149    /// `grpc.reflection.v1` wire types.
150    pub mod v1 {
151        pub use crate::connect::grpc::reflection::v1::SERVER_REFLECTION_SERVER_REFLECTION_INFO_SPEC;
152        pub use crate::proto::grpc::reflection::v1::{
153            ErrorResponse, ExtensionNumberResponse, ExtensionRequest, FileDescriptorResponse,
154            ListServiceResponse, ServerReflectionRequest, ServerReflectionResponse,
155            ServiceResponse, server_reflection_request, server_reflection_response,
156        };
157    }
158    /// `grpc.reflection.v1alpha` wire types.
159    pub mod v1alpha {
160        pub use crate::connect::grpc::reflection::v1alpha::SERVER_REFLECTION_SERVER_REFLECTION_INFO_SPEC;
161        pub use crate::proto::grpc::reflection::v1alpha::{
162            ErrorResponse, ExtensionNumberResponse, ExtensionRequest, FileDescriptorResponse,
163            ListServiceResponse, ServerReflectionRequest, ServerReflectionResponse,
164            ServiceResponse, server_reflection_request, server_reflection_response,
165        };
166    }
167}