Skip to main content

cratestack_axum/envelope_layer/
build.rs

1//! [`EnvelopeLayerBuilder::build`]: every check the configuration gets.
2
3use std::sync::Arc;
4
5use cratestack_core::CratestackError;
6use http::HeaderValue;
7
8use super::builder::{EnvelopeLayerBuilder, Transport};
9use super::layer::{Config, EnvelopeLayer};
10use super::media;
11use super::mode::{EnvelopePolicy, WithUnresolved};
12use super::resolver::BindingResolver;
13use super::resolver_rest::RestBindingResolver;
14use super::resolver_rpc::RpcBindingResolver;
15use crate::idempotency::mount_prefix;
16
17impl EnvelopeLayerBuilder {
18    /// Check the configuration: `CratestackError::Validation` when the
19    /// audience is empty (it would bind no recipient), the policy or the
20    /// transport is missing, the REST route table is empty (a `rest(..)`
21    /// given another schema's, or an RPC schema's, empty table would bind
22    /// nothing), the body limit or the contract trials is zero, an allow-listed template does not
23    /// start with `/`, or the envelope's media type is not a valid header
24    /// value naming `application/cose` or a type the envelope claims.
25    pub fn build(self) -> Result<EnvelopeLayer, CratestackError> {
26        if self.audience.is_empty() {
27            return Err(invalid("the envelope layer's audience must not be empty"));
28        }
29        let policy = self
30            .policy
31            .ok_or_else(|| invalid("the envelope layer has no policy (decision D9: no default)"))?;
32        let policy: Box<dyn EnvelopePolicy> = match self.unresolved_mode {
33            Some(unresolved) => Box::new(WithUnresolved { policy, unresolved }),
34            None => policy,
35        };
36        // `mount_prefix(..)` wins over `rest(..)`/`rpc(..)`'s, in any order.
37        let prefix = self
38            .mount_prefix
39            .as_deref()
40            .or(self.transport_prefix.as_deref())
41            .unwrap_or("");
42        let resolver: Box<dyn BindingResolver> = match self.transport {
43            Transport::None => {
44                return Err(invalid(
45                    "the envelope layer needs a transport: rest(..), rpc(..) or binding_resolver(..)",
46                ));
47            }
48            Transport::Rest([]) => {
49                return Err(invalid(
50                    "rest(..) was given an empty route table: pass the generated \
51                     ROUTE_TRANSPORTS of a REST schema, or use rpc(..) for `transport rpc`",
52                ));
53            }
54            Transport::Rest(routes) => Box::new(RestBindingResolver::new(prefix, routes)),
55            Transport::Rpc => Box::new(RpcBindingResolver::new(prefix)),
56            Transport::Custom(resolver) => resolver,
57        };
58        if self.max_contract_trials == 0 {
59            return Err(invalid(
60                "the envelope layer's contract trials must be at least 1",
61            ));
62        }
63        if self.max_body_bytes == 0 {
64            return Err(invalid("the envelope layer's body limit must not be zero"));
65        }
66        if let Some(bad) = self.allow_unresolved.iter().find(|t| !t.starts_with('/')) {
67            return Err(invalid(&format!(
68                "allow_unresolved({bad:?}): a route template starts with '/'"
69            )));
70        }
71        super::builder_payload::validate(&self.payload_request, &self.payload_response)?;
72        let media_type = self.envelope.media_type();
73        if HeaderValue::from_str(media_type).is_err()
74            || !media::is_envelope_media_type(media_type, &*self.envelope)
75        {
76            return Err(invalid(
77                "the envelope's media type must be a valid header value naming \
78                 application/cose or a type the envelope claims (is_envelope_content_type)",
79            ));
80        }
81        Ok(EnvelopeLayer {
82            config: Arc::new(Config {
83                envelope: self.envelope,
84                policy,
85                resolver,
86                principal: self.principal,
87                seal_policy: self.seal_policy,
88                audience: self.audience,
89                contracts: self.contracts,
90                max_contract_trials: self.max_contract_trials,
91                max_body_bytes: self.max_body_bytes,
92                mount_prefix: mount_prefix::normalize(prefix),
93                allow_unresolved: self.allow_unresolved,
94                payload_request: self.payload_request,
95                payload_response: self.payload_response,
96            }),
97        })
98    }
99}
100
101fn invalid(message: &str) -> CratestackError {
102    CratestackError::Validation(message.to_owned())
103}