Skip to main content

connectrpc/
response.rs

1//! Handler request/response types.
2//!
3//! This module splits the old `Context` struct into a read-only
4//! [`RequestContext`] (passed *into* handlers) and a [`Response<B>`]
5//! wrapper (returned *from* handlers). The body type `B` is bounded by
6//! [`Encodable<M>`] in the generated trait so handlers can return either
7//! the owned message `M`, a borrowing `MView<'_>` /
8//! [`OwnedView<MView<'static>>`](buffa::view::OwnedView), or
9//! [`MaybeBorrowed`] for the conditional case.
10
11use std::marker::PhantomData;
12use std::pin::Pin;
13use std::time::{Duration, Instant};
14
15use buffa::Message;
16use buffa::view::{MessageView, ViewEncode};
17use bytes::Bytes;
18use bytes::BytesMut;
19use futures::Stream;
20use http::HeaderMap;
21use http::header::{HeaderName, HeaderValue};
22
23use crate::codec::CodecFormat;
24use crate::codec::JsonSerialize;
25use crate::codec::encode_json;
26use crate::error::ConnectError;
27
28// ---------------------------------------------------------------------------
29// RequestContext
30// ---------------------------------------------------------------------------
31
32/// Read-only request context passed to RPC handlers.
33///
34/// Carries the request headers, parsed deadline, and any
35/// connection-scoped extensions (peer address, TLS certs, auth context)
36/// inserted by a tower layer in front of the service. Handlers do *not*
37/// return this; response-side metadata lives on [`Response`].
38///
39/// `RequestContext` is `#[non_exhaustive]`: construct it with
40/// [`RequestContext::new`] and the `with_*` builders, and read fields
41/// through the accessor methods (`headers()`, `deadline()`,
42/// `extensions()`, …). New request-scoped metadata can be added in minor
43/// releases without breaking downstream code.
44#[derive(Debug, Clone, Default)]
45#[non_exhaustive]
46pub struct RequestContext {
47    /// Request headers (after protocol-prefix stripping).
48    pub(crate) headers: HeaderMap,
49    /// Absolute request deadline parsed from the protocol's timeout header,
50    /// if any. Propagate to downstream calls.
51    ///
52    /// If a [`DeadlinePolicy`](crate::DeadlinePolicy) is configured on the
53    /// service, this is the *moderated* value — clamped to the policy's
54    /// `[min, max]` range, or the policy default when the client asserted
55    /// nothing — not the raw client header.
56    pub(crate) deadline: Option<Instant>,
57    /// Request extensions carried from the underlying `http::Request`.
58    pub(crate) extensions: http::Extensions,
59    /// Static metadata for the dispatched RPC method, when known.
60    pub(crate) spec: Option<crate::spec::Spec>,
61    /// The wire protocol negotiated for this request, when known.
62    pub(crate) protocol: Option<crate::Protocol>,
63    /// The procedure path the client requested, with a leading slash.
64    pub(crate) path: Option<String>,
65    /// Decode limits for this request's message, from the service's
66    /// [`Limits`](crate::Limits). Default when the context was built outside
67    /// the service (a hand-rolled test, a tower layer), which is why this is
68    /// the service's value rather than a global.
69    pub(crate) decode_options: buffa::DecodeOptions,
70}
71
72impl RequestContext {
73    /// Create a new context with the given request headers.
74    pub fn new(headers: HeaderMap) -> Self {
75        Self {
76            headers,
77            deadline: None,
78            extensions: http::Extensions::new(),
79            spec: None,
80            protocol: None,
81            path: None,
82            decode_options: buffa::DecodeOptions::new(),
83        }
84    }
85
86    /// Set the decode limits applied to this request's message.
87    #[doc(hidden)] // set by the service from its configured `Limits`
88    #[must_use]
89    pub fn with_decode_options(mut self, options: buffa::DecodeOptions) -> Self {
90        self.decode_options = options;
91        self
92    }
93
94    /// The decode limits applied to this request's message.
95    #[doc(hidden)] // read by generated dispatch
96    #[must_use]
97    pub fn decode_options(&self) -> &buffa::DecodeOptions {
98        &self.decode_options
99    }
100
101    /// Set the request deadline (absolute `Instant`).
102    #[must_use]
103    pub fn with_deadline(mut self, deadline: Option<Instant>) -> Self {
104        self.deadline = deadline;
105        self
106    }
107
108    /// Attach request extensions captured from the underlying `http::Request`.
109    #[must_use]
110    pub fn with_extensions(mut self, extensions: http::Extensions) -> Self {
111        self.extensions = extensions;
112        self
113    }
114
115    /// Attach the static method metadata for the dispatched RPC.
116    #[must_use]
117    pub fn with_spec(mut self, spec: Option<crate::spec::Spec>) -> Self {
118        self.spec = spec;
119        self
120    }
121
122    /// Attach the negotiated wire protocol.
123    #[must_use]
124    pub fn with_protocol(mut self, protocol: Option<crate::Protocol>) -> Self {
125        self.protocol = protocol;
126        self
127    }
128
129    /// Attach the procedure path the client requested. The dispatch path
130    /// always supplies the leading-slash form (`"/package.Service/Method"`),
131    /// matching [`Spec::procedure`](crate::Spec::procedure); custom
132    /// dispatch shims and test fixtures should do the same so consumers
133    /// of [`path()`](Self::path) see a consistent shape.
134    #[must_use]
135    pub fn with_path(mut self, path: impl Into<String>) -> Self {
136        self.path = Some(path.into());
137        self
138    }
139
140    /// Request headers (after protocol-prefix stripping).
141    ///
142    /// For a single header lookup, [`header`](Self::header) is simpler.
143    pub fn headers(&self) -> &HeaderMap {
144        &self.headers
145    }
146
147    /// Get a request header value.
148    pub fn header(&self, key: impl http::header::AsHeaderName) -> Option<&HeaderValue> {
149        self.headers.get(key)
150    }
151
152    /// Absolute request deadline parsed from the protocol's timeout header
153    /// (`Connect-Timeout-Ms` or `grpc-timeout`), if the client asserted one.
154    ///
155    /// Propagate this to downstream calls so the whole call chain shares a
156    /// single budget. For the remaining budget as a `Duration`, see
157    /// [`time_remaining`](Self::time_remaining).
158    ///
159    /// If a [`DeadlinePolicy`](crate::DeadlinePolicy) is configured on the
160    /// service, this is the *moderated* value — clamped to the policy's
161    /// `[min, max]` range, or the policy default when the client asserted
162    /// nothing — not the raw client header.
163    pub fn deadline(&self) -> Option<Instant> {
164        self.deadline
165    }
166
167    /// Time remaining until the request deadline, saturating at zero.
168    ///
169    /// `None` if the client did not assert a timeout. Use this to budget
170    /// downstream calls — for example, subtract a margin before passing the
171    /// remainder as a downstream RPC's per-call timeout. See also issue
172    /// [#92](https://github.com/anthropics/connect-rust/issues/92) for
173    /// server-side deadline enforcement.
174    pub fn time_remaining(&self) -> Option<Duration> {
175        self.deadline
176            .map(|d| d.saturating_duration_since(Instant::now()))
177    }
178
179    /// Request extensions carried from the underlying `http::Request`.
180    ///
181    /// This is the passthrough for connection-scoped metadata that a tower
182    /// layer in front of the service can attach — TLS peer certificates,
183    /// remote socket address, auth context, etc. The dispatch path moves
184    /// `parts.extensions` here verbatim; handlers read it with
185    /// `ctx.extensions().get::<T>()`. For the well-known peer types, prefer
186    /// the typed accessors `peer_addr()` and `peer_certs()` (gated on the
187    /// `server` and `server-tls` features respectively) — they return
188    /// `None` instead of panicking when the transport didn't insert the
189    /// extension.
190    pub fn extensions(&self) -> &http::Extensions {
191        &self.extensions
192    }
193
194    /// Mutable access to the request extensions.
195    ///
196    /// Useful for code that constructs a `RequestContext` directly — e.g.
197    /// a custom dispatch shim or test fixture — and needs to insert
198    /// connection-scoped values before calling a handler.
199    ///
200    /// # Note
201    ///
202    /// Handlers receive `RequestContext` **by value**, so calling
203    /// `ctx.extensions_mut().insert(...)` inside a handler mutates a local
204    /// copy that the framework never sees again — it has no effect on the
205    /// dispatch path or on downstream layers. To pass values *into* a
206    /// handler from middleware, mutate `http::Request::extensions_mut()`
207    /// in the layer instead; the dispatcher moves request extensions into
208    /// `RequestContext` automatically before dispatch.
209    pub fn extensions_mut(&mut self) -> &mut http::Extensions {
210        &mut self.extensions
211    }
212
213    /// Static metadata for the dispatched RPC method, when known.
214    ///
215    /// Populated by code-generated `FooServiceServer<T>` dispatchers and
216    /// by the dynamic [`Router`](crate::Router) when registered through
217    /// the generated `register()` (which chains
218    /// [`Router::with_spec`](crate::Router::with_spec) per route).
219    /// `None` only for low-level manual registrations that do not attach a
220    /// [`Spec`](crate::Spec). See [`path`](Self::path) for the always-present
221    /// procedure path.
222    pub fn spec(&self) -> Option<crate::spec::Spec> {
223        self.spec
224    }
225
226    /// The wire protocol negotiated for this request, when known.
227    ///
228    /// `None` if the runtime constructed the context outside the dispatch
229    /// path (e.g. unit tests calling handlers directly).
230    pub fn protocol(&self) -> Option<crate::Protocol> {
231        self.protocol
232    }
233
234    /// The procedure path the client requested, `"/package.Service/Method"`.
235    ///
236    /// Always present when constructed by the dispatch path: it is taken
237    /// from the request URI, so it is populated whenever a handler is
238    /// dispatched — including dispatch through the dynamic
239    /// [`Router`](crate::Router), which does not supply a
240    /// [`Spec`](crate::Spec). `None` only for hand-built contexts (unit
241    /// tests calling handlers directly, custom dispatch shims). Code that
242    /// must label or gate every request — auth interceptors, span
243    /// builders, rate limiters — should read `path()`, not `spec()`, and
244    /// treat `None` as a misconfigured or synthetic context rather than a
245    /// real RPC.
246    ///
247    /// Compare [`spec()`](Self::spec): that is the registered method's
248    /// *static* metadata, populated when a generated
249    /// `FooServiceServer<T>` dispatcher — or a `register()`-built
250    /// [`Router`](crate::Router) — resolved the route, and
251    /// [`Spec::procedure`](crate::Spec::procedure) is its `&'static str`
252    /// procedure name. When both are present they are identical strings;
253    /// `path()` exists for the cases where `spec()` cannot be.
254    ///
255    /// The leading slash is included to match `Spec::procedure`, the
256    /// `connect-go` `Spec.Procedure` convention, and `http::Uri::path()`
257    /// for any HTTP request that reached the dispatch layer. To compare
258    /// against [`Dispatcher::lookup`](crate::Dispatcher::lookup) keys
259    /// (which omit it), use `path.strip_prefix('/').unwrap_or(path)`.
260    pub fn path(&self) -> Option<&str> {
261        self.path.as_deref()
262    }
263
264    /// Remote peer socket address, if the transport recorded one.
265    ///
266    /// Present when the request arrived through
267    /// [`Server::serve`](crate::server::Server::serve) (plain) or
268    /// `Server::with_tls(...)` (TLS), or any integration that inserts
269    /// [`PeerAddr`](crate::server::PeerAddr) into the request extensions
270    /// (`connectrpc::axum::serve_tls` does).
271    /// Returns `None` otherwise (e.g. an axum app without a layer that
272    /// captures the connect info), so prefer this over
273    /// `ctx.extensions().get::<PeerAddr>().unwrap()` — the latter compiles,
274    /// passes in unit tests, and panics in production behind a transport
275    /// that didn't insert it.
276    #[cfg(feature = "server")]
277    #[cfg_attr(docsrs, doc(cfg(feature = "server")))]
278    pub fn peer_addr(&self) -> Option<std::net::SocketAddr> {
279        self.extensions
280            .get::<crate::server::PeerAddr>()
281            .map(|p| p.0)
282    }
283
284    /// TLS client certificate chain presented by the peer (leaf first), if any.
285    ///
286    /// Present only when the request arrived over a TLS listener that
287    /// requested a client certificate and the client presented one — see
288    /// [`Server::with_tls`](crate::server::Server::with_tls) and
289    /// `connectrpc::axum::serve_tls`. Returns `None` for plaintext
290    /// transports, for TLS without mutual auth, and for integrations that
291    /// don't insert [`PeerCerts`](crate::server::PeerCerts) into the
292    /// request extensions. Like [`peer_addr`](Self::peer_addr), prefer
293    /// this over a raw `extensions().get()` + `unwrap()`.
294    #[cfg(feature = "server-tls")]
295    #[cfg_attr(docsrs, doc(cfg(feature = "server-tls")))]
296    pub fn peer_certs(&self) -> Option<&[rustls::pki_types::CertificateDer<'static>]> {
297        self.extensions
298            .get::<crate::server::PeerCerts>()
299            .map(|p| &p.0[..])
300    }
301}
302
303// ---------------------------------------------------------------------------
304// Response<B>
305// ---------------------------------------------------------------------------
306
307/// Handler response wrapper: a body plus optional response headers,
308/// trailers, and compression hint.
309///
310/// `B` is bounded by [`Encodable<M>`] in the generated service trait so
311/// handlers can return the owned message `M` (the common case), or any
312/// type that encodes to the same wire bytes.
313///
314/// # Happy path
315///
316/// [`Response::ok`] is the bare-body shorthand:
317///
318/// ```rust,ignore
319/// async fn say(&self, _ctx: RequestContext, req: OwnedSayRequestView)
320///     -> ServiceResult<SayResponse>
321/// {
322///     Response::ok(SayResponse { sentence: reply, ..Default::default() })
323/// }
324/// ```
325///
326/// # With metadata
327///
328/// ```rust,ignore
329/// Ok(Response::new(reply)
330///     .with_header("x-request-id", id)
331///     .with_trailer("x-timing", elapsed))
332/// ```
333#[derive(Debug, Clone)]
334pub struct Response<B> {
335    /// The response body.
336    pub body: B,
337    /// Response headers to send before the body.
338    pub headers: HeaderMap,
339    /// Trailers to send after the body. Sent as HTTP/2 trailing
340    /// HEADERS for gRPC, or as `trailer-`-prefixed headers / the
341    /// EndStreamResponse JSON for Connect.
342    pub trailers: HeaderMap,
343    /// Whether to compress the response. `None` uses the server's
344    /// compression policy; `Some(false)` disables compression for this
345    /// response, `Some(true)` forces it.
346    pub compress: Option<bool>,
347}
348
349impl<B> Response<B> {
350    /// Shorthand for `Ok(Response::from(body))` — the bare-body happy
351    /// path.
352    ///
353    /// Use `Ok(Response::new(body).with_header(...))` when setting
354    /// response metadata; this constructor is for the common case of
355    /// "just the body".
356    pub fn ok(body: B) -> ServiceResult<B> {
357        Ok(Self::from(body))
358    }
359
360    /// Wrap a body with empty response metadata.
361    pub fn new(body: B) -> Self {
362        Self {
363            body,
364            headers: HeaderMap::new(),
365            trailers: HeaderMap::new(),
366            compress: None,
367        }
368    }
369
370    /// Append a response header.
371    ///
372    /// Uses [`HeaderMap::append`], so calling twice with the same name
373    /// accumulates values rather than replacing.
374    ///
375    /// # Panics
376    ///
377    /// Panics if `name` or `value` cannot be converted into the
378    /// corresponding header type (invalid characters, non-ASCII name,
379    /// etc.). Use [`try_with_header`](Self::try_with_header) for
380    /// dynamic values, or the `headers` field directly for full
381    /// control.
382    #[must_use]
383    pub fn with_header<K, V>(mut self, name: K, value: V) -> Self
384    where
385        K: TryInto<HeaderName>,
386        K::Error: std::fmt::Debug,
387        V: TryInto<HeaderValue>,
388        V::Error: std::fmt::Debug,
389    {
390        self.headers
391            .append(name.try_into().unwrap(), value.try_into().unwrap());
392        self
393    }
394
395    /// Append a response header, returning an error if `name` or
396    /// `value` is invalid.
397    ///
398    /// Non-panicking sibling of [`with_header`](Self::with_header) for
399    /// dynamic values. Uses [`HeaderMap::append`], so repeated calls
400    /// accumulate.
401    pub fn try_with_header<K, V>(mut self, name: K, value: V) -> Result<Self, http::Error>
402    where
403        K: TryInto<HeaderName>,
404        K::Error: Into<http::Error>,
405        V: TryInto<HeaderValue>,
406        V::Error: Into<http::Error>,
407    {
408        self.headers.append(
409            name.try_into().map_err(Into::into)?,
410            value.try_into().map_err(Into::into)?,
411        );
412        Ok(self)
413    }
414
415    /// Append a response trailer.
416    ///
417    /// Uses [`HeaderMap::append`], so calling twice with the same name
418    /// accumulates values rather than replacing.
419    ///
420    /// # Panics
421    ///
422    /// Panics if `name` or `value` cannot be converted into the
423    /// corresponding header type. Use
424    /// [`try_with_trailer`](Self::try_with_trailer) for dynamic
425    /// values, or the `trailers` field directly for full control.
426    #[must_use]
427    pub fn with_trailer<K, V>(mut self, name: K, value: V) -> Self
428    where
429        K: TryInto<HeaderName>,
430        K::Error: std::fmt::Debug,
431        V: TryInto<HeaderValue>,
432        V::Error: std::fmt::Debug,
433    {
434        self.trailers
435            .append(name.try_into().unwrap(), value.try_into().unwrap());
436        self
437    }
438
439    /// Append a response trailer, returning an error if `name` or
440    /// `value` is invalid.
441    ///
442    /// Non-panicking sibling of [`with_trailer`](Self::with_trailer)
443    /// for dynamic values. Uses [`HeaderMap::append`], so repeated
444    /// calls accumulate.
445    pub fn try_with_trailer<K, V>(mut self, name: K, value: V) -> Result<Self, http::Error>
446    where
447        K: TryInto<HeaderName>,
448        K::Error: Into<http::Error>,
449        V: TryInto<HeaderValue>,
450        V::Error: Into<http::Error>,
451    {
452        self.trailers.append(
453            name.try_into().map_err(Into::into)?,
454            value.try_into().map_err(Into::into)?,
455        );
456        Ok(self)
457    }
458
459    /// Override the server's compression policy for this response.
460    ///
461    /// `true` forces compression, `false` disables it, `None` (or
462    /// never calling this) defers to the server's policy.
463    #[must_use]
464    pub fn compress(mut self, enabled: impl Into<Option<bool>>) -> Self {
465        self.compress = enabled.into();
466        self
467    }
468
469    /// Replace the body, preserving headers/trailers/compression.
470    pub fn map_body<C>(self, f: impl FnOnce(B) -> C) -> Response<C> {
471        Response {
472            body: f(self.body),
473            headers: self.headers,
474            trailers: self.trailers,
475            compress: self.compress,
476        }
477    }
478}
479
480impl<B> From<B> for Response<B> {
481    fn from(body: B) -> Self {
482        Self::new(body)
483    }
484}
485
486impl<T> Response<ServiceStream<T>> {
487    /// Wrap a streaming body, boxing and unsize-coercing it to
488    /// [`ServiceStream<T>`]. Handles the explicit coercion that
489    /// `Ok(Box::pin(s).into())` would otherwise need.
490    pub fn stream(s: impl Stream<Item = Result<T, ConnectError>> + Send + 'static) -> Self {
491        Self::new(Box::pin(s))
492    }
493
494    /// Shorthand for `Ok(Response::stream(s))` — the bare-stream
495    /// happy path.
496    pub fn stream_ok(
497        s: impl Stream<Item = Result<T, ConnectError>> + Send + 'static,
498    ) -> ServiceResult<ServiceStream<T>> {
499        Ok(Self::stream(s))
500    }
501}
502
503/// Result type returned by handler trait methods.
504///
505/// `B` is the body type — typically the owned response message, or any
506/// `impl Encodable<M>`.
507pub type ServiceResult<B> = Result<Response<B>, ConnectError>;
508
509/// Boxed `Send` stream of `Result<T, ConnectError>`.
510///
511/// Used as the request type for client/bidi-streaming handlers and the
512/// body type for server/bidi-streaming responses.
513///
514/// For an inbound request stream, `None` means the client finished the
515/// stream cleanly; `Some(Err(..))` means the stream ended abnormally — a
516/// decode failure or a request body that failed mid-upload (truncated or
517/// broken transport). Treat only `None` as a complete stream; propagating
518/// the error with `?` fails the RPC, which is the right default for
519/// handlers that aggregate inbound messages.
520pub type ServiceStream<T> = Pin<Box<dyn Stream<Item = Result<T, ConnectError>> + Send>>;
521
522/// The inbound request stream a client/bidi-streaming handler receives:
523/// [`ServiceStream`] of [`StreamMessage`](crate::StreamMessage) items.
524///
525/// Pure sugar for the composed type — generated handler traits spell their
526/// parameters with this alias so signatures stay readable.
527pub type InboundStream<M> = ServiceStream<crate::StreamMessage<M>>;
528
529/// Encoded message bytes, either contiguous or split into reference-counted
530/// segments.
531///
532/// Concatenating [`segments`](Self::segments) always yields the message's wire
533/// bytes; how they are divided is an artifact of how the body was encoded and
534/// carries no protocol meaning. Envelope framing has never depended on HTTP
535/// frame boundaries, so a segmented body reaches the peer as the same message.
536///
537/// The single-buffer case is kept unboxed: a small message that was never
538/// worth segmenting costs no allocation to carry.
539#[derive(Debug, Clone)]
540pub enum EncodedBody {
541    /// One contiguous buffer — what a non-segmenting encode produces.
542    Contiguous(Bytes),
543    /// Several buffers, concatenating to the message's wire bytes.
544    Segmented(Vec<Bytes>),
545}
546
547impl EncodedBody {
548    /// Build from a rope's segments, collapsing the trivial cases so callers
549    /// never see a needless `Vec` for zero or one segment.
550    #[must_use]
551    pub fn from_segments(mut segments: Vec<Bytes>) -> Self {
552        match segments.len() {
553            0 => Self::Contiguous(Bytes::new()),
554            1 => Self::Contiguous(segments.pop().unwrap_or_default()),
555            _ => Self::Segmented(segments),
556        }
557    }
558
559    /// Total encoded length across all segments.
560    #[must_use]
561    pub fn len(&self) -> usize {
562        match self {
563            Self::Contiguous(b) => b.len(),
564            Self::Segmented(v) => v.iter().map(Bytes::len).sum(),
565        }
566    }
567
568    /// Whether the encoded message is empty.
569    #[must_use]
570    pub fn is_empty(&self) -> bool {
571        self.len() == 0
572    }
573
574    /// The segments in wire order.
575    #[must_use]
576    pub fn segments(&self) -> &[Bytes] {
577        match self {
578            Self::Contiguous(b) => std::slice::from_ref(b),
579            Self::Segmented(v) => v,
580        }
581    }
582
583    /// Flatten to a single contiguous buffer, copying only when segmented.
584    ///
585    /// This is the escape hatch for paths that genuinely need one buffer
586    /// (compression, JSON, base64) — it gives back exactly what a
587    /// non-segmenting encode would have produced.
588    #[must_use]
589    pub fn into_contiguous(self) -> Bytes {
590        match self {
591            Self::Contiguous(b) => b,
592            Self::Segmented(v) => {
593                let mut out = BytesMut::with_capacity(v.iter().map(Bytes::len).sum());
594                for segment in &v {
595                    out.extend_from_slice(segment);
596                }
597                out.freeze()
598            }
599        }
600    }
601}
602
603impl From<Bytes> for EncodedBody {
604    fn from(bytes: Bytes) -> Self {
605        Self::Contiguous(bytes)
606    }
607}
608
609// ---------------------------------------------------------------------------
610// Encodable<M>
611// ---------------------------------------------------------------------------
612
613/// Encodes to the same wire bytes as proto message `M`.
614///
615/// This is the bound on the response body in generated trait methods.
616/// Provided implementations:
617/// - the owned `M` itself (blanket `M: Message + JsonSerialize` below);
618/// - `MView<'_>` and [`OwnedView<MView<'static>>`](buffa::view::OwnedView),
619///   emitted by codegen per RPC output type;
620/// - [`MaybeBorrowed<M, V>`] for handlers that conditionally return
621///   either;
622/// - [`StreamMessage<M>`](crate::StreamMessage) for echoing inbound
623///   stream items back out (re-encodes from the retained wire bytes);
624/// - [`PreEncoded`] for handlers that encode a non-`'static` view
625///   internally and pass the bytes across the handler boundary.
626///
627/// # Contract
628///
629/// Implementations must produce bytes that decode as a valid `M` in
630/// the given format.
631///
632/// `encode` is fallible: the owned-message impl never errors. The
633/// view-body impls are proto-only (view types lack `Serialize`) and return
634/// [`ErrorCode::Unimplemented`](crate::ErrorCode::Unimplemented) for
635/// `CodecFormat::Json`. [`PreEncoded`] supports both codecs but the JSON
636/// path is a slow fallback (decode + re-serialize) — see its
637/// `# Codec behaviour` doc.
638pub trait Encodable<M> {
639    /// Encode `self` as wire bytes for `M` in the requested format.
640    fn encode(&self, codec: CodecFormat) -> Result<Bytes, ConnectError>;
641
642    /// Encode `self` as wire bytes that may arrive in several reference-counted
643    /// segments rather than one contiguous buffer.
644    ///
645    /// Concatenating the segments yields exactly what [`encode`](Self::encode)
646    /// would have returned, so this is an optimization and never a wire-format
647    /// difference. A payload the encoder can hand over by reference count — a
648    /// large `bytes::Bytes` field, or a view field borrowed from the buffer the
649    /// view was decoded from — becomes its own segment instead of being copied
650    /// into the output.
651    ///
652    /// The default implementation returns [`encode`](Self::encode)'s single
653    /// buffer, which is always correct. Overriding it is worthwhile only for a
654    /// body that can carry a large payload by reference; a body whose fields
655    /// are `String` or `Vec<u8>` has nothing to hand over, and segmenting it
656    /// would add the rope's cost for no saving.
657    ///
658    /// How large a payload has to be before it earns its own segment is the
659    /// framing layer's decision, not the implementation's — anything smaller
660    /// is copied into the framing buffer downstream regardless, so a smaller
661    /// threshold spends effort without saving a copy. Implementations that
662    /// need to encode a view should call
663    /// [`__codegen::encode_view_body_segments`](crate::__codegen::encode_view_body_segments),
664    /// which applies that threshold for them.
665    ///
666    /// # Errors
667    ///
668    /// Same conditions as [`encode`](Self::encode).
669    fn encode_segments(&self, codec: CodecFormat) -> Result<EncodedBody, ConnectError> {
670        self.encode(codec).map(EncodedBody::from)
671    }
672}
673
674impl<M: Message + JsonSerialize> Encodable<M> for M {
675    fn encode(&self, codec: CodecFormat) -> Result<Bytes, ConnectError> {
676        match codec {
677            CodecFormat::Proto => Ok(self.encode_to_bytes()),
678            CodecFormat::Json => encode_json(self),
679        }
680    }
681
682    // Deliberately does not override `encode_segments`. An owned message
683    // holds its `string` and `bytes` fields as `String` / `Vec<u8>` under the
684    // default codegen mapping, and neither can be handed over by reference
685    // count, so a rope here would capture nothing and only add its own cost.
686    // The win lives on the view path, which borrows its fields out of a
687    // buffer a rope can capture from.
688}
689
690/// Encode a view body via [`ViewEncode`] for [`CodecFormat::Proto`], or
691/// return [`ErrorCode::Unimplemented`](crate::ErrorCode::Unimplemented)
692/// for [`CodecFormat::Json`] (view types don't implement `Serialize`).
693///
694/// Used by codegen-emitted `impl Encodable<Foo> for FooView<'_>` /
695/// `impl Encodable<Foo> for OwnedView<FooView<'static>>` blocks. A
696/// runtime blanket on [`OwnedView`](buffa::view::OwnedView) would
697/// conflict with the `M: Message + JsonSerialize` blanket above (coherence
698/// can't rule out upstream adding `Message`/`Serialize` for
699/// `OwnedView`), so the impls are emitted per output type instead.
700#[doc(hidden)]
701pub fn encode_view_body<'a, V: ViewEncode<'a>>(
702    view: &V,
703    codec: CodecFormat,
704) -> Result<Bytes, ConnectError> {
705    match codec {
706        // Not `encode_to_bytes`, which panics past the 2 GiB protobuf limit:
707        // an oversized response is a request-shaped input reaching a server,
708        // and the segmented sibling already reports it as an error. The work
709        // is the same either way — `encode_to_bytes` runs these two passes
710        // internally.
711        CodecFormat::Proto => {
712            let mut cache = buffa::SizeCache::new();
713            let size = checked_response_size(view.compute_size(&mut cache))?;
714            let mut buf = BytesMut::with_capacity(size);
715            view.write_to(&mut cache, &mut buf);
716            Ok(buf.freeze())
717        }
718        CodecFormat::Json => Err(ConnectError::unimplemented(
719            "view-body responses do not support the JSON codec; return the owned message type for JSON-serving handlers",
720        )),
721    }
722}
723
724/// Whether a response of `size` bytes should be encoded through a rope, given
725/// that its captures would alias a `backing` buffer of `backing_len` bytes.
726///
727/// Two ways a rope loses. A response below one segment has nothing large
728/// enough to capture, so the rope is pure overhead. And a small response
729/// derived from a large request would capture slices of that request's buffer,
730/// keeping the whole allocation alive until the response finishes flushing —
731/// a handler that answers a 64 MiB upload with a 32 KiB summary would hold
732/// 64 MiB per in-flight response where it used to hold 32 KiB. Copying is
733/// cheaper than that. When the response is at least half the buffer it borrows from,
734/// the buffer was going to stay alive anyway and the capture is free.
735fn worth_segmenting(size: usize, backing_len: usize, min_segment: usize) -> bool {
736    size >= min_segment && size.saturating_mul(2) >= backing_len
737}
738
739/// Merge each *run* of consecutive sub-`min_segment` segments into one.
740///
741/// A rope flushes its pending tail before each capture, so a view with several
742/// large fields yields alternating tag/length fragments and captured payloads.
743/// Emitted as-is those fragments become their own HTTP data frames, each a
744/// handful of bytes behind a 9-byte HTTP/2 frame header. Merging a run costs a
745/// copy proportional to the fragments, not the payload.
746///
747/// An isolated fragment therefore stays its own segment: it has no small
748/// neighbour to join, and folding it into an adjacent capture would mean
749/// allocating and copying that capture, which is the one cost this whole path
750/// exists to avoid. It is still re-copied into a run of its own — a handful of
751/// bytes — so what survives untouched is the capture, not the fragment. The
752/// alternating shape above thus keeps one 9-byte frame header per captured
753/// field, a fixed price per field paid to leave the payloads themselves
754/// un-copied.
755fn coalesce_small_runs(segments: Vec<Bytes>, min_segment: usize) -> Vec<Bytes> {
756    if segments.len() < 2 {
757        return segments;
758    }
759    let mut out: Vec<Bytes> = Vec::with_capacity(segments.len());
760    let mut pending = BytesMut::new();
761    for segment in segments {
762        if segment.len() >= min_segment {
763            if !pending.is_empty() {
764                out.push(std::mem::take(&mut pending).freeze());
765            }
766            out.push(segment);
767        } else {
768            pending.extend_from_slice(&segment);
769        }
770    }
771    if !pending.is_empty() {
772        out.push(pending.freeze());
773    }
774    out
775}
776
777/// Reject a response larger than protobuf can encode, as an error rather than
778/// a panic — this runs on a server, where the size is a function of what a
779/// caller asked for.
780fn checked_response_size(size: u32) -> Result<usize, ConnectError> {
781    buffa::checked_encode_size(size)
782        .map(|size| size as usize)
783        .map_err(|_| {
784            ConnectError::internal("response message exceeds the 2 GiB protobuf size limit")
785        })
786}
787
788/// Encode a view body, capturing its large borrowed fields by reference count
789/// instead of copying them.
790///
791/// This is where buffa 0.9's rope pays. A view's fields are slices into the
792/// buffer it was decoded from, so a rope told about that buffer can take a
793/// large field by reference, and the encode then costs the same whatever the
794/// payload weighs. The `view_rope_encode` benchmark in `benches/rpc` measures the
795/// curve; above the threshold the encode goes flat, because only the framing
796/// is still being written.
797///
798/// `backing` must be the buffer this view was decoded from. A rope pointed
799/// anywhere else captures nothing and is slower than a contiguous encode — it
800/// still produces correct bytes, so the cost of getting this wrong is silent.
801/// A caller with no buffer to give should use [`encode_view_body`].
802///
803/// The threshold below which a payload is not worth its own segment is the
804/// framing layer's, applied here so callers cannot pick a worse one: anything
805/// smaller is copied into the framing buffer downstream regardless, and a
806/// message can clear a smaller gate while none of its individual fields do,
807/// which spends the rope's cost and captures nothing. Matching the framing
808/// threshold also makes every segment map to exactly one body frame.
809///
810/// # Errors
811///
812/// [`ErrorCode::Unimplemented`](crate::ErrorCode::Unimplemented) for
813/// [`CodecFormat::Json`], as [`encode_view_body`].
814#[doc(hidden)]
815pub fn encode_view_body_segments<'a, V: ViewEncode<'a>>(
816    view: &V,
817    backing: &Bytes,
818    codec: CodecFormat,
819) -> Result<EncodedBody, ConnectError> {
820    encode_view_body_with_min_segment(view, backing, codec, crate::envelope::MIN_CHAIN_SIZE)
821}
822
823/// [`encode_view_body_segments`] with the segment threshold spelled out, so
824/// tests and benchmarks can sweep it. Production callers take the framing
825/// layer's threshold via [`encode_view_body_segments`].
826///
827/// # Errors
828///
829/// As [`encode_view_body_segments`].
830#[doc(hidden)]
831pub fn encode_view_body_with_min_segment<'a, V: ViewEncode<'a>>(
832    view: &V,
833    backing: &Bytes,
834    codec: CodecFormat,
835    min_segment: usize,
836) -> Result<EncodedBody, ConnectError> {
837    match codec {
838        CodecFormat::Json => Err(ConnectError::unimplemented(
839            "view-body responses do not support the JSON codec; return the owned message type for JSON-serving handlers",
840        )),
841        CodecFormat::Proto => {
842            let mut cache = buffa::SizeCache::new();
843            let size = checked_response_size(view.compute_size(&mut cache))?;
844
845            if !worth_segmenting(size, backing.len(), min_segment) {
846                let mut buf = BytesMut::with_capacity(size);
847                view.write_to(&mut cache, &mut buf);
848                return Ok(EncodedBody::Contiguous(buf.freeze()));
849            }
850
851            // Known cost: a rope's tail starts empty and grows by doubling,
852            // and every field too small to capture lands in it. A message that
853            // clears the gate while none of its fields do therefore copies
854            // itself roughly twice over instead of once into a sized buffer.
855            // buffa 0.9 exposes no way to pre-size the tail; until it does,
856            // that shape pays for a rope that captures nothing.
857            let mut rope = buffa::Rope::with_min_segment(min_segment).with_backing(backing.clone());
858            view.write_to(&mut cache, &mut rope);
859            Ok(EncodedBody::from_segments(coalesce_small_runs(
860                rope.into_segments(),
861                min_segment,
862            )))
863        }
864    }
865}
866
867// ---------------------------------------------------------------------------
868// MaybeBorrowed
869// ---------------------------------------------------------------------------
870
871/// Either an owned message `M` or a borrowing view `V`, both
872/// [`Encodable<M>`].
873///
874/// Use this when a handler conditionally passes the request through
875/// unchanged (return the view, zero allocations) versus modifying it
876/// (clone to owned, mutate, return owned). The single concrete return
877/// type satisfies the `impl Encodable<M>` bound on the generated trait.
878///
879/// This is not [`std::borrow::Cow`]: `V` is a separate
880/// [`Encodable<M>`] type (e.g. `MView<'a>` or `OwnedView<MView>`),
881/// not a `&M`, and there is no `ToOwned` relationship between the
882/// arms — each encodes independently.
883///
884/// ```rust,ignore
885/// async fn redact(&self, _ctx: RequestContext, req: ServiceRequest<'_, Record>)
886///     -> ServiceResult<MaybeBorrowed<Record, OwnedRecordView>>
887/// {
888///     if req.email.is_empty() && req.ssn.is_empty() {
889///         // pass-through: rebuild a 'static view from the request bytes
890///         return Response::ok(MaybeBorrowed::Borrowed(req.to_owned_view()));
891///     }
892///     let mut owned = req.to_owned_message();
893///     owned.email.clear();
894///     owned.ssn.clear();
895///     Response::ok(MaybeBorrowed::Owned(owned))
896/// }
897/// ```
898///
899/// # Codec compatibility
900///
901/// The `Borrowed` arm only encodes for [`CodecFormat::Proto`]. JSON
902/// clients receive an `unimplemented` error; if your service must
903/// support JSON, return `Owned` (or just the owned message) on every
904/// path.
905#[derive(Debug, Clone)]
906pub enum MaybeBorrowed<M, V> {
907    /// An owned message body.
908    Owned(M),
909    /// A borrowing body that encodes to the same wire bytes as `M`.
910    Borrowed(V),
911}
912
913impl<M, V> Encodable<M> for MaybeBorrowed<M, V>
914where
915    // satisfied via the blanket impl for M: Message + JsonSerialize
916    M: Encodable<M>,
917    V: Encodable<M>,
918{
919    fn encode(&self, codec: CodecFormat) -> Result<Bytes, ConnectError> {
920        match self {
921            Self::Owned(m) => m.encode(codec),
922            Self::Borrowed(v) => v.encode(codec),
923        }
924    }
925
926    /// Forwards to the wrapped body rather than taking the contiguous
927    /// default. `Borrowed` is the arm handlers reach for to avoid copying, so
928    /// it is exactly the arm that must not lose the segmented encode by being
929    /// wrapped — the wrapper would otherwise quietly undo the reason it was
930    /// chosen.
931    fn encode_segments(&self, codec: CodecFormat) -> Result<EncodedBody, ConnectError> {
932        match self {
933            Self::Owned(m) => m.encode_segments(codec),
934            Self::Borrowed(v) => v.encode_segments(codec),
935        }
936    }
937}
938
939// ---------------------------------------------------------------------------
940// PreEncoded
941// ---------------------------------------------------------------------------
942
943/// Pre-encoded protobuf response body for message type `M`.
944///
945/// Use when the handler builds and encodes a borrowing view internally —
946/// e.g. a `FooView<'a>` borrowing from a local snapshot — rather than
947/// returning the view itself. The `'static` bound on `Handler::Body` (and
948/// on streaming items, see the `use<Self>` note in the
949/// [`StreamingHandler`](crate::StreamingHandler) docs) means a view with a
950/// non-`'static` lifetime can't cross the handler
951/// boundary; `PreEncoded` carries the bytes across instead.
952///
953/// The `M` type parameter is a compile-time witness for which RPC output
954/// type the bytes encode. Three construction paths, in decreasing order
955/// of compile-time guarantee:
956///
957/// - [`from_message(&m)`](PreEncoded::from_message) — encodes an owned
958///   `M`; the receiver type *is* the witness.
959/// - [`from_view(&view)`](PreEncoded::from_view) — encodes a borrowing
960///   view; `MessageView::Owned = M` is the witness.
961/// - [`from_bytes_unchecked(bytes)`](PreEncoded::from_bytes_unchecked) —
962///   wraps already-encoded bytes from elsewhere (a cache, storage,
963///   another service). No witness; you're asserting the bytes decode as
964///   `M`.
965///
966/// `from_message` and `from_view` produce the same `PreEncoded<M>` type,
967/// so a stream can mix items built either way (e.g. a cache-hit path
968/// returning the cached owned `M`, a cache-miss path building a view from
969/// a snapshot) — the same role [`MaybeBorrowed`] fills for unary
970/// handlers, but with the encode happening eagerly inside the stream
971/// body.
972///
973/// # Streaming example
974///
975/// The motivating shape — a server-streaming handler that builds and
976/// encodes per-item views borrowing from a local store snapshot, then
977/// yields the bytes:
978///
979/// ```rust,ignore
980/// use connectrpc::{PreEncoded, Response, RequestContext, ServiceResult, ServiceStream};
981///
982/// async fn watch(
983///     &self,
984///     _ctx: RequestContext,
985///     req: OwnedWatchRequestView,
986/// ) -> ServiceResult<ServiceStream<PreEncoded<WatchResponse>>> {
987///     let store = self.store.clone();
988///     let stream = futures::stream::unfold(store, |store| async move {
989///         let snapshot = store.load();
990///         // `view` borrows from `snapshot`; encode while the borrow is live.
991///         let view = build_view_from_snapshot(&snapshot);
992///         let item = PreEncoded::from_view(&view);
993///         Some((Ok(item), store))
994///     });
995///     Response::stream_ok(stream)
996/// }
997/// ```
998///
999/// For a unary handler, the same pattern applies — return
1000/// `ServiceResult<PreEncoded<MyResponse>>`.
1001///
1002/// # Codec behaviour
1003///
1004/// `PreEncoded` is optimized for the `proto` codec: the wrapped bytes are
1005/// passed through verbatim with no re-encoding. The motivating use case
1006/// (high-throughput fanout) is proto-only.
1007///
1008/// For the `json` codec, `PreEncoded` falls back to decoding the bytes as
1009/// `M` and re-serializing as JSON. **This is correct but not fast** — a
1010/// full proto decode plus a JSON serialize per response (or per stream
1011/// item). The fallback exists so that registering a `PreEncoded` handler
1012/// on a JSON-capable router degrades gracefully instead of returning a
1013/// runtime error. If your service serves a meaningful JSON traffic share,
1014/// build and return the owned message (or [`MaybeBorrowed::Owned`])
1015/// instead — that lets the codec layer pick the right encoding without
1016/// the proto round-trip.
1017///
1018/// If the wrapped bytes don't decode as `M` (e.g. you passed mismatched
1019/// bytes to [`from_bytes_unchecked`](PreEncoded::from_bytes_unchecked)),
1020/// the JSON path returns an [`internal`](crate::ErrorCode::Internal)
1021/// error at the server; the proto path passes the bytes through and the
1022/// client sees a decode error.
1023///
1024/// ## Codec-dependent fidelity
1025///
1026/// The proto path is byte-exact; the JSON path is **only as faithful as
1027/// decoding the bytes to an owned `M` and re-serializing**. The two
1028/// diverge when the wrapped bytes carry information not representable in
1029/// `M` itself:
1030///
1031/// - **Unknown fields** (proto bytes encoded against a *newer* schema
1032///   than the server's `M`) are preserved on the proto path and dropped
1033///   on the JSON path. This matters only for
1034///   [`from_bytes_unchecked`](PreEncoded::from_bytes_unchecked) bytes
1035///   sourced externally; bytes produced by
1036///   [`from_message`](PreEncoded::from_message) /
1037///   [`from_view`](PreEncoded::from_view) cannot carry unknown fields.
1038/// - **Non-canonical proto encodings** (out-of-order fields, redundant
1039///   length prefixes, repeated non-`repeated` fields) are passed through
1040///   verbatim on the proto path and normalized by the decode on the JSON
1041///   path.
1042///
1043/// If byte-exact fidelity across codecs matters (e.g. signature
1044/// verification, content-addressed storage), do not use `PreEncoded` with
1045/// JSON-capable routes.
1046///
1047/// ## Cost is selected by the client
1048///
1049/// The codec is chosen per-request by the client's `Content-Type` header.
1050/// For a service that adopted `PreEncoded` for proto throughput, a client
1051/// sending JSON requests (intentionally, by misconfiguration, or
1052/// adversarially) shifts those requests onto the slow decode-reserialize
1053/// path. The marginal cost is bounded by the response size and is usually
1054/// small relative to the handler's own work, but a streaming RPC pays it
1055/// per item. A service that wants to *enforce* proto-only should reject
1056/// non-proto `Content-Type` at the middleware layer (e.g. an axum
1057/// middleware that returns `415 Unsupported Media Type`) rather than rely
1058/// on the body type — that keeps the policy outside the handler and
1059/// applies before the request body is read.
1060///
1061/// # Contract
1062///
1063/// `PreEncoded` is a transparent byte container — it does not validate
1064/// the wrapped bytes on the proto path. [`PreEncoded::from_view`] gives a
1065/// compile-time witness via `MessageView::Owned = M`;
1066/// [`PreEncoded::from_bytes_unchecked`] trusts the caller. Returning bytes
1067/// that don't decode as `M` will produce decode errors on the client (or,
1068/// for JSON clients, an `internal` error from the server-side fallback
1069/// decode).
1070#[must_use = "PreEncoded must be returned from a handler to take effect"]
1071pub struct PreEncoded<M> {
1072    bytes: Bytes,
1073    // `fn() -> M` keeps `PreEncoded<M>` `Send + Sync` regardless of `M`'s
1074    // auto-trait surface (the bytes are owned; `M` is only a type witness).
1075    _marker: PhantomData<fn() -> M>,
1076}
1077
1078// Manual derives: `#[derive(Debug, Clone)]` would add a spurious `M: Debug` /
1079// `M: Clone` bound (PhantomData carries it through to the where-clause).
1080impl<M> std::fmt::Debug for PreEncoded<M> {
1081    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1082        f.debug_tuple("PreEncoded").field(&self.bytes).finish()
1083    }
1084}
1085
1086impl<M> Clone for PreEncoded<M> {
1087    fn clone(&self) -> Self {
1088        Self {
1089            bytes: self.bytes.clone(),
1090            _marker: PhantomData,
1091        }
1092    }
1093}
1094
1095impl<M: Message> PreEncoded<M> {
1096    /// Encode an owned `M` to protobuf bytes.
1097    ///
1098    /// The receiver type is the compile-time witness — there's no way to
1099    /// produce a `PreEncoded<M>` from a `&Other`. This is the right
1100    /// constructor when the handler builds an owned `M` and wants to
1101    /// share the encoding (e.g. encode once, clone the
1102    /// [`Bytes`]-backed `PreEncoded` for N readers in a fanout) or when a
1103    /// stream needs to mix owned-message and view-built items under a
1104    /// single `type Item = PreEncoded<M>`.
1105    ///
1106    /// Equivalent to `PreEncoded::from_bytes_unchecked(m.encode_to_bytes())`,
1107    /// but with `M` enforced by the type system rather than asserted by
1108    /// the caller.
1109    pub fn from_message(msg: &M) -> Self {
1110        Self {
1111            bytes: msg.encode_to_bytes(),
1112            _marker: PhantomData,
1113        }
1114    }
1115
1116    /// Encode a [`ViewEncode`] view to protobuf bytes.
1117    ///
1118    /// The `MessageView<'a, Owned = M>` bound is the compile-time witness
1119    /// that the bytes decode as `M` — passing `OtherView<'a>` won't
1120    /// type-check unless `OtherView::Owned == M`.
1121    pub fn from_view<'a, V>(view: &V) -> Self
1122    where
1123        V: ViewEncode<'a> + MessageView<'a, Owned = M>,
1124    {
1125        Self {
1126            bytes: view.encode_to_bytes(),
1127            _marker: PhantomData,
1128        }
1129    }
1130
1131    /// Wrap already-encoded protobuf bytes without validating them.
1132    ///
1133    /// Use when the bytes come from somewhere with no structural type
1134    /// guarantee — a byte cache, a blob store, a sidecar service. You are
1135    /// asserting the bytes decode as `M`; the proto path does not
1136    /// validate this. In debug builds, the bytes are decoded once as a
1137    /// `debug_assert!` to surface mismatches early.
1138    ///
1139    /// Prefer [`from_message`](PreEncoded::from_message) when you have an
1140    /// owned `M` in hand and [`from_view`](PreEncoded::from_view) when
1141    /// you have a view — both enforce `M` at compile time.
1142    ///
1143    /// Zero-copy for `Bytes` and `Vec<u8>`; passing `&[u8]` allocates and
1144    /// copies.
1145    pub fn from_bytes_unchecked(bytes: impl Into<Bytes>) -> Self {
1146        let bytes = bytes.into();
1147        debug_assert!(
1148            M::decode_from_slice(&bytes).is_ok(),
1149            "PreEncoded::from_bytes_unchecked: bytes do not decode as {}",
1150            std::any::type_name::<M>(),
1151        );
1152        Self {
1153            bytes,
1154            _marker: PhantomData,
1155        }
1156    }
1157}
1158
1159/// Encode an owned `M` to a [`PreEncoded<M>`].
1160///
1161/// Equivalent to [`PreEncoded::from_message`]; provided for `.into()`
1162/// ergonomics.
1163impl<M: Message> From<&M> for PreEncoded<M> {
1164    fn from(msg: &M) -> Self {
1165        Self::from_message(msg)
1166    }
1167}
1168
1169// Coherence: this impl is non-overlapping with the
1170// `impl<M: Message + JsonSerialize> Encodable<M> for M` blanket above for
1171// structural reasons. For the two to overlap, some `T` would have to satisfy
1172// both `T: Encodable<T>` (blanket, with `T: Message + JsonSerialize`) and
1173// `T = PreEncoded<U>` with `T: Encodable<U>` (this impl) for the *same* trait
1174// parameter — i.e. `T = U`, i.e. `PreEncoded<U> = U`, which is infinite. So
1175// the impls cannot overlap even if a future change made `PreEncoded` a
1176// `Message` (which would only add `PreEncoded<M>: Encodable<PreEncoded<M>>` —
1177// a different trait instantiation). No invariant to maintain here.
1178//
1179// The `M: Message + JsonSerialize` bound matches the blanket so a `PreEncoded<M>`
1180// is `Encodable<M>` exactly when an owned `M` would be — and is what makes the
1181// JSON fallback path possible (decode as `M`, re-serialize).
1182impl<M: Message + JsonSerialize> Encodable<M> for PreEncoded<M> {
1183    fn encode(&self, codec: CodecFormat) -> Result<Bytes, ConnectError> {
1184        match codec {
1185            CodecFormat::Proto => Ok(self.bytes.clone()),
1186            // Slow path: decode the proto bytes back to `M`, then serialize
1187            // as JSON. This exists for correctness (JSON clients should get
1188            // a response, not `unimplemented`), not throughput; the owned
1189            // message path skips the proto round-trip and is preferable for
1190            // JSON-heavy services. See the type-level docs.
1191            CodecFormat::Json => {
1192                let msg = M::decode_from_slice(&self.bytes).map_err(|e| {
1193                    ConnectError::internal(format!(
1194                        "pre-encoded bytes did not decode as {}: {e}",
1195                        std::any::type_name::<M>(),
1196                    ))
1197                })?;
1198                encode_json(&msg)
1199            }
1200        }
1201    }
1202}
1203
1204// ---------------------------------------------------------------------------
1205// EncodedResponse (dispatcher boundary)
1206// ---------------------------------------------------------------------------
1207
1208/// A [`Response`] with the body already encoded to bytes.
1209///
1210/// This is what the [`Dispatcher`](crate::Dispatcher) returns to the
1211/// protocol layer — encoding happens inside the dispatcher so the body
1212/// type stays generic across the trait boundary.
1213pub type EncodedResponse = Response<EncodedBody>;
1214
1215impl<B> Response<B> {
1216    /// Encode the body to bytes via [`Encodable<M>`], preserving
1217    /// response metadata.
1218    #[doc(hidden)] // exposed for dispatcher::codegen (generated code)
1219    pub fn encode<M>(self, codec: CodecFormat) -> Result<EncodedResponse, ConnectError>
1220    where
1221        B: Encodable<M>,
1222    {
1223        // Bodies that can hand a large payload over by reference count say so
1224        // here; everything else takes the default and returns the same single
1225        // buffer it always did.
1226        let body = self.body.encode_segments(codec)?;
1227        Ok(Response {
1228            body,
1229            headers: self.headers,
1230            trailers: self.trailers,
1231            compress: self.compress,
1232        })
1233    }
1234}
1235
1236#[cfg(test)]
1237mod tests {
1238    use super::*;
1239    use buffa_types::google::protobuf::__buffa::view::StringValueView;
1240    use buffa_types::google::protobuf::StringValue;
1241
1242    /// The invariant the whole segmented path rests on: however the encoder
1243    /// chose to divide the output, concatenating it must reproduce exactly
1244    /// what the contiguous encode produced. A divergence here is a wire-format
1245    /// bug, not a performance one.
1246    ///
1247    /// Swept across thresholds because each one divides the output
1248    /// differently — 1 makes almost every write its own segment, `usize::MAX`
1249    /// never segments at all, and the interesting cases sit between.
1250    #[test]
1251    fn view_segments_concatenate_to_the_contiguous_encoding() {
1252        let buffer = encoded_string_value(&"m".repeat(64 * 1024));
1253        let view = StringValueView::decode_view(&buffer).expect("decode view");
1254        let contiguous = encode_view_body(&view, CodecFormat::Proto).expect("proto encode");
1255
1256        for min_segment in [1usize, 8, 4096, 64 * 1024, usize::MAX] {
1257            let segmented =
1258                encode_view_body_with_min_segment(&view, &buffer, CodecFormat::Proto, min_segment)
1259                    .expect("proto encode");
1260
1261            assert_eq!(
1262                segmented.len(),
1263                contiguous.len(),
1264                "min_segment={min_segment}: length must match"
1265            );
1266            assert_eq!(
1267                segmented.into_contiguous(),
1268                contiguous,
1269                "min_segment={min_segment}: bytes must match"
1270            );
1271        }
1272    }
1273
1274    /// Wire bytes for a `StringValue`, to decode a borrowing view from.
1275    fn encoded_string_value(value: &str) -> Bytes {
1276        Bytes::from(buffa::Message::encode_to_vec(&StringValue::from(value)))
1277    }
1278
1279    #[test]
1280    fn small_views_skip_the_rope() {
1281        // A rope costs more than it saves on a message too small to contain a
1282        // capturable field, so the gate must send those down the contiguous
1283        // path. Pinning it because the regression would be invisible: the
1284        // bytes stay correct and only the encode gets slower.
1285        let buffer = encoded_string_value("small");
1286        let view = StringValueView::decode_view(&buffer).expect("decode view");
1287
1288        let body =
1289            encode_view_body_segments(&view, &buffer, CodecFormat::Proto).expect("proto encode");
1290        assert!(
1291            matches!(body, EncodedBody::Contiguous(_)),
1292            "a message under one segment must not pay for a rope"
1293        );
1294    }
1295
1296    #[test]
1297    fn large_view_fields_are_captured_as_segments() {
1298        // The whole point of the exercise: a field larger than one segment,
1299        // borrowed from the buffer the rope is backed by, is handed over by
1300        // reference instead of copied. If this stops splitting, the encode has
1301        // silently gone back to copying the payload.
1302        let buffer = encoded_string_value(&"x".repeat(64 * 1024));
1303        let view = StringValueView::decode_view(&buffer).expect("decode view");
1304
1305        let body =
1306            encode_view_body_segments(&view, &buffer, CodecFormat::Proto).expect("proto encode");
1307        assert!(
1308            matches!(body, EncodedBody::Segmented(_)),
1309            "a 64 KiB borrowed field must be captured, not copied"
1310        );
1311        assert_eq!(
1312            body.into_contiguous(),
1313            encode_view_body(&view, CodecFormat::Proto).expect("proto encode"),
1314            "segmented output must equal what the contiguous encoder produced"
1315        );
1316    }
1317
1318    #[test]
1319    fn a_small_response_does_not_pin_a_large_request_buffer() {
1320        // Capturing means the response's segments alias the request's buffer,
1321        // which keeps the whole allocation alive until the response finishes
1322        // flushing. For a summary of a large upload that trades a copy for
1323        // holding orders of magnitude more memory per in-flight response, so
1324        // the encoder copies instead.
1325        let big_request = 4 * 1024 * 1024;
1326        let small_response = 64 * 1024;
1327        assert!(
1328            !worth_segmenting(small_response, big_request, 16 * 1024),
1329            "a response this much smaller than its request must not capture"
1330        );
1331
1332        // Returning most of what arrived is the case capture is for: the
1333        // buffer stays alive regardless, so aliasing it costs nothing.
1334        assert!(worth_segmenting(64 * 1024, 66 * 1024, 16 * 1024));
1335
1336        // Still gated on the segment threshold.
1337        assert!(!worth_segmenting(1024, 1024, 16 * 1024));
1338    }
1339
1340    #[test]
1341    fn isolated_fragments_stay_their_own_segments() {
1342        // A rope flushes its tail before each capture, so a multi-field view
1343        // yields alternating small tag/length fragments and large payloads.
1344        // Each fragment's only neighbours are captures, and folding it into
1345        // one would mean copying that capture — the cost this path exists to
1346        // avoid — so it stays its own small frame.
1347        let big = Bytes::from(vec![1u8; 32 * 1024]);
1348        let segments = vec![
1349            Bytes::from_static(b"ab"),
1350            big.clone(),
1351            Bytes::from_static(b"cd"),
1352            big.clone(),
1353            Bytes::from_static(b"ef"),
1354        ];
1355        let merged = coalesce_small_runs(segments, 16 * 1024);
1356
1357        assert_eq!(
1358            merged.len(),
1359            5,
1360            "nothing merges: no fragment is adjacent to another"
1361        );
1362        let total: usize = merged.iter().map(Bytes::len).sum();
1363        assert_eq!(total, 2 + 32 * 1024 + 2 + 32 * 1024 + 2);
1364
1365        // The large payloads must still be the original allocations. This is
1366        // the property the non-merging buys.
1367        assert!(std::ptr::eq(merged[1].as_ptr(), big.as_ptr()));
1368        assert!(std::ptr::eq(merged[3].as_ptr(), big.as_ptr()));
1369    }
1370
1371    #[test]
1372    fn consecutive_fragments_merge_into_one_segment() {
1373        // The case the function does handle: a run of adjacent sub-threshold
1374        // fragments collapses to a single segment, so a rope tail that came
1375        // out in pieces costs one frame rather than one per piece.
1376        let big = Bytes::from(vec![1u8; 32 * 1024]);
1377        let segments = vec![
1378            Bytes::from_static(b"ab"),
1379            Bytes::from_static(b"cd"),
1380            Bytes::from_static(b"ef"),
1381            big.clone(),
1382        ];
1383        let merged = coalesce_small_runs(segments, 16 * 1024);
1384
1385        assert_eq!(merged.len(), 2, "the three fragments become one segment");
1386        assert_eq!(&merged[0][..], b"abcdef");
1387        assert!(
1388            std::ptr::eq(merged[1].as_ptr(), big.as_ptr()),
1389            "merging a run must not copy the capture that follows it"
1390        );
1391    }
1392
1393    #[test]
1394    fn view_segments_without_backing_are_still_correct() {
1395        // A rope pointed at the wrong buffer captures nothing, which costs
1396        // speed but must never cost correctness.
1397        let buffer = encoded_string_value(&"y".repeat(64 * 1024));
1398        let view = StringValueView::decode_view(&buffer).expect("decode view");
1399        let unrelated = Bytes::from_static(b"not the buffer this view came from");
1400
1401        let body =
1402            encode_view_body_segments(&view, &unrelated, CodecFormat::Proto).expect("proto encode");
1403        assert_eq!(
1404            body.into_contiguous(),
1405            encode_view_body(&view, CodecFormat::Proto).expect("proto encode")
1406        );
1407    }
1408
1409    #[test]
1410    #[cfg(feature = "json")]
1411    fn json_encoding_stays_contiguous() {
1412        // JSON is serialized whole, so there is nothing to hand over by
1413        // reference and the segmented call must not pretend otherwise.
1414        let msg = StringValue::from("json");
1415        let body =
1416            Encodable::<StringValue>::encode_segments(&msg, CodecFormat::Json).expect("json");
1417        assert!(matches!(body, EncodedBody::Contiguous(_)));
1418    }
1419
1420    #[test]
1421    fn encoded_body_collapses_trivial_segment_counts() {
1422        assert!(matches!(
1423            EncodedBody::from_segments(vec![]),
1424            EncodedBody::Contiguous(b) if b.is_empty()
1425        ));
1426        assert!(matches!(
1427            EncodedBody::from_segments(vec![Bytes::from_static(b"one")]),
1428            EncodedBody::Contiguous(_)
1429        ));
1430        assert!(matches!(
1431            EncodedBody::from_segments(vec![
1432                Bytes::from_static(b"one"),
1433                Bytes::from_static(b"two")
1434            ]),
1435            EncodedBody::Segmented(_)
1436        ));
1437    }
1438
1439    #[tokio::test]
1440    async fn response_stream_ok_shorthand() {
1441        use futures::StreamExt;
1442        let r: ServiceResult<ServiceStream<i32>> =
1443            Response::stream_ok(futures::stream::iter([Ok(7)]));
1444        let collected: Vec<_> = r.unwrap().body.map(|x| x.unwrap()).collect().await;
1445        assert_eq!(collected, vec![7]);
1446    }
1447
1448    #[test]
1449    fn compress_tristate() {
1450        assert_eq!(Response::new(()).compress(true).compress, Some(true));
1451        assert_eq!(Response::new(()).compress(false).compress, Some(false));
1452        assert_eq!(Response::new(()).compress(None).compress, None);
1453    }
1454
1455    #[test]
1456    fn header_accepts_str() {
1457        let mut h = HeaderMap::new();
1458        h.insert("x-custom", HeaderValue::from_static("v"));
1459        let ctx = RequestContext::new(h);
1460        assert_eq!(ctx.header("x-custom").unwrap(), "v");
1461    }
1462
1463    #[test]
1464    fn response_ok_shorthand() {
1465        let r: ServiceResult<u32> = Response::ok(42);
1466        let r = r.unwrap();
1467        assert_eq!(r.body, 42);
1468        assert!(r.headers.is_empty());
1469    }
1470
1471    #[test]
1472    fn response_from_body() {
1473        let r: Response<StringValue> = StringValue::from("hi").into();
1474        assert_eq!(r.body.value, "hi");
1475        assert!(r.headers.is_empty());
1476        assert!(r.trailers.is_empty());
1477        assert_eq!(r.compress, None);
1478    }
1479
1480    #[test]
1481    fn response_builder() {
1482        let r = Response::new(StringValue::from("hi"))
1483            .with_header("x-a", "1")
1484            .with_trailer("x-b", "2")
1485            .compress(true);
1486        assert_eq!(r.headers.get("x-a").unwrap(), "1");
1487        assert_eq!(r.trailers.get("x-b").unwrap(), "2");
1488        assert_eq!(r.compress, Some(true));
1489    }
1490
1491    #[test]
1492    fn encodable_owned_proto() {
1493        let m = StringValue::from("hello");
1494        let bytes = Encodable::<StringValue>::encode(&m, CodecFormat::Proto).unwrap();
1495        assert_eq!(
1496            StringValue::decode_from_slice(&bytes).unwrap().value,
1497            "hello"
1498        );
1499    }
1500
1501    #[cfg(feature = "json")]
1502    #[test]
1503    fn encodable_owned_json() {
1504        let m = StringValue::from("hello");
1505        let bytes = Encodable::<StringValue>::encode(&m, CodecFormat::Json).unwrap();
1506        assert_eq!(&bytes[..], b"\"hello\"");
1507    }
1508
1509    #[test]
1510    fn response_encode() {
1511        let r = Response::new(StringValue::from("hi")).with_header("x-a", "1");
1512        let enc = r.encode::<StringValue>(CodecFormat::Proto).unwrap();
1513        assert_eq!(enc.headers.get("x-a").unwrap(), "1");
1514        assert_eq!(
1515            StringValue::decode_from_slice(&enc.body.into_contiguous())
1516                .unwrap()
1517                .value,
1518            "hi"
1519        );
1520    }
1521
1522    #[test]
1523    fn request_context_new() {
1524        let mut h = HeaderMap::new();
1525        h.insert("x-custom", HeaderValue::from_static("v"));
1526        let ctx = RequestContext::new(h);
1527        assert_eq!(
1528            ctx.header(HeaderName::from_static("x-custom")).unwrap(),
1529            "v"
1530        );
1531        assert_eq!(ctx.headers().get("x-custom").unwrap(), "v");
1532        assert!(ctx.deadline().is_none());
1533        assert!(ctx.time_remaining().is_none());
1534        assert!(ctx.extensions().is_empty());
1535    }
1536
1537    #[test]
1538    fn request_context_with_deadline() {
1539        let d = Instant::now();
1540        let ctx = RequestContext::new(HeaderMap::new()).with_deadline(Some(d));
1541        assert_eq!(ctx.deadline(), Some(d));
1542    }
1543
1544    #[test]
1545    fn request_context_time_remaining_saturates_at_zero() {
1546        // Deadline in the past — `time_remaining()` should clamp to zero,
1547        // not underflow.
1548        let past = Instant::now() - Duration::from_secs(60);
1549        let ctx = RequestContext::new(HeaderMap::new()).with_deadline(Some(past));
1550        assert_eq!(ctx.time_remaining(), Some(Duration::ZERO));
1551    }
1552
1553    #[test]
1554    fn request_context_time_remaining_future() {
1555        let future = Instant::now() + Duration::from_secs(60);
1556        let ctx = RequestContext::new(HeaderMap::new()).with_deadline(Some(future));
1557        let remaining = ctx.time_remaining().unwrap();
1558        // Some elapsed time between `with_deadline` and the assertion is
1559        // expected; just bound it.
1560        assert!(remaining > Duration::from_secs(55));
1561        assert!(remaining <= Duration::from_secs(60));
1562    }
1563
1564    #[test]
1565    fn request_context_extensions_mut() {
1566        #[derive(Clone, Debug, PartialEq)]
1567        struct Tag(u8);
1568        let mut ctx = RequestContext::new(HeaderMap::new());
1569        ctx.extensions_mut().insert(Tag(1));
1570        assert_eq!(ctx.extensions().get::<Tag>(), Some(&Tag(1)));
1571    }
1572
1573    #[cfg(feature = "server")]
1574    #[test]
1575    fn request_context_peer_addr_absent() {
1576        // No transport inserted `PeerAddr`; the typed accessor returns
1577        // `None` rather than panicking.
1578        let ctx = RequestContext::new(HeaderMap::new());
1579        assert_eq!(ctx.peer_addr(), None);
1580    }
1581
1582    #[cfg(feature = "server")]
1583    #[test]
1584    fn request_context_peer_addr_present() {
1585        use std::net::{IpAddr, Ipv4Addr, SocketAddr};
1586        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080);
1587        let mut ext = http::Extensions::new();
1588        ext.insert(crate::server::PeerAddr(addr));
1589        let ctx = RequestContext::new(HeaderMap::new()).with_extensions(ext);
1590        assert_eq!(ctx.peer_addr(), Some(addr));
1591    }
1592
1593    #[cfg(feature = "server-tls")]
1594    #[test]
1595    fn request_context_peer_certs_absent() {
1596        let ctx = RequestContext::new(HeaderMap::new());
1597        assert!(ctx.peer_certs().is_none());
1598    }
1599
1600    #[test]
1601    fn response_map_body_preserves_metadata() {
1602        let r = Response::new(2u32)
1603            .with_header("x-h", "1")
1604            .with_trailer("x-t", "2")
1605            .compress(true);
1606        let r = r.map_body(|n| n.to_string());
1607        assert_eq!(r.body, "2");
1608        assert_eq!(r.headers.get("x-h").unwrap(), "1");
1609        assert_eq!(r.trailers.get("x-t").unwrap(), "2");
1610        assert_eq!(r.compress, Some(true));
1611    }
1612
1613    #[tokio::test]
1614    async fn response_stream_yields_items() {
1615        use futures::StreamExt;
1616        let r: Response<ServiceStream<i32>> =
1617            Response::stream(futures::stream::iter([Ok(1), Ok(2), Ok(3)]));
1618        let collected: Vec<_> = r.body.map(|x| x.unwrap()).collect().await;
1619        assert_eq!(collected, vec![1, 2, 3]);
1620    }
1621
1622    #[test]
1623    #[should_panic]
1624    fn with_header_panics_on_invalid_name() {
1625        let _ = Response::new(()).with_header("invalid header name", "v");
1626    }
1627
1628    #[test]
1629    fn try_with_header_errors_on_invalid_name() {
1630        let err = Response::new(())
1631            .try_with_header("invalid header name", "v")
1632            .unwrap_err();
1633        assert!(err.is::<http::header::InvalidHeaderName>());
1634    }
1635
1636    #[test]
1637    fn try_with_header_ok_appends() {
1638        let r = Response::new(())
1639            .try_with_header("x-a", "1")
1640            .unwrap()
1641            .try_with_header("x-a", "2")
1642            .unwrap();
1643        let vals: Vec<_> = r.headers.get_all("x-a").iter().collect();
1644        assert_eq!(vals.len(), 2);
1645    }
1646
1647    #[test]
1648    fn try_with_trailer_errors_on_invalid_value() {
1649        // Newlines are not permitted in header values.
1650        let err = Response::new(())
1651            .try_with_trailer("x-t", "bad\nvalue")
1652            .unwrap_err();
1653        assert!(err.is::<http::header::InvalidHeaderValue>());
1654    }
1655
1656    #[test]
1657    fn encode_view_body_proto() {
1658        use buffa_types::google::protobuf::__buffa::view::StringValueView;
1659        let v = StringValueView {
1660            value: "hi",
1661            ..Default::default()
1662        };
1663        let bytes = encode_view_body(&v, CodecFormat::Proto).unwrap();
1664        assert_eq!(StringValue::decode_from_slice(&bytes).unwrap().value, "hi");
1665    }
1666
1667    #[test]
1668    fn encode_view_body_json_errors() {
1669        use buffa_types::google::protobuf::__buffa::view::StringValueView;
1670        let v = StringValueView::default();
1671        let err = encode_view_body(&v, CodecFormat::Json).unwrap_err();
1672        assert_eq!(err.code, crate::ErrorCode::Unimplemented);
1673        assert!(err.message.as_deref().unwrap().contains("JSON codec"));
1674    }
1675
1676    // Manual Encodable<StringValue> impl modelling what codegen emits
1677    // for FooView<'_>. Shared by the MaybeBorrowed tests below.
1678    struct V<'a>(buffa_types::google::protobuf::__buffa::view::StringValueView<'a>);
1679    impl Encodable<StringValue> for V<'_> {
1680        fn encode(&self, c: CodecFormat) -> Result<Bytes, ConnectError> {
1681            encode_view_body(&self.0, c)
1682        }
1683    }
1684
1685    #[test]
1686    fn maybe_borrowed_dispatch() {
1687        use buffa_types::google::protobuf::__buffa::view::StringValueView;
1688        let owned: MaybeBorrowed<StringValue, V<'_>> =
1689            MaybeBorrowed::Owned(StringValue::from("owned"));
1690        let borrowed = MaybeBorrowed::Borrowed(V(StringValueView {
1691            value: "view",
1692            ..Default::default()
1693        }));
1694        assert_eq!(
1695            StringValue::decode_from_slice(&owned.encode(CodecFormat::Proto).unwrap())
1696                .unwrap()
1697                .value,
1698            "owned"
1699        );
1700        assert_eq!(
1701            StringValue::decode_from_slice(&borrowed.encode(CodecFormat::Proto).unwrap())
1702                .unwrap()
1703                .value,
1704            "view"
1705        );
1706    }
1707
1708    #[test]
1709    fn maybe_borrowed_borrowed_json_unimplemented() {
1710        use buffa_types::google::protobuf::__buffa::view::StringValueView;
1711        let borrowed: MaybeBorrowed<StringValue, V<'_>> =
1712            MaybeBorrowed::Borrowed(V(StringValueView::default()));
1713        let err = borrowed.encode(CodecFormat::Json).unwrap_err();
1714        assert_eq!(err.code, crate::ErrorCode::Unimplemented);
1715    }
1716
1717    #[test]
1718    fn pre_encoded_proto_round_trip() {
1719        let m = StringValue::from("pre-encoded");
1720        let bytes = m.encode_to_bytes();
1721        let body = PreEncoded::<StringValue>::from_bytes_unchecked(bytes.clone());
1722        let out = Encodable::<StringValue>::encode(&body, CodecFormat::Proto).unwrap();
1723        assert_eq!(out, bytes);
1724        assert_eq!(
1725            StringValue::decode_from_slice(&out).unwrap().value,
1726            "pre-encoded"
1727        );
1728    }
1729
1730    #[cfg(feature = "json")]
1731    #[test]
1732    fn pre_encoded_json_decodes_then_serializes() {
1733        // The JSON path round-trips: proto bytes → owned `M` → JSON. Slow,
1734        // but correct — see the `# Codec behaviour` doc on `PreEncoded`.
1735        let m = StringValue::from("hi");
1736        let body = PreEncoded::<StringValue>::from_bytes_unchecked(m.encode_to_bytes());
1737        let out = Encodable::<StringValue>::encode(&body, CodecFormat::Json).unwrap();
1738        // Output should match what serializing the owned message directly
1739        // would produce.
1740        assert_eq!(out, Bytes::from(serde_json::to_vec(&m).unwrap()));
1741    }
1742
1743    #[cfg(feature = "json")]
1744    #[test]
1745    fn pre_encoded_json_decode_failure_is_internal_error() {
1746        // `from_bytes_unchecked` is unvalidated on the proto path. The JSON
1747        // fallback necessarily decodes; if that fails (the wrapped bytes
1748        // were never a valid `M`), the server-side `internal` error surfaces
1749        // closer to the construction bug than the proto path would.
1750        //
1751        // Field 1 (LEN) declares 99 bytes but only 2 follow — guaranteed
1752        // truncated for `StringValue`.
1753        let body = PreEncoded::<StringValue> {
1754            bytes: Bytes::from_static(&[0x0a, 0x63, b'h', b'i']),
1755            _marker: std::marker::PhantomData,
1756        };
1757        let err = Encodable::<StringValue>::encode(&body, CodecFormat::Json).unwrap_err();
1758        assert_eq!(err.code, crate::ErrorCode::Internal);
1759        assert!(err.message.as_deref().unwrap().contains("did not decode"));
1760    }
1761
1762    #[test]
1763    fn pre_encoded_from_view() {
1764        use buffa::view::ViewEncode;
1765        use buffa_types::google::protobuf::__buffa::view::StringValueView;
1766        let v = StringValueView {
1767            value: "from-view",
1768            ..Default::default()
1769        };
1770        // `from_view` infers `M = StringValue` from `StringValueView::Owned`.
1771        let body = PreEncoded::from_view(&v);
1772        let out = Encodable::<StringValue>::encode(&body, CodecFormat::Proto).unwrap();
1773        assert_eq!(out, v.encode_to_bytes());
1774        assert_eq!(
1775            StringValue::decode_from_slice(&out).unwrap().value,
1776            "from-view"
1777        );
1778    }
1779
1780    #[test]
1781    fn pre_encoded_from_message() {
1782        let m = StringValue::from("from-message");
1783        // `from_message` infers `M` from the receiver — no annotation.
1784        let body = PreEncoded::from_message(&m);
1785        let out = Encodable::<StringValue>::encode(&body, CodecFormat::Proto).unwrap();
1786        assert_eq!(out, m.encode_to_bytes());
1787
1788        // `From<&M>` is the same conversion via `.into()`.
1789        let body2: PreEncoded<StringValue> = (&m).into();
1790        let out2 = Encodable::<StringValue>::encode(&body2, CodecFormat::Proto).unwrap();
1791        assert_eq!(out2, out);
1792    }
1793
1794    #[cfg(feature = "json")]
1795    #[test]
1796    fn pre_encoded_codec_fidelity_diverges_on_unknown_fields() {
1797        // Documents the codec-dependent fidelity caveat: the proto path
1798        // is byte-exact (unknown fields preserved); the JSON path
1799        // round-trips through `M` (unknown fields dropped). Only relevant
1800        // for `from_bytes_unchecked` bytes sourced externally.
1801        //
1802        // Wire bytes: field 1 = "hi" (the known `StringValue.value`),
1803        // plus field 2 = varint 42 (unknown to `StringValue`).
1804        let bytes_with_unknown =
1805            Bytes::from_static(&[0x0a, 0x02, b'h', b'i', /* tag 2 varint */ 0x10, 42]);
1806        let body = PreEncoded::<StringValue> {
1807            bytes: bytes_with_unknown.clone(),
1808            _marker: std::marker::PhantomData,
1809        };
1810
1811        // Proto: byte-exact passthrough, unknown field preserved.
1812        let proto = Encodable::<StringValue>::encode(&body, CodecFormat::Proto).unwrap();
1813        assert_eq!(proto, bytes_with_unknown);
1814
1815        // JSON: round-trips through `StringValue`, which drops the
1816        // unknown field. Output equals serializing the bare known
1817        // message.
1818        let json = Encodable::<StringValue>::encode(&body, CodecFormat::Json).unwrap();
1819        assert_eq!(
1820            json,
1821            Bytes::from(serde_json::to_vec(&StringValue::from("hi")).unwrap())
1822        );
1823    }
1824
1825    // --- proto-only (json feature disabled) fallback behaviour ---
1826
1827    #[cfg(not(feature = "json"))]
1828    #[test]
1829    fn encodable_owned_json_is_unimplemented_without_feature() {
1830        let m = StringValue::from("hello");
1831        // Proto still encodes normally...
1832        assert!(Encodable::<StringValue>::encode(&m, CodecFormat::Proto).is_ok());
1833        // ...but the JSON codec is compiled out and reports it cleanly.
1834        let err = Encodable::<StringValue>::encode(&m, CodecFormat::Json).unwrap_err();
1835        assert_eq!(err.code, crate::ErrorCode::Unimplemented);
1836    }
1837
1838    #[cfg(not(feature = "json"))]
1839    #[test]
1840    fn pre_encoded_json_is_unimplemented_without_feature() {
1841        let m = StringValue::from("hi");
1842        let body = PreEncoded::<StringValue>::from_bytes_unchecked(m.encode_to_bytes());
1843        assert!(Encodable::<StringValue>::encode(&body, CodecFormat::Proto).is_ok());
1844        let err = Encodable::<StringValue>::encode(&body, CodecFormat::Json).unwrap_err();
1845        assert_eq!(err.code, crate::ErrorCode::Unimplemented);
1846    }
1847
1848    #[test]
1849    fn pre_encoded_is_typed() {
1850        // `PreEncoded<M>` only implements `Encodable<M>` — the type witness
1851        // means `PreEncoded<StringValue>` cannot be used where
1852        // `Encodable<Int32Value>` is required. Verified at compile time;
1853        // this test just exercises the happy path for both types.
1854        use buffa_types::google::protobuf::Int32Value;
1855        let s = PreEncoded::<StringValue>::from_bytes_unchecked(
1856            StringValue::from("a").encode_to_bytes(),
1857        );
1858        let i =
1859            PreEncoded::<Int32Value>::from_bytes_unchecked(Int32Value::from(1).encode_to_bytes());
1860        Encodable::<StringValue>::encode(&s, CodecFormat::Proto).unwrap();
1861        Encodable::<Int32Value>::encode(&i, CodecFormat::Proto).unwrap();
1862        // The following would not compile:
1863        //   Encodable::<Int32Value>::encode(&s, CodecFormat::Proto)
1864    }
1865
1866    #[test]
1867    #[cfg(debug_assertions)]
1868    #[should_panic(expected = "do not decode as")]
1869    fn pre_encoded_from_bytes_unchecked_debug_asserts() {
1870        // In debug builds, `from_bytes_unchecked` decodes once to surface
1871        // mismatched bytes early. Field 1 (LEN) declares 99 bytes; only 2
1872        // follow.
1873        let _ = PreEncoded::<StringValue>::from_bytes_unchecked(Bytes::from_static(&[
1874            0x0a, 0x63, b'h', b'i',
1875        ]));
1876    }
1877
1878    #[test]
1879    fn request_context_with_extensions() {
1880        #[derive(Clone, Debug, PartialEq)]
1881        struct Peer(u32);
1882        let mut ext = http::Extensions::new();
1883        ext.insert(Peer(7));
1884        let ctx = RequestContext::new(HeaderMap::new()).with_extensions(ext);
1885        assert_eq!(ctx.extensions().get::<Peer>(), Some(&Peer(7)));
1886    }
1887
1888    #[test]
1889    fn request_context_with_spec_and_protocol() {
1890        use crate::spec::{Spec, StreamType};
1891
1892        // Default-constructed context has neither.
1893        let ctx = RequestContext::new(HeaderMap::new());
1894        assert_eq!(ctx.spec(), None);
1895        assert_eq!(ctx.protocol(), None);
1896
1897        // Both round-trip through the builders.
1898        const SPEC: Spec = Spec::server("/pkg.Svc/M", StreamType::Unary);
1899        let ctx = RequestContext::new(HeaderMap::new())
1900            .with_spec(Some(SPEC))
1901            .with_protocol(Some(crate::Protocol::Grpc));
1902        assert_eq!(ctx.spec(), Some(SPEC));
1903        assert_eq!(ctx.protocol(), Some(crate::Protocol::Grpc));
1904
1905        // Builders accept `None` to clear (matches `with_deadline`).
1906        let ctx = ctx.with_spec(None).with_protocol(None);
1907        assert_eq!(ctx.spec(), None);
1908        assert_eq!(ctx.protocol(), None);
1909    }
1910
1911    #[test]
1912    fn request_context_with_path() {
1913        // Hand-built contexts (tests, custom dispatchers) have no path.
1914        let ctx = RequestContext::new(HeaderMap::new());
1915        assert_eq!(ctx.path(), None);
1916
1917        // Round-trips through the builder.
1918        let ctx = RequestContext::new(HeaderMap::new()).with_path("/pkg.Svc/M");
1919        assert_eq!(ctx.path(), Some("/pkg.Svc/M"));
1920
1921        // The builder takes ownership (Into<String>) so callers can pass
1922        // borrowed or owned without an extra clone.
1923        let owned = String::from("/pkg.Svc/Other");
1924        let ctx = RequestContext::new(HeaderMap::new()).with_path(owned);
1925        assert_eq!(ctx.path(), Some("/pkg.Svc/Other"));
1926
1927        // The builder does not normalize or validate — `Some("")` is
1928        // preserved verbatim. The dispatch path always supplies a non-empty
1929        // leading-slash form; `Some("")` only reaches consumers from a
1930        // misconfigured custom dispatch shim, which is a wiring bug they
1931        // should surface rather than silently coerce to `None`.
1932        let ctx = RequestContext::new(HeaderMap::new()).with_path("");
1933        assert_eq!(ctx.path(), Some(""));
1934    }
1935}