Skip to main content

cratestack_axum/envelope_layer/
opened.rs

1//! The values a [`super::ServerEnvelope`] hands back: [`OpenedRequest`],
2//! the opaque per-request [`SealContext`], and a [`Sealed`] response.
3
4use std::any::Any;
5use std::borrow::Cow;
6use std::fmt;
7use std::sync::Arc;
8
9use bytes::Bytes;
10use cratestack_core::VerifiedSigner;
11
12/// A request an envelope verified: the payload to hand the router, the
13/// signer to record, and the context its response is sealed with.
14///
15/// An implementation builds it with [`OpenedRequest::new`] only after the
16/// checks passed. The layer does not hand it to plug-ins: the principal
17/// mapper gets a [`super::VerifiedRequest`], which only the layer can make.
18#[derive(Clone)]
19pub struct OpenedRequest {
20    payload: Bytes,
21    signer: VerifiedSigner,
22    context: SealContext,
23}
24
25impl OpenedRequest {
26    /// `payload` exactly as signed (a slice of the body is fine), `signer`
27    /// the key that verified it. The seal context is
28    /// [`SealContext::empty`]; see [`with_seal_context`](Self::with_seal_context).
29    pub fn new(payload: Bytes, signer: VerifiedSigner) -> Self {
30        Self {
31            payload,
32            signer,
33            context: SealContext::empty(),
34        }
35    }
36
37    /// Carry `context` to this request's `seal_response`.
38    pub fn with_seal_context(self, context: SealContext) -> Self {
39        Self { context, ..self }
40    }
41
42    /// The payload, as signed.
43    pub fn payload(&self) -> &Bytes {
44        &self.payload
45    }
46
47    /// The key that verified.
48    pub fn signer(&self) -> &VerifiedSigner {
49        &self.signer
50    }
51
52    /// What `seal_response` will be handed for this request.
53    pub fn seal_context(&self) -> &SealContext {
54        &self.context
55    }
56
57    pub(super) fn into_parts(self) -> (Bytes, VerifiedSigner, SealContext) {
58        (self.payload, self.signer, self.context)
59    }
60}
61
62/// The payload's length, not its bytes: a verified body is application data
63/// and `{:?}` is how it would reach a log.
64impl fmt::Debug for OpenedRequest {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        f.debug_struct("OpenedRequest")
67            .field("payload_len", &self.payload.len())
68            .field("signer", &self.signer)
69            .field("seal_context", &self.context)
70            .finish()
71    }
72}
73
74/// Opaque per-request state an envelope passes from `open_request` to the
75/// `seal_response` of the same request (API-review decision, 2026-09-26).
76///
77/// The layer never looks inside: it only carries it. A composite envelope
78/// (cratestack#1078) records which framing opened the request, so that a
79/// Mac0 request is answered with Mac0 and a Sign1 one with Sign1; a
80/// single-framing envelope such as `CoseEnvelope` needs nothing and leaves
81/// it [`empty`](Self::empty).
82#[derive(Clone, Default)]
83pub struct SealContext(Option<Arc<dyn Any + Send + Sync>>);
84
85impl SealContext {
86    /// No context: what a single-framing envelope returns, and what the
87    /// layer passes when sealing the response to an unsigned request.
88    pub fn empty() -> Self {
89        Self(None)
90    }
91
92    /// Carry `value`.
93    pub fn new<T: Any + Send + Sync>(value: T) -> Self {
94        Self(Some(Arc::new(value)))
95    }
96
97    /// The value, if it is a `T`.
98    pub fn get<T: Any>(&self) -> Option<&T> {
99        self.0.as_deref().and_then(|value| value.downcast_ref())
100    }
101
102    /// Whether this is [`empty`](Self::empty).
103    pub fn is_empty(&self) -> bool {
104        self.0.is_none()
105    }
106}
107
108impl fmt::Debug for SealContext {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        f.write_str(if self.is_empty() {
111            "SealContext(empty)"
112        } else {
113            "SealContext(..)"
114        })
115    }
116}
117
118/// A sealed response body and the media type the layer stamps on it as its
119/// `Content-Type` (API-review decision, 2026-09-26: a composite answers
120/// each request in its own framing, so the type is per response, not per
121/// envelope).
122///
123/// The layer checks the type before sending: it must be a valid header
124/// value naming `application/cose` or a type the envelope claims.
125#[derive(Clone, PartialEq, Eq)]
126pub struct Sealed {
127    body: Bytes,
128    media_type: Cow<'static, str>,
129}
130
131impl Sealed {
132    /// `body`, sealed, to be sent as `media_type`.
133    pub fn new(body: Bytes, media_type: impl Into<Cow<'static, str>>) -> Self {
134        Self {
135            body,
136            media_type: media_type.into(),
137        }
138    }
139
140    /// The sealed bytes.
141    pub fn body(&self) -> &Bytes {
142        &self.body
143    }
144
145    /// The `Content-Type` to send them as.
146    pub fn media_type(&self) -> &str {
147        &self.media_type
148    }
149
150    pub(super) fn into_parts(self) -> (Bytes, Cow<'static, str>) {
151        (self.body, self.media_type)
152    }
153}
154
155impl fmt::Debug for Sealed {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        f.debug_struct("Sealed")
158            .field("body_len", &self.body.len())
159            .field("media_type", &self.media_type)
160            .finish()
161    }
162}