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};
31use tokio::runtime::Runtime;
32
33#[cfg(test)]
34mod handler_trait_tests;
35
36pub use auth::{Claims, api_key_auth_middleware, jwt_auth_middleware};
37pub use background::{
38 BackgroundHandle, BackgroundJobError, BackgroundJobMetadata, BackgroundRuntime, BackgroundSpawnError,
39 BackgroundTaskConfig,
40};
41pub use body_metadata::ResponseBodySize;
42#[cfg(feature = "di")]
43pub use di_handler::DependencyInjectingHandler;
44pub use grpc::{
45 GrpcConfig, GrpcHandler, GrpcHandlerResult, GrpcRegistry, GrpcRequestData, GrpcResponseData, MessageStream,
46 StreamingRequest, StreamingResponse,
47};
48pub use handler_response::HandlerResponse;
49pub use handler_trait::{Handler, HandlerResult, RequestData, StaticResponse, StaticResponseHandler, ValidatedParams};
50pub use jsonrpc::JsonRpcConfig;
51pub use lifecycle::{HookResult, LifecycleHook, LifecycleHooks, LifecycleHooksBuilder, request_hook, response_hook};
52pub use openapi::{ContactInfo, LicenseInfo, OpenApiConfig, SecuritySchemeInfo, ServerInfo};
53pub use response::Response;
54pub use server::Server;
55pub use spikard_core::{
56 CompressionConfig, CorsConfig, Method, ParameterValidator, ProblemDetails, RateLimitConfig, Route, RouteHandler,
57 RouteMetadata, Router, SchemaRegistry, SchemaValidator,
58};
59pub use sse::{SseEvent, SseEventProducer, SseState, sse_handler};
60pub use testing::{ResponseSnapshot, SnapshotError, snapshot_response};
61pub use websocket::{WebSocketHandler, WebSocketState, websocket_handler};
62
63pub use spikard_core::problem::CONTENT_TYPE_PROBLEM_JSON;
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct JwtConfig {
69 pub secret: String,
71 #[serde(default = "default_jwt_algorithm")]
73 pub algorithm: String,
74 pub audience: Option<Vec<String>>,
76 pub issuer: Option<String>,
78 #[serde(default)]
80 pub leeway: u64,
81}
82
83fn default_jwt_algorithm() -> String {
84 "HS256".to_string()
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct ApiKeyConfig {
90 pub keys: Vec<String>,
92 #[serde(default = "default_api_key_header")]
94 pub header_name: String,
95}
96
97fn default_api_key_header() -> String {
98 "X-API-Key".to_string()
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct StaticFilesConfig {
104 pub directory: String,
106 pub route_prefix: String,
108 #[serde(default = "default_true")]
110 pub index_file: bool,
111 pub cache_control: Option<String>,
113}
114
115#[derive(Debug, Clone)]
117pub struct ServerConfig {
118 pub host: String,
120 pub port: u16,
122 pub workers: usize,
124
125 pub enable_request_id: bool,
127 pub max_body_size: Option<usize>,
129 pub request_timeout: Option<u64>,
131 pub compression: Option<CompressionConfig>,
133 pub rate_limit: Option<RateLimitConfig>,
135 pub jwt_auth: Option<JwtConfig>,
137 pub api_key_auth: Option<ApiKeyConfig>,
139 pub static_files: Vec<StaticFilesConfig>,
141 pub graceful_shutdown: bool,
143 pub shutdown_timeout: u64,
145 pub openapi: Option<crate::openapi::OpenApiConfig>,
147 pub jsonrpc: Option<crate::jsonrpc::JsonRpcConfig>,
149 pub grpc: Option<crate::grpc::GrpcConfig>,
151 pub lifecycle_hooks: Option<std::sync::Arc<LifecycleHooks>>,
153 pub background_tasks: BackgroundTaskConfig,
155 pub enable_http_trace: bool,
157 #[cfg(feature = "di")]
159 pub di_container: Option<std::sync::Arc<spikard_core::di::DependencyContainer>>,
160}
161
162impl Default for ServerConfig {
163 fn default() -> Self {
164 Self {
165 host: "127.0.0.1".to_string(),
166 port: 8000,
167 workers: 1,
168 enable_request_id: false,
169 max_body_size: Some(10 * 1024 * 1024),
170 request_timeout: None,
171 compression: None,
172 rate_limit: None,
173 jwt_auth: None,
174 api_key_auth: None,
175 static_files: Vec::new(),
176 graceful_shutdown: true,
177 shutdown_timeout: 30,
178 openapi: None,
179 jsonrpc: None,
180 grpc: None,
181 lifecycle_hooks: None,
182 background_tasks: BackgroundTaskConfig::default(),
183 enable_http_trace: false,
184 #[cfg(feature = "di")]
185 di_container: None,
186 }
187 }
188}
189
190impl ServerConfig {
191 pub fn builder() -> ServerConfigBuilder {
204 ServerConfigBuilder::default()
205 }
206}
207
208#[derive(Debug, Clone, Default)]
244pub struct ServerConfigBuilder {
245 config: ServerConfig,
246}
247
248impl ServerConfigBuilder {
249 pub fn host(mut self, host: impl Into<String>) -> Self {
251 self.config.host = host.into();
252 self
253 }
254
255 pub fn port(mut self, port: u16) -> Self {
257 self.config.port = port;
258 self
259 }
260
261 pub fn workers(mut self, workers: usize) -> Self {
263 self.config.workers = workers;
264 self
265 }
266
267 pub fn enable_request_id(mut self, enable: bool) -> Self {
269 self.config.enable_request_id = enable;
270 self
271 }
272
273 pub fn enable_http_trace(mut self, enable: bool) -> Self {
275 self.config.enable_http_trace = enable;
276 self
277 }
278
279 pub fn max_body_size(mut self, size: Option<usize>) -> Self {
281 self.config.max_body_size = size;
282 self
283 }
284
285 pub fn request_timeout(mut self, timeout: Option<u64>) -> Self {
287 self.config.request_timeout = timeout;
288 self
289 }
290
291 pub fn compression(mut self, compression: Option<CompressionConfig>) -> Self {
293 self.config.compression = compression;
294 self
295 }
296
297 pub fn rate_limit(mut self, rate_limit: Option<RateLimitConfig>) -> Self {
299 self.config.rate_limit = rate_limit;
300 self
301 }
302
303 pub fn jwt_auth(mut self, jwt_auth: Option<JwtConfig>) -> Self {
305 self.config.jwt_auth = jwt_auth;
306 self
307 }
308
309 pub fn api_key_auth(mut self, api_key_auth: Option<ApiKeyConfig>) -> Self {
311 self.config.api_key_auth = api_key_auth;
312 self
313 }
314
315 pub fn static_files(mut self, static_files: Vec<StaticFilesConfig>) -> Self {
317 self.config.static_files = static_files;
318 self
319 }
320
321 pub fn add_static_files(mut self, static_file: StaticFilesConfig) -> Self {
323 self.config.static_files.push(static_file);
324 self
325 }
326
327 pub fn graceful_shutdown(mut self, enable: bool) -> Self {
329 self.config.graceful_shutdown = enable;
330 self
331 }
332
333 pub fn shutdown_timeout(mut self, timeout: u64) -> Self {
335 self.config.shutdown_timeout = timeout;
336 self
337 }
338
339 pub fn openapi(mut self, openapi: Option<crate::openapi::OpenApiConfig>) -> Self {
341 self.config.openapi = openapi;
342 self
343 }
344
345 pub fn jsonrpc(mut self, jsonrpc: Option<crate::jsonrpc::JsonRpcConfig>) -> Self {
347 self.config.jsonrpc = jsonrpc;
348 self
349 }
350
351 pub fn grpc(mut self, grpc: Option<crate::grpc::GrpcConfig>) -> Self {
353 self.config.grpc = grpc;
354 self
355 }
356
357 pub fn lifecycle_hooks(mut self, hooks: Option<std::sync::Arc<LifecycleHooks>>) -> Self {
359 self.config.lifecycle_hooks = hooks;
360 self
361 }
362
363 pub fn background_tasks(mut self, config: BackgroundTaskConfig) -> Self {
365 self.config.background_tasks = config;
366 self
367 }
368
369 #[cfg(feature = "di")]
389 pub fn provide_value<T: Clone + Send + Sync + 'static>(mut self, key: impl Into<String>, value: T) -> Self {
390 use spikard_core::di::{DependencyContainer, ValueDependency};
391 use std::sync::Arc;
392
393 let key_str = key.into();
394
395 let container = if let Some(container) = self.config.di_container.take() {
396 Arc::try_unwrap(container).unwrap_or_else(|_arc| DependencyContainer::new())
397 } else {
398 DependencyContainer::new()
399 };
400
401 let mut container = container;
402
403 let dep = ValueDependency::new(key_str.clone(), value);
404
405 container
406 .register(key_str, Arc::new(dep))
407 .expect("Failed to register dependency");
408
409 self.config.di_container = Some(Arc::new(container));
410 self
411 }
412
413 #[cfg(feature = "di")]
448 pub fn provide_factory<F, Fut, T>(mut self, key: impl Into<String>, factory: F) -> Self
449 where
450 F: Fn(&spikard_core::di::ResolvedDependencies) -> Fut + Send + Sync + Clone + 'static,
451 Fut: std::future::Future<Output = Result<T, String>> + Send + 'static,
452 T: Send + Sync + 'static,
453 {
454 use futures::future::BoxFuture;
455 use spikard_core::di::{DependencyContainer, DependencyError, FactoryDependency};
456 use std::sync::Arc;
457
458 let key_str = key.into();
459
460 let container = if let Some(container) = self.config.di_container.take() {
461 Arc::try_unwrap(container).unwrap_or_else(|_| DependencyContainer::new())
462 } else {
463 DependencyContainer::new()
464 };
465
466 let mut container = container;
467
468 let factory_clone = factory.clone();
469
470 let dep = FactoryDependency::builder(key_str.clone())
471 .factory(
472 move |_req: &axum::http::Request<()>,
473 _data: &spikard_core::RequestData,
474 resolved: &spikard_core::di::ResolvedDependencies| {
475 let factory = factory_clone.clone();
476 let factory_result = factory(resolved);
477 Box::pin(async move {
478 let result = factory_result
479 .await
480 .map_err(|e| DependencyError::ResolutionFailed { message: e })?;
481 Ok(Arc::new(result) as Arc<dyn std::any::Any + Send + Sync>)
482 })
483 as BoxFuture<'static, Result<Arc<dyn std::any::Any + Send + Sync>, DependencyError>>
484 },
485 )
486 .build()
487 .expect("Factory dependency must have a configured factory function");
488
489 container
490 .register(key_str, Arc::new(dep))
491 .expect("Failed to register dependency");
492
493 self.config.di_container = Some(Arc::new(container));
494 self
495 }
496
497 #[cfg(feature = "di")]
520 pub fn provide(mut self, dependency: std::sync::Arc<dyn spikard_core::di::Dependency>) -> Self {
521 use spikard_core::di::DependencyContainer;
522 use std::sync::Arc;
523
524 let key = dependency.key().to_string();
525
526 let container = if let Some(container) = self.config.di_container.take() {
527 Arc::try_unwrap(container).unwrap_or_else(|_| DependencyContainer::new())
528 } else {
529 DependencyContainer::new()
530 };
531
532 let mut container = container;
533
534 container
535 .register(key, dependency)
536 .expect("Failed to register dependency");
537
538 self.config.di_container = Some(Arc::new(container));
539 self
540 }
541
542 pub fn build(self) -> ServerConfig {
544 self.config
545 }
546}
547
548pub fn build_server_runtime(config: &ServerConfig) -> std::io::Result<Runtime> {
553 let mut builder = if config.workers <= 1 {
554 tokio::runtime::Builder::new_current_thread()
555 } else {
556 let mut builder = tokio::runtime::Builder::new_multi_thread();
557 builder.worker_threads(config.workers);
558 builder
559 };
560
561 builder.enable_all().build()
562}
563
564const fn default_true() -> bool {
565 true
566}