1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![doc = include_str!("../docs/config-reference.md")]
4#![warn(clippy::all, clippy::pedantic)]
5#![allow(clippy::missing_errors_doc)]
6
7mod limits;
12mod validate;
13
14pub use crate::validate::DOMAIN_REGEX;
15use crate::validate::validate;
16use std::collections::BTreeSet;
17
18use crate::limits::check_config_against_limits;
19use anyhow::bail;
20use arrayvec::ArrayVec;
21use hashbrown::{HashMap, HashSet};
22use ordinary_types::{Field, Kind};
23use serde::{Deserialize, Serialize};
24use std::env;
25use std::fmt::{Display, Formatter, Write};
26use std::path::Path;
27use std::process::Command;
28use tracing::instrument;
29
30fn default_env_name() -> String {
31 "development".to_string()
32}
33
34#[derive(Deserialize, Serialize, Clone)]
35pub struct OrdinaryApiConfig {
36 pub domain: String,
37 pub contacts: Vec<String>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 #[serde(default)]
40 pub public_dns_ip: Option<[u8; 4]>,
41 #[serde(default = "default_env_name")]
42 pub env_name: String,
43 #[serde(default)]
44 pub limits: OrdinaryApiLimits,
45}
46
47#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
48#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
49#[derive(Deserialize, Serialize, Debug, Clone)]
50pub struct AssetsLimits {
51 pub allowed_extensions: Vec<String>,
84 pub max_store_size: u64,
87 pub max_asset_size: u64,
89}
90
91impl Default for AssetsLimits {
92 fn default() -> Self {
93 Self {
94 allowed_extensions: vec![
95 "otf".into(),
96 "ttf".into(),
97 "woff".into(),
98 "woff2".into(),
99 "txt".into(),
100 "xml".into(),
101 "html".into(),
102 "css".into(),
103 "css.map".into(),
104 "csv".into(),
105 "js".into(),
106 "png".into(),
107 "apng".into(),
108 "gif".into(),
109 "svg".into(),
110 "jpg".into(),
111 "jpeg".into(),
112 "bmp".into(),
113 "tif".into(),
114 "tiff".into(),
115 "webp".into(),
116 "avif".into(),
117 "ico".into(),
118 "pdf".into(),
119 "json".into(),
120 "wasm".into(),
121 ],
122 max_store_size: 100_000_000,
123 max_asset_size: 1_500_000,
124 }
125 }
126}
127
128#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
129#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
130#[derive(Deserialize, Serialize, Debug, Clone)]
131pub struct ArtifactLimits {
132 pub max_store_size: u64,
133 pub max_artifact_size: u64,
134}
135
136impl Default for ArtifactLimits {
137 fn default() -> Self {
138 Self {
139 max_store_size: 10_000_000,
140 max_artifact_size: 500_000,
141 }
142 }
143}
144
145#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
146#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
147#[derive(Deserialize, Serialize, Debug, Clone)]
148pub struct CacheLimits {
149 pub max_size_range: (u64, u64),
150 pub max_count_range: (usize, usize),
151
152 pub clean_interval_ranges: ((u64, u64), (u64, u64)),
153}
154
155impl Default for CacheLimits {
156 fn default() -> Self {
157 Self {
158 max_size_range: (1_000_000, 10_000_000),
159 max_count_range: (100, 500),
160
161 clean_interval_ranges: ((5, 10), (15, 20)),
162 }
163 }
164}
165
166#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
167#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
168#[derive(Deserialize, Serialize, Debug, Clone)]
169pub struct ContentLimits {
170 pub search_enabled: bool,
171
172 pub max_content_definitions: u8,
173 pub max_content_fields: u8,
174
175 pub max_store_size: u64,
176 pub max_object_size: u64,
177 pub max_field_size: u64,
178}
179
180impl Default for ContentLimits {
181 fn default() -> Self {
182 Self {
183 search_enabled: true,
184
185 max_content_definitions: 255,
186 max_content_fields: 255,
187
188 max_store_size: 10_000_000,
189 max_object_size: 400_000,
190 max_field_size: 200_000,
191 }
192 }
193}
194
195#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
196#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
197#[derive(Deserialize, Serialize, Debug, Clone)]
198pub struct ModelLimits {
199 pub search_enabled: bool,
200
201 pub max_model_definitions: u8,
202 pub max_model_fields: u8,
203
204 pub max_item_size: u64,
205 pub max_field_size: u64,
206}
207
208impl Default for ModelLimits {
209 fn default() -> Self {
210 Self {
211 search_enabled: true,
212
213 max_model_definitions: 255,
214 max_model_fields: 255,
215
216 max_item_size: 400_000,
217 max_field_size: 100_000,
218 }
219 }
220}
221
222#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
223#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
224#[derive(Deserialize, Serialize, Debug, Clone)]
225pub struct SecretsLimits {
226 pub max_count: u8,
227 pub max_size: u64,
228}
229
230impl Default for SecretsLimits {
231 fn default() -> Self {
232 Self {
233 max_count: 225,
234 max_size: 2_000,
235 }
236 }
237}
238
239#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
240#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
241#[derive(Deserialize, Serialize, Debug, Clone)]
242pub struct StorageLimits {
243 pub max_storage: u64,
249 pub max_app_storage: u64,
255
256 pub assets: AssetsLimits,
257 pub artifact: ArtifactLimits,
258 pub cache: CacheLimits,
259 pub content: ContentLimits,
260 pub model: ModelLimits,
261 pub secrets: SecretsLimits,
262}
263
264impl Default for StorageLimits {
265 fn default() -> Self {
266 Self {
267 max_storage: 20_000_000_000,
268 max_app_storage: 50_000_000,
269
270 assets: AssetsLimits::default(),
271 artifact: ArtifactLimits::default(),
272 cache: CacheLimits::default(),
273 content: ContentLimits::default(),
274 model: ModelLimits::default(),
275 secrets: SecretsLimits::default(),
276 }
277 }
278}
279
280#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
281#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
282#[derive(Deserialize, Serialize, Debug, Clone, Default)]
283pub struct MonitorLimits {}
284
285#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
286#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
287#[derive(Deserialize, Serialize, Debug, Clone)]
288pub struct IntegrationLimits {
289 pub count: u8,
291
292 pub max_timeout: u16,
296}
297
298impl Default for IntegrationLimits {
299 fn default() -> Self {
300 Self {
301 count: 255,
302 max_timeout: 10,
303 }
304 }
305}
306
307#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
308#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
309#[derive(Deserialize, Serialize, Debug, Clone)]
310pub struct ActionLimits {
311 pub count: u8,
313
314 pub max_timeout: u16,
318}
319
320impl Default for ActionLimits {
321 fn default() -> Self {
322 Self {
323 count: 255,
324 max_timeout: 10,
325 }
326 }
327}
328
329#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
330#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
331#[derive(Deserialize, Serialize, Debug, Clone, Default)]
332pub struct AuthLimits {}
333
334#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
335#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
336#[derive(Deserialize, Serialize, Debug, Clone)]
337pub struct ProxyLimits {
338 pub count: u8,
340 pub disallowed_targets: Vec<String>,
343}
344
345impl Default for ProxyLimits {
346 fn default() -> Self {
347 Self {
348 count: 255,
349 disallowed_targets: vec![],
350 }
351 }
352}
353
354#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
355#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
356#[derive(Deserialize, Serialize, Debug, Clone)]
357pub struct MiddlewareLimits {
358 pub count: u16,
360 pub disallowed_endpoints: Vec<String>,
363}
364
365impl Default for MiddlewareLimits {
366 fn default() -> Self {
367 Self {
368 count: u16::MAX,
369 disallowed_endpoints: vec![],
370 }
371 }
372}
373
374#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
375#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
376#[derive(Deserialize, Serialize, Debug, Clone)]
377pub struct TemplateLimits {
378 pub count: u8,
380
381 pub max_timeout: u16,
385}
386
387impl Default for TemplateLimits {
388 fn default() -> Self {
389 Self {
390 count: 255,
391 max_timeout: 10,
392 }
393 }
394}
395
396#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
397#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
398#[derive(Deserialize, Serialize, Debug, Clone)]
399pub struct OrdinaryApiLimits {
400 pub app_domains: Vec<String>,
401 pub privileged_domains: Vec<String>,
402
403 pub max_default_timeout: u16,
407
408 pub proxy: ProxyLimits,
409 pub middleware: MiddlewareLimits,
410
411 pub action: ActionLimits,
412 pub auth: AuthLimits,
413 pub integration: IntegrationLimits,
414 pub monitor: MonitorLimits,
415 pub storage: StorageLimits,
416 pub template: TemplateLimits,
417}
418
419impl Default for OrdinaryApiLimits {
420 fn default() -> Self {
421 Self {
422 app_domains: vec![],
423 privileged_domains: vec![],
424
425 max_default_timeout: 10,
426
427 proxy: ProxyLimits::default(),
428 middleware: MiddlewareLimits::default(),
429
430 action: ActionLimits::default(),
431 auth: AuthLimits::default(),
432 integration: IntegrationLimits::default(),
433 monitor: MonitorLimits::default(),
434 storage: StorageLimits::default(),
435 template: TemplateLimits::default(),
436 }
437 }
438}
439
440#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
441#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
442#[derive(Deserialize, Serialize, Debug, Clone)]
443pub struct ClientLoggingConfig {
444 #[serde(skip_serializing_if = "Option::is_none")]
446 #[serde(default)]
447 min_delay: Option<u32>,
448 #[serde(skip_serializing_if = "Option::is_none")]
450 #[serde(default)]
451 max_delay: Option<u32>,
452 #[serde(skip_serializing_if = "Option::is_none")]
455 #[serde(default)]
456 max_buffer: Option<u16>,
457 #[serde(skip_serializing_if = "Option::is_none")]
459 #[serde(default)]
460 max_batch: Option<u16>,
461}
462
463#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
464#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
465#[derive(Deserialize, Serialize, Debug, Clone)]
466pub enum RedactedHashAlg {
467 Blake2,
468 Blake3,
469}
470
471#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
472#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
473#[derive(Deserialize, Serialize, Debug, Clone)]
474pub struct ServerLoggingConfig {
475 #[serde(skip_serializing_if = "Option::is_none")]
476 #[serde(default)]
477 pub ips: Option<bool>,
478 #[serde(skip_serializing_if = "Option::is_none")]
479 #[serde(default)]
480 pub headers: Option<bool>,
481 #[serde(skip_serializing_if = "Option::is_none")]
482 #[serde(default)]
483 pub credentials: Option<RedactedHashAlg>,
484 #[serde(skip_serializing_if = "Option::is_none")]
485 #[serde(default)]
486 pub timing: Option<bool>,
487 #[serde(skip_serializing_if = "Option::is_none")]
488 #[serde(default)]
489 pub sizes: Option<bool>,
490}
491
492#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
493#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
494#[derive(Deserialize, Serialize, Debug, Clone)]
495pub struct LoggingConfig {
496 #[serde(skip_serializing_if = "Option::is_none")]
497 #[serde(default)]
498 pub client: Option<ClientLoggingConfig>,
499 #[serde(skip_serializing_if = "Option::is_none")]
500 #[serde(default)]
501 pub server: Option<ServerLoggingConfig>,
502}
503
504#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
505#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
506#[derive(Deserialize, Serialize, Debug, Clone)]
507pub struct Check {
508 }
511
512#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
513#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
514#[derive(Deserialize, Serialize, Debug, Clone)]
515pub enum TokenAlgorithm {
516 HmacBlake2b256,
517}
518
519#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
521#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
522#[derive(Deserialize, Serialize, Debug, Clone)]
523pub struct RefreshTokenConfig {
524 pub algorithm: TokenAlgorithm,
526 pub lifetime: u32,
528 pub rotation: u32,
530}
531
532impl Default for RefreshTokenConfig {
533 fn default() -> RefreshTokenConfig {
534 RefreshTokenConfig {
535 algorithm: TokenAlgorithm::HmacBlake2b256,
536 lifetime: 60 * 60 * 24 * 7,
537 rotation: 60 * 60 * 24 * 7 * 2,
538 }
539 }
540}
541
542#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
544#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
545#[derive(Deserialize, Serialize, Debug, Clone)]
546pub struct AccessTokenConfig {
547 pub algorithm: TokenAlgorithm,
549 pub lifetime: u32,
551 pub rotation: u32,
553 pub claims: Vec<Field>,
557}
558
559impl Default for AccessTokenConfig {
560 fn default() -> AccessTokenConfig {
561 AccessTokenConfig {
562 algorithm: TokenAlgorithm::HmacBlake2b256,
563 lifetime: 60 * 60 * 24,
564 rotation: 60 * 60 * 24 * 3,
565 claims: vec![],
566 }
567 }
568}
569
570#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
577#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
578#[derive(Deserialize, Serialize, Debug, Clone)]
579pub enum ClientPasswordHash {
580 Sha256,
585}
586
587#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
589#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
590#[derive(Deserialize, Serialize, Debug, Clone)]
591pub enum PasswordProtocol {
592 Opaque,
602 }
604
605#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
607#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
608#[derive(Deserialize, Serialize, Debug, Clone)]
609pub struct PasswordConfig {
610 pub protocol: PasswordProtocol,
611}
612
613impl Default for PasswordConfig {
614 fn default() -> PasswordConfig {
615 PasswordConfig {
616 protocol: PasswordProtocol::Opaque,
617 }
618 }
619}
620
621#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
622#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
623#[derive(Deserialize, Serialize, Debug, Clone)]
624pub enum TotpAlgorithm {
625 Sha1,
629}
630
631#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
632#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
633#[derive(Deserialize, Serialize, Debug, Clone)]
634pub struct TotpConfig {
635 #[serde(skip_serializing_if = "Option::is_none")]
638 #[serde(default)]
639 pub template: Option<String>,
640 pub algorithm: TotpAlgorithm,
641}
642
643impl Default for TotpConfig {
644 fn default() -> TotpConfig {
645 TotpConfig {
646 template: None,
647 algorithm: TotpAlgorithm::Sha1,
648 }
649 }
650}
651
652#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
653#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
654#[derive(Serialize, Deserialize, Debug, Clone, Default)]
655pub struct MfaConfig {
656 pub totp: TotpConfig,
657}
658
659#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
660#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
661#[derive(Deserialize, Serialize, Debug, Clone)]
662pub enum InviteMode {
663 Root,
665 Admin,
667 Viral,
669 }
675
676#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
677#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
678#[derive(Deserialize, Serialize, Debug, Clone)]
679pub struct InviteConfig {
680 pub mode: InviteMode,
681 pub lifetime: u32,
685 pub clean_interval: (u32, u32),
689 #[serde(skip_serializing_if = "Option::is_none")]
702 #[serde(default)]
703 pub claims: Option<Vec<Field>>,
704}
705
706impl Default for InviteConfig {
707 fn default() -> InviteConfig {
708 InviteConfig {
709 mode: InviteMode::Viral,
710 lifetime: 60 * 60 * 24 * 7,
711 clean_interval: (30, 90),
712 claims: None,
713 }
714 }
715}
716
717#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
719#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
720#[derive(Deserialize, Serialize, Debug, Clone)]
721pub struct AuthConfig {
722 pub password: PasswordConfig,
724 pub mfa: MfaConfig,
726 pub refresh_token: RefreshTokenConfig,
728 pub access_token: AccessTokenConfig,
730 pub cookies_enabled: bool,
741 pub client_hash: ClientPasswordHash,
743
744 #[serde(skip_serializing_if = "Option::is_none")]
746 #[serde(default)]
747 pub invite: Option<InviteConfig>,
748}
749
750impl Default for AuthConfig {
751 fn default() -> AuthConfig {
752 AuthConfig {
753 password: PasswordConfig::default(),
754 mfa: MfaConfig::default(),
755 refresh_token: RefreshTokenConfig::default(),
756 access_token: AccessTokenConfig::default(),
757 cookies_enabled: false,
758 client_hash: ClientPasswordHash::Sha256,
759 invite: None,
760 }
761 }
762}
763
764#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
766#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
767#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
768pub enum CompressionAlgorithm {
769 All,
770 Gzip,
771 Zstd { level: u8 },
772 Brotli,
773 Deflate,
774}
775
776impl CompressionAlgorithm {
777 #[must_use]
778 pub fn as_u8(&self) -> u8 {
779 match self {
780 Self::All => 0,
781 Self::Gzip => 1,
782 Self::Zstd { level: _ } => 2,
783 Self::Brotli => 3,
784 Self::Deflate => 4,
785 }
786 }
787
788 #[must_use]
789 pub fn as_char(&self) -> char {
790 match self {
791 Self::All => '0',
792 Self::Gzip => '1',
793 Self::Zstd { level: _ } => '2',
794 Self::Brotli => '3',
795 Self::Deflate => '4',
796 }
797 }
798
799 #[must_use]
800 pub fn as_str(&self) -> &'static str {
801 match self {
802 Self::All => "",
803 Self::Gzip => "gzip",
804 Self::Zstd { level: _ } => "zstd",
805 Self::Brotli => "br",
806 Self::Deflate => "deflate",
807 }
808 }
809}
810
811#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
812#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
813#[derive(Deserialize, Serialize, Debug, Clone)]
814pub enum StoredCachePolicy {
815 Permanent,
819 FRs(u64, u64),
823}
824
825#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
830#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
831#[derive(Deserialize, Serialize, Debug, Clone)]
832pub struct StoredCache {
833 pub policy: StoredCachePolicy,
834
835 #[serde(skip_serializing_if = "Option::is_none")]
837 #[serde(default)]
838 pub compression: Option<CompressionAlgorithms>,
839
840 #[serde(skip)]
841 #[serde(default)]
842 pub internal_compression: Option<ArrayVec<CompressionAlgorithm, 4>>,
843
844 #[serde(skip_serializing_if = "Option::is_none")]
848 #[serde(default)]
849 pub max_ttl: Option<u64>,
850
851 #[serde(skip_serializing_if = "Option::is_none")]
855 #[serde(default)]
856 pub hit_ttl: Option<u64>,
857
858 #[serde(skip_serializing_if = "Option::is_none")]
863 #[serde(default)]
864 pub max_size: Option<u64>,
865
866 #[serde(skip_serializing_if = "Option::is_none")]
869 #[serde(default)]
870 pub max_count: Option<usize>,
871
872 #[serde(skip_serializing_if = "Option::is_none")]
876 #[serde(default)]
877 pub frequency_window: Option<u64>,
878
879 #[serde(skip_serializing_if = "Option::is_none")]
883 #[serde(default)]
884 pub clean_interval: Option<(u64, u64)>,
885
886 #[serde(skip_serializing_if = "Option::is_none")]
889 #[serde(default)]
890 pub evict_on_dependency_change: Option<bool>,
891}
892
893#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
894#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
895#[derive(Deserialize, Serialize, Debug, Clone)]
896pub enum XXH3Variation {
897 Bit64,
898 Bit128,
899}
900
901#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
905#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
906#[derive(Deserialize, Serialize, Debug, Clone)]
907pub enum HttpEtagAlgorithm {
908 AHash,
909 XXH3(XXH3Variation),
910 Rustc,
911 Blake3,
912}
913
914#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
917#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
918#[derive(Deserialize, Serialize, Debug, Clone)]
919pub struct HttpEtag {
920 #[serde(skip_serializing_if = "Option::is_none")]
921 #[serde(default)]
922 pub alg: Option<HttpEtagAlgorithm>,
923}
924
925#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
928#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
929#[derive(Deserialize, Serialize, Debug, Clone, Default)]
930pub struct HttpCacheControl {
931 #[serde(skip_serializing_if = "Option::is_none")]
932 #[serde(default)]
933 pub max_age: Option<usize>,
934 #[serde(skip_serializing_if = "Option::is_none")]
935 #[serde(default)]
936 pub s_maxage: Option<usize>,
937 #[serde(skip_serializing_if = "Option::is_none")]
938 #[serde(default)]
939 pub no_cache: Option<bool>,
940 #[serde(skip_serializing_if = "Option::is_none")]
941 #[serde(default)]
942 pub no_store: Option<bool>,
943 #[serde(skip_serializing_if = "Option::is_none")]
944 #[serde(default)]
945 pub no_transform: Option<bool>,
946 #[serde(skip_serializing_if = "Option::is_none")]
947 #[serde(default)]
948 pub must_revalidate: Option<bool>,
949 #[serde(skip_serializing_if = "Option::is_none")]
950 #[serde(default)]
951 pub proxy_revalidate: Option<bool>,
952 #[serde(skip_serializing_if = "Option::is_none")]
953 #[serde(default)]
954 pub private: Option<bool>,
955 #[serde(skip_serializing_if = "Option::is_none")]
956 #[serde(default)]
957 pub public: Option<bool>,
958 #[serde(skip_serializing_if = "Option::is_none")]
959 #[serde(default)]
960 pub immutable: Option<bool>,
961 #[serde(skip_serializing_if = "Option::is_none")]
962 #[serde(default)]
963 pub stale_while_revalidate: Option<bool>,
964 #[serde(skip_serializing_if = "Option::is_none")]
965 #[serde(default)]
966 pub stale_if_error: Option<bool>,
967}
968
969impl HttpCacheControl {
970 pub fn header_value(&self, cache_control: &mut String, default: &str) -> anyhow::Result<()> {
971 if let Some(max_age) = self.max_age {
972 write!(cache_control, "max-age={max_age}, ")?;
973 }
974 if let Some(s_maxage) = self.s_maxage {
975 write!(cache_control, "s-maxage={s_maxage}, ")?;
976 }
977 if let Some(no_cache) = self.no_cache
978 && no_cache
979 {
980 write!(cache_control, "no-cache, ")?;
981 }
982 if let Some(no_store) = self.no_store
983 && no_store
984 {
985 write!(cache_control, "no-store, ")?;
986 }
987 if let Some(no_transform) = self.no_transform
988 && no_transform
989 {
990 write!(cache_control, "no-transform, ")?;
991 }
992 if let Some(must_revalidate) = self.must_revalidate
993 && must_revalidate
994 {
995 write!(cache_control, "must-revalidate, ")?;
996 }
997 if let Some(proxy_revalidate) = self.proxy_revalidate
998 && proxy_revalidate
999 {
1000 write!(cache_control, "proxy-revalidate, ")?;
1001 }
1002 if let Some(stale_while_revalidate) = self.stale_while_revalidate
1003 && stale_while_revalidate
1004 {
1005 write!(cache_control, "stale-while-revalidate, ")?;
1006 }
1007 if let Some(private) = self.private
1008 && private
1009 {
1010 write!(cache_control, "private, ")?;
1011 }
1012 if let Some(public) = self.public
1013 && public
1014 {
1015 write!(cache_control, "public, ")?;
1016 }
1017 if let Some(immutable) = self.immutable
1018 && immutable
1019 {
1020 write!(cache_control, "immutable, ")?;
1021 }
1022 if let Some(stale_if_error) = self.stale_if_error
1023 && stale_if_error
1024 {
1025 write!(cache_control, "stale-if-error, ")?;
1026 }
1027
1028 if cache_control.is_empty() {
1029 cache_control.push_str(default);
1030 } else {
1031 cache_control.pop();
1032 cache_control.pop();
1033 }
1034
1035 Ok(())
1036 }
1037}
1038
1039#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1040#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1041#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1042pub struct HttpCache {
1043 #[serde(skip_serializing_if = "Option::is_none")]
1044 #[serde(default)]
1045 pub cache_control: Option<HttpCacheControl>,
1046
1047 #[serde(skip_serializing_if = "Option::is_none")]
1052 #[serde(default)]
1053 pub expires: Option<u64>,
1054
1055 #[serde(skip_serializing_if = "Option::is_none")]
1056 #[serde(default)]
1057 pub etag: Option<HttpEtag>,
1058}
1059
1060#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1062#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1063#[derive(Deserialize, Serialize, Debug, Clone)]
1064pub struct TemplateField {
1065 pub name: String,
1067 pub kind: Kind,
1069 pub value: serde_json::Value,
1071}
1072
1073#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1075#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1076#[derive(Deserialize, Serialize, Debug, Clone)]
1077pub enum QueryExpression {
1078 Gte,
1079 Gt,
1080 Lte,
1081 Lt,
1082 Eq,
1083 BeginsWith,
1084}
1085
1086#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1088#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1089#[derive(Deserialize, Serialize, Debug, Clone)]
1090pub enum TemplateRefFieldBind {
1091 Token {
1093 field: String,
1098 #[serde(skip_serializing_if = "Option::is_none")]
1100 #[serde(default)]
1101 expression: Option<QueryExpression>,
1102 },
1103 Segment {
1105 name: String,
1107 #[serde(skip_serializing_if = "Option::is_none")]
1109 #[serde(default)]
1110 expression: Option<QueryExpression>,
1111 },
1112}
1113
1114#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1117#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1118#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1119pub struct TemplateRefField {
1120 pub idx: u8,
1123 pub name: String,
1125 #[serde(skip_serializing_if = "Option::is_none")]
1128 #[serde(default)]
1129 pub bind: Option<TemplateRefFieldBind>,
1130 #[cfg_attr(feature = "utoipa", schema(no_recursion))]
1132 #[serde(skip_serializing_if = "Option::is_none")]
1133 #[serde(default)]
1134 pub fields: Option<Vec<TemplateRefField>>,
1135}
1136
1137#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1141#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1142#[derive(Deserialize, Serialize, Debug, Clone)]
1143pub struct TemplateRef {
1144 pub idx: u8,
1148 pub name: String,
1150 pub fields: Vec<TemplateRefField>,
1152 #[serde(skip_serializing_if = "Option::is_none")]
1156 #[serde(default)]
1157 pub all: Option<String>,
1158}
1159
1160#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1163#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1164#[derive(Deserialize, Serialize, Debug, Clone)]
1165pub struct TemplateFlagRef {
1166 pub idx: u8,
1170 pub name: String,
1172}
1173
1174#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1175#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1176#[derive(Deserialize, Serialize, Debug, Clone)]
1177pub struct TemplateParamRef {
1178 pub idx: u8,
1182 pub name: String,
1184}
1185
1186#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1187#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1188#[derive(Deserialize, Serialize, Debug, Clone)]
1189pub struct TemplateCache {
1190 #[serde(skip_serializing_if = "Option::is_none")]
1191 #[serde(default)]
1192 pub stored: Option<StoredCache>,
1193
1194 #[serde(skip_serializing_if = "Option::is_none")]
1195 #[serde(default)]
1196 pub http: Option<HttpCache>,
1197}
1198
1199#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1203#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1204#[derive(Deserialize, Serialize, Debug, Clone)]
1205pub enum WasmOpt {
1206 Size,
1207 SizeAggressive,
1208 Level0,
1209 Level1,
1210 Level2,
1211 Level3,
1212 Level4,
1213}
1214
1215impl WasmOpt {
1216 #[must_use]
1217 pub fn as_flag(&self) -> &'static str {
1218 match self {
1219 Self::Size => "-Os",
1220 Self::SizeAggressive => "-Oz",
1221 Self::Level0 => "-O0",
1222 Self::Level1 => "-O1",
1223 Self::Level2 => "-O2",
1224 Self::Level3 => "-O3",
1225 Self::Level4 => "-O4",
1226 }
1227 }
1228}
1229
1230#[allow(clippy::derivable_impls)]
1231impl Default for TemplateFfiVersion {
1232 fn default() -> Self {
1233 Self::V1
1234 }
1235}
1236
1237#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1238#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1239#[derive(Deserialize, Serialize, Debug, Clone)]
1240pub enum TemplateFfiVersion {
1241 V1,
1242}
1243
1244#[allow(clippy::derivable_impls)]
1245impl Default for TemplateFfiSerialization {
1246 fn default() -> Self {
1247 Self::FlexBufferVector
1248 }
1249}
1250
1251#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1253#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1254#[derive(Deserialize, Serialize, Debug, Clone)]
1255pub enum TemplateFfiSerialization {
1256 FlexBufferVector,
1257}
1258
1259#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1260#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1261#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1262pub struct TemplateFfi {
1263 pub version: TemplateFfiVersion,
1264 pub serialization: TemplateFfiSerialization,
1265}
1266
1267#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1269#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1270#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1271pub struct TemplateConfig {
1272 pub ffi: TemplateFfi,
1274
1275 pub idx: u8,
1277 pub name: String,
1279 pub mime: String,
1283 #[serde(skip_serializing_if = "Option::is_none")]
1286 #[serde(default)]
1287 pub minify: Option<bool>,
1288 #[serde(skip_serializing_if = "Option::is_none")]
1290 #[serde(default)]
1291 pub path: Option<String>,
1292 pub route: String,
1295 #[serde(skip_serializing_if = "Option::is_none")]
1297 #[serde(default)]
1298 pub protected: Option<Check>,
1299 #[serde(skip_serializing_if = "Option::is_none")]
1301 #[serde(default)]
1302 pub cache: Option<TemplateCache>,
1303
1304 #[serde(skip_serializing_if = "Option::is_none")]
1314 #[serde(default)]
1315 pub csp: Option<HttpCsp>,
1316
1317 #[serde(skip_serializing_if = "Option::is_none")]
1318 #[serde(default)]
1319 pub cors: Option<HttpCors>,
1320
1321 #[serde(skip_serializing_if = "Option::is_none")]
1325 #[serde(default)]
1326 pub timeout: Option<u16>,
1327
1328 #[serde(skip_serializing_if = "Option::is_none")]
1332 #[serde(default)]
1333 pub fields: Option<Vec<TemplateField>>,
1334 #[serde(skip_serializing_if = "Option::is_none")]
1338 #[serde(default)]
1339 pub globals: Option<Vec<String>>,
1340 #[serde(skip_serializing_if = "Option::is_none")]
1342 #[serde(default)]
1343 pub flags: Option<Vec<TemplateFlagRef>>,
1344 #[serde(skip_serializing_if = "Option::is_none")]
1346 #[serde(default)]
1347 pub params: Option<Vec<TemplateParamRef>>,
1348 #[serde(skip_serializing_if = "Option::is_none")]
1351 #[serde(default)]
1352 pub models: Option<Vec<TemplateRef>>,
1353 #[serde(skip_serializing_if = "Option::is_none")]
1356 #[serde(default)]
1357 pub content: Option<Vec<TemplateRef>>,
1358
1359 #[serde(skip_serializing_if = "Option::is_none")]
1363 #[serde(default)]
1364 pub actions: Option<Vec<String>>,
1365
1366 #[serde(skip_serializing_if = "Option::is_none")]
1367 #[serde(default)]
1368 pub wasm_opt: Option<WasmOpt>,
1369
1370 #[serde(skip_serializing_if = "Option::is_none")]
1374 #[serde(default)]
1375 pub variables: Option<Vec<String>>,
1376
1377 #[serde(skip_serializing_if = "Option::is_none")]
1379 #[serde(default)]
1380 pub middlewares: Option<Vec<String>>,
1381}
1382
1383#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1384#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1385#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1386pub struct CompressionAlgorithms(pub Vec<CompressionAlgorithm>);
1387
1388impl CompressionAlgorithms {
1389 #[must_use]
1390 fn get_list(&self) -> ArrayVec<CompressionAlgorithm, 4> {
1391 let mut list = ArrayVec::<CompressionAlgorithm, 4>::new();
1392 let mut has_all = false;
1393
1394 for alg in &self.0 {
1395 if *alg == CompressionAlgorithm::All {
1396 has_all = true;
1397 } else if !list.contains(alg) {
1398 list.push(alg.clone());
1399 }
1400 }
1401
1402 if has_all {
1403 for alg in [
1404 CompressionAlgorithm::Brotli,
1405 CompressionAlgorithm::Zstd { level: 17 },
1406 CompressionAlgorithm::Deflate,
1407 CompressionAlgorithm::Gzip,
1408 ] {
1409 if !list.contains(&alg) {
1410 list.push(alg);
1411 }
1412 }
1413 }
1414
1415 list
1416 }
1417}
1418
1419#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1420#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1421#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1422pub struct AssetsConfig {
1423 #[serde(skip_serializing_if = "Option::is_none")]
1425 #[serde(default)]
1426 pub dir_path: Option<String>,
1427
1428 #[serde(default = "AssetsConfig::default_base_route")]
1431 pub base_route: String,
1432
1433 #[serde(skip_serializing_if = "Option::is_none")]
1446 #[serde(default)]
1447 pub append_index_html: Option<bool>,
1448
1449 #[serde(skip_serializing_if = "Option::is_none")]
1455 #[serde(default)]
1456 pub skip_base_route_index_html: Option<bool>,
1457
1458 #[serde(skip_serializing_if = "Option::is_none")]
1468 #[serde(default)]
1469 pub append_html_ext: Option<bool>,
1470
1471 #[serde(skip_serializing_if = "Option::is_none")]
1476 #[serde(default)]
1477 pub preserve_exif: Option<bool>,
1478
1479 #[serde(skip_serializing_if = "Option::is_none")]
1481 #[serde(default)]
1482 pub html_csp: Option<HttpCsp>,
1483
1484 #[serde(skip_serializing_if = "Option::is_none")]
1489 #[serde(default)]
1490 pub http: Option<HttpCache>,
1491
1492 #[serde(skip_serializing_if = "Option::is_none")]
1496 #[serde(default)]
1497 pub precompression: Option<CompressionAlgorithms>,
1498
1499 #[serde(skip)]
1500 #[serde(default)]
1501 pub internal_precompression: Option<ArrayVec<CompressionAlgorithm, 4>>,
1502
1503 #[serde(skip_serializing_if = "Option::is_none")]
1506 #[serde(default)]
1507 pub minify_css: Option<bool>,
1508
1509 #[serde(skip_serializing_if = "Option::is_none")]
1512 #[serde(default)]
1513 pub minify_js: Option<bool>,
1514
1515 #[serde(skip_serializing_if = "Option::is_none")]
1518 #[serde(default)]
1519 pub minify_html: Option<bool>,
1520
1521 #[serde(skip)]
1522 #[serde(default)]
1523 pub internal_cache_control_header_value: Option<String>,
1524
1525 #[serde(skip_serializing_if = "Option::is_none")]
1527 #[serde(default)]
1528 pub middlewares: Option<Vec<String>>,
1529}
1530
1531impl AssetsConfig {
1532 fn default_base_route() -> String {
1533 "/assets".to_string()
1534 }
1535}
1536
1537impl AssetsConfig {
1538 pub fn init(&mut self, default_cache_control_header_value: &str) -> anyhow::Result<()> {
1539 if let Some(http_cache) = &self.http
1540 && let Some(http_cache_control) = &http_cache.cache_control
1541 {
1542 let mut header = String::new();
1543 http_cache_control.header_value(&mut header, default_cache_control_header_value)?;
1544
1545 self.internal_cache_control_header_value = Some(header);
1546 }
1547
1548 Ok(())
1549 }
1550}
1551
1552#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1553#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1554#[derive(Deserialize, Serialize, Debug, Clone)]
1555pub struct FragmentsConfig {
1556 pub dir_path: String,
1558}
1559
1560#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1562#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1563#[derive(Deserialize, Serialize, Debug, Clone)]
1564pub struct Global {
1565 pub name: String,
1567 pub kind: Kind,
1569 pub value: serde_json::Value,
1571}
1572
1573#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1575#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1576#[derive(Deserialize, Serialize, Debug, Clone)]
1577pub enum SecretSource {
1578 Env,
1591 Stored,
1599 }
1604
1605#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1607#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1608#[derive(Deserialize, Serialize, Debug, Clone)]
1609pub enum SecretVisibility {
1610 Integrations,
1622}
1623
1624#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1626#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1627#[derive(Deserialize, Serialize, Debug, Clone)]
1628pub struct Secret {
1629 pub name: String,
1631 pub source: SecretSource,
1633 pub visibility: SecretVisibility,
1635}
1636
1637#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1639#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1640#[derive(Deserialize, Serialize, Debug, Clone)]
1641pub struct FlagOption {
1642 pub idx: u8,
1644 pub name: String,
1646 pub percentage: u8,
1649}
1650
1651#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1658#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1659#[derive(Deserialize, Serialize, Debug, Clone)]
1660pub struct Flag {
1661 pub idx: u8,
1663 pub name: String,
1665 pub options: Vec<FlagOption>,
1668}
1669
1670#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1671#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1672#[derive(Deserialize, Serialize, Debug, Clone)]
1673pub struct ContentDefinition {
1674 pub idx: u8,
1676
1677 pub name: String,
1679
1680 pub fields: Vec<Field>,
1685
1686 #[serde(skip_serializing_if = "Option::is_none")]
1688 #[serde(default)]
1689 pub lifecycle: Option<ContentObjectLifecycle>,
1690}
1691
1692#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1693#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1694#[derive(Deserialize, Serialize, Debug, Clone)]
1695pub struct ContentObjectLifecycle {
1696 #[serde(skip_serializing_if = "Option::is_none")]
1698 #[serde(default)]
1699 pub before_all: Option<Vec<Vec<String>>>,
1700 #[serde(skip_serializing_if = "Option::is_none")]
1706 #[serde(default)]
1707 pub on_add: Option<LifecycleBeforeAfterScripts>,
1708 #[serde(skip_serializing_if = "Option::is_none")]
1714 #[serde(default)]
1715 pub on_edit: Option<LifecycleBeforeAfterScripts>,
1716 #[serde(skip_serializing_if = "Option::is_none")]
1722 #[serde(default)]
1723 pub on_delete: Option<LifecycleBeforeAfterScripts>,
1724}
1725
1726#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1732#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1733#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1734pub struct Content {
1735 pub file_path: String,
1738 pub definitions: Vec<ContentDefinition>,
1741
1742 #[serde(skip_serializing_if = "Option::is_none")]
1743 #[serde(default)]
1744 pub update: Option<ContentUpdateConfig>,
1745}
1746
1747#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1748#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1749#[derive(Deserialize, Serialize, Debug, Clone)]
1750pub struct ContentUpdateConfig {
1751 #[serde(skip_serializing_if = "Option::is_none")]
1752 #[serde(default)]
1753 pub lifecycle: Option<LifecycleBeforeAfterScripts>,
1754}
1755
1756#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1757#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1758#[derive(Deserialize, Serialize, Debug, Clone)]
1759pub enum UuidVersion {
1760 V4,
1761 V7,
1762}
1763
1764#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1766#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1767#[derive(Deserialize, Serialize, Debug, Clone)]
1768pub struct ModelConfig {
1769 pub idx: u8,
1776 pub name: String,
1778 pub fields: Vec<Field>,
1780 #[serde(skip_serializing_if = "Option::is_none")]
1784 #[serde(default)]
1785 pub uuid: Option<UuidVersion>,
1786}
1787
1788#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1789#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1790#[derive(Deserialize, Serialize, Debug, Clone)]
1791pub enum IntegrationProtocolHttpEncoding {
1792 Json,
1793 Text,
1794 None,
1795}
1796
1797#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1799#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1800#[derive(Deserialize, Serialize, Debug, Clone)]
1801pub enum IntegrationProtocol {
1802 Http {
1804 method: String,
1806 headers: Vec<(String, String)>,
1808 send_encoding: IntegrationProtocolHttpEncoding,
1810 recv_encoding: IntegrationProtocolHttpEncoding,
1812 },
1813 }
1826
1827#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1830#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1831#[derive(Deserialize, Serialize, Debug, Clone)]
1832pub struct IntegrationConfig {
1833 pub idx: u8,
1835 pub name: String,
1837 pub protocol: IntegrationProtocol,
1839 pub endpoint: String,
1841 pub send: Kind,
1845 pub recv: Kind,
1849 #[serde(skip_serializing_if = "Option::is_none")]
1851 #[serde(default)]
1852 pub secrets: Option<Vec<String>>,
1853
1854 #[serde(skip_serializing_if = "Option::is_none")]
1858 #[serde(default)]
1859 pub timeout: Option<u16>,
1860}
1861
1862#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1866#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1867#[derive(Deserialize, Serialize, Debug, Clone)]
1868pub enum ActionLang {
1869 Rust,
1873 JavaScript,
1879 }
1883
1884#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1886#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1887#[derive(Deserialize, Serialize, Debug, Clone)]
1888pub enum ActionAccessModelOps {
1889 Insert,
1891 Get,
1893 Query,
1895 Search,
1897 Update,
1899 Delete,
1901}
1902
1903#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1905#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1906#[derive(Deserialize, Serialize, Debug, Clone)]
1907pub enum ActionAccessAuthOps {
1908 SetTokenFields,
1911}
1912
1913#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1916#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1917#[derive(Deserialize, Serialize, Debug, Clone)]
1918pub enum ActionAccessPermission {
1919 Model {
1921 name: String,
1923 ops: Vec<ActionAccessModelOps>,
1926 },
1927 Content {
1929 name: String,
1931 },
1932 Integration {
1934 name: String,
1936 },
1937 Action {
1939 name: String,
1941 },
1942 Auth {
1944 ops: Vec<ActionAccessAuthOps>,
1946 },
1947}
1948
1949#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1950#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1951#[derive(Deserialize, Serialize, Debug, Clone)]
1952pub enum HttpMethod {
1953 PUT,
1954 POST,
1955 GET,
1956 DELETE,
1957}
1958
1959#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1960#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1961#[derive(Deserialize, Serialize, Debug, Clone)]
1962pub enum ActionTriggerModelOps {
1963 Insert,
1964 Update,
1965 Delete,
1966}
1967
1968#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1970#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1971#[derive(Deserialize, Serialize, Debug, Clone)]
1972pub enum ActionTrigger {
1973 Ordinary,
1975 Json {
1977 route: String,
1979 method: HttpMethod,
1981 },
1982 Form {
1984 route: String,
1986 method: HttpMethod,
1988 redirect: String,
1993 },
1994 Login,
1996 Registration,
1998 Content { name: String },
2000 }
2013
2014#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2015#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2016#[derive(Deserialize, Serialize, Debug, Clone)]
2017pub enum ActionFfiVersion {
2018 V1,
2019}
2020
2021#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2023#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2024#[derive(Deserialize, Serialize, Debug, Clone)]
2025pub enum ActionFfiSerialization {
2026 FlexBufferVector,
2027}
2028
2029#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2030#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2031#[derive(Deserialize, Serialize, Debug, Clone)]
2032pub struct ActionFfi {
2033 pub version: ActionFfiVersion,
2034 pub serialization: ActionFfiSerialization,
2035}
2036
2037#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2039#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2040#[derive(Deserialize, Serialize, Debug, Clone)]
2041pub struct ActionConfig {
2042 pub ffi: ActionFfi,
2044 pub idx: u8,
2046 pub name: String,
2048 pub lang: ActionLang,
2050 #[serde(skip_serializing_if = "Option::is_none")]
2052 #[serde(default)]
2053 pub dir_path: Option<String>,
2054 #[serde(skip_serializing_if = "Option::is_none")]
2057 #[serde(default)]
2058 pub protected: Option<Check>,
2059 #[serde(skip_serializing_if = "Option::is_none")]
2062 #[serde(default)]
2063 pub transactional: Option<bool>,
2064 pub access: Vec<ActionAccessPermission>,
2066 pub accepts: Kind,
2068 pub returns: Kind,
2070 pub triggered_by: Vec<ActionTrigger>,
2073
2074 #[serde(skip_serializing_if = "Option::is_none")]
2078 #[serde(default)]
2079 pub timeout: Option<u16>,
2080
2081 #[serde(skip_serializing_if = "Option::is_none")]
2082 #[serde(default)]
2083 pub cors: Option<HttpCors>,
2084
2085 #[serde(skip_serializing_if = "Option::is_none")]
2086 #[serde(default)]
2087 pub wasm_opt: Option<WasmOpt>,
2088
2089 #[serde(skip_serializing_if = "Option::is_none")]
2094 #[serde(default)]
2095 pub privileged: Option<bool>,
2096
2097 #[serde(skip_serializing_if = "Option::is_none")]
2101 #[serde(default)]
2102 pub variables: Option<Vec<String>>,
2103
2104 #[serde(skip_serializing_if = "Option::is_none")]
2106 #[serde(default)]
2107 pub middlewares: Option<Vec<String>>,
2108}
2109
2110#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2111#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2112#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2113pub struct ErrorConfig {
2114 #[serde(skip_serializing_if = "Option::is_none")]
2118 #[serde(default)]
2119 pub template: Option<String>,
2120
2121 #[serde(skip_serializing_if = "Option::is_none")]
2125 #[serde(default)]
2126 pub asset: Option<String>,
2127}
2128
2129#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2130#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2131#[derive(Deserialize, Serialize, Debug, Clone)]
2132pub enum RuntimeMode {
2133 Shared,
2136 SingleThreaded,
2139 MultiThreaded,
2142}
2143
2144#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2145#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2146#[derive(Deserialize, Serialize, Debug, Clone)]
2147pub enum HttpCorsAllowHeaders {
2148 Any,
2149 Headers(Vec<String>),
2151}
2152
2153#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2154#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2155#[derive(Deserialize, Serialize, Debug, Clone)]
2156pub enum HttpCorsExposeHeaders {
2157 Any,
2158 Headers(Vec<String>),
2160}
2161
2162#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2163#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2164#[derive(Deserialize, Serialize, Debug, Clone)]
2165pub enum HttpCorsAllowMethods {
2166 Any,
2167 Methods(Vec<String>),
2169}
2170
2171#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2172#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2173#[derive(Deserialize, Serialize, Debug, Clone)]
2174pub enum HttpCorsAllowOrigin {
2175 Any,
2176 Origins(Vec<String>),
2178}
2179
2180#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2181#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2182#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2183pub struct HttpCors {
2184 pub allow_credentials: Option<bool>,
2185
2186 pub allow_headers: Option<HttpCorsAllowHeaders>,
2187
2188 pub max_age: Option<u32>,
2190
2191 pub allow_methods: Option<HttpCorsAllowMethods>,
2192
2193 pub allow_origin: Option<HttpCorsAllowOrigin>,
2194
2195 pub expose_headers: Option<HttpCorsExposeHeaders>,
2196
2197 pub allow_private_network: Option<bool>,
2198}
2199
2200impl HttpCors {
2201 #[must_use]
2202 pub fn overwrite(&self, base: &Self) -> Self {
2203 Self {
2204 allow_credentials: if self.allow_credentials.is_none() {
2205 base.allow_credentials
2206 } else {
2207 self.allow_credentials
2208 },
2209 allow_headers: if self.allow_headers.is_none() {
2210 base.allow_headers.clone()
2211 } else {
2212 self.allow_headers.clone()
2213 },
2214 max_age: if self.max_age.is_none() {
2215 base.max_age
2216 } else {
2217 self.max_age
2218 },
2219 allow_methods: if self.allow_methods.is_none() {
2220 base.allow_methods.clone()
2221 } else {
2222 self.allow_methods.clone()
2223 },
2224 allow_origin: if self.allow_origin.is_none() {
2225 base.allow_origin.clone()
2226 } else {
2227 self.allow_origin.clone()
2228 },
2229 expose_headers: if self.expose_headers.is_none() {
2230 base.expose_headers.clone()
2231 } else {
2232 self.expose_headers.clone()
2233 },
2234 allow_private_network: if self.allow_private_network.is_none() {
2235 base.allow_private_network
2236 } else {
2237 self.allow_private_network
2238 },
2239 }
2240 }
2241}
2242
2243#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2244#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2245#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2246pub struct HttpCsp {
2247 #[serde(skip_serializing_if = "Option::is_none")]
2249 #[serde(default)]
2250 pub default_src: Option<String>,
2251
2252 #[serde(skip_serializing_if = "Option::is_none")]
2255 #[serde(default)]
2256 pub script_src: Option<String>,
2257
2258 #[serde(skip_serializing_if = "Option::is_none")]
2261 #[serde(default)]
2262 pub style_src: Option<String>,
2263
2264 #[serde(skip_serializing_if = "Option::is_none")]
2268 #[serde(default)]
2269 pub font_src: Option<String>,
2270
2271 #[serde(skip_serializing_if = "Option::is_none")]
2275 #[serde(default)]
2276 pub img_src: Option<String>,
2277
2278 #[serde(skip_serializing_if = "Option::is_none")]
2282 #[serde(default)]
2283 pub frame_src: Option<String>,
2284
2285 #[serde(skip_serializing_if = "Option::is_none")]
2287 #[serde(default)]
2288 pub include_inline_hashes: Option<bool>,
2289}
2290
2291impl HttpCsp {
2292 #[must_use]
2293 #[allow(clippy::too_many_lines)]
2294 pub fn build_string(
2295 &self,
2296 base: &Self,
2297 inline_style_hashes: Option<Vec<String>>,
2298 inline_script_hashes: Option<Vec<String>>,
2299 script_urls: Option<Vec<String>>,
2300 secure: bool,
2301 has_wasm: bool,
2302 ) -> String {
2303 let mut out = String::new();
2304
2305 let include_inline_hashes = self
2306 .include_inline_hashes
2307 .unwrap_or(base.include_inline_hashes.unwrap_or(true));
2308
2309 let default_src = self
2310 .default_src
2311 .clone()
2312 .unwrap_or(base.default_src.clone().unwrap_or("'self'".to_string()));
2313
2314 if !default_src.is_empty() {
2315 out.push_str("default-src ");
2316 out.push_str(default_src.as_str());
2317 out.push_str("; ");
2318 }
2319
2320 let mut script_src = self
2321 .script_src
2322 .clone()
2323 .unwrap_or(base.script_src.clone().unwrap_or_default());
2324
2325 if include_inline_hashes
2326 && let Some(script_hashes) = inline_script_hashes
2327 && !script_hashes.is_empty()
2328 {
2329 if script_src.is_empty() {
2330 script_src.push_str("'self'");
2331 if has_wasm {
2332 script_src.push_str(" 'wasm-unsafe-eval'");
2333 }
2334 }
2335
2336 for hash in script_hashes {
2337 script_src.push_str(" '");
2338 script_src.push_str(hash.as_str());
2339 script_src.push('\'');
2340 }
2341 }
2342
2343 if let Some(script_urls) = script_urls {
2344 if script_src.is_empty() {
2345 script_src.push_str("'self'");
2346 }
2347
2348 for script_url in script_urls {
2349 script_src.push(' ');
2350 script_src.push_str(&script_url);
2351 }
2352 }
2353
2354 if !script_src.is_empty() {
2355 out.push_str("script-src ");
2356 out.push_str(script_src.as_str());
2357 out.push_str("; ");
2358 }
2359
2360 let mut style_src = self
2361 .style_src
2362 .clone()
2363 .unwrap_or(base.style_src.clone().unwrap_or_default());
2364
2365 if include_inline_hashes
2366 && let Some(style_hashes) = inline_style_hashes
2367 && !style_hashes.is_empty()
2368 {
2369 if style_src.is_empty() {
2370 style_src.push_str("'self'");
2371 }
2372
2373 for hash in style_hashes {
2374 style_src.push_str(" '");
2375 style_src.push_str(hash.as_str());
2376 style_src.push('\'');
2377 }
2378 }
2379
2380 if !style_src.is_empty() {
2381 out.push_str("style-src ");
2382 out.push_str(style_src.as_str());
2383 out.push_str("; ");
2384 }
2385
2386 let font_src = self
2387 .font_src
2388 .clone()
2389 .unwrap_or(base.font_src.clone().unwrap_or_default());
2390 if !font_src.is_empty() {
2391 out.push_str("font-src ");
2392 out.push_str(font_src.as_str());
2393 out.push_str("; ");
2394 }
2395
2396 let img_src = self
2397 .img_src
2398 .clone()
2399 .unwrap_or(base.img_src.clone().unwrap_or_default());
2400 if !img_src.is_empty() {
2401 out.push_str("img-src ");
2402 out.push_str(img_src.as_str());
2403 out.push_str("; ");
2404 }
2405
2406 let frame_src = self
2407 .frame_src
2408 .clone()
2409 .unwrap_or(base.frame_src.clone().unwrap_or_default());
2410 if !frame_src.is_empty() {
2411 out.push_str("frame-src ");
2412 out.push_str(frame_src.as_str());
2413 out.push_str("; ");
2414 }
2415
2416 if secure {
2417 out.push_str("upgrade-insecure-requests; ");
2418 }
2419
2420 out.push_str("report-to csp");
2421
2422 out = out.trim().to_string();
2423 out
2424 }
2425}
2426
2427#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2428#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2429#[derive(Deserialize, Serialize, Debug, Clone)]
2430pub struct LifecycleBeforeAfterScripts {
2431 #[serde(skip_serializing_if = "Option::is_none")]
2432 #[serde(default)]
2433 pub before: Option<Vec<Vec<String>>>,
2434 #[serde(skip_serializing_if = "Option::is_none")]
2435 #[serde(default)]
2436 pub after: Option<Vec<Vec<String>>>,
2437}
2438
2439#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2440#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2441#[derive(Deserialize, Serialize, Debug, Clone)]
2442pub struct TopLevelLifecycle {
2443 pub before_all: Option<Vec<Vec<String>>>,
2445
2446 #[serde(skip_serializing_if = "Option::is_none")]
2448 #[serde(default)]
2449 pub build: Option<LifecycleBeforeAfterScripts>,
2450}
2451
2452#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2453#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2454#[derive(Deserialize, Serialize, Debug, Clone)]
2455pub enum RedirectMethod {
2456 Temporary,
2458 Permanent,
2460}
2461
2462impl Display for RedirectMethod {
2463 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2464 match self {
2465 Self::Temporary => write!(f, "TEMPORARY"),
2466 Self::Permanent => write!(f, "PERMANENT"),
2467 }
2468 }
2469}
2470
2471#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2472#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2473#[derive(Deserialize, Serialize, Debug, Clone)]
2474pub struct HostRedirect {
2475 pub from: String,
2477 pub to: String,
2479 pub method: RedirectMethod,
2481}
2482
2483#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2484#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2485#[derive(Deserialize, Serialize, Debug, Clone)]
2486pub struct RouteRedirect {
2487 pub condition: String,
2489 pub rule: (String, String),
2492 pub method: RedirectMethod,
2494}
2495
2496#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2497#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2498#[derive(Deserialize, Serialize, Debug, Clone)]
2499pub struct Redirects {
2500 #[serde(skip_serializing_if = "Option::is_none")]
2502 #[serde(default)]
2503 pub host: Option<Vec<HostRedirect>>,
2504 #[serde(skip_serializing_if = "Option::is_none")]
2506 #[serde(default)]
2507 pub route: Option<Vec<RouteRedirect>>,
2508}
2509
2510#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2517#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2518#[derive(Deserialize, Serialize, Debug, Clone)]
2519pub struct ProxyConfig {
2520 #[serde(skip_serializing_if = "Option::is_none")]
2527 #[serde(default)]
2528 pub path: Option<String>,
2529 #[serde(skip_serializing_if = "Option::is_none")]
2536 #[serde(default)]
2537 pub domain: Option<String>,
2538
2539 #[serde(skip_serializing_if = "Option::is_none")]
2547 #[serde(default)]
2548 pub port: Option<u16>,
2549
2550 pub target: String,
2554
2555 #[serde(skip_serializing_if = "Option::is_none")]
2557 #[serde(default)]
2558 pub middlewares: Option<Vec<String>>,
2559}
2560
2561#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2562#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2563#[derive(Deserialize, Serialize, Debug, Clone)]
2564pub enum MiddlewareMechanism {
2565 Request { endpoint: String },
2566}
2567
2568#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2569#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2570#[derive(Deserialize, Serialize, Debug, Clone)]
2571pub enum MiddlewareValidationRule {
2572 Any(Vec<Box<MiddlewareValidationRule>>),
2574 All(Vec<Box<MiddlewareValidationRule>>),
2576 Not(Box<MiddlewareValidationRule>),
2578
2579 StatusCode(u16),
2582}
2583
2584#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2585#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2586#[derive(Deserialize, Serialize, Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
2587pub enum MiddlewareValidationComponent {
2588 Headers,
2590 Path,
2592 Query,
2594}
2595
2596#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2597#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2598#[derive(Deserialize, Serialize, Debug, Clone)]
2599pub enum MiddlewareOperation {
2600 Validate {
2601 rule: MiddlewareValidationRule,
2602 components: BTreeSet<MiddlewareValidationComponent>,
2603 },
2604}
2605
2606#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2608#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2609#[derive(Deserialize, Serialize, Debug, Clone)]
2610pub struct MiddlewareConfig {
2611 pub name: String,
2614
2615 pub operation: MiddlewareOperation,
2617
2618 pub mechanism: MiddlewareMechanism,
2620}
2621
2622#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2624#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2625#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2626pub struct OrdinaryConfig {
2627 #[serde(skip_serializing_if = "Option::is_none")]
2628 #[serde(default)]
2629 pub lifecycle: Option<TopLevelLifecycle>,
2630
2631 pub domain: String,
2634
2635 #[serde(skip_serializing_if = "Option::is_none")]
2641 #[serde(default)]
2642 pub cnames: Option<Vec<String>>,
2643
2644 #[serde(skip_serializing_if = "Option::is_none")]
2654 #[serde(default)]
2655 pub canonical: Option<String>,
2656
2657 #[serde(skip_serializing_if = "Option::is_none")]
2659 #[serde(default)]
2660 pub redirects: Option<Redirects>,
2661
2662 #[serde(skip_serializing_if = "Option::is_none")]
2664 #[serde(default)]
2665 pub proxies: Option<Vec<ProxyConfig>>,
2666
2667 #[serde(skip_serializing_if = "Option::is_none")]
2670 #[serde(default)]
2671 pub middlewares: Option<Vec<MiddlewareConfig>>,
2672
2673 #[serde(skip)]
2674 #[serde(default)]
2675 pub internal_middlewares: Option<HashMap<String, MiddlewareConfig>>,
2676
2677 #[serde(skip_serializing_if = "Option::is_none")]
2680 #[serde(default)]
2681 pub contacts: Option<Vec<String>>,
2682
2683 #[serde(skip_serializing_if = "Option::is_none")]
2685 #[serde(default)]
2686 pub hide_contacts: Option<bool>,
2687
2688 pub version: String,
2690
2691 #[serde(skip_serializing_if = "Option::is_none")]
2693 #[serde(default = "OrdinaryConfig::default_storage_size")]
2694 pub storage_size: Option<u64>,
2695
2696 #[serde(skip_serializing_if = "Option::is_none")]
2700 #[serde(default)]
2701 pub default_timeout: Option<u16>,
2702
2703 #[serde(skip_serializing_if = "Option::is_none")]
2713 #[serde(default)]
2714 pub csp: Option<HttpCsp>,
2715
2716 #[serde(skip_serializing_if = "Option::is_none")]
2717 #[serde(default)]
2718 pub cors: Option<HttpCors>,
2719
2720 #[serde(skip_serializing_if = "Option::is_none")]
2724 #[serde(default)]
2725 pub runtime: Option<RuntimeMode>,
2726
2727 #[serde(skip_serializing_if = "Option::is_none")]
2733 #[serde(default)]
2734 pub hide_schema: Option<bool>,
2735
2736 #[serde(skip_serializing_if = "Option::is_none")]
2738 #[serde(default)]
2739 pub client_rendering: Option<bool>,
2740
2741 #[serde(skip_serializing_if = "Option::is_none")]
2743 #[serde(default)]
2744 pub obfuscation: Option<bool>,
2745
2746 #[serde(skip_serializing_if = "Option::is_none")]
2748 #[serde(default)]
2749 pub client_events: Option<bool>,
2750
2751 #[serde(skip_serializing_if = "Option::is_none")]
2753 #[serde(default)]
2754 pub port: Option<u16>,
2755 #[serde(skip_serializing_if = "Option::is_none")]
2758 #[serde(default)]
2759 pub redirect_port: Option<u16>,
2760
2761 #[serde(skip_serializing_if = "Option::is_none")]
2762 #[serde(default)]
2763 pub logging: Option<LoggingConfig>,
2764 #[serde(skip_serializing_if = "Option::is_none")]
2769 #[serde(default)]
2770 pub error: Option<ErrorConfig>,
2771 #[serde(skip_serializing_if = "Option::is_none")]
2773 #[serde(default)]
2774 pub auth: Option<AuthConfig>,
2775 #[serde(skip_serializing_if = "Option::is_none")]
2777 #[serde(default)]
2778 pub globals: Option<Vec<Global>>,
2779 #[serde(skip_serializing_if = "Option::is_none")]
2781 #[serde(default)]
2782 pub secrets: Option<Vec<Secret>>,
2783 #[serde(skip_serializing_if = "Option::is_none")]
2786 #[serde(default)]
2787 pub flags: Option<Vec<Flag>>,
2788 #[serde(skip_serializing_if = "Option::is_none")]
2793 #[serde(default)]
2794 pub content: Option<Content>,
2795 #[serde(skip_serializing_if = "Option::is_none")]
2797 #[serde(default)]
2798 pub models: Option<Vec<ModelConfig>>,
2799 #[serde(skip_serializing_if = "Option::is_none")]
2802 #[serde(default)]
2803 pub integrations: Option<Vec<IntegrationConfig>>,
2804 #[serde(skip_serializing_if = "Option::is_none")]
2807 #[serde(default)]
2808 pub actions: Option<Vec<ActionConfig>>,
2809 #[serde(skip_serializing_if = "Option::is_none")]
2813 #[serde(default)]
2814 pub assets: Option<AssetsConfig>,
2815 #[serde(skip_serializing_if = "Option::is_none")]
2817 #[serde(default)]
2818 pub fragments: Option<FragmentsConfig>,
2819 #[serde(skip_serializing_if = "Option::is_none")]
2832 #[serde(default)]
2833 pub templates: Option<Vec<TemplateConfig>>,
2834}
2835
2836impl OrdinaryConfig {
2837 pub fn get(proj_path: &str) -> anyhow::Result<OrdinaryConfig> {
2839 let path = Path::new(proj_path).join("ordinary.json");
2840 let config_json = fs_err::read_to_string(&path)?;
2841
2842 let mut config = match serde_json::from_str::<OrdinaryConfig>(config_json.as_str()) {
2843 Ok(config) => config,
2844 Err(err) => bail!("{}: {err}", path.display()),
2845 };
2846
2847 config.load_internal();
2848
2849 Ok(config)
2850 }
2851
2852 pub fn load_internal(&mut self) {
2853 self.load_internal_middlewares();
2854 self.load_internal_compression();
2855 }
2856
2857 fn load_internal_middlewares(&mut self) {
2858 if let Some(middlewares) = &self.middlewares {
2859 let mut map = HashMap::new();
2860
2861 for middleware in middlewares {
2862 map.insert(middleware.name.clone(), middleware.clone());
2863 }
2864
2865 self.internal_middlewares = Some(map);
2866 }
2867 }
2868
2869 fn load_internal_compression(&mut self) {
2870 if let Some(assets) = self.assets.as_mut()
2871 && let Some(precompression) = &assets.precompression
2872 {
2873 assets.internal_precompression = Some(precompression.get_list());
2874 }
2875
2876 if let Some(templates) = self.templates.as_mut() {
2877 for template in templates {
2878 if let Some(cache) = template.cache.as_mut()
2879 && let Some(stored) = cache.stored.as_mut()
2880 && let Some(compression) = &stored.compression
2881 {
2882 stored.internal_compression = Some(compression.get_list());
2883 }
2884 }
2885 }
2886 }
2887
2888 #[must_use]
2889 pub fn get_middlewares(&self, middleware_names: &Vec<String>) -> Option<Vec<MiddlewareConfig>> {
2890 let mut middleware_configs = vec![];
2891
2892 if let Some(middleware_map) = &self.internal_middlewares {
2893 for middleware in middleware_names {
2894 if let Some(middleware_config) = middleware_map.get(middleware) {
2895 middleware_configs.push(middleware_config.clone());
2896 }
2897 }
2898 }
2899
2900 if !middleware_configs.is_empty() {
2901 return Some(middleware_configs);
2902 }
2903
2904 None
2905 }
2906
2907 pub fn for_send(&self) -> anyhow::Result<OrdinaryConfig> {
2910 let mut config = self.clone();
2911
2912 config.lifecycle = None;
2913
2914 if let Some(assets) = config.assets.as_mut() {
2915 assets.dir_path = None;
2916 }
2917
2918 if let Some(content) = config.content.as_mut() {
2919 content.update = None;
2920
2921 for def in &mut content.definitions {
2922 def.lifecycle = None;
2923 }
2924 }
2925
2926 if let Some(templates) = config.templates.as_mut() {
2927 for template in templates {
2928 template.path = None;
2929 template.wasm_opt = None;
2930 template.minify = None;
2931 }
2932 }
2933
2934 if let Some(actions) = config.actions.as_mut() {
2935 for action in actions {
2936 action.wasm_opt = None;
2937 action.dir_path = None;
2938 }
2939 }
2940
2941 Ok(config)
2942 }
2943
2944 #[instrument(skip_all, err, level = "debug")]
2947 pub fn validate(&self) -> anyhow::Result<()> {
2948 validate(self)
2949 }
2950
2951 #[must_use]
2953 #[allow(clippy::unnecessary_wraps)]
2954 pub fn default_storage_size() -> Option<u64> {
2955 Some(5_000_000)
2956 }
2957 #[must_use]
2960 pub fn has_ordinary_actions(&self) -> bool {
2961 if let Some(actions) = &self.actions {
2962 actions
2963 .iter()
2964 .find(|a| {
2965 for trigger in &a.triggered_by {
2966 if let ActionTrigger::Ordinary = trigger {
2967 return true;
2968 }
2969 }
2970
2971 false
2972 })
2973 .is_some()
2974 } else {
2975 false
2976 }
2977 }
2978
2979 #[allow(clippy::too_many_lines)]
2984 pub fn check_config_against_limits(
2985 &self,
2986 limits: &OrdinaryApiLimits,
2987 privileged_domains: &HashSet<String>,
2988 ) -> anyhow::Result<()> {
2989 check_config_against_limits(self, limits, privileged_domains)
2990 }
2991
2992 pub fn exec_lifecycle_script(
2993 proj_path: &Path,
2994 argument: &Option<String>,
2995 name: &str,
2996 when: &str,
2997 scripts: &Vec<Vec<String>>,
2998 ) -> anyhow::Result<()> {
2999 let span = tracing::info_span!("lifecycle", %when, %name);
3000
3001 span.in_scope(|| {
3002 let curr_dir = env::current_dir()?;
3003 env::set_current_dir(proj_path)?;
3004
3005 for script in scripts {
3006 let mut script_iter = script.iter();
3007
3008 if let Some(command) = script_iter.nth(0) {
3009 let mut command_str = command.clone();
3010 let mut command = Command::new(command);
3011
3012 for arg in script_iter {
3013 write!(command_str, " {arg}")?;
3014 command.arg(arg);
3015 }
3016
3017 tracing::info!(cmd = %command_str, "exec");
3018
3019 let output = match &argument {
3020 Some(arg) => command.arg(arg).output()?,
3021 None => command.output()?,
3022 };
3023
3024 if output.status.success() {
3025 tracing::info!("success");
3026 } else {
3027 let stderr = str::from_utf8(&output.stderr)?;
3028 let stdout = str::from_utf8(&output.stdout)?;
3029
3030 tracing::error!(%stderr, %stdout, "failed");
3031 bail!(stderr.to_string());
3032 }
3033 }
3034 }
3035
3036 env::set_current_dir(curr_dir)?;
3037
3038 anyhow::Ok(())
3039 })
3040 }
3041}