#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
use ordinary_types::{Field, Kind};
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt::Write;
fn default_public_dns_ip() -> [u8; 4] {
[8, 8, 8, 8]
}
fn default_env_name() -> String {
"development".to_string()
}
#[derive(Deserialize, Serialize, Clone)]
pub struct OrdinaryApiConfig {
#[serde(default = "default_public_dns_ip")]
pub public_dns_ip: [u8; 4],
#[serde(default = "default_env_name")]
pub env_name: String,
#[serde(default)]
pub limits: OrdinaryApiLimits,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct AssetsLimits {
pub allowed_extensions: Vec<String>,
pub max_store_size: u64,
pub max_asset_size: u64,
}
impl Default for AssetsLimits {
fn default() -> Self {
Self {
allowed_extensions: vec![
"txt".into(),
"xml".into(),
"html".into(),
"css".into(),
"csv".into(),
"js".into(),
"png".into(),
"apng".into(),
"gif".into(),
"svg".into(),
"jpg".into(),
"jpeg".into(),
"bmp".into(),
"tif".into(),
"tiff".into(),
"webp".into(),
"avif".into(),
"ico".into(),
"pdf".into(),
"json".into(),
"wasm".into(),
],
max_store_size: 100_000_000,
max_asset_size: 1_500_000,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ArtifactLimits {
pub max_store_size: u64,
pub max_artifact_size: u64,
}
impl Default for ArtifactLimits {
fn default() -> Self {
Self {
max_store_size: 10_000_000,
max_artifact_size: 500_000,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct CacheLimits {
pub max_size_range: (u64, u64),
pub max_count_range: (usize, usize),
pub sync_interval_ranges: ((u64, u64), (u64, u64)),
pub clean_interval_ranges: ((u64, u64), (u64, u64)),
}
impl Default for CacheLimits {
fn default() -> Self {
Self {
max_size_range: (1_000_000, 10_000_000),
max_count_range: (100, 500),
sync_interval_ranges: ((2, 14), (18, 30)),
clean_interval_ranges: ((5, 10), (15, 20)),
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ContentLimits {
pub search_enabled: bool,
pub max_content_definitions: u8,
pub max_content_fields: u8,
pub max_store_size: u64,
pub max_object_size: u64,
pub max_field_size: u64,
}
impl Default for ContentLimits {
fn default() -> Self {
Self {
search_enabled: true,
max_content_definitions: 255,
max_content_fields: 255,
max_store_size: 10_000_000,
max_object_size: 400_000,
max_field_size: 200_000,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ModelLimits {
pub search_enabled: bool,
pub max_model_definitions: u8,
pub max_model_fields: u8,
pub max_item_size: u64,
pub max_field_size: u64,
}
impl Default for ModelLimits {
fn default() -> Self {
Self {
search_enabled: true,
max_model_definitions: 255,
max_model_fields: 255,
max_item_size: 400_000,
max_field_size: 100_000,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct StorageLimits {
pub max_storage: u64,
pub max_app_storage: u64,
pub assets: AssetsLimits,
pub artifact: ArtifactLimits,
pub cache: CacheLimits,
pub content: ContentLimits,
pub model: ModelLimits,
}
impl Default for StorageLimits {
fn default() -> Self {
Self {
max_storage: 20_000_000_000,
max_app_storage: 50_000_000,
assets: AssetsLimits::default(),
artifact: ArtifactLimits::default(),
cache: CacheLimits::default(),
content: ContentLimits::default(),
model: ModelLimits::default(),
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct MonitorLimits {}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct IntegrationLimits {
pub count: u8,
pub max_timeout: u16,
}
impl Default for IntegrationLimits {
fn default() -> Self {
Self {
count: 255,
max_timeout: 10,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ActionLimits {
pub count: u8,
pub max_timeout: u16,
}
impl Default for ActionLimits {
fn default() -> Self {
Self {
count: 255,
max_timeout: 10,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct AuthLimits {}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TemplateLimits {
pub count: u8,
pub max_timeout: u16,
}
impl Default for TemplateLimits {
fn default() -> Self {
Self {
count: 255,
max_timeout: 10,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct OrdinaryApiLimits {
pub max_default_timeout: u16,
pub action: ActionLimits,
pub auth: AuthLimits,
pub integration: IntegrationLimits,
pub monitor: MonitorLimits,
pub storage: StorageLimits,
pub template: TemplateLimits,
}
impl Default for OrdinaryApiLimits {
fn default() -> Self {
Self {
max_default_timeout: 10,
action: ActionLimits::default(),
auth: AuthLimits::default(),
integration: IntegrationLimits::default(),
monitor: MonitorLimits::default(),
storage: StorageLimits::default(),
template: TemplateLimits::default(),
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ClientLoggingConfig {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
min_delay: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
max_delay: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
max_buffer: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
max_batch: Option<u16>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum RedactedHashAlg {
Blake2,
Blake3,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ServerLoggingConfig {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub ips: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub headers: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub credentials: Option<RedactedHashAlg>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub timing: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub sizes: Option<bool>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct LoggingConfig {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub client: Option<ClientLoggingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub server: Option<ServerLoggingConfig>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Check {
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum SignedTokenAlgorithm {
DsaEd25519,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum HmacTokenAlgorithm {
HmacBlake2b256,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SignedTokenConfig {
pub algorithm: SignedTokenAlgorithm,
pub rotation: u32,
}
impl Default for SignedTokenConfig {
fn default() -> SignedTokenConfig {
SignedTokenConfig {
algorithm: SignedTokenAlgorithm::DsaEd25519,
rotation: 60 * 60 * 24 * 30,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct RefreshTokenConfig {
pub lifetime: u32,
}
impl Default for RefreshTokenConfig {
fn default() -> RefreshTokenConfig {
RefreshTokenConfig {
lifetime: 60 * 60 * 24 * 7,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct HmacTokenConfig {
pub algorithm: HmacTokenAlgorithm,
pub rotation: u32,
}
impl Default for HmacTokenConfig {
fn default() -> HmacTokenConfig {
HmacTokenConfig {
algorithm: HmacTokenAlgorithm::HmacBlake2b256,
rotation: 60 * 60 * 24 * 30,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct AccessTokenConfig {
pub lifetime: u32,
pub claims: Vec<Field>,
}
impl Default for AccessTokenConfig {
fn default() -> AccessTokenConfig {
AccessTokenConfig {
lifetime: 60 * 60 * 24,
claims: vec![],
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum ClientPasswordHash {
Sha256,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum PasswordProtocol {
Opaque,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct PasswordConfig {
pub protocol: PasswordProtocol,
}
impl Default for PasswordConfig {
fn default() -> PasswordConfig {
PasswordConfig {
protocol: PasswordProtocol::Opaque,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum TotpAlgorithm {
Sha1,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TotpConfig {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub template: Option<String>,
pub algorithm: TotpAlgorithm,
}
impl Default for TotpConfig {
fn default() -> TotpConfig {
TotpConfig {
template: None,
algorithm: TotpAlgorithm::Sha1,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct MfaConfig {
pub totp: TotpConfig,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum InviteMode {
Viral,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct InviteConfig {
pub mode: InviteMode,
pub lifetime: u32,
pub clean_interval: (u32, u32),
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub claims: Option<Vec<Field>>,
}
impl Default for InviteConfig {
fn default() -> InviteConfig {
InviteConfig {
mode: InviteMode::Viral,
lifetime: 60 * 60 * 24 * 7,
clean_interval: (30, 90),
claims: None,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct AuthConfig {
pub password: PasswordConfig,
pub mfa: MfaConfig,
pub hmac_token: HmacTokenConfig,
pub signed_token: SignedTokenConfig,
pub refresh_token: RefreshTokenConfig,
pub access_token: AccessTokenConfig,
pub cookies_enabled: bool,
pub client_hash: ClientPasswordHash,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub invite: Option<InviteConfig>,
}
impl Default for AuthConfig {
fn default() -> AuthConfig {
AuthConfig {
password: PasswordConfig::default(),
mfa: MfaConfig::default(),
hmac_token: HmacTokenConfig::default(),
signed_token: SignedTokenConfig::default(),
refresh_token: RefreshTokenConfig::default(),
access_token: AccessTokenConfig::default(),
cookies_enabled: false,
client_hash: ClientPasswordHash::Sha256,
invite: None,
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum CompressionAlgorithm {
Gzip,
Zstd { level: u8 },
Brotli,
Deflate,
}
impl CompressionAlgorithm {
#[must_use]
pub fn as_u8(&self) -> u8 {
match self {
Self::Gzip => 1,
Self::Zstd { level: _ } => 2,
Self::Brotli => 3,
Self::Deflate => 4,
}
}
#[must_use]
pub fn as_char(&self) -> char {
match self {
Self::Gzip => '1',
Self::Zstd { level: _ } => '2',
Self::Brotli => '3',
Self::Deflate => '4',
}
}
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Gzip => "gzip",
Self::Zstd { level: _ } => "zstd",
Self::Brotli => "br",
Self::Deflate => "deflate",
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum StoredCachePolicy {
Permanent,
FRs(u64, u64),
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct StoredCache {
pub policy: StoredCachePolicy,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub compression: Option<Vec<CompressionAlgorithm>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub max_ttl: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub hit_ttl: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub max_size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub max_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub frequency_window: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub clean_interval: Option<(u64, u64)>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub sync_interval: Option<(u64, u64)>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub evict_on_dependency_change: Option<bool>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum XXH3Variation {
Bit64,
Bit128,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum HttpEtagAlgorithm {
AHash,
XXH3(XXH3Variation),
Rustc,
Blake3,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct HttpEtag {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub alg: Option<HttpEtagAlgorithm>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct HttpCacheControl {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub max_age: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub s_maxage: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub no_cache: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub no_store: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub no_transform: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub must_revalidate: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub proxy_revalidate: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub private: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub public: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub immutable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub stale_while_revalidate: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub stale_if_error: Option<bool>,
}
impl HttpCacheControl {
pub fn header_value(
&self,
cache_control: &mut String,
default: &str,
) -> Result<(), Box<dyn Error>> {
if let Some(max_age) = self.max_age {
write!(cache_control, "max-age={max_age}, ")?;
}
if let Some(s_maxage) = self.s_maxage {
write!(cache_control, "s-maxage={s_maxage}, ")?;
}
if let Some(no_cache) = self.no_cache
&& no_cache
{
write!(cache_control, "no-cache, ")?;
}
if let Some(no_store) = self.no_store
&& no_store
{
write!(cache_control, "no-store, ")?;
}
if let Some(no_transform) = self.no_transform
&& no_transform
{
write!(cache_control, "no-transform, ")?;
}
if let Some(must_revalidate) = self.must_revalidate
&& must_revalidate
{
write!(cache_control, "must-revalidate, ")?;
}
if let Some(proxy_revalidate) = self.proxy_revalidate
&& proxy_revalidate
{
write!(cache_control, "proxy-revalidate, ")?;
}
if let Some(stale_while_revalidate) = self.stale_while_revalidate
&& stale_while_revalidate
{
write!(cache_control, "stale-while-revalidate, ")?;
}
if let Some(private) = self.private
&& private
{
write!(cache_control, "private, ")?;
}
if let Some(public) = self.public
&& public
{
write!(cache_control, "public, ")?;
}
if let Some(immutable) = self.immutable
&& immutable
{
write!(cache_control, "immutable, ")?;
}
if let Some(stale_if_error) = self.stale_if_error
&& stale_if_error
{
write!(cache_control, "stale-if-error, ")?;
}
if cache_control.is_empty() {
cache_control.push_str(default);
} else {
cache_control.pop();
cache_control.pop();
}
Ok(())
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct HttpCache {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub cache_control: Option<HttpCacheControl>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub expires: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub etag: Option<HttpEtag>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TemplateField {
pub name: String,
pub kind: Kind,
pub value: String,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum QueryExpression {
Gte,
Gt,
Lte,
Lt,
Eq,
BeginsWith,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum TemplateRefFieldBind {
Token {
field: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
expression: Option<QueryExpression>,
},
Segment {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
expression: Option<QueryExpression>,
},
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TemplateRefField {
pub idx: u8,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub bind: Option<TemplateRefFieldBind>,
#[cfg_attr(feature = "utoipa", schema(no_recursion))]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub fields: Option<Vec<TemplateRefField>>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TemplateRef {
pub idx: u8,
pub name: String,
pub fields: Vec<TemplateRefField>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub all: Option<String>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TemplateFlagRef {
pub idx: u8,
pub name: String,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TemplateParamRef {
pub idx: u8,
pub name: String,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TemplateCache {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub stored: Option<StoredCache>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub http: Option<HttpCache>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum WasmOpt {
Size,
SizeAggressive,
Level0,
Level1,
Level2,
Level3,
Level4,
}
impl WasmOpt {
#[must_use]
pub fn as_flag(&self) -> &'static str {
match self {
Self::Size => "-Os",
Self::SizeAggressive => "-Oz",
Self::Level0 => "-O0",
Self::Level1 => "-O1",
Self::Level2 => "-O2",
Self::Level3 => "-O3",
Self::Level4 => "-O4",
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TemplateConfig {
pub idx: u8,
pub name: String,
pub mime: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub minify: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub path: Option<String>,
pub route: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub protected: Option<Check>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub cache: Option<TemplateCache>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub csp: Option<HttpCsp>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub cors: Option<HttpCors>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub timeout: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub fields: Option<Vec<TemplateField>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub globals: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub flags: Option<Vec<TemplateFlagRef>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub params: Option<Vec<TemplateParamRef>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub models: Option<Vec<TemplateRef>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub content: Option<Vec<TemplateRef>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub actions: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub wasm_opt: Option<WasmOpt>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct AssetsConfig {
pub dir_path: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub http: Option<HttpCache>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub precompression: Option<Vec<CompressionAlgorithm>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub minify_css: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub minify_js: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub internal_cache_control_header_value: Option<String>,
}
impl AssetsConfig {
pub fn init(&mut self, default_cache_control_header_value: &str) -> Result<(), Box<dyn Error>> {
if let Some(http_cache) = &self.http
&& let Some(http_cache_control) = &http_cache.cache_control
{
let mut header = String::new();
http_cache_control.header_value(&mut header, default_cache_control_header_value)?;
self.internal_cache_control_header_value = Some(header);
}
Ok(())
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Global {
pub name: String,
pub kind: Kind,
pub value: String,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum SecretSource {
Env(String),
Stored(String),
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum SecretVisibility {
Integrations,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Secret {
pub name: String,
pub source: SecretSource,
pub visibility: SecretVisibility,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct FlagOption {
pub idx: u8,
pub name: String,
pub percentage: u8,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Flag {
pub idx: u8,
pub name: String,
pub options: Vec<FlagOption>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ContentDefinition {
pub idx: u8,
pub name: String,
pub fields: Vec<Field>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Content {
pub file_path: String,
pub definitions: Vec<ContentDefinition>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum UuidVersion {
V4,
V7,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ModelConfig {
pub idx: u8,
pub name: String,
pub fields: Vec<Field>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub uuid: Option<UuidVersion>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum IntegrationProtocolHttpEncoding {
Json,
Text,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum IntegrationProtocol {
Http {
method: String,
headers: Vec<(String, String)>,
send_encoding: IntegrationProtocolHttpEncoding,
recv_encoding: IntegrationProtocolHttpEncoding,
},
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct IntegrationConfig {
pub idx: u8,
pub name: String,
pub protocol: IntegrationProtocol,
pub endpoint: String,
pub send: Kind,
pub recv: Kind,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub secrets: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub timeout: Option<u16>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum ActionLang {
Rust,
JavaScript,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum ActionAccessModelOps {
Insert,
Get,
Query,
Search,
Update,
Delete,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum ActionAccessAuthOps {
SetTokenFields,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum ActionAccessPermission {
Model {
name: String,
ops: Vec<ActionAccessModelOps>,
},
Content {
name: String,
},
Integration {
name: String,
},
Action {
name: String,
},
Auth {
ops: Vec<ActionAccessAuthOps>,
},
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum HttpMethod {
PUT,
POST,
GET,
DELETE,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum ActionTriggerModelOps {
Insert,
Update,
Delete,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum ActionTrigger {
Ordinary,
Json {
route: String,
method: HttpMethod,
},
Form {
route: String,
method: HttpMethod,
redirect: String,
},
Login,
Registration,
Content { name: String },
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ActionConfig {
pub idx: u8,
pub name: String,
pub lang: ActionLang,
pub dir_path: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub protected: Option<Check>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub transactional: Option<bool>,
pub access: Vec<ActionAccessPermission>,
pub accepts: Kind,
pub returns: Kind,
pub triggered_by: Vec<ActionTrigger>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub timeout: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub cors: Option<HttpCors>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub wasm_opt: Option<WasmOpt>,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ErrorConfig {
pub template: String,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum RuntimeMode {
Shared,
SingleThreaded,
MultiThreaded,
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum HttpCorsAllowHeaders {
Any,
Headers(Vec<String>),
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum HttpCorsExposeHeaders {
Any,
Headers(Vec<String>),
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum HttpCorsAllowMethods {
Any,
Methods(Vec<String>),
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum HttpCorsAllowOrigin {
Any,
Origins(Vec<String>),
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct HttpCors {
pub allow_credentials: Option<bool>,
pub allow_headers: Option<HttpCorsAllowHeaders>,
pub max_age: Option<u32>,
pub allow_methods: Option<HttpCorsAllowMethods>,
pub allow_origin: Option<HttpCorsAllowOrigin>,
pub expose_headers: Option<HttpCorsExposeHeaders>,
pub allow_private_network: Option<bool>,
}
impl HttpCors {
#[must_use]
pub fn overwrite(&self, base: &Self) -> Self {
Self {
allow_credentials: if self.allow_credentials.is_none() {
base.allow_credentials
} else {
self.allow_credentials
},
allow_headers: if self.allow_headers.is_none() {
base.allow_headers.clone()
} else {
self.allow_headers.clone()
},
max_age: if self.max_age.is_none() {
base.max_age
} else {
self.max_age
},
allow_methods: if self.allow_methods.is_none() {
base.allow_methods.clone()
} else {
self.allow_methods.clone()
},
allow_origin: if self.allow_origin.is_none() {
base.allow_origin.clone()
} else {
self.allow_origin.clone()
},
expose_headers: if self.expose_headers.is_none() {
base.expose_headers.clone()
} else {
self.expose_headers.clone()
},
allow_private_network: if self.allow_private_network.is_none() {
base.allow_private_network
} else {
self.allow_private_network
},
}
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct HttpCsp {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub default_src: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub script_src: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub style_src: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub font_src: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub img_src: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub include_inline_hashes: Option<bool>,
}
impl HttpCsp {
#[must_use]
pub fn build_string(
&self,
base: &Self,
inline_style_hashes: Option<Vec<String>>,
inline_script_hashes: Option<Vec<String>>,
secure: bool,
) -> String {
let mut out = String::new();
let include_inline_hashes = self
.include_inline_hashes
.unwrap_or(base.include_inline_hashes.unwrap_or(true));
let default_src = self
.default_src
.clone()
.unwrap_or(base.default_src.clone().unwrap_or("'self'".to_string()));
if !default_src.is_empty() {
out.push_str("default-src ");
out.push_str(default_src.as_str());
out.push_str("; ");
}
let mut script_src = self
.script_src
.clone()
.unwrap_or(base.script_src.clone().unwrap_or_default());
if include_inline_hashes
&& let Some(script_hashes) = inline_script_hashes
&& !script_hashes.is_empty()
{
if script_src.is_empty() {
script_src.push_str("'self'");
}
for hash in script_hashes {
script_src.push_str(" '");
script_src.push_str(hash.as_str());
script_src.push('\'');
}
}
if !script_src.is_empty() {
out.push_str("script-src ");
out.push_str(script_src.as_str());
out.push_str("; ");
}
let mut style_src = self
.style_src
.clone()
.unwrap_or(base.style_src.clone().unwrap_or_default());
if include_inline_hashes
&& let Some(style_hashes) = inline_style_hashes
&& !style_hashes.is_empty()
{
if style_src.is_empty() {
style_src.push_str("'self'");
}
for hash in style_hashes {
style_src.push_str(" '");
style_src.push_str(hash.as_str());
style_src.push('\'');
}
}
if !style_src.is_empty() {
out.push_str("style-src ");
out.push_str(style_src.as_str());
out.push_str("; ");
}
let font_src = self
.font_src
.clone()
.unwrap_or(base.font_src.clone().unwrap_or_default());
if !font_src.is_empty() {
out.push_str("font-src ");
out.push_str(font_src.as_str());
out.push_str("; ");
}
let img_src = self
.img_src
.clone()
.unwrap_or(base.img_src.clone().unwrap_or_default());
if !img_src.is_empty() {
out.push_str("img-src ");
out.push_str(img_src.as_str());
out.push_str("; ");
}
if secure {
out.push_str("upgrade-insecure-requests; ");
}
out.push_str("report-to csp");
out = out.trim().to_string();
out
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct OrdinaryConfig {
pub domain: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub cnames: Option<Vec<String>>,
pub contacts: Vec<String>,
pub version: String,
pub storage_size: u64,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub default_timeout: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub csp: Option<HttpCsp>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub cors: Option<HttpCors>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub runtime: Option<RuntimeMode>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub hide_schema: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub client_rendering: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub client_e2ee: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub client_events: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub redirect_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub logging: Option<LoggingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub error: Option<ErrorConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub auth: Option<AuthConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub globals: Option<Vec<Global>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub secrets: Option<Vec<Secret>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub flags: Option<Vec<Flag>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub content: Option<Content>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub models: Option<Vec<ModelConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub integrations: Option<Vec<IntegrationConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub actions: Option<Vec<ActionConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub assets: Option<AssetsConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub templates: Option<Vec<TemplateConfig>>,
}
impl OrdinaryConfig {
#[allow(clippy::too_many_lines)]
pub fn check_config_against_limits(
&self,
limits: &OrdinaryApiLimits,
) -> Result<(), Box<dyn Error>> {
if let Some(default_timeout) = self.default_timeout
&& default_timeout > limits.max_default_timeout
{
return Err(format!(
"default timeout {} greater than limit {}",
default_timeout, limits.max_default_timeout
)
.into());
}
if let Some(templates) = &self.templates {
if templates.len() > limits.template.count as usize {
return Err(format!(
"template count {} greater than limit {}",
templates.len(),
limits.template.count
)
.into());
}
for template in templates {
if let Some(timeout) = template.timeout
&& timeout > limits.template.max_timeout
{
return Err(format!(
"template timeout {} greater than limit {} for {}",
timeout, limits.template.max_timeout, template.name
)
.into());
}
if let Some(cache) = &template.cache
&& let Some(stored_cache) = &cache.stored
{
if let Some(max_size) = stored_cache.max_size
&& (max_size < limits.storage.cache.max_size_range.0
|| max_size > limits.storage.cache.max_size_range.1)
{
return Err(format!(
"template cache max_size {} is not within range [{}, {}] for {}",
max_size,
limits.storage.cache.max_size_range.0,
limits.storage.cache.max_size_range.1,
template.name
)
.into());
}
if let Some(max_count) = stored_cache.max_count
&& (max_count < limits.storage.cache.max_count_range.0
|| max_count > limits.storage.cache.max_count_range.1)
{
return Err(format!(
"template cache max_count {} is not within range [{}, {}] for {}",
max_count,
limits.storage.cache.max_count_range.0,
limits.storage.cache.max_count_range.1,
template.name
)
.into());
}
if let Some((sync_interval_min, sync_interval_max)) = stored_cache.sync_interval
{
if sync_interval_min < limits.storage.cache.sync_interval_ranges.0.0
|| sync_interval_min > limits.storage.cache.sync_interval_ranges.0.1
{
return Err(format!(
"template cache sync_interval min {} is not within range [{}, {}] for {}",
sync_interval_min,
limits.storage.cache.sync_interval_ranges.0.0,
limits.storage.cache.sync_interval_ranges.0.1,
template.name
)
.into());
}
if sync_interval_max < limits.storage.cache.sync_interval_ranges.1.0
|| sync_interval_max > limits.storage.cache.sync_interval_ranges.1.1
{
return Err(format!(
"template cache sync_interval min {} is not within range [{}, {}] for {}",
sync_interval_max,
limits.storage.cache.sync_interval_ranges.1.0,
limits.storage.cache.sync_interval_ranges.1.1,
template.name
)
.into());
}
}
if let Some((clean_interval_min, clean_interval_max)) =
stored_cache.clean_interval
{
if clean_interval_min < limits.storage.cache.clean_interval_ranges.0.0
|| clean_interval_min > limits.storage.cache.clean_interval_ranges.0.1
{
return Err(format!(
"template cache clean_interval min {} is not within range [{}, {}] for {}",
clean_interval_min,
limits.storage.cache.clean_interval_ranges.0.0,
limits.storage.cache.clean_interval_ranges.0.1,
template.name
)
.into());
}
if clean_interval_max < limits.storage.cache.clean_interval_ranges.1.0
|| clean_interval_max > limits.storage.cache.clean_interval_ranges.1.1
{
return Err(format!(
"template cache clean_interval min {} is not within range [{}, {}] for {}",
clean_interval_max,
limits.storage.cache.clean_interval_ranges.1.0,
limits.storage.cache.clean_interval_ranges.1.1,
template.name
)
.into());
}
}
}
}
}
if let Some(integrations) = &self.integrations {
if integrations.len() > limits.integration.count as usize {
return Err(format!(
"integration count {} greater than limit {}",
integrations.len(),
limits.integration.count
)
.into());
}
for integration in integrations {
if let Some(timeout) = integration.timeout
&& timeout > limits.integration.max_timeout
{
return Err(format!(
"integration timeout {} greater than limit {} for {}",
timeout, limits.integration.max_timeout, integration.name,
)
.into());
}
}
}
if let Some(actions) = &self.actions {
if actions.len() > limits.action.count as usize {
return Err(format!(
"action count {} greater than limit {}",
actions.len(),
limits.action.count
)
.into());
}
for action in actions {
if let Some(timeout) = action.timeout
&& timeout > limits.action.max_timeout
{
return Err(format!(
"action timeout {} greater than limit {} for {}",
timeout, limits.action.max_timeout, action.name
)
.into());
}
}
}
if self.storage_size > limits.storage.max_app_storage {
return Err("storage size is greater than limit".into());
}
if let Some(content) = &self.content {
if content.definitions.len() > limits.storage.content.max_content_definitions as usize {
return Err(format!(
"content definition count {} greater than limit {}",
content.definitions.len(),
limits.storage.content.max_content_definitions
)
.into());
}
for content_def in &content.definitions {
if content_def.fields.len() > limits.storage.content.max_content_fields as usize {
return Err(format!(
"content field count {} greater than limit {} for {}",
content_def.fields.len(),
limits.storage.content.max_content_fields,
content_def.name,
)
.into());
}
for field in &content_def.fields {
if field.searchable == Some(true) && !limits.storage.content.search_enabled {
return Err(format!(
"content field cannot be 'searchable' for field {} on definition {}",
field.name, content_def.name,
)
.into());
}
}
}
}
if let Some(models) = &self.models {
if models.len() > limits.storage.model.max_model_definitions as usize {
return Err(format!(
"model definition count {} greater than limit {}",
models.len(),
limits.storage.model.max_model_definitions
)
.into());
}
for model in models {
if model.fields.len() > limits.storage.model.max_model_fields as usize {
return Err(format!(
"model field count {} greater than limit {} for {}",
model.fields.len(),
limits.storage.content.max_content_fields,
model.name,
)
.into());
}
for field in &model.fields {
if field.searchable == Some(true) && !limits.storage.content.search_enabled {
return Err(format!(
"content field cannot be 'searchable' for field {} on definition {}",
field.name, model.name,
)
.into());
}
}
}
}
Ok(())
}
}