fraiseql_server/server/mod.rs
1//! HTTP server implementation.
2
3use std::sync::Arc;
4
5#[cfg(feature = "arrow")]
6use fraiseql_arrow::FraiseQLFlightService;
7use fraiseql_core::{
8 db::traits::DatabaseAdapter,
9 runtime::{Executor, SubscriptionManager},
10 security::{AuthMiddleware, OidcValidator},
11};
12#[cfg(feature = "observers")]
13use {
14 crate::observers::{ObserverRuntime, ObserverRuntimeConfig},
15 tokio::sync::RwLock,
16};
17
18#[cfg(feature = "auth")]
19use crate::routes::{AuthMeState, AuthPkceState, auth_callback, auth_me, auth_start};
20use crate::{
21 Result, ServerError,
22 middleware::{
23 BearerAuthState, OidcAuthState, RateLimiter, admin_auth_middleware, bearer_auth_middleware,
24 cors_layer_restricted, metrics_middleware, oidc_auth_middleware, require_json_content_type,
25 required_auth_middleware, trace_layer,
26 },
27 routes::{
28 BroadcastState, PlaygroundState, SubscriptionState, api, broadcast_handler,
29 graphql_get_handler, graphql_handler, health_handler, introspection_handler,
30 metrics_handler, metrics_json_handler, playground_handler, readiness_handler,
31 subscription_handler,
32 },
33 server_config::ServerConfig,
34 tls::TlsSetup,
35};
36
37mod builder;
38mod extensions;
39#[cfg(feature = "functions-runtime")]
40mod functions_setup;
41mod initialization;
42mod lifecycle;
43mod routing;
44
45#[cfg(test)]
46mod routing_tests;
47
48#[cfg(test)]
49mod tests;
50
51/// FraiseQL HTTP Server.
52///
53/// `Server<A>` is generic over a `DatabaseAdapter` implementation, which allows
54/// swapping database backends and injecting mock adapters in tests.
55///
56/// # Feature: `observers`
57///
58/// When compiled with the `observers` Cargo feature, the server mounts observer
59/// management and runtime-health API endpoints under `/api/observers`. These
60/// endpoints require a live **PostgreSQL** connection pool (`sqlx::PgPool`).
61///
62/// Pass `Some(pg_pool)` as the `db_pool` argument to [`Server::new`] when the
63/// `observers` feature is enabled. Passing `None` causes the observer routes to
64/// be skipped at startup (an error is logged) rather than panicking, but the
65/// rest of the server continues to function normally.
66///
67/// The PostgreSQL pool is distinct from the generic `DatabaseAdapter`: the
68/// adapter handles application queries, while the pool is used exclusively by
69/// the observer subsystem to store and retrieve reactive rule metadata.
70pub struct Server<A: DatabaseAdapter> {
71 pub(super) config: ServerConfig,
72 pub(super) executor: Arc<Executor<A>>,
73 pub(super) subscription_manager: Arc<SubscriptionManager>,
74 pub(super) subscription_lifecycle: Arc<dyn crate::subscriptions::SubscriptionLifecycle>,
75 pub(super) max_subscriptions_per_connection: Option<u32>,
76 pub(super) oidc_validator: Option<Arc<OidcValidator>>,
77 /// Local HS256 JWT validator (alternative to `oidc_validator`).
78 ///
79 /// When set, the GraphQL endpoint is protected by shared-secret JWT
80 /// validation instead of OIDC. Intended for integration testing and
81 /// internal service-to-service auth.
82 pub(super) hs256_auth: Option<Arc<AuthMiddleware>>,
83 pub(super) rate_limiter: Option<Arc<RateLimiter>>,
84 #[cfg(feature = "secrets")]
85 pub(super) secrets_manager: Option<Arc<crate::secrets_manager::SecretsManager>>,
86 #[cfg(feature = "federation")]
87 pub(super) circuit_breaker:
88 Option<Arc<crate::federation::circuit_breaker::FederationCircuitBreakerManager>>,
89 pub(super) error_sanitizer: Arc<crate::config::error_sanitization::ErrorSanitizer>,
90 #[cfg(feature = "auth")]
91 pub(super) state_encryption: Option<Arc<crate::auth::state_encryption::StateEncryptionService>>,
92 #[cfg(feature = "auth")]
93 pub(super) pkce_store: Option<Arc<crate::auth::PkceStateStore>>,
94 #[cfg(feature = "auth")]
95 pub(super) oidc_server_client: Option<Arc<crate::auth::OidcServerClient>>,
96 /// Unified social login provider registry.
97 ///
98 /// When `Some`, the server mounts `GET /auth/v1/authorize` and uses the
99 /// registry to look up `OAuth` providers by name. Set via
100 /// [`Server::with_social_login`].
101 #[cfg(feature = "auth")]
102 pub(super) social_login: Option<Arc<crate::auth::social::SocialLoginState>>,
103 /// Anonymous session signup state.
104 ///
105 /// When `Some`, mounts `POST /auth/v1/signup`. Set via [`Server::with_anon_signup`].
106 #[cfg(feature = "auth")]
107 pub(super) anon_signup_state: Option<Arc<crate::auth::AnonSignupState>>,
108 /// `TOTP` `MFA` route state.
109 ///
110 /// When `Some`, the server mounts the four `MFA` endpoints under
111 /// `/auth/v1/mfa/`. Set via [`Server::with_mfa`].
112 #[cfg(feature = "auth")]
113 pub(super) mfa_state: Option<Arc<crate::auth::MfaRouteState>>,
114 pub(super) api_key_authenticator: Option<Arc<crate::api_key::ApiKeyAuthenticator>>,
115 pub(super) service_account_authenticator:
116 Option<Arc<crate::service_account::ServiceAccountAuthenticator>>,
117 // Reason: only read inside #[cfg(feature = "auth")] blocks in routing.rs
118 #[allow(dead_code)] // Reason: field kept for API completeness; may be used in future features
119 pub(super) revocation_manager: Option<Arc<crate::token_revocation::TokenRevocationManager>>,
120 pub(super) apq_store: Option<fraiseql_core::apq::ArcApqStorage>,
121 pub(super) trusted_docs: Option<Arc<crate::trusted_documents::TrustedDocumentStore>>,
122
123 #[cfg(feature = "observers")]
124 pub(super) observer_runtime: Option<Arc<RwLock<ObserverRuntime>>>,
125
126 #[cfg(feature = "observers")]
127 pub(super) db_pool: Option<sqlx::PgPool>,
128
129 /// PostgreSQL pool for claims enrichment queries (independent of observers).
130 #[cfg(feature = "auth")]
131 #[allow(dead_code)] // Reason: read by enrichment routing code (ported in sub-phase 4e)
132 pub(super) enrichment_pool: Option<sqlx::PgPool>,
133
134 #[cfg(feature = "arrow")]
135 pub(super) flight_service: Option<FraiseQLFlightService>,
136
137 #[cfg(feature = "mcp")]
138 pub(super) mcp_config: Option<fraiseql_core::schema::McpConfig>,
139
140 /// Pre-built storage state for mounting storage routes.
141 ///
142 /// Populated during server construction when `[storage]` is configured and
143 /// a PostgreSQL pool is available for metadata tracking.
144 pub(super) storage_state: Option<fraiseql_storage::StorageState>,
145
146 /// Pre-built realtime state for mounting the `WebSocket` endpoint.
147 ///
148 /// When `Some`, `build_base_router` merges `realtime_router(state)` at
149 /// `/realtime/v1`. Set via [`Server::with_realtime`].
150 pub(super) realtime_state: Option<crate::realtime::server::RealtimeState>,
151
152 /// Before-mutation function-dispatch hooks, prepared at serve time from the
153 /// compiled schema's functions config (modules loaded, runtimes registered,
154 /// `send_email` wiring attached). When `Some`, `build_app_state` attaches them
155 /// so after:mutation functions fire. `None` when no functions are declared or
156 /// the `functions-runtime` feature is off.
157 #[cfg(feature = "functions-runtime")]
158 pub(super) functions_hooks: Option<Arc<crate::subsystems::BeforeMutationHooks>>,
159
160 /// Factory for building per-tenant executors at registration time.
161 ///
162 /// Set by the binary's PostgreSQL boot path (where the concrete adapter
163 /// implements [`FromPoolConfig`](crate::tenancy::FromPoolConfig)) via
164 /// [`Server::with_tenant_executor_factory`]. When the multi-tenant runtime is
165 /// enabled, `build_app_state` installs it into `AppState` so
166 /// `PUT /api/v1/admin/tenants/{key}` can provision tenants. `None` leaves
167 /// runtime provisioning unavailable (dispatch to pre-registered tenants still
168 /// works).
169 pub(super) tenant_executor_factory: Option<crate::tenancy::TenantExecutorFactory<A>>,
170
171 /// Pool pressure monitoring configuration (loaded from `[pool_tuning]` in `fraiseql.toml`).
172 pub(super) pool_tuning_config: Option<crate::config::pool_tuning::PoolPressureMonitorConfig>,
173
174 /// Whether the adapter-level query result cache (`CachedDatabaseAdapter`) is active.
175 ///
176 /// Set to `true` when `ServerConfig::cache_enabled = true` and the server was built
177 /// with `Server::new` or `Server::with_relay_pagination`.
178 pub(super) adapter_cache_enabled: bool,
179
180 /// Broadcast channel manager for ephemeral realtime pub/sub.
181 pub(super) broadcast_manager: Option<Arc<crate::subscriptions::BroadcastManager>>,
182
183 /// Presence manager for room-based member tracking.
184 pub(super) presence_manager: Option<Arc<crate::subscriptions::PresenceManager>>,
185
186 /// Object storage backend for the `/storage/v1/` routes.
187 ///
188 /// Set via [`Server::with_storage`]. When `None`, storage routes are not mounted.
189 pub(super) storage_backend: Option<Arc<dyn crate::storage::StorageBackend>>,
190 /// Maximum allowed upload size for the storage backend (bytes).
191 ///
192 /// Defaults to 100 `MiB`. Applied as a per-request body limit on upload routes.
193 pub(super) storage_max_upload_bytes: usize,
194
195 /// Function deployment store for the `/functions/v1/` routes.
196 ///
197 /// Set via [`Server::with_functions`]. When `None`, function routes are not mounted.
198 #[cfg(feature = "functions")]
199 pub(super) function_store: Option<Arc<dyn fraiseql_functions::FunctionStore>>,
200
201 /// Function execution runtime for the `/functions/v1/` routes.
202 ///
203 /// Set via [`Server::with_functions`]. When `None`, function routes are not mounted.
204 #[cfg(feature = "functions")]
205 pub(super) function_runtime: Option<Arc<dyn fraiseql_functions::runtime::SendFunctionRuntime>>,
206
207 /// Shared usage aggregator — written by [`MutationAuditLayer`] and read by
208 /// the `GET /api/v1/admin/usage` endpoint via [`AppState::usage`].
209 ///
210 /// [`MutationAuditLayer`]: crate::usage::layer::MutationAuditLayer
211 /// [`AppState::usage`]: crate::routes::graphql::AppState::usage
212 pub(super) usage: Arc<crate::usage::aggregator::UsageAggregator>,
213
214 /// Background lifecycle tasks owned by the server.
215 ///
216 /// Long-running tasks spawned during server construction or `serve_with_shutdown`
217 /// (e.g. SIGUSR1 schema reload, PKCE state cleanup, trusted-documents manifest
218 /// reload, usage persistence flush, Arrow Flight gRPC server) are tracked on
219 /// this [`tokio::task::JoinSet`]. On graceful shutdown the server aborts and
220 /// awaits the set so per-process state is not abandoned mid-flight.
221 pub(super) tasks: tokio::task::JoinSet<()>,
222}