Skip to main content

coil_runtime/storage/
host.rs

1use coil_assets::{
2    ActiveAssetManifest, AssetDeliveryPlan, ContentFingerprint, DeliveryContext, DeploymentRelease,
3    ManagedAsset, ManagedAssetRevision, RevisionId, ThemeAssetPublicationPlan,
4    ThemeAssetPublicationReceipt,
5};
6use coil_auth::{AuthModelPackage, CoilAuth, DefaultSubject};
7use coil_storage::{
8    SingleNodeEscapeHatchPlanner, StoragePlan, StoragePlanRequest, StoragePlanner,
9    StoragePolicyOverride,
10    execution::{
11        ObjectStoreClientConfig, StorageDeliveryLocation, StorageExecutor, StorageReadReceipt,
12        StorageWriteReceipt,
13    },
14};
15use zanzibar::RebacEngine;
16
17use super::{ManagedAssetPublicationGate, RuntimeStorageError};
18
19#[derive(Debug, Clone)]
20pub struct StorageHost {
21    pub customer_app: String,
22    pub planner: StoragePlanner,
23    executor: StorageExecutor,
24    single_node_escape_hatch: SingleNodeEscapeHatchPlanner,
25    cdn_base_url: Option<String>,
26}
27
28impl StorageHost {
29    pub(crate) fn new(
30        customer_app: String,
31        planner: StoragePlanner,
32        cdn_base_url: Option<String>,
33        object_store: Option<ObjectStoreClientConfig>,
34    ) -> Self {
35        let executor =
36            StorageExecutor::from_topology_and_object_store(planner.topology(), object_store);
37        Self {
38            customer_app,
39            single_node_escape_hatch: planner.single_node_escape_hatch(),
40            planner,
41            executor,
42            cdn_base_url,
43        }
44    }
45
46    pub fn plan_write(
47        &self,
48        request: StoragePlanRequest,
49    ) -> Result<StoragePlan, RuntimeStorageError> {
50        Ok(self.planner.plan_scalable_write(request)?)
51    }
52
53    pub fn plan_single_node_escape_hatch_write(
54        &self,
55        request: StoragePlanRequest,
56    ) -> Result<StoragePlan, RuntimeStorageError> {
57        Ok(self.single_node_escape_hatch.plan_write(request)?)
58    }
59
60    pub fn execute_write(
61        &self,
62        plan: &StoragePlan,
63        bytes: impl AsRef<[u8]>,
64    ) -> Result<StorageWriteReceipt, RuntimeStorageError> {
65        Ok(self.executor.execute_write(plan, bytes)?)
66    }
67
68    pub fn execute_write_with_content_type(
69        &self,
70        plan: &StoragePlan,
71        bytes: impl AsRef<[u8]>,
72        content_type: Option<&str>,
73    ) -> Result<StorageWriteReceipt, RuntimeStorageError> {
74        Ok(self
75            .executor
76            .execute_write_with_content_type(plan, bytes, content_type)?)
77    }
78
79    pub fn execute_read(
80        &self,
81        plan: &StoragePlan,
82    ) -> Result<StorageReadReceipt, RuntimeStorageError> {
83        Ok(self.executor.execute_read(plan)?)
84    }
85
86    pub fn delivery_location(
87        &self,
88        plan: &StoragePlan,
89    ) -> Result<StorageDeliveryLocation, RuntimeStorageError> {
90        Ok(self
91            .executor
92            .delivery_location(plan, self.cdn_base_url.as_deref())?)
93    }
94
95    pub fn publish_deployment_release(
96        &self,
97        release: &DeploymentRelease,
98    ) -> Result<ActiveAssetManifest, RuntimeStorageError> {
99        let cdn_base_url = self
100            .cdn_base_url
101            .as_deref()
102            .ok_or(RuntimeStorageError::MissingCdnBaseUrl)?;
103        Ok(release.publish(&self.planner, cdn_base_url)?)
104    }
105
106    pub fn publish_theme_assets(
107        &self,
108        publication: &ThemeAssetPublicationPlan,
109    ) -> Result<ThemeAssetPublicationReceipt, RuntimeStorageError> {
110        let cdn_base_url = self
111            .cdn_base_url
112            .as_deref()
113            .ok_or(RuntimeStorageError::MissingCdnBaseUrl)?;
114        Ok(publication.publish_and_sync(&self.planner, cdn_base_url, &self.executor)?)
115    }
116
117    pub fn plan_managed_revision(
118        &self,
119        revision_id: RevisionId,
120        logical_path: impl Into<String>,
121        override_policy: Option<StoragePolicyOverride>,
122        content_type: impl Into<String>,
123        byte_length: u64,
124        fingerprint: ContentFingerprint,
125    ) -> Result<ManagedAssetRevision, RuntimeStorageError> {
126        Ok(ManagedAssetRevision::plan(
127            revision_id,
128            &self.planner,
129            logical_path,
130            override_policy,
131            content_type,
132            byte_length,
133            fingerprint,
134        )?)
135    }
136
137    pub fn plan_managed_revision_with_single_node_escape_hatch(
138        &self,
139        revision_id: RevisionId,
140        logical_path: impl Into<String>,
141        override_policy: Option<StoragePolicyOverride>,
142        content_type: impl Into<String>,
143        byte_length: u64,
144        fingerprint: ContentFingerprint,
145    ) -> Result<ManagedAssetRevision, RuntimeStorageError> {
146        Ok(ManagedAssetRevision::plan_with_single_node_escape_hatch(
147            revision_id,
148            &self.single_node_escape_hatch,
149            logical_path,
150            override_policy,
151            content_type,
152            byte_length,
153            fingerprint,
154        )?)
155    }
156
157    pub fn plan_public_asset_delivery(
158        &self,
159        asset: &ManagedAsset,
160    ) -> Result<AssetDeliveryPlan, RuntimeStorageError> {
161        let cdn_base_url = self
162            .cdn_base_url
163            .as_deref()
164            .ok_or(RuntimeStorageError::MissingCdnBaseUrl)?;
165        let context = DeliveryContext::default().with_cdn_base_url(cdn_base_url);
166        Ok(asset.plan_public_delivery(&context)?)
167    }
168
169    pub fn plan_authorized_asset_delivery(
170        &self,
171        asset: &ManagedAsset,
172    ) -> Result<AssetDeliveryPlan, RuntimeStorageError> {
173        Ok(asset.plan_authorized_delivery(&DeliveryContext::default())?)
174    }
175
176    pub async fn managed_asset_publication_gate<E>(
177        &self,
178        auth: &CoilAuth<E>,
179        package: &impl AuthModelPackage,
180        subject: &DefaultSubject,
181        asset: &ManagedAsset,
182    ) -> Result<ManagedAssetPublicationGate, RuntimeStorageError>
183    where
184        E: RebacEngine,
185    {
186        ManagedAssetPublicationGate::resolve(auth, package, subject, asset).await
187    }
188}