Skip to main content

cratestack_axum/envelope_layer/
layer.rs

1//! [`EnvelopeLayer`] and the configuration it shares with its services.
2
3use std::fmt;
4use std::sync::Arc;
5
6use cratestack_core::AcceptedContracts;
7use tower::Layer;
8
9use super::builder::{EnvelopeLayerBuilder, Transport};
10use super::mode::EnvelopePolicy;
11use super::principal::{PrincipalMapper, ThumbprintPrincipal};
12use super::resolver::BindingResolver;
13use super::seal_policy::{AcceptNamesEnvelope, ResponseSealPolicy};
14use super::server_envelope::ServerEnvelope;
15use super::service::EnvelopeService;
16use super::{DEFAULT_MAX_BODY_BYTES, DEFAULT_MAX_CONTRACT_TRIALS, PAYLOAD_MEDIA_TYPE};
17
18pub(super) struct Config {
19    pub(super) envelope: Arc<dyn ServerEnvelope>,
20    pub(super) policy: Box<dyn EnvelopePolicy>,
21    pub(super) resolver: Box<dyn BindingResolver>,
22    pub(super) principal: Box<dyn PrincipalMapper>,
23    pub(super) seal_policy: Box<dyn ResponseSealPolicy>,
24    pub(super) audience: String,
25    /// Per op key, the digests a request may bind (`contract.rs`).
26    pub(super) contracts: AcceptedContracts,
27    pub(super) max_contract_trials: usize,
28    pub(super) max_body_bytes: usize,
29    /// Normalised (`""` or `"/api"`), for the allow-list.
30    pub(super) mount_prefix: String,
31    /// Route templates, relative to the mount prefix, that may be matched
32    /// without resolving (decision S2).
33    pub(super) allow_unresolved: Vec<String>,
34    /// The payload types the layer allows inside the seal (`payload.rs`).
35    pub(super) payload_request: Vec<String>,
36    pub(super) payload_response: Vec<String>,
37}
38
39/// Opens signed requests and seals responses for a generated router; see
40/// the [module docs](crate::envelope_layer) for placement and guarantees.
41/// Build it with [`EnvelopeLayer::builder`], or with the generated
42/// `cratestack_schema::axum::envelope_layer`. Cheap to clone.
43#[derive(Clone)]
44pub struct EnvelopeLayer {
45    pub(super) config: Arc<Config>,
46}
47
48impl EnvelopeLayer {
49    /// Start a layer for `envelope` (a `cratestack_cose::CoseEnvelope`
50    /// built with `CoseEnvelope::server`, or your own [`ServerEnvelope`]).
51    ///
52    /// - `audience`: this service's configured logical id, which every
53    ///   binding carries. Not the `Host` header, and distinct from the
54    ///   audience this service seals its own outbound requests for.
55    /// - `contracts`: the generated `cratestack_schema::ACCEPTED_CONTRACTS`,
56    ///   per op, the op-contract digests a request may bind (binding
57    ///   version 2, cratestack#1123). Which one a request used is told by
58    ///   its `Cratestack-Contract` header (see [`EnvelopeLayerBuilder::max_contract_trials`]).
59    ///   The table is `&'static`: a deployer who loads history at runtime
60    ///   has to `Box::leak` it, which the generated table (PR 3's lock)
61    ///   never needs; accepting a `Cow`/`Arc` is a later, compatible change.
62    ///
63    /// Then name the transport ([`EnvelopeLayerBuilder::rest`],
64    /// [`EnvelopeLayerBuilder::rpc`] or a custom
65    /// [`EnvelopeLayerBuilder::binding_resolver`]) and the
66    /// [`EnvelopeLayerBuilder::policy`]; neither has a default. The
67    /// generated `envelope_layer(envelope, policy, audience)` does both
68    /// for the schema's own transport.
69    pub fn builder(
70        envelope: impl ServerEnvelope,
71        audience: impl Into<String>,
72        contracts: AcceptedContracts,
73    ) -> EnvelopeLayerBuilder {
74        EnvelopeLayerBuilder {
75            envelope: Arc::new(envelope),
76            audience: audience.into(),
77            contracts,
78            max_contract_trials: DEFAULT_MAX_CONTRACT_TRIALS,
79            policy: None,
80            transport: Transport::None,
81            transport_prefix: None,
82            mount_prefix: None,
83            unresolved_mode: None,
84            allow_unresolved: Vec::new(),
85            principal: Box::new(ThumbprintPrincipal::new()),
86            seal_policy: Box::new(AcceptNamesEnvelope),
87            max_body_bytes: DEFAULT_MAX_BODY_BYTES,
88            payload_request: vec![PAYLOAD_MEDIA_TYPE.to_owned()],
89            payload_response: vec![PAYLOAD_MEDIA_TYPE.to_owned()],
90        }
91    }
92}
93
94impl<S> Layer<S> for EnvelopeLayer {
95    type Service = EnvelopeService<S>;
96
97    fn layer(&self, inner: S) -> Self::Service {
98        EnvelopeService {
99            inner,
100            config: self.config.clone(),
101        }
102    }
103}
104
105impl fmt::Debug for EnvelopeLayer {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        f.debug_struct("EnvelopeLayer")
108            .field("audience", &self.config.audience)
109            .field("media_type", &self.config.envelope.media_type())
110            .field("mount_prefix", &self.config.mount_prefix)
111            .field("allow_unresolved", &self.config.allow_unresolved)
112            .field("max_body_bytes", &self.config.max_body_bytes)
113            .finish_non_exhaustive()
114    }
115}