1use std::fmt;
2
3use coil_config::StorageClass;
4
5use super::StoragePolicyError;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum StorageBackendKind {
9 LocalDisk,
10 S3Compatible,
11}
12
13impl fmt::Display for StorageBackendKind {
14 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15 match self {
16 Self::LocalDisk => f.write_str("local_disk"),
17 Self::S3Compatible => f.write_str("s3_compatible"),
18 }
19 }
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum PathPolicyKind {
24 Folder,
25 Upload,
26}
27
28impl fmt::Display for PathPolicyKind {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::Folder => f.write_str("folder"),
32 Self::Upload => f.write_str("upload"),
33 }
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum DurableStore {
39 LocalDisk,
40 ObjectStore,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44pub enum DeliveryMode {
45 PublicCdn,
46 SignedUrl,
47 AppProxy,
48 LocalOnly,
49}
50
51impl fmt::Display for DeliveryMode {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 Self::PublicCdn => f.write_str("public_cdn"),
55 Self::SignedUrl => f.write_str("signed_url"),
56 Self::AppProxy => f.write_str("app_proxy"),
57 Self::LocalOnly => f.write_str("local_only"),
58 }
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
63pub enum SyncMode {
64 ObjectStore,
65 LocalOnly,
66}
67
68impl fmt::Display for SyncMode {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 Self::ObjectStore => f.write_str("object_store"),
72 Self::LocalOnly => f.write_str("local_only"),
73 }
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
78pub enum Sensitivity {
79 Public,
80 Internal,
81 Restricted,
82 Secret,
83}
84
85impl fmt::Display for Sensitivity {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 match self {
88 Self::Public => f.write_str("public"),
89 Self::Internal => f.write_str("internal"),
90 Self::Restricted => f.write_str("restricted"),
91 Self::Secret => f.write_str("secret"),
92 }
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97pub struct StoragePolicy {
98 pub delivery_mode: DeliveryMode,
99 pub sync_mode: SyncMode,
100 pub sensitivity: Sensitivity,
101}
102
103impl StoragePolicy {
104 pub const fn new(
105 delivery_mode: DeliveryMode,
106 sync_mode: SyncMode,
107 sensitivity: Sensitivity,
108 ) -> Self {
109 Self {
110 delivery_mode,
111 sync_mode,
112 sensitivity,
113 }
114 }
115
116 pub const fn public_asset() -> Self {
117 Self::new(
118 DeliveryMode::PublicCdn,
119 SyncMode::ObjectStore,
120 Sensitivity::Public,
121 )
122 }
123
124 pub const fn public_upload() -> Self {
125 Self::new(
126 DeliveryMode::PublicCdn,
127 SyncMode::ObjectStore,
128 Sensitivity::Public,
129 )
130 }
131
132 pub const fn private_shared() -> Self {
133 Self::new(
134 DeliveryMode::SignedUrl,
135 SyncMode::ObjectStore,
136 Sensitivity::Restricted,
137 )
138 }
139
140 pub const fn single_node_sensitive() -> Self {
141 Self::new(
142 DeliveryMode::LocalOnly,
143 SyncMode::LocalOnly,
144 Sensitivity::Secret,
145 )
146 }
147
148 pub fn validate(&self) -> Result<(), StoragePolicyError> {
149 match (self.delivery_mode, self.sync_mode, self.sensitivity) {
150 (DeliveryMode::PublicCdn, SyncMode::LocalOnly, _) => {
151 Err(StoragePolicyError::InvalidCombination {
152 detail: "public_cdn delivery requires object_store sync".to_string(),
153 })
154 }
155 (DeliveryMode::SignedUrl, SyncMode::LocalOnly, _) => {
156 Err(StoragePolicyError::InvalidCombination {
157 detail: "signed_url delivery requires object_store sync".to_string(),
158 })
159 }
160 (
161 DeliveryMode::PublicCdn,
162 _,
163 Sensitivity::Internal | Sensitivity::Restricted | Sensitivity::Secret,
164 ) => Err(StoragePolicyError::InvalidCombination {
165 detail: "public_cdn delivery is only valid for public content".to_string(),
166 }),
167 (DeliveryMode::LocalOnly, SyncMode::ObjectStore, _) => {
168 Err(StoragePolicyError::InvalidCombination {
169 detail: "local_only delivery cannot use object_store sync".to_string(),
170 })
171 }
172 (DeliveryMode::SignedUrl, _, Sensitivity::Public) => {
173 Err(StoragePolicyError::InvalidCombination {
174 detail: "signed_url delivery is for non-public content".to_string(),
175 })
176 }
177 (_, _, _) => Ok(()),
178 }
179 }
180
181 pub const fn durable_store(&self) -> DurableStore {
182 match self.sync_mode {
183 SyncMode::ObjectStore => DurableStore::ObjectStore,
184 SyncMode::LocalOnly => DurableStore::LocalDisk,
185 }
186 }
187
188 pub const fn is_public_delivery_eligible(&self) -> bool {
189 matches!(
190 (self.delivery_mode, self.sensitivity),
191 (DeliveryMode::PublicCdn, Sensitivity::Public)
192 )
193 }
194}
195
196impl From<StorageClass> for StoragePolicy {
197 fn from(value: StorageClass) -> Self {
198 match value {
199 StorageClass::PublicAsset => Self::public_asset(),
200 StorageClass::PublicUpload => Self::public_upload(),
201 StorageClass::PrivateShared => Self::private_shared(),
202 StorageClass::LocalOnlySensitive => Self::single_node_sensitive(),
203 }
204 }
205}
206
207#[derive(Debug, Clone, Default, PartialEq, Eq)]
208pub struct StoragePolicyOverride {
209 pub delivery_mode: Option<DeliveryMode>,
210 pub sync_mode: Option<SyncMode>,
211 pub sensitivity: Option<Sensitivity>,
212}
213
214impl StoragePolicyOverride {
215 pub fn apply_to(&self, base: StoragePolicy) -> StoragePolicy {
216 StoragePolicy {
217 delivery_mode: self.delivery_mode.unwrap_or(base.delivery_mode),
218 sync_mode: self.sync_mode.unwrap_or(base.sync_mode),
219 sensitivity: self.sensitivity.unwrap_or(base.sensitivity),
220 }
221 }
222
223 pub const fn is_local_only_escape_hatch(&self) -> bool {
224 matches!(
225 (self.delivery_mode, self.sync_mode, self.sensitivity,),
226 (
227 Some(DeliveryMode::LocalOnly),
228 Some(SyncMode::LocalOnly),
229 Some(Sensitivity::Secret),
230 )
231 )
232 }
233
234 pub fn force_single_node_escape_hatch() -> Self {
235 Self {
236 delivery_mode: Some(DeliveryMode::LocalOnly),
237 sync_mode: Some(SyncMode::LocalOnly),
238 sensitivity: Some(Sensitivity::Secret),
239 }
240 }
241}