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}