Skip to main content

vox_types/
request_context.rs

1use std::sync::OnceLock;
2
3use crate::{ConnectionId, Extensions, MetadataEntry, MethodDescriptor, RequestId};
4
5/// Borrowed per-request context exposed to opted-in Rust service handlers.
6///
7/// This is constructed by generated dispatchers from the inbound request and
8/// borrows request metadata directly rather than cloning it.
9#[derive(Clone, Copy, Debug)]
10pub struct RequestContext<'a> {
11    method: &'static MethodDescriptor,
12    metadata: &'a [MetadataEntry<'static>],
13    request_id: Option<RequestId>,
14    connection_id: Option<ConnectionId>,
15    extensions: &'a Extensions,
16}
17
18impl<'a> RequestContext<'a> {
19    /// Create a new borrowed request context.
20    pub fn new(method: &'static MethodDescriptor, metadata: &'a [MetadataEntry<'static>]) -> Self {
21        Self::with_transport(method, metadata, None, None, empty_extensions())
22    }
23
24    /// Create a new borrowed request context with middleware extensions.
25    pub fn with_extensions(
26        method: &'static MethodDescriptor,
27        metadata: &'a [MetadataEntry<'static>],
28        extensions: &'a Extensions,
29    ) -> Self {
30        Self::with_transport(method, metadata, None, None, extensions)
31    }
32
33    /// Create a new borrowed request context with transport identifiers.
34    pub fn with_transport(
35        method: &'static MethodDescriptor,
36        metadata: &'a [MetadataEntry<'static>],
37        request_id: Option<RequestId>,
38        connection_id: Option<ConnectionId>,
39        extensions: &'a Extensions,
40    ) -> Self {
41        Self {
42            method,
43            metadata,
44            request_id,
45            connection_id,
46            extensions,
47        }
48    }
49
50    /// Static descriptor for the method being handled.
51    pub fn method(&self) -> &'static MethodDescriptor {
52        self.method
53    }
54
55    /// Request metadata borrowed from the inbound call.
56    pub fn metadata(&self) -> &'a [MetadataEntry<'static>] {
57        self.metadata
58    }
59
60    /// Wire-level request identifier for this call, when the reply sink exposes it.
61    pub fn request_id(&self) -> Option<RequestId> {
62        self.request_id
63    }
64
65    /// Virtual connection identifier for this call, when the reply sink exposes it.
66    pub fn connection_id(&self) -> Option<ConnectionId> {
67        self.connection_id
68    }
69
70    /// Per-request middleware extensions bag.
71    pub fn extensions(&self) -> &'a Extensions {
72        self.extensions
73    }
74}
75
76fn empty_extensions() -> &'static Extensions {
77    static EMPTY: OnceLock<Extensions> = OnceLock::new();
78    EMPTY.get_or_init(Extensions::new)
79}
80
81#[cfg(test)]
82mod tests {
83    use crate::{ConnectionId, MetadataEntry, RequestId, method_descriptor};
84
85    use super::RequestContext;
86
87    #[test]
88    fn transport_identifiers_are_exposed_when_present() {
89        let method = method_descriptor::<(), ()>("demo-service", "demo", &[], None);
90        let metadata: [MetadataEntry<'static>; 0] = [];
91
92        let context = RequestContext::with_transport(
93            method,
94            &metadata,
95            Some(RequestId(11)),
96            Some(ConnectionId(13)),
97            super::empty_extensions(),
98        );
99
100        assert_eq!(context.request_id(), Some(RequestId(11)));
101        assert_eq!(context.connection_id(), Some(ConnectionId(13)));
102        assert_eq!(context.method().id, method.id);
103        assert_eq!(context.method().method_name, "demo");
104    }
105}