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 handler_response;
17pub mod handler_trait;
18pub mod jsonrpc;
19pub mod lifecycle;
20pub mod middleware;
21pub mod openapi;
22pub mod query_parser;
23pub mod response;
24pub mod server;
25pub mod sse;
26pub mod testing;
27pub mod websocket;
28
29use serde::{Deserialize, Serialize};
30
31#[cfg(test)]
32mod handler_trait_tests;
33
34pub use auth::{Claims, api_key_auth_middleware, jwt_auth_middleware};
35pub use background::{
36 BackgroundHandle, BackgroundJobError, BackgroundJobMetadata, BackgroundRuntime, BackgroundSpawnError,
37 BackgroundTaskConfig,
38};
39pub use body_metadata::ResponseBodySize;
40#[cfg(feature = "di")]
41pub use di_handler::DependencyInjectingHandler;
42pub use handler_response::HandlerResponse;
43pub use handler_trait::{Handler, HandlerResult, RequestData, ValidatedParams};
44pub use jsonrpc::JsonRpcConfig;
45pub use lifecycle::{HookResult, LifecycleHook, LifecycleHooks, LifecycleHooksBuilder, request_hook, response_hook};
46pub use openapi::{ContactInfo, LicenseInfo, OpenApiConfig, SecuritySchemeInfo, ServerInfo};
47pub use response::Response;
48pub use server::Server;
49pub use spikard_core::{
50 CompressionConfig, CorsConfig, Method, ParameterValidator, ProblemDetails, RateLimitConfig, Route, RouteHandler,
51 RouteMetadata, Router, SchemaRegistry, SchemaValidator,
52};
53pub use sse::{SseEvent, SseEventProducer, SseState, sse_handler};
54pub use testing::{ResponseSnapshot, SnapshotError, snapshot_response};
55pub use websocket::{WebSocketHandler, WebSocketState, websocket_handler};
56
57pub use spikard_core::problem::CONTENT_TYPE_PROBLEM_JSON;
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct JwtConfig {
63 pub secret: String,
65 #[serde(default = "default_jwt_algorithm")]
67 pub algorithm: String,
68 pub audience: Option<Vec<String>>,
70 pub issuer: Option<String>,
72 #[serde(default)]
74 pub leeway: u64,
75}
76
77fn default_jwt_algorithm() -> String {
78 "HS256".to_string()
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct ApiKeyConfig {
84 pub keys: Vec<String>,
86 #[serde(default = "default_api_key_header")]
88 pub header_name: String,
89}
90
91fn default_api_key_header() -> String {
92 "X-API-Key".to_string()
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct StaticFilesConfig {
98 pub directory: String,
100 pub route_prefix: String,
102 #[serde(default = "default_true")]
104 pub index_file: bool,
105 pub cache_control: Option<String>,
107}
108
109#[derive(Debug, Clone)]
111pub struct ServerConfig {
112 pub host: String,
114 pub port: u16,
116 pub workers: usize,
118
119 pub enable_request_id: bool,
121 pub max_body_size: Option<usize>,
123 pub request_timeout: Option<u64>,
125 pub compression: Option<CompressionConfig>,
127 pub rate_limit: Option<RateLimitConfig>,
129 pub jwt_auth: Option<JwtConfig>,
131 pub api_key_auth: Option<ApiKeyConfig>,
133 pub static_files: Vec<StaticFilesConfig>,
135 pub graceful_shutdown: bool,
137 pub shutdown_timeout: u64,
139 pub openapi: Option<crate::openapi::OpenApiConfig>,
141 pub jsonrpc: Option<crate::jsonrpc::JsonRpcConfig>,
143 pub lifecycle_hooks: Option<std::sync::Arc<LifecycleHooks>>,
145 pub background_tasks: BackgroundTaskConfig,
147 #[cfg(feature = "di")]
149 pub di_container: Option<std::sync::Arc<spikard_core::di::DependencyContainer>>,
150}
151
152impl Default for ServerConfig {
153 fn default() -> Self {
154 Self {
155 host: "127.0.0.1".to_string(),
156 port: 8000,
157 workers: 1,
158 enable_request_id: true,
159 max_body_size: Some(10 * 1024 * 1024),
160 request_timeout: Some(30),
161 compression: Some(CompressionConfig::default()),
162 rate_limit: None,
163 jwt_auth: None,
164 api_key_auth: None,
165 static_files: Vec::new(),
166 graceful_shutdown: true,
167 shutdown_timeout: 30,
168 openapi: None,
169 jsonrpc: None,
170 lifecycle_hooks: None,
171 background_tasks: BackgroundTaskConfig::default(),
172 #[cfg(feature = "di")]
173 di_container: None,
174 }
175 }
176}
177
178impl ServerConfig {
179 pub fn builder() -> ServerConfigBuilder {
192 ServerConfigBuilder::default()
193 }
194}
195
196#[derive(Debug, Clone, Default)]
232pub struct ServerConfigBuilder {
233 config: ServerConfig,
234}
235
236impl ServerConfigBuilder {
237 pub fn host(mut self, host: impl Into<String>) -> Self {
239 self.config.host = host.into();
240 self
241 }
242
243 pub fn port(mut self, port: u16) -> Self {
245 self.config.port = port;
246 self
247 }
248
249 pub fn workers(mut self, workers: usize) -> Self {
251 self.config.workers = workers;
252 self
253 }
254
255 pub fn enable_request_id(mut self, enable: bool) -> Self {
257 self.config.enable_request_id = enable;
258 self
259 }
260
261 pub fn max_body_size(mut self, size: Option<usize>) -> Self {
263 self.config.max_body_size = size;
264 self
265 }
266
267 pub fn request_timeout(mut self, timeout: Option<u64>) -> Self {
269 self.config.request_timeout = timeout;
270 self
271 }
272
273 pub fn compression(mut self, compression: Option<CompressionConfig>) -> Self {
275 self.config.compression = compression;
276 self
277 }
278
279 pub fn rate_limit(mut self, rate_limit: Option<RateLimitConfig>) -> Self {
281 self.config.rate_limit = rate_limit;
282 self
283 }
284
285 pub fn jwt_auth(mut self, jwt_auth: Option<JwtConfig>) -> Self {
287 self.config.jwt_auth = jwt_auth;
288 self
289 }
290
291 pub fn api_key_auth(mut self, api_key_auth: Option<ApiKeyConfig>) -> Self {
293 self.config.api_key_auth = api_key_auth;
294 self
295 }
296
297 pub fn static_files(mut self, static_files: Vec<StaticFilesConfig>) -> Self {
299 self.config.static_files = static_files;
300 self
301 }
302
303 pub fn add_static_files(mut self, static_file: StaticFilesConfig) -> Self {
305 self.config.static_files.push(static_file);
306 self
307 }
308
309 pub fn graceful_shutdown(mut self, enable: bool) -> Self {
311 self.config.graceful_shutdown = enable;
312 self
313 }
314
315 pub fn shutdown_timeout(mut self, timeout: u64) -> Self {
317 self.config.shutdown_timeout = timeout;
318 self
319 }
320
321 pub fn openapi(mut self, openapi: Option<crate::openapi::OpenApiConfig>) -> Self {
323 self.config.openapi = openapi;
324 self
325 }
326
327 pub fn jsonrpc(mut self, jsonrpc: Option<crate::jsonrpc::JsonRpcConfig>) -> Self {
329 self.config.jsonrpc = jsonrpc;
330 self
331 }
332
333 pub fn lifecycle_hooks(mut self, hooks: Option<std::sync::Arc<LifecycleHooks>>) -> Self {
335 self.config.lifecycle_hooks = hooks;
336 self
337 }
338
339 pub fn background_tasks(mut self, config: BackgroundTaskConfig) -> Self {
341 self.config.background_tasks = config;
342 self
343 }
344
345 #[cfg(feature = "di")]
365 pub fn provide_value<T: Clone + Send + Sync + 'static>(mut self, key: impl Into<String>, value: T) -> Self {
366 use spikard_core::di::{DependencyContainer, ValueDependency};
367 use std::sync::Arc;
368
369 let key_str = key.into();
370
371 let container = if let Some(container) = self.config.di_container.take() {
372 Arc::try_unwrap(container).unwrap_or_else(|_arc| DependencyContainer::new())
373 } else {
374 DependencyContainer::new()
375 };
376
377 let mut container = container;
378
379 let dep = ValueDependency::new(key_str.clone(), value);
380
381 container
382 .register(key_str, Arc::new(dep))
383 .expect("Failed to register dependency");
384
385 self.config.di_container = Some(Arc::new(container));
386 self
387 }
388
389 #[cfg(feature = "di")]
424 pub fn provide_factory<F, Fut, T>(mut self, key: impl Into<String>, factory: F) -> Self
425 where
426 F: Fn(&spikard_core::di::ResolvedDependencies) -> Fut + Send + Sync + Clone + 'static,
427 Fut: std::future::Future<Output = Result<T, String>> + Send + 'static,
428 T: Send + Sync + 'static,
429 {
430 use futures::future::BoxFuture;
431 use spikard_core::di::{DependencyContainer, DependencyError, FactoryDependency};
432 use std::sync::Arc;
433
434 let key_str = key.into();
435
436 let container = if let Some(container) = self.config.di_container.take() {
437 Arc::try_unwrap(container).unwrap_or_else(|_| DependencyContainer::new())
438 } else {
439 DependencyContainer::new()
440 };
441
442 let mut container = container;
443
444 let factory_clone = factory.clone();
445
446 let dep = FactoryDependency::builder(key_str.clone())
447 .factory(
448 move |_req: &axum::http::Request<()>,
449 _data: &spikard_core::RequestData,
450 resolved: &spikard_core::di::ResolvedDependencies| {
451 let factory = factory_clone.clone();
452 let factory_result = factory(resolved);
453 Box::pin(async move {
454 let result = factory_result
455 .await
456 .map_err(|e| DependencyError::ResolutionFailed { message: e })?;
457 Ok(Arc::new(result) as Arc<dyn std::any::Any + Send + Sync>)
458 })
459 as BoxFuture<'static, Result<Arc<dyn std::any::Any + Send + Sync>, DependencyError>>
460 },
461 )
462 .build();
463
464 container
465 .register(key_str, Arc::new(dep))
466 .expect("Failed to register dependency");
467
468 self.config.di_container = Some(Arc::new(container));
469 self
470 }
471
472 #[cfg(feature = "di")]
495 pub fn provide(mut self, dependency: std::sync::Arc<dyn spikard_core::di::Dependency>) -> Self {
496 use spikard_core::di::DependencyContainer;
497 use std::sync::Arc;
498
499 let key = dependency.key().to_string();
500
501 let container = if let Some(container) = self.config.di_container.take() {
502 Arc::try_unwrap(container).unwrap_or_else(|_| DependencyContainer::new())
503 } else {
504 DependencyContainer::new()
505 };
506
507 let mut container = container;
508
509 container
510 .register(key, dependency)
511 .expect("Failed to register dependency");
512
513 self.config.di_container = Some(Arc::new(container));
514 self
515 }
516
517 pub fn build(self) -> ServerConfig {
519 self.config
520 }
521}
522
523const fn default_true() -> bool {
524 true
525}