1#![allow(clippy::pedantic, clippy::nursery)]
2#![cfg_attr(test, allow(clippy::all))]
3pub mod auth;
9pub mod background;
10pub mod bindings;
11pub mod body_metadata;
12pub mod cors;
13pub mod debug;
14#[cfg(feature = "di")]
15pub mod di_handler;
16pub mod grpc;
17pub mod handler_response;
18pub mod handler_trait;
19pub mod jsonrpc;
20pub mod lifecycle;
21pub mod middleware;
22pub mod openapi;
23pub mod query_parser;
24pub mod response;
25pub mod server;
26pub mod sse;
27pub mod testing;
28pub mod websocket;
29
30use serde::{Deserialize, Serialize};
31
32#[cfg(test)]
33mod handler_trait_tests;
34
35pub use auth::{Claims, api_key_auth_middleware, jwt_auth_middleware};
36pub use background::{
37 BackgroundHandle, BackgroundJobError, BackgroundJobMetadata, BackgroundRuntime, BackgroundSpawnError,
38 BackgroundTaskConfig,
39};
40pub use body_metadata::ResponseBodySize;
41#[cfg(feature = "di")]
42pub use di_handler::DependencyInjectingHandler;
43pub use grpc::{
44 GrpcConfig, GrpcHandler, GrpcHandlerResult, GrpcRegistry, GrpcRequestData, GrpcResponseData, MessageStream,
45 StreamingRequest, StreamingResponse,
46};
47pub use handler_response::HandlerResponse;
48pub use handler_trait::{Handler, HandlerResult, RequestData, StaticResponse, StaticResponseHandler, ValidatedParams};
49pub use jsonrpc::JsonRpcConfig;
50pub use lifecycle::{HookResult, LifecycleHook, LifecycleHooks, LifecycleHooksBuilder, request_hook, response_hook};
51pub use openapi::{ContactInfo, LicenseInfo, OpenApiConfig, SecuritySchemeInfo, ServerInfo};
52pub use response::Response;
53pub use server::Server;
54pub use spikard_core::{
55 CompressionConfig, CorsConfig, Method, ParameterValidator, ProblemDetails, RateLimitConfig, Route, RouteHandler,
56 RouteMetadata, Router, SchemaRegistry, SchemaValidator,
57};
58pub use sse::{SseEvent, SseEventProducer, SseState, sse_handler};
59pub use testing::{ResponseSnapshot, SnapshotError, snapshot_response};
60pub use websocket::{WebSocketHandler, WebSocketState, websocket_handler};
61
62pub use spikard_core::problem::CONTENT_TYPE_PROBLEM_JSON;
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct JwtConfig {
68 pub secret: String,
70 #[serde(default = "default_jwt_algorithm")]
72 pub algorithm: String,
73 pub audience: Option<Vec<String>>,
75 pub issuer: Option<String>,
77 #[serde(default)]
79 pub leeway: u64,
80}
81
82fn default_jwt_algorithm() -> String {
83 "HS256".to_string()
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct ApiKeyConfig {
89 pub keys: Vec<String>,
91 #[serde(default = "default_api_key_header")]
93 pub header_name: String,
94}
95
96fn default_api_key_header() -> String {
97 "X-API-Key".to_string()
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct StaticFilesConfig {
103 pub directory: String,
105 pub route_prefix: String,
107 #[serde(default = "default_true")]
109 pub index_file: bool,
110 pub cache_control: Option<String>,
112}
113
114#[derive(Debug, Clone)]
116pub struct ServerConfig {
117 pub host: String,
119 pub port: u16,
121 pub workers: usize,
123
124 pub enable_request_id: bool,
126 pub max_body_size: Option<usize>,
128 pub request_timeout: Option<u64>,
130 pub compression: Option<CompressionConfig>,
132 pub rate_limit: Option<RateLimitConfig>,
134 pub jwt_auth: Option<JwtConfig>,
136 pub api_key_auth: Option<ApiKeyConfig>,
138 pub static_files: Vec<StaticFilesConfig>,
140 pub graceful_shutdown: bool,
142 pub shutdown_timeout: u64,
144 pub openapi: Option<crate::openapi::OpenApiConfig>,
146 pub jsonrpc: Option<crate::jsonrpc::JsonRpcConfig>,
148 pub grpc: Option<crate::grpc::GrpcConfig>,
150 pub lifecycle_hooks: Option<std::sync::Arc<LifecycleHooks>>,
152 pub background_tasks: BackgroundTaskConfig,
154 pub enable_http_trace: bool,
156 #[cfg(feature = "di")]
158 pub di_container: Option<std::sync::Arc<spikard_core::di::DependencyContainer>>,
159}
160
161impl Default for ServerConfig {
162 fn default() -> Self {
163 Self {
164 host: "127.0.0.1".to_string(),
165 port: 8000,
166 workers: 1,
167 enable_request_id: false,
168 max_body_size: Some(10 * 1024 * 1024),
169 request_timeout: None,
170 compression: None,
171 rate_limit: None,
172 jwt_auth: None,
173 api_key_auth: None,
174 static_files: Vec::new(),
175 graceful_shutdown: true,
176 shutdown_timeout: 30,
177 openapi: None,
178 jsonrpc: None,
179 grpc: None,
180 lifecycle_hooks: None,
181 background_tasks: BackgroundTaskConfig::default(),
182 enable_http_trace: false,
183 #[cfg(feature = "di")]
184 di_container: None,
185 }
186 }
187}
188
189impl ServerConfig {
190 pub fn builder() -> ServerConfigBuilder {
203 ServerConfigBuilder::default()
204 }
205}
206
207#[derive(Debug, Clone, Default)]
243pub struct ServerConfigBuilder {
244 config: ServerConfig,
245}
246
247impl ServerConfigBuilder {
248 pub fn host(mut self, host: impl Into<String>) -> Self {
250 self.config.host = host.into();
251 self
252 }
253
254 pub fn port(mut self, port: u16) -> Self {
256 self.config.port = port;
257 self
258 }
259
260 pub fn workers(mut self, workers: usize) -> Self {
262 self.config.workers = workers;
263 self
264 }
265
266 pub fn enable_request_id(mut self, enable: bool) -> Self {
268 self.config.enable_request_id = enable;
269 self
270 }
271
272 pub fn enable_http_trace(mut self, enable: bool) -> Self {
274 self.config.enable_http_trace = enable;
275 self
276 }
277
278 pub fn max_body_size(mut self, size: Option<usize>) -> Self {
280 self.config.max_body_size = size;
281 self
282 }
283
284 pub fn request_timeout(mut self, timeout: Option<u64>) -> Self {
286 self.config.request_timeout = timeout;
287 self
288 }
289
290 pub fn compression(mut self, compression: Option<CompressionConfig>) -> Self {
292 self.config.compression = compression;
293 self
294 }
295
296 pub fn rate_limit(mut self, rate_limit: Option<RateLimitConfig>) -> Self {
298 self.config.rate_limit = rate_limit;
299 self
300 }
301
302 pub fn jwt_auth(mut self, jwt_auth: Option<JwtConfig>) -> Self {
304 self.config.jwt_auth = jwt_auth;
305 self
306 }
307
308 pub fn api_key_auth(mut self, api_key_auth: Option<ApiKeyConfig>) -> Self {
310 self.config.api_key_auth = api_key_auth;
311 self
312 }
313
314 pub fn static_files(mut self, static_files: Vec<StaticFilesConfig>) -> Self {
316 self.config.static_files = static_files;
317 self
318 }
319
320 pub fn add_static_files(mut self, static_file: StaticFilesConfig) -> Self {
322 self.config.static_files.push(static_file);
323 self
324 }
325
326 pub fn graceful_shutdown(mut self, enable: bool) -> Self {
328 self.config.graceful_shutdown = enable;
329 self
330 }
331
332 pub fn shutdown_timeout(mut self, timeout: u64) -> Self {
334 self.config.shutdown_timeout = timeout;
335 self
336 }
337
338 pub fn openapi(mut self, openapi: Option<crate::openapi::OpenApiConfig>) -> Self {
340 self.config.openapi = openapi;
341 self
342 }
343
344 pub fn jsonrpc(mut self, jsonrpc: Option<crate::jsonrpc::JsonRpcConfig>) -> Self {
346 self.config.jsonrpc = jsonrpc;
347 self
348 }
349
350 pub fn grpc(mut self, grpc: Option<crate::grpc::GrpcConfig>) -> Self {
352 self.config.grpc = grpc;
353 self
354 }
355
356 pub fn lifecycle_hooks(mut self, hooks: Option<std::sync::Arc<LifecycleHooks>>) -> Self {
358 self.config.lifecycle_hooks = hooks;
359 self
360 }
361
362 pub fn background_tasks(mut self, config: BackgroundTaskConfig) -> Self {
364 self.config.background_tasks = config;
365 self
366 }
367
368 #[cfg(feature = "di")]
388 pub fn provide_value<T: Clone + Send + Sync + 'static>(mut self, key: impl Into<String>, value: T) -> Self {
389 use spikard_core::di::{DependencyContainer, ValueDependency};
390 use std::sync::Arc;
391
392 let key_str = key.into();
393
394 let container = if let Some(container) = self.config.di_container.take() {
395 Arc::try_unwrap(container).unwrap_or_else(|_arc| DependencyContainer::new())
396 } else {
397 DependencyContainer::new()
398 };
399
400 let mut container = container;
401
402 let dep = ValueDependency::new(key_str.clone(), value);
403
404 container
405 .register(key_str, Arc::new(dep))
406 .expect("Failed to register dependency");
407
408 self.config.di_container = Some(Arc::new(container));
409 self
410 }
411
412 #[cfg(feature = "di")]
447 pub fn provide_factory<F, Fut, T>(mut self, key: impl Into<String>, factory: F) -> Self
448 where
449 F: Fn(&spikard_core::di::ResolvedDependencies) -> Fut + Send + Sync + Clone + 'static,
450 Fut: std::future::Future<Output = Result<T, String>> + Send + 'static,
451 T: Send + Sync + 'static,
452 {
453 use futures::future::BoxFuture;
454 use spikard_core::di::{DependencyContainer, DependencyError, FactoryDependency};
455 use std::sync::Arc;
456
457 let key_str = key.into();
458
459 let container = if let Some(container) = self.config.di_container.take() {
460 Arc::try_unwrap(container).unwrap_or_else(|_| DependencyContainer::new())
461 } else {
462 DependencyContainer::new()
463 };
464
465 let mut container = container;
466
467 let factory_clone = factory.clone();
468
469 let dep = FactoryDependency::builder(key_str.clone())
470 .factory(
471 move |_req: &axum::http::Request<()>,
472 _data: &spikard_core::RequestData,
473 resolved: &spikard_core::di::ResolvedDependencies| {
474 let factory = factory_clone.clone();
475 let factory_result = factory(resolved);
476 Box::pin(async move {
477 let result = factory_result
478 .await
479 .map_err(|e| DependencyError::ResolutionFailed { message: e })?;
480 Ok(Arc::new(result) as Arc<dyn std::any::Any + Send + Sync>)
481 })
482 as BoxFuture<'static, Result<Arc<dyn std::any::Any + Send + Sync>, DependencyError>>
483 },
484 )
485 .build();
486
487 container
488 .register(key_str, Arc::new(dep))
489 .expect("Failed to register dependency");
490
491 self.config.di_container = Some(Arc::new(container));
492 self
493 }
494
495 #[cfg(feature = "di")]
518 pub fn provide(mut self, dependency: std::sync::Arc<dyn spikard_core::di::Dependency>) -> Self {
519 use spikard_core::di::DependencyContainer;
520 use std::sync::Arc;
521
522 let key = dependency.key().to_string();
523
524 let container = if let Some(container) = self.config.di_container.take() {
525 Arc::try_unwrap(container).unwrap_or_else(|_| DependencyContainer::new())
526 } else {
527 DependencyContainer::new()
528 };
529
530 let mut container = container;
531
532 container
533 .register(key, dependency)
534 .expect("Failed to register dependency");
535
536 self.config.di_container = Some(Arc::new(container));
537 self
538 }
539
540 pub fn build(self) -> ServerConfig {
542 self.config
543 }
544}
545
546const fn default_true() -> bool {
547 true
548}