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
6mod validate;
11
12use crate::validate::validate;
13
14use anyhow::bail;
15use arrayvec::ArrayVec;
16use hashbrown::HashSet;
17use ordinary_types::{Field, Kind};
18use serde::{Deserialize, Serialize};
19use std::env;
20use std::fmt::{Display, Formatter, Write};
21use std::path::Path;
22use std::process::Command;
23use tracing::instrument;
24
25fn default_env_name() -> String {
26 "development".to_string()
27}
28
29#[derive(Deserialize, Serialize, Clone)]
30pub struct OrdinaryApiConfig {
31 pub domain: String,
32 pub contacts: Vec<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 #[serde(default)]
35 pub public_dns_ip: Option<[u8; 4]>,
36 #[serde(default = "default_env_name")]
37 pub env_name: String,
38 #[serde(default)]
39 pub limits: OrdinaryApiLimits,
40}
41
42#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
43#[derive(Deserialize, Serialize, Debug, Clone)]
44pub struct AssetsLimits {
45 pub allowed_extensions: Vec<String>,
78 pub max_store_size: u64,
81 pub max_asset_size: u64,
83}
84
85impl Default for AssetsLimits {
86 fn default() -> Self {
87 Self {
88 allowed_extensions: vec![
89 "otf".into(),
90 "ttf".into(),
91 "woff".into(),
92 "woff2".into(),
93 "txt".into(),
94 "xml".into(),
95 "html".into(),
96 "css".into(),
97 "css.map".into(),
98 "csv".into(),
99 "js".into(),
100 "png".into(),
101 "apng".into(),
102 "gif".into(),
103 "svg".into(),
104 "jpg".into(),
105 "jpeg".into(),
106 "bmp".into(),
107 "tif".into(),
108 "tiff".into(),
109 "webp".into(),
110 "avif".into(),
111 "ico".into(),
112 "pdf".into(),
113 "json".into(),
114 "wasm".into(),
115 ],
116 max_store_size: 100_000_000,
117 max_asset_size: 1_500_000,
118 }
119 }
120}
121
122#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
123#[derive(Deserialize, Serialize, Debug, Clone)]
124pub struct ArtifactLimits {
125 pub max_store_size: u64,
126 pub max_artifact_size: u64,
127}
128
129impl Default for ArtifactLimits {
130 fn default() -> Self {
131 Self {
132 max_store_size: 10_000_000,
133 max_artifact_size: 500_000,
134 }
135 }
136}
137
138#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
139#[derive(Deserialize, Serialize, Debug, Clone)]
140pub struct CacheLimits {
141 pub max_size_range: (u64, u64),
142 pub max_count_range: (usize, usize),
143
144 pub clean_interval_ranges: ((u64, u64), (u64, u64)),
145}
146
147impl Default for CacheLimits {
148 fn default() -> Self {
149 Self {
150 max_size_range: (1_000_000, 10_000_000),
151 max_count_range: (100, 500),
152
153 clean_interval_ranges: ((5, 10), (15, 20)),
154 }
155 }
156}
157
158#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
159#[derive(Deserialize, Serialize, Debug, Clone)]
160pub struct ContentLimits {
161 pub search_enabled: bool,
162
163 pub max_content_definitions: u8,
164 pub max_content_fields: u8,
165
166 pub max_store_size: u64,
167 pub max_object_size: u64,
168 pub max_field_size: u64,
169}
170
171impl Default for ContentLimits {
172 fn default() -> Self {
173 Self {
174 search_enabled: true,
175
176 max_content_definitions: 255,
177 max_content_fields: 255,
178
179 max_store_size: 10_000_000,
180 max_object_size: 400_000,
181 max_field_size: 200_000,
182 }
183 }
184}
185
186#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
187#[derive(Deserialize, Serialize, Debug, Clone)]
188pub struct ModelLimits {
189 pub search_enabled: bool,
190
191 pub max_model_definitions: u8,
192 pub max_model_fields: u8,
193
194 pub max_item_size: u64,
195 pub max_field_size: u64,
196}
197
198impl Default for ModelLimits {
199 fn default() -> Self {
200 Self {
201 search_enabled: true,
202
203 max_model_definitions: 255,
204 max_model_fields: 255,
205
206 max_item_size: 400_000,
207 max_field_size: 100_000,
208 }
209 }
210}
211
212#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
213#[derive(Deserialize, Serialize, Debug, Clone)]
214pub struct SecretsLimits {
215 pub max_count: u8,
216 pub max_size: u64,
217}
218
219impl Default for SecretsLimits {
220 fn default() -> Self {
221 Self {
222 max_count: 225,
223 max_size: 2_000,
224 }
225 }
226}
227
228#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
229#[derive(Deserialize, Serialize, Debug, Clone)]
230pub struct StorageLimits {
231 pub max_storage: u64,
237 pub max_app_storage: u64,
243
244 pub assets: AssetsLimits,
245 pub artifact: ArtifactLimits,
246 pub cache: CacheLimits,
247 pub content: ContentLimits,
248 pub model: ModelLimits,
249 pub secrets: SecretsLimits,
250}
251
252impl Default for StorageLimits {
253 fn default() -> Self {
254 Self {
255 max_storage: 20_000_000_000,
256 max_app_storage: 50_000_000,
257
258 assets: AssetsLimits::default(),
259 artifact: ArtifactLimits::default(),
260 cache: CacheLimits::default(),
261 content: ContentLimits::default(),
262 model: ModelLimits::default(),
263 secrets: SecretsLimits::default(),
264 }
265 }
266}
267
268#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
269#[derive(Deserialize, Serialize, Debug, Clone, Default)]
270pub struct MonitorLimits {}
271
272#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
273#[derive(Deserialize, Serialize, Debug, Clone)]
274pub struct IntegrationLimits {
275 pub count: u8,
277
278 pub max_timeout: u16,
282}
283
284impl Default for IntegrationLimits {
285 fn default() -> Self {
286 Self {
287 count: 255,
288 max_timeout: 10,
289 }
290 }
291}
292
293#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
294#[derive(Deserialize, Serialize, Debug, Clone)]
295pub struct ActionLimits {
296 pub count: u8,
298
299 pub max_timeout: u16,
303}
304
305impl Default for ActionLimits {
306 fn default() -> Self {
307 Self {
308 count: 255,
309 max_timeout: 10,
310 }
311 }
312}
313
314#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
315#[derive(Deserialize, Serialize, Debug, Clone, Default)]
316pub struct AuthLimits {}
317
318#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
319#[derive(Deserialize, Serialize, Debug, Clone)]
320pub struct TemplateLimits {
321 pub count: u8,
323
324 pub max_timeout: u16,
328}
329
330impl Default for TemplateLimits {
331 fn default() -> Self {
332 Self {
333 count: 255,
334 max_timeout: 10,
335 }
336 }
337}
338
339#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
340#[derive(Deserialize, Serialize, Debug, Clone)]
341pub struct OrdinaryApiLimits {
342 pub app_domains: Vec<String>,
343 pub privileged_domains: Vec<String>,
344
345 pub max_default_timeout: u16,
349
350 pub action: ActionLimits,
351 pub auth: AuthLimits,
352 pub integration: IntegrationLimits,
353 pub monitor: MonitorLimits,
354 pub storage: StorageLimits,
355 pub template: TemplateLimits,
356}
357
358impl Default for OrdinaryApiLimits {
359 fn default() -> Self {
360 Self {
361 app_domains: vec![],
362 privileged_domains: vec![],
363
364 max_default_timeout: 10,
365
366 action: ActionLimits::default(),
367 auth: AuthLimits::default(),
368 integration: IntegrationLimits::default(),
369 monitor: MonitorLimits::default(),
370 storage: StorageLimits::default(),
371 template: TemplateLimits::default(),
372 }
373 }
374}
375
376#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
377#[derive(Deserialize, Serialize, Debug, Clone)]
378pub struct ClientLoggingConfig {
379 #[serde(skip_serializing_if = "Option::is_none")]
381 #[serde(default)]
382 min_delay: Option<u32>,
383 #[serde(skip_serializing_if = "Option::is_none")]
385 #[serde(default)]
386 max_delay: Option<u32>,
387 #[serde(skip_serializing_if = "Option::is_none")]
390 #[serde(default)]
391 max_buffer: Option<u16>,
392 #[serde(skip_serializing_if = "Option::is_none")]
394 #[serde(default)]
395 max_batch: Option<u16>,
396}
397
398#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
399#[derive(Deserialize, Serialize, Debug, Clone)]
400pub enum RedactedHashAlg {
401 Blake2,
402 Blake3,
403}
404
405#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
406#[derive(Deserialize, Serialize, Debug, Clone)]
407pub struct ServerLoggingConfig {
408 #[serde(skip_serializing_if = "Option::is_none")]
409 #[serde(default)]
410 pub ips: Option<bool>,
411 #[serde(skip_serializing_if = "Option::is_none")]
412 #[serde(default)]
413 pub headers: Option<bool>,
414 #[serde(skip_serializing_if = "Option::is_none")]
415 #[serde(default)]
416 pub credentials: Option<RedactedHashAlg>,
417 #[serde(skip_serializing_if = "Option::is_none")]
418 #[serde(default)]
419 pub timing: Option<bool>,
420 #[serde(skip_serializing_if = "Option::is_none")]
421 #[serde(default)]
422 pub sizes: Option<bool>,
423}
424
425#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
426#[derive(Deserialize, Serialize, Debug, Clone)]
427pub struct LoggingConfig {
428 #[serde(skip_serializing_if = "Option::is_none")]
429 #[serde(default)]
430 pub client: Option<ClientLoggingConfig>,
431 #[serde(skip_serializing_if = "Option::is_none")]
432 #[serde(default)]
433 pub server: Option<ServerLoggingConfig>,
434}
435
436#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
437#[derive(Deserialize, Serialize, Debug, Clone)]
438pub struct Check {
439 }
442
443#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
444#[derive(Deserialize, Serialize, Debug, Clone)]
445pub enum TokenAlgorithm {
446 HmacBlake2b256,
447}
448
449#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
451#[derive(Deserialize, Serialize, Debug, Clone)]
452pub struct RefreshTokenConfig {
453 pub algorithm: TokenAlgorithm,
455 pub lifetime: u32,
457 pub rotation: u32,
459}
460
461impl Default for RefreshTokenConfig {
462 fn default() -> RefreshTokenConfig {
463 RefreshTokenConfig {
464 algorithm: TokenAlgorithm::HmacBlake2b256,
465 lifetime: 60 * 60 * 24 * 7,
466 rotation: 60 * 60 * 24 * 7 * 2,
467 }
468 }
469}
470
471#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
473#[derive(Deserialize, Serialize, Debug, Clone)]
474pub struct AccessTokenConfig {
475 pub algorithm: TokenAlgorithm,
477 pub lifetime: u32,
479 pub rotation: u32,
481 pub claims: Vec<Field>,
485}
486
487impl Default for AccessTokenConfig {
488 fn default() -> AccessTokenConfig {
489 AccessTokenConfig {
490 algorithm: TokenAlgorithm::HmacBlake2b256,
491 lifetime: 60 * 60 * 24,
492 rotation: 60 * 60 * 24 * 3,
493 claims: vec![],
494 }
495 }
496}
497
498#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
505#[derive(Deserialize, Serialize, Debug, Clone)]
506pub enum ClientPasswordHash {
507 Sha256,
512}
513
514#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
516#[derive(Deserialize, Serialize, Debug, Clone)]
517pub enum PasswordProtocol {
518 Opaque,
528 }
530
531#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
533#[derive(Deserialize, Serialize, Debug, Clone)]
534pub struct PasswordConfig {
535 pub protocol: PasswordProtocol,
536}
537
538impl Default for PasswordConfig {
539 fn default() -> PasswordConfig {
540 PasswordConfig {
541 protocol: PasswordProtocol::Opaque,
542 }
543 }
544}
545
546#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
547#[derive(Deserialize, Serialize, Debug, Clone)]
548pub enum TotpAlgorithm {
549 Sha1,
553}
554
555#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
556#[derive(Deserialize, Serialize, Debug, Clone)]
557pub struct TotpConfig {
558 #[serde(skip_serializing_if = "Option::is_none")]
561 #[serde(default)]
562 pub template: Option<String>,
563 pub algorithm: TotpAlgorithm,
564}
565
566impl Default for TotpConfig {
567 fn default() -> TotpConfig {
568 TotpConfig {
569 template: None,
570 algorithm: TotpAlgorithm::Sha1,
571 }
572 }
573}
574
575#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
576#[derive(Serialize, Deserialize, Debug, Clone, Default)]
577pub struct MfaConfig {
578 pub totp: TotpConfig,
579}
580
581#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
582#[derive(Deserialize, Serialize, Debug, Clone)]
583pub enum InviteMode {
584 Root,
586 Admin,
588 Viral,
590 }
596
597#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
598#[derive(Deserialize, Serialize, Debug, Clone)]
599pub struct InviteConfig {
600 pub mode: InviteMode,
601 pub lifetime: u32,
605 pub clean_interval: (u32, u32),
609 #[serde(skip_serializing_if = "Option::is_none")]
622 #[serde(default)]
623 pub claims: Option<Vec<Field>>,
624}
625
626impl Default for InviteConfig {
627 fn default() -> InviteConfig {
628 InviteConfig {
629 mode: InviteMode::Viral,
630 lifetime: 60 * 60 * 24 * 7,
631 clean_interval: (30, 90),
632 claims: None,
633 }
634 }
635}
636
637#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
639#[derive(Deserialize, Serialize, Debug, Clone)]
640pub struct AuthConfig {
641 pub password: PasswordConfig,
643 pub mfa: MfaConfig,
645 pub refresh_token: RefreshTokenConfig,
647 pub access_token: AccessTokenConfig,
649 pub cookies_enabled: bool,
660 pub client_hash: ClientPasswordHash,
662
663 #[serde(skip_serializing_if = "Option::is_none")]
665 #[serde(default)]
666 pub invite: Option<InviteConfig>,
667}
668
669impl Default for AuthConfig {
670 fn default() -> AuthConfig {
671 AuthConfig {
672 password: PasswordConfig::default(),
673 mfa: MfaConfig::default(),
674 refresh_token: RefreshTokenConfig::default(),
675 access_token: AccessTokenConfig::default(),
676 cookies_enabled: false,
677 client_hash: ClientPasswordHash::Sha256,
678 invite: None,
679 }
680 }
681}
682
683#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
685#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
686pub enum CompressionAlgorithm {
687 All,
688 Gzip,
689 Zstd { level: u8 },
690 Brotli,
691 Deflate,
692}
693
694impl CompressionAlgorithm {
695 #[must_use]
696 pub fn as_u8(&self) -> u8 {
697 match self {
698 Self::All => 0,
699 Self::Gzip => 1,
700 Self::Zstd { level: _ } => 2,
701 Self::Brotli => 3,
702 Self::Deflate => 4,
703 }
704 }
705
706 #[must_use]
707 pub fn as_char(&self) -> char {
708 match self {
709 Self::All => '0',
710 Self::Gzip => '1',
711 Self::Zstd { level: _ } => '2',
712 Self::Brotli => '3',
713 Self::Deflate => '4',
714 }
715 }
716
717 #[must_use]
718 pub fn as_str(&self) -> &'static str {
719 match self {
720 Self::All => "",
721 Self::Gzip => "gzip",
722 Self::Zstd { level: _ } => "zstd",
723 Self::Brotli => "br",
724 Self::Deflate => "deflate",
725 }
726 }
727}
728
729#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
730#[derive(Deserialize, Serialize, Debug, Clone)]
731pub enum StoredCachePolicy {
732 Permanent,
736 FRs(u64, u64),
740}
741
742#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
747#[derive(Deserialize, Serialize, Debug, Clone)]
748pub struct StoredCache {
749 pub policy: StoredCachePolicy,
750
751 #[serde(skip_serializing_if = "Option::is_none")]
753 #[serde(default)]
754 pub compression: Option<CompressionAlgorithms>,
755
756 #[serde(skip_serializing)]
757 #[serde(default)]
758 pub internal_compression: Option<ArrayVec<CompressionAlgorithm, 4>>,
759
760 #[serde(skip_serializing_if = "Option::is_none")]
764 #[serde(default)]
765 pub max_ttl: Option<u64>,
766
767 #[serde(skip_serializing_if = "Option::is_none")]
771 #[serde(default)]
772 pub hit_ttl: Option<u64>,
773
774 #[serde(skip_serializing_if = "Option::is_none")]
779 #[serde(default)]
780 pub max_size: Option<u64>,
781
782 #[serde(skip_serializing_if = "Option::is_none")]
785 #[serde(default)]
786 pub max_count: Option<usize>,
787
788 #[serde(skip_serializing_if = "Option::is_none")]
792 #[serde(default)]
793 pub frequency_window: Option<u64>,
794
795 #[serde(skip_serializing_if = "Option::is_none")]
799 #[serde(default)]
800 pub clean_interval: Option<(u64, u64)>,
801
802 #[serde(skip_serializing_if = "Option::is_none")]
805 #[serde(default)]
806 pub evict_on_dependency_change: Option<bool>,
807}
808
809#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
810#[derive(Deserialize, Serialize, Debug, Clone)]
811pub enum XXH3Variation {
812 Bit64,
813 Bit128,
814}
815
816#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
820#[derive(Deserialize, Serialize, Debug, Clone)]
821pub enum HttpEtagAlgorithm {
822 AHash,
823 XXH3(XXH3Variation),
824 Rustc,
825 Blake3,
826}
827
828#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
831#[derive(Deserialize, Serialize, Debug, Clone)]
832pub struct HttpEtag {
833 #[serde(skip_serializing_if = "Option::is_none")]
834 #[serde(default)]
835 pub alg: Option<HttpEtagAlgorithm>,
836}
837
838#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
841#[derive(Deserialize, Serialize, Debug, Clone, Default)]
842pub struct HttpCacheControl {
843 #[serde(skip_serializing_if = "Option::is_none")]
844 #[serde(default)]
845 pub max_age: Option<usize>,
846 #[serde(skip_serializing_if = "Option::is_none")]
847 #[serde(default)]
848 pub s_maxage: Option<usize>,
849 #[serde(skip_serializing_if = "Option::is_none")]
850 #[serde(default)]
851 pub no_cache: Option<bool>,
852 #[serde(skip_serializing_if = "Option::is_none")]
853 #[serde(default)]
854 pub no_store: Option<bool>,
855 #[serde(skip_serializing_if = "Option::is_none")]
856 #[serde(default)]
857 pub no_transform: Option<bool>,
858 #[serde(skip_serializing_if = "Option::is_none")]
859 #[serde(default)]
860 pub must_revalidate: Option<bool>,
861 #[serde(skip_serializing_if = "Option::is_none")]
862 #[serde(default)]
863 pub proxy_revalidate: Option<bool>,
864 #[serde(skip_serializing_if = "Option::is_none")]
865 #[serde(default)]
866 pub private: Option<bool>,
867 #[serde(skip_serializing_if = "Option::is_none")]
868 #[serde(default)]
869 pub public: Option<bool>,
870 #[serde(skip_serializing_if = "Option::is_none")]
871 #[serde(default)]
872 pub immutable: Option<bool>,
873 #[serde(skip_serializing_if = "Option::is_none")]
874 #[serde(default)]
875 pub stale_while_revalidate: Option<bool>,
876 #[serde(skip_serializing_if = "Option::is_none")]
877 #[serde(default)]
878 pub stale_if_error: Option<bool>,
879}
880
881impl HttpCacheControl {
882 pub fn header_value(&self, cache_control: &mut String, default: &str) -> anyhow::Result<()> {
883 if let Some(max_age) = self.max_age {
884 write!(cache_control, "max-age={max_age}, ")?;
885 }
886 if let Some(s_maxage) = self.s_maxage {
887 write!(cache_control, "s-maxage={s_maxage}, ")?;
888 }
889 if let Some(no_cache) = self.no_cache
890 && no_cache
891 {
892 write!(cache_control, "no-cache, ")?;
893 }
894 if let Some(no_store) = self.no_store
895 && no_store
896 {
897 write!(cache_control, "no-store, ")?;
898 }
899 if let Some(no_transform) = self.no_transform
900 && no_transform
901 {
902 write!(cache_control, "no-transform, ")?;
903 }
904 if let Some(must_revalidate) = self.must_revalidate
905 && must_revalidate
906 {
907 write!(cache_control, "must-revalidate, ")?;
908 }
909 if let Some(proxy_revalidate) = self.proxy_revalidate
910 && proxy_revalidate
911 {
912 write!(cache_control, "proxy-revalidate, ")?;
913 }
914 if let Some(stale_while_revalidate) = self.stale_while_revalidate
915 && stale_while_revalidate
916 {
917 write!(cache_control, "stale-while-revalidate, ")?;
918 }
919 if let Some(private) = self.private
920 && private
921 {
922 write!(cache_control, "private, ")?;
923 }
924 if let Some(public) = self.public
925 && public
926 {
927 write!(cache_control, "public, ")?;
928 }
929 if let Some(immutable) = self.immutable
930 && immutable
931 {
932 write!(cache_control, "immutable, ")?;
933 }
934 if let Some(stale_if_error) = self.stale_if_error
935 && stale_if_error
936 {
937 write!(cache_control, "stale-if-error, ")?;
938 }
939
940 if cache_control.is_empty() {
941 cache_control.push_str(default);
942 } else {
943 cache_control.pop();
944 cache_control.pop();
945 }
946
947 Ok(())
948 }
949}
950
951#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
952#[derive(Deserialize, Serialize, Debug, Clone, Default)]
953pub struct HttpCache {
954 #[serde(skip_serializing_if = "Option::is_none")]
955 #[serde(default)]
956 pub cache_control: Option<HttpCacheControl>,
957
958 #[serde(skip_serializing_if = "Option::is_none")]
963 #[serde(default)]
964 pub expires: Option<u64>,
965
966 #[serde(skip_serializing_if = "Option::is_none")]
967 #[serde(default)]
968 pub etag: Option<HttpEtag>,
969}
970
971#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
973#[derive(Deserialize, Serialize, Debug, Clone)]
974pub struct TemplateField {
975 pub name: String,
977 pub kind: Kind,
979 pub value: serde_json::Value,
981}
982
983#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
985#[derive(Deserialize, Serialize, Debug, Clone)]
986pub enum QueryExpression {
987 Gte,
988 Gt,
989 Lte,
990 Lt,
991 Eq,
992 BeginsWith,
993}
994
995#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
997#[derive(Deserialize, Serialize, Debug, Clone)]
998pub enum TemplateRefFieldBind {
999 Token {
1001 field: String,
1006 #[serde(skip_serializing_if = "Option::is_none")]
1008 #[serde(default)]
1009 expression: Option<QueryExpression>,
1010 },
1011 Segment {
1013 name: String,
1015 #[serde(skip_serializing_if = "Option::is_none")]
1017 #[serde(default)]
1018 expression: Option<QueryExpression>,
1019 },
1020}
1021
1022#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1025#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1026pub struct TemplateRefField {
1027 pub idx: u8,
1030 pub name: String,
1032 #[serde(skip_serializing_if = "Option::is_none")]
1035 #[serde(default)]
1036 pub bind: Option<TemplateRefFieldBind>,
1037 #[cfg_attr(feature = "utoipa", schema(no_recursion))]
1039 #[serde(skip_serializing_if = "Option::is_none")]
1040 #[serde(default)]
1041 pub fields: Option<Vec<TemplateRefField>>,
1042}
1043
1044#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1048#[derive(Deserialize, Serialize, Debug, Clone)]
1049pub struct TemplateRef {
1050 pub idx: u8,
1054 pub name: String,
1056 pub fields: Vec<TemplateRefField>,
1058 #[serde(skip_serializing_if = "Option::is_none")]
1062 #[serde(default)]
1063 pub all: Option<String>,
1064}
1065
1066#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1069#[derive(Deserialize, Serialize, Debug, Clone)]
1070pub struct TemplateFlagRef {
1071 pub idx: u8,
1075 pub name: String,
1077}
1078
1079#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1080#[derive(Deserialize, Serialize, Debug, Clone)]
1081pub struct TemplateParamRef {
1082 pub idx: u8,
1086 pub name: String,
1088}
1089
1090#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1091#[derive(Deserialize, Serialize, Debug, Clone)]
1092pub struct TemplateCache {
1093 #[serde(skip_serializing_if = "Option::is_none")]
1094 #[serde(default)]
1095 pub stored: Option<StoredCache>,
1096
1097 #[serde(skip_serializing_if = "Option::is_none")]
1098 #[serde(default)]
1099 pub http: Option<HttpCache>,
1100}
1101
1102#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1106#[derive(Deserialize, Serialize, Debug, Clone)]
1107pub enum WasmOpt {
1108 Size,
1109 SizeAggressive,
1110 Level0,
1111 Level1,
1112 Level2,
1113 Level3,
1114 Level4,
1115}
1116
1117impl WasmOpt {
1118 #[must_use]
1119 pub fn as_flag(&self) -> &'static str {
1120 match self {
1121 Self::Size => "-Os",
1122 Self::SizeAggressive => "-Oz",
1123 Self::Level0 => "-O0",
1124 Self::Level1 => "-O1",
1125 Self::Level2 => "-O2",
1126 Self::Level3 => "-O3",
1127 Self::Level4 => "-O4",
1128 }
1129 }
1130}
1131
1132#[allow(clippy::derivable_impls)]
1133impl Default for TemplateFfiVersion {
1134 fn default() -> Self {
1135 Self::V1
1136 }
1137}
1138
1139#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1140#[derive(Deserialize, Serialize, Debug, Clone)]
1141pub enum TemplateFfiVersion {
1142 V1,
1143}
1144
1145#[allow(clippy::derivable_impls)]
1146impl Default for TemplateFfiSerialization {
1147 fn default() -> Self {
1148 Self::FlexBufferVector
1149 }
1150}
1151
1152#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1154#[derive(Deserialize, Serialize, Debug, Clone)]
1155pub enum TemplateFfiSerialization {
1156 FlexBufferVector,
1157}
1158
1159#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1160#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1161pub struct TemplateFfi {
1162 pub version: TemplateFfiVersion,
1163 pub serialization: TemplateFfiSerialization,
1164}
1165
1166#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1168#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1169pub struct TemplateConfig {
1170 pub ffi: TemplateFfi,
1172
1173 pub idx: u8,
1175 pub name: String,
1177 pub mime: String,
1181 #[serde(skip_serializing_if = "Option::is_none")]
1184 #[serde(default)]
1185 pub minify: Option<bool>,
1186 #[serde(skip_serializing_if = "Option::is_none")]
1188 #[serde(default)]
1189 pub path: Option<String>,
1190 pub route: String,
1210 #[serde(skip_serializing_if = "Option::is_none")]
1212 #[serde(default)]
1213 pub protected: Option<Check>,
1214 #[serde(skip_serializing_if = "Option::is_none")]
1216 #[serde(default)]
1217 pub cache: Option<TemplateCache>,
1218
1219 #[serde(skip_serializing_if = "Option::is_none")]
1229 #[serde(default)]
1230 pub csp: Option<HttpCsp>,
1231
1232 #[serde(skip_serializing_if = "Option::is_none")]
1233 #[serde(default)]
1234 pub cors: Option<HttpCors>,
1235
1236 #[serde(skip_serializing_if = "Option::is_none")]
1240 #[serde(default)]
1241 pub timeout: Option<u16>,
1242
1243 #[serde(skip_serializing_if = "Option::is_none")]
1247 #[serde(default)]
1248 pub fields: Option<Vec<TemplateField>>,
1249 #[serde(skip_serializing_if = "Option::is_none")]
1253 #[serde(default)]
1254 pub globals: Option<Vec<String>>,
1255 #[serde(skip_serializing_if = "Option::is_none")]
1257 #[serde(default)]
1258 pub flags: Option<Vec<TemplateFlagRef>>,
1259 #[serde(skip_serializing_if = "Option::is_none")]
1261 #[serde(default)]
1262 pub params: Option<Vec<TemplateParamRef>>,
1263 #[serde(skip_serializing_if = "Option::is_none")]
1266 #[serde(default)]
1267 pub models: Option<Vec<TemplateRef>>,
1268 #[serde(skip_serializing_if = "Option::is_none")]
1271 #[serde(default)]
1272 pub content: Option<Vec<TemplateRef>>,
1273
1274 #[serde(skip_serializing_if = "Option::is_none")]
1278 #[serde(default)]
1279 pub actions: Option<Vec<String>>,
1280
1281 #[serde(skip_serializing_if = "Option::is_none")]
1282 #[serde(default)]
1283 pub wasm_opt: Option<WasmOpt>,
1284
1285 #[serde(skip_serializing_if = "Option::is_none")]
1289 #[serde(default)]
1290 pub variables: Option<Vec<String>>,
1291}
1292
1293#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1294#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1295pub struct CompressionAlgorithms(pub Vec<CompressionAlgorithm>);
1296
1297impl CompressionAlgorithms {
1298 #[must_use]
1299 fn get_list(&self) -> ArrayVec<CompressionAlgorithm, 4> {
1300 let mut list = ArrayVec::<CompressionAlgorithm, 4>::new();
1301 let mut has_all = false;
1302
1303 for alg in &self.0 {
1304 if *alg == CompressionAlgorithm::All {
1305 has_all = true;
1306 } else if !list.contains(alg) {
1307 list.push(alg.clone());
1308 }
1309 }
1310
1311 if has_all {
1312 for alg in [
1313 CompressionAlgorithm::Brotli,
1314 CompressionAlgorithm::Zstd { level: 17 },
1315 CompressionAlgorithm::Deflate,
1316 CompressionAlgorithm::Gzip,
1317 ] {
1318 if !list.contains(&alg) {
1319 list.push(alg);
1320 }
1321 }
1322 }
1323
1324 list
1325 }
1326}
1327
1328#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1329#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1330pub struct AssetsConfig {
1331 #[serde(skip_serializing_if = "Option::is_none")]
1333 #[serde(default)]
1334 pub dir_path: Option<String>,
1335
1336 #[serde(default = "AssetsConfig::default_base_route")]
1339 pub base_route: String,
1340
1341 #[serde(skip_serializing_if = "Option::is_none")]
1354 #[serde(default)]
1355 pub append_index_html: Option<bool>,
1356
1357 #[serde(skip_serializing_if = "Option::is_none")]
1363 #[serde(default)]
1364 pub skip_base_route_index_html: Option<bool>,
1365
1366 #[serde(skip_serializing_if = "Option::is_none")]
1376 #[serde(default)]
1377 pub append_html_ext: Option<bool>,
1378
1379 #[serde(skip_serializing_if = "Option::is_none")]
1384 #[serde(default)]
1385 pub preserve_exif: Option<bool>,
1386
1387 #[serde(skip_serializing_if = "Option::is_none")]
1389 #[serde(default)]
1390 pub html_csp: Option<HttpCsp>,
1391
1392 #[serde(skip_serializing_if = "Option::is_none")]
1397 #[serde(default)]
1398 pub http: Option<HttpCache>,
1399
1400 #[serde(skip_serializing_if = "Option::is_none")]
1404 #[serde(default)]
1405 pub precompression: Option<CompressionAlgorithms>,
1406
1407 #[serde(skip_serializing)]
1408 #[serde(default)]
1409 pub internal_precompression: Option<ArrayVec<CompressionAlgorithm, 4>>,
1410
1411 #[serde(skip_serializing_if = "Option::is_none")]
1414 #[serde(default)]
1415 pub minify_css: Option<bool>,
1416
1417 #[serde(skip_serializing_if = "Option::is_none")]
1420 #[serde(default)]
1421 pub minify_js: Option<bool>,
1422
1423 #[serde(skip_serializing_if = "Option::is_none")]
1426 #[serde(default)]
1427 pub minify_html: Option<bool>,
1428
1429 #[serde(skip_serializing_if = "Option::is_none")]
1430 #[serde(default)]
1431 pub internal_cache_control_header_value: Option<String>,
1432}
1433
1434impl AssetsConfig {
1435 fn default_base_route() -> String {
1436 "/assets".to_string()
1437 }
1438}
1439
1440impl AssetsConfig {
1441 pub fn init(&mut self, default_cache_control_header_value: &str) -> anyhow::Result<()> {
1442 if let Some(http_cache) = &self.http
1443 && let Some(http_cache_control) = &http_cache.cache_control
1444 {
1445 let mut header = String::new();
1446 http_cache_control.header_value(&mut header, default_cache_control_header_value)?;
1447
1448 self.internal_cache_control_header_value = Some(header);
1449 }
1450
1451 Ok(())
1452 }
1453}
1454
1455#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1456#[derive(Deserialize, Serialize, Debug, Clone)]
1457pub struct FragmentsConfig {
1458 pub dir_path: String,
1460}
1461
1462#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1464#[derive(Deserialize, Serialize, Debug, Clone)]
1465pub struct Global {
1466 pub name: String,
1468 pub kind: Kind,
1470 pub value: serde_json::Value,
1472}
1473
1474#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1476#[derive(Deserialize, Serialize, Debug, Clone)]
1477pub enum SecretSource {
1478 Env,
1491 Stored,
1499 }
1504
1505#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1507#[derive(Deserialize, Serialize, Debug, Clone)]
1508pub enum SecretVisibility {
1509 Integrations,
1521}
1522
1523#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1525#[derive(Deserialize, Serialize, Debug, Clone)]
1526pub struct Secret {
1527 pub name: String,
1529 pub source: SecretSource,
1531 pub visibility: SecretVisibility,
1533}
1534
1535#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1537#[derive(Deserialize, Serialize, Debug, Clone)]
1538pub struct FlagOption {
1539 pub idx: u8,
1541 pub name: String,
1543 pub percentage: u8,
1546}
1547
1548#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1555#[derive(Deserialize, Serialize, Debug, Clone)]
1556pub struct Flag {
1557 pub idx: u8,
1559 pub name: String,
1561 pub options: Vec<FlagOption>,
1564}
1565
1566#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1567#[derive(Deserialize, Serialize, Debug, Clone)]
1568pub struct ContentDefinition {
1569 pub idx: u8,
1571
1572 pub name: String,
1574
1575 pub fields: Vec<Field>,
1580
1581 #[serde(skip_serializing_if = "Option::is_none")]
1583 #[serde(default)]
1584 pub lifecycle: Option<ContentObjectLifecycle>,
1585}
1586
1587#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1588#[derive(Deserialize, Serialize, Debug, Clone)]
1589pub struct ContentObjectLifecycle {
1590 #[serde(skip_serializing_if = "Option::is_none")]
1592 #[serde(default)]
1593 pub before_all: Option<Vec<Vec<String>>>,
1594 #[serde(skip_serializing_if = "Option::is_none")]
1600 #[serde(default)]
1601 pub on_add: Option<LifecycleBeforeAfterScripts>,
1602 #[serde(skip_serializing_if = "Option::is_none")]
1608 #[serde(default)]
1609 pub on_edit: Option<LifecycleBeforeAfterScripts>,
1610 #[serde(skip_serializing_if = "Option::is_none")]
1616 #[serde(default)]
1617 pub on_delete: Option<LifecycleBeforeAfterScripts>,
1618}
1619
1620#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1626#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1627pub struct Content {
1628 pub file_path: String,
1631 pub definitions: Vec<ContentDefinition>,
1634
1635 #[serde(skip_serializing_if = "Option::is_none")]
1636 #[serde(default)]
1637 pub update: Option<ContentUpdateConfig>,
1638}
1639
1640#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1641#[derive(Deserialize, Serialize, Debug, Clone)]
1642pub struct ContentUpdateConfig {
1643 #[serde(skip_serializing_if = "Option::is_none")]
1644 #[serde(default)]
1645 pub lifecycle: Option<LifecycleBeforeAfterScripts>,
1646}
1647
1648#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1649#[derive(Deserialize, Serialize, Debug, Clone)]
1650pub enum UuidVersion {
1651 V4,
1652 V7,
1653}
1654
1655#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1657#[derive(Deserialize, Serialize, Debug, Clone)]
1658pub struct ModelConfig {
1659 pub idx: u8,
1666 pub name: String,
1668 pub fields: Vec<Field>,
1670 #[serde(skip_serializing_if = "Option::is_none")]
1674 #[serde(default)]
1675 pub uuid: Option<UuidVersion>,
1676}
1677
1678#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1679#[derive(Deserialize, Serialize, Debug, Clone)]
1680pub enum IntegrationProtocolHttpEncoding {
1681 Json,
1682 Text,
1683 None,
1684}
1685
1686#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1688#[derive(Deserialize, Serialize, Debug, Clone)]
1689pub enum IntegrationProtocol {
1690 Http {
1692 method: String,
1694 headers: Vec<(String, String)>,
1696 send_encoding: IntegrationProtocolHttpEncoding,
1698 recv_encoding: IntegrationProtocolHttpEncoding,
1700 },
1701 }
1714
1715#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1718#[derive(Deserialize, Serialize, Debug, Clone)]
1719pub struct IntegrationConfig {
1720 pub idx: u8,
1722 pub name: String,
1724 pub protocol: IntegrationProtocol,
1726 pub endpoint: String,
1728 pub send: Kind,
1732 pub recv: Kind,
1736 #[serde(skip_serializing_if = "Option::is_none")]
1738 #[serde(default)]
1739 pub secrets: Option<Vec<String>>,
1740
1741 #[serde(skip_serializing_if = "Option::is_none")]
1745 #[serde(default)]
1746 pub timeout: Option<u16>,
1747}
1748
1749#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1753#[derive(Deserialize, Serialize, Debug, Clone)]
1754pub enum ActionLang {
1755 Rust,
1759 JavaScript,
1765 }
1769
1770#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1772#[derive(Deserialize, Serialize, Debug, Clone)]
1773pub enum ActionAccessModelOps {
1774 Insert,
1776 Get,
1778 Query,
1780 Search,
1782 Update,
1784 Delete,
1786}
1787
1788#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1790#[derive(Deserialize, Serialize, Debug, Clone)]
1791pub enum ActionAccessAuthOps {
1792 SetTokenFields,
1795}
1796
1797#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1800#[derive(Deserialize, Serialize, Debug, Clone)]
1801pub enum ActionAccessPermission {
1802 Model {
1804 name: String,
1806 ops: Vec<ActionAccessModelOps>,
1809 },
1810 Content {
1812 name: String,
1814 },
1815 Integration {
1817 name: String,
1819 },
1820 Action {
1822 name: String,
1824 },
1825 Auth {
1827 ops: Vec<ActionAccessAuthOps>,
1829 },
1830}
1831
1832#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1833#[derive(Deserialize, Serialize, Debug, Clone)]
1834pub enum HttpMethod {
1835 PUT,
1836 POST,
1837 GET,
1838 DELETE,
1839}
1840
1841#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1842#[derive(Deserialize, Serialize, Debug, Clone)]
1843pub enum ActionTriggerModelOps {
1844 Insert,
1845 Update,
1846 Delete,
1847}
1848
1849#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1851#[derive(Deserialize, Serialize, Debug, Clone)]
1852pub enum ActionTrigger {
1853 Ordinary,
1855 Json {
1857 route: String,
1859 method: HttpMethod,
1861 },
1862 Form {
1864 route: String,
1866 method: HttpMethod,
1868 redirect: String,
1873 },
1874 Login,
1876 Registration,
1878 Content { name: String },
1880 }
1893
1894#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1895#[derive(Deserialize, Serialize, Debug, Clone)]
1896pub enum ActionFfiVersion {
1897 V1,
1898}
1899
1900#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1902#[derive(Deserialize, Serialize, Debug, Clone)]
1903pub enum ActionFfiSerialization {
1904 FlexBufferVector,
1905}
1906
1907#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1908#[derive(Deserialize, Serialize, Debug, Clone)]
1909pub struct ActionFfi {
1910 pub version: ActionFfiVersion,
1911 pub serialization: ActionFfiSerialization,
1912}
1913
1914#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1916#[derive(Deserialize, Serialize, Debug, Clone)]
1917pub struct ActionConfig {
1918 pub ffi: ActionFfi,
1920 pub idx: u8,
1922 pub name: String,
1924 pub lang: ActionLang,
1926 #[serde(skip_serializing_if = "Option::is_none")]
1928 #[serde(default)]
1929 pub dir_path: Option<String>,
1930 #[serde(skip_serializing_if = "Option::is_none")]
1933 #[serde(default)]
1934 pub protected: Option<Check>,
1935 #[serde(skip_serializing_if = "Option::is_none")]
1938 #[serde(default)]
1939 pub transactional: Option<bool>,
1940 pub access: Vec<ActionAccessPermission>,
1942 pub accepts: Kind,
1944 pub returns: Kind,
1946 pub triggered_by: Vec<ActionTrigger>,
1949
1950 #[serde(skip_serializing_if = "Option::is_none")]
1954 #[serde(default)]
1955 pub timeout: Option<u16>,
1956
1957 #[serde(skip_serializing_if = "Option::is_none")]
1958 #[serde(default)]
1959 pub cors: Option<HttpCors>,
1960
1961 #[serde(skip_serializing_if = "Option::is_none")]
1962 #[serde(default)]
1963 pub wasm_opt: Option<WasmOpt>,
1964
1965 #[serde(skip_serializing_if = "Option::is_none")]
1970 #[serde(default)]
1971 pub privileged: Option<bool>,
1972
1973 #[serde(skip_serializing_if = "Option::is_none")]
1977 #[serde(default)]
1978 pub variables: Option<Vec<String>>,
1979}
1980
1981#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1982#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1983pub struct ErrorConfig {
1984 #[serde(skip_serializing_if = "Option::is_none")]
1988 #[serde(default)]
1989 pub template: Option<String>,
1990
1991 #[serde(skip_serializing_if = "Option::is_none")]
1995 #[serde(default)]
1996 pub asset: Option<String>,
1997}
1998
1999#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2000#[derive(Deserialize, Serialize, Debug, Clone)]
2001pub enum RuntimeMode {
2002 Shared,
2005 SingleThreaded,
2008 MultiThreaded,
2011}
2012
2013#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2014#[derive(Deserialize, Serialize, Debug, Clone)]
2015pub enum HttpCorsAllowHeaders {
2016 Any,
2017 Headers(Vec<String>),
2019}
2020
2021#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2022#[derive(Deserialize, Serialize, Debug, Clone)]
2023pub enum HttpCorsExposeHeaders {
2024 Any,
2025 Headers(Vec<String>),
2027}
2028
2029#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2030#[derive(Deserialize, Serialize, Debug, Clone)]
2031pub enum HttpCorsAllowMethods {
2032 Any,
2033 Methods(Vec<String>),
2035}
2036
2037#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2038#[derive(Deserialize, Serialize, Debug, Clone)]
2039pub enum HttpCorsAllowOrigin {
2040 Any,
2041 Origins(Vec<String>),
2043}
2044
2045#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2046#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2047pub struct HttpCors {
2048 pub allow_credentials: Option<bool>,
2049
2050 pub allow_headers: Option<HttpCorsAllowHeaders>,
2051
2052 pub max_age: Option<u32>,
2054
2055 pub allow_methods: Option<HttpCorsAllowMethods>,
2056
2057 pub allow_origin: Option<HttpCorsAllowOrigin>,
2058
2059 pub expose_headers: Option<HttpCorsExposeHeaders>,
2060
2061 pub allow_private_network: Option<bool>,
2062}
2063
2064impl HttpCors {
2065 #[must_use]
2066 pub fn overwrite(&self, base: &Self) -> Self {
2067 Self {
2068 allow_credentials: if self.allow_credentials.is_none() {
2069 base.allow_credentials
2070 } else {
2071 self.allow_credentials
2072 },
2073 allow_headers: if self.allow_headers.is_none() {
2074 base.allow_headers.clone()
2075 } else {
2076 self.allow_headers.clone()
2077 },
2078 max_age: if self.max_age.is_none() {
2079 base.max_age
2080 } else {
2081 self.max_age
2082 },
2083 allow_methods: if self.allow_methods.is_none() {
2084 base.allow_methods.clone()
2085 } else {
2086 self.allow_methods.clone()
2087 },
2088 allow_origin: if self.allow_origin.is_none() {
2089 base.allow_origin.clone()
2090 } else {
2091 self.allow_origin.clone()
2092 },
2093 expose_headers: if self.expose_headers.is_none() {
2094 base.expose_headers.clone()
2095 } else {
2096 self.expose_headers.clone()
2097 },
2098 allow_private_network: if self.allow_private_network.is_none() {
2099 base.allow_private_network
2100 } else {
2101 self.allow_private_network
2102 },
2103 }
2104 }
2105}
2106
2107#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2108#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2109pub struct HttpCsp {
2110 #[serde(skip_serializing_if = "Option::is_none")]
2112 #[serde(default)]
2113 pub default_src: Option<String>,
2114
2115 #[serde(skip_serializing_if = "Option::is_none")]
2118 #[serde(default)]
2119 pub script_src: Option<String>,
2120
2121 #[serde(skip_serializing_if = "Option::is_none")]
2124 #[serde(default)]
2125 pub style_src: Option<String>,
2126
2127 #[serde(skip_serializing_if = "Option::is_none")]
2131 #[serde(default)]
2132 pub font_src: Option<String>,
2133
2134 #[serde(skip_serializing_if = "Option::is_none")]
2138 #[serde(default)]
2139 pub img_src: Option<String>,
2140
2141 #[serde(skip_serializing_if = "Option::is_none")]
2145 #[serde(default)]
2146 pub frame_src: Option<String>,
2147
2148 #[serde(skip_serializing_if = "Option::is_none")]
2150 #[serde(default)]
2151 pub include_inline_hashes: Option<bool>,
2152}
2153
2154impl HttpCsp {
2155 #[must_use]
2156 #[allow(clippy::too_many_lines)]
2157 pub fn build_string(
2158 &self,
2159 base: &Self,
2160 inline_style_hashes: Option<Vec<String>>,
2161 inline_script_hashes: Option<Vec<String>>,
2162 script_urls: Option<Vec<String>>,
2163 secure: bool,
2164 has_wasm: bool,
2165 ) -> String {
2166 let mut out = String::new();
2167
2168 let include_inline_hashes = self
2169 .include_inline_hashes
2170 .unwrap_or(base.include_inline_hashes.unwrap_or(true));
2171
2172 let default_src = self
2173 .default_src
2174 .clone()
2175 .unwrap_or(base.default_src.clone().unwrap_or("'self'".to_string()));
2176
2177 if !default_src.is_empty() {
2178 out.push_str("default-src ");
2179 out.push_str(default_src.as_str());
2180 out.push_str("; ");
2181 }
2182
2183 let mut script_src = self
2184 .script_src
2185 .clone()
2186 .unwrap_or(base.script_src.clone().unwrap_or_default());
2187
2188 if include_inline_hashes
2189 && let Some(script_hashes) = inline_script_hashes
2190 && !script_hashes.is_empty()
2191 {
2192 if script_src.is_empty() {
2193 script_src.push_str("'self'");
2194 if has_wasm {
2195 script_src.push_str(" 'wasm-unsafe-eval'");
2196 }
2197 }
2198
2199 for hash in script_hashes {
2200 script_src.push_str(" '");
2201 script_src.push_str(hash.as_str());
2202 script_src.push('\'');
2203 }
2204 }
2205
2206 if let Some(script_urls) = script_urls {
2207 if script_src.is_empty() {
2208 script_src.push_str("'self'");
2209 }
2210
2211 for script_url in script_urls {
2212 script_src.push(' ');
2213 script_src.push_str(&script_url);
2214 }
2215 }
2216
2217 if !script_src.is_empty() {
2218 out.push_str("script-src ");
2219 out.push_str(script_src.as_str());
2220 out.push_str("; ");
2221 }
2222
2223 let mut style_src = self
2224 .style_src
2225 .clone()
2226 .unwrap_or(base.style_src.clone().unwrap_or_default());
2227
2228 if include_inline_hashes
2229 && let Some(style_hashes) = inline_style_hashes
2230 && !style_hashes.is_empty()
2231 {
2232 if style_src.is_empty() {
2233 style_src.push_str("'self'");
2234 }
2235
2236 for hash in style_hashes {
2237 style_src.push_str(" '");
2238 style_src.push_str(hash.as_str());
2239 style_src.push('\'');
2240 }
2241 }
2242
2243 if !style_src.is_empty() {
2244 out.push_str("style-src ");
2245 out.push_str(style_src.as_str());
2246 out.push_str("; ");
2247 }
2248
2249 let font_src = self
2250 .font_src
2251 .clone()
2252 .unwrap_or(base.font_src.clone().unwrap_or_default());
2253 if !font_src.is_empty() {
2254 out.push_str("font-src ");
2255 out.push_str(font_src.as_str());
2256 out.push_str("; ");
2257 }
2258
2259 let img_src = self
2260 .img_src
2261 .clone()
2262 .unwrap_or(base.img_src.clone().unwrap_or_default());
2263 if !img_src.is_empty() {
2264 out.push_str("img-src ");
2265 out.push_str(img_src.as_str());
2266 out.push_str("; ");
2267 }
2268
2269 let frame_src = self
2270 .frame_src
2271 .clone()
2272 .unwrap_or(base.frame_src.clone().unwrap_or_default());
2273 if !frame_src.is_empty() {
2274 out.push_str("frame-src ");
2275 out.push_str(frame_src.as_str());
2276 out.push_str("; ");
2277 }
2278
2279 if secure {
2280 out.push_str("upgrade-insecure-requests; ");
2281 }
2282
2283 out.push_str("report-to csp");
2284
2285 out = out.trim().to_string();
2286 out
2287 }
2288}
2289
2290#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2291#[derive(Deserialize, Serialize, Debug, Clone)]
2292pub struct LifecycleBeforeAfterScripts {
2293 #[serde(skip_serializing_if = "Option::is_none")]
2294 #[serde(default)]
2295 pub before: Option<Vec<Vec<String>>>,
2296 #[serde(skip_serializing_if = "Option::is_none")]
2297 #[serde(default)]
2298 pub after: Option<Vec<Vec<String>>>,
2299}
2300
2301#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2302#[derive(Deserialize, Serialize, Debug, Clone)]
2303pub struct TopLevelLifecycle {
2304 pub before_all: Option<Vec<Vec<String>>>,
2306
2307 #[serde(skip_serializing_if = "Option::is_none")]
2309 #[serde(default)]
2310 pub build: Option<LifecycleBeforeAfterScripts>,
2311}
2312
2313#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2314#[derive(Deserialize, Serialize, Debug, Clone)]
2315pub enum RedirectMethod {
2316 Temporary,
2318 Permanent,
2320}
2321
2322impl Display for RedirectMethod {
2323 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2324 match self {
2325 Self::Temporary => write!(f, "TEMPORARY"),
2326 Self::Permanent => write!(f, "PERMANENT"),
2327 }
2328 }
2329}
2330
2331#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2332#[derive(Deserialize, Serialize, Debug, Clone)]
2333pub struct HostRedirect {
2334 pub from: String,
2336 pub to: String,
2338 pub method: RedirectMethod,
2340}
2341
2342#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2343#[derive(Deserialize, Serialize, Debug, Clone)]
2344pub struct RouteRedirect {
2345 pub condition: String,
2347 pub rule: (String, String),
2350 pub method: RedirectMethod,
2352}
2353
2354#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2355#[derive(Deserialize, Serialize, Debug, Clone)]
2356pub struct Redirects {
2357 #[serde(skip_serializing_if = "Option::is_none")]
2359 #[serde(default)]
2360 pub host: Option<Vec<HostRedirect>>,
2361 #[serde(skip_serializing_if = "Option::is_none")]
2363 #[serde(default)]
2364 pub route: Option<Vec<RouteRedirect>>,
2365}
2366
2367#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2369#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2370pub struct OrdinaryConfig {
2371 #[serde(skip_serializing_if = "Option::is_none")]
2372 #[serde(default)]
2373 pub lifecycle: Option<TopLevelLifecycle>,
2374
2375 pub domain: String,
2378
2379 #[serde(skip_serializing_if = "Option::is_none")]
2385 #[serde(default)]
2386 pub cnames: Option<Vec<String>>,
2387
2388 #[serde(skip_serializing_if = "Option::is_none")]
2398 #[serde(default)]
2399 pub canonical: Option<String>,
2400
2401 #[serde(skip_serializing_if = "Option::is_none")]
2402 #[serde(default)]
2403 pub redirects: Option<Redirects>,
2404
2405 #[serde(skip_serializing_if = "Option::is_none")]
2408 #[serde(default)]
2409 pub contacts: Option<Vec<String>>,
2410
2411 #[serde(skip_serializing_if = "Option::is_none")]
2413 #[serde(default)]
2414 pub hide_contacts: Option<bool>,
2415
2416 pub version: String,
2418
2419 #[serde(skip_serializing_if = "Option::is_none")]
2421 #[serde(default = "OrdinaryConfig::default_storage_size")]
2422 pub storage_size: Option<u64>,
2423
2424 #[serde(skip_serializing_if = "Option::is_none")]
2428 #[serde(default)]
2429 pub default_timeout: Option<u16>,
2430
2431 #[serde(skip_serializing_if = "Option::is_none")]
2441 #[serde(default)]
2442 pub csp: Option<HttpCsp>,
2443
2444 #[serde(skip_serializing_if = "Option::is_none")]
2445 #[serde(default)]
2446 pub cors: Option<HttpCors>,
2447
2448 #[serde(skip_serializing_if = "Option::is_none")]
2452 #[serde(default)]
2453 pub runtime: Option<RuntimeMode>,
2454
2455 #[serde(skip_serializing_if = "Option::is_none")]
2461 #[serde(default)]
2462 pub hide_schema: Option<bool>,
2463
2464 #[serde(skip_serializing_if = "Option::is_none")]
2466 #[serde(default)]
2467 pub client_rendering: Option<bool>,
2468
2469 #[serde(skip_serializing_if = "Option::is_none")]
2471 #[serde(default)]
2472 pub obfuscation: Option<bool>,
2473
2474 #[serde(skip_serializing_if = "Option::is_none")]
2476 #[serde(default)]
2477 pub client_events: Option<bool>,
2478
2479 #[serde(skip_serializing_if = "Option::is_none")]
2481 #[serde(default)]
2482 pub port: Option<u16>,
2483 #[serde(skip_serializing_if = "Option::is_none")]
2486 #[serde(default)]
2487 pub redirect_port: Option<u16>,
2488
2489 #[serde(skip_serializing_if = "Option::is_none")]
2490 #[serde(default)]
2491 pub logging: Option<LoggingConfig>,
2492 #[serde(skip_serializing_if = "Option::is_none")]
2497 #[serde(default)]
2498 pub error: Option<ErrorConfig>,
2499 #[serde(skip_serializing_if = "Option::is_none")]
2501 #[serde(default)]
2502 pub auth: Option<AuthConfig>,
2503 #[serde(skip_serializing_if = "Option::is_none")]
2505 #[serde(default)]
2506 pub globals: Option<Vec<Global>>,
2507 #[serde(skip_serializing_if = "Option::is_none")]
2509 #[serde(default)]
2510 pub secrets: Option<Vec<Secret>>,
2511 #[serde(skip_serializing_if = "Option::is_none")]
2514 #[serde(default)]
2515 pub flags: Option<Vec<Flag>>,
2516 #[serde(skip_serializing_if = "Option::is_none")]
2521 #[serde(default)]
2522 pub content: Option<Content>,
2523 #[serde(skip_serializing_if = "Option::is_none")]
2525 #[serde(default)]
2526 pub models: Option<Vec<ModelConfig>>,
2527 #[serde(skip_serializing_if = "Option::is_none")]
2530 #[serde(default)]
2531 pub integrations: Option<Vec<IntegrationConfig>>,
2532 #[serde(skip_serializing_if = "Option::is_none")]
2535 #[serde(default)]
2536 pub actions: Option<Vec<ActionConfig>>,
2537 #[serde(skip_serializing_if = "Option::is_none")]
2541 #[serde(default)]
2542 pub assets: Option<AssetsConfig>,
2543 #[serde(skip_serializing_if = "Option::is_none")]
2545 #[serde(default)]
2546 pub fragments: Option<FragmentsConfig>,
2547 #[serde(skip_serializing_if = "Option::is_none")]
2560 #[serde(default)]
2561 pub templates: Option<Vec<TemplateConfig>>,
2562}
2563
2564impl OrdinaryConfig {
2565 pub fn get(proj_path: &str) -> anyhow::Result<OrdinaryConfig> {
2567 let path = Path::new(proj_path).join("ordinary.json");
2568 let config_json = fs_err::read_to_string(&path)?;
2569
2570 let mut config = match serde_json::from_str::<OrdinaryConfig>(config_json.as_str()) {
2571 Ok(config) => config,
2572 Err(err) => bail!("{}: {err}", path.display()),
2573 };
2574
2575 config.load_internal_compression();
2576
2577 Ok(config)
2578 }
2579
2580 pub fn load_internal_compression(&mut self) {
2581 if let Some(assets) = self.assets.as_mut()
2582 && let Some(precompression) = &assets.precompression
2583 {
2584 assets.internal_precompression = Some(precompression.get_list());
2585 }
2586
2587 if let Some(templates) = self.templates.as_mut() {
2588 for template in templates {
2589 if let Some(cache) = template.cache.as_mut()
2590 && let Some(stored) = cache.stored.as_mut()
2591 && let Some(compression) = &stored.compression
2592 {
2593 stored.internal_compression = Some(compression.get_list());
2594 }
2595 }
2596 }
2597 }
2598
2599 pub fn for_send(&self) -> anyhow::Result<OrdinaryConfig> {
2602 let mut config = self.clone();
2603
2604 config.lifecycle = None;
2605
2606 if let Some(assets) = config.assets.as_mut() {
2607 assets.dir_path = None;
2608 }
2609
2610 if let Some(content) = config.content.as_mut() {
2611 content.update = None;
2612
2613 for def in &mut content.definitions {
2614 def.lifecycle = None;
2615 }
2616 }
2617
2618 if let Some(templates) = config.templates.as_mut() {
2619 for template in templates {
2620 template.path = None;
2621 template.wasm_opt = None;
2622 template.minify = None;
2623 }
2624 }
2625
2626 if let Some(actions) = config.actions.as_mut() {
2627 for action in actions {
2628 action.wasm_opt = None;
2629 action.dir_path = None;
2630 }
2631 }
2632
2633 Ok(config)
2634 }
2635
2636 #[instrument(skip_all, err, level = "debug")]
2639 pub fn validate(&self) -> anyhow::Result<()> {
2640 validate(self)
2641 }
2642
2643 #[must_use]
2645 #[allow(clippy::unnecessary_wraps)]
2646 pub fn default_storage_size() -> Option<u64> {
2647 Some(5_000_000)
2648 }
2649 #[must_use]
2652 pub fn has_ordinary_actions(&self) -> bool {
2653 if let Some(actions) = &self.actions {
2654 actions
2655 .iter()
2656 .find(|a| {
2657 for trigger in &a.triggered_by {
2658 if let ActionTrigger::Ordinary = trigger {
2659 return true;
2660 }
2661 }
2662
2663 false
2664 })
2665 .is_some()
2666 } else {
2667 false
2668 }
2669 }
2670
2671 #[allow(clippy::too_many_lines)]
2676 pub fn check_config_against_limits(
2677 &self,
2678 limits: &OrdinaryApiLimits,
2679 privileged_domains: &HashSet<String>,
2680 ) -> anyhow::Result<()> {
2681 if privileged_domains.contains(&self.domain) {
2682 return Ok(());
2683 }
2684
2685 if let Some(canonical) = &self.canonical {
2686 let mut is_valid = false;
2687
2688 if canonical == &self.domain {
2689 is_valid = true;
2690 }
2691
2692 if let Some(cnames) = &self.cnames {
2693 for cname in cnames {
2694 if cname == canonical {
2695 is_valid = true;
2696 break;
2697 }
2698 }
2699 }
2700
2701 if !is_valid {
2702 bail!("canonical: {canonical}, is not in 'domain' or 'cnames'");
2703 }
2704 }
2705
2706 if let Some(default_timeout) = self.default_timeout
2707 && default_timeout > limits.max_default_timeout
2708 {
2709 bail!(
2710 "default timeout {} greater than limit {}",
2711 default_timeout,
2712 limits.max_default_timeout
2713 );
2714 }
2715
2716 if let Some(templates) = &self.templates {
2717 if templates.len() > limits.template.count as usize {
2718 bail!(
2719 "template count {} greater than limit {}",
2720 templates.len(),
2721 limits.template.count
2722 );
2723 }
2724
2725 for template in templates {
2726 if let Some(timeout) = template.timeout
2727 && timeout > limits.template.max_timeout
2728 {
2729 bail!(
2730 "template timeout {} greater than limit {} for {}",
2731 timeout,
2732 limits.template.max_timeout,
2733 template.name
2734 );
2735 }
2736
2737 if let Some(cache) = &template.cache
2738 && let Some(stored_cache) = &cache.stored
2739 {
2740 if let Some(max_size) = stored_cache.max_size
2741 && (max_size < limits.storage.cache.max_size_range.0
2742 || max_size > limits.storage.cache.max_size_range.1)
2743 {
2744 bail!(
2745 "template cache max_size {} is not within range [{}, {}] for {}",
2746 max_size,
2747 limits.storage.cache.max_size_range.0,
2748 limits.storage.cache.max_size_range.1,
2749 template.name
2750 );
2751 }
2752
2753 if let Some(max_count) = stored_cache.max_count
2754 && (max_count < limits.storage.cache.max_count_range.0
2755 || max_count > limits.storage.cache.max_count_range.1)
2756 {
2757 bail!(
2758 "template cache max_count {} is not within range [{}, {}] for {}",
2759 max_count,
2760 limits.storage.cache.max_count_range.0,
2761 limits.storage.cache.max_count_range.1,
2762 template.name
2763 );
2764 }
2765
2766 if let Some((clean_interval_min, clean_interval_max)) =
2767 stored_cache.clean_interval
2768 {
2769 if clean_interval_min < limits.storage.cache.clean_interval_ranges.0.0
2770 || clean_interval_min > limits.storage.cache.clean_interval_ranges.0.1
2771 {
2772 bail!(
2773 "template cache clean_interval min {} is not within range [{}, {}] for {}",
2774 clean_interval_min,
2775 limits.storage.cache.clean_interval_ranges.0.0,
2776 limits.storage.cache.clean_interval_ranges.0.1,
2777 template.name
2778 );
2779 }
2780
2781 if clean_interval_max < limits.storage.cache.clean_interval_ranges.1.0
2782 || clean_interval_max > limits.storage.cache.clean_interval_ranges.1.1
2783 {
2784 bail!(
2785 "template cache clean_interval min {} is not within range [{}, {}] for {}",
2786 clean_interval_max,
2787 limits.storage.cache.clean_interval_ranges.1.0,
2788 limits.storage.cache.clean_interval_ranges.1.1,
2789 template.name
2790 );
2791 }
2792 }
2793 }
2794 }
2795 }
2796
2797 if let Some(integrations) = &self.integrations {
2798 if integrations.len() > limits.integration.count as usize {
2799 bail!(
2800 "integration count {} greater than limit {}",
2801 integrations.len(),
2802 limits.integration.count
2803 );
2804 }
2805
2806 for integration in integrations {
2807 if let Some(timeout) = integration.timeout
2808 && timeout > limits.integration.max_timeout
2809 {
2810 bail!(
2811 "integration timeout {} greater than limit {} for {}",
2812 timeout,
2813 limits.integration.max_timeout,
2814 integration.name,
2815 );
2816 }
2817 }
2818 }
2819
2820 if let Some(actions) = &self.actions {
2821 if actions.len() > limits.action.count as usize {
2822 bail!(
2823 "action count {} greater than limit {}",
2824 actions.len(),
2825 limits.action.count
2826 );
2827 }
2828
2829 for action in actions {
2830 if action.privileged == Some(true) {
2831 bail!("action {} is not under a privileged domain", action.name);
2832 }
2833
2834 if let Some(timeout) = action.timeout
2835 && timeout > limits.action.max_timeout
2836 {
2837 bail!(
2838 "action timeout {} greater than limit {} for {}",
2839 timeout,
2840 limits.action.max_timeout,
2841 action.name
2842 );
2843 }
2844 }
2845 }
2846
2847 if let Some(storage_size) = self.storage_size
2848 && storage_size > limits.storage.max_app_storage
2849 {
2850 bail!("storage size is greater than limit");
2851 }
2852
2853 if let Some(content) = &self.content {
2854 if content.definitions.len() > limits.storage.content.max_content_definitions as usize {
2855 bail!(
2856 "content definition count {} greater than limit {}",
2857 content.definitions.len(),
2858 limits.storage.content.max_content_definitions
2859 );
2860 }
2861
2862 for content_def in &content.definitions {
2863 if content_def.fields.len() > limits.storage.content.max_content_fields as usize {
2864 bail!(
2865 "content field count {} greater than limit {} for {}",
2866 content_def.fields.len(),
2867 limits.storage.content.max_content_fields,
2868 content_def.name,
2869 );
2870 }
2871
2872 for field in &content_def.fields {
2873 if field.searchable == Some(true) && !limits.storage.content.search_enabled {
2874 bail!(
2875 "content field cannot be 'searchable' for field {} on definition {}",
2876 field.name,
2877 content_def.name,
2878 );
2879 }
2880 }
2881 }
2882 }
2883
2884 if let Some(models) = &self.models {
2885 if models.len() > limits.storage.model.max_model_definitions as usize {
2886 bail!(
2887 "model definition count {} greater than limit {}",
2888 models.len(),
2889 limits.storage.model.max_model_definitions
2890 );
2891 }
2892
2893 for model in models {
2894 if model.fields.len() > limits.storage.model.max_model_fields as usize {
2895 bail!(
2896 "model field count {} greater than limit {} for {}",
2897 model.fields.len(),
2898 limits.storage.content.max_content_fields,
2899 model.name,
2900 );
2901 }
2902
2903 for field in &model.fields {
2904 if field.searchable == Some(true) && !limits.storage.content.search_enabled {
2905 bail!(
2906 "content field cannot be 'searchable' for field {} on definition {}",
2907 field.name,
2908 model.name,
2909 );
2910 }
2911 }
2912 }
2913 }
2914
2915 if let Some(secrets) = &self.secrets
2916 && secrets.len() > limits.storage.secrets.max_count as usize
2917 {
2918 bail!(
2919 "secrets len {} exceeds max count limit {}",
2920 secrets.len(),
2921 limits.storage.secrets.max_count
2922 );
2923 }
2924
2925 Ok(())
2926 }
2927
2928 pub fn exec_lifecycle_script(
2929 proj_path: &Path,
2930 argument: &Option<String>,
2931 name: &str,
2932 when: &str,
2933 scripts: &Vec<Vec<String>>,
2934 ) -> anyhow::Result<()> {
2935 let span = tracing::info_span!("lifecycle", %when, %name);
2936
2937 span.in_scope(|| {
2938 let curr_dir = env::current_dir()?;
2939 env::set_current_dir(proj_path)?;
2940
2941 for script in scripts {
2942 let mut script_iter = script.iter();
2943
2944 if let Some(command) = script_iter.nth(0) {
2945 let mut command_str = command.clone();
2946 let mut command = Command::new(command);
2947
2948 for arg in script_iter {
2949 write!(command_str, " {arg}")?;
2950 command.arg(arg);
2951 }
2952
2953 tracing::info!(cmd = %command_str, "exec");
2954
2955 let output = match &argument {
2956 Some(arg) => command.arg(arg).output()?,
2957 None => command.output()?,
2958 };
2959
2960 if output.status.success() {
2961 tracing::info!("success");
2962 } else {
2963 let stderr = str::from_utf8(&output.stderr)?;
2964 let stdout = str::from_utf8(&output.stdout)?;
2965
2966 tracing::error!(%stderr, %stdout, "failed");
2967 bail!(stderr.to_string());
2968 }
2969 }
2970 }
2971
2972 env::set_current_dir(curr_dir)?;
2973
2974 anyhow::Ok(())
2975 })
2976 }
2977}