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
8mod api;
13mod validate;
14
15pub use api::*;
16
17pub use crate::validate::DOMAIN_REGEX;
18use crate::validate::validate;
19use std::collections::BTreeSet;
20
21use anyhow::bail;
22use arrayvec::ArrayVec;
23use hashbrown::{HashMap, HashSet};
24use ordinary_types::{Field, Kind};
25use serde::{Deserialize, Serialize};
26use std::env;
27use std::fmt::{Display, Formatter, Write};
28use std::path::Path;
29use std::process::Command;
30use tracing::instrument;
31
32#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
33#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
34#[derive(Deserialize, Serialize, Debug, Clone)]
35pub struct ClientLoggingConfig {
36 #[serde(skip_serializing_if = "Option::is_none")]
38 #[serde(default)]
39 min_delay: Option<u32>,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 #[serde(default)]
43 max_delay: Option<u32>,
44 #[serde(skip_serializing_if = "Option::is_none")]
47 #[serde(default)]
48 max_buffer: Option<u16>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 #[serde(default)]
52 max_batch: Option<u16>,
53}
54
55#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
56#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
57#[derive(Deserialize, Serialize, Debug, Clone)]
58pub enum RedactedHashAlg {
59 Blake2,
60 Blake3,
61}
62
63#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
64#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
65#[derive(Deserialize, Serialize, Debug, Clone)]
66pub struct ServerLoggingConfig {
67 #[serde(skip_serializing_if = "Option::is_none")]
68 #[serde(default)]
69 pub ips: Option<bool>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 #[serde(default)]
72 pub headers: Option<bool>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 #[serde(default)]
75 pub credentials: Option<RedactedHashAlg>,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 #[serde(default)]
78 pub timing: Option<bool>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 #[serde(default)]
81 pub sizes: Option<bool>,
82}
83
84#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
85#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
86#[derive(Deserialize, Serialize, Debug, Clone)]
87pub struct LoggingConfig {
88 #[serde(skip_serializing_if = "Option::is_none")]
89 #[serde(default)]
90 pub client: Option<ClientLoggingConfig>,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 #[serde(default)]
93 pub server: Option<ServerLoggingConfig>,
94}
95
96#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
97#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
98#[derive(Deserialize, Serialize, Debug, Clone)]
99pub struct Check {
100 }
103
104#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
105#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
106#[derive(Deserialize, Serialize, Debug, Clone)]
107pub enum TokenAlgorithm {
108 HmacBlake2b256,
109}
110
111#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
113#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
114#[derive(Deserialize, Serialize, Debug, Clone)]
115pub struct RefreshTokenConfig {
116 pub algorithm: TokenAlgorithm,
118 pub lifetime: u32,
120 pub rotation: u32,
122}
123
124impl Default for RefreshTokenConfig {
125 fn default() -> RefreshTokenConfig {
126 RefreshTokenConfig {
127 algorithm: TokenAlgorithm::HmacBlake2b256,
128 lifetime: 60 * 60 * 24 * 7,
129 rotation: 60 * 60 * 24 * 7 * 2,
130 }
131 }
132}
133
134#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
136#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
137#[derive(Deserialize, Serialize, Debug, Clone)]
138pub struct AccessTokenConfig {
139 pub algorithm: TokenAlgorithm,
141 pub lifetime: u32,
143 pub rotation: u32,
145 pub claims: Vec<Field>,
149}
150
151impl Default for AccessTokenConfig {
152 fn default() -> AccessTokenConfig {
153 AccessTokenConfig {
154 algorithm: TokenAlgorithm::HmacBlake2b256,
155 lifetime: 60 * 60 * 24,
156 rotation: 60 * 60 * 24 * 3,
157 claims: vec![],
158 }
159 }
160}
161
162#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
169#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
170#[derive(Deserialize, Serialize, Debug, Clone)]
171pub enum ClientPasswordHash {
172 Sha256,
177}
178
179#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
181#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
182#[derive(Deserialize, Serialize, Debug, Clone)]
183pub enum PasswordProtocol {
184 Opaque,
194 }
196
197#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
199#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
200#[derive(Deserialize, Serialize, Debug, Clone)]
201pub struct PasswordConfig {
202 pub protocol: PasswordProtocol,
203}
204
205impl Default for PasswordConfig {
206 fn default() -> PasswordConfig {
207 PasswordConfig {
208 protocol: PasswordProtocol::Opaque,
209 }
210 }
211}
212
213#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
214#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
215#[derive(Deserialize, Serialize, Debug, Clone)]
216pub enum TotpAlgorithm {
217 Sha1,
221}
222
223#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
224#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
225#[derive(Deserialize, Serialize, Debug, Clone)]
226pub struct TotpConfig {
227 #[serde(skip_serializing_if = "Option::is_none")]
230 #[serde(default)]
231 pub template: Option<String>,
232 pub algorithm: TotpAlgorithm,
233}
234
235impl Default for TotpConfig {
236 fn default() -> TotpConfig {
237 TotpConfig {
238 template: None,
239 algorithm: TotpAlgorithm::Sha1,
240 }
241 }
242}
243
244#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
245#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
246#[derive(Serialize, Deserialize, Debug, Clone, Default)]
247pub struct MfaConfig {
248 pub totp: TotpConfig,
249}
250
251#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
252#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
253#[derive(Deserialize, Serialize, Debug, Clone)]
254pub enum InviteMode {
255 Root,
257 Admin,
259 Viral,
261 }
267
268#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
269#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
270#[derive(Deserialize, Serialize, Debug, Clone)]
271pub struct InviteConfig {
272 pub mode: InviteMode,
273 pub lifetime: u32,
277 pub clean_interval: (u32, u32),
281 #[serde(skip_serializing_if = "Option::is_none")]
294 #[serde(default)]
295 pub claims: Option<Vec<Field>>,
296}
297
298impl Default for InviteConfig {
299 fn default() -> InviteConfig {
300 InviteConfig {
301 mode: InviteMode::Viral,
302 lifetime: 60 * 60 * 24 * 7,
303 clean_interval: (30, 90),
304 claims: None,
305 }
306 }
307}
308
309#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
311#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
312#[derive(Deserialize, Serialize, Debug, Clone)]
313pub struct AuthConfig {
314 pub password: PasswordConfig,
316 pub mfa: MfaConfig,
318 pub refresh_token: RefreshTokenConfig,
320 pub access_token: AccessTokenConfig,
322 pub cookies_enabled: bool,
333 pub client_hash: ClientPasswordHash,
335
336 #[serde(skip_serializing_if = "Option::is_none")]
338 #[serde(default)]
339 pub invite: Option<InviteConfig>,
340}
341
342impl Default for AuthConfig {
343 fn default() -> AuthConfig {
344 AuthConfig {
345 password: PasswordConfig::default(),
346 mfa: MfaConfig::default(),
347 refresh_token: RefreshTokenConfig::default(),
348 access_token: AccessTokenConfig::default(),
349 cookies_enabled: false,
350 client_hash: ClientPasswordHash::Sha256,
351 invite: None,
352 }
353 }
354}
355
356#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
358#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
359#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
360pub enum CompressionAlgorithm {
361 All,
362 Gzip,
363 Zstd { level: u8 },
364 Brotli,
365 Deflate,
366}
367
368impl CompressionAlgorithm {
369 #[must_use]
370 pub fn as_u8(&self) -> u8 {
371 match self {
372 Self::All => 0,
373 Self::Gzip => 1,
374 Self::Zstd { level: _ } => 2,
375 Self::Brotli => 3,
376 Self::Deflate => 4,
377 }
378 }
379
380 #[must_use]
381 pub fn as_char(&self) -> char {
382 match self {
383 Self::All => '0',
384 Self::Gzip => '1',
385 Self::Zstd { level: _ } => '2',
386 Self::Brotli => '3',
387 Self::Deflate => '4',
388 }
389 }
390
391 #[must_use]
392 pub fn as_str(&self) -> &'static str {
393 match self {
394 Self::All => "",
395 Self::Gzip => "gzip",
396 Self::Zstd { level: _ } => "zstd",
397 Self::Brotli => "br",
398 Self::Deflate => "deflate",
399 }
400 }
401}
402
403#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
404#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
405#[derive(Deserialize, Serialize, Debug, Clone)]
406pub enum StoredCachePolicy {
407 Permanent,
411 FRs(u64, u64),
415}
416
417#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
422#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
423#[derive(Deserialize, Serialize, Debug, Clone)]
424pub struct StoredCache {
425 pub policy: StoredCachePolicy,
426
427 #[serde(skip_serializing_if = "Option::is_none")]
429 #[serde(default)]
430 pub compression: Option<CompressionAlgorithms>,
431
432 #[serde(skip)]
433 #[serde(default)]
434 pub internal_compression: Option<ArrayVec<CompressionAlgorithm, 4>>,
435
436 #[serde(skip_serializing_if = "Option::is_none")]
440 #[serde(default)]
441 pub max_ttl: Option<u64>,
442
443 #[serde(skip_serializing_if = "Option::is_none")]
447 #[serde(default)]
448 pub hit_ttl: Option<u64>,
449
450 #[serde(skip_serializing_if = "Option::is_none")]
455 #[serde(default)]
456 pub max_size: Option<u64>,
457
458 #[serde(skip_serializing_if = "Option::is_none")]
461 #[serde(default)]
462 pub max_count: Option<usize>,
463
464 #[serde(skip_serializing_if = "Option::is_none")]
468 #[serde(default)]
469 pub frequency_window: Option<u64>,
470
471 #[serde(skip_serializing_if = "Option::is_none")]
475 #[serde(default)]
476 pub clean_interval: Option<(u64, u64)>,
477
478 #[serde(skip_serializing_if = "Option::is_none")]
481 #[serde(default)]
482 pub evict_on_dependency_change: Option<bool>,
483}
484
485#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
486#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
487#[derive(Deserialize, Serialize, Debug, Clone)]
488pub enum XXH3Variation {
489 Bit64,
490 Bit128,
491}
492
493#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
497#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
498#[derive(Deserialize, Serialize, Debug, Clone)]
499pub enum HttpEtagAlgorithm {
500 AHash,
501 XXH3(XXH3Variation),
502 Rustc,
503 Blake3,
504}
505
506#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
509#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
510#[derive(Deserialize, Serialize, Debug, Clone)]
511pub struct HttpEtag {
512 #[serde(skip_serializing_if = "Option::is_none")]
513 #[serde(default)]
514 pub alg: Option<HttpEtagAlgorithm>,
515}
516
517#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
520#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
521#[derive(Deserialize, Serialize, Debug, Clone, Default)]
522pub struct HttpCacheControl {
523 #[serde(skip_serializing_if = "Option::is_none")]
524 #[serde(default)]
525 pub max_age: Option<usize>,
526 #[serde(skip_serializing_if = "Option::is_none")]
527 #[serde(default)]
528 pub s_maxage: Option<usize>,
529 #[serde(skip_serializing_if = "Option::is_none")]
530 #[serde(default)]
531 pub no_cache: Option<bool>,
532 #[serde(skip_serializing_if = "Option::is_none")]
533 #[serde(default)]
534 pub no_store: Option<bool>,
535 #[serde(skip_serializing_if = "Option::is_none")]
536 #[serde(default)]
537 pub no_transform: Option<bool>,
538 #[serde(skip_serializing_if = "Option::is_none")]
539 #[serde(default)]
540 pub must_revalidate: Option<bool>,
541 #[serde(skip_serializing_if = "Option::is_none")]
542 #[serde(default)]
543 pub proxy_revalidate: Option<bool>,
544 #[serde(skip_serializing_if = "Option::is_none")]
545 #[serde(default)]
546 pub private: Option<bool>,
547 #[serde(skip_serializing_if = "Option::is_none")]
548 #[serde(default)]
549 pub public: Option<bool>,
550 #[serde(skip_serializing_if = "Option::is_none")]
551 #[serde(default)]
552 pub immutable: Option<bool>,
553 #[serde(skip_serializing_if = "Option::is_none")]
554 #[serde(default)]
555 pub stale_while_revalidate: Option<bool>,
556 #[serde(skip_serializing_if = "Option::is_none")]
557 #[serde(default)]
558 pub stale_if_error: Option<bool>,
559}
560
561impl HttpCacheControl {
562 pub fn header_value(&self, cache_control: &mut String, default: &str) -> anyhow::Result<()> {
563 if let Some(max_age) = self.max_age {
564 write!(cache_control, "max-age={max_age}, ")?;
565 }
566 if let Some(s_maxage) = self.s_maxage {
567 write!(cache_control, "s-maxage={s_maxage}, ")?;
568 }
569 if let Some(no_cache) = self.no_cache
570 && no_cache
571 {
572 write!(cache_control, "no-cache, ")?;
573 }
574 if let Some(no_store) = self.no_store
575 && no_store
576 {
577 write!(cache_control, "no-store, ")?;
578 }
579 if let Some(no_transform) = self.no_transform
580 && no_transform
581 {
582 write!(cache_control, "no-transform, ")?;
583 }
584 if let Some(must_revalidate) = self.must_revalidate
585 && must_revalidate
586 {
587 write!(cache_control, "must-revalidate, ")?;
588 }
589 if let Some(proxy_revalidate) = self.proxy_revalidate
590 && proxy_revalidate
591 {
592 write!(cache_control, "proxy-revalidate, ")?;
593 }
594 if let Some(stale_while_revalidate) = self.stale_while_revalidate
595 && stale_while_revalidate
596 {
597 write!(cache_control, "stale-while-revalidate, ")?;
598 }
599 if let Some(private) = self.private
600 && private
601 {
602 write!(cache_control, "private, ")?;
603 }
604 if let Some(public) = self.public
605 && public
606 {
607 write!(cache_control, "public, ")?;
608 }
609 if let Some(immutable) = self.immutable
610 && immutable
611 {
612 write!(cache_control, "immutable, ")?;
613 }
614 if let Some(stale_if_error) = self.stale_if_error
615 && stale_if_error
616 {
617 write!(cache_control, "stale-if-error, ")?;
618 }
619
620 if cache_control.is_empty() {
621 cache_control.push_str(default);
622 } else {
623 cache_control.pop();
624 cache_control.pop();
625 }
626
627 Ok(())
628 }
629}
630
631#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
632#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
633#[derive(Deserialize, Serialize, Debug, Clone, Default)]
634pub struct HttpCache {
635 #[serde(skip_serializing_if = "Option::is_none")]
636 #[serde(default)]
637 pub cache_control: Option<HttpCacheControl>,
638
639 #[serde(skip_serializing_if = "Option::is_none")]
644 #[serde(default)]
645 pub expires: Option<u64>,
646
647 #[serde(skip_serializing_if = "Option::is_none")]
648 #[serde(default)]
649 pub etag: Option<HttpEtag>,
650}
651
652#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
654#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
655#[derive(Deserialize, Serialize, Debug, Clone)]
656pub struct TemplateField {
657 pub name: String,
659 pub kind: Kind,
661 pub value: serde_json::Value,
663}
664
665#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
667#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
668#[derive(Deserialize, Serialize, Debug, Clone)]
669pub enum QueryExpression {
670 Gte,
671 Gt,
672 Lte,
673 Lt,
674 Eq,
675 BeginsWith,
676}
677
678#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
680#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
681#[derive(Deserialize, Serialize, Debug, Clone)]
682pub enum TemplateRefFieldBind {
683 Token {
685 field: String,
690 #[serde(skip_serializing_if = "Option::is_none")]
692 #[serde(default)]
693 expression: Option<QueryExpression>,
694 },
695 Segment {
697 name: String,
699 #[serde(skip_serializing_if = "Option::is_none")]
701 #[serde(default)]
702 expression: Option<QueryExpression>,
703 },
704}
705
706#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
709#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
710#[derive(Deserialize, Serialize, Debug, Clone, Default)]
711pub struct TemplateRefField {
712 pub idx: u8,
715 pub name: String,
717 #[serde(skip_serializing_if = "Option::is_none")]
720 #[serde(default)]
721 pub bind: Option<TemplateRefFieldBind>,
722 #[cfg_attr(feature = "utoipa", schema(no_recursion))]
724 #[serde(skip_serializing_if = "Option::is_none")]
725 #[serde(default)]
726 pub fields: Option<Vec<TemplateRefField>>,
727}
728
729#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
733#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
734#[derive(Deserialize, Serialize, Debug, Clone)]
735pub struct TemplateRef {
736 pub idx: u8,
740 pub name: String,
742 pub fields: Vec<TemplateRefField>,
744 #[serde(skip_serializing_if = "Option::is_none")]
748 #[serde(default)]
749 pub all: Option<String>,
750}
751
752#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
755#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
756#[derive(Deserialize, Serialize, Debug, Clone)]
757pub struct TemplateFlagRef {
758 pub idx: u8,
762 pub name: String,
764}
765
766#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
767#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
768#[derive(Deserialize, Serialize, Debug, Clone)]
769pub struct TemplateParamRef {
770 pub idx: u8,
774 pub name: String,
776}
777
778#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
779#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
780#[derive(Deserialize, Serialize, Debug, Clone)]
781pub struct TemplateCache {
782 #[serde(skip_serializing_if = "Option::is_none")]
783 #[serde(default)]
784 pub stored: Option<StoredCache>,
785
786 #[serde(skip_serializing_if = "Option::is_none")]
787 #[serde(default)]
788 pub http: Option<HttpCache>,
789}
790
791#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
795#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
796#[derive(Deserialize, Serialize, Debug, Clone)]
797pub enum WasmOpt {
798 Size,
799 SizeAggressive,
800 Level0,
801 Level1,
802 Level2,
803 Level3,
804 Level4,
805}
806
807impl WasmOpt {
808 #[must_use]
809 pub fn as_flag(&self) -> &'static str {
810 match self {
811 Self::Size => "-Os",
812 Self::SizeAggressive => "-Oz",
813 Self::Level0 => "-O0",
814 Self::Level1 => "-O1",
815 Self::Level2 => "-O2",
816 Self::Level3 => "-O3",
817 Self::Level4 => "-O4",
818 }
819 }
820}
821
822#[allow(clippy::derivable_impls)]
823impl Default for TemplateFfiVersion {
824 fn default() -> Self {
825 Self::V1
826 }
827}
828
829#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
830#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
831#[derive(Deserialize, Serialize, Debug, Clone)]
832pub enum TemplateFfiVersion {
833 V1,
834}
835
836#[allow(clippy::derivable_impls)]
837impl Default for TemplateFfiSerialization {
838 fn default() -> Self {
839 Self::FlexBufferVector
840 }
841}
842
843#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
845#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
846#[derive(Deserialize, Serialize, Debug, Clone)]
847pub enum TemplateFfiSerialization {
848 FlexBufferVector,
849}
850
851#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
852#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
853#[derive(Deserialize, Serialize, Debug, Clone, Default)]
854pub struct TemplateFfi {
855 pub version: TemplateFfiVersion,
856 pub serialization: TemplateFfiSerialization,
857}
858
859#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
861#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
862#[derive(Deserialize, Serialize, Debug, Clone, Default)]
863pub struct TemplateConfig {
864 pub ffi: TemplateFfi,
866
867 pub idx: u8,
869 pub name: String,
871 pub mime: String,
875 #[serde(skip_serializing_if = "Option::is_none")]
878 #[serde(default)]
879 pub minify: Option<bool>,
880 #[serde(skip_serializing_if = "Option::is_none")]
882 #[serde(default)]
883 pub path: Option<String>,
884 pub route: String,
887 #[serde(skip_serializing_if = "Option::is_none")]
889 #[serde(default)]
890 pub protected: Option<Check>,
891 #[serde(skip_serializing_if = "Option::is_none")]
893 #[serde(default)]
894 pub cache: Option<TemplateCache>,
895
896 #[serde(skip_serializing_if = "Option::is_none")]
906 #[serde(default)]
907 pub csp: Option<HttpCsp>,
908
909 #[serde(skip_serializing_if = "Option::is_none")]
910 #[serde(default)]
911 pub cors: Option<HttpCors>,
912
913 #[serde(skip_serializing_if = "Option::is_none")]
917 #[serde(default)]
918 pub timeout: Option<u16>,
919
920 #[serde(skip_serializing_if = "Option::is_none")]
924 #[serde(default)]
925 pub fields: Option<Vec<TemplateField>>,
926 #[serde(skip_serializing_if = "Option::is_none")]
930 #[serde(default)]
931 pub globals: Option<Vec<String>>,
932 #[serde(skip_serializing_if = "Option::is_none")]
934 #[serde(default)]
935 pub flags: Option<Vec<TemplateFlagRef>>,
936 #[serde(skip_serializing_if = "Option::is_none")]
938 #[serde(default)]
939 pub params: Option<Vec<TemplateParamRef>>,
940 #[serde(skip_serializing_if = "Option::is_none")]
943 #[serde(default)]
944 pub models: Option<Vec<TemplateRef>>,
945 #[serde(skip_serializing_if = "Option::is_none")]
948 #[serde(default)]
949 pub content: Option<Vec<TemplateRef>>,
950
951 #[serde(skip_serializing_if = "Option::is_none")]
955 #[serde(default)]
956 pub actions: Option<Vec<String>>,
957
958 #[serde(skip_serializing_if = "Option::is_none")]
959 #[serde(default)]
960 pub wasm_opt: Option<WasmOpt>,
961
962 #[serde(skip_serializing_if = "Option::is_none")]
966 #[serde(default)]
967 pub variables: Option<Vec<String>>,
968
969 #[serde(skip_serializing_if = "Option::is_none")]
971 #[serde(default)]
972 pub middlewares: Option<Vec<String>>,
973}
974
975#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
976#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
977#[derive(Deserialize, Serialize, Debug, Clone, Default)]
978pub struct CompressionAlgorithms(pub Vec<CompressionAlgorithm>);
979
980impl CompressionAlgorithms {
981 #[must_use]
982 fn get_list(&self) -> ArrayVec<CompressionAlgorithm, 4> {
983 let mut list = ArrayVec::<CompressionAlgorithm, 4>::new();
984 let mut has_all = false;
985
986 for alg in &self.0 {
987 if *alg == CompressionAlgorithm::All {
988 has_all = true;
989 } else if !list.contains(alg) {
990 list.push(alg.clone());
991 }
992 }
993
994 if has_all {
995 for alg in [
996 CompressionAlgorithm::Brotli,
997 CompressionAlgorithm::Zstd { level: 17 },
998 CompressionAlgorithm::Deflate,
999 CompressionAlgorithm::Gzip,
1000 ] {
1001 if !list.contains(&alg) {
1002 list.push(alg);
1003 }
1004 }
1005 }
1006
1007 list
1008 }
1009}
1010
1011#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1012#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1013#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1014pub struct AssetsConfig {
1015 #[serde(skip_serializing_if = "Option::is_none")]
1017 #[serde(default)]
1018 pub dir_path: Option<String>,
1019
1020 #[serde(default = "AssetsConfig::default_base_route")]
1023 pub base_route: String,
1024
1025 #[serde(skip_serializing_if = "Option::is_none")]
1038 #[serde(default)]
1039 pub append_index_html: Option<bool>,
1040
1041 #[serde(skip_serializing_if = "Option::is_none")]
1047 #[serde(default)]
1048 pub skip_base_route_index_html: Option<bool>,
1049
1050 #[serde(skip_serializing_if = "Option::is_none")]
1060 #[serde(default)]
1061 pub append_html_ext: Option<bool>,
1062
1063 #[serde(skip_serializing_if = "Option::is_none")]
1068 #[serde(default)]
1069 pub preserve_exif: Option<bool>,
1070
1071 #[serde(skip_serializing_if = "Option::is_none")]
1073 #[serde(default)]
1074 pub html_csp: Option<HttpCsp>,
1075
1076 #[serde(skip_serializing_if = "Option::is_none")]
1081 #[serde(default)]
1082 pub http: Option<HttpCache>,
1083
1084 #[serde(skip_serializing_if = "Option::is_none")]
1088 #[serde(default)]
1089 pub precompression: Option<CompressionAlgorithms>,
1090
1091 #[serde(skip)]
1092 #[serde(default)]
1093 pub internal_precompression: Option<ArrayVec<CompressionAlgorithm, 4>>,
1094
1095 #[serde(skip_serializing_if = "Option::is_none")]
1098 #[serde(default)]
1099 pub minify_css: Option<bool>,
1100
1101 #[serde(skip_serializing_if = "Option::is_none")]
1104 #[serde(default)]
1105 pub minify_js: Option<bool>,
1106
1107 #[serde(skip_serializing_if = "Option::is_none")]
1110 #[serde(default)]
1111 pub minify_html: Option<bool>,
1112
1113 #[serde(skip)]
1114 #[serde(default)]
1115 pub internal_cache_control_header_value: Option<String>,
1116
1117 #[serde(skip_serializing_if = "Option::is_none")]
1119 #[serde(default)]
1120 pub middlewares: Option<Vec<String>>,
1121}
1122
1123impl AssetsConfig {
1124 fn default_base_route() -> String {
1125 "/assets".to_string()
1126 }
1127}
1128
1129impl AssetsConfig {
1130 pub fn init(&mut self, default_cache_control_header_value: &str) -> anyhow::Result<()> {
1131 if let Some(http_cache) = &self.http
1132 && let Some(http_cache_control) = &http_cache.cache_control
1133 {
1134 let mut header = String::new();
1135 http_cache_control.header_value(&mut header, default_cache_control_header_value)?;
1136
1137 self.internal_cache_control_header_value = Some(header);
1138 }
1139
1140 Ok(())
1141 }
1142}
1143
1144#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1145#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1146#[derive(Deserialize, Serialize, Debug, Clone)]
1147pub struct FragmentsConfig {
1148 pub dir_path: String,
1150}
1151
1152#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1154#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1155#[derive(Deserialize, Serialize, Debug, Clone)]
1156pub struct Global {
1157 pub name: String,
1159 pub kind: Kind,
1161 pub value: serde_json::Value,
1163}
1164
1165#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1167#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1168#[derive(Deserialize, Serialize, Debug, Clone)]
1169pub enum SecretSource {
1170 Env,
1183 Stored,
1191 }
1196
1197#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1199#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1200#[derive(Deserialize, Serialize, Debug, Clone)]
1201pub enum SecretVisibility {
1202 Integrations,
1214}
1215
1216#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1218#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1219#[derive(Deserialize, Serialize, Debug, Clone)]
1220pub struct Secret {
1221 pub name: String,
1223 pub source: SecretSource,
1225 pub visibility: SecretVisibility,
1227}
1228
1229#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1231#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1232#[derive(Deserialize, Serialize, Debug, Clone)]
1233pub struct FlagOption {
1234 pub idx: u8,
1236 pub name: String,
1238 pub percentage: u8,
1241}
1242
1243#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1250#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1251#[derive(Deserialize, Serialize, Debug, Clone)]
1252pub struct Flag {
1253 pub idx: u8,
1255 pub name: String,
1257 pub options: Vec<FlagOption>,
1260}
1261
1262#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1263#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1264#[derive(Deserialize, Serialize, Debug, Clone)]
1265pub struct ContentDefinition {
1266 pub idx: u8,
1268
1269 pub name: String,
1271
1272 pub fields: Vec<Field>,
1277
1278 #[serde(skip_serializing_if = "Option::is_none")]
1280 #[serde(default)]
1281 pub lifecycle: Option<ContentObjectLifecycle>,
1282}
1283
1284#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1285#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1286#[derive(Deserialize, Serialize, Debug, Clone)]
1287pub struct ContentObjectLifecycle {
1288 #[serde(skip_serializing_if = "Option::is_none")]
1290 #[serde(default)]
1291 pub before_all: Option<Vec<Vec<String>>>,
1292 #[serde(skip_serializing_if = "Option::is_none")]
1298 #[serde(default)]
1299 pub on_add: Option<LifecycleBeforeAfterScripts>,
1300 #[serde(skip_serializing_if = "Option::is_none")]
1306 #[serde(default)]
1307 pub on_edit: Option<LifecycleBeforeAfterScripts>,
1308 #[serde(skip_serializing_if = "Option::is_none")]
1314 #[serde(default)]
1315 pub on_delete: Option<LifecycleBeforeAfterScripts>,
1316}
1317
1318#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1324#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1325#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1326pub struct Content {
1327 pub file_path: String,
1330 pub definitions: Vec<ContentDefinition>,
1333
1334 #[serde(skip_serializing_if = "Option::is_none")]
1335 #[serde(default)]
1336 pub update: Option<ContentUpdateConfig>,
1337}
1338
1339#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1340#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1341#[derive(Deserialize, Serialize, Debug, Clone)]
1342pub struct ContentUpdateConfig {
1343 #[serde(skip_serializing_if = "Option::is_none")]
1344 #[serde(default)]
1345 pub lifecycle: Option<LifecycleBeforeAfterScripts>,
1346}
1347
1348#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1349#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1350#[derive(Deserialize, Serialize, Debug, Clone)]
1351pub enum UuidVersion {
1352 V4,
1353 V7,
1354}
1355
1356#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1358#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1359#[derive(Deserialize, Serialize, Debug, Clone)]
1360pub struct ModelConfig {
1361 pub idx: u8,
1368 pub name: String,
1370 pub fields: Vec<Field>,
1372 #[serde(skip_serializing_if = "Option::is_none")]
1376 #[serde(default)]
1377 pub uuid: Option<UuidVersion>,
1378}
1379
1380#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1381#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1382#[derive(Deserialize, Serialize, Debug, Clone)]
1383pub enum IntegrationProtocolHttpEncoding {
1384 Json,
1385 Text,
1386 None,
1387}
1388
1389#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1391#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1392#[derive(Deserialize, Serialize, Debug, Clone)]
1393pub enum IntegrationProtocol {
1394 Http {
1396 method: String,
1398 headers: Vec<(String, String)>,
1400 send_encoding: IntegrationProtocolHttpEncoding,
1402 recv_encoding: IntegrationProtocolHttpEncoding,
1404 },
1405 }
1418
1419#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1422#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1423#[derive(Deserialize, Serialize, Debug, Clone)]
1424pub struct IntegrationConfig {
1425 pub idx: u8,
1427 pub name: String,
1429 pub protocol: IntegrationProtocol,
1431 pub endpoint: String,
1433 pub send: Kind,
1437 pub recv: Kind,
1441 #[serde(skip_serializing_if = "Option::is_none")]
1443 #[serde(default)]
1444 pub secrets: Option<Vec<String>>,
1445
1446 #[serde(skip_serializing_if = "Option::is_none")]
1450 #[serde(default)]
1451 pub timeout: Option<u16>,
1452}
1453
1454#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1458#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1459#[derive(Deserialize, Serialize, Debug, Clone)]
1460pub enum ActionLang {
1461 Rust,
1465 JavaScript,
1471 }
1475
1476#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1478#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1479#[derive(Deserialize, Serialize, Debug, Clone)]
1480pub enum ActionAccessModelOps {
1481 Insert,
1483 Get,
1485 Query,
1487 Search,
1489 Update,
1491 Delete,
1493}
1494
1495#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1497#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1498#[derive(Deserialize, Serialize, Debug, Clone)]
1499pub enum ActionAccessAuthOps {
1500 SetTokenFields,
1503}
1504
1505#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1508#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1509#[derive(Deserialize, Serialize, Debug, Clone)]
1510pub enum ActionAccessPermission {
1511 Model {
1513 name: String,
1515 ops: Vec<ActionAccessModelOps>,
1518 },
1519 Content {
1521 name: String,
1523 },
1524 Integration {
1526 name: String,
1528 },
1529 Action {
1531 name: String,
1533 },
1534 Auth {
1536 ops: Vec<ActionAccessAuthOps>,
1538 },
1539}
1540
1541#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1542#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1543#[derive(Deserialize, Serialize, Debug, Clone)]
1544pub enum HttpMethod {
1545 PUT,
1546 POST,
1547 GET,
1548 DELETE,
1549}
1550
1551#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1552#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1553#[derive(Deserialize, Serialize, Debug, Clone)]
1554pub enum ActionTriggerModelOps {
1555 Insert,
1556 Update,
1557 Delete,
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 enum ActionTrigger {
1565 Ordinary,
1567 Json {
1569 route: String,
1571 method: HttpMethod,
1573 },
1574 Form {
1576 route: String,
1578 method: HttpMethod,
1580 redirect: String,
1585 },
1586 Login,
1588 Registration,
1590 Content { name: String },
1592 }
1605
1606#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1607#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1608#[derive(Deserialize, Serialize, Debug, Clone)]
1609pub enum ActionFfiVersion {
1610 V1,
1611}
1612
1613#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1615#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1616#[derive(Deserialize, Serialize, Debug, Clone)]
1617pub enum ActionFfiSerialization {
1618 FlexBufferVector,
1619}
1620
1621#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1622#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1623#[derive(Deserialize, Serialize, Debug, Clone)]
1624pub struct ActionFfi {
1625 pub version: ActionFfiVersion,
1626 pub serialization: ActionFfiSerialization,
1627}
1628
1629#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1631#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1632#[derive(Deserialize, Serialize, Debug, Clone)]
1633pub struct ActionConfig {
1634 pub ffi: ActionFfi,
1636 pub idx: u8,
1638 pub name: String,
1640 pub lang: ActionLang,
1642 #[serde(skip_serializing_if = "Option::is_none")]
1644 #[serde(default)]
1645 pub dir_path: Option<String>,
1646 #[serde(skip_serializing_if = "Option::is_none")]
1649 #[serde(default)]
1650 pub protected: Option<Check>,
1651 #[serde(skip_serializing_if = "Option::is_none")]
1654 #[serde(default)]
1655 pub transactional: Option<bool>,
1656 pub access: Vec<ActionAccessPermission>,
1658 pub accepts: Kind,
1660 pub returns: Kind,
1662 pub triggered_by: Vec<ActionTrigger>,
1665
1666 #[serde(skip_serializing_if = "Option::is_none")]
1670 #[serde(default)]
1671 pub timeout: Option<u16>,
1672
1673 #[serde(skip_serializing_if = "Option::is_none")]
1674 #[serde(default)]
1675 pub cors: Option<HttpCors>,
1676
1677 #[serde(skip_serializing_if = "Option::is_none")]
1678 #[serde(default)]
1679 pub wasm_opt: Option<WasmOpt>,
1680
1681 #[serde(skip_serializing_if = "Option::is_none")]
1686 #[serde(default)]
1687 pub privileged: Option<bool>,
1688
1689 #[serde(skip_serializing_if = "Option::is_none")]
1693 #[serde(default)]
1694 pub variables: Option<Vec<String>>,
1695
1696 #[serde(skip_serializing_if = "Option::is_none")]
1698 #[serde(default)]
1699 pub middlewares: Option<Vec<String>>,
1700}
1701
1702#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1703#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1704#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1705pub struct ErrorConfig {
1706 #[serde(skip_serializing_if = "Option::is_none")]
1710 #[serde(default)]
1711 pub template: Option<String>,
1712
1713 #[serde(skip_serializing_if = "Option::is_none")]
1717 #[serde(default)]
1718 pub asset: Option<String>,
1719}
1720
1721#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1722#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1723#[derive(Deserialize, Serialize, Debug, Clone)]
1724pub enum RuntimeMode {
1725 Shared,
1728 SingleThreaded,
1731 MultiThreaded,
1734}
1735
1736#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1737#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1738#[derive(Deserialize, Serialize, Debug, Clone)]
1739pub enum HttpCorsAllowHeaders {
1740 Any,
1741 Headers(Vec<String>),
1743}
1744
1745#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1746#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1747#[derive(Deserialize, Serialize, Debug, Clone)]
1748pub enum HttpCorsExposeHeaders {
1749 Any,
1750 Headers(Vec<String>),
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 enum HttpCorsAllowMethods {
1758 Any,
1759 Methods(Vec<String>),
1761}
1762
1763#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1764#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1765#[derive(Deserialize, Serialize, Debug, Clone)]
1766pub enum HttpCorsAllowOrigin {
1767 Any,
1768 Origins(Vec<String>),
1770}
1771
1772#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1773#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1774#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1775pub struct HttpCors {
1776 pub allow_credentials: Option<bool>,
1777
1778 pub allow_headers: Option<HttpCorsAllowHeaders>,
1779
1780 pub max_age: Option<u32>,
1782
1783 pub allow_methods: Option<HttpCorsAllowMethods>,
1784
1785 pub allow_origin: Option<HttpCorsAllowOrigin>,
1786
1787 pub expose_headers: Option<HttpCorsExposeHeaders>,
1788
1789 pub allow_private_network: Option<bool>,
1790}
1791
1792impl HttpCors {
1793 #[must_use]
1794 pub fn overwrite(&self, base: &Self) -> Self {
1795 Self {
1796 allow_credentials: if self.allow_credentials.is_none() {
1797 base.allow_credentials
1798 } else {
1799 self.allow_credentials
1800 },
1801 allow_headers: if self.allow_headers.is_none() {
1802 base.allow_headers.clone()
1803 } else {
1804 self.allow_headers.clone()
1805 },
1806 max_age: if self.max_age.is_none() {
1807 base.max_age
1808 } else {
1809 self.max_age
1810 },
1811 allow_methods: if self.allow_methods.is_none() {
1812 base.allow_methods.clone()
1813 } else {
1814 self.allow_methods.clone()
1815 },
1816 allow_origin: if self.allow_origin.is_none() {
1817 base.allow_origin.clone()
1818 } else {
1819 self.allow_origin.clone()
1820 },
1821 expose_headers: if self.expose_headers.is_none() {
1822 base.expose_headers.clone()
1823 } else {
1824 self.expose_headers.clone()
1825 },
1826 allow_private_network: if self.allow_private_network.is_none() {
1827 base.allow_private_network
1828 } else {
1829 self.allow_private_network
1830 },
1831 }
1832 }
1833}
1834
1835#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1836#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1837#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1838pub struct HttpCsp {
1839 #[serde(skip_serializing_if = "Option::is_none")]
1841 #[serde(default)]
1842 pub default_src: Option<String>,
1843
1844 #[serde(skip_serializing_if = "Option::is_none")]
1847 #[serde(default)]
1848 pub script_src: Option<String>,
1849
1850 #[serde(skip_serializing_if = "Option::is_none")]
1853 #[serde(default)]
1854 pub style_src: Option<String>,
1855
1856 #[serde(skip_serializing_if = "Option::is_none")]
1860 #[serde(default)]
1861 pub font_src: Option<String>,
1862
1863 #[serde(skip_serializing_if = "Option::is_none")]
1867 #[serde(default)]
1868 pub img_src: Option<String>,
1869
1870 #[serde(skip_serializing_if = "Option::is_none")]
1874 #[serde(default)]
1875 pub frame_src: Option<String>,
1876
1877 #[serde(skip_serializing_if = "Option::is_none")]
1879 #[serde(default)]
1880 pub include_inline_hashes: Option<bool>,
1881}
1882
1883impl HttpCsp {
1884 #[must_use]
1885 #[allow(clippy::too_many_lines)]
1886 pub fn build_string(
1887 &self,
1888 base: &Self,
1889 inline_style_hashes: Option<Vec<String>>,
1890 inline_script_hashes: Option<Vec<String>>,
1891 script_urls: Option<Vec<String>>,
1892 secure: bool,
1893 has_wasm: bool,
1894 ) -> String {
1895 let mut out = String::new();
1896
1897 let include_inline_hashes = self
1898 .include_inline_hashes
1899 .unwrap_or(base.include_inline_hashes.unwrap_or(true));
1900
1901 let default_src = self
1902 .default_src
1903 .clone()
1904 .unwrap_or(base.default_src.clone().unwrap_or("'self'".to_string()));
1905
1906 if !default_src.is_empty() {
1907 out.push_str("default-src ");
1908 out.push_str(default_src.as_str());
1909 out.push_str("; ");
1910 }
1911
1912 let mut script_src = self
1913 .script_src
1914 .clone()
1915 .unwrap_or(base.script_src.clone().unwrap_or_default());
1916
1917 if include_inline_hashes
1918 && let Some(script_hashes) = inline_script_hashes
1919 && !script_hashes.is_empty()
1920 {
1921 if script_src.is_empty() {
1922 script_src.push_str("'self'");
1923 if has_wasm {
1924 script_src.push_str(" 'wasm-unsafe-eval'");
1925 }
1926 }
1927
1928 for hash in script_hashes {
1929 script_src.push_str(" '");
1930 script_src.push_str(hash.as_str());
1931 script_src.push('\'');
1932 }
1933 }
1934
1935 if let Some(script_urls) = script_urls {
1936 if script_src.is_empty() {
1937 script_src.push_str("'self'");
1938 }
1939
1940 for script_url in script_urls {
1941 script_src.push(' ');
1942 script_src.push_str(&script_url);
1943 }
1944 }
1945
1946 if !script_src.is_empty() {
1947 out.push_str("script-src ");
1948 out.push_str(script_src.as_str());
1949 out.push_str("; ");
1950 }
1951
1952 let mut style_src = self
1953 .style_src
1954 .clone()
1955 .unwrap_or(base.style_src.clone().unwrap_or_default());
1956
1957 if include_inline_hashes
1958 && let Some(style_hashes) = inline_style_hashes
1959 && !style_hashes.is_empty()
1960 {
1961 if style_src.is_empty() {
1962 style_src.push_str("'self'");
1963 }
1964
1965 for hash in style_hashes {
1966 style_src.push_str(" '");
1967 style_src.push_str(hash.as_str());
1968 style_src.push('\'');
1969 }
1970 }
1971
1972 if !style_src.is_empty() {
1973 out.push_str("style-src ");
1974 out.push_str(style_src.as_str());
1975 out.push_str("; ");
1976 }
1977
1978 let font_src = self
1979 .font_src
1980 .clone()
1981 .unwrap_or(base.font_src.clone().unwrap_or_default());
1982 if !font_src.is_empty() {
1983 out.push_str("font-src ");
1984 out.push_str(font_src.as_str());
1985 out.push_str("; ");
1986 }
1987
1988 let img_src = self
1989 .img_src
1990 .clone()
1991 .unwrap_or(base.img_src.clone().unwrap_or_default());
1992 if !img_src.is_empty() {
1993 out.push_str("img-src ");
1994 out.push_str(img_src.as_str());
1995 out.push_str("; ");
1996 }
1997
1998 let frame_src = self
1999 .frame_src
2000 .clone()
2001 .unwrap_or(base.frame_src.clone().unwrap_or_default());
2002 if !frame_src.is_empty() {
2003 out.push_str("frame-src ");
2004 out.push_str(frame_src.as_str());
2005 out.push_str("; ");
2006 }
2007
2008 if secure {
2009 out.push_str("upgrade-insecure-requests; ");
2010 }
2011
2012 out.push_str("report-to csp");
2013
2014 out = out.trim().to_string();
2015 out
2016 }
2017}
2018
2019#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2020#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2021#[derive(Deserialize, Serialize, Debug, Clone)]
2022pub struct LifecycleBeforeAfterScripts {
2023 #[serde(skip_serializing_if = "Option::is_none")]
2024 #[serde(default)]
2025 pub before: Option<Vec<Vec<String>>>,
2026 #[serde(skip_serializing_if = "Option::is_none")]
2027 #[serde(default)]
2028 pub after: Option<Vec<Vec<String>>>,
2029}
2030
2031#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2032#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2033#[derive(Deserialize, Serialize, Debug, Clone)]
2034pub struct TopLevelLifecycle {
2035 pub before_all: Option<Vec<Vec<String>>>,
2037
2038 #[serde(skip_serializing_if = "Option::is_none")]
2040 #[serde(default)]
2041 pub build: Option<LifecycleBeforeAfterScripts>,
2042}
2043
2044#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2045#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2046#[derive(Deserialize, Serialize, Debug, Clone)]
2047pub enum RedirectMethod {
2048 Temporary,
2050 Permanent,
2052}
2053
2054impl Display for RedirectMethod {
2055 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2056 match self {
2057 Self::Temporary => write!(f, "TEMPORARY"),
2058 Self::Permanent => write!(f, "PERMANENT"),
2059 }
2060 }
2061}
2062
2063#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2064#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2065#[derive(Deserialize, Serialize, Debug, Clone)]
2066pub struct HostRedirect {
2067 pub from: String,
2069 pub to: String,
2071 pub method: RedirectMethod,
2073}
2074
2075#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2076#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2077#[derive(Deserialize, Serialize, Debug, Clone)]
2078pub struct RouteRedirect {
2079 pub condition: String,
2081 pub rule: (String, String),
2084 pub method: RedirectMethod,
2086}
2087
2088#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2089#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2090#[derive(Deserialize, Serialize, Debug, Clone)]
2091pub struct Redirects {
2092 #[serde(skip_serializing_if = "Option::is_none")]
2094 #[serde(default)]
2095 pub host: Option<Vec<HostRedirect>>,
2096 #[serde(skip_serializing_if = "Option::is_none")]
2098 #[serde(default)]
2099 pub route: Option<Vec<RouteRedirect>>,
2100}
2101
2102#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2109#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2110#[derive(Deserialize, Serialize, Debug, Clone)]
2111pub struct ProxyConfig {
2112 #[serde(skip_serializing_if = "Option::is_none")]
2119 #[serde(default)]
2120 pub path: Option<String>,
2121 #[serde(skip_serializing_if = "Option::is_none")]
2128 #[serde(default)]
2129 pub domain: Option<String>,
2130
2131 #[serde(skip_serializing_if = "Option::is_none")]
2139 #[serde(default)]
2140 pub port: Option<u16>,
2141
2142 pub target: String,
2146
2147 #[serde(skip_serializing_if = "Option::is_none")]
2149 #[serde(default)]
2150 pub middlewares: Option<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 MiddlewareMechanism {
2157 Request { endpoint: String },
2158}
2159
2160#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2161#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2162#[derive(Deserialize, Serialize, Debug, Clone)]
2163pub enum MiddlewareValidationRule {
2164 #[cfg_attr(feature = "utoipa", schema(no_recursion))]
2166 Any(Vec<Box<MiddlewareValidationRule>>),
2167 #[cfg_attr(feature = "utoipa", schema(no_recursion))]
2169 All(Vec<Box<MiddlewareValidationRule>>),
2170 #[cfg_attr(feature = "utoipa", schema(no_recursion))]
2172 Not(Box<MiddlewareValidationRule>),
2173
2174 StatusCode(u16),
2177}
2178
2179#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2180#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2181#[derive(Deserialize, Serialize, Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
2182pub enum MiddlewareValidationComponent {
2183 Headers,
2185 Path,
2187 Query,
2189}
2190
2191#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2192#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2193#[derive(Deserialize, Serialize, Debug, Clone)]
2194pub enum MiddlewareOperation {
2195 Validate {
2196 rule: MiddlewareValidationRule,
2197 components: BTreeSet<MiddlewareValidationComponent>,
2198 },
2199}
2200
2201#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2203#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2204#[derive(Deserialize, Serialize, Debug, Clone)]
2205pub struct MiddlewareConfig {
2206 pub name: String,
2209
2210 pub operation: MiddlewareOperation,
2212
2213 pub mechanism: MiddlewareMechanism,
2215}
2216
2217#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2219#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2220#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2221pub struct OrdinaryConfig {
2222 #[serde(skip_serializing_if = "Option::is_none")]
2223 #[serde(default)]
2224 pub lifecycle: Option<TopLevelLifecycle>,
2225
2226 pub domain: String,
2229
2230 #[serde(skip_serializing_if = "Option::is_none")]
2236 #[serde(default)]
2237 pub cnames: Option<Vec<String>>,
2238
2239 #[serde(skip_serializing_if = "Option::is_none")]
2249 #[serde(default)]
2250 pub canonical: Option<String>,
2251
2252 #[serde(skip_serializing_if = "Option::is_none")]
2254 #[serde(default)]
2255 pub redirects: Option<Redirects>,
2256
2257 #[serde(skip_serializing_if = "Option::is_none")]
2259 #[serde(default)]
2260 pub proxies: Option<Vec<ProxyConfig>>,
2261
2262 #[serde(skip_serializing_if = "Option::is_none")]
2265 #[serde(default)]
2266 pub middlewares: Option<Vec<MiddlewareConfig>>,
2267
2268 #[serde(skip)]
2269 #[serde(default)]
2270 pub internal_middlewares: Option<HashMap<String, MiddlewareConfig>>,
2271
2272 #[serde(skip_serializing_if = "Option::is_none")]
2275 #[serde(default)]
2276 pub contacts: Option<Vec<String>>,
2277
2278 #[serde(skip_serializing_if = "Option::is_none")]
2280 #[serde(default)]
2281 pub hide_contacts: Option<bool>,
2282
2283 pub version: String,
2285
2286 #[serde(skip_serializing_if = "Option::is_none")]
2288 #[serde(default = "OrdinaryConfig::default_storage_size")]
2289 pub storage_size: Option<u64>,
2290
2291 #[serde(skip_serializing_if = "Option::is_none")]
2295 #[serde(default)]
2296 pub default_timeout: Option<u16>,
2297
2298 #[serde(skip_serializing_if = "Option::is_none")]
2308 #[serde(default)]
2309 pub csp: Option<HttpCsp>,
2310
2311 #[serde(skip_serializing_if = "Option::is_none")]
2312 #[serde(default)]
2313 pub cors: Option<HttpCors>,
2314
2315 #[serde(skip_serializing_if = "Option::is_none")]
2319 #[serde(default)]
2320 pub runtime: Option<RuntimeMode>,
2321
2322 #[serde(skip_serializing_if = "Option::is_none")]
2328 #[serde(default)]
2329 pub hide_schema: Option<bool>,
2330
2331 #[serde(skip_serializing_if = "Option::is_none")]
2333 #[serde(default)]
2334 pub client_rendering: Option<bool>,
2335
2336 #[serde(skip_serializing_if = "Option::is_none")]
2338 #[serde(default)]
2339 pub obfuscation: Option<bool>,
2340
2341 #[serde(skip_serializing_if = "Option::is_none")]
2343 #[serde(default)]
2344 pub client_events: Option<bool>,
2345
2346 #[serde(skip_serializing_if = "Option::is_none")]
2348 #[serde(default)]
2349 pub port: Option<u16>,
2350 #[serde(skip_serializing_if = "Option::is_none")]
2353 #[serde(default)]
2354 pub redirect_port: Option<u16>,
2355
2356 #[serde(skip_serializing_if = "Option::is_none")]
2357 #[serde(default)]
2358 pub logging: Option<LoggingConfig>,
2359 #[serde(skip_serializing_if = "Option::is_none")]
2364 #[serde(default)]
2365 pub error: Option<ErrorConfig>,
2366 #[serde(skip_serializing_if = "Option::is_none")]
2368 #[serde(default)]
2369 pub auth: Option<AuthConfig>,
2370 #[serde(skip_serializing_if = "Option::is_none")]
2372 #[serde(default)]
2373 pub globals: Option<Vec<Global>>,
2374 #[serde(skip_serializing_if = "Option::is_none")]
2376 #[serde(default)]
2377 pub secrets: Option<Vec<Secret>>,
2378 #[serde(skip_serializing_if = "Option::is_none")]
2381 #[serde(default)]
2382 pub flags: Option<Vec<Flag>>,
2383 #[serde(skip_serializing_if = "Option::is_none")]
2388 #[serde(default)]
2389 pub content: Option<Content>,
2390 #[serde(skip_serializing_if = "Option::is_none")]
2392 #[serde(default)]
2393 pub models: Option<Vec<ModelConfig>>,
2394 #[serde(skip_serializing_if = "Option::is_none")]
2397 #[serde(default)]
2398 pub integrations: Option<Vec<IntegrationConfig>>,
2399 #[serde(skip_serializing_if = "Option::is_none")]
2402 #[serde(default)]
2403 pub actions: Option<Vec<ActionConfig>>,
2404 #[serde(skip_serializing_if = "Option::is_none")]
2408 #[serde(default)]
2409 pub assets: Option<AssetsConfig>,
2410 #[serde(skip_serializing_if = "Option::is_none")]
2412 #[serde(default)]
2413 pub fragments: Option<FragmentsConfig>,
2414 #[serde(skip_serializing_if = "Option::is_none")]
2427 #[serde(default)]
2428 pub templates: Option<Vec<TemplateConfig>>,
2429}
2430
2431impl OrdinaryConfig {
2432 pub fn get(proj_path: impl AsRef<Path>) -> anyhow::Result<OrdinaryConfig> {
2434 let path = proj_path.as_ref().join("ordinary.json");
2435 let mut config_json = std::fs::read(&path)?;
2437
2438 let mut config = match simd_json::from_slice::<OrdinaryConfig>(&mut config_json) {
2439 Ok(config) => config,
2440 Err(err) => bail!("{}: {err}", path.display()),
2441 };
2442
2443 config.load_internal();
2444
2445 Ok(config)
2446 }
2447
2448 pub fn load_internal(&mut self) {
2449 self.load_internal_middlewares();
2450 self.load_internal_compression();
2451 }
2452
2453 fn load_internal_middlewares(&mut self) {
2454 if let Some(middlewares) = &self.middlewares {
2455 let mut map = HashMap::new();
2456
2457 for middleware in middlewares {
2458 map.insert(middleware.name.clone(), middleware.clone());
2459 }
2460
2461 self.internal_middlewares = Some(map);
2462 }
2463 }
2464
2465 fn load_internal_compression(&mut self) {
2466 if let Some(assets) = self.assets.as_mut()
2467 && let Some(precompression) = &assets.precompression
2468 {
2469 assets.internal_precompression = Some(precompression.get_list());
2470 }
2471
2472 if let Some(templates) = self.templates.as_mut() {
2473 for template in templates {
2474 if let Some(cache) = template.cache.as_mut()
2475 && let Some(stored) = cache.stored.as_mut()
2476 && let Some(compression) = &stored.compression
2477 {
2478 stored.internal_compression = Some(compression.get_list());
2479 }
2480 }
2481 }
2482 }
2483
2484 #[must_use]
2485 pub fn get_middlewares(&self, middleware_names: &Vec<String>) -> Option<Vec<MiddlewareConfig>> {
2486 let mut middleware_configs = vec![];
2487
2488 if let Some(middleware_map) = &self.internal_middlewares {
2489 for middleware in middleware_names {
2490 if let Some(middleware_config) = middleware_map.get(middleware) {
2491 middleware_configs.push(middleware_config.clone());
2492 }
2493 }
2494 }
2495
2496 if !middleware_configs.is_empty() {
2497 return Some(middleware_configs);
2498 }
2499
2500 None
2501 }
2502
2503 pub fn for_send(&self) -> anyhow::Result<OrdinaryConfig> {
2506 let mut config = self.clone();
2507
2508 config.lifecycle = None;
2509
2510 if let Some(assets) = config.assets.as_mut() {
2511 assets.dir_path = None;
2512 }
2513
2514 if let Some(content) = config.content.as_mut() {
2515 content.update = None;
2516
2517 for def in &mut content.definitions {
2518 def.lifecycle = None;
2519 }
2520 }
2521
2522 if let Some(templates) = config.templates.as_mut() {
2523 for template in templates {
2524 template.path = None;
2525 template.wasm_opt = None;
2526 template.minify = None;
2527 }
2528 }
2529
2530 if let Some(actions) = config.actions.as_mut() {
2531 for action in actions {
2532 action.wasm_opt = None;
2533 action.dir_path = None;
2534 }
2535 }
2536
2537 Ok(config)
2538 }
2539
2540 #[instrument(skip_all, err, level = "debug")]
2543 pub fn validate(&self) -> anyhow::Result<()> {
2544 validate(self)
2545 }
2546
2547 #[must_use]
2549 #[allow(clippy::unnecessary_wraps)]
2550 pub fn default_storage_size() -> Option<u64> {
2551 Some(5_000_000)
2552 }
2553 #[must_use]
2556 pub fn has_ordinary_actions(&self) -> bool {
2557 if let Some(actions) = &self.actions {
2558 actions
2559 .iter()
2560 .find(|a| {
2561 for trigger in &a.triggered_by {
2562 if let ActionTrigger::Ordinary = trigger {
2563 return true;
2564 }
2565 }
2566
2567 false
2568 })
2569 .is_some()
2570 } else {
2571 false
2572 }
2573 }
2574
2575 #[allow(clippy::too_many_lines)]
2580 pub fn check_config_against_limits(
2581 &self,
2582 limits: &OrdinaryApiLimits,
2583 privileged_domains: &HashSet<String>,
2584 ) -> anyhow::Result<()> {
2585 check_config_against_limits(self, limits, privileged_domains)
2586 }
2587
2588 pub fn exec_lifecycle_script(
2589 proj_path: &Path,
2590 argument: &Option<String>,
2591 name: &str,
2592 when: &str,
2593 scripts: &Vec<Vec<String>>,
2594 ) -> anyhow::Result<()> {
2595 let span = tracing::info_span!("lifecycle", %when, %name);
2596
2597 span.in_scope(|| {
2598 let curr_dir = env::current_dir()?;
2599 env::set_current_dir(proj_path)?;
2600
2601 for script in scripts {
2602 let mut script_iter = script.iter();
2603
2604 if let Some(command) = script_iter.nth(0) {
2605 let mut command_str = command.clone();
2606 let mut command = Command::new(command);
2607
2608 for arg in script_iter {
2609 write!(command_str, " {arg}")?;
2610 command.arg(arg);
2611 }
2612
2613 tracing::info!(cmd = %command_str, "exec");
2614
2615 let output = match &argument {
2616 Some(arg) => command.arg(arg).output()?,
2617 None => command.output()?,
2618 };
2619
2620 if output.status.success() {
2621 tracing::info!("success");
2622 } else {
2623 let stderr = str::from_utf8(&output.stderr)?;
2624 let stdout = str::from_utf8(&output.stdout)?;
2625
2626 tracing::error!(%stderr, %stdout, "failed");
2627 bail!(stderr.to_string());
2628 }
2629 }
2630 }
2631
2632 env::set_current_dir(curr_dir)?;
2633
2634 anyhow::Ok(())
2635 })
2636 }
2637}