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}