1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![doc = include_str!("../docs/app-config-reference.md")]
4#![doc = include_str!("../docs/api-config-reference.md")]
5#![warn(clippy::all, clippy::pedantic)]
6#![allow(clippy::missing_errors_doc)]
7
8#[cfg(feature = "docs")]
13pub mod jsonschema;
14#[cfg(feature = "docs")]
15pub use schemars;
16
17mod api;
18mod app;
19mod validate;
20
21pub use api::*;
22pub use app::*;
23
24pub use crate::validate::DOMAIN_REGEX;
25use crate::validate::validate;
26use std::collections::BTreeSet;
27
28use anyhow::bail;
29use arrayvec::ArrayVec;
30use hashbrown::{HashMap, HashSet};
31use ordinary_types::{Field, Kind};
32use serde::{Deserialize, Serialize};
33use std::fmt::{Display, Formatter, Write};
34use std::path::Path;
35use std::process::Command;
36use std::{env, fs};
37use tracing::instrument;
38
39#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
40#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
41#[derive(Deserialize, Serialize, Debug, Clone)]
42pub struct ClientLoggingConfig {
43 #[serde(skip_serializing_if = "Option::is_none")]
45 #[serde(default)]
46 min_delay: Option<u32>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 #[serde(default)]
50 max_delay: Option<u32>,
51 #[serde(skip_serializing_if = "Option::is_none")]
54 #[serde(default)]
55 max_buffer: Option<u16>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 #[serde(default)]
59 max_batch: Option<u16>,
60}
61
62#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
63#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
64#[derive(Deserialize, Serialize, Debug, Clone)]
65pub enum RedactedHashAlg {
66 Blake2,
67 Blake3,
68}
69
70#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
71#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
72#[derive(Deserialize, Serialize, Debug, Clone)]
73pub struct ServerLoggingConfig {
74 #[serde(skip_serializing_if = "Option::is_none")]
75 #[serde(default)]
76 pub ips: Option<bool>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 #[serde(default)]
79 pub headers: Option<bool>,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 #[serde(default)]
82 pub credentials: Option<RedactedHashAlg>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 #[serde(default)]
85 pub timing: Option<bool>,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 #[serde(default)]
88 pub sizes: Option<bool>,
89}
90
91#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
92#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
93#[derive(Deserialize, Serialize, Debug, Clone)]
94pub struct LoggingConfig {
95 #[serde(skip_serializing_if = "Option::is_none")]
96 #[serde(default)]
97 pub client: Option<ClientLoggingConfig>,
98 #[serde(skip_serializing_if = "Option::is_none")]
99 #[serde(default)]
100 pub server: Option<ServerLoggingConfig>,
101}
102
103#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
104#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
105#[derive(Deserialize, Serialize, Debug, Clone)]
106pub struct Check {
107 }
110
111#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
112#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
113#[derive(Deserialize, Serialize, Debug, Clone)]
114pub enum TokenAlgorithm {
115 HmacBlake2b256,
116}
117
118#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
120#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
121#[derive(Deserialize, Serialize, Debug, Clone)]
122pub struct RefreshTokenConfig {
123 pub algorithm: TokenAlgorithm,
125 pub lifetime: u32,
127 pub rotation: u32,
129}
130
131impl Default for RefreshTokenConfig {
132 fn default() -> RefreshTokenConfig {
133 RefreshTokenConfig {
134 algorithm: TokenAlgorithm::HmacBlake2b256,
135 lifetime: 60 * 60 * 24 * 7,
136 rotation: 60 * 60 * 24 * 7 * 2,
137 }
138 }
139}
140
141#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
143#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
144#[derive(Deserialize, Serialize, Debug, Clone)]
145pub struct AccessTokenConfig {
146 pub algorithm: TokenAlgorithm,
148 pub lifetime: u32,
150 pub rotation: u32,
152 pub claims: Vec<Field>,
156}
157
158impl Default for AccessTokenConfig {
159 fn default() -> AccessTokenConfig {
160 AccessTokenConfig {
161 algorithm: TokenAlgorithm::HmacBlake2b256,
162 lifetime: 60 * 60 * 24,
163 rotation: 60 * 60 * 24 * 3,
164 claims: vec![],
165 }
166 }
167}
168
169#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
176#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
177#[derive(Deserialize, Serialize, Debug, Clone)]
178pub enum ClientPasswordHash {
179 Sha256,
184}
185
186#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
188#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
189#[derive(Deserialize, Serialize, Debug, Clone)]
190pub enum PasswordProtocol {
191 Opaque,
201 }
203
204#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
206#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
207#[derive(Deserialize, Serialize, Debug, Clone)]
208pub struct PasswordConfig {
209 pub protocol: PasswordProtocol,
210}
211
212impl Default for PasswordConfig {
213 fn default() -> PasswordConfig {
214 PasswordConfig {
215 protocol: PasswordProtocol::Opaque,
216 }
217 }
218}
219
220#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
221#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
222#[derive(Deserialize, Serialize, Debug, Clone)]
223pub enum TotpAlgorithm {
224 Sha1,
228}
229
230#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
231#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
232#[derive(Deserialize, Serialize, Debug, Clone)]
233pub struct TotpConfig {
234 #[serde(skip_serializing_if = "Option::is_none")]
237 #[serde(default)]
238 pub template: Option<String>,
239 pub algorithm: TotpAlgorithm,
240}
241
242impl Default for TotpConfig {
243 fn default() -> TotpConfig {
244 TotpConfig {
245 template: None,
246 algorithm: TotpAlgorithm::Sha1,
247 }
248 }
249}
250
251#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
252#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
253#[derive(Serialize, Deserialize, Debug, Clone, Default)]
254pub struct MfaConfig {
255 pub totp: TotpConfig,
256}
257
258#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
259#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
260#[derive(Deserialize, Serialize, Debug, Clone)]
261pub enum InviteMode {
262 Root,
264 Admin,
266 Viral,
268 }
274
275#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
276#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
277#[derive(Deserialize, Serialize, Debug, Clone)]
278pub struct InviteConfig {
279 pub mode: InviteMode,
280 pub lifetime: u32,
284 pub clean_interval: (u32, u32),
288 #[serde(skip_serializing_if = "Option::is_none")]
301 #[serde(default)]
302 pub claims: Option<Vec<Field>>,
303}
304
305impl Default for InviteConfig {
306 fn default() -> InviteConfig {
307 InviteConfig {
308 mode: InviteMode::Viral,
309 lifetime: 60 * 60 * 24 * 7,
310 clean_interval: (30, 90),
311 claims: None,
312 }
313 }
314}
315
316#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
318#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
319#[derive(Deserialize, Serialize, Debug, Clone)]
320pub struct AuthConfig {
321 pub password: PasswordConfig,
323 pub mfa: MfaConfig,
325 pub refresh_token: RefreshTokenConfig,
327 pub access_token: AccessTokenConfig,
329 pub cookies_enabled: bool,
340 pub client_hash: ClientPasswordHash,
342
343 #[serde(skip_serializing_if = "Option::is_none")]
345 #[serde(default)]
346 pub invite: Option<InviteConfig>,
347}
348
349impl Default for AuthConfig {
350 fn default() -> AuthConfig {
351 AuthConfig {
352 password: PasswordConfig::default(),
353 mfa: MfaConfig::default(),
354 refresh_token: RefreshTokenConfig::default(),
355 access_token: AccessTokenConfig::default(),
356 cookies_enabled: false,
357 client_hash: ClientPasswordHash::Sha256,
358 invite: None,
359 }
360 }
361}
362
363#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
365#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
366#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
367pub enum CompressionAlgorithm {
368 All,
369 Gzip,
370 Zstd { level: u8 },
371 Brotli,
372 Deflate,
373}
374
375impl CompressionAlgorithm {
376 #[must_use]
377 pub fn as_u8(&self) -> u8 {
378 match self {
379 Self::All => 0,
380 Self::Gzip => 1,
381 Self::Zstd { level: _ } => 2,
382 Self::Brotli => 3,
383 Self::Deflate => 4,
384 }
385 }
386
387 #[must_use]
388 pub fn as_char(&self) -> char {
389 match self {
390 Self::All => '0',
391 Self::Gzip => '1',
392 Self::Zstd { level: _ } => '2',
393 Self::Brotli => '3',
394 Self::Deflate => '4',
395 }
396 }
397
398 #[must_use]
399 pub fn as_str(&self) -> &'static str {
400 match self {
401 Self::All => "",
402 Self::Gzip => "gzip",
403 Self::Zstd { level: _ } => "zstd",
404 Self::Brotli => "br",
405 Self::Deflate => "deflate",
406 }
407 }
408}
409
410#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
411#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
412#[derive(Deserialize, Serialize, Debug, Clone)]
413pub enum StoredCachePolicy {
414 Permanent,
418 QuickCache,
420}
421
422#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
427#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
428#[derive(Deserialize, Serialize, Debug, Clone)]
429pub struct StoredCache {
430 pub policy: StoredCachePolicy,
431
432 #[serde(skip_serializing_if = "Option::is_none")]
434 #[serde(default)]
435 pub compression: Option<CompressionAlgorithms>,
436
437 #[serde(skip)]
438 #[serde(default)]
439 pub internal_compression: Option<ArrayVec<CompressionAlgorithm, 4>>,
440
441 #[serde(skip_serializing_if = "Option::is_none")]
445 #[serde(default)]
446 pub max_ttl: Option<u64>,
447
448 #[serde(skip_serializing_if = "Option::is_none")]
452 #[serde(default)]
453 pub hit_ttl: Option<u64>,
454
455 #[serde(skip_serializing_if = "Option::is_none")]
460 #[serde(default)]
461 pub max_size: Option<u64>,
462
463 #[serde(skip_serializing_if = "Option::is_none")]
466 #[serde(default)]
467 pub max_count: Option<usize>,
468
469 #[serde(skip_serializing_if = "Option::is_none")]
473 #[serde(default)]
474 pub frequency_window: Option<u64>,
475
476 #[serde(skip_serializing_if = "Option::is_none")]
480 #[serde(default)]
481 pub clean_interval: Option<(u64, u64)>,
482
483 #[serde(skip_serializing_if = "Option::is_none")]
486 #[serde(default)]
487 pub evict_on_dependency_change: Option<bool>,
488}
489
490#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
491#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
492#[derive(Deserialize, Serialize, Debug, Clone)]
493pub enum XXH3Variation {
494 Bit64,
495 Bit128,
496}
497
498#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
502#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
503#[derive(Deserialize, Serialize, Debug, Clone)]
504pub enum HttpEtagAlgorithm {
505 AHash,
506 XXH3(XXH3Variation),
507 Rustc,
508 Blake3,
509 Foldhash,
510}
511
512#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
515#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
516#[derive(Deserialize, Serialize, Debug, Clone)]
517pub struct HttpEtag {
518 #[serde(skip_serializing_if = "Option::is_none")]
519 #[serde(default)]
520 pub alg: Option<HttpEtagAlgorithm>,
521}
522
523#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
526#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
527#[derive(Deserialize, Serialize, Debug, Clone, Default)]
528pub struct HttpCacheControl {
529 #[serde(skip_serializing_if = "Option::is_none")]
530 #[serde(default)]
531 pub max_age: Option<usize>,
532 #[serde(skip_serializing_if = "Option::is_none")]
533 #[serde(default)]
534 pub s_maxage: Option<usize>,
535 #[serde(skip_serializing_if = "Option::is_none")]
536 #[serde(default)]
537 pub no_cache: Option<bool>,
538 #[serde(skip_serializing_if = "Option::is_none")]
539 #[serde(default)]
540 pub no_store: Option<bool>,
541 #[serde(skip_serializing_if = "Option::is_none")]
542 #[serde(default)]
543 pub no_transform: Option<bool>,
544 #[serde(skip_serializing_if = "Option::is_none")]
545 #[serde(default)]
546 pub must_revalidate: Option<bool>,
547 #[serde(skip_serializing_if = "Option::is_none")]
548 #[serde(default)]
549 pub proxy_revalidate: Option<bool>,
550 #[serde(skip_serializing_if = "Option::is_none")]
551 #[serde(default)]
552 pub private: Option<bool>,
553 #[serde(skip_serializing_if = "Option::is_none")]
554 #[serde(default)]
555 pub public: Option<bool>,
556 #[serde(skip_serializing_if = "Option::is_none")]
557 #[serde(default)]
558 pub immutable: Option<bool>,
559 #[serde(skip_serializing_if = "Option::is_none")]
560 #[serde(default)]
561 pub stale_while_revalidate: Option<bool>,
562 #[serde(skip_serializing_if = "Option::is_none")]
563 #[serde(default)]
564 pub stale_if_error: Option<bool>,
565}
566
567impl HttpCacheControl {
568 pub fn header_value(&self, cache_control: &mut String, default: &str) -> anyhow::Result<()> {
569 if let Some(max_age) = self.max_age {
570 write!(cache_control, "max-age={max_age}, ")?;
571 }
572 if let Some(s_maxage) = self.s_maxage {
573 write!(cache_control, "s-maxage={s_maxage}, ")?;
574 }
575 if let Some(no_cache) = self.no_cache
576 && no_cache
577 {
578 write!(cache_control, "no-cache, ")?;
579 }
580 if let Some(no_store) = self.no_store
581 && no_store
582 {
583 write!(cache_control, "no-store, ")?;
584 }
585 if let Some(no_transform) = self.no_transform
586 && no_transform
587 {
588 write!(cache_control, "no-transform, ")?;
589 }
590 if let Some(must_revalidate) = self.must_revalidate
591 && must_revalidate
592 {
593 write!(cache_control, "must-revalidate, ")?;
594 }
595 if let Some(proxy_revalidate) = self.proxy_revalidate
596 && proxy_revalidate
597 {
598 write!(cache_control, "proxy-revalidate, ")?;
599 }
600 if let Some(stale_while_revalidate) = self.stale_while_revalidate
601 && stale_while_revalidate
602 {
603 write!(cache_control, "stale-while-revalidate, ")?;
604 }
605 if let Some(private) = self.private
606 && private
607 {
608 write!(cache_control, "private, ")?;
609 }
610 if let Some(public) = self.public
611 && public
612 {
613 write!(cache_control, "public, ")?;
614 }
615 if let Some(immutable) = self.immutable
616 && immutable
617 {
618 write!(cache_control, "immutable, ")?;
619 }
620 if let Some(stale_if_error) = self.stale_if_error
621 && stale_if_error
622 {
623 write!(cache_control, "stale-if-error, ")?;
624 }
625
626 if cache_control.is_empty() {
627 cache_control.push_str(default);
628 } else {
629 cache_control.pop();
630 cache_control.pop();
631 }
632
633 Ok(())
634 }
635}
636
637#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
638#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
639#[derive(Deserialize, Serialize, Debug, Clone, Default)]
640pub struct HttpCache {
641 #[serde(skip_serializing_if = "Option::is_none")]
642 #[serde(default)]
643 pub cache_control: Option<HttpCacheControl>,
644
645 #[serde(skip_serializing_if = "Option::is_none")]
650 #[serde(default)]
651 pub expires: Option<u64>,
652
653 #[serde(skip_serializing_if = "Option::is_none")]
654 #[serde(default)]
655 pub etag: Option<HttpEtag>,
656}
657
658#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
659#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
660#[derive(Deserialize, Serialize, Debug, Clone, Default)]
661pub struct CompressionAlgorithms(pub Vec<CompressionAlgorithm>);
662
663impl CompressionAlgorithms {
664 #[must_use]
665 fn get_list(&self) -> ArrayVec<CompressionAlgorithm, 4> {
666 let mut list = ArrayVec::<CompressionAlgorithm, 4>::new();
667 let mut has_all = false;
668
669 for alg in &self.0 {
670 if *alg == CompressionAlgorithm::All {
671 has_all = true;
672 } else if !list.contains(alg) {
673 list.push(alg.clone());
674 }
675 }
676
677 if has_all {
678 for alg in [
679 CompressionAlgorithm::Brotli,
680 CompressionAlgorithm::Zstd { level: 17 },
681 CompressionAlgorithm::Deflate,
682 CompressionAlgorithm::Gzip,
683 ] {
684 if !list.contains(&alg) {
685 list.push(alg);
686 }
687 }
688 }
689
690 list
691 }
692}
693
694#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
695#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
696#[derive(Deserialize, Serialize, Debug, Clone, Default)]
697pub struct AssetsConfig {
698 #[serde(skip_serializing_if = "Option::is_none")]
700 #[serde(default)]
701 pub dir_path: Option<String>,
702
703 #[serde(default = "AssetsConfig::default_base_route")]
706 pub base_route: String,
707
708 #[serde(skip_serializing_if = "Option::is_none")]
721 #[serde(default)]
722 pub append_index_html: Option<bool>,
723
724 #[serde(skip_serializing_if = "Option::is_none")]
730 #[serde(default)]
731 pub skip_base_route_index_html: Option<bool>,
732
733 #[serde(skip_serializing_if = "Option::is_none")]
743 #[serde(default)]
744 pub append_html_ext: Option<bool>,
745
746 #[serde(skip_serializing_if = "Option::is_none")]
751 #[serde(default)]
752 pub preserve_exif: Option<bool>,
753
754 #[serde(skip_serializing_if = "Option::is_none")]
756 #[serde(default)]
757 pub html_csp: Option<HttpCsp>,
758
759 #[serde(skip_serializing_if = "Option::is_none")]
764 #[serde(default)]
765 pub http: Option<HttpCache>,
766
767 #[serde(skip_serializing_if = "Option::is_none")]
771 #[serde(default)]
772 pub precompression: Option<CompressionAlgorithms>,
773
774 #[serde(skip)]
775 #[serde(default)]
776 pub internal_precompression: Option<ArrayVec<CompressionAlgorithm, 4>>,
777
778 #[serde(skip_serializing_if = "Option::is_none")]
781 #[serde(default)]
782 pub minify_css: Option<bool>,
783
784 #[serde(skip_serializing_if = "Option::is_none")]
787 #[serde(default)]
788 pub minify_js: Option<bool>,
789
790 #[serde(skip_serializing_if = "Option::is_none")]
793 #[serde(default)]
794 pub minify_html: Option<bool>,
795
796 #[serde(skip)]
797 #[serde(default)]
798 pub internal_cache_control_header_value: Option<String>,
799
800 #[serde(skip_serializing_if = "Option::is_none")]
802 #[serde(default)]
803 pub middlewares: Option<Vec<String>>,
804}
805
806impl AssetsConfig {
807 fn default_base_route() -> String {
808 "/assets".to_string()
809 }
810}
811
812impl AssetsConfig {
813 pub fn init(&mut self, default_cache_control_header_value: &str) -> anyhow::Result<()> {
814 if let Some(http_cache) = &self.http
815 && let Some(http_cache_control) = &http_cache.cache_control
816 {
817 let mut header = String::new();
818 http_cache_control.header_value(&mut header, default_cache_control_header_value)?;
819
820 self.internal_cache_control_header_value = Some(header);
821 }
822
823 Ok(())
824 }
825}
826
827#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
828#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
829#[derive(Deserialize, Serialize, Debug, Clone)]
830pub struct FragmentsConfig {
831 pub dir_path: String,
833}
834
835#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
837#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
838#[derive(Deserialize, Serialize, Debug, Clone)]
839pub struct Global {
840 pub name: String,
842 pub kind: Kind,
844 pub value: serde_json::Value,
846}
847
848#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
850#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
851#[derive(Deserialize, Serialize, Debug, Clone)]
852pub enum SecretSource {
853 Env,
866 Stored,
874 }
879
880#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
882#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
883#[derive(Deserialize, Serialize, Debug, Clone)]
884pub enum SecretVisibility {
885 Integrations,
897}
898
899#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
901#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
902#[derive(Deserialize, Serialize, Debug, Clone)]
903pub struct Secret {
904 pub name: String,
906 pub source: SecretSource,
908 pub visibility: SecretVisibility,
910}
911
912#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
914#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
915#[derive(Deserialize, Serialize, Debug, Clone)]
916pub struct FlagOption {
917 pub idx: u8,
919 pub name: String,
921 pub percentage: u8,
924}
925
926#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
933#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
934#[derive(Deserialize, Serialize, Debug, Clone)]
935pub struct Flag {
936 pub idx: u8,
938 pub name: String,
940 pub options: Vec<FlagOption>,
943}
944
945#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
946#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
947#[derive(Deserialize, Serialize, Debug, Clone)]
948pub struct ContentDefinition {
949 pub idx: u8,
951
952 pub name: String,
954
955 pub fields: Vec<Field>,
960
961 #[serde(skip_serializing_if = "Option::is_none")]
963 #[serde(default)]
964 pub lifecycle: Option<ContentObjectLifecycle>,
965}
966
967#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
968#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
969#[derive(Deserialize, Serialize, Debug, Clone)]
970pub struct ContentObjectLifecycle {
971 #[serde(skip_serializing_if = "Option::is_none")]
973 #[serde(default)]
974 pub before_all: Option<Vec<Vec<String>>>,
975 #[serde(skip_serializing_if = "Option::is_none")]
981 #[serde(default)]
982 pub on_add: Option<LifecycleBeforeAfterScripts>,
983 #[serde(skip_serializing_if = "Option::is_none")]
989 #[serde(default)]
990 pub on_edit: Option<LifecycleBeforeAfterScripts>,
991 #[serde(skip_serializing_if = "Option::is_none")]
997 #[serde(default)]
998 pub on_delete: Option<LifecycleBeforeAfterScripts>,
999}
1000
1001#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1007#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1008#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1009pub struct Content {
1010 pub file_path: String,
1013 pub definitions: Vec<ContentDefinition>,
1016
1017 #[serde(skip_serializing_if = "Option::is_none")]
1018 #[serde(default)]
1019 pub update: Option<ContentUpdateConfig>,
1020}
1021
1022#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1023#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1024#[derive(Deserialize, Serialize, Debug, Clone)]
1025pub struct ContentUpdateConfig {
1026 #[serde(skip_serializing_if = "Option::is_none")]
1027 #[serde(default)]
1028 pub lifecycle: Option<LifecycleBeforeAfterScripts>,
1029}
1030
1031#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1032#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1033#[derive(Deserialize, Serialize, Debug, Clone)]
1034pub enum UuidVersion {
1035 V4,
1036 V7,
1037}
1038
1039#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1041#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1042#[derive(Deserialize, Serialize, Debug, Clone)]
1043pub struct ModelConfig {
1044 pub idx: u8,
1051 pub name: String,
1053 pub fields: Vec<Field>,
1055 #[serde(skip_serializing_if = "Option::is_none")]
1059 #[serde(default)]
1060 pub uuid: Option<UuidVersion>,
1061}
1062
1063#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1064#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1065#[derive(Deserialize, Serialize, Debug, Clone)]
1066pub enum IntegrationProtocolHttpEncoding {
1067 Json,
1068 Text,
1069 None,
1070}
1071
1072#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1074#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1075#[derive(Deserialize, Serialize, Debug, Clone)]
1076pub enum IntegrationProtocol {
1077 Http {
1079 method: String,
1081 headers: Vec<(String, String)>,
1083 send_encoding: IntegrationProtocolHttpEncoding,
1085 recv_encoding: IntegrationProtocolHttpEncoding,
1087 },
1088 }
1101
1102#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1105#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1106#[derive(Deserialize, Serialize, Debug, Clone)]
1107pub struct IntegrationConfig {
1108 pub idx: u8,
1110 pub name: String,
1112 pub protocol: IntegrationProtocol,
1114 pub endpoint: String,
1116 pub send: Kind,
1120 pub recv: Kind,
1124 #[serde(skip_serializing_if = "Option::is_none")]
1126 #[serde(default)]
1127 pub secrets: Option<Vec<String>>,
1128
1129 #[serde(skip_serializing_if = "Option::is_none")]
1133 #[serde(default)]
1134 pub timeout: Option<u16>,
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 enum ActionLang {
1144 Rust,
1148 JavaScript,
1154 }
1158
1159#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1161#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1162#[derive(Deserialize, Serialize, Debug, Clone)]
1163pub enum ActionAccessModelOps {
1164 Insert,
1166 Get,
1168 Query,
1170 Search,
1172 Update,
1174 Delete,
1176}
1177
1178#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1180#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1181#[derive(Deserialize, Serialize, Debug, Clone)]
1182pub enum ActionAccessAuthOps {
1183 SetTokenFields,
1186}
1187
1188#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1191#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1192#[derive(Deserialize, Serialize, Debug, Clone)]
1193pub enum ActionAccessPermission {
1194 Model {
1196 name: String,
1198 ops: Vec<ActionAccessModelOps>,
1201 },
1202 Content {
1204 name: String,
1206 },
1207 Integration {
1209 name: String,
1211 },
1212 Action {
1214 name: String,
1216 },
1217 Auth {
1219 ops: Vec<ActionAccessAuthOps>,
1221 },
1222}
1223
1224#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1225#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1226#[derive(Deserialize, Serialize, Debug, Clone)]
1227pub enum HttpMethod {
1228 PUT,
1229 POST,
1230 GET,
1231 DELETE,
1232}
1233
1234#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1235#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1236#[derive(Deserialize, Serialize, Debug, Clone)]
1237pub enum ActionTriggerModelOps {
1238 Insert,
1239 Update,
1240 Delete,
1241}
1242
1243#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1245#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1246#[derive(Deserialize, Serialize, Debug, Clone)]
1247pub enum ActionTrigger {
1248 Ordinary,
1250 Json {
1252 route: String,
1254 method: HttpMethod,
1256 },
1257 Form {
1259 route: String,
1261 method: HttpMethod,
1263 redirect: String,
1268 },
1269 Login,
1271 Registration,
1273 Content { name: String },
1275 }
1288
1289#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1290#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1291#[derive(Deserialize, Serialize, Debug, Clone)]
1292pub enum ActionFfiVersion {
1293 V1,
1294}
1295
1296#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1298#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1299#[derive(Deserialize, Serialize, Debug, Clone)]
1300pub enum ActionFfiSerialization {
1301 FlexBufferVector,
1302}
1303
1304#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1305#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1306#[derive(Deserialize, Serialize, Debug, Clone)]
1307pub struct ActionFfi {
1308 pub version: ActionFfiVersion,
1309 pub serialization: ActionFfiSerialization,
1310}
1311
1312#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1314#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1315#[derive(Deserialize, Serialize, Debug, Clone)]
1316pub struct ActionConfig {
1317 pub ffi: ActionFfi,
1319 pub idx: u8,
1321 pub name: String,
1323 pub lang: ActionLang,
1325 #[serde(skip_serializing_if = "Option::is_none")]
1327 #[serde(default)]
1328 pub dir_path: Option<String>,
1329 #[serde(skip_serializing_if = "Option::is_none")]
1332 #[serde(default)]
1333 pub protected: Option<Check>,
1334 #[serde(skip_serializing_if = "Option::is_none")]
1337 #[serde(default)]
1338 pub transactional: Option<bool>,
1339 pub access: Vec<ActionAccessPermission>,
1341 pub accepts: Kind,
1343 pub returns: Kind,
1345 pub triggered_by: Vec<ActionTrigger>,
1348
1349 #[serde(skip_serializing_if = "Option::is_none")]
1353 #[serde(default)]
1354 pub timeout: Option<u16>,
1355
1356 #[serde(skip_serializing_if = "Option::is_none")]
1357 #[serde(default)]
1358 pub cors: Option<HttpCors>,
1359
1360 #[serde(skip_serializing_if = "Option::is_none")]
1365 #[serde(default)]
1366 pub privileged: Option<bool>,
1367
1368 #[serde(skip_serializing_if = "Option::is_none")]
1372 #[serde(default)]
1373 pub variables: Option<Vec<String>>,
1374
1375 #[serde(skip_serializing_if = "Option::is_none")]
1377 #[serde(default)]
1378 pub middlewares: Option<Vec<String>>,
1379}
1380
1381#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1382#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1383#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1384pub struct ErrorConfig {
1385 #[serde(skip_serializing_if = "Option::is_none")]
1389 #[serde(default)]
1390 pub template: Option<String>,
1391
1392 #[serde(skip_serializing_if = "Option::is_none")]
1396 #[serde(default)]
1397 pub asset: Option<String>,
1398}
1399
1400#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1401#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1402#[derive(Deserialize, Serialize, Debug, Clone)]
1403pub enum RuntimeMode {
1404 Shared,
1407 SingleThreaded,
1410 MultiThreaded,
1413}
1414
1415#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1416#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1417#[derive(Deserialize, Serialize, Debug, Clone)]
1418pub enum HttpCorsAllowHeaders {
1419 Any,
1420 Headers(Vec<String>),
1422}
1423
1424#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1425#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1426#[derive(Deserialize, Serialize, Debug, Clone)]
1427pub enum HttpCorsExposeHeaders {
1428 Any,
1429 Headers(Vec<String>),
1431}
1432
1433#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1434#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1435#[derive(Deserialize, Serialize, Debug, Clone)]
1436pub enum HttpCorsAllowMethods {
1437 Any,
1438 Methods(Vec<String>),
1440}
1441
1442#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1443#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1444#[derive(Deserialize, Serialize, Debug, Clone)]
1445pub enum HttpCorsAllowOrigin {
1446 Any,
1447 Origins(Vec<String>),
1449}
1450
1451#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1452#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1453#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1454pub struct HttpCors {
1455 pub allow_credentials: Option<bool>,
1456
1457 pub allow_headers: Option<HttpCorsAllowHeaders>,
1458
1459 pub max_age: Option<u32>,
1461
1462 pub allow_methods: Option<HttpCorsAllowMethods>,
1463
1464 pub allow_origin: Option<HttpCorsAllowOrigin>,
1465
1466 pub expose_headers: Option<HttpCorsExposeHeaders>,
1467
1468 pub allow_private_network: Option<bool>,
1469}
1470
1471impl HttpCors {
1472 #[must_use]
1473 pub fn overwrite(&self, base: &Self) -> Self {
1474 Self {
1475 allow_credentials: if self.allow_credentials.is_none() {
1476 base.allow_credentials
1477 } else {
1478 self.allow_credentials
1479 },
1480 allow_headers: if self.allow_headers.is_none() {
1481 base.allow_headers.clone()
1482 } else {
1483 self.allow_headers.clone()
1484 },
1485 max_age: if self.max_age.is_none() {
1486 base.max_age
1487 } else {
1488 self.max_age
1489 },
1490 allow_methods: if self.allow_methods.is_none() {
1491 base.allow_methods.clone()
1492 } else {
1493 self.allow_methods.clone()
1494 },
1495 allow_origin: if self.allow_origin.is_none() {
1496 base.allow_origin.clone()
1497 } else {
1498 self.allow_origin.clone()
1499 },
1500 expose_headers: if self.expose_headers.is_none() {
1501 base.expose_headers.clone()
1502 } else {
1503 self.expose_headers.clone()
1504 },
1505 allow_private_network: if self.allow_private_network.is_none() {
1506 base.allow_private_network
1507 } else {
1508 self.allow_private_network
1509 },
1510 }
1511 }
1512}
1513
1514#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1515#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1516#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1517pub struct HttpCsp {
1518 #[serde(skip_serializing_if = "Option::is_none")]
1520 #[serde(default)]
1521 pub default_src: Option<String>,
1522
1523 #[serde(skip_serializing_if = "Option::is_none")]
1526 #[serde(default)]
1527 pub script_src: Option<String>,
1528
1529 #[serde(skip_serializing_if = "Option::is_none")]
1532 #[serde(default)]
1533 pub style_src: Option<String>,
1534
1535 #[serde(skip_serializing_if = "Option::is_none")]
1539 #[serde(default)]
1540 pub font_src: Option<String>,
1541
1542 #[serde(skip_serializing_if = "Option::is_none")]
1546 #[serde(default)]
1547 pub img_src: Option<String>,
1548
1549 #[serde(skip_serializing_if = "Option::is_none")]
1553 #[serde(default)]
1554 pub frame_src: Option<String>,
1555
1556 #[serde(skip_serializing_if = "Option::is_none")]
1558 #[serde(default)]
1559 pub include_inline_hashes: Option<bool>,
1560}
1561
1562impl HttpCsp {
1563 #[must_use]
1564 #[allow(clippy::too_many_lines)]
1565 pub fn build_string(
1566 &self,
1567 base: &Self,
1568 inline_style_hashes: Option<Vec<String>>,
1569 inline_script_hashes: Option<Vec<String>>,
1570 script_urls: Option<Vec<String>>,
1571 secure: bool,
1572 has_wasm: bool,
1573 ) -> String {
1574 let mut out = String::new();
1575
1576 let include_inline_hashes = self
1577 .include_inline_hashes
1578 .unwrap_or(base.include_inline_hashes.unwrap_or(true));
1579
1580 let default_src = self
1581 .default_src
1582 .clone()
1583 .unwrap_or(base.default_src.clone().unwrap_or("'self'".to_string()));
1584
1585 if !default_src.is_empty() {
1586 out.push_str("default-src ");
1587 out.push_str(default_src.as_str());
1588 out.push_str("; ");
1589 }
1590
1591 let mut script_src = self
1592 .script_src
1593 .clone()
1594 .unwrap_or(base.script_src.clone().unwrap_or_default());
1595
1596 if include_inline_hashes
1597 && let Some(script_hashes) = inline_script_hashes
1598 && !script_hashes.is_empty()
1599 {
1600 if script_src.is_empty() {
1601 script_src.push_str("'self'");
1602 if has_wasm {
1603 script_src.push_str(" 'wasm-unsafe-eval'");
1604 }
1605 }
1606
1607 for hash in script_hashes {
1608 script_src.push_str(" '");
1609 script_src.push_str(hash.as_str());
1610 script_src.push('\'');
1611 }
1612 }
1613
1614 if let Some(script_urls) = script_urls {
1615 if script_src.is_empty() {
1616 script_src.push_str("'self'");
1617 }
1618
1619 for script_url in script_urls {
1620 script_src.push(' ');
1621 script_src.push_str(&script_url);
1622 }
1623 }
1624
1625 if !script_src.is_empty() {
1626 out.push_str("script-src ");
1627 out.push_str(script_src.as_str());
1628 out.push_str("; ");
1629 }
1630
1631 let mut style_src = self
1632 .style_src
1633 .clone()
1634 .unwrap_or(base.style_src.clone().unwrap_or_default());
1635
1636 if include_inline_hashes
1637 && let Some(style_hashes) = inline_style_hashes
1638 && !style_hashes.is_empty()
1639 {
1640 if style_src.is_empty() {
1641 style_src.push_str("'self'");
1642 }
1643
1644 for hash in style_hashes {
1645 style_src.push_str(" '");
1646 style_src.push_str(hash.as_str());
1647 style_src.push('\'');
1648 }
1649 }
1650
1651 if !style_src.is_empty() {
1652 out.push_str("style-src ");
1653 out.push_str(style_src.as_str());
1654 out.push_str("; ");
1655 }
1656
1657 let font_src = self
1658 .font_src
1659 .clone()
1660 .unwrap_or(base.font_src.clone().unwrap_or_default());
1661 if !font_src.is_empty() {
1662 out.push_str("font-src ");
1663 out.push_str(font_src.as_str());
1664 out.push_str("; ");
1665 }
1666
1667 let img_src = self
1668 .img_src
1669 .clone()
1670 .unwrap_or(base.img_src.clone().unwrap_or_default());
1671 if !img_src.is_empty() {
1672 out.push_str("img-src ");
1673 out.push_str(img_src.as_str());
1674 out.push_str("; ");
1675 }
1676
1677 let frame_src = self
1678 .frame_src
1679 .clone()
1680 .unwrap_or(base.frame_src.clone().unwrap_or_default());
1681 if !frame_src.is_empty() {
1682 out.push_str("frame-src ");
1683 out.push_str(frame_src.as_str());
1684 out.push_str("; ");
1685 }
1686
1687 if secure {
1688 out.push_str("upgrade-insecure-requests; ");
1689 }
1690
1691 out.push_str("report-to csp");
1692
1693 out = out.trim().to_string();
1694 out
1695 }
1696}
1697
1698#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1699#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1700#[derive(Deserialize, Serialize, Debug, Clone)]
1701pub struct LifecycleBeforeAfterScripts {
1702 #[serde(skip_serializing_if = "Option::is_none")]
1703 #[serde(default)]
1704 pub before: Option<Vec<Vec<String>>>,
1705 #[serde(skip_serializing_if = "Option::is_none")]
1706 #[serde(default)]
1707 pub after: Option<Vec<Vec<String>>>,
1708}
1709
1710#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1711#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1712#[derive(Deserialize, Serialize, Debug, Clone)]
1713pub struct TopLevelLifecycle {
1714 pub before_all: Option<Vec<Vec<String>>>,
1716
1717 #[serde(skip_serializing_if = "Option::is_none")]
1719 #[serde(default)]
1720 pub build: Option<LifecycleBeforeAfterScripts>,
1721}
1722
1723#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1724#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1725#[derive(Deserialize, Serialize, Debug, Clone)]
1726pub enum RedirectMethod {
1727 Temporary,
1729 Permanent,
1731}
1732
1733impl Display for RedirectMethod {
1734 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1735 match self {
1736 Self::Temporary => write!(f, "TEMPORARY"),
1737 Self::Permanent => write!(f, "PERMANENT"),
1738 }
1739 }
1740}
1741
1742#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1743#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1744#[derive(Deserialize, Serialize, Debug, Clone)]
1745pub struct HostRedirect {
1746 pub from: String,
1748 pub to: String,
1750 pub method: RedirectMethod,
1752}
1753
1754#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1755#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1756#[derive(Deserialize, Serialize, Debug, Clone)]
1757pub struct RouteRedirect {
1758 pub condition: String,
1760 pub rule: (String, String),
1763 pub method: RedirectMethod,
1765}
1766
1767#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1768#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1769#[derive(Deserialize, Serialize, Debug, Clone)]
1770pub struct Redirects {
1771 #[serde(skip_serializing_if = "Option::is_none")]
1773 #[serde(default)]
1774 pub host: Option<Vec<HostRedirect>>,
1775 #[serde(skip_serializing_if = "Option::is_none")]
1777 #[serde(default)]
1778 pub route: Option<Vec<RouteRedirect>>,
1779}
1780
1781#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1788#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1789#[derive(Deserialize, Serialize, Debug, Clone)]
1790pub struct ProxyConfig {
1791 #[serde(skip_serializing_if = "Option::is_none")]
1798 #[serde(default)]
1799 pub path: Option<String>,
1800 #[serde(skip_serializing_if = "Option::is_none")]
1807 #[serde(default)]
1808 pub domain: Option<String>,
1809
1810 #[serde(skip_serializing_if = "Option::is_none")]
1818 #[serde(default)]
1819 pub port: Option<u16>,
1820
1821 pub target: String,
1825
1826 #[serde(skip_serializing_if = "Option::is_none")]
1828 #[serde(default)]
1829 pub middlewares: Option<Vec<String>>,
1830}
1831
1832#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1833#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1834#[derive(Deserialize, Serialize, Debug, Clone)]
1835pub enum MiddlewareMechanism {
1836 Request { endpoint: String },
1837}
1838
1839#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1840#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1841#[derive(Deserialize, Serialize, Debug, Clone)]
1842pub enum MiddlewareValidationRule {
1843 #[cfg_attr(feature = "utoipa", schema(no_recursion))]
1845 Any(Vec<Box<MiddlewareValidationRule>>),
1846 #[cfg_attr(feature = "utoipa", schema(no_recursion))]
1848 All(Vec<Box<MiddlewareValidationRule>>),
1849 #[cfg_attr(feature = "utoipa", schema(no_recursion))]
1851 Not(Box<MiddlewareValidationRule>),
1852
1853 StatusCode(u16),
1856}
1857
1858#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1859#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1860#[derive(Deserialize, Serialize, Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
1861pub enum MiddlewareValidationComponent {
1862 Headers,
1864 Path,
1866 Query,
1868}
1869
1870#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1871#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1872#[derive(Deserialize, Serialize, Debug, Clone)]
1873pub enum MiddlewareOperation {
1874 Validate {
1875 rule: MiddlewareValidationRule,
1876 components: BTreeSet<MiddlewareValidationComponent>,
1877 },
1878}
1879
1880#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1882#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1883#[derive(Deserialize, Serialize, Debug, Clone)]
1884pub struct MiddlewareConfig {
1885 pub name: String,
1888
1889 pub operation: MiddlewareOperation,
1891
1892 pub mechanism: MiddlewareMechanism,
1894}
1895
1896#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1898#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1899#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1900pub struct OrdinaryConfig {
1901 #[serde(skip_serializing_if = "Option::is_none")]
1902 #[serde(default)]
1903 pub lifecycle: Option<TopLevelLifecycle>,
1904
1905 pub domain: String,
1908
1909 #[serde(skip_serializing_if = "Option::is_none")]
1915 #[serde(default)]
1916 pub cnames: Option<Vec<String>>,
1917
1918 #[serde(skip_serializing_if = "Option::is_none")]
1928 #[serde(default)]
1929 pub canonical: Option<String>,
1930
1931 #[serde(skip_serializing_if = "Option::is_none")]
1933 #[serde(default)]
1934 pub redirects: Option<Redirects>,
1935
1936 #[serde(skip_serializing_if = "Option::is_none")]
1938 #[serde(default)]
1939 pub proxies: Option<Vec<ProxyConfig>>,
1940
1941 #[serde(skip_serializing_if = "Option::is_none")]
1944 #[serde(default)]
1945 pub middlewares: Option<Vec<MiddlewareConfig>>,
1946
1947 #[serde(skip)]
1948 #[serde(default)]
1949 pub internal_middlewares: Option<HashMap<String, MiddlewareConfig>>,
1950
1951 #[serde(skip_serializing_if = "Option::is_none")]
1954 #[serde(default)]
1955 pub contacts: Option<Vec<String>>,
1956
1957 #[serde(skip_serializing_if = "Option::is_none")]
1959 #[serde(default)]
1960 pub hide_contacts: Option<bool>,
1961
1962 pub version: String,
1964
1965 #[serde(skip_serializing_if = "Option::is_none")]
1967 #[serde(default = "OrdinaryConfig::default_storage_size")]
1968 pub storage_size: Option<u64>,
1969
1970 #[serde(skip_serializing_if = "Option::is_none")]
1974 #[serde(default)]
1975 pub default_timeout: Option<u16>,
1976
1977 #[serde(skip_serializing_if = "Option::is_none")]
1987 #[serde(default)]
1988 pub csp: Option<HttpCsp>,
1989
1990 #[serde(skip_serializing_if = "Option::is_none")]
1991 #[serde(default)]
1992 pub cors: Option<HttpCors>,
1993
1994 #[serde(skip_serializing_if = "Option::is_none")]
1998 #[serde(default)]
1999 pub runtime: Option<RuntimeMode>,
2000
2001 #[serde(skip_serializing_if = "Option::is_none")]
2007 #[serde(default)]
2008 pub hide_schema: Option<bool>,
2009
2010 #[serde(skip_serializing_if = "Option::is_none")]
2012 #[serde(default)]
2013 pub client_events: Option<bool>,
2014
2015 #[serde(skip_serializing_if = "Option::is_none")]
2017 #[serde(default)]
2018 pub port: Option<u16>,
2019 #[serde(skip_serializing_if = "Option::is_none")]
2022 #[serde(default)]
2023 pub redirect_port: Option<u16>,
2024
2025 #[serde(skip_serializing_if = "Option::is_none")]
2026 #[serde(default)]
2027 pub logging: Option<LoggingConfig>,
2028 #[serde(skip_serializing_if = "Option::is_none")]
2033 #[serde(default)]
2034 pub error: Option<ErrorConfig>,
2035 #[serde(skip_serializing_if = "Option::is_none")]
2037 #[serde(default)]
2038 pub auth: Option<AuthConfig>,
2039 #[serde(skip_serializing_if = "Option::is_none")]
2041 #[serde(default)]
2042 pub globals: Option<Vec<Global>>,
2043 #[serde(skip_serializing_if = "Option::is_none")]
2045 #[serde(default)]
2046 pub secrets: Option<Vec<Secret>>,
2047 #[serde(skip_serializing_if = "Option::is_none")]
2050 #[serde(default)]
2051 pub flags: Option<Vec<Flag>>,
2052 #[serde(skip_serializing_if = "Option::is_none")]
2057 #[serde(default)]
2058 pub content: Option<Content>,
2059 #[serde(skip_serializing_if = "Option::is_none")]
2061 #[serde(default)]
2062 pub models: Option<Vec<ModelConfig>>,
2063 #[serde(skip_serializing_if = "Option::is_none")]
2066 #[serde(default)]
2067 pub integrations: Option<Vec<IntegrationConfig>>,
2068 #[serde(skip_serializing_if = "Option::is_none")]
2071 #[serde(default)]
2072 pub actions: Option<Vec<ActionConfig>>,
2073 #[serde(skip_serializing_if = "Option::is_none")]
2077 #[serde(default)]
2078 pub assets: Option<AssetsConfig>,
2079 #[serde(skip_serializing_if = "Option::is_none")]
2081 #[serde(default)]
2082 pub fragments: Option<FragmentsConfig>,
2083 #[serde(skip_serializing_if = "Option::is_none")]
2096 #[serde(default)]
2097 pub templates: Option<Vec<TemplateConfig>>,
2098}
2099
2100impl OrdinaryConfig {
2101 pub fn get(proj_path: impl AsRef<Path>, load_refs: bool) -> anyhow::Result<OrdinaryConfig> {
2103 let path = proj_path.as_ref().join("ordinary.json");
2104 let mut config_json = fs::read(&path)?;
2106
2107 let mut config = match simd_json::from_slice::<OrdinaryConfig>(&mut config_json) {
2108 Ok(config) => config,
2109 Err(err) => bail!("{}: {err}", path.display()),
2110 };
2111
2112 if load_refs {
2113 config.load_refs(proj_path.as_ref())?;
2114 }
2115
2116 config.load_internal();
2117
2118 if let Some(content) = config.content.as_mut() {
2119 content.definitions.sort_by_key(|d| d.idx);
2121 }
2122
2123 if let Some(templates) = config.templates.as_mut() {
2124 templates.sort_by_key(|t| t.idx);
2125 }
2126
2127 if let Some(models) = config.models.as_mut() {
2128 models.sort_by_key(|m| m.idx);
2130 }
2131
2132 if let Some(actions) = config.actions.as_mut() {
2133 actions.sort_by_key(|a| a.idx);
2134 }
2135
2136 Ok(config)
2137 }
2138
2139 pub fn write(&self, proj_path: &Path) -> anyhow::Result<()> {
2140 use std::io::Write;
2141
2142 let ordinary_json = serde_json::to_string_pretty(self)?;
2143
2144 let mut file = fs::File::create(proj_path.join("ordinary.json"))?;
2145 file.write_all(ordinary_json.as_bytes())?;
2146
2147 Ok(())
2148 }
2149
2150 fn load_refs(&mut self, proj_path: &Path) -> anyhow::Result<()> {
2151 if let Some(templates) = self.templates.as_mut() {
2152 for base_template in templates {
2153 if let Some(reference) = &base_template.r#ref {
2154 let json_path = proj_path.join(reference);
2155 let mut json_bytes = fs_err::read(json_path)?;
2156 let mut ref_template: TemplateConfig =
2157 simd_json::from_slice(json_bytes.as_mut_slice())?;
2158
2159 ref_template.load_managed();
2160
2161 ref_template.r#ref.clone_from(&base_template.r#ref);
2162
2163 if let Some(idx) = base_template.idx {
2164 ref_template.idx = Some(idx);
2165 }
2166
2167 if let Some(name) = &base_template.name {
2168 ref_template.name = Some(name.clone());
2169 }
2170
2171 if let Some(route) = &base_template.route {
2172 ref_template.route = Some(route.clone());
2173 }
2174
2175 if let Some(minify) = base_template.minify {
2176 ref_template.minify = Some(minify);
2177 }
2178
2179 if let Some(cache) = &base_template.cache {
2180 ref_template.cache = Some(cache.clone());
2181 }
2182
2183 if let Some(cors) = &base_template.cors {
2184 ref_template.cors = Some(cors.clone());
2185 }
2186
2187 if let Some(timeout) = base_template.timeout {
2188 ref_template.timeout = Some(timeout);
2189 }
2190
2191 *base_template = ref_template;
2192 }
2193 }
2194 }
2195
2196 Ok(())
2197 }
2198
2199 #[must_use]
2200 pub fn content_map(&self) -> HashMap<String, ContentDefinition> {
2201 let mut content_map = HashMap::new();
2202
2203 if let Some(content) = &self.content {
2204 for content_def in &content.definitions {
2205 content_map.insert(content_def.name.clone(), content_def.clone());
2206 }
2207 }
2208
2209 content_map
2210 }
2211
2212 #[must_use]
2213 pub fn model_map(&self) -> HashMap<String, ModelConfig> {
2214 let mut model_map = HashMap::new();
2215
2216 if let Some(models) = &self.models {
2217 for model_config in models {
2218 model_map.insert(model_config.name.clone(), model_config.clone());
2219 }
2220 }
2221
2222 model_map
2223 }
2224
2225 pub fn load_internal(&mut self) {
2226 self.load_internal_middlewares();
2227 self.load_internal_compression();
2228 }
2229
2230 fn load_internal_middlewares(&mut self) {
2231 if let Some(middlewares) = &self.middlewares {
2232 let mut map = HashMap::new();
2233
2234 for middleware in middlewares {
2235 map.insert(middleware.name.clone(), middleware.clone());
2236 }
2237
2238 self.internal_middlewares = Some(map);
2239 }
2240 }
2241
2242 fn load_internal_compression(&mut self) {
2243 if let Some(assets) = self.assets.as_mut()
2244 && let Some(precompression) = &assets.precompression
2245 {
2246 assets.internal_precompression = Some(precompression.get_list());
2247 }
2248
2249 if let Some(templates) = self.templates.as_mut() {
2250 for template in templates {
2251 if let Some(cache) = template.cache.as_mut()
2252 && let Some(stored) = cache.stored.as_mut()
2253 && let Some(compression) = &stored.compression
2254 {
2255 stored.internal_compression = Some(compression.get_list());
2256 }
2257 }
2258 }
2259 }
2260
2261 #[must_use]
2262 pub fn get_middlewares(&self, middleware_names: &Vec<String>) -> Option<Vec<MiddlewareConfig>> {
2263 let mut middleware_configs = vec![];
2264
2265 if let Some(middleware_map) = &self.internal_middlewares {
2266 for middleware in middleware_names {
2267 if let Some(middleware_config) = middleware_map.get(middleware) {
2268 middleware_configs.push(middleware_config.clone());
2269 }
2270 }
2271 }
2272
2273 if !middleware_configs.is_empty() {
2274 return Some(middleware_configs);
2275 }
2276
2277 None
2278 }
2279
2280 pub fn for_send(&self) -> anyhow::Result<OrdinaryConfig> {
2283 let mut config = self.clone();
2284
2285 config.lifecycle = None;
2286
2287 if let Some(assets) = config.assets.as_mut() {
2288 assets.dir_path = None;
2289 }
2290
2291 if let Some(content) = config.content.as_mut() {
2292 content.update = None;
2293
2294 for def in &mut content.definitions {
2295 def.lifecycle = None;
2296 }
2297 }
2298
2299 if let Some(templates) = config.templates.as_mut() {
2300 for template in templates {
2301 template.r#ref = None;
2302 template.build = None;
2303 template.bin = None;
2304 template.path = None;
2305 template.minify = None;
2306 }
2307 }
2308
2309 if let Some(actions) = config.actions.as_mut() {
2310 for action in actions {
2311 action.dir_path = None;
2312 }
2313 }
2314
2315 Ok(config)
2316 }
2317
2318 #[instrument(skip_all, err, level = "debug")]
2321 pub fn validate(&self) -> anyhow::Result<()> {
2322 validate(self)
2323 }
2324
2325 #[must_use]
2327 #[allow(clippy::unnecessary_wraps)]
2328 pub fn default_storage_size() -> Option<u64> {
2329 Some(5_000_000)
2330 }
2331 #[must_use]
2334 pub fn has_ordinary_actions(&self) -> bool {
2335 if let Some(actions) = &self.actions {
2336 actions
2337 .iter()
2338 .find(|a| {
2339 for trigger in &a.triggered_by {
2340 if let ActionTrigger::Ordinary = trigger {
2341 return true;
2342 }
2343 }
2344
2345 false
2346 })
2347 .is_some()
2348 } else {
2349 false
2350 }
2351 }
2352
2353 #[allow(clippy::too_many_lines)]
2358 pub fn check_config_against_limits(
2359 &self,
2360 limits: &OrdinaryApiLimits,
2361 privileged_domains: &HashSet<String>,
2362 ) -> anyhow::Result<()> {
2363 check_config_against_limits(self, limits, privileged_domains)
2364 }
2365
2366 pub fn exec_script(
2367 proj_path: &Path,
2368 argument: &Option<String>,
2369 name: &str,
2370 when: &str,
2371 scripts: &Vec<Vec<String>>,
2372 ) -> anyhow::Result<()> {
2373 let span = tracing::info_span!("lifecycle", %when, %name);
2374
2375 span.in_scope(|| {
2376 exec_script(proj_path, argument, scripts)?;
2377 anyhow::Ok(())
2378 })
2379 }
2380
2381 #[must_use]
2382 pub fn get_next_template_idx(&self) -> u8 {
2383 if let Some(templates) = &self.templates
2384 && let Some(last) = templates.last()
2385 && let Some(idx) = last.idx
2386 {
2387 idx + 1
2388 } else {
2389 0
2390 }
2391 }
2392
2393 pub fn check_template_name_exists(&self, name: &str) -> anyhow::Result<()> {
2394 if let Some(templates) = &self.templates {
2395 for template in templates {
2396 if template.name.as_deref() == Some(name) {
2397 bail!("template with name {name} already exists");
2398 }
2399 }
2400 }
2401
2402 Ok(())
2403 }
2404}
2405
2406pub fn exec_script(
2407 proj_path: &Path,
2408 argument: &Option<String>,
2409 scripts: &Vec<Vec<String>>,
2410) -> anyhow::Result<()> {
2411 let curr_dir = env::current_dir()?;
2412 env::set_current_dir(proj_path)?;
2413
2414 for script in scripts {
2415 let mut script_iter = script.iter();
2416
2417 if let Some(command) = script_iter.next() {
2418 let mut command_str = command.clone();
2419 let mut command = Command::new(command);
2420
2421 for arg in script_iter {
2422 write!(command_str, " {arg}")?;
2423 command.arg(arg);
2424 }
2425
2426 tracing::info!(cmd = %command_str, "exec");
2427
2428 let output = match &argument {
2429 Some(arg) => command.arg(arg).output()?,
2430 None => command.output()?,
2431 };
2432
2433 if !output.status.success() {
2434 let stderr = str::from_utf8(&output.stderr)?;
2435 let stdout = str::from_utf8(&output.stdout)?;
2436
2437 tracing::error!(%stderr, %stdout, "failed");
2438 bail!(stderr.to_string());
2439 }
2440 }
2441 }
2442
2443 env::set_current_dir(curr_dir)?;
2444
2445 Ok(())
2446}