Skip to main content

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