Skip to main content

fraiseql_server/server_config/
mod.rs

1//! Server configuration (`*Config` types).
2//!
3//! These are developer-facing configuration types loaded from `fraiseql.toml`,
4//! environment variables, or CLI flags. They are mutable between deployments.
5//!
6//! For the distinction between `*Config` (developer-facing, mutable) and
7//! `*Settings` (compiled into `schema.compiled.json`, immutable at runtime),
8//! see `docs/architecture/config-vs-settings.md`.
9
10pub(crate) mod defaults;
11pub mod hs256;
12mod methods;
13pub mod observers;
14pub mod tls;
15
16#[cfg(test)]
17mod tests;
18
19use std::{net::SocketAddr, path::PathBuf};
20
21use defaults::{
22    default_bind_addr, default_database_url, default_graphql_path, default_health_path,
23    default_introspection_path, default_max_header_bytes, default_max_header_count,
24    default_max_request_body_bytes, default_metrics_json_path, default_metrics_path,
25    default_playground_path, default_pool_max_size, default_pool_min_size, default_pool_timeout,
26    default_readiness_path, default_schema_path, default_shutdown_timeout_secs,
27    default_subscription_path,
28};
29use fraiseql_core::security::OidcConfig;
30pub use hs256::Hs256Config;
31pub use observers::AdmissionConfig;
32#[cfg(feature = "observers")]
33pub use observers::{ObserverConfig, ObserverPoolConfig};
34use serde::{Deserialize, Serialize};
35pub use tls::{DatabaseTlsConfig, PlaygroundTool, TlsServerConfig};
36
37use crate::middleware::RateLimitConfig;
38
39/// Server configuration.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ServerConfig {
42    /// Path to compiled schema JSON file.
43    #[serde(default = "defaults::default_schema_path")]
44    pub schema_path: PathBuf,
45
46    /// Database connection URL (PostgreSQL, MySQL, SQLite, SQL Server).
47    #[serde(default = "defaults::default_database_url")]
48    pub database_url: String,
49
50    /// Server bind address.
51    #[serde(default = "defaults::default_bind_addr")]
52    pub bind_addr: SocketAddr,
53
54    /// Arrow Flight gRPC bind address (requires `arrow` feature).
55    ///
56    /// Defaults to `0.0.0.0:50051`. Override with `FRAISEQL_FLIGHT_BIND_ADDR`
57    /// environment variable or this field in the config file.
58    #[cfg(feature = "arrow")]
59    #[serde(default = "defaults::default_flight_bind_addr")]
60    pub flight_bind_addr: SocketAddr,
61
62    /// Enable CORS.
63    #[serde(default = "defaults::default_true")]
64    pub cors_enabled: bool,
65
66    /// CORS allowed origins (if empty, allows all).
67    #[serde(default)]
68    pub cors_origins: Vec<String>,
69
70    /// Enable framework-level response compression.
71    ///
72    /// Defaults to `false`. In production FraiseQL is typically deployed
73    /// behind a reverse proxy (Nginx, Caddy, cloud load balancer) that
74    /// handles compression more efficiently (brotli, shared across upstreams,
75    /// cacheable). Enable this only for single-binary / no-proxy deployments.
76    #[serde(default = "defaults::default_false")]
77    pub compression_enabled: bool,
78
79    /// Enable request tracing.
80    #[serde(default = "defaults::default_true")]
81    pub tracing_enabled: bool,
82
83    /// OTLP exporter endpoint for distributed tracing.
84    ///
85    /// When set (e.g. `"http://otel-collector:4317"`), the server initializes an
86    /// `OpenTelemetry` OTLP exporter. When `None`, the `OTEL_EXPORTER_OTLP_ENDPOINT`
87    /// environment variable is checked as a fallback. If neither is set, no OTLP
88    /// export occurs (zero overhead).
89    #[serde(default)]
90    pub otlp_endpoint: Option<String>,
91
92    /// OTLP exporter timeout in seconds (default: 10).
93    #[serde(default = "defaults::default_otlp_timeout_secs")]
94    pub otlp_export_timeout_secs: u64,
95
96    /// Service name for distributed tracing (default: `"fraiseql"`).
97    #[serde(default = "defaults::default_service_name")]
98    pub tracing_service_name: String,
99
100    /// Enable APQ (Automatic Persisted Queries).
101    #[serde(default = "defaults::default_true")]
102    pub apq_enabled: bool,
103
104    /// Enable query caching.
105    #[serde(default = "defaults::default_true")]
106    pub cache_enabled: bool,
107
108    /// GraphQL endpoint path.
109    #[serde(default = "defaults::default_graphql_path")]
110    pub graphql_path: String,
111
112    /// Health check endpoint path (liveness probe).
113    ///
114    /// Returns 200 as long as the process is alive, 503 if the database is down.
115    #[serde(default = "defaults::default_health_path")]
116    pub health_path: String,
117
118    /// Readiness probe endpoint path.
119    ///
120    /// Returns 200 when the server is ready to serve traffic (database reachable),
121    /// 503 otherwise. Kubernetes `readinessProbe` should point here.
122    #[serde(default = "defaults::default_readiness_path")]
123    pub readiness_path: String,
124
125    /// Introspection endpoint path.
126    #[serde(default = "defaults::default_introspection_path")]
127    pub introspection_path: String,
128
129    /// Metrics endpoint path (Prometheus format).
130    #[serde(default = "defaults::default_metrics_path")]
131    pub metrics_path: String,
132
133    /// Metrics JSON endpoint path.
134    #[serde(default = "defaults::default_metrics_json_path")]
135    pub metrics_json_path: String,
136
137    /// Playground (GraphQL IDE) endpoint path.
138    #[serde(default = "defaults::default_playground_path")]
139    pub playground_path: String,
140
141    /// Enable GraphQL playground/IDE (default: false for production safety).
142    ///
143    /// When enabled, serves a GraphQL IDE (`GraphiQL` or Apollo Sandbox)
144    /// at the configured `playground_path`.
145    ///
146    /// **Security**: Disabled by default for production safety. Set to true for development
147    /// environments only. The playground exposes schema information and can be a
148    /// reconnaissance vector for attackers.
149    #[serde(default)]
150    pub playground_enabled: bool,
151
152    /// Which GraphQL IDE to use.
153    ///
154    /// - `graphiql`: The classic GraphQL IDE (default)
155    /// - `apollo-sandbox`: Apollo's embeddable sandbox
156    #[serde(default)]
157    pub playground_tool: PlaygroundTool,
158
159    /// `WebSocket` endpoint path for GraphQL subscriptions.
160    #[serde(default = "defaults::default_subscription_path")]
161    pub subscription_path: String,
162
163    /// Enable GraphQL subscriptions over `WebSocket`.
164    ///
165    /// When enabled, provides graphql-ws (graphql-transport-ws) protocol
166    /// support for real-time subscription events.
167    #[serde(default = "defaults::default_true")]
168    pub subscriptions_enabled: bool,
169
170    /// Enable metrics endpoints.
171    ///
172    /// **Security**: Disabled by default for production safety.
173    /// When enabled, requires `metrics_token` to be set for authentication.
174    #[serde(default)]
175    pub metrics_enabled: bool,
176
177    /// Bearer token for metrics endpoint authentication.
178    ///
179    /// Required when `metrics_enabled` is true. Requests must include:
180    /// `Authorization: Bearer <token>`
181    ///
182    /// **Security**: Use a strong, random token (e.g., 32+ characters).
183    #[serde(default)]
184    pub metrics_token: Option<String>,
185
186    /// Enable admin API endpoints (default: false for production safety).
187    ///
188    /// **Security**: Disabled by default. When enabled, requires `admin_token` to be set.
189    /// Admin endpoints allow schema reloading, cache management, and config inspection.
190    #[serde(default)]
191    pub admin_api_enabled: bool,
192
193    /// Bearer token for admin API authentication.
194    ///
195    /// Required when `admin_api_enabled` is true. Requests must include:
196    /// `Authorization: Bearer <token>`
197    ///
198    /// **Security**: Use a strong, random token (minimum 32 characters).
199    /// This token grants access to **destructive** admin operations:
200    /// `reload-schema`, `cache/clear`.
201    ///
202    /// If `admin_readonly_token` is set, this token is restricted to write
203    /// operations only. If `admin_readonly_token` is not set, this token
204    /// also grants access to read-only endpoints (backwards-compatible).
205    #[serde(default)]
206    pub admin_token: Option<String>,
207
208    /// Optional separate bearer token for read-only admin operations.
209    ///
210    /// When set, restricts `admin_token` to destructive operations only
211    /// (`reload-schema`, `cache/clear`) and uses this token for read-only
212    /// endpoints (`config`, `cache/stats`, `explain`, `grafana-dashboard`).
213    ///
214    /// Operators and monitoring tools can use this token without gaining
215    /// the ability to modify server state or reload the schema.
216    ///
217    /// **Security**: Must be different from `admin_token` and at least 32
218    /// characters. Requires `admin_api_enabled = true` and `admin_token` set.
219    #[serde(default)]
220    pub admin_readonly_token: Option<String>,
221
222    /// Enable introspection endpoint (default: false for production safety).
223    ///
224    /// **Security**: Disabled by default. When enabled, the introspection endpoint
225    /// exposes the complete GraphQL schema structure. Combined with `introspection_require_auth`,
226    /// you can optionally protect it with OIDC authentication.
227    #[serde(default)]
228    pub introspection_enabled: bool,
229
230    /// Require authentication for introspection endpoint (default: true).
231    ///
232    /// When true and OIDC is configured, introspection requires same auth as GraphQL endpoint.
233    /// When false, introspection is publicly accessible (use only in development).
234    #[serde(default = "defaults::default_true")]
235    pub introspection_require_auth: bool,
236
237    /// Require authentication for the schema metadata endpoint (default: None).
238    ///
239    /// When `Some(true)`, the `/api/v1/schema/metadata` endpoint requires OIDC auth
240    /// independently of introspection. When `Some(false)`, metadata is publicly
241    /// accessible regardless of introspection auth. When `None` (default), falls
242    /// back to `introspection_require_auth` for backwards compatibility.
243    #[serde(default, skip_serializing_if = "Option::is_none")]
244    pub metadata_require_auth: Option<bool>,
245
246    /// Require authentication for schema export endpoints (default: None).
247    ///
248    /// Controls `/api/v1/schema.graphql` and `/api/v1/schema.json` independently of
249    /// introspection auth. When `Some(true)`, schema export requires OIDC auth. When
250    /// `Some(false)`, schema export is publicly accessible. When `None` (default),
251    /// falls back to `introspection_require_auth` for backwards compatibility.
252    #[serde(default, skip_serializing_if = "Option::is_none")]
253    pub schema_export_require_auth: Option<bool>,
254
255    /// Require authentication for the GraphQL Playground endpoint (default: None).
256    ///
257    /// Controls the playground independently of introspection auth. When `Some(true)`,
258    /// the playground requires OIDC auth. When `Some(false)`, the playground is publicly
259    /// accessible. When `None` (default), falls back to `introspection_require_auth`
260    /// for backwards compatibility.
261    #[serde(default, skip_serializing_if = "Option::is_none")]
262    pub playground_require_auth: Option<bool>,
263
264    /// Require authentication for the `WebSocket` subscription endpoint (default: None).
265    ///
266    /// Controls the `/subscriptions` endpoint independently of introspection auth.
267    /// When `Some(true)`, the subscription endpoint requires OIDC auth. When `Some(false)`,
268    /// subscriptions are publicly accessible. When `None` (default), falls back to
269    /// `introspection_require_auth` for backwards compatibility.
270    #[serde(default, skip_serializing_if = "Option::is_none")]
271    pub subscription_require_auth: Option<bool>,
272
273    /// Require authentication for design audit API endpoints (default: true).
274    ///
275    /// Design audit endpoints expose system architecture and optimization opportunities.
276    /// When true and OIDC is configured, design endpoints require same auth as GraphQL endpoint.
277    /// When false, design endpoints are publicly accessible (use only in development).
278    #[serde(default = "defaults::default_true")]
279    pub design_api_require_auth: bool,
280
281    /// Database connection pool minimum size.
282    #[serde(default = "defaults::default_pool_min_size")]
283    pub pool_min_size: usize,
284
285    /// Database connection pool maximum size.
286    #[serde(default = "defaults::default_pool_max_size")]
287    pub pool_max_size: usize,
288
289    /// Database connection pool timeout in seconds.
290    #[serde(default = "defaults::default_pool_timeout")]
291    pub pool_timeout_secs: u64,
292
293    /// OIDC authentication configuration (optional).
294    ///
295    /// When set, enables JWT authentication using OIDC discovery.
296    /// Supports Auth0, Keycloak, Okta, Cognito, Azure AD, and any
297    /// OIDC-compliant provider.
298    ///
299    /// # Example (TOML)
300    ///
301    /// ```toml
302    /// [auth]
303    /// issuer = "https://your-tenant.auth0.com/"
304    /// audience = "your-api-identifier"
305    /// ```
306    #[serde(default)]
307    pub auth: Option<OidcConfig>,
308
309    /// HS256 symmetric-key authentication (optional).
310    ///
311    /// Alternative to `auth` (OIDC) for integration testing and internal
312    /// service-to-service scenarios. Mutually exclusive with `auth`.
313    ///
314    /// Validation is fully local — no discovery endpoint, no JWKS fetch.
315    /// Not recommended for public-facing production.
316    ///
317    /// # Example (TOML)
318    ///
319    /// ```toml
320    /// [auth_hs256]
321    /// secret_env = "FRAISEQL_HS256_SECRET"
322    /// issuer = "my-test-suite"
323    /// audience = "my-api"
324    /// ```
325    #[serde(default)]
326    pub auth_hs256: Option<Hs256Config>,
327
328    /// TLS/SSL configuration for HTTPS and encrypted connections.
329    ///
330    /// When set, enables TLS enforcement for HTTP/gRPC endpoints and
331    /// optionally requires mutual TLS (mTLS) for client certificates.
332    ///
333    /// # Example (TOML)
334    ///
335    /// ```toml
336    /// [tls]
337    /// enabled = true
338    /// cert_path = "/etc/fraiseql/cert.pem"
339    /// key_path = "/etc/fraiseql/key.pem"
340    /// require_client_cert = false
341    /// min_version = "1.2"  # "1.2" or "1.3"
342    /// ```
343    #[serde(default)]
344    pub tls: Option<TlsServerConfig>,
345
346    /// Database TLS configuration.
347    ///
348    /// Enables TLS for database connections and configures
349    /// per-database TLS settings (PostgreSQL, Redis, `ClickHouse`, etc.).
350    ///
351    /// # Example (TOML)
352    ///
353    /// ```toml
354    /// [database_tls]
355    /// postgres_ssl_mode = "require"  # disable, allow, prefer, require, verify-ca, verify-full
356    /// redis_ssl = true               # Use rediss:// protocol
357    /// clickhouse_https = true         # Use HTTPS
358    /// elasticsearch_https = true      # Use HTTPS
359    /// verify_certificates = true      # Verify server certificates
360    /// ```
361    #[serde(default)]
362    pub database_tls: Option<DatabaseTlsConfig>,
363
364    /// Require `Content-Type: application/json` on POST requests (default: true).
365    ///
366    /// CSRF protection: rejects POST requests with non-JSON Content-Type
367    /// (e.g. `text/plain`, `application/x-www-form-urlencoded`) with 415.
368    #[serde(default = "defaults::default_true")]
369    pub require_json_content_type: bool,
370
371    /// Maximum request body size in bytes (default: 1 MB).
372    ///
373    /// Requests exceeding this limit receive 413 Payload Too Large.
374    /// Set to 0 to use axum's default (no limit).
375    #[serde(default = "defaults::default_max_request_body_bytes")]
376    pub max_request_body_bytes: usize,
377
378    /// Maximum number of HTTP headers per request (default: 100).
379    ///
380    /// Requests with more headers than this limit receive 431 Request Header Fields Too Large.
381    /// Prevents header-flooding `DoS` attacks that exhaust memory.
382    #[serde(default = "defaults::default_max_header_count")]
383    pub max_header_count: usize,
384
385    /// Maximum total size of all HTTP headers in bytes (default: 32 `KiB`).
386    ///
387    /// Requests whose combined header name+value bytes exceed this limit receive
388    /// 431 Request Header Fields Too Large. Prevents memory exhaustion from
389    /// oversized header values.
390    #[serde(default = "defaults::default_max_header_bytes")]
391    pub max_header_bytes: usize,
392
393    /// Per-request processing timeout in seconds (default: `None` — no timeout).
394    ///
395    /// When set, each HTTP request must complete within this many seconds or
396    /// the server returns **408 Request Timeout**.  This is a defence-in-depth
397    /// measure against slow or runaway database queries.
398    ///
399    /// **Recommendation**: set to `60` for production deployments.
400    ///
401    /// # Example (TOML)
402    ///
403    /// ```toml
404    /// request_timeout_secs = 60
405    /// ```
406    #[serde(default)]
407    pub request_timeout_secs: Option<u64>,
408
409    /// Maximum byte length for a query string delivered via HTTP GET.
410    ///
411    /// GET queries are URL-encoded and passed as a query parameter. Very long
412    /// strings are either a `DoS` attempt or a sign that the caller should use
413    /// POST instead. Default: `100_000` (100 `KiB`).
414    ///
415    /// # Example (TOML)
416    ///
417    /// ```toml
418    /// max_get_query_bytes = 50000
419    /// ```
420    #[serde(default = "defaults::default_max_get_query_bytes")]
421    pub max_get_query_bytes: usize,
422
423    /// Rate limiting configuration for GraphQL requests.
424    ///
425    /// When configured, enables per-IP and per-user rate limiting with token bucket algorithm.
426    /// Defaults to enabled with sensible per-IP limits for security-by-default.
427    ///
428    /// # Example (TOML)
429    ///
430    /// ```toml
431    /// [rate_limiting]
432    /// enabled = true
433    /// rps_per_ip = 100      # 100 requests/second per IP
434    /// rps_per_user = 1000   # 1000 requests/second per authenticated user
435    /// burst_size = 500      # Allow bursts up to 500 requests
436    /// ```
437    #[serde(default)]
438    pub rate_limiting: Option<RateLimitConfig>,
439
440    /// Observer runtime configuration (optional, requires `observers` feature).
441    #[cfg(feature = "observers")]
442    #[serde(default)]
443    pub observers: Option<ObserverConfig>,
444
445    /// Connection pool pressure monitoring configuration.
446    ///
447    /// When `enabled = true`, the server spawns a background task that monitors
448    /// pool metrics and emits scaling recommendations via Prometheus metrics and
449    /// log lines. **The pool is not resized at runtime** — act on
450    /// `fraiseql_pool_tuning_*` events by adjusting `max_connections` and restarting.
451    ///
452    /// # Example (TOML)
453    ///
454    /// ```toml
455    /// [pool_tuning]
456    /// enabled = true
457    /// min_pool_size = 5
458    /// max_pool_size = 50
459    /// tuning_interval_ms = 30000
460    /// ```
461    #[serde(default)]
462    pub pool_tuning: Option<crate::config::pool_tuning::PoolPressureMonitorConfig>,
463
464    /// Admission control configuration.
465    ///
466    /// When set, enforces a maximum number of concurrent in-flight requests and
467    /// a maximum queue depth.  Requests that exceed either limit receive
468    /// `503 Service Unavailable` immediately instead of stalling under load.
469    ///
470    /// # Example (TOML)
471    ///
472    /// ```toml
473    /// [admission_control]
474    /// max_concurrent = 500
475    /// max_queue_depth = 1000
476    /// ```
477    #[serde(default)]
478    pub admission_control: Option<AdmissionConfig>,
479
480    /// Security contact email for `/.well-known/security.txt` (RFC 9116).
481    ///
482    /// When set, the server exposes a `/.well-known/security.txt` endpoint
483    /// with this email address as the security contact. This helps security
484    /// researchers report vulnerabilities responsibly.
485    ///
486    /// # Example (TOML)
487    ///
488    /// ```toml
489    /// security_contact = "security@example.com"
490    /// ```
491    #[serde(default)]
492    pub security_contact: Option<String>,
493
494    /// Query validation overrides (depth and complexity limits).
495    ///
496    /// When present, these values take precedence over the limits baked into
497    /// the compiled schema, allowing operators to tune validation without
498    /// recompiling.
499    ///
500    /// # Example (TOML)
501    ///
502    /// ```toml
503    /// [validation]
504    /// max_query_depth = 15
505    /// max_query_complexity = 200
506    /// ```
507    #[serde(default)]
508    pub validation: Option<fraiseql_core::schema::ValidationConfig>,
509
510    /// Maximum failed admin bearer auth attempts per IP within a 60-second
511    /// window before the IP is blocked with 429 Too Many Requests (default: 10).
512    ///
513    /// Set to `0` to disable brute-force protection entirely (not recommended).
514    ///
515    /// # Example (TOML)
516    ///
517    /// ```toml
518    /// admin_auth_max_failures = 5
519    /// ```
520    #[serde(default = "defaults::default_admin_auth_max_failures")]
521    pub admin_auth_max_failures: u32,
522
523    /// Bearer token protecting the storage REST API (`/storage/v1/`).
524    ///
525    /// When set, all requests to storage endpoints must include an
526    /// `Authorization: Bearer <token>` header that matches this value.  Requests
527    /// without a valid token receive **401 Unauthorized**.
528    ///
529    /// **Security**: This token protects *all* storage operations (upload, download,
530    /// delete, presigned URL).  Use a strong random string (minimum 32 characters).
531    /// Omit the field (or set `None`) to leave storage endpoints open — appropriate
532    /// only in development or when the storage API is behind a trusted network boundary.
533    ///
534    /// # Example (TOML)
535    ///
536    /// ```toml
537    /// storage_token = "your-strong-random-token-here"
538    /// ```
539    #[serde(default)]
540    pub storage_token: Option<String>,
541
542    /// Graceful shutdown drain timeout in seconds (default: 30).
543    ///
544    /// After a SIGTERM or Ctrl+C signal, the server stops accepting new connections and
545    /// waits for in-flight requests and background runtimes (observers) to finish.
546    /// If the drain takes longer than this value, the process logs a warning and exits
547    /// immediately instead of hanging indefinitely.
548    ///
549    /// Set this to match `terminationGracePeriodSeconds` in your Kubernetes pod spec
550    /// minus a small buffer (e.g., 25s when `terminationGracePeriodSeconds = 30`).
551    ///
552    /// Override with `FRAISEQL_SHUTDOWN_TIMEOUT_SECS`.
553    #[serde(default = "defaults::default_shutdown_timeout_secs")]
554    pub shutdown_timeout_secs: u64,
555
556    /// Usage counter persistence configuration (optional).
557    ///
558    /// When set, mutation usage counters are periodically flushed to PostgreSQL
559    /// and restored on server startup.  Requires a PostgreSQL database URL.
560    ///
561    /// ```toml
562    /// [usage]
563    /// flush_interval_secs = 60
564    /// ```
565    ///
566    /// When absent (default), counters are in-memory only and reset on restart.
567    #[serde(default)]
568    pub usage: Option<crate::config::UsagePersistenceConfig>,
569}
570
571impl Default for ServerConfig {
572    fn default() -> Self {
573        Self {
574            schema_path: default_schema_path(),
575            database_url: default_database_url(),
576            bind_addr: default_bind_addr(),
577            #[cfg(feature = "arrow")]
578            flight_bind_addr: defaults::default_flight_bind_addr(),
579            cors_enabled: true,
580            cors_origins: Vec::new(),
581            compression_enabled: false,
582            tracing_enabled: true,
583            otlp_endpoint: None,
584            otlp_export_timeout_secs: defaults::default_otlp_timeout_secs(),
585            tracing_service_name: defaults::default_service_name(),
586            apq_enabled: true,
587            cache_enabled: true,
588            graphql_path: default_graphql_path(),
589            health_path: default_health_path(),
590            readiness_path: default_readiness_path(),
591            introspection_path: default_introspection_path(),
592            metrics_path: default_metrics_path(),
593            metrics_json_path: default_metrics_json_path(),
594            playground_path: default_playground_path(),
595            playground_enabled: false, // Disabled by default for security
596            playground_tool: PlaygroundTool::default(),
597            subscription_path: default_subscription_path(),
598            subscriptions_enabled: true,
599            metrics_enabled: false, // Disabled by default for security
600            metrics_token: None,
601            admin_api_enabled: false, // Disabled by default for security
602            admin_token: None,
603            admin_readonly_token: None,
604            introspection_enabled: false, // Disabled by default for security
605            introspection_require_auth: true, // Require auth when enabled
606            metadata_require_auth: None,  // Falls back to introspection_require_auth
607            schema_export_require_auth: None, // Falls back to introspection_require_auth
608            playground_require_auth: None, // Falls back to introspection_require_auth
609            subscription_require_auth: None, // Falls back to introspection_require_auth
610            design_api_require_auth: true, // Require auth for design endpoints
611            pool_min_size: default_pool_min_size(),
612            pool_max_size: default_pool_max_size(),
613            pool_timeout_secs: default_pool_timeout(),
614            auth: None,       // No auth by default
615            auth_hs256: None, // No HS256 auth by default
616            tls: None,        // TLS disabled by default
617            database_tls: None, /* Database TLS disabled
618                               * by default */
619            require_json_content_type: true, // CSRF protection
620            max_request_body_bytes: default_max_request_body_bytes(), // 1 MB
621            max_header_count: default_max_header_count(), // 100 headers
622            max_header_bytes: default_max_header_bytes(), // 32 KiB
623            rate_limiting: None,             // Rate limiting uses defaults
624            #[cfg(feature = "observers")]
625            observers: None, // Observers disabled by default
626            pool_tuning: None,               // Pool pressure monitoring disabled by default
627            admission_control: None,         // Admission control disabled by default
628            security_contact: None,          // No security.txt by default
629            validation: None,                // Use compiled schema defaults
630            shutdown_timeout_secs: default_shutdown_timeout_secs(),
631            request_timeout_secs: None,
632            max_get_query_bytes: defaults::default_max_get_query_bytes(),
633            admin_auth_max_failures: defaults::default_admin_auth_max_failures(),
634            storage_token: None,
635            usage: None, // Usage persistence disabled by default
636        }
637    }
638}