Skip to main content

chio_tower/
layer.rs

1//! Chio tower Layer implementation.
2
3use chio_core_types::crypto::Keypair;
4use tower_layer::Layer;
5
6use crate::error::ChioTowerError;
7use crate::evaluator::{ChioEvaluator, ChioEvaluatorBuilder};
8use crate::service::{ChioService, DEFAULT_MAX_BODY_BYTES};
9
10/// Tower `Layer` that wraps inner services with Chio evaluation.
11///
12/// # Example
13///
14/// ```rust,no_run
15/// use chio_tower::ChioLayer;
16/// use chio_core_types::crypto::Keypair;
17///
18/// let keypair = Keypair::generate();
19/// let layer = ChioLayer::new(keypair, "policy-hash".to_string());
20/// ```
21#[derive(Clone)]
22pub struct ChioLayer {
23    evaluator: ChioEvaluator,
24    max_body_bytes: usize,
25}
26
27impl ChioLayer {
28    /// Create a fail-closed Chio layer with the given kernel keypair and policy
29    /// hash. No durable store is attached; use [`ChioLayer::builder`] to wire
30    /// one, or [`ChioLayer::new_ephemeral`] for an in-memory scaffold.
31    pub fn new(keypair: Keypair, policy_hash: String) -> Self {
32        Self {
33            evaluator: ChioEvaluator::new(keypair, policy_hash),
34            max_body_bytes: DEFAULT_MAX_BODY_BYTES,
35        }
36    }
37
38    /// Create an explicitly ephemeral Chio layer for local scaffolds and tests.
39    pub fn new_ephemeral(keypair: Keypair, policy_hash: String) -> Self {
40        Self {
41            evaluator: ChioEvaluator::new_ephemeral(keypair, policy_hash),
42            max_body_bytes: DEFAULT_MAX_BODY_BYTES,
43        }
44    }
45
46    /// Start building a layer backed by durable receipt and revocation stores.
47    #[must_use]
48    pub fn builder(keypair: Keypair, policy_hash: String) -> ChioLayerBuilder {
49        ChioLayerBuilder {
50            inner: ChioEvaluator::builder(keypair, policy_hash),
51            max_body_bytes: DEFAULT_MAX_BODY_BYTES,
52        }
53    }
54
55    /// Create a layer from an existing evaluator.
56    pub fn from_evaluator(evaluator: ChioEvaluator) -> Self {
57        Self {
58            evaluator,
59            max_body_bytes: DEFAULT_MAX_BODY_BYTES,
60        }
61    }
62
63    /// Override the maximum body size buffered for hashing in each request.
64    ///
65    /// See [`ChioService::with_max_body_bytes`] for details.
66    #[must_use]
67    pub fn with_max_body_bytes(mut self, max_body_bytes: usize) -> Self {
68        self.max_body_bytes = max_body_bytes;
69        self
70    }
71}
72
73impl<S> Layer<S> for ChioLayer {
74    type Service = ChioService<S>;
75
76    fn layer(&self, inner: S) -> Self::Service {
77        ChioService::new(inner, self.evaluator.clone()).with_max_body_bytes(self.max_body_bytes)
78    }
79}
80
81/// Builder for a [`ChioLayer`] backed by durable stores. Mirrors
82/// [`ChioEvaluatorBuilder`] and adds the layer's body-size cap.
83pub struct ChioLayerBuilder {
84    inner: ChioEvaluatorBuilder,
85    max_body_bytes: usize,
86}
87
88impl ChioLayerBuilder {
89    #[must_use]
90    pub fn receipt_store(mut self, store: std::sync::Arc<dyn chio_kernel::ReceiptStore>) -> Self {
91        self.inner = self.inner.receipt_store(store);
92        self
93    }
94
95    #[must_use]
96    pub fn revocation_store(
97        mut self,
98        store: std::sync::Arc<dyn chio_kernel::RevocationStore>,
99    ) -> Self {
100        self.inner = self.inner.revocation_store(store);
101        self
102    }
103
104    #[must_use]
105    pub fn allow_ephemeral(mut self, allow: bool) -> Self {
106        self.inner = self.inner.allow_ephemeral(allow);
107        self
108    }
109
110    #[must_use]
111    pub fn with_max_body_bytes(mut self, max_body_bytes: usize) -> Self {
112        self.max_body_bytes = max_body_bytes;
113        self
114    }
115
116    pub fn build(self) -> Result<ChioLayer, ChioTowerError> {
117        Ok(ChioLayer {
118            evaluator: self.inner.build()?,
119            max_body_bytes: self.max_body_bytes,
120        })
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use bytes::Bytes;
128    use http_body_util::Full;
129
130    #[test]
131    fn layer_creates_service() {
132        let keypair = Keypair::generate();
133        let layer = ChioLayer::new_ephemeral(keypair, "test-policy".to_string());
134
135        // Verify that layer can wrap a simple closure.
136        let _service = layer.layer(tower::service_fn(
137            |_req: http::Request<Full<Bytes>>| async {
138                Ok::<_, std::convert::Infallible>(http::Response::new(Full::new(Bytes::new())))
139            },
140        ));
141    }
142}