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