1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![warn(clippy::all, clippy::pedantic)]
4#![allow(clippy::missing_errors_doc)]
5
6use anyhow::bail;
11use hashbrown::HashSet;
12use ordinary_types::{Field, Kind};
13use serde::{Deserialize, Serialize};
14use std::fmt::Write;
15
16fn default_public_dns_ip() -> [u8; 4] {
17 [8, 8, 8, 8]
19}
20
21fn default_env_name() -> String {
22 "development".to_string()
23}
24
25#[derive(Deserialize, Serialize, Clone)]
26pub struct OrdinaryApiConfig {
27 pub domain: String,
28 pub contacts: Vec<String>,
29 #[serde(default = "default_public_dns_ip")]
30 pub public_dns_ip: [u8; 4],
31 #[serde(default = "default_env_name")]
32 pub env_name: String,
33 #[serde(default)]
34 pub limits: OrdinaryApiLimits,
35}
36
37#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
38#[derive(Deserialize, Serialize, Debug, Clone)]
39pub struct AssetsLimits {
40 pub allowed_extensions: Vec<String>,
72 pub max_store_size: u64,
75 pub max_asset_size: u64,
77}
78
79impl Default for AssetsLimits {
80 fn default() -> Self {
81 Self {
82 allowed_extensions: vec![
83 "txt".into(),
84 "xml".into(),
85 "html".into(),
86 "css".into(),
87 "csv".into(),
88 "js".into(),
89 "png".into(),
90 "apng".into(),
91 "gif".into(),
92 "svg".into(),
93 "jpg".into(),
94 "jpeg".into(),
95 "bmp".into(),
96 "tif".into(),
97 "tiff".into(),
98 "webp".into(),
99 "avif".into(),
100 "ico".into(),
101 "pdf".into(),
102 "json".into(),
103 "wasm".into(),
104 ],
105 max_store_size: 100_000_000,
106 max_asset_size: 1_500_000,
107 }
108 }
109}
110
111#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
112#[derive(Deserialize, Serialize, Debug, Clone)]
113pub struct ArtifactLimits {
114 pub max_store_size: u64,
115 pub max_artifact_size: u64,
116}
117
118impl Default for ArtifactLimits {
119 fn default() -> Self {
120 Self {
121 max_store_size: 10_000_000,
122 max_artifact_size: 500_000,
123 }
124 }
125}
126
127#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
128#[derive(Deserialize, Serialize, Debug, Clone)]
129pub struct CacheLimits {
130 pub max_size_range: (u64, u64),
131 pub max_count_range: (usize, usize),
132
133 pub sync_interval_ranges: ((u64, u64), (u64, u64)),
134 pub clean_interval_ranges: ((u64, u64), (u64, u64)),
135}
136
137impl Default for CacheLimits {
138 fn default() -> Self {
139 Self {
140 max_size_range: (1_000_000, 10_000_000),
141 max_count_range: (100, 500),
142
143 sync_interval_ranges: ((2, 14), (18, 30)),
144 clean_interval_ranges: ((5, 10), (15, 20)),
145 }
146 }
147}
148
149#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
150#[derive(Deserialize, Serialize, Debug, Clone)]
151pub struct ContentLimits {
152 pub search_enabled: bool,
153
154 pub max_content_definitions: u8,
155 pub max_content_fields: u8,
156
157 pub max_store_size: u64,
158 pub max_object_size: u64,
159 pub max_field_size: u64,
160}
161
162impl Default for ContentLimits {
163 fn default() -> Self {
164 Self {
165 search_enabled: true,
166
167 max_content_definitions: 255,
168 max_content_fields: 255,
169
170 max_store_size: 10_000_000,
171 max_object_size: 400_000,
172 max_field_size: 200_000,
173 }
174 }
175}
176
177#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
178#[derive(Deserialize, Serialize, Debug, Clone)]
179pub struct ModelLimits {
180 pub search_enabled: bool,
181
182 pub max_model_definitions: u8,
183 pub max_model_fields: u8,
184
185 pub max_item_size: u64,
186 pub max_field_size: u64,
187}
188
189impl Default for ModelLimits {
190 fn default() -> Self {
191 Self {
192 search_enabled: true,
193
194 max_model_definitions: 255,
195 max_model_fields: 255,
196
197 max_item_size: 400_000,
198 max_field_size: 100_000,
199 }
200 }
201}
202
203#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
204#[derive(Deserialize, Serialize, Debug, Clone)]
205pub struct SecretsLimits {
206 pub max_count: u8,
207 pub max_size: u64,
208}
209
210impl Default for SecretsLimits {
211 fn default() -> Self {
212 Self {
213 max_count: 225,
214 max_size: 2_000,
215 }
216 }
217}
218
219#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
220#[derive(Deserialize, Serialize, Debug, Clone)]
221pub struct StorageLimits {
222 pub max_storage: u64,
228 pub max_app_storage: u64,
234
235 pub assets: AssetsLimits,
236 pub artifact: ArtifactLimits,
237 pub cache: CacheLimits,
238 pub content: ContentLimits,
239 pub model: ModelLimits,
240 pub secrets: SecretsLimits,
241}
242
243impl Default for StorageLimits {
244 fn default() -> Self {
245 Self {
246 max_storage: 20_000_000_000,
247 max_app_storage: 50_000_000,
248
249 assets: AssetsLimits::default(),
250 artifact: ArtifactLimits::default(),
251 cache: CacheLimits::default(),
252 content: ContentLimits::default(),
253 model: ModelLimits::default(),
254 secrets: SecretsLimits::default(),
255 }
256 }
257}
258
259#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
260#[derive(Deserialize, Serialize, Debug, Clone, Default)]
261pub struct MonitorLimits {}
262
263#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
264#[derive(Deserialize, Serialize, Debug, Clone)]
265pub struct IntegrationLimits {
266 pub count: u8,
268
269 pub max_timeout: u16,
273}
274
275impl Default for IntegrationLimits {
276 fn default() -> Self {
277 Self {
278 count: 255,
279 max_timeout: 10,
280 }
281 }
282}
283
284#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
285#[derive(Deserialize, Serialize, Debug, Clone)]
286pub struct ActionLimits {
287 pub count: u8,
289
290 pub max_timeout: u16,
294}
295
296impl Default for ActionLimits {
297 fn default() -> Self {
298 Self {
299 count: 255,
300 max_timeout: 10,
301 }
302 }
303}
304
305#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
306#[derive(Deserialize, Serialize, Debug, Clone, Default)]
307pub struct AuthLimits {}
308
309#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
310#[derive(Deserialize, Serialize, Debug, Clone)]
311pub struct TemplateLimits {
312 pub count: u8,
314
315 pub max_timeout: u16,
319}
320
321impl Default for TemplateLimits {
322 fn default() -> Self {
323 Self {
324 count: 255,
325 max_timeout: 10,
326 }
327 }
328}
329
330#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
331#[derive(Deserialize, Serialize, Debug, Clone)]
332pub struct OrdinaryApiLimits {
333 pub app_domains: Vec<String>,
334 pub privileged_domains: Vec<String>,
335
336 pub max_default_timeout: u16,
340
341 pub action: ActionLimits,
342 pub auth: AuthLimits,
343 pub integration: IntegrationLimits,
344 pub monitor: MonitorLimits,
345 pub storage: StorageLimits,
346 pub template: TemplateLimits,
347}
348
349impl Default for OrdinaryApiLimits {
350 fn default() -> Self {
351 Self {
352 app_domains: vec![],
353 privileged_domains: vec![],
354
355 max_default_timeout: 10,
356
357 action: ActionLimits::default(),
358 auth: AuthLimits::default(),
359 integration: IntegrationLimits::default(),
360 monitor: MonitorLimits::default(),
361 storage: StorageLimits::default(),
362 template: TemplateLimits::default(),
363 }
364 }
365}
366
367#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
368#[derive(Deserialize, Serialize, Debug, Clone)]
369pub struct ClientLoggingConfig {
370 #[serde(skip_serializing_if = "Option::is_none")]
372 #[serde(default)]
373 min_delay: Option<u32>,
374 #[serde(skip_serializing_if = "Option::is_none")]
376 #[serde(default)]
377 max_delay: Option<u32>,
378 #[serde(skip_serializing_if = "Option::is_none")]
381 #[serde(default)]
382 max_buffer: Option<u16>,
383 #[serde(skip_serializing_if = "Option::is_none")]
385 #[serde(default)]
386 max_batch: Option<u16>,
387}
388
389#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
390#[derive(Deserialize, Serialize, Debug, Clone)]
391pub enum RedactedHashAlg {
392 Blake2,
393 Blake3,
394}
395
396#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
397#[derive(Deserialize, Serialize, Debug, Clone)]
398pub struct ServerLoggingConfig {
399 #[serde(skip_serializing_if = "Option::is_none")]
400 #[serde(default)]
401 pub ips: Option<bool>,
402 #[serde(skip_serializing_if = "Option::is_none")]
403 #[serde(default)]
404 pub headers: Option<bool>,
405 #[serde(skip_serializing_if = "Option::is_none")]
406 #[serde(default)]
407 pub credentials: Option<RedactedHashAlg>,
408 #[serde(skip_serializing_if = "Option::is_none")]
409 #[serde(default)]
410 pub timing: Option<bool>,
411 #[serde(skip_serializing_if = "Option::is_none")]
412 #[serde(default)]
413 pub sizes: Option<bool>,
414}
415
416#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
417#[derive(Deserialize, Serialize, Debug, Clone)]
418pub struct LoggingConfig {
419 #[serde(skip_serializing_if = "Option::is_none")]
420 #[serde(default)]
421 pub client: Option<ClientLoggingConfig>,
422 #[serde(skip_serializing_if = "Option::is_none")]
423 #[serde(default)]
424 pub server: Option<ServerLoggingConfig>,
425}
426
427#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
428#[derive(Deserialize, Serialize, Debug, Clone)]
429pub struct Check {
430 }
433
434#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
435#[derive(Deserialize, Serialize, Debug, Clone)]
436pub enum SignedTokenAlgorithm {
437 DsaEd25519,
438}
439
440#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
441#[derive(Deserialize, Serialize, Debug, Clone)]
442pub enum HmacTokenAlgorithm {
443 HmacBlake2b256,
444}
445
446#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
448#[derive(Deserialize, Serialize, Debug, Clone)]
449pub struct SignedTokenConfig {
450 pub algorithm: SignedTokenAlgorithm,
452 pub rotation: u32,
454}
455
456impl Default for SignedTokenConfig {
457 fn default() -> SignedTokenConfig {
458 SignedTokenConfig {
459 algorithm: SignedTokenAlgorithm::DsaEd25519,
460 rotation: 60 * 60 * 24 * 30,
461 }
462 }
463}
464
465#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
467#[derive(Deserialize, Serialize, Debug, Clone)]
468pub struct RefreshTokenConfig {
469 pub lifetime: u32,
471}
472
473impl Default for RefreshTokenConfig {
474 fn default() -> RefreshTokenConfig {
475 RefreshTokenConfig {
476 lifetime: 60 * 60 * 24 * 7,
477 }
478 }
479}
480
481#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
483#[derive(Deserialize, Serialize, Debug, Clone)]
484pub struct HmacTokenConfig {
485 pub algorithm: HmacTokenAlgorithm,
487 pub rotation: u32,
489}
490
491impl Default for HmacTokenConfig {
492 fn default() -> HmacTokenConfig {
493 HmacTokenConfig {
494 algorithm: HmacTokenAlgorithm::HmacBlake2b256,
495 rotation: 60 * 60 * 24 * 30,
496 }
497 }
498}
499
500#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
502#[derive(Deserialize, Serialize, Debug, Clone)]
503pub struct AccessTokenConfig {
504 pub lifetime: u32,
506 pub claims: Vec<Field>,
510}
511
512impl Default for AccessTokenConfig {
513 fn default() -> AccessTokenConfig {
514 AccessTokenConfig {
515 lifetime: 60 * 60 * 24,
516 claims: vec![],
517 }
518 }
519}
520
521#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
528#[derive(Deserialize, Serialize, Debug, Clone)]
529pub enum ClientPasswordHash {
530 Sha256,
535}
536
537#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
539#[derive(Deserialize, Serialize, Debug, Clone)]
540pub enum PasswordProtocol {
541 Opaque,
551 }
553
554#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
556#[derive(Deserialize, Serialize, Debug, Clone)]
557pub struct PasswordConfig {
558 pub protocol: PasswordProtocol,
559}
560
561impl Default for PasswordConfig {
562 fn default() -> PasswordConfig {
563 PasswordConfig {
564 protocol: PasswordProtocol::Opaque,
565 }
566 }
567}
568
569#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
570#[derive(Deserialize, Serialize, Debug, Clone)]
571pub enum TotpAlgorithm {
572 Sha1,
576}
577
578#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
579#[derive(Deserialize, Serialize, Debug, Clone)]
580pub struct TotpConfig {
581 #[serde(skip_serializing_if = "Option::is_none")]
584 #[serde(default)]
585 pub template: Option<String>,
586 pub algorithm: TotpAlgorithm,
587}
588
589impl Default for TotpConfig {
590 fn default() -> TotpConfig {
591 TotpConfig {
592 template: None,
593 algorithm: TotpAlgorithm::Sha1,
594 }
595 }
596}
597
598#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
599#[derive(Serialize, Deserialize, Debug, Clone, Default)]
600pub struct MfaConfig {
601 pub totp: TotpConfig,
602}
603
604#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
605#[derive(Deserialize, Serialize, Debug, Clone)]
606pub enum InviteMode {
607 Root,
609 Admin,
611 Viral,
613 }
619
620#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
621#[derive(Deserialize, Serialize, Debug, Clone)]
622pub struct InviteConfig {
623 pub mode: InviteMode,
624 pub lifetime: u32,
628 pub clean_interval: (u32, u32),
632 #[serde(skip_serializing_if = "Option::is_none")]
645 #[serde(default)]
646 pub claims: Option<Vec<Field>>,
647}
648
649impl Default for InviteConfig {
650 fn default() -> InviteConfig {
651 InviteConfig {
652 mode: InviteMode::Viral,
653 lifetime: 60 * 60 * 24 * 7,
654 clean_interval: (30, 90),
655 claims: None,
656 }
657 }
658}
659
660#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
662#[derive(Deserialize, Serialize, Debug, Clone)]
663pub struct AuthConfig {
664 pub password: PasswordConfig,
666 pub mfa: MfaConfig,
668 pub hmac_token: HmacTokenConfig,
670 pub signed_token: SignedTokenConfig,
672 pub refresh_token: RefreshTokenConfig,
674 pub access_token: AccessTokenConfig,
676 pub cookies_enabled: bool,
687 pub client_hash: ClientPasswordHash,
689
690 #[serde(skip_serializing_if = "Option::is_none")]
692 #[serde(default)]
693 pub invite: Option<InviteConfig>,
694}
695
696impl Default for AuthConfig {
697 fn default() -> AuthConfig {
698 AuthConfig {
699 password: PasswordConfig::default(),
700 mfa: MfaConfig::default(),
701 hmac_token: HmacTokenConfig::default(),
702 signed_token: SignedTokenConfig::default(),
703 refresh_token: RefreshTokenConfig::default(),
704 access_token: AccessTokenConfig::default(),
705 cookies_enabled: false,
706 client_hash: ClientPasswordHash::Sha256,
707 invite: None,
708 }
709 }
710}
711
712#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
714#[derive(Deserialize, Serialize, Debug, Clone)]
715pub enum CompressionAlgorithm {
716 Gzip,
717 Zstd { level: u8 },
718 Brotli,
719 Deflate,
720}
721
722impl CompressionAlgorithm {
723 #[must_use]
724 pub fn as_u8(&self) -> u8 {
725 match self {
726 Self::Gzip => 1,
727 Self::Zstd { level: _ } => 2,
728 Self::Brotli => 3,
729 Self::Deflate => 4,
730 }
731 }
732
733 #[must_use]
734 pub fn as_char(&self) -> char {
735 match self {
736 Self::Gzip => '1',
737 Self::Zstd { level: _ } => '2',
738 Self::Brotli => '3',
739 Self::Deflate => '4',
740 }
741 }
742
743 #[must_use]
744 pub fn as_str(&self) -> &'static str {
745 match self {
746 Self::Gzip => "gzip",
747 Self::Zstd { level: _ } => "zstd",
748 Self::Brotli => "br",
749 Self::Deflate => "deflate",
750 }
751 }
752}
753
754#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
755#[derive(Deserialize, Serialize, Debug, Clone)]
756pub enum StoredCachePolicy {
757 Permanent,
761 FRs(u64, u64),
765}
766
767#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
772#[derive(Deserialize, Serialize, Debug, Clone)]
773pub struct StoredCache {
774 pub policy: StoredCachePolicy,
775
776 #[serde(skip_serializing_if = "Option::is_none")]
778 #[serde(default)]
779 pub compression: Option<Vec<CompressionAlgorithm>>,
780
781 #[serde(skip_serializing_if = "Option::is_none")]
785 #[serde(default)]
786 pub max_ttl: Option<u64>,
787
788 #[serde(skip_serializing_if = "Option::is_none")]
792 #[serde(default)]
793 pub hit_ttl: Option<u64>,
794
795 #[serde(skip_serializing_if = "Option::is_none")]
800 #[serde(default)]
801 pub max_size: Option<u64>,
802
803 #[serde(skip_serializing_if = "Option::is_none")]
806 #[serde(default)]
807 pub max_count: Option<usize>,
808
809 #[serde(skip_serializing_if = "Option::is_none")]
813 #[serde(default)]
814 pub frequency_window: Option<u64>,
815
816 #[serde(skip_serializing_if = "Option::is_none")]
820 #[serde(default)]
821 pub clean_interval: Option<(u64, u64)>,
822
823 #[serde(skip_serializing_if = "Option::is_none")]
827 #[serde(default)]
828 pub sync_interval: Option<(u64, u64)>,
829
830 #[serde(skip_serializing_if = "Option::is_none")]
833 #[serde(default)]
834 pub evict_on_dependency_change: Option<bool>,
835}
836
837#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
838#[derive(Deserialize, Serialize, Debug, Clone)]
839pub enum XXH3Variation {
840 Bit64,
841 Bit128,
842}
843
844#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
848#[derive(Deserialize, Serialize, Debug, Clone)]
849pub enum HttpEtagAlgorithm {
850 AHash,
851 XXH3(XXH3Variation),
852 Rustc,
853 Blake3,
854}
855
856#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
859#[derive(Deserialize, Serialize, Debug, Clone)]
860pub struct HttpEtag {
861 #[serde(skip_serializing_if = "Option::is_none")]
862 #[serde(default)]
863 pub alg: Option<HttpEtagAlgorithm>,
864}
865
866#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
869#[derive(Deserialize, Serialize, Debug, Clone)]
870pub struct HttpCacheControl {
871 #[serde(skip_serializing_if = "Option::is_none")]
872 #[serde(default)]
873 pub max_age: Option<usize>,
874 #[serde(skip_serializing_if = "Option::is_none")]
875 #[serde(default)]
876 pub s_maxage: Option<usize>,
877 #[serde(skip_serializing_if = "Option::is_none")]
878 #[serde(default)]
879 pub no_cache: Option<bool>,
880 #[serde(skip_serializing_if = "Option::is_none")]
881 #[serde(default)]
882 pub no_store: Option<bool>,
883 #[serde(skip_serializing_if = "Option::is_none")]
884 #[serde(default)]
885 pub no_transform: Option<bool>,
886 #[serde(skip_serializing_if = "Option::is_none")]
887 #[serde(default)]
888 pub must_revalidate: Option<bool>,
889 #[serde(skip_serializing_if = "Option::is_none")]
890 #[serde(default)]
891 pub proxy_revalidate: Option<bool>,
892 #[serde(skip_serializing_if = "Option::is_none")]
893 #[serde(default)]
894 pub private: Option<bool>,
895 #[serde(skip_serializing_if = "Option::is_none")]
896 #[serde(default)]
897 pub public: Option<bool>,
898 #[serde(skip_serializing_if = "Option::is_none")]
899 #[serde(default)]
900 pub immutable: Option<bool>,
901 #[serde(skip_serializing_if = "Option::is_none")]
902 #[serde(default)]
903 pub stale_while_revalidate: Option<bool>,
904 #[serde(skip_serializing_if = "Option::is_none")]
905 #[serde(default)]
906 pub stale_if_error: Option<bool>,
907}
908
909impl HttpCacheControl {
910 pub fn header_value(&self, cache_control: &mut String, default: &str) -> anyhow::Result<()> {
911 if let Some(max_age) = self.max_age {
912 write!(cache_control, "max-age={max_age}, ")?;
913 }
914 if let Some(s_maxage) = self.s_maxage {
915 write!(cache_control, "s-maxage={s_maxage}, ")?;
916 }
917 if let Some(no_cache) = self.no_cache
918 && no_cache
919 {
920 write!(cache_control, "no-cache, ")?;
921 }
922 if let Some(no_store) = self.no_store
923 && no_store
924 {
925 write!(cache_control, "no-store, ")?;
926 }
927 if let Some(no_transform) = self.no_transform
928 && no_transform
929 {
930 write!(cache_control, "no-transform, ")?;
931 }
932 if let Some(must_revalidate) = self.must_revalidate
933 && must_revalidate
934 {
935 write!(cache_control, "must-revalidate, ")?;
936 }
937 if let Some(proxy_revalidate) = self.proxy_revalidate
938 && proxy_revalidate
939 {
940 write!(cache_control, "proxy-revalidate, ")?;
941 }
942 if let Some(stale_while_revalidate) = self.stale_while_revalidate
943 && stale_while_revalidate
944 {
945 write!(cache_control, "stale-while-revalidate, ")?;
946 }
947 if let Some(private) = self.private
948 && private
949 {
950 write!(cache_control, "private, ")?;
951 }
952 if let Some(public) = self.public
953 && public
954 {
955 write!(cache_control, "public, ")?;
956 }
957 if let Some(immutable) = self.immutable
958 && immutable
959 {
960 write!(cache_control, "immutable, ")?;
961 }
962 if let Some(stale_if_error) = self.stale_if_error
963 && stale_if_error
964 {
965 write!(cache_control, "stale-if-error, ")?;
966 }
967
968 if cache_control.is_empty() {
969 cache_control.push_str(default);
970 } else {
971 cache_control.pop();
972 cache_control.pop();
973 }
974
975 Ok(())
976 }
977}
978
979#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
980#[derive(Deserialize, Serialize, Debug, Clone)]
981pub struct HttpCache {
982 #[serde(skip_serializing_if = "Option::is_none")]
983 #[serde(default)]
984 pub cache_control: Option<HttpCacheControl>,
985
986 #[serde(skip_serializing_if = "Option::is_none")]
991 #[serde(default)]
992 pub expires: Option<u64>,
993
994 #[serde(skip_serializing_if = "Option::is_none")]
995 #[serde(default)]
996 pub etag: Option<HttpEtag>,
997}
998
999#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1001#[derive(Deserialize, Serialize, Debug, Clone)]
1002pub struct TemplateField {
1003 pub name: String,
1005 pub kind: Kind,
1007 pub value: String,
1011 }
1013
1014#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1016#[derive(Deserialize, Serialize, Debug, Clone)]
1017pub enum QueryExpression {
1018 Gte,
1019 Gt,
1020 Lte,
1021 Lt,
1022 Eq,
1023 BeginsWith,
1024}
1025
1026#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1028#[derive(Deserialize, Serialize, Debug, Clone)]
1029pub enum TemplateRefFieldBind {
1030 Token {
1032 field: String,
1037 #[serde(skip_serializing_if = "Option::is_none")]
1039 #[serde(default)]
1040 expression: Option<QueryExpression>,
1041 },
1042 Segment {
1044 name: String,
1046 #[serde(skip_serializing_if = "Option::is_none")]
1048 #[serde(default)]
1049 expression: Option<QueryExpression>,
1050 },
1051}
1052
1053#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1056#[derive(Deserialize, Serialize, Debug, Clone)]
1057pub struct TemplateRefField {
1058 pub idx: u8,
1061 pub name: String,
1063 #[serde(skip_serializing_if = "Option::is_none")]
1066 #[serde(default)]
1067 pub bind: Option<TemplateRefFieldBind>,
1068 #[cfg_attr(feature = "utoipa", schema(no_recursion))]
1070 #[serde(skip_serializing_if = "Option::is_none")]
1071 #[serde(default)]
1072 pub fields: Option<Vec<TemplateRefField>>,
1073}
1074
1075#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1079#[derive(Deserialize, Serialize, Debug, Clone)]
1080pub struct TemplateRef {
1081 pub idx: u8,
1085 pub name: String,
1087 pub fields: Vec<TemplateRefField>,
1089 #[serde(skip_serializing_if = "Option::is_none")]
1093 #[serde(default)]
1094 pub all: Option<String>,
1095}
1096
1097#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1100#[derive(Deserialize, Serialize, Debug, Clone)]
1101pub struct TemplateFlagRef {
1102 pub idx: u8,
1106 pub name: String,
1108}
1109
1110#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1111#[derive(Deserialize, Serialize, Debug, Clone)]
1112pub struct TemplateParamRef {
1113 pub idx: u8,
1117 pub name: String,
1119}
1120
1121#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1122#[derive(Deserialize, Serialize, Debug, Clone)]
1123pub struct TemplateCache {
1124 #[serde(skip_serializing_if = "Option::is_none")]
1125 #[serde(default)]
1126 pub stored: Option<StoredCache>,
1127
1128 #[serde(skip_serializing_if = "Option::is_none")]
1129 #[serde(default)]
1130 pub http: Option<HttpCache>,
1131}
1132
1133#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1137#[derive(Deserialize, Serialize, Debug, Clone)]
1138pub enum WasmOpt {
1139 Size,
1140 SizeAggressive,
1141 Level0,
1142 Level1,
1143 Level2,
1144 Level3,
1145 Level4,
1146}
1147
1148impl WasmOpt {
1149 #[must_use]
1150 pub fn as_flag(&self) -> &'static str {
1151 match self {
1152 Self::Size => "-Os",
1153 Self::SizeAggressive => "-Oz",
1154 Self::Level0 => "-O0",
1155 Self::Level1 => "-O1",
1156 Self::Level2 => "-O2",
1157 Self::Level3 => "-O3",
1158 Self::Level4 => "-O4",
1159 }
1160 }
1161}
1162
1163#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1164#[derive(Deserialize, Serialize, Debug, Clone)]
1165pub enum TemplateFfiVersion {
1166 V1,
1167}
1168
1169#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1171#[derive(Deserialize, Serialize, Debug, Clone)]
1172pub enum TemplateFfiSerialization {
1173 FlexBufferVector,
1174}
1175
1176#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1177#[derive(Deserialize, Serialize, Debug, Clone)]
1178pub struct TemplateFfi {
1179 pub version: TemplateFfiVersion,
1180 pub serialization: TemplateFfiSerialization,
1181}
1182
1183#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1185#[derive(Deserialize, Serialize, Debug, Clone)]
1186pub struct TemplateConfig {
1187 pub ffi: TemplateFfi,
1189
1190 pub idx: u8,
1192 pub name: String,
1194 pub mime: String,
1198 #[serde(skip_serializing_if = "Option::is_none")]
1201 #[serde(default)]
1202 pub minify: Option<bool>,
1203 #[serde(skip_serializing_if = "Option::is_none")]
1205 #[serde(default)]
1206 pub path: Option<String>,
1207 pub route: String,
1227 #[serde(skip_serializing_if = "Option::is_none")]
1229 #[serde(default)]
1230 pub protected: Option<Check>,
1231 #[serde(skip_serializing_if = "Option::is_none")]
1233 #[serde(default)]
1234 pub cache: Option<TemplateCache>,
1235
1236 #[serde(skip_serializing_if = "Option::is_none")]
1246 #[serde(default)]
1247 pub csp: Option<HttpCsp>,
1248
1249 #[serde(skip_serializing_if = "Option::is_none")]
1250 #[serde(default)]
1251 pub cors: Option<HttpCors>,
1252
1253 #[serde(skip_serializing_if = "Option::is_none")]
1257 #[serde(default)]
1258 pub timeout: Option<u16>,
1259
1260 #[serde(skip_serializing_if = "Option::is_none")]
1264 #[serde(default)]
1265 pub fields: Option<Vec<TemplateField>>,
1266 #[serde(skip_serializing_if = "Option::is_none")]
1270 #[serde(default)]
1271 pub globals: Option<Vec<String>>,
1272 #[serde(skip_serializing_if = "Option::is_none")]
1274 #[serde(default)]
1275 pub flags: Option<Vec<TemplateFlagRef>>,
1276 #[serde(skip_serializing_if = "Option::is_none")]
1278 #[serde(default)]
1279 pub params: Option<Vec<TemplateParamRef>>,
1280 #[serde(skip_serializing_if = "Option::is_none")]
1283 #[serde(default)]
1284 pub models: Option<Vec<TemplateRef>>,
1285 #[serde(skip_serializing_if = "Option::is_none")]
1288 #[serde(default)]
1289 pub content: Option<Vec<TemplateRef>>,
1290
1291 #[serde(skip_serializing_if = "Option::is_none")]
1295 #[serde(default)]
1296 pub actions: Option<Vec<String>>,
1297
1298 #[serde(skip_serializing_if = "Option::is_none")]
1299 #[serde(default)]
1300 pub wasm_opt: Option<WasmOpt>,
1301
1302 #[serde(skip_serializing_if = "Option::is_none")]
1306 #[serde(default)]
1307 pub variables: Option<Vec<String>>,
1308}
1309
1310#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1311#[derive(Deserialize, Serialize, Debug, Clone)]
1312pub struct AssetsConfig {
1313 pub dir_path: String,
1315
1316 #[serde(skip_serializing_if = "Option::is_none")]
1321 #[serde(default)]
1322 pub http: Option<HttpCache>,
1323
1324 #[serde(skip_serializing_if = "Option::is_none")]
1328 #[serde(default)]
1329 pub precompression: Option<Vec<CompressionAlgorithm>>,
1330
1331 #[serde(skip_serializing_if = "Option::is_none")]
1334 #[serde(default)]
1335 pub minify_css: Option<bool>,
1336
1337 #[serde(skip_serializing_if = "Option::is_none")]
1340 #[serde(default)]
1341 pub minify_js: Option<bool>,
1342
1343 #[serde(skip_serializing_if = "Option::is_none")]
1344 #[serde(default)]
1345 pub internal_cache_control_header_value: Option<String>,
1346}
1347
1348impl AssetsConfig {
1349 pub fn init(&mut self, default_cache_control_header_value: &str) -> anyhow::Result<()> {
1350 if let Some(http_cache) = &self.http
1351 && let Some(http_cache_control) = &http_cache.cache_control
1352 {
1353 let mut header = String::new();
1354 http_cache_control.header_value(&mut header, default_cache_control_header_value)?;
1355
1356 self.internal_cache_control_header_value = Some(header);
1357 }
1358
1359 Ok(())
1360 }
1361}
1362
1363#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1365#[derive(Deserialize, Serialize, Debug, Clone)]
1366pub struct Global {
1367 pub name: String,
1369 pub kind: Kind,
1371 pub value: String,
1376 }
1378
1379#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1381#[derive(Deserialize, Serialize, Debug, Clone)]
1382pub enum SecretSource {
1383 Env,
1396 Stored,
1404 }
1409
1410#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1412#[derive(Deserialize, Serialize, Debug, Clone)]
1413pub enum SecretVisibility {
1414 Integrations,
1426}
1427
1428#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1430#[derive(Deserialize, Serialize, Debug, Clone)]
1431pub struct Secret {
1432 pub name: String,
1434 pub source: SecretSource,
1436 pub visibility: SecretVisibility,
1438}
1439
1440#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1442#[derive(Deserialize, Serialize, Debug, Clone)]
1443pub struct FlagOption {
1444 pub idx: u8,
1446 pub name: String,
1448 pub percentage: u8,
1451}
1452
1453#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1460#[derive(Deserialize, Serialize, Debug, Clone)]
1461pub struct Flag {
1462 pub idx: u8,
1464 pub name: String,
1466 pub options: Vec<FlagOption>,
1469}
1470
1471#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1472#[derive(Deserialize, Serialize, Debug, Clone)]
1473pub struct ContentDefinition {
1474 pub idx: u8,
1476
1477 pub name: String,
1479
1480 pub fields: Vec<Field>,
1485}
1486
1487#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1493#[derive(Deserialize, Serialize, Debug, Clone)]
1494pub struct Content {
1495 pub file_path: String,
1498 pub definitions: Vec<ContentDefinition>,
1501}
1502
1503#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1504#[derive(Deserialize, Serialize, Debug, Clone)]
1505pub enum UuidVersion {
1506 V4,
1507 V7,
1508}
1509
1510#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1512#[derive(Deserialize, Serialize, Debug, Clone)]
1513pub struct ModelConfig {
1514 pub idx: u8,
1521 pub name: String,
1523 pub fields: Vec<Field>,
1525 #[serde(skip_serializing_if = "Option::is_none")]
1529 #[serde(default)]
1530 pub uuid: Option<UuidVersion>,
1531}
1532
1533#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1534#[derive(Deserialize, Serialize, Debug, Clone)]
1535pub enum IntegrationProtocolHttpEncoding {
1536 Json,
1537 Text,
1538 None,
1539}
1540
1541#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1543#[derive(Deserialize, Serialize, Debug, Clone)]
1544pub enum IntegrationProtocol {
1545 Http {
1547 method: String,
1549 headers: Vec<(String, String)>,
1551 send_encoding: IntegrationProtocolHttpEncoding,
1553 recv_encoding: IntegrationProtocolHttpEncoding,
1555 },
1556 }
1569
1570#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1573#[derive(Deserialize, Serialize, Debug, Clone)]
1574pub struct IntegrationConfig {
1575 pub idx: u8,
1577 pub name: String,
1579 pub protocol: IntegrationProtocol,
1581 pub endpoint: String,
1583 pub send: Kind,
1587 pub recv: Kind,
1591 #[serde(skip_serializing_if = "Option::is_none")]
1593 #[serde(default)]
1594 pub secrets: Option<Vec<String>>,
1595
1596 #[serde(skip_serializing_if = "Option::is_none")]
1600 #[serde(default)]
1601 pub timeout: Option<u16>,
1602}
1603
1604#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1608#[derive(Deserialize, Serialize, Debug, Clone)]
1609pub enum ActionLang {
1610 Rust,
1614 JavaScript,
1620 }
1624
1625#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1627#[derive(Deserialize, Serialize, Debug, Clone)]
1628pub enum ActionAccessModelOps {
1629 Insert,
1631 Get,
1633 Query,
1635 Search,
1637 Update,
1639 Delete,
1641}
1642
1643#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1645#[derive(Deserialize, Serialize, Debug, Clone)]
1646pub enum ActionAccessAuthOps {
1647 SetTokenFields,
1650}
1651
1652#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1655#[derive(Deserialize, Serialize, Debug, Clone)]
1656pub enum ActionAccessPermission {
1657 Model {
1659 name: String,
1661 ops: Vec<ActionAccessModelOps>,
1664 },
1665 Content {
1667 name: String,
1669 },
1670 Integration {
1672 name: String,
1674 },
1675 Action {
1677 name: String,
1679 },
1680 Auth {
1682 ops: Vec<ActionAccessAuthOps>,
1684 },
1685}
1686
1687#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1688#[derive(Deserialize, Serialize, Debug, Clone)]
1689pub enum HttpMethod {
1690 PUT,
1691 POST,
1692 GET,
1693 DELETE,
1694}
1695
1696#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1697#[derive(Deserialize, Serialize, Debug, Clone)]
1698pub enum ActionTriggerModelOps {
1699 Insert,
1700 Update,
1701 Delete,
1702}
1703
1704#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1706#[derive(Deserialize, Serialize, Debug, Clone)]
1707pub enum ActionTrigger {
1708 Ordinary,
1710 Json {
1712 route: String,
1714 method: HttpMethod,
1716 },
1717 Form {
1719 route: String,
1721 method: HttpMethod,
1723 redirect: String,
1728 },
1729 Login,
1731 Registration,
1733 Content { name: String },
1735 }
1748
1749#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1750#[derive(Deserialize, Serialize, Debug, Clone)]
1751pub enum ActionFfiVersion {
1752 V1,
1753}
1754
1755#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1757#[derive(Deserialize, Serialize, Debug, Clone)]
1758pub enum ActionFfiSerialization {
1759 FlexBufferVector,
1760}
1761
1762#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1763#[derive(Deserialize, Serialize, Debug, Clone)]
1764pub struct ActionFfi {
1765 pub version: ActionFfiVersion,
1766 pub serialization: ActionFfiSerialization,
1767}
1768
1769#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1771#[derive(Deserialize, Serialize, Debug, Clone)]
1772pub struct ActionConfig {
1773 pub ffi: ActionFfi,
1775 pub idx: u8,
1777 pub name: String,
1779 pub lang: ActionLang,
1781 pub dir_path: String,
1783 #[serde(skip_serializing_if = "Option::is_none")]
1786 #[serde(default)]
1787 pub protected: Option<Check>,
1788 #[serde(skip_serializing_if = "Option::is_none")]
1791 #[serde(default)]
1792 pub transactional: Option<bool>,
1793 pub access: Vec<ActionAccessPermission>,
1795 pub accepts: Kind,
1797 pub returns: Kind,
1799 pub triggered_by: Vec<ActionTrigger>,
1802
1803 #[serde(skip_serializing_if = "Option::is_none")]
1807 #[serde(default)]
1808 pub timeout: Option<u16>,
1809
1810 #[serde(skip_serializing_if = "Option::is_none")]
1811 #[serde(default)]
1812 pub cors: Option<HttpCors>,
1813
1814 #[serde(skip_serializing_if = "Option::is_none")]
1815 #[serde(default)]
1816 pub wasm_opt: Option<WasmOpt>,
1817
1818 #[serde(skip_serializing_if = "Option::is_none")]
1823 #[serde(default)]
1824 pub privileged: Option<bool>,
1825
1826 #[serde(skip_serializing_if = "Option::is_none")]
1830 #[serde(default)]
1831 pub variables: Option<Vec<String>>,
1832}
1833
1834#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1835#[derive(Deserialize, Serialize, Debug, Clone)]
1836pub struct ErrorConfig {
1837 pub template: String,
1839}
1840
1841#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1842#[derive(Deserialize, Serialize, Debug, Clone)]
1843pub enum RuntimeMode {
1844 Shared,
1847 SingleThreaded,
1850 MultiThreaded,
1853}
1854
1855#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1856#[derive(Deserialize, Serialize, Debug, Clone)]
1857pub enum HttpCorsAllowHeaders {
1858 Any,
1859 Headers(Vec<String>),
1861}
1862
1863#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1864#[derive(Deserialize, Serialize, Debug, Clone)]
1865pub enum HttpCorsExposeHeaders {
1866 Any,
1867 Headers(Vec<String>),
1869}
1870
1871#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1872#[derive(Deserialize, Serialize, Debug, Clone)]
1873pub enum HttpCorsAllowMethods {
1874 Any,
1875 Methods(Vec<String>),
1877}
1878
1879#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1880#[derive(Deserialize, Serialize, Debug, Clone)]
1881pub enum HttpCorsAllowOrigin {
1882 Any,
1883 Origins(Vec<String>),
1885}
1886
1887#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1888#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1889pub struct HttpCors {
1890 pub allow_credentials: Option<bool>,
1891
1892 pub allow_headers: Option<HttpCorsAllowHeaders>,
1893
1894 pub max_age: Option<u32>,
1896
1897 pub allow_methods: Option<HttpCorsAllowMethods>,
1898
1899 pub allow_origin: Option<HttpCorsAllowOrigin>,
1900
1901 pub expose_headers: Option<HttpCorsExposeHeaders>,
1902
1903 pub allow_private_network: Option<bool>,
1904}
1905
1906impl HttpCors {
1907 #[must_use]
1908 pub fn overwrite(&self, base: &Self) -> Self {
1909 Self {
1910 allow_credentials: if self.allow_credentials.is_none() {
1911 base.allow_credentials
1912 } else {
1913 self.allow_credentials
1914 },
1915 allow_headers: if self.allow_headers.is_none() {
1916 base.allow_headers.clone()
1917 } else {
1918 self.allow_headers.clone()
1919 },
1920 max_age: if self.max_age.is_none() {
1921 base.max_age
1922 } else {
1923 self.max_age
1924 },
1925 allow_methods: if self.allow_methods.is_none() {
1926 base.allow_methods.clone()
1927 } else {
1928 self.allow_methods.clone()
1929 },
1930 allow_origin: if self.allow_origin.is_none() {
1931 base.allow_origin.clone()
1932 } else {
1933 self.allow_origin.clone()
1934 },
1935 expose_headers: if self.expose_headers.is_none() {
1936 base.expose_headers.clone()
1937 } else {
1938 self.expose_headers.clone()
1939 },
1940 allow_private_network: if self.allow_private_network.is_none() {
1941 base.allow_private_network
1942 } else {
1943 self.allow_private_network
1944 },
1945 }
1946 }
1947}
1948
1949#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1950#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1951pub struct HttpCsp {
1952 #[serde(skip_serializing_if = "Option::is_none")]
1954 #[serde(default)]
1955 pub default_src: Option<String>,
1956
1957 #[serde(skip_serializing_if = "Option::is_none")]
1960 #[serde(default)]
1961 pub script_src: Option<String>,
1962
1963 #[serde(skip_serializing_if = "Option::is_none")]
1966 #[serde(default)]
1967 pub style_src: Option<String>,
1968
1969 #[serde(skip_serializing_if = "Option::is_none")]
1973 #[serde(default)]
1974 pub font_src: Option<String>,
1975
1976 #[serde(skip_serializing_if = "Option::is_none")]
1980 #[serde(default)]
1981 pub img_src: Option<String>,
1982
1983 #[serde(skip_serializing_if = "Option::is_none")]
1987 #[serde(default)]
1988 pub frame_src: Option<String>,
1989
1990 #[serde(skip_serializing_if = "Option::is_none")]
1992 #[serde(default)]
1993 pub include_inline_hashes: Option<bool>,
1994}
1995
1996impl HttpCsp {
1997 #[must_use]
1998 pub fn build_string(
1999 &self,
2000 base: &Self,
2001 inline_style_hashes: Option<Vec<String>>,
2002 inline_script_hashes: Option<Vec<String>>,
2003 secure: bool,
2004 ) -> String {
2005 let mut out = String::new();
2006
2007 let include_inline_hashes = self
2008 .include_inline_hashes
2009 .unwrap_or(base.include_inline_hashes.unwrap_or(true));
2010
2011 let default_src = self
2012 .default_src
2013 .clone()
2014 .unwrap_or(base.default_src.clone().unwrap_or("'self'".to_string()));
2015
2016 if !default_src.is_empty() {
2017 out.push_str("default-src ");
2018 out.push_str(default_src.as_str());
2019 out.push_str("; ");
2020 }
2021
2022 let mut script_src = self
2023 .script_src
2024 .clone()
2025 .unwrap_or(base.script_src.clone().unwrap_or_default());
2026
2027 if include_inline_hashes
2028 && let Some(script_hashes) = inline_script_hashes
2029 && !script_hashes.is_empty()
2030 {
2031 if script_src.is_empty() {
2032 script_src.push_str("'self'");
2033 }
2034
2035 for hash in script_hashes {
2036 script_src.push_str(" '");
2037 script_src.push_str(hash.as_str());
2038 script_src.push('\'');
2039 }
2040 }
2041
2042 if !script_src.is_empty() {
2043 out.push_str("script-src ");
2044 out.push_str(script_src.as_str());
2045 out.push_str("; ");
2046 }
2047
2048 let mut style_src = self
2049 .style_src
2050 .clone()
2051 .unwrap_or(base.style_src.clone().unwrap_or_default());
2052
2053 if include_inline_hashes
2054 && let Some(style_hashes) = inline_style_hashes
2055 && !style_hashes.is_empty()
2056 {
2057 if style_src.is_empty() {
2058 style_src.push_str("'self'");
2059 }
2060
2061 for hash in style_hashes {
2062 style_src.push_str(" '");
2063 style_src.push_str(hash.as_str());
2064 style_src.push('\'');
2065 }
2066 }
2067
2068 if !style_src.is_empty() {
2069 out.push_str("style-src ");
2070 out.push_str(style_src.as_str());
2071 out.push_str("; ");
2072 }
2073
2074 let font_src = self
2075 .font_src
2076 .clone()
2077 .unwrap_or(base.font_src.clone().unwrap_or_default());
2078 if !font_src.is_empty() {
2079 out.push_str("font-src ");
2080 out.push_str(font_src.as_str());
2081 out.push_str("; ");
2082 }
2083
2084 let img_src = self
2085 .img_src
2086 .clone()
2087 .unwrap_or(base.img_src.clone().unwrap_or_default());
2088 if !img_src.is_empty() {
2089 out.push_str("img-src ");
2090 out.push_str(img_src.as_str());
2091 out.push_str("; ");
2092 }
2093
2094 let frame_src = self
2095 .frame_src
2096 .clone()
2097 .unwrap_or(base.frame_src.clone().unwrap_or_default());
2098 if !frame_src.is_empty() {
2099 out.push_str("frame-src ");
2100 out.push_str(frame_src.as_str());
2101 out.push_str("; ");
2102 }
2103
2104 if secure {
2105 out.push_str("upgrade-insecure-requests; ");
2106 }
2107
2108 out.push_str("report-to csp");
2109
2110 out = out.trim().to_string();
2111 out
2112 }
2113}
2114
2115#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2117#[derive(Deserialize, Serialize, Debug, Clone)]
2118pub struct OrdinaryConfig {
2119 pub domain: String,
2122
2123 #[serde(skip_serializing_if = "Option::is_none")]
2126 #[serde(default)]
2127 pub cnames: Option<Vec<String>>,
2128
2129 pub contacts: Vec<String>,
2132
2133 #[serde(skip_serializing_if = "Option::is_none")]
2135 #[serde(default)]
2136 pub hide_contacts: Option<bool>,
2137
2138 pub version: String,
2140
2141 pub storage_size: u64,
2143
2144 #[serde(skip_serializing_if = "Option::is_none")]
2148 #[serde(default)]
2149 pub default_timeout: Option<u16>,
2150
2151 #[serde(skip_serializing_if = "Option::is_none")]
2161 #[serde(default)]
2162 pub csp: Option<HttpCsp>,
2163
2164 #[serde(skip_serializing_if = "Option::is_none")]
2165 #[serde(default)]
2166 pub cors: Option<HttpCors>,
2167
2168 #[serde(skip_serializing_if = "Option::is_none")]
2172 #[serde(default)]
2173 pub runtime: Option<RuntimeMode>,
2174
2175 #[serde(skip_serializing_if = "Option::is_none")]
2181 #[serde(default)]
2182 pub hide_schema: Option<bool>,
2183
2184 #[serde(skip_serializing_if = "Option::is_none")]
2186 #[serde(default)]
2187 pub client_rendering: Option<bool>,
2188
2189 #[serde(skip_serializing_if = "Option::is_none")]
2191 #[serde(default)]
2192 pub obfuscation: Option<bool>,
2193
2194 #[serde(skip_serializing_if = "Option::is_none")]
2196 #[serde(default)]
2197 pub client_events: Option<bool>,
2198
2199 #[serde(skip_serializing_if = "Option::is_none")]
2201 #[serde(default)]
2202 pub port: Option<u16>,
2203 #[serde(skip_serializing_if = "Option::is_none")]
2206 #[serde(default)]
2207 pub redirect_port: Option<u16>,
2208
2209 #[serde(skip_serializing_if = "Option::is_none")]
2210 #[serde(default)]
2211 pub logging: Option<LoggingConfig>,
2212 #[serde(skip_serializing_if = "Option::is_none")]
2217 #[serde(default)]
2218 pub error: Option<ErrorConfig>,
2219 #[serde(skip_serializing_if = "Option::is_none")]
2221 #[serde(default)]
2222 pub auth: Option<AuthConfig>,
2223 #[serde(skip_serializing_if = "Option::is_none")]
2225 #[serde(default)]
2226 pub globals: Option<Vec<Global>>,
2227 #[serde(skip_serializing_if = "Option::is_none")]
2229 #[serde(default)]
2230 pub secrets: Option<Vec<Secret>>,
2231 #[serde(skip_serializing_if = "Option::is_none")]
2234 #[serde(default)]
2235 pub flags: Option<Vec<Flag>>,
2236 #[serde(skip_serializing_if = "Option::is_none")]
2241 #[serde(default)]
2242 pub content: Option<Content>,
2243 #[serde(skip_serializing_if = "Option::is_none")]
2245 #[serde(default)]
2246 pub models: Option<Vec<ModelConfig>>,
2247 #[serde(skip_serializing_if = "Option::is_none")]
2250 #[serde(default)]
2251 pub integrations: Option<Vec<IntegrationConfig>>,
2252 #[serde(skip_serializing_if = "Option::is_none")]
2255 #[serde(default)]
2256 pub actions: Option<Vec<ActionConfig>>,
2257 #[serde(skip_serializing_if = "Option::is_none")]
2261 #[serde(default)]
2262 pub assets: Option<AssetsConfig>,
2263 #[serde(skip_serializing_if = "Option::is_none")]
2276 #[serde(default)]
2277 pub templates: Option<Vec<TemplateConfig>>,
2278}
2279
2280impl OrdinaryConfig {
2281 #[allow(clippy::too_many_lines)]
2286 pub fn check_config_against_limits(
2287 &self,
2288 limits: &OrdinaryApiLimits,
2289 privileged_domains: &HashSet<String>,
2290 ) -> anyhow::Result<()> {
2291 if privileged_domains.contains(&self.domain) {
2292 return Ok(());
2293 }
2294
2295 if let Some(default_timeout) = self.default_timeout
2296 && default_timeout > limits.max_default_timeout
2297 {
2298 bail!(
2299 "default timeout {} greater than limit {}",
2300 default_timeout,
2301 limits.max_default_timeout
2302 );
2303 }
2304
2305 if let Some(templates) = &self.templates {
2306 if templates.len() > limits.template.count as usize {
2307 bail!(
2308 "template count {} greater than limit {}",
2309 templates.len(),
2310 limits.template.count
2311 );
2312 }
2313
2314 for template in templates {
2315 if let Some(timeout) = template.timeout
2316 && timeout > limits.template.max_timeout
2317 {
2318 bail!(
2319 "template timeout {} greater than limit {} for {}",
2320 timeout,
2321 limits.template.max_timeout,
2322 template.name
2323 );
2324 }
2325
2326 if let Some(cache) = &template.cache
2327 && let Some(stored_cache) = &cache.stored
2328 {
2329 if let Some(max_size) = stored_cache.max_size
2330 && (max_size < limits.storage.cache.max_size_range.0
2331 || max_size > limits.storage.cache.max_size_range.1)
2332 {
2333 bail!(
2334 "template cache max_size {} is not within range [{}, {}] for {}",
2335 max_size,
2336 limits.storage.cache.max_size_range.0,
2337 limits.storage.cache.max_size_range.1,
2338 template.name
2339 );
2340 }
2341
2342 if let Some(max_count) = stored_cache.max_count
2343 && (max_count < limits.storage.cache.max_count_range.0
2344 || max_count > limits.storage.cache.max_count_range.1)
2345 {
2346 bail!(
2347 "template cache max_count {} is not within range [{}, {}] for {}",
2348 max_count,
2349 limits.storage.cache.max_count_range.0,
2350 limits.storage.cache.max_count_range.1,
2351 template.name
2352 );
2353 }
2354
2355 if let Some((sync_interval_min, sync_interval_max)) = stored_cache.sync_interval
2356 {
2357 if sync_interval_min < limits.storage.cache.sync_interval_ranges.0.0
2358 || sync_interval_min > limits.storage.cache.sync_interval_ranges.0.1
2359 {
2360 bail!(
2361 "template cache sync_interval min {} is not within range [{}, {}] for {}",
2362 sync_interval_min,
2363 limits.storage.cache.sync_interval_ranges.0.0,
2364 limits.storage.cache.sync_interval_ranges.0.1,
2365 template.name
2366 );
2367 }
2368
2369 if sync_interval_max < limits.storage.cache.sync_interval_ranges.1.0
2370 || sync_interval_max > limits.storage.cache.sync_interval_ranges.1.1
2371 {
2372 bail!(
2373 "template cache sync_interval min {} is not within range [{}, {}] for {}",
2374 sync_interval_max,
2375 limits.storage.cache.sync_interval_ranges.1.0,
2376 limits.storage.cache.sync_interval_ranges.1.1,
2377 template.name
2378 );
2379 }
2380 }
2381
2382 if let Some((clean_interval_min, clean_interval_max)) =
2383 stored_cache.clean_interval
2384 {
2385 if clean_interval_min < limits.storage.cache.clean_interval_ranges.0.0
2386 || clean_interval_min > limits.storage.cache.clean_interval_ranges.0.1
2387 {
2388 bail!(
2389 "template cache clean_interval min {} is not within range [{}, {}] for {}",
2390 clean_interval_min,
2391 limits.storage.cache.clean_interval_ranges.0.0,
2392 limits.storage.cache.clean_interval_ranges.0.1,
2393 template.name
2394 );
2395 }
2396
2397 if clean_interval_max < limits.storage.cache.clean_interval_ranges.1.0
2398 || clean_interval_max > limits.storage.cache.clean_interval_ranges.1.1
2399 {
2400 bail!(
2401 "template cache clean_interval min {} is not within range [{}, {}] for {}",
2402 clean_interval_max,
2403 limits.storage.cache.clean_interval_ranges.1.0,
2404 limits.storage.cache.clean_interval_ranges.1.1,
2405 template.name
2406 );
2407 }
2408 }
2409 }
2410 }
2411 }
2412
2413 if let Some(integrations) = &self.integrations {
2414 if integrations.len() > limits.integration.count as usize {
2415 bail!(
2416 "integration count {} greater than limit {}",
2417 integrations.len(),
2418 limits.integration.count
2419 );
2420 }
2421
2422 for integration in integrations {
2423 if let Some(timeout) = integration.timeout
2424 && timeout > limits.integration.max_timeout
2425 {
2426 bail!(
2427 "integration timeout {} greater than limit {} for {}",
2428 timeout,
2429 limits.integration.max_timeout,
2430 integration.name,
2431 );
2432 }
2433 }
2434 }
2435
2436 if let Some(actions) = &self.actions {
2437 if actions.len() > limits.action.count as usize {
2438 bail!(
2439 "action count {} greater than limit {}",
2440 actions.len(),
2441 limits.action.count
2442 );
2443 }
2444
2445 for action in actions {
2446 if action.privileged == Some(true) {
2447 bail!("action {} is not under a privileged domain", action.name);
2448 }
2449
2450 if let Some(timeout) = action.timeout
2451 && timeout > limits.action.max_timeout
2452 {
2453 bail!(
2454 "action timeout {} greater than limit {} for {}",
2455 timeout,
2456 limits.action.max_timeout,
2457 action.name
2458 );
2459 }
2460 }
2461 }
2462
2463 if self.storage_size > limits.storage.max_app_storage {
2464 bail!("storage size is greater than limit");
2465 }
2466
2467 if let Some(content) = &self.content {
2468 if content.definitions.len() > limits.storage.content.max_content_definitions as usize {
2469 bail!(
2470 "content definition count {} greater than limit {}",
2471 content.definitions.len(),
2472 limits.storage.content.max_content_definitions
2473 );
2474 }
2475
2476 for content_def in &content.definitions {
2477 if content_def.fields.len() > limits.storage.content.max_content_fields as usize {
2478 bail!(
2479 "content field count {} greater than limit {} for {}",
2480 content_def.fields.len(),
2481 limits.storage.content.max_content_fields,
2482 content_def.name,
2483 );
2484 }
2485
2486 for field in &content_def.fields {
2487 if field.searchable == Some(true) && !limits.storage.content.search_enabled {
2488 bail!(
2489 "content field cannot be 'searchable' for field {} on definition {}",
2490 field.name,
2491 content_def.name,
2492 );
2493 }
2494 }
2495 }
2496 }
2497
2498 if let Some(models) = &self.models {
2499 if models.len() > limits.storage.model.max_model_definitions as usize {
2500 bail!(
2501 "model definition count {} greater than limit {}",
2502 models.len(),
2503 limits.storage.model.max_model_definitions
2504 );
2505 }
2506
2507 for model in models {
2508 if model.fields.len() > limits.storage.model.max_model_fields as usize {
2509 bail!(
2510 "model field count {} greater than limit {} for {}",
2511 model.fields.len(),
2512 limits.storage.content.max_content_fields,
2513 model.name,
2514 );
2515 }
2516
2517 for field in &model.fields {
2518 if field.searchable == Some(true) && !limits.storage.content.search_enabled {
2519 bail!(
2520 "content field cannot be 'searchable' for field {} on definition {}",
2521 field.name,
2522 model.name,
2523 );
2524 }
2525 }
2526 }
2527 }
2528
2529 if let Some(secrets) = &self.secrets
2530 && secrets.len() > limits.storage.secrets.max_count as usize
2531 {
2532 bail!(
2533 "secrets len {} exceeds max count limit {}",
2534 secrets.len(),
2535 limits.storage.secrets.max_count
2536 );
2537 }
2538
2539 Ok(())
2540 }
2541}