connectrpc 0.8.0

A Tower-based Rust implementation of the ConnectRPC protocol
Documentation
//! Borrowed request wrapper for single-request RPCs: [`ServiceRequest`].
//!
//! Unary and server-streaming handler methods receive a
//! [`ServiceRequest<'_, Req>`] — a zero-copy view of the request message plus
//! the raw request body, both borrowed from buffers that the generated
//! dispatch glue owns for the duration of the call. The name mirrors
//! [`ServiceResult`](crate::response::ServiceResult) on the response side.
//!
//! Not to be confused with the interceptor-facing request types
//! (`UnaryRequest` / `StreamRequest`), which wrap the *wire-level* request a
//! middleware sees; `ServiceRequest` is what a service trait implementation
//! receives after decoding.

use buffa::view::MessageView;
use buffa::view::OwnedView;
use bytes::Bytes;

/// Re-export of buffa's view-family trait.
///
/// Generated buffa code implements this for every message (when views are
/// generated), linking the owned type to its borrowed view (`Req::View<'a>`)
/// and its `'static` handle (`Req::ViewHandle`, the `FooOwnedView` wrapper).
/// [`ServiceRequest<'_, Req>`] and
/// [`StreamMessage<Req>`](crate::StreamMessage) are bounded on it.
///
/// The per-field accessor methods (`msg.name()`, …) are inherent on the
/// concrete generated wrapper and on the view's public fields; code that is
/// generic over `M: HasMessageView` reaches the message through
/// `as_ref()`/[`view`](crate::StreamMessage::view) /
/// [`to_owned_message`](crate::StreamMessage::to_owned_message) instead of
/// named fields.
pub use buffa::HasMessageView;

/// A borrowed single-message RPC request (unary and server-streaming): a
/// zero-copy view of `Req` plus the raw request body, valid for the duration
/// of the handler call.
///
/// `ServiceRequest` dereferences to the request's view type, so message
/// fields are read directly (`request.name`, `request.id`) with their
/// borrows tied to the call frame — they cannot outlive the request body.
/// Anything that must outlive the call (`tokio::spawn`, channels, server
/// state) takes owned data: call [`to_owned_message`](Self::to_owned_message)
/// (or copy the specific fields) first and move the owned value instead.
///
/// The wrapper is `Copy` (it is two references), so it can be passed to
/// helper functions freely without `&` ceremony.
pub struct ServiceRequest<'a, Req: HasMessageView> {
    view: &'a Req::View<'a>,
    body: &'a Bytes,
}

impl<'a, Req: HasMessageView> ServiceRequest<'a, Req> {
    /// Assemble a request from a decoded view and the buffer it borrows from.
    ///
    /// Called by the generated dispatch glue; also the supported way to
    /// construct a request for unit-testing a handler:
    ///
    /// ```rust,ignore
    /// use buffa::Message;               // encode_to_vec
    /// use buffa::view::HasMessageView;  // GreetRequest::decode_view
    ///
    /// let body = Bytes::from(GreetRequest { name: "ada".into(), ..Default::default() }.encode_to_vec());
    /// let view = GreetRequest::decode_view(&body)?;
    /// let req = ServiceRequest::<GreetRequest>::from_parts(&view, &body);
    /// let resp = service.greet(RequestContext::new(HeaderMap::new()), req).await?;
    /// ```
    ///
    /// `view` must have been produced by buffa's wire decoder — that is the
    /// precondition behind [`to_owned_message`](Self::to_owned_message)'s
    /// infallibility (a hand-assembled view, e.g. one whose unknown fields
    /// were pushed via buffa's `push_raw`, can make it panic). Decoding from
    /// `body` specifically is what makes the zero-copy `bytes`-field path
    /// apply; if the buffers don't match, conversion still succeeds —
    /// `bytes` fields are copied instead of sliced.
    pub fn from_parts(view: &'a Req::View<'a>, body: &'a Bytes) -> Self {
        Self { view, body }
    }

    /// The zero-copy view of the request message.
    ///
    /// Equivalent to the `Deref` target; useful when a `&FooRequestView<'_>`
    /// is needed explicitly (struct patterns, lifetime-parameterised helpers).
    #[must_use]
    pub fn view(&self) -> &'a Req::View<'a> {
        self.view
    }

    /// Convert to the owned request message.
    ///
    /// `bytes` fields are sliced zero-copy out of the retained body where
    /// possible; string and repeated fields are allocated. Use this for data
    /// that must outlive the handler call.
    ///
    /// Infallible for dispatcher-built requests: buffa (≥ 0.8.1) charges
    /// every unknown-field record against the decode-time allowance, so any
    /// view the dispatcher decoded successfully re-materializes within that
    /// same allowance, and known fields were already validated at decode.
    ///
    /// # Panics
    ///
    /// Panics if the view was not produced by buffa's wire decoder (see
    /// [`from_parts`](Self::from_parts)); unreachable through the generated
    /// dispatch glue.
    #[must_use]
    pub fn to_owned_message(&self) -> Req {
        self.view
            .to_owned_from_source(Some(self.body))
            .expect("wire-decoded view always converts (buffa >= 0.8.1)")
    }

    /// The request body as protobuf wire bytes.
    ///
    /// For JSON-encoded requests this is the body re-encoded to protobuf
    /// (the same buffer the view borrows from), not the original JSON text.
    #[must_use]
    pub fn bytes(&self) -> &'a Bytes {
        self.body
    }

    /// Rebuild a `'static` owned view of the request from the retained body.
    ///
    /// Zero-copy despite the `to_owned_` name: a `Bytes` refcount bump plus
    /// a decode walk, with no per-field allocation. Use this to return the
    /// request as a response body (e.g.
    /// `MaybeBorrowed::Borrowed(req.to_owned_view())` in a pass-through
    /// handler) — the response must be `'static`, so the borrowed view
    /// itself cannot be returned.
    ///
    /// View response bodies encode for the proto codec only; JSON clients
    /// receive `Unimplemented`. See
    /// [`MaybeBorrowed`](crate::MaybeBorrowed)'s codec note.
    ///
    /// # Panics
    ///
    /// Panics if the body is not a valid encoding of `Req`. This cannot
    /// happen for requests built by the generated dispatch glue, which
    /// decodes the view from exactly these bytes before constructing the
    /// `ServiceRequest`.
    #[must_use]
    pub fn to_owned_view(&self) -> OwnedView<Req::View<'static>> {
        OwnedView::decode(self.body.clone())
            .expect("ServiceRequest body was already view-decoded by the dispatch glue")
    }
}

// Manual `Clone`/`Copy`: a derive would bound `Req` itself rather than the
// stored references, and `ServiceRequest` is two references regardless of `Req`.
impl<Req: HasMessageView> Clone for ServiceRequest<'_, Req> {
    fn clone(&self) -> Self {
        *self
    }
}
impl<Req: HasMessageView> Copy for ServiceRequest<'_, Req> {}

impl<'a, Req: HasMessageView> core::ops::Deref for ServiceRequest<'a, Req> {
    type Target = Req::View<'a>;

    fn deref(&self) -> &Req::View<'a> {
        self.view
    }
}

impl<'a, Req: HasMessageView> core::fmt::Debug for ServiceRequest<'a, Req>
where
    Req::View<'a>: core::fmt::Debug,
{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        self.view.fmt(f)
    }
}

#[cfg(test)]
pub(crate) mod tests {
    use super::*;
    use buffa::Message;
    use buffa_types::google::protobuf::__buffa::view::StringValueView;
    use buffa_types::google::protobuf::StringValue;

    // `StringValue: HasMessageView` comes from buffa-types' generated code —
    // no local glue impls are needed.

    fn encode(value: &str) -> Bytes {
        Bytes::from(
            StringValue {
                value: value.into(),
                ..Default::default()
            }
            .encode_to_vec(),
        )
    }

    #[test]
    fn deref_field_access_and_view() {
        let body = encode("zero-copy");
        let view = StringValueView::decode_view(&body).unwrap();
        let req = ServiceRequest::<StringValue>::from_parts(&view, &body);

        // Field access through Deref, plus the explicit view() escape hatch.
        assert_eq!(req.value, "zero-copy");
        assert_eq!(req.view().value, "zero-copy");

        // The borrow points into the request body, not a copy.
        let range = body.as_ptr_range();
        assert!(range.contains(&req.value.as_ptr()));
    }

    #[test]
    fn to_owned_message_and_bytes() {
        let body = encode("keep me");
        let view = StringValueView::decode_view(&body).unwrap();
        let req = ServiceRequest::<StringValue>::from_parts(&view, &body);

        let owned: StringValue = req.to_owned_message();
        assert_eq!(owned.value, "keep me");
        assert_eq!(req.bytes().as_ref(), body.as_ref());

        // to_owned_view rebuilds a 'static view backed by the same buffer.
        let owned_view = req.to_owned_view();
        assert_eq!(owned_view.reborrow().value, "keep me");
        let range = body.as_ptr_range();
        assert!(range.contains(&owned_view.reborrow().value.as_ptr()));

        // Copy semantics: passing by value doesn't consume the original.
        let copy = req;
        assert_eq!(copy.value, req.value);
        assert_eq!(format!("{req:?}"), format!("{copy:?}"));
    }

    /// A `StringValue` payload followed by `records` contiguous unknown
    /// wire records (field 15, varint 0 — two bytes each), which the view
    /// decoder coalesces into a single span. The infallible conversion
    /// contract rests on decode-time and replay-time accounting agreeing
    /// for exactly this shape.
    pub(crate) fn body_with_unknown_records(records: usize) -> Bytes {
        let mut bytes = StringValue {
            value: "known".into(),
            ..Default::default()
        }
        .encode_to_vec();
        bytes.extend([0x78, 0x00].repeat(records));
        Bytes::from(bytes)
    }

    /// One more unknown record than the default allowance — must be
    /// rejected at the decode boundary, never at owned conversion.
    pub(crate) fn unknown_field_overflow_body() -> Bytes {
        body_with_unknown_records(buffa::DEFAULT_UNKNOWN_FIELD_LIMIT + 1)
    }

    #[test]
    fn unknown_field_overflow_is_rejected_at_decode() {
        let body = unknown_field_overflow_body();
        assert!(matches!(
            StringValueView::decode_view(&body),
            Err(buffa::DecodeError::UnknownFieldLimitExceeded)
        ));
    }

    #[test]
    fn unknown_fields_at_exact_allowance_convert_infallibly() {
        // Boundary pin for the infallible signature: a payload accepted at
        // the decode limit must also convert. Uses a small explicit limit so
        // the boundary is exercised exactly without a multi-megabyte body.
        use buffa::view::HasMessageView;
        let opts = buffa::DecodeOptions::new().with_unknown_field_limit(4);
        let body = body_with_unknown_records(4);
        let view =
            StringValue::decode_view_with_options(&body, &opts).expect("exactly at the allowance");
        let req = ServiceRequest::<StringValue>::from_parts(&view, &body);
        assert_eq!(req.to_owned_message().value, "known");

        // One more record and the decode boundary rejects it instead.
        let over = body_with_unknown_records(5);
        assert!(matches!(
            StringValue::decode_view_with_options(&over, &opts),
            Err(buffa::DecodeError::UnknownFieldLimitExceeded)
        ));
    }
}