fraiseql_server/subsystems/
validator.rs1use std::fmt;
21
22use fraiseql_storage::backend::StorageBackend;
23
24use super::{ServerSubsystems, StorageSubsystem};
25use crate::schema::loader::SchemaStorageConfig;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum SubsystemConfigWarning {
36 LocalStorageInProduction,
39
40 UnknownBucket {
43 name: String,
45 },
46
47 EmptyFunctionsRegistry,
50
51 RealtimeWithNoEntities,
54}
55
56impl fmt::Display for SubsystemConfigWarning {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match self {
59 Self::LocalStorageInProduction => f.write_str(
60 "local filesystem storage is active — not suitable for multi-instance \
61 deployments; use S3, GCS, or Azure Blob in production",
62 ),
63 Self::UnknownBucket { name } => write!(
64 f,
65 "bucket '{name}' is declared in the schema config but not found in the \
66 runtime storage state — requests for this bucket will return 404; \
67 ensure the bucket name matches exactly"
68 ),
69 Self::EmptyFunctionsRegistry => f.write_str(
70 "the functions module directory is configured but no function \
71 definitions exist in the schema; the directory will be ignored",
72 ),
73 Self::RealtimeWithNoEntities => f.write_str(
74 "the realtime subsystem is enabled but no entities are declared; \
75 clients will receive errors for all subscribe requests — \
76 add entities under the 'realtime.entities' key in the compiled schema",
77 ),
78 }
79 }
80}
81
82#[must_use]
90pub fn validate_subsystems_config(subsystems: &ServerSubsystems) -> Vec<SubsystemConfigWarning> {
91 let mut warnings = Vec::new();
92
93 if let Some(storage) = &subsystems.storage {
94 check_storage(&mut warnings, storage);
95 }
96
97 if let Some(functions) = &subsystems.functions {
98 if functions.config.definitions.is_empty() {
99 warnings.push(SubsystemConfigWarning::EmptyFunctionsRegistry);
100 }
101 }
102
103 if let Some(realtime) = &subsystems.realtime {
104 if realtime.schema_config.entities.is_empty() {
105 warnings.push(SubsystemConfigWarning::RealtimeWithNoEntities);
106 }
107 }
108
109 warnings
110}
111
112fn check_storage(warnings: &mut Vec<SubsystemConfigWarning>, storage: &StorageSubsystem) {
114 if matches!(*storage.state.backend, StorageBackend::Local(_)) {
116 warnings.push(SubsystemConfigWarning::LocalStorageInProduction);
117 }
118
119 check_unknown_buckets(warnings, &storage.schema_config, storage);
121}
122
123fn check_unknown_buckets(
125 warnings: &mut Vec<SubsystemConfigWarning>,
126 schema_config: &SchemaStorageConfig,
127 storage: &StorageSubsystem,
128) {
129 for bucket_def in &schema_config.buckets {
130 if !storage.state.buckets.contains_key(&bucket_def.name) {
131 warnings.push(SubsystemConfigWarning::UnknownBucket {
132 name: bucket_def.name.clone(),
133 });
134 }
135 }
136}