Skip to main content

connectrpc/
handler.rs

1//! Handler traits for implementing RPC methods.
2//!
3//! This module defines the traits that RPC method implementations must
4//! satisfy. Generated `FooService` traits are the primary surface; these
5//! lower-level traits are the building blocks that generated
6//! `<Service>Ext::register` wires into a [`Router`](crate::Router).
7//!
8//! Handlers receive a read-only [`RequestContext`] and return a
9//! [`Response<B>`](crate::Response) carrying the body plus any response
10//! headers/trailers/compression hint. See [`crate::response`] for the
11//! type definitions.
12//!
13//! # Why response metadata lives on `Response<B>`
14//!
15//! The earlier `Context` design conflated request-side reads
16//! (`headers`, `deadline`, `extensions`) with response-side writes
17//! (`response_headers`, `trailers`, `compress_response`) on one struct
18//! that the handler took ownership of and threaded back. Splitting it
19//! gives a clean in/out separation: handlers that don't touch response
20//! metadata bind `_ctx` and return `Ok(body.into())` with no `mut`
21//! ceremony, while handlers that do attach metadata get a fluent
22//! builder (`Response::new(body).with_header(..).with_trailer(..)`)
23//! instead of field-mutation followed by `Ok((body, ctx))`.
24
25use std::pin::Pin;
26use std::sync::Arc;
27
28use buffa::Message;
29use buffa::view::MessageView;
30use buffa::view::OwnedView;
31use bytes::Bytes;
32use futures::Stream;
33
34use crate::codec::CodecFormat;
35use crate::codec::decode_json;
36use crate::codec::{JsonDeserialize, JsonSerialize};
37use crate::error::ConnectError;
38use crate::response::{
39    Encodable, EncodedResponse, RequestContext, Response, ServiceResult, ServiceStream,
40};
41
42/// Report a failed request decode as `invalid_argument`.
43///
44/// Exceeding the element-memory budget is the one decode failure a server
45/// operator can fix without the peer changing anything, so it says which
46/// limit to raise. Every other variant is a malformed request, where naming
47/// a limit would misdirect. `DecodeError` is `#[non_exhaustive]`, hence the
48/// catch-all arm.
49fn decode_request_error(e: &buffa::DecodeError) -> ConnectError {
50    match e {
51        buffa::DecodeError::ElementMemoryLimitExceeded => ConnectError::invalid_argument(format!(
52            "failed to decode proto request: {e}; if this peer is trusted, \
53             raise Limits::element_memory_limit"
54        )),
55        _ => ConnectError::invalid_argument(format!("failed to decode proto request: {e}")),
56    }
57}
58
59/// Decode a request message from bytes using the specified codec format.
60pub(crate) fn decode_request<Req>(
61    request: &Bytes,
62    format: CodecFormat,
63    options: &buffa::DecodeOptions,
64) -> Result<Req, ConnectError>
65where
66    Req: Message + JsonDeserialize,
67{
68    match format {
69        CodecFormat::Proto => options
70            .decode_from_slice(&request[..])
71            .map_err(|e| decode_request_error(&e)),
72        CodecFormat::Json => decode_json(&request[..]),
73    }
74}
75
76/// Type alias for a boxed future used in handlers.
77pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
78
79/// Type alias for a boxed stream of encoded response bytes.
80pub type BoxStream<T> = Pin<Box<dyn Stream<Item = T> + Send>>;
81
82/// Map a stream of typed responses through [`Encodable`].
83///
84/// `B` is any [`Encodable<Res>`] — typically `Res` itself, but may be
85/// [`PreEncoded`](crate::PreEncoded) or [`MaybeBorrowed`](crate::MaybeBorrowed)
86/// for handlers that encode borrowing views per item.
87///
88/// Thin re-export wrapper so the four `*StreamingHandlerWrapper`
89/// `call_erased` impls below don't have to spell out the
90/// `dispatcher::codegen` path; the implementation is shared with the
91/// codegen-emitted dispatcher arms (see
92/// [`encode_response_stream`](crate::dispatcher::codegen::encode_response_stream)).
93fn encode_body_stream<Res, B, S>(
94    stream: S,
95    format: CodecFormat,
96) -> BoxStream<Result<Bytes, ConnectError>>
97where
98    Res: Message + Send + 'static,
99    B: Encodable<Res> + Send + 'static,
100    S: Stream<Item = Result<B, ConnectError>> + Send + 'static,
101{
102    crate::dispatcher::codegen::encode_response_stream::<Res, B, S>(stream, format)
103}
104
105// ============================================================================
106// Type-erased handler boundaries (Router → service.rs)
107// ============================================================================
108
109/// Type-erased unary handler for use in the router.
110pub(crate) trait ErasedHandler: Send + Sync {
111    /// Handle a request, decoding the [`Payload`] to the concrete request
112    /// type. Owned-message handlers should call [`Payload::take_message`]
113    /// to reuse a decode an interceptor may already have cached; view
114    /// handlers should call [`Payload::encoded`] for the wire bytes.
115    fn call_erased(
116        &self,
117        ctx: RequestContext,
118        request: crate::Payload,
119        format: CodecFormat,
120    ) -> BoxFuture<'static, Result<EncodedResponse, ConnectError>>;
121
122    /// Check if this is a streaming handler.
123    #[allow(dead_code)]
124    fn is_streaming(&self) -> bool;
125}
126
127/// Result type for erased streaming handlers.
128pub(crate) type StreamingHandlerResult =
129    BoxFuture<'static, Result<Response<BoxStream<Result<Bytes, ConnectError>>>, ConnectError>>;
130
131/// Type-erased server-streaming handler for use in the router.
132pub(crate) trait ErasedStreamingHandler: Send + Sync {
133    /// Handle a streaming request with raw bytes and specified codec format.
134    fn call_erased(
135        &self,
136        ctx: RequestContext,
137        request: Bytes,
138        format: CodecFormat,
139    ) -> StreamingHandlerResult;
140}
141
142/// Type-erased client-streaming handler for use in the router.
143pub(crate) trait ErasedClientStreamingHandler: Send + Sync {
144    /// Handle a client streaming request with a stream of raw message bytes.
145    fn call_erased(
146        &self,
147        ctx: RequestContext,
148        requests: BoxStream<Result<Bytes, ConnectError>>,
149        format: CodecFormat,
150    ) -> BoxFuture<'static, Result<EncodedResponse, ConnectError>>;
151}
152
153/// Type-erased bidi-streaming handler for use in the router.
154pub(crate) trait ErasedBidiStreamingHandler: Send + Sync {
155    /// Handle a bidi streaming request with a stream of raw message bytes.
156    fn call_erased(
157        &self,
158        ctx: RequestContext,
159        requests: BoxStream<Result<Bytes, ConnectError>>,
160        format: CodecFormat,
161    ) -> StreamingHandlerResult;
162}
163
164// ============================================================================
165// Unary handler (owned request)
166// ============================================================================
167
168/// Trait for unary RPC handlers (owned request type).
169///
170/// Handlers return a [`Response<Self::Body>`](crate::Response) where
171/// `Body` is any type [`Encodable`] as `Res` — typically `Res` itself.
172/// The happy path is `Ok(res.into())`.
173pub trait Handler<Req, Res>: Send + Sync + 'static
174where
175    Req: Message + Send + 'static,
176    Res: Message + Send + 'static,
177{
178    /// The response body type. Typically `Res`, or any
179    /// [`Encodable<Res>`](Encodable) (e.g.
180    /// [`MaybeBorrowed`](crate::MaybeBorrowed)).
181    type Body: Encodable<Res> + Send + 'static;
182
183    /// Handle a unary RPC request.
184    fn call(
185        &self,
186        ctx: RequestContext,
187        request: Req,
188    ) -> BoxFuture<'static, ServiceResult<Self::Body>>;
189}
190
191/// Wrapper that implements [`Handler`] for async functions.
192pub struct FnHandler<F> {
193    f: Arc<F>,
194}
195
196impl<F> FnHandler<F> {
197    /// Create a new function handler.
198    pub fn new(f: F) -> Self {
199        Self { f: Arc::new(f) }
200    }
201}
202
203impl<F, Fut, Req, Res, B> Handler<Req, Res> for FnHandler<F>
204where
205    F: Fn(RequestContext, Req) -> Fut + Send + Sync + 'static,
206    Fut: Future<Output = ServiceResult<B>> + Send + 'static,
207    Req: Message + Send + 'static,
208    Res: Message + Send + 'static,
209    B: Encodable<Res> + Send + 'static,
210{
211    type Body = B;
212
213    fn call(&self, ctx: RequestContext, request: Req) -> BoxFuture<'static, ServiceResult<B>> {
214        let f = Arc::clone(&self.f);
215        Box::pin(async move { f(ctx, request).await })
216    }
217}
218
219/// Helper function to create a handler from an async function.
220pub fn handler_fn<F, Fut, Req, Res, B>(f: F) -> FnHandler<F>
221where
222    F: Fn(RequestContext, Req) -> Fut + Send + Sync + 'static,
223    Fut: Future<Output = ServiceResult<B>> + Send + 'static,
224    Req: Message + Send + 'static,
225    Res: Message + Send + 'static,
226    B: Encodable<Res> + Send + 'static,
227{
228    FnHandler::new(f)
229}
230
231/// Wrapper to erase the types from a unary handler.
232pub(crate) struct UnaryHandlerWrapper<H, Req, Res>
233where
234    H: Handler<Req, Res>,
235    Req: Message + JsonDeserialize + Send + 'static,
236    Res: Message + JsonSerialize + Send + 'static,
237{
238    handler: Arc<H>,
239    _phantom: std::marker::PhantomData<fn(Req) -> Res>,
240}
241
242impl<H, Req, Res> UnaryHandlerWrapper<H, Req, Res>
243where
244    H: Handler<Req, Res>,
245    Req: Message + JsonDeserialize + Send + 'static,
246    Res: Message + JsonSerialize + Send + 'static,
247{
248    /// Create a new wrapper around the given handler.
249    pub fn new(handler: H) -> Self {
250        Self {
251            handler: Arc::new(handler),
252            _phantom: std::marker::PhantomData,
253        }
254    }
255}
256
257impl<H, Req, Res> ErasedHandler for UnaryHandlerWrapper<H, Req, Res>
258where
259    H: Handler<Req, Res>,
260    Req: Message + JsonDeserialize + Send + 'static,
261    Res: Message + JsonSerialize + Send + 'static,
262{
263    fn call_erased(
264        &self,
265        ctx: RequestContext,
266        request: crate::Payload,
267        format: CodecFormat,
268    ) -> BoxFuture<'static, Result<EncodedResponse, ConnectError>> {
269        let handler = Arc::clone(&self.handler);
270        Box::pin(async move {
271            // `take_message` reuses an interceptor's decode when one ran
272            // and cached this `Req`, instead of decoding the bytes again.
273            let req: Req = request.take_message()?;
274            handler.call(ctx, req).await?.encode::<Res>(format)
275        })
276    }
277
278    fn is_streaming(&self) -> bool {
279        false
280    }
281}
282
283// ============================================================================
284// Server-streaming handler (owned request)
285// ============================================================================
286
287/// Trait for server streaming RPC handlers.
288///
289/// # Migrating from connectrpc 0.4.x
290///
291/// `Item` is new in 0.5: a hand-written `impl StreamingHandler` previously
292/// returned `ServiceStream<Res>`; add `type Item = Res;` to keep the same
293/// behavior. Generated traits and the [`streaming_handler_fn`] helper
294/// infer it.
295pub trait StreamingHandler<Req, Res>: Send + Sync + 'static
296where
297    Req: Message + Send + 'static,
298    Res: Message + Send + 'static,
299{
300    /// The stream item type. Typically `Res` itself; may be
301    /// [`PreEncoded`](crate::PreEncoded) or
302    /// [`MaybeBorrowed`](crate::MaybeBorrowed) for handlers that encode
303    /// borrowing views per item.
304    ///
305    /// Items must be `'static` — a stream item cannot borrow `&self` or a
306    /// per-call snapshot. To stream view-encoded data, encode each item
307    /// inside the stream's body and yield [`PreEncoded`](crate::PreEncoded).
308    type Item: Encodable<Res> + Send + 'static;
309
310    /// Handle a server streaming RPC request.
311    fn call(
312        &self,
313        ctx: RequestContext,
314        request: Req,
315    ) -> BoxFuture<'static, ServiceResult<ServiceStream<Self::Item>>>;
316}
317
318/// Wrapper that implements [`StreamingHandler`] for async functions.
319pub struct FnStreamingHandler<F> {
320    f: Arc<F>,
321}
322
323impl<F> FnStreamingHandler<F> {
324    /// Create a new function streaming handler.
325    pub fn new(f: F) -> Self {
326        Self { f: Arc::new(f) }
327    }
328}
329
330impl<F, Fut, Req, Res, B> StreamingHandler<Req, Res> for FnStreamingHandler<F>
331where
332    F: Fn(RequestContext, Req) -> Fut + Send + Sync + 'static,
333    Fut: Future<Output = ServiceResult<ServiceStream<B>>> + Send + 'static,
334    Req: Message + Send + 'static,
335    Res: Message + Send + 'static,
336    B: Encodable<Res> + Send + 'static,
337{
338    type Item = B;
339
340    fn call(
341        &self,
342        ctx: RequestContext,
343        request: Req,
344    ) -> BoxFuture<'static, ServiceResult<ServiceStream<B>>> {
345        let f = Arc::clone(&self.f);
346        Box::pin(async move { f(ctx, request).await })
347    }
348}
349
350/// Helper function to create a streaming handler from an async function.
351///
352/// `Res` is inferred from the stream item type `B` whenever the closure
353/// pins `B` to a concrete type — yielding an owned `Res`,
354/// [`PreEncoded::from_view(&view)`](crate::PreEncoded::from_view), or
355/// [`PreEncoded::<MyResponse>::from_bytes_unchecked(bytes)`](crate::PreEncoded::from_bytes_unchecked)
356/// all infer cleanly. Inference only fails when the closure leaves the
357/// message type itself open (e.g. `PreEncoded::from_bytes_unchecked(bytes)`
358/// with no `::<M>`); the simplest fix is to name `M` at the construction
359/// site rather than turbofishing this helper:
360///
361/// ```rust,ignore
362/// // `M` named at the construction site — `Res` is inferred:
363/// PreEncoded::<MyResponse>::from_bytes_unchecked(bytes)
364/// ```
365///
366/// Generated server-streaming registrations always pin `Res` because the
367/// trait method's stream item is the *opaque* `impl Encodable<Out>`, which
368/// can't be unified against the `Encodable<Res>` impls. Hand-written
369/// `Router` registrations don't hit this unless they leave the message type
370/// open.
371pub fn streaming_handler_fn<F, Fut, Req, Res, B>(f: F) -> FnStreamingHandler<F>
372where
373    F: Fn(RequestContext, Req) -> Fut + Send + Sync + 'static,
374    Fut: Future<Output = ServiceResult<ServiceStream<B>>> + Send + 'static,
375    Req: Message + Send + 'static,
376    Res: Message + Send + 'static,
377    B: Encodable<Res> + Send + 'static,
378{
379    FnStreamingHandler::new(f)
380}
381
382/// Wrapper to erase the types from a server streaming handler.
383pub(crate) struct ServerStreamingHandlerWrapper<H, Req, Res>
384where
385    H: StreamingHandler<Req, Res>,
386    Req: Message + JsonDeserialize + Send + 'static,
387    Res: Message + Send + 'static,
388{
389    handler: Arc<H>,
390    _phantom: std::marker::PhantomData<fn(Req) -> Res>,
391}
392
393impl<H, Req, Res> ServerStreamingHandlerWrapper<H, Req, Res>
394where
395    H: StreamingHandler<Req, Res>,
396    Req: Message + JsonDeserialize + Send + 'static,
397    Res: Message + Send + 'static,
398{
399    /// Create a new wrapper around the given streaming handler.
400    pub fn new(handler: H) -> Self {
401        Self {
402            handler: Arc::new(handler),
403            _phantom: std::marker::PhantomData,
404        }
405    }
406}
407
408impl<H, Req, Res> ErasedStreamingHandler for ServerStreamingHandlerWrapper<H, Req, Res>
409where
410    H: StreamingHandler<Req, Res>,
411    Req: Message + JsonDeserialize + Send + 'static,
412    Res: Message + Send + 'static,
413{
414    fn call_erased(
415        &self,
416        ctx: RequestContext,
417        request: Bytes,
418        format: CodecFormat,
419    ) -> StreamingHandlerResult {
420        let handler = Arc::clone(&self.handler);
421        Box::pin(async move {
422            let req: Req = decode_request(&request, format, ctx.decode_options())?;
423            let resp = handler.call(ctx, req).await?;
424            Ok(resp.map_body(|s| encode_body_stream(s, format)))
425        })
426    }
427}
428
429// ============================================================================
430// Client-streaming handler (owned request)
431// ============================================================================
432
433/// Trait for client streaming RPC handlers.
434pub trait ClientStreamingHandler<Req, Res>: Send + Sync + 'static
435where
436    Req: Message + Send + 'static,
437    Res: Message + Send + 'static,
438{
439    /// The response body type. Typically `Res`.
440    type Body: Encodable<Res> + Send + 'static;
441
442    /// Handle a client streaming RPC request.
443    fn call(
444        &self,
445        ctx: RequestContext,
446        requests: ServiceStream<Req>,
447    ) -> BoxFuture<'static, ServiceResult<Self::Body>>;
448}
449
450/// Wrapper that implements [`ClientStreamingHandler`] for async functions.
451pub struct FnClientStreamingHandler<F> {
452    f: Arc<F>,
453}
454
455impl<F> FnClientStreamingHandler<F> {
456    /// Create a new function client streaming handler.
457    pub fn new(f: F) -> Self {
458        Self { f: Arc::new(f) }
459    }
460}
461
462impl<F, Fut, Req, Res, B> ClientStreamingHandler<Req, Res> for FnClientStreamingHandler<F>
463where
464    F: Fn(RequestContext, ServiceStream<Req>) -> Fut + Send + Sync + 'static,
465    Fut: Future<Output = ServiceResult<B>> + Send + 'static,
466    Req: Message + Send + 'static,
467    Res: Message + Send + 'static,
468    B: Encodable<Res> + Send + 'static,
469{
470    type Body = B;
471
472    fn call(
473        &self,
474        ctx: RequestContext,
475        requests: ServiceStream<Req>,
476    ) -> BoxFuture<'static, ServiceResult<B>> {
477        let f = Arc::clone(&self.f);
478        Box::pin(async move { f(ctx, requests).await })
479    }
480}
481
482/// Helper function to create a client streaming handler from an async function.
483pub fn client_streaming_handler_fn<F, Fut, Req, Res, B>(f: F) -> FnClientStreamingHandler<F>
484where
485    F: Fn(RequestContext, ServiceStream<Req>) -> Fut + Send + Sync + 'static,
486    Fut: Future<Output = ServiceResult<B>> + Send + 'static,
487    Req: Message + Send + 'static,
488    Res: Message + Send + 'static,
489    B: Encodable<Res> + Send + 'static,
490{
491    FnClientStreamingHandler::new(f)
492}
493
494/// Wrapper to erase the types from a client streaming handler.
495pub(crate) struct ClientStreamingHandlerWrapper<H, Req, Res>
496where
497    H: ClientStreamingHandler<Req, Res>,
498    Req: Message + JsonDeserialize + Send + 'static,
499    Res: Message + JsonSerialize + Send + 'static,
500{
501    handler: Arc<H>,
502    _phantom: std::marker::PhantomData<fn(Req) -> Res>,
503}
504
505impl<H, Req, Res> ClientStreamingHandlerWrapper<H, Req, Res>
506where
507    H: ClientStreamingHandler<Req, Res>,
508    Req: Message + JsonDeserialize + Send + 'static,
509    Res: Message + JsonSerialize + Send + 'static,
510{
511    /// Create a new wrapper around the given client streaming handler.
512    pub fn new(handler: H) -> Self {
513        Self {
514            handler: Arc::new(handler),
515            _phantom: std::marker::PhantomData,
516        }
517    }
518}
519
520impl<H, Req, Res> ErasedClientStreamingHandler for ClientStreamingHandlerWrapper<H, Req, Res>
521where
522    H: ClientStreamingHandler<Req, Res>,
523    Req: Message + JsonDeserialize + Send + 'static,
524    Res: Message + JsonSerialize + Send + 'static,
525{
526    fn call_erased(
527        &self,
528        ctx: RequestContext,
529        requests: BoxStream<Result<Bytes, ConnectError>>,
530        format: CodecFormat,
531    ) -> BoxFuture<'static, Result<EncodedResponse, ConnectError>> {
532        use futures::StreamExt as _;
533        let handler = Arc::clone(&self.handler);
534        Box::pin(async move {
535            // The stream outlives this frame, so it owns its limits rather
536            // than borrowing them from `ctx`, which is moved into the call.
537            let options = ctx.decode_options().clone();
538            let request_stream: ServiceStream<Req> =
539                Box::pin(requests.map(move |result| {
540                    result.and_then(|raw| decode_request(&raw, format, &options))
541                }));
542            handler
543                .call(ctx, request_stream)
544                .await?
545                .encode::<Res>(format)
546        })
547    }
548}
549
550// ============================================================================
551// Bidi-streaming handler (owned request)
552// ============================================================================
553
554/// Trait for bidirectional streaming RPC handlers.
555///
556/// # Migrating from connectrpc 0.4.x
557///
558/// `Item` is new in 0.5: hand-written impls add `type Item = Res;`.
559/// See [`StreamingHandler`] for details.
560pub trait BidiStreamingHandler<Req, Res>: Send + Sync + 'static
561where
562    Req: Message + Send + 'static,
563    Res: Message + Send + 'static,
564{
565    /// The stream item type. Typically `Res` itself; may be
566    /// [`PreEncoded`](crate::PreEncoded) or
567    /// [`MaybeBorrowed`](crate::MaybeBorrowed) for handlers that encode
568    /// borrowing views per item. See [`StreamingHandler::Item`].
569    type Item: Encodable<Res> + Send + 'static;
570
571    /// Handle a bidi streaming RPC request.
572    fn call(
573        &self,
574        ctx: RequestContext,
575        requests: ServiceStream<Req>,
576    ) -> BoxFuture<'static, ServiceResult<ServiceStream<Self::Item>>>;
577}
578
579/// Wrapper that implements [`BidiStreamingHandler`] for async functions.
580pub struct FnBidiStreamingHandler<F> {
581    f: Arc<F>,
582}
583
584impl<F> FnBidiStreamingHandler<F> {
585    /// Create a new function bidi streaming handler.
586    pub fn new(f: F) -> Self {
587        Self { f: Arc::new(f) }
588    }
589}
590
591impl<F, Fut, Req, Res, B> BidiStreamingHandler<Req, Res> for FnBidiStreamingHandler<F>
592where
593    F: Fn(RequestContext, ServiceStream<Req>) -> Fut + Send + Sync + 'static,
594    Fut: Future<Output = ServiceResult<ServiceStream<B>>> + Send + 'static,
595    Req: Message + Send + 'static,
596    Res: Message + Send + 'static,
597    B: Encodable<Res> + Send + 'static,
598{
599    type Item = B;
600
601    fn call(
602        &self,
603        ctx: RequestContext,
604        requests: ServiceStream<Req>,
605    ) -> BoxFuture<'static, ServiceResult<ServiceStream<B>>> {
606        let f = Arc::clone(&self.f);
607        Box::pin(async move { f(ctx, requests).await })
608    }
609}
610
611/// Helper function to create a bidi streaming handler from an async function.
612pub fn bidi_streaming_handler_fn<F, Fut, Req, Res, B>(f: F) -> FnBidiStreamingHandler<F>
613where
614    F: Fn(RequestContext, ServiceStream<Req>) -> Fut + Send + Sync + 'static,
615    Fut: Future<Output = ServiceResult<ServiceStream<B>>> + Send + 'static,
616    Req: Message + Send + 'static,
617    Res: Message + Send + 'static,
618    B: Encodable<Res> + Send + 'static,
619{
620    FnBidiStreamingHandler::new(f)
621}
622
623/// Wrapper to erase the types from a bidi streaming handler.
624pub(crate) struct BidiStreamingHandlerWrapper<H, Req, Res>
625where
626    H: BidiStreamingHandler<Req, Res>,
627    Req: Message + JsonDeserialize + Send + 'static,
628    Res: Message + Send + 'static,
629{
630    handler: Arc<H>,
631    _phantom: std::marker::PhantomData<fn(Req) -> Res>,
632}
633
634impl<H, Req, Res> BidiStreamingHandlerWrapper<H, Req, Res>
635where
636    H: BidiStreamingHandler<Req, Res>,
637    Req: Message + JsonDeserialize + Send + 'static,
638    Res: Message + Send + 'static,
639{
640    /// Create a new wrapper around the given bidi streaming handler.
641    pub fn new(handler: H) -> Self {
642        Self {
643            handler: Arc::new(handler),
644            _phantom: std::marker::PhantomData,
645        }
646    }
647}
648
649impl<H, Req, Res> ErasedBidiStreamingHandler for BidiStreamingHandlerWrapper<H, Req, Res>
650where
651    H: BidiStreamingHandler<Req, Res>,
652    Req: Message + JsonDeserialize + Send + 'static,
653    Res: Message + Send + 'static,
654{
655    fn call_erased(
656        &self,
657        ctx: RequestContext,
658        requests: BoxStream<Result<Bytes, ConnectError>>,
659        format: CodecFormat,
660    ) -> StreamingHandlerResult {
661        use futures::StreamExt as _;
662        let handler = Arc::clone(&self.handler);
663        Box::pin(async move {
664            // The stream outlives this frame, so it owns its limits rather
665            // than borrowing them from `ctx`, which is moved into the call.
666            let options = ctx.decode_options().clone();
667            let request_stream: ServiceStream<Req> =
668                Box::pin(requests.map(move |result| {
669                    result.and_then(|raw| decode_request(&raw, format, &options))
670                }));
671            let resp = handler.call(ctx, request_stream).await?;
672            Ok(resp.map_body(|s| encode_body_stream(s, format)))
673        })
674    }
675}
676
677// ============================================================================
678// View-based handlers (zero-copy request views)
679// ============================================================================
680
681/// Decode a request as an `OwnedView` from bytes using the specified codec format.
682///
683/// Normalizes the body to proto wire bytes via [`request_proto_bytes`],
684/// then decodes the view over that buffer — a true zero-copy decode for
685/// proto-encoded requests. The JSON round-trip adds overhead relative to
686/// owned-type decoding, but is negligible compared to JSON parsing itself.
687pub(crate) fn decode_request_view<ReqView>(
688    request: Bytes,
689    format: CodecFormat,
690    options: &buffa::DecodeOptions,
691) -> Result<OwnedView<ReqView>, ConnectError>
692where
693    ReqView: MessageView<'static> + Send,
694    ReqView::Owned: Message + JsonDeserialize,
695{
696    let body = request_proto_bytes::<ReqView::Owned>(request, format)?;
697    OwnedView::<ReqView>::decode_with_options(body, options).map_err(|e| decode_request_error(&e))
698}
699
700/// Normalize a request body to protobuf wire bytes.
701///
702/// For proto-encoded requests this is a pass-through of the input `Bytes`.
703/// For JSON-encoded requests the body is deserialized to the owned message
704/// and re-encoded to proto bytes. The returned buffer is what a request
705/// view borrows from — in the generated unary dispatch glue the dispatcher
706/// keeps it alive for the duration of the handler call, so a scoped view's
707/// borrows are tied to the call frame; on the streaming and Router paths it
708/// backs an [`OwnedView`].
709///
710/// # Errors
711///
712/// Returns `ConnectError::invalid_argument` if the JSON body cannot be
713/// deserialized into the request message.
714#[doc(hidden)] // exposed only for dispatcher::codegen (generated code)
715pub fn request_proto_bytes<Req>(request: Bytes, format: CodecFormat) -> Result<Bytes, ConnectError>
716where
717    Req: Message + JsonDeserialize,
718{
719    match format {
720        CodecFormat::Proto => Ok(request),
721        CodecFormat::Json => {
722            let owned: Req = decode_json(&request[..])?;
723            Ok(Bytes::from(owned.encode_to_vec()))
724        }
725    }
726}
727
728/// Decode a scoped (borrowed) request view from normalized proto bytes.
729///
730/// Companion to [`request_proto_bytes`]: the generated dispatch glue
731/// keeps the returned view's backing buffer alive across the handler call,
732/// so the view's borrows are tied to the call frame rather than promoted to
733/// a synthetic `'static`.
734///
735/// `options` carries the service's configured decode limits; see
736/// [`Limits`](crate::Limits).
737///
738/// # Errors
739///
740/// Returns `ConnectError::invalid_argument` if the bytes exceed one of
741/// `options`' limits, or if the bytes are not a valid
742/// encoding of the request message.
743#[doc(hidden)] // exposed only for dispatcher::codegen (generated code)
744pub fn decode_borrowed_request_view<'a, ReqView>(
745    body: &'a [u8],
746    options: &buffa::DecodeOptions,
747) -> Result<ReqView, ConnectError>
748where
749    ReqView: MessageView<'a>,
750{
751    options
752        .decode_view(body)
753        .map_err(|e| decode_request_error(&e))
754}
755
756/// Trait for unary RPC handlers using zero-copy request views.
757///
758/// `call` returns the response **already encoded** so the body's
759/// lifetime can be tied to data the handler borrows from `&self` (or
760/// from the request) without surfacing in the trait object boundary.
761pub trait ViewHandler<ReqView>: Send + Sync + 'static
762where
763    ReqView: MessageView<'static> + Send + Sync + 'static,
764{
765    /// Handle a unary RPC request with a zero-copy view, encoding the
766    /// response in `format`.
767    fn call(
768        &self,
769        ctx: RequestContext,
770        request: OwnedView<ReqView>,
771        format: CodecFormat,
772    ) -> BoxFuture<'static, Result<EncodedResponse, ConnectError>>;
773}
774
775/// Wrapper that implements [`ViewHandler`] for async functions.
776pub struct FnViewHandler<F> {
777    f: Arc<F>,
778}
779
780impl<F> FnViewHandler<F> {
781    /// Create a new function view handler.
782    pub fn new(f: F) -> Self {
783        Self { f: Arc::new(f) }
784    }
785}
786
787impl<F, Fut, ReqView> ViewHandler<ReqView> for FnViewHandler<F>
788where
789    F: Fn(RequestContext, OwnedView<ReqView>, CodecFormat) -> Fut + Send + Sync + 'static,
790    Fut: Future<Output = Result<EncodedResponse, ConnectError>> + Send + 'static,
791    ReqView: MessageView<'static> + Send + Sync + 'static,
792{
793    fn call(
794        &self,
795        ctx: RequestContext,
796        request: OwnedView<ReqView>,
797        format: CodecFormat,
798    ) -> BoxFuture<'static, Result<EncodedResponse, ConnectError>> {
799        let f = Arc::clone(&self.f);
800        Box::pin(async move { f(ctx, request, format).await })
801    }
802}
803
804/// Helper function to create a view handler from an async function.
805///
806/// The closure receives the negotiated [`CodecFormat`] and returns the
807/// response **already encoded**, so a body that borrows from `&svc` is
808/// encoded before the borrow ends. Generated service registration uses this
809/// adapter for unary handlers that operate on borrowed request views.
810pub fn view_handler_fn<F, Fut, ReqView>(f: F) -> FnViewHandler<F>
811where
812    F: Fn(RequestContext, OwnedView<ReqView>, CodecFormat) -> Fut + Send + Sync + 'static,
813    Fut: Future<Output = Result<EncodedResponse, ConnectError>> + Send + 'static,
814    ReqView: MessageView<'static> + Send + Sync + 'static,
815{
816    FnViewHandler::new(f)
817}
818
819/// Wrapper to erase the types from a unary view handler.
820pub(crate) struct UnaryViewHandlerWrapper<H, ReqView>
821where
822    H: ViewHandler<ReqView>,
823    ReqView: MessageView<'static> + Send + Sync + 'static,
824    ReqView::Owned: Message + JsonDeserialize,
825{
826    handler: Arc<H>,
827    _phantom: std::marker::PhantomData<fn(ReqView)>,
828}
829
830impl<H, ReqView> UnaryViewHandlerWrapper<H, ReqView>
831where
832    H: ViewHandler<ReqView>,
833    ReqView: MessageView<'static> + Send + Sync + 'static,
834    ReqView::Owned: Message + JsonDeserialize,
835{
836    pub fn new(handler: H) -> Self {
837        Self {
838            handler: Arc::new(handler),
839            _phantom: std::marker::PhantomData,
840        }
841    }
842}
843
844impl<H, ReqView> ErasedHandler for UnaryViewHandlerWrapper<H, ReqView>
845where
846    H: ViewHandler<ReqView>,
847    ReqView: MessageView<'static> + Send + Sync + 'static,
848    ReqView::Owned: Message + JsonDeserialize,
849{
850    fn call_erased(
851        &self,
852        ctx: RequestContext,
853        request: crate::Payload,
854        format: CodecFormat,
855    ) -> BoxFuture<'static, Result<EncodedResponse, ConnectError>> {
856        let handler = Arc::clone(&self.handler);
857        Box::pin(async move {
858            // The cache stores owned messages, not views, so it can't help
859            // here. `encoded()` is the wire bytes — a cheap `Bytes` clone
860            // unless an interceptor replaced the body, in which case it
861            // re-encodes the replacement.
862            let req =
863                decode_request_view::<ReqView>(request.encoded()?, format, ctx.decode_options())?;
864            handler.call(ctx, req, format).await
865        })
866    }
867
868    fn is_streaming(&self) -> bool {
869        false
870    }
871}
872
873/// Trait for server streaming RPC handlers using zero-copy request views.
874pub trait ViewStreamingHandler<ReqView, Res>: Send + Sync + 'static
875where
876    ReqView: MessageView<'static> + Send + Sync + 'static,
877    Res: Message + Send + 'static,
878{
879    /// The stream item type. Typically `Res` itself; may be
880    /// [`PreEncoded`](crate::PreEncoded) or
881    /// [`MaybeBorrowed`](crate::MaybeBorrowed) for handlers that encode
882    /// borrowing views per item.
883    type Item: Encodable<Res> + Send + 'static;
884
885    /// Handle a server streaming RPC request with a zero-copy view.
886    fn call(
887        &self,
888        ctx: RequestContext,
889        request: OwnedView<ReqView>,
890    ) -> BoxFuture<'static, ServiceResult<ServiceStream<Self::Item>>>;
891}
892
893/// Wrapper that implements [`ViewStreamingHandler`] for async functions.
894pub struct FnViewStreamingHandler<F> {
895    f: Arc<F>,
896}
897
898impl<F> FnViewStreamingHandler<F> {
899    /// Create a new function view streaming handler.
900    pub fn new(f: F) -> Self {
901        Self { f: Arc::new(f) }
902    }
903}
904
905impl<F, Fut, ReqView, Res, B> ViewStreamingHandler<ReqView, Res> for FnViewStreamingHandler<F>
906where
907    F: Fn(RequestContext, OwnedView<ReqView>) -> Fut + Send + Sync + 'static,
908    Fut: Future<Output = ServiceResult<ServiceStream<B>>> + Send + 'static,
909    ReqView: MessageView<'static> + Send + Sync + 'static,
910    Res: Message + Send + 'static,
911    B: Encodable<Res> + Send + 'static,
912{
913    type Item = B;
914
915    fn call(
916        &self,
917        ctx: RequestContext,
918        request: OwnedView<ReqView>,
919    ) -> BoxFuture<'static, ServiceResult<ServiceStream<B>>> {
920        let f = Arc::clone(&self.f);
921        Box::pin(async move { f(ctx, request).await })
922    }
923}
924
925/// Helper function to create a view streaming handler from an async function.
926pub fn view_streaming_handler_fn<F, Fut, ReqView, Res, B>(f: F) -> FnViewStreamingHandler<F>
927where
928    F: Fn(RequestContext, OwnedView<ReqView>) -> Fut + Send + Sync + 'static,
929    Fut: Future<Output = ServiceResult<ServiceStream<B>>> + Send + 'static,
930    ReqView: MessageView<'static> + Send + Sync + 'static,
931    Res: Message + Send + 'static,
932    B: Encodable<Res> + Send + 'static,
933{
934    FnViewStreamingHandler::new(f)
935}
936
937/// Wrapper to erase the types from a server streaming view handler.
938pub(crate) struct ServerStreamingViewHandlerWrapper<H, ReqView, Res>
939where
940    H: ViewStreamingHandler<ReqView, Res>,
941    ReqView: MessageView<'static> + Send + Sync + 'static,
942    ReqView::Owned: Message + JsonDeserialize,
943    Res: Message + Send + 'static,
944{
945    handler: Arc<H>,
946    _phantom: std::marker::PhantomData<fn(ReqView) -> Res>,
947}
948
949impl<H, ReqView, Res> ServerStreamingViewHandlerWrapper<H, ReqView, Res>
950where
951    H: ViewStreamingHandler<ReqView, Res>,
952    ReqView: MessageView<'static> + Send + Sync + 'static,
953    ReqView::Owned: Message + JsonDeserialize,
954    Res: Message + Send + 'static,
955{
956    pub fn new(handler: H) -> Self {
957        Self {
958            handler: Arc::new(handler),
959            _phantom: std::marker::PhantomData,
960        }
961    }
962}
963
964impl<H, ReqView, Res> ErasedStreamingHandler for ServerStreamingViewHandlerWrapper<H, ReqView, Res>
965where
966    H: ViewStreamingHandler<ReqView, Res>,
967    ReqView: MessageView<'static> + Send + Sync + 'static,
968    ReqView::Owned: Message + JsonDeserialize,
969    Res: Message + Send + 'static,
970{
971    fn call_erased(
972        &self,
973        ctx: RequestContext,
974        request: Bytes,
975        format: CodecFormat,
976    ) -> StreamingHandlerResult {
977        let handler = Arc::clone(&self.handler);
978        Box::pin(async move {
979            let req = decode_request_view::<ReqView>(request, format, ctx.decode_options())?;
980            let resp = handler.call(ctx, req).await?;
981            Ok(resp.map_body(|s| encode_body_stream(s, format)))
982        })
983    }
984}
985
986/// Trait for client streaming RPC handlers using zero-copy request views.
987///
988/// `call` returns the response **already encoded**; see [`ViewHandler`].
989pub trait ViewClientStreamingHandler<ReqView>: Send + Sync + 'static
990where
991    ReqView: MessageView<'static> + Send + Sync + 'static,
992{
993    /// Handle a client streaming RPC request with zero-copy view items,
994    /// encoding the response in `format`.
995    fn call(
996        &self,
997        ctx: RequestContext,
998        requests: ServiceStream<OwnedView<ReqView>>,
999        format: CodecFormat,
1000    ) -> BoxFuture<'static, Result<EncodedResponse, ConnectError>>;
1001}
1002
1003/// Wrapper that implements [`ViewClientStreamingHandler`] for async functions.
1004pub struct FnViewClientStreamingHandler<F> {
1005    f: Arc<F>,
1006}
1007
1008impl<F> FnViewClientStreamingHandler<F> {
1009    /// Create a new function view client streaming handler.
1010    pub fn new(f: F) -> Self {
1011        Self { f: Arc::new(f) }
1012    }
1013}
1014
1015impl<F, Fut, ReqView> ViewClientStreamingHandler<ReqView> for FnViewClientStreamingHandler<F>
1016where
1017    F: Fn(RequestContext, ServiceStream<OwnedView<ReqView>>, CodecFormat) -> Fut
1018        + Send
1019        + Sync
1020        + 'static,
1021    Fut: Future<Output = Result<EncodedResponse, ConnectError>> + Send + 'static,
1022    ReqView: MessageView<'static> + Send + Sync + 'static,
1023{
1024    fn call(
1025        &self,
1026        ctx: RequestContext,
1027        requests: ServiceStream<OwnedView<ReqView>>,
1028        format: CodecFormat,
1029    ) -> BoxFuture<'static, Result<EncodedResponse, ConnectError>> {
1030        let f = Arc::clone(&self.f);
1031        Box::pin(async move { f(ctx, requests, format).await })
1032    }
1033}
1034
1035/// Helper function to create a view client streaming handler from an async function.
1036pub fn view_client_streaming_handler_fn<F, Fut, ReqView>(f: F) -> FnViewClientStreamingHandler<F>
1037where
1038    F: Fn(RequestContext, ServiceStream<OwnedView<ReqView>>, CodecFormat) -> Fut
1039        + Send
1040        + Sync
1041        + 'static,
1042    Fut: Future<Output = Result<EncodedResponse, ConnectError>> + Send + 'static,
1043    ReqView: MessageView<'static> + Send + Sync + 'static,
1044{
1045    FnViewClientStreamingHandler::new(f)
1046}
1047
1048/// Wrapper to erase the types from a client streaming view handler.
1049pub(crate) struct ClientStreamingViewHandlerWrapper<H, ReqView>
1050where
1051    H: ViewClientStreamingHandler<ReqView>,
1052    ReqView: MessageView<'static> + Send + Sync + 'static,
1053    ReqView::Owned: Message + JsonDeserialize,
1054{
1055    handler: Arc<H>,
1056    _phantom: std::marker::PhantomData<fn(ReqView)>,
1057}
1058
1059impl<H, ReqView> ClientStreamingViewHandlerWrapper<H, ReqView>
1060where
1061    H: ViewClientStreamingHandler<ReqView>,
1062    ReqView: MessageView<'static> + Send + Sync + 'static,
1063    ReqView::Owned: Message + JsonDeserialize,
1064{
1065    pub fn new(handler: H) -> Self {
1066        Self {
1067            handler: Arc::new(handler),
1068            _phantom: std::marker::PhantomData,
1069        }
1070    }
1071}
1072
1073impl<H, ReqView> ErasedClientStreamingHandler for ClientStreamingViewHandlerWrapper<H, ReqView>
1074where
1075    H: ViewClientStreamingHandler<ReqView>,
1076    ReqView: MessageView<'static> + Send + Sync + 'static,
1077    ReqView::Owned: Message + JsonDeserialize,
1078{
1079    fn call_erased(
1080        &self,
1081        ctx: RequestContext,
1082        requests: BoxStream<Result<Bytes, ConnectError>>,
1083        format: CodecFormat,
1084    ) -> BoxFuture<'static, Result<EncodedResponse, ConnectError>> {
1085        use futures::StreamExt as _;
1086        let handler = Arc::clone(&self.handler);
1087        Box::pin(async move {
1088            // The stream outlives this frame, so it owns its limits rather
1089            // than borrowing them from `ctx`, which is moved into the call.
1090            let options = ctx.decode_options().clone();
1091            let request_stream: ServiceStream<OwnedView<ReqView>> =
1092                Box::pin(requests.map(move |result| {
1093                    result.and_then(|raw| decode_request_view::<ReqView>(raw, format, &options))
1094                }));
1095            handler.call(ctx, request_stream, format).await
1096        })
1097    }
1098}
1099
1100/// Trait for bidi streaming RPC handlers using zero-copy request views.
1101pub trait ViewBidiStreamingHandler<ReqView, Res>: Send + Sync + 'static
1102where
1103    ReqView: MessageView<'static> + Send + Sync + 'static,
1104    Res: Message + Send + 'static,
1105{
1106    /// The stream item type. Typically `Res` itself; may be
1107    /// [`PreEncoded`](crate::PreEncoded) or
1108    /// [`MaybeBorrowed`](crate::MaybeBorrowed) for handlers that encode
1109    /// borrowing views per item.
1110    type Item: Encodable<Res> + Send + 'static;
1111
1112    /// Handle a bidi streaming RPC request with zero-copy view items.
1113    fn call(
1114        &self,
1115        ctx: RequestContext,
1116        requests: ServiceStream<OwnedView<ReqView>>,
1117    ) -> BoxFuture<'static, ServiceResult<ServiceStream<Self::Item>>>;
1118}
1119
1120/// Wrapper that implements [`ViewBidiStreamingHandler`] for async functions.
1121pub struct FnViewBidiStreamingHandler<F> {
1122    f: Arc<F>,
1123}
1124
1125impl<F> FnViewBidiStreamingHandler<F> {
1126    /// Create a new function view bidi streaming handler.
1127    pub fn new(f: F) -> Self {
1128        Self { f: Arc::new(f) }
1129    }
1130}
1131
1132impl<F, Fut, ReqView, Res, B> ViewBidiStreamingHandler<ReqView, Res>
1133    for FnViewBidiStreamingHandler<F>
1134where
1135    F: Fn(RequestContext, ServiceStream<OwnedView<ReqView>>) -> Fut + Send + Sync + 'static,
1136    Fut: Future<Output = ServiceResult<ServiceStream<B>>> + Send + 'static,
1137    ReqView: MessageView<'static> + Send + Sync + 'static,
1138    Res: Message + Send + 'static,
1139    B: Encodable<Res> + Send + 'static,
1140{
1141    type Item = B;
1142
1143    fn call(
1144        &self,
1145        ctx: RequestContext,
1146        requests: ServiceStream<OwnedView<ReqView>>,
1147    ) -> BoxFuture<'static, ServiceResult<ServiceStream<B>>> {
1148        let f = Arc::clone(&self.f);
1149        Box::pin(async move { f(ctx, requests).await })
1150    }
1151}
1152
1153/// Helper function to create a view bidi streaming handler from an async function.
1154pub fn view_bidi_streaming_handler_fn<F, Fut, ReqView, Res, B>(
1155    f: F,
1156) -> FnViewBidiStreamingHandler<F>
1157where
1158    F: Fn(RequestContext, ServiceStream<OwnedView<ReqView>>) -> Fut + Send + Sync + 'static,
1159    Fut: Future<Output = ServiceResult<ServiceStream<B>>> + Send + 'static,
1160    ReqView: MessageView<'static> + Send + Sync + 'static,
1161    Res: Message + Send + 'static,
1162    B: Encodable<Res> + Send + 'static,
1163{
1164    FnViewBidiStreamingHandler::new(f)
1165}
1166
1167/// Wrapper to erase the types from a bidi streaming view handler.
1168pub(crate) struct BidiStreamingViewHandlerWrapper<H, ReqView, Res>
1169where
1170    H: ViewBidiStreamingHandler<ReqView, Res>,
1171    ReqView: MessageView<'static> + Send + Sync + 'static,
1172    ReqView::Owned: Message + JsonDeserialize,
1173    Res: Message + Send + 'static,
1174{
1175    handler: Arc<H>,
1176    _phantom: std::marker::PhantomData<fn(ReqView) -> Res>,
1177}
1178
1179impl<H, ReqView, Res> BidiStreamingViewHandlerWrapper<H, ReqView, Res>
1180where
1181    H: ViewBidiStreamingHandler<ReqView, Res>,
1182    ReqView: MessageView<'static> + Send + Sync + 'static,
1183    ReqView::Owned: Message + JsonDeserialize,
1184    Res: Message + Send + 'static,
1185{
1186    pub fn new(handler: H) -> Self {
1187        Self {
1188            handler: Arc::new(handler),
1189            _phantom: std::marker::PhantomData,
1190        }
1191    }
1192}
1193
1194impl<H, ReqView, Res> ErasedBidiStreamingHandler
1195    for BidiStreamingViewHandlerWrapper<H, ReqView, Res>
1196where
1197    H: ViewBidiStreamingHandler<ReqView, Res>,
1198    ReqView: MessageView<'static> + Send + Sync + 'static,
1199    ReqView::Owned: Message + JsonDeserialize,
1200    Res: Message + Send + 'static,
1201{
1202    fn call_erased(
1203        &self,
1204        ctx: RequestContext,
1205        requests: BoxStream<Result<Bytes, ConnectError>>,
1206        format: CodecFormat,
1207    ) -> StreamingHandlerResult {
1208        use futures::StreamExt as _;
1209        let handler = Arc::clone(&self.handler);
1210        Box::pin(async move {
1211            // The stream outlives this frame, so it owns its limits rather
1212            // than borrowing them from `ctx`, which is moved into the call.
1213            let options = ctx.decode_options().clone();
1214            let request_stream: ServiceStream<OwnedView<ReqView>> =
1215                Box::pin(requests.map(move |result| {
1216                    result.and_then(|raw| decode_request_view::<ReqView>(raw, format, &options))
1217                }));
1218            let resp = handler.call(ctx, request_stream).await?;
1219            Ok(resp.map_body(|s| encode_body_stream(s, format)))
1220        })
1221    }
1222}
1223
1224#[cfg(test)]
1225mod tests {
1226    use super::*;
1227    use crate::test_budget::elements_over_default_budget;
1228    use buffa_types::google::protobuf::__buffa::view::StringValueView;
1229    use buffa_types::google::protobuf::StringValue;
1230
1231    #[test]
1232    fn test_decode_request_proto() {
1233        let msg = StringValue::from("hello");
1234        let encoded = Bytes::from(msg.encode_to_vec());
1235        let decoded: StringValue =
1236            decode_request(&encoded, CodecFormat::Proto, &buffa::DecodeOptions::new()).unwrap();
1237        assert_eq!(decoded.value, "hello");
1238    }
1239
1240    #[cfg(feature = "json")]
1241    #[test]
1242    fn test_decode_request_json() {
1243        let encoded = Bytes::from_static(b"\"world\"");
1244        let decoded: StringValue =
1245            decode_request(&encoded, CodecFormat::Json, &buffa::DecodeOptions::new()).unwrap();
1246        assert_eq!(decoded.value, "world");
1247    }
1248
1249    #[test]
1250    fn test_decode_request_proto_invalid() {
1251        let garbage = Bytes::from_static(&[0xFF, 0xFF, 0xFF]);
1252        let err = decode_request::<StringValue>(
1253            &garbage,
1254            CodecFormat::Proto,
1255            &buffa::DecodeOptions::new(),
1256        )
1257        .unwrap_err();
1258        assert_eq!(err.code, crate::error::ErrorCode::InvalidArgument);
1259    }
1260
1261    #[cfg(feature = "json")]
1262    #[test]
1263    fn test_decode_request_json_invalid() {
1264        let garbage = Bytes::from_static(b"not json");
1265        let err = decode_request::<StringValue>(
1266            &garbage,
1267            CodecFormat::Json,
1268            &buffa::DecodeOptions::new(),
1269        )
1270        .unwrap_err();
1271        assert_eq!(err.code, crate::error::ErrorCode::InvalidArgument);
1272    }
1273
1274    #[test]
1275    fn overflow_payload_is_invalid_argument_at_decode_boundary() {
1276        // The wire-visible contract behind the infallible to_owned_message:
1277        // a request whose unknown fields exceed the allowance fails here,
1278        // classified like any other malformed request, before any handler
1279        // (and its owned conversion) runs.
1280        let body = crate::request::tests::unknown_field_overflow_body();
1281        let err = decode_request_view::<StringValueView<'static>>(
1282            body,
1283            CodecFormat::Proto,
1284            &buffa::DecodeOptions::new(),
1285        )
1286        .unwrap_err();
1287        assert_eq!(err.code, crate::error::ErrorCode::InvalidArgument);
1288    }
1289
1290    #[test]
1291    fn test_decode_request_view_proto() {
1292        let msg = StringValue::from("view-test");
1293        let encoded = Bytes::from(msg.encode_to_vec());
1294        let view = decode_request_view::<StringValueView>(
1295            encoded,
1296            CodecFormat::Proto,
1297            &buffa::DecodeOptions::new(),
1298        )
1299        .unwrap();
1300        assert_eq!(view.reborrow().value, "view-test");
1301    }
1302
1303    #[cfg(feature = "json")]
1304    #[test]
1305    fn test_decode_request_view_json() {
1306        let encoded = Bytes::from_static(b"\"json-view\"");
1307        let view = decode_request_view::<StringValueView>(
1308            encoded,
1309            CodecFormat::Json,
1310            &buffa::DecodeOptions::new(),
1311        )
1312        .unwrap();
1313        assert_eq!(view.reborrow().value, "json-view");
1314    }
1315
1316    // Proto-only build: the JSON request-decode arms (`decode_request` and
1317    // `request_proto_bytes`, the latter reached via `decode_request_view`)
1318    // are compiled out and report `Unimplemented`; proto decoding is
1319    // unaffected (covered by the `*_proto` tests above).
1320
1321    #[cfg(not(feature = "json"))]
1322    #[test]
1323    fn decode_request_json_is_unimplemented_without_feature() {
1324        let body = Bytes::from_static(b"\"world\"");
1325        let err =
1326            decode_request::<StringValue>(&body, CodecFormat::Json, &buffa::DecodeOptions::new())
1327                .unwrap_err();
1328        assert_eq!(err.code, crate::error::ErrorCode::Unimplemented);
1329    }
1330
1331    #[cfg(not(feature = "json"))]
1332    #[test]
1333    fn decode_request_view_json_is_unimplemented_without_feature() {
1334        let body = Bytes::from_static(b"\"world\"");
1335        let err = decode_request_view::<StringValueView>(
1336            body,
1337            CodecFormat::Json,
1338            &buffa::DecodeOptions::new(),
1339        )
1340        .unwrap_err();
1341        assert_eq!(err.code, crate::error::ErrorCode::Unimplemented);
1342    }
1343
1344    #[test]
1345    fn test_decode_request_view_proto_invalid() {
1346        let garbage = Bytes::from_static(&[0xFF, 0xFF, 0xFF]);
1347        let err = decode_request_view::<StringValueView>(
1348            garbage,
1349            CodecFormat::Proto,
1350            &buffa::DecodeOptions::new(),
1351        )
1352        .unwrap_err();
1353        assert_eq!(err.code, crate::error::ErrorCode::InvalidArgument);
1354    }
1355
1356    #[tokio::test]
1357    async fn encode_body_stream_owned_items() {
1358        use futures::StreamExt as _;
1359        let s = futures::stream::iter([
1360            Ok(StringValue::from("a")),
1361            Ok(StringValue::from("b")),
1362            Err(ConnectError::internal("boom")),
1363        ]);
1364        let mut out = encode_body_stream::<StringValue, _, _>(s, CodecFormat::Proto);
1365        let a = out.next().await.unwrap().unwrap();
1366        let b = out.next().await.unwrap().unwrap();
1367        assert_eq!(StringValue::decode_from_slice(&a).unwrap().value, "a");
1368        assert_eq!(StringValue::decode_from_slice(&b).unwrap().value, "b");
1369        assert!(out.next().await.unwrap().is_err());
1370        assert!(out.next().await.is_none());
1371    }
1372
1373    #[tokio::test]
1374    async fn encode_body_stream_pre_encoded_items() {
1375        use crate::PreEncoded;
1376        use futures::StreamExt as _;
1377        // A `StreamingHandler` (or `ViewStreamingHandler`) with
1378        // `type Item = PreEncoded` yields bytes the handler encoded
1379        // internally; the proto codec must pass them through verbatim.
1380        let bytes_a = StringValue::from("a").encode_to_bytes();
1381        let bytes_b = StringValue::from("b").encode_to_bytes();
1382        let s = futures::stream::iter([
1383            Ok(PreEncoded::<StringValue>::from_bytes_unchecked(
1384                bytes_a.clone(),
1385            )),
1386            Ok(PreEncoded::<StringValue>::from_bytes_unchecked(
1387                bytes_b.clone(),
1388            )),
1389        ]);
1390        let mut out =
1391            encode_body_stream::<StringValue, PreEncoded<StringValue>, _>(s, CodecFormat::Proto);
1392        assert_eq!(out.next().await.unwrap().unwrap(), bytes_a);
1393        assert_eq!(out.next().await.unwrap().unwrap(), bytes_b);
1394        assert!(out.next().await.is_none());
1395    }
1396
1397    #[cfg(feature = "json")]
1398    #[tokio::test]
1399    async fn encode_body_stream_pre_encoded_json_decodes_per_item() {
1400        use crate::PreEncoded;
1401        use futures::StreamExt as _;
1402        // The JSON path decodes the proto bytes back to `M` per item and
1403        // re-serializes — slow but correct. Each item should match what
1404        // serializing the owned message directly would produce.
1405        let m_a = StringValue::from("a");
1406        let m_b = StringValue::from("b");
1407        let s = futures::stream::iter([
1408            Ok(PreEncoded::<StringValue>::from_bytes_unchecked(
1409                m_a.encode_to_bytes(),
1410            )),
1411            Ok(PreEncoded::<StringValue>::from_bytes_unchecked(
1412                m_b.encode_to_bytes(),
1413            )),
1414        ]);
1415        let mut out =
1416            encode_body_stream::<StringValue, PreEncoded<StringValue>, _>(s, CodecFormat::Json);
1417        assert_eq!(
1418            out.next().await.unwrap().unwrap(),
1419            Bytes::from(serde_json::to_vec(&m_a).unwrap())
1420        );
1421        assert_eq!(
1422            out.next().await.unwrap().unwrap(),
1423            Bytes::from(serde_json::to_vec(&m_b).unwrap())
1424        );
1425        assert!(out.next().await.is_none());
1426    }
1427
1428    #[test]
1429    fn streaming_handler_item_is_inferred_from_closure() {
1430        // `streaming_handler_fn` infers `Item` from the closure's stream
1431        // type. This is a compile-only test: the call type-checks iff
1432        // `FnStreamingHandler<F>: StreamingHandler<Req, Res, Item = B>`
1433        // unifies for both an owned-message and a `PreEncoded` stream.
1434        use crate::PreEncoded;
1435
1436        fn assert_handler<H, Req, Res, B>(_: &H)
1437        where
1438            H: StreamingHandler<Req, Res, Item = B>,
1439            Req: Message + Send + 'static,
1440            Res: Message + Send + 'static,
1441            B: Encodable<Res> + Send + 'static,
1442        {
1443        }
1444
1445        let owned = streaming_handler_fn(|_ctx: RequestContext, _req: StringValue| async move {
1446            Response::stream_ok(futures::stream::iter([Ok(StringValue::from("x"))]))
1447        });
1448        assert_handler::<_, StringValue, StringValue, StringValue>(&owned);
1449
1450        // When the closure pins the `PreEncoded` message type concretely,
1451        // `Res` is inferred from the unique `Encodable<M> for PreEncoded<M>`
1452        // impl. No turbofish needed on `streaming_handler_fn`. (The codegen
1453        // path is different: the trait method's `impl Encodable<Out>` item
1454        // is opaque, so the generated `register_routes` impl pins `Res` at
1455        // the `route_view_*_stream::<_, _, Res>(...)` call site instead.)
1456        let pre = streaming_handler_fn(|_ctx: RequestContext, _req: StringValue| async move {
1457            Response::stream_ok(futures::stream::iter([Ok(
1458                PreEncoded::<StringValue>::from_bytes_unchecked(
1459                    StringValue::from("x").encode_to_bytes(),
1460                ),
1461            )]))
1462        });
1463        assert_handler::<_, StringValue, StringValue, PreEncoded<StringValue>>(&pre);
1464    }
1465
1466    /// Owned-message handlers decode through `Payload`/`decode_request`
1467    /// rather than the view helpers, so they read their budget from the
1468    /// `DecodeOptions` carried on `Payload`. That is easy to leave
1469    /// unattached, which silently falls back to buffa's defaults, so both
1470    /// owned entry points are pinned here.
1471    #[test]
1472    fn owned_message_decoding_honours_the_configured_limit() {
1473        use buffa_types::google::protobuf::{ListValue, Value};
1474
1475        // Decoded as owned `Value`s, so the owned footprint sets the count.
1476        let n = elements_over_default_budget::<Value>();
1477        let list = ListValue {
1478            values: (0..n).map(|_| Value::default()).collect(),
1479            ..Default::default()
1480        };
1481        let encoded = Bytes::from(buffa::Message::encode_to_vec(&list));
1482        let raised = crate::Limits::default().element_memory_limit(usize::MAX);
1483
1484        // `decode_request`, used by the owned-message streaming wrappers.
1485        assert!(
1486            decode_request::<ListValue>(
1487                &encoded,
1488                CodecFormat::Proto,
1489                &crate::Limits::default().decode_options()
1490            )
1491            .is_err(),
1492            "the default budget must still reject"
1493        );
1494        let decoded: ListValue =
1495            decode_request(&encoded, CodecFormat::Proto, &raised.decode_options())
1496                .expect("raised budget must admit");
1497        assert_eq!(decoded.values.len(), n);
1498
1499        // `Payload::take_message`, used by the owned-message unary wrapper.
1500        let payload = crate::Payload::new(encoded.clone(), CodecFormat::Proto);
1501        assert!(
1502            payload.take_message::<ListValue>().is_err(),
1503            "a payload with no limits attached decodes under buffa defaults"
1504        );
1505        let payload = crate::Payload::new(encoded, CodecFormat::Proto)
1506            .with_decode_options(raised.decode_options());
1507        let decoded: ListValue = payload
1508            .take_message()
1509            .expect("a payload carrying raised limits must admit");
1510        assert_eq!(decoded.values.len(), n);
1511    }
1512
1513    /// The budget rejection names the limit to raise, since it is the one
1514    /// decode failure an operator can fix without the peer changing.
1515    #[test]
1516    fn an_over_budget_decode_says_which_limit_to_raise() {
1517        use buffa_types::google::protobuf::__buffa::view::{ListValueView, ValueView};
1518        use buffa_types::google::protobuf::{ListValue, Value};
1519
1520        // Decoded as borrowed `ValueView`s, so the view footprint — the
1521        // smaller of the two — sets the count.
1522        let list = ListValue {
1523            values: (0..elements_over_default_budget::<ValueView<'_>>())
1524                .map(|_| Value::default())
1525                .collect(),
1526            ..Default::default()
1527        };
1528        let encoded = Bytes::from(buffa::Message::encode_to_vec(&list));
1529        let err = decode_borrowed_request_view::<ListValueView<'_>>(
1530            &encoded,
1531            &crate::Limits::default().decode_options(),
1532        )
1533        .expect_err("over budget");
1534        let message = err.message.unwrap_or_default();
1535        assert!(
1536            message.contains("element_memory_limit"),
1537            "the budget rejection must name the knob, got {message:?}"
1538        );
1539
1540        // A malformed request must NOT suggest raising a limit — that would
1541        // send an operator chasing a setting that cannot help.
1542        let garbage = Bytes::from_static(&[0xFF, 0xFF, 0xFF]);
1543        let err = decode_borrowed_request_view::<ListValueView<'_>>(
1544            &garbage,
1545            &crate::Limits::default().decode_options(),
1546        )
1547        .expect_err("malformed");
1548        let message = err.message.unwrap_or_default();
1549        assert!(
1550            !message.contains("element_memory_limit"),
1551            "a malformed request must not point at a limit, got {message:?}"
1552        );
1553    }
1554
1555    /// The element-memory budget is a *configured* limit, not a constant:
1556    /// the same bytes must be rejected at the default and accepted once the
1557    /// service raises it. Without the second half, wiring the knob to
1558    /// nothing would still pass.
1559    #[test]
1560    fn element_memory_limit_is_taken_from_the_configured_limits() {
1561        use buffa_types::google::protobuf::__buffa::view::{ListValueView, ValueView};
1562        use buffa_types::google::protobuf::{ListValue, Value};
1563
1564        // Element footprint is what the budget charges, not element
1565        // contents, so this stays small on the wire.
1566        let n = elements_over_default_budget::<ValueView<'_>>();
1567        let list = ListValue {
1568            values: (0..n).map(|_| Value::default()).collect(),
1569            ..Default::default()
1570        };
1571        let encoded = Bytes::from(buffa::Message::encode_to_vec(&list));
1572
1573        let defaults = crate::Limits::default();
1574        let err =
1575            decode_borrowed_request_view::<ListValueView<'_>>(&encoded, &defaults.decode_options())
1576                .expect_err("the fixture must exceed the default element-memory budget");
1577        assert_eq!(err.code, crate::error::ErrorCode::InvalidArgument);
1578
1579        let raised = crate::Limits::default().element_memory_limit(usize::MAX);
1580        let view =
1581            decode_borrowed_request_view::<ListValueView<'_>>(&encoded, &raised.decode_options())
1582                .expect("raising the limit must admit the same bytes");
1583        assert_eq!(view.values.len(), n);
1584    }
1585
1586    /// `unlimited()` must lift the decode budget too — a caller who asks for
1587    /// no restrictions and still gets a 32 MiB element ceiling has been
1588    /// silently ignored.
1589    #[test]
1590    fn unlimited_limits_lift_the_element_budget() {
1591        assert_eq!(crate::Limits::unlimited().element_memory_limit, usize::MAX);
1592    }
1593
1594    /// A context built outside the service carries buffa's defaults rather
1595    /// than no limits at all.
1596    #[test]
1597    fn a_bare_request_context_decodes_under_buffa_defaults() {
1598        let ctx = RequestContext::new(http::HeaderMap::new());
1599        let listing = format!("{:?}", ctx.decode_options());
1600        assert!(
1601            listing.contains(&buffa::DEFAULT_ELEMENT_MEMORY_LIMIT.to_string()),
1602            "expected buffa's default element-memory budget, got {listing}"
1603        );
1604    }
1605}