Skip to main content

fraiseql_server/
lib.rs

1//! FraiseQL HTTP Server
2//!
3//! HTTP server for FraiseQL v2 compiled GraphQL execution engine.
4//!
5//! # Architecture
6//!
7//! The server exposes a GraphQL HTTP endpoint that:
8//! 1. Receives GraphQL queries via POST
9//! 2. Executes queries using the runtime Executor
10//! 3. Returns GraphQL-compliant JSON responses
11//!
12//! # Features
13//!
14//! - GraphQL endpoint (`/graphql`)
15//! - Health check endpoint (`/health`)
16//! - Schema introspection endpoint (`/introspection`)
17//! - CORS support
18//! - Compression (gzip, br, zstd)
19//! - Request tracing
20//! - APQ (Automatic Persisted Queries)
21//! - Query caching
22//! - Authentication middleware (optional)
23
24#![forbid(unsafe_code)]
25
26// CLI argument parsing (shared with fraiseql-cli) — requires `cli` feature
27#[cfg(feature = "cli")]
28pub mod cli;
29// API key authentication
30pub mod api_key;
31// Token revocation
32pub mod token_revocation;
33
34// Original fraiseql-server modules
35pub mod api;
36pub mod error;
37pub mod extractors;
38#[cfg(feature = "federation")]
39pub mod federation;
40pub mod logging;
41pub mod middleware;
42pub mod routes;
43pub mod schema;
44pub mod server;
45pub mod server_config;
46#[cfg(feature = "sources")]
47pub mod sources;
48pub mod sql_source_check;
49pub mod subscriptions;
50pub mod url_guard;
51pub mod validation;
52
53// Renamed to avoid conflicts with runtime modules
54pub mod metrics_server;
55
56/// Process-global `metrics`-facade recorder (Prometheus) install + render.
57#[cfg(feature = "metrics")]
58pub mod metrics_recorder;
59
60// fraiseql-runtime modules (merged)
61
62/// Runtime configuration types loaded from `fraiseql.toml` or environment variables.
63pub mod config;
64/// Resilience primitives: backpressure and retry policies.
65pub mod resilience;
66/// Utilities for distributed tracing, span propagation, and trace context formatting.
67#[cfg(feature = "federation")]
68pub mod tracing_utils;
69#[cfg(not(feature = "federation"))]
70pub mod tracing_utils {
71    //! Stub tracing utilities when federation is disabled.
72    use axum::http::HeaderMap;
73
74    /// Stub trace context extraction when federation is disabled.
75    #[allow(clippy::missing_const_for_fn)] // Reason: signature must match federation-enabled version which is not const
76    #[must_use]
77    pub fn extract_trace_context(_headers: &HeaderMap) -> Option<()> {
78        None
79    }
80
81    /// Extract the W3C trace id from the inbound `traceparent` header.
82    ///
83    /// Feature-independent (used to stamp the change-log `trace_id`, #375), so it
84    /// is a real implementation even without federation — identical to the
85    /// federation-enabled [`tracing_utils::extract_trace_id`](super::tracing_utils).
86    #[must_use]
87    pub fn extract_trace_id(headers: &HeaderMap) -> Option<String> {
88        let value = headers.get("traceparent")?.to_str().ok()?;
89        let trace_id = value.split('-').nth(1)?;
90        let valid = trace_id.len() == 32
91            && trace_id.bytes().all(|b| b.is_ascii_hexdigit())
92            && trace_id.bytes().any(|b| b != b'0');
93        valid.then(|| trace_id.to_ascii_lowercase())
94    }
95
96    /// Extract the full W3C trace context as a JSON object for the change-log
97    /// `trace_context` column (#375).
98    ///
99    /// Feature-independent — identical to the federation-enabled
100    /// [`tracing_utils::extract_trace_context_json`](super::tracing_utils).
101    #[must_use]
102    pub fn extract_trace_context_json(headers: &HeaderMap) -> Option<serde_json::Value> {
103        let traceparent = headers.get("traceparent")?.to_str().ok()?;
104        let mut parts = traceparent.split('-');
105        let (version, trace_id, parent_id, trace_flags) =
106            (parts.next()?, parts.next()?, parts.next()?, parts.next()?);
107        let is_hex =
108            |s: &str, len: usize| s.len() == len && s.bytes().all(|b| b.is_ascii_hexdigit());
109        let valid = is_hex(version, 2)
110            && is_hex(trace_id, 32)
111            && trace_id.bytes().any(|b| b != b'0')
112            && is_hex(parent_id, 16)
113            && is_hex(trace_flags, 2);
114        if !valid {
115            return None;
116        }
117        let mut obj = serde_json::Map::with_capacity(5);
118        obj.insert("version".to_owned(), version.to_ascii_lowercase().into());
119        obj.insert("trace_id".to_owned(), trace_id.to_ascii_lowercase().into());
120        obj.insert("parent_id".to_owned(), parent_id.to_ascii_lowercase().into());
121        obj.insert("trace_flags".to_owned(), trace_flags.to_ascii_lowercase().into());
122        if let Some(tracestate) = headers
123            .get("tracestate")
124            .and_then(|h| h.to_str().ok())
125            .map(str::trim)
126            .filter(|s| !s.is_empty())
127        {
128            obj.insert("tracestate".to_owned(), tracestate.into());
129        }
130        Some(serde_json::Value::Object(obj))
131    }
132}
133
134// Webhooks (extracted to fraiseql-webhooks crate) — optional, enable with `features = ["webhooks"]`
135// Authentication (extracted to fraiseql-auth crate) — optional, enable with `features =
136// ["auth"]`
137#[cfg(feature = "auth")]
138pub use fraiseql_auth as auth;
139#[cfg(feature = "webhooks")]
140pub use fraiseql_webhooks as webhooks;
141
142/// Stub auth types compiled when the `auth` feature is disabled.
143///
144/// These zero-sized types allow internal code that references `crate::auth::*` to compile
145/// in no-auth builds without requiring every call-site to be cfg-gated.  All stub methods
146/// are pure stubs that the compiler will dead-code-eliminate.
147#[cfg(not(feature = "auth"))]
148pub mod auth {
149    use std::sync::Arc;
150
151    /// Stub for `fraiseql_auth::state_encryption::StateEncryptionService`.
152    pub mod state_encryption {
153        /// Zero-sized stub; never instantiated when `auth` feature is off.
154        pub struct StateEncryptionService;
155        impl StateEncryptionService {
156            /// Stub: returns `None`.
157            ///
158            /// # Errors
159            ///
160            /// Currently infallible — always returns `Ok(None)`.
161            /// Errors may be returned when the `auth` feature is enabled.
162            pub fn from_compiled_schema(
163                _s: &serde_json::Value,
164            ) -> crate::Result<Option<std::sync::Arc<Self>>> {
165                Ok(None)
166            }
167        }
168    }
169
170    /// Stub for `fraiseql_auth::PkceStateStore`.
171    pub struct PkceStateStore;
172    impl PkceStateStore {
173        /// Stub: always returns `true` (in-memory).
174        pub fn is_in_memory(&self) -> bool {
175            true
176        }
177
178        /// Stub: no-op.
179        pub async fn cleanup_expired(&self) {}
180    }
181
182    /// Stub for `fraiseql_auth::OidcServerClient`.
183    pub struct OidcServerClient;
184    impl OidcServerClient {
185        /// Stub: always returns `None`.
186        pub fn from_compiled_schema(_schema_json: &serde_json::Value) -> Option<Arc<Self>> {
187            None
188        }
189    }
190}
191
192// Secrets management and encryption (extracted to fraiseql-secrets crate) — optional, enable with
193// `features = ["secrets"]`
194#[cfg(feature = "secrets")]
195pub use fraiseql_secrets::{encryption, secrets_manager};
196
197// TLS/SSL and encryption
198pub mod tls;
199
200// Enriched-identity resolution (#539): request-scoped `sub → DB → identity`
201// mapping for RLS read-scoping and verified sender-identity. Requires an
202// authenticated subject and the unscoped enrichment pool, so gated on `auth`.
203#[cfg(feature = "auth")]
204pub mod identity;
205
206// Observer management - optional
207#[cfg(feature = "observers")]
208pub mod observers;
209
210// Arrow Flight integration - optional
211#[cfg(feature = "arrow")]
212pub mod arrow;
213
214// MCP (Model Context Protocol) server - optional
215#[cfg(feature = "mcp")]
216pub mod mcp;
217
218// Inbound ingestion as a source - optional
219#[cfg(feature = "inbound")]
220pub mod inbound;
221
222// Connection pool management and auto-tuning
223pub mod pool;
224
225// Realtime WebSocket server — entity change streams (complementary to subscriptions/)
226pub mod realtime;
227
228// Object storage backends (local, S3, GCS, Azure Blob)
229pub mod storage;
230
231// Server subsystem assembly and lifecycle management
232pub mod subsystems;
233
234// Trusted documents (query allowlist)
235pub mod trusted_documents;
236
237// Multi-tenancy: pool factory, executor construction, health monitoring
238pub mod tenancy;
239
240// Usage aggregation: in-memory mutation counters fed by tracing events
241pub mod usage;
242
243// Testing utilities
244#[cfg(any(test, feature = "testing"))]
245pub mod testing;
246
247#[cfg(test)]
248mod tests;
249
250#[cfg(feature = "cli")]
251pub use cli::{Cli, ServerArgs};
252pub use logging::{
253    ErrorDetails, LogLevel, LogMetrics, RequestContext, RequestId, RequestLogger, SourceLocation,
254    StructuredLogEntry,
255};
256pub use metrics_server::{MetricsCollector, PrometheusMetrics};
257pub use schema::CompiledSchemaLoader;
258pub use server::Server;
259pub use server_config::ServerConfig;
260pub use tls::TlsSetup;
261pub use validation::{ComplexityValidationError, RequestValidator};
262
263/// Convenience re-exports for building a FraiseQL HTTP server.
264///
265/// Provides [`Server`], [`ServerConfig`], `CompiledSchema`, and [`RequestValidator`].
266///
267/// ```rust
268/// use fraiseql_server::prelude::*;
269/// ```
270pub mod prelude {
271    pub use fraiseql_core::schema::CompiledSchema;
272
273    pub use crate::{
274        ComplexityValidationError, RequestValidator, Server, ServerConfig, ServerError, TlsSetup,
275    };
276}
277
278/// Server error type.
279#[derive(Debug, thiserror::Error)]
280#[non_exhaustive]
281pub enum ServerError {
282    /// Server binding error.
283    #[error("Failed to bind server: {0}")]
284    BindError(String),
285
286    /// Configuration error.
287    #[error("Configuration error: {0}")]
288    ConfigError(String),
289
290    /// Error from the FraiseQL execution engine (parse, validate, execute, …).
291    ///
292    /// Wraps the canonical [`fraiseql_core::error::FraiseQLError`] so engine
293    /// failures bubble up through `ServerError` without losing the structured
294    /// payload. The original variant name (`RuntimeError`) collided with the
295    /// retired `fraiseql_error::RuntimeError` HTTP-shaped enum; `Engine`
296    /// reflects what the variant actually wraps.
297    #[error("Engine error: {0}")]
298    Engine(#[from] fraiseql_core::error::FraiseQLError),
299
300    /// IO error.
301    #[error("IO error: {0}")]
302    IoError(#[from] std::io::Error),
303
304    /// Database error.
305    #[error("Database error: {0}")]
306    Database(String),
307
308    /// Validation error.
309    #[error("Validation error: {0}")]
310    Validation(String),
311
312    /// Resource conflict error.
313    #[error("Conflict: {0}")]
314    Conflict(String),
315
316    /// Resource not found error.
317    #[error("Not found: {0}")]
318    NotFound(String),
319}
320
321/// Server result type.
322pub type Result<T> = std::result::Result<T, ServerError>;