spikard 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +659 -659
- data/ext/spikard_rb/Cargo.toml +17 -17
- data/ext/spikard_rb/extconf.rb +10 -10
- data/ext/spikard_rb/src/lib.rs +6 -6
- data/lib/spikard/app.rb +386 -386
- data/lib/spikard/background.rb +27 -27
- data/lib/spikard/config.rb +396 -396
- data/lib/spikard/converters.rb +13 -13
- data/lib/spikard/handler_wrapper.rb +113 -113
- data/lib/spikard/provide.rb +214 -214
- data/lib/spikard/response.rb +173 -173
- data/lib/spikard/schema.rb +243 -243
- data/lib/spikard/sse.rb +111 -111
- data/lib/spikard/streaming_response.rb +44 -44
- data/lib/spikard/testing.rb +221 -221
- data/lib/spikard/upload_file.rb +131 -131
- data/lib/spikard/version.rb +5 -5
- data/lib/spikard/websocket.rb +59 -59
- data/lib/spikard.rb +43 -43
- data/sig/spikard.rbs +360 -360
- data/vendor/crates/spikard-core/Cargo.toml +40 -40
- data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +133 -133
- data/vendor/crates/spikard-core/src/debug.rs +63 -63
- data/vendor/crates/spikard-core/src/di/container.rs +726 -726
- data/vendor/crates/spikard-core/src/di/dependency.rs +273 -273
- data/vendor/crates/spikard-core/src/di/error.rs +118 -118
- data/vendor/crates/spikard-core/src/di/factory.rs +538 -538
- data/vendor/crates/spikard-core/src/di/graph.rs +545 -545
- data/vendor/crates/spikard-core/src/di/mod.rs +192 -192
- data/vendor/crates/spikard-core/src/di/resolved.rs +411 -411
- data/vendor/crates/spikard-core/src/di/value.rs +283 -283
- data/vendor/crates/spikard-core/src/errors.rs +39 -39
- data/vendor/crates/spikard-core/src/http.rs +153 -153
- data/vendor/crates/spikard-core/src/lib.rs +29 -29
- data/vendor/crates/spikard-core/src/lifecycle.rs +422 -422
- data/vendor/crates/spikard-core/src/parameters.rs +722 -722
- data/vendor/crates/spikard-core/src/problem.rs +310 -310
- data/vendor/crates/spikard-core/src/request_data.rs +189 -189
- data/vendor/crates/spikard-core/src/router.rs +249 -249
- data/vendor/crates/spikard-core/src/schema_registry.rs +183 -183
- data/vendor/crates/spikard-core/src/type_hints.rs +304 -304
- data/vendor/crates/spikard-core/src/validation.rs +699 -699
- data/vendor/crates/spikard-http/Cargo.toml +58 -58
- data/vendor/crates/spikard-http/src/auth.rs +247 -247
- data/vendor/crates/spikard-http/src/background.rs +249 -249
- data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -3
- data/vendor/crates/spikard-http/src/bindings/response.rs +1 -1
- data/vendor/crates/spikard-http/src/body_metadata.rs +8 -8
- data/vendor/crates/spikard-http/src/cors.rs +490 -490
- data/vendor/crates/spikard-http/src/debug.rs +63 -63
- data/vendor/crates/spikard-http/src/di_handler.rs +423 -423
- data/vendor/crates/spikard-http/src/handler_response.rs +190 -190
- data/vendor/crates/spikard-http/src/handler_trait.rs +228 -228
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +284 -284
- data/vendor/crates/spikard-http/src/lib.rs +529 -529
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +149 -149
- data/vendor/crates/spikard-http/src/lifecycle.rs +428 -428
- data/vendor/crates/spikard-http/src/middleware/mod.rs +285 -285
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +86 -86
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +147 -147
- data/vendor/crates/spikard-http/src/middleware/validation.rs +287 -287
- data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -309
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +190 -190
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +308 -308
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +195 -195
- data/vendor/crates/spikard-http/src/parameters.rs +1 -1
- data/vendor/crates/spikard-http/src/problem.rs +1 -1
- data/vendor/crates/spikard-http/src/query_parser.rs +369 -369
- data/vendor/crates/spikard-http/src/response.rs +399 -399
- data/vendor/crates/spikard-http/src/router.rs +1 -1
- data/vendor/crates/spikard-http/src/schema_registry.rs +1 -1
- data/vendor/crates/spikard-http/src/server/handler.rs +87 -87
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +98 -98
- data/vendor/crates/spikard-http/src/server/mod.rs +805 -805
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +119 -119
- data/vendor/crates/spikard-http/src/sse.rs +447 -447
- data/vendor/crates/spikard-http/src/testing/form.rs +14 -14
- data/vendor/crates/spikard-http/src/testing/multipart.rs +60 -60
- data/vendor/crates/spikard-http/src/testing/test_client.rs +285 -285
- data/vendor/crates/spikard-http/src/testing.rs +377 -377
- data/vendor/crates/spikard-http/src/type_hints.rs +1 -1
- data/vendor/crates/spikard-http/src/validation.rs +1 -1
- data/vendor/crates/spikard-http/src/websocket.rs +324 -324
- data/vendor/crates/spikard-rb/Cargo.toml +42 -42
- data/vendor/crates/spikard-rb/build.rs +8 -8
- data/vendor/crates/spikard-rb/src/background.rs +63 -63
- data/vendor/crates/spikard-rb/src/config.rs +294 -294
- data/vendor/crates/spikard-rb/src/conversion.rs +453 -453
- data/vendor/crates/spikard-rb/src/di.rs +409 -409
- data/vendor/crates/spikard-rb/src/handler.rs +625 -625
- data/vendor/crates/spikard-rb/src/lib.rs +2771 -2771
- data/vendor/crates/spikard-rb/src/lifecycle.rs +274 -274
- data/vendor/crates/spikard-rb/src/server.rs +283 -283
- data/vendor/crates/spikard-rb/src/sse.rs +231 -231
- data/vendor/crates/spikard-rb/src/test_client.rs +404 -404
- data/vendor/crates/spikard-rb/src/test_sse.rs +143 -143
- data/vendor/crates/spikard-rb/src/test_websocket.rs +221 -221
- data/vendor/crates/spikard-rb/src/websocket.rs +233 -233
- data/vendor/spikard-core/Cargo.toml +40 -40
- data/vendor/spikard-core/src/bindings/mod.rs +3 -3
- data/vendor/spikard-core/src/bindings/response.rs +133 -133
- data/vendor/spikard-core/src/debug.rs +63 -63
- data/vendor/spikard-core/src/di/container.rs +726 -726
- data/vendor/spikard-core/src/di/dependency.rs +273 -273
- data/vendor/spikard-core/src/di/error.rs +118 -118
- data/vendor/spikard-core/src/di/factory.rs +538 -538
- data/vendor/spikard-core/src/di/graph.rs +545 -545
- data/vendor/spikard-core/src/di/mod.rs +192 -192
- data/vendor/spikard-core/src/di/resolved.rs +411 -411
- data/vendor/spikard-core/src/di/value.rs +283 -283
- data/vendor/spikard-core/src/http.rs +153 -153
- data/vendor/spikard-core/src/lib.rs +28 -28
- data/vendor/spikard-core/src/lifecycle.rs +422 -422
- data/vendor/spikard-core/src/parameters.rs +719 -719
- data/vendor/spikard-core/src/problem.rs +310 -310
- data/vendor/spikard-core/src/request_data.rs +189 -189
- data/vendor/spikard-core/src/router.rs +249 -249
- data/vendor/spikard-core/src/schema_registry.rs +183 -183
- data/vendor/spikard-core/src/type_hints.rs +304 -304
- data/vendor/spikard-core/src/validation.rs +699 -699
- data/vendor/spikard-http/Cargo.toml +58 -58
- data/vendor/spikard-http/src/auth.rs +247 -247
- data/vendor/spikard-http/src/background.rs +249 -249
- data/vendor/spikard-http/src/bindings/mod.rs +3 -3
- data/vendor/spikard-http/src/bindings/response.rs +1 -1
- data/vendor/spikard-http/src/body_metadata.rs +8 -8
- data/vendor/spikard-http/src/cors.rs +490 -490
- data/vendor/spikard-http/src/debug.rs +63 -63
- data/vendor/spikard-http/src/di_handler.rs +423 -423
- data/vendor/spikard-http/src/handler_response.rs +190 -190
- data/vendor/spikard-http/src/handler_trait.rs +228 -228
- data/vendor/spikard-http/src/handler_trait_tests.rs +284 -284
- data/vendor/spikard-http/src/lib.rs +529 -529
- data/vendor/spikard-http/src/lifecycle/adapter.rs +149 -149
- data/vendor/spikard-http/src/lifecycle.rs +428 -428
- data/vendor/spikard-http/src/middleware/mod.rs +285 -285
- data/vendor/spikard-http/src/middleware/multipart.rs +86 -86
- data/vendor/spikard-http/src/middleware/urlencoded.rs +147 -147
- data/vendor/spikard-http/src/middleware/validation.rs +287 -287
- data/vendor/spikard-http/src/openapi/mod.rs +309 -309
- data/vendor/spikard-http/src/openapi/parameter_extraction.rs +190 -190
- data/vendor/spikard-http/src/openapi/schema_conversion.rs +308 -308
- data/vendor/spikard-http/src/openapi/spec_generation.rs +195 -195
- data/vendor/spikard-http/src/parameters.rs +1 -1
- data/vendor/spikard-http/src/problem.rs +1 -1
- data/vendor/spikard-http/src/query_parser.rs +369 -369
- data/vendor/spikard-http/src/response.rs +399 -399
- data/vendor/spikard-http/src/router.rs +1 -1
- data/vendor/spikard-http/src/schema_registry.rs +1 -1
- data/vendor/spikard-http/src/server/handler.rs +80 -80
- data/vendor/spikard-http/src/server/lifecycle_execution.rs +98 -98
- data/vendor/spikard-http/src/server/mod.rs +805 -805
- data/vendor/spikard-http/src/server/request_extraction.rs +119 -119
- data/vendor/spikard-http/src/sse.rs +447 -447
- data/vendor/spikard-http/src/testing/form.rs +14 -14
- data/vendor/spikard-http/src/testing/multipart.rs +60 -60
- data/vendor/spikard-http/src/testing/test_client.rs +285 -285
- data/vendor/spikard-http/src/testing.rs +377 -377
- data/vendor/spikard-http/src/type_hints.rs +1 -1
- data/vendor/spikard-http/src/validation.rs +1 -1
- data/vendor/spikard-http/src/websocket.rs +324 -324
- data/vendor/spikard-rb/Cargo.toml +42 -42
- data/vendor/spikard-rb/build.rs +8 -8
- data/vendor/spikard-rb/src/background.rs +63 -63
- data/vendor/spikard-rb/src/config.rs +294 -294
- data/vendor/spikard-rb/src/conversion.rs +392 -392
- data/vendor/spikard-rb/src/di.rs +409 -409
- data/vendor/spikard-rb/src/handler.rs +534 -534
- data/vendor/spikard-rb/src/lib.rs +2020 -2020
- data/vendor/spikard-rb/src/lifecycle.rs +267 -267
- data/vendor/spikard-rb/src/server.rs +283 -283
- data/vendor/spikard-rb/src/sse.rs +231 -231
- data/vendor/spikard-rb/src/test_client.rs +404 -404
- data/vendor/spikard-rb/src/test_sse.rs +143 -143
- data/vendor/spikard-rb/src/test_websocket.rs +221 -221
- data/vendor/spikard-rb/src/websocket.rs +233 -233
- metadata +1 -1
|
@@ -1,529 +1,529 @@
|
|
|
1
|
-
//! Spikard HTTP Server
|
|
2
|
-
//!
|
|
3
|
-
//! Pure Rust HTTP server with language-agnostic handler trait.
|
|
4
|
-
//! Language bindings (Python, Node, WASM) implement the Handler trait.
|
|
5
|
-
|
|
6
|
-
pub mod auth;
|
|
7
|
-
pub mod background;
|
|
8
|
-
pub mod bindings;
|
|
9
|
-
pub mod body_metadata;
|
|
10
|
-
pub mod cors;
|
|
11
|
-
pub mod debug;
|
|
12
|
-
#[cfg(feature = "di")]
|
|
13
|
-
pub mod di_handler;
|
|
14
|
-
pub mod handler_response;
|
|
15
|
-
pub mod handler_trait;
|
|
16
|
-
pub mod lifecycle;
|
|
17
|
-
pub mod middleware;
|
|
18
|
-
pub mod openapi;
|
|
19
|
-
pub mod parameters;
|
|
20
|
-
pub mod problem;
|
|
21
|
-
pub mod query_parser;
|
|
22
|
-
pub mod response;
|
|
23
|
-
pub mod router;
|
|
24
|
-
pub mod schema_registry;
|
|
25
|
-
pub mod server;
|
|
26
|
-
pub mod sse;
|
|
27
|
-
pub mod testing;
|
|
28
|
-
pub mod type_hints;
|
|
29
|
-
pub mod validation;
|
|
30
|
-
pub mod websocket;
|
|
31
|
-
|
|
32
|
-
use serde::{Deserialize, Serialize};
|
|
33
|
-
|
|
34
|
-
#[cfg(test)]
|
|
35
|
-
mod handler_trait_tests;
|
|
36
|
-
|
|
37
|
-
pub use auth::{Claims, api_key_auth_middleware, jwt_auth_middleware};
|
|
38
|
-
pub use background::{
|
|
39
|
-
BackgroundHandle, BackgroundJobError, BackgroundJobMetadata, BackgroundRuntime, BackgroundSpawnError,
|
|
40
|
-
BackgroundTaskConfig,
|
|
41
|
-
};
|
|
42
|
-
pub use body_metadata::ResponseBodySize;
|
|
43
|
-
#[cfg(feature = "di")]
|
|
44
|
-
pub use di_handler::DependencyInjectingHandler;
|
|
45
|
-
pub use handler_response::HandlerResponse;
|
|
46
|
-
pub use handler_trait::{Handler, HandlerResult, RequestData, ValidatedParams};
|
|
47
|
-
pub use lifecycle::{HookResult, LifecycleHook, LifecycleHooks, LifecycleHooksBuilder, request_hook, response_hook};
|
|
48
|
-
pub use openapi::{ContactInfo, LicenseInfo, OpenApiConfig, SecuritySchemeInfo, ServerInfo};
|
|
49
|
-
pub use parameters::ParameterValidator;
|
|
50
|
-
pub use problem::{CONTENT_TYPE_PROBLEM_JSON, ProblemDetails};
|
|
51
|
-
pub use response::Response;
|
|
52
|
-
pub use router::{Route, RouteHandler, Router};
|
|
53
|
-
pub use schema_registry::SchemaRegistry;
|
|
54
|
-
pub use server::Server;
|
|
55
|
-
pub use spikard_core::{CompressionConfig, CorsConfig, Method, RateLimitConfig, RouteMetadata};
|
|
56
|
-
pub use sse::{SseEvent, SseEventProducer, SseState, sse_handler};
|
|
57
|
-
pub use testing::{ResponseSnapshot, SnapshotError, snapshot_response};
|
|
58
|
-
pub use validation::SchemaValidator;
|
|
59
|
-
pub use websocket::{WebSocketHandler, WebSocketState, websocket_handler};
|
|
60
|
-
|
|
61
|
-
/// JWT authentication configuration
|
|
62
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
63
|
-
pub struct JwtConfig {
|
|
64
|
-
/// Secret key for JWT verification
|
|
65
|
-
pub secret: String,
|
|
66
|
-
/// Required algorithm (HS256, HS384, HS512, RS256, etc.)
|
|
67
|
-
#[serde(default = "default_jwt_algorithm")]
|
|
68
|
-
pub algorithm: String,
|
|
69
|
-
/// Required audience claim
|
|
70
|
-
pub audience: Option<Vec<String>>,
|
|
71
|
-
/// Required issuer claim
|
|
72
|
-
pub issuer: Option<String>,
|
|
73
|
-
/// Leeway for expiration checks (seconds)
|
|
74
|
-
#[serde(default)]
|
|
75
|
-
pub leeway: u64,
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
fn default_jwt_algorithm() -> String {
|
|
79
|
-
"HS256".to_string()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/// API Key authentication configuration
|
|
83
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
84
|
-
pub struct ApiKeyConfig {
|
|
85
|
-
/// Valid API keys
|
|
86
|
-
pub keys: Vec<String>,
|
|
87
|
-
/// Header name to check (e.g., "X-API-Key")
|
|
88
|
-
#[serde(default = "default_api_key_header")]
|
|
89
|
-
pub header_name: String,
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
fn default_api_key_header() -> String {
|
|
93
|
-
"X-API-Key".to_string()
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/// Static file serving configuration
|
|
97
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
98
|
-
pub struct StaticFilesConfig {
|
|
99
|
-
/// Directory path to serve
|
|
100
|
-
pub directory: String,
|
|
101
|
-
/// URL path prefix (e.g., "/static")
|
|
102
|
-
pub route_prefix: String,
|
|
103
|
-
/// Fallback to index.html for directories
|
|
104
|
-
#[serde(default = "default_true")]
|
|
105
|
-
pub index_file: bool,
|
|
106
|
-
/// Cache-Control header value
|
|
107
|
-
pub cache_control: Option<String>,
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/// Server configuration
|
|
111
|
-
#[derive(Debug, Clone)]
|
|
112
|
-
pub struct ServerConfig {
|
|
113
|
-
/// Host to bind to
|
|
114
|
-
pub host: String,
|
|
115
|
-
/// Port to bind to
|
|
116
|
-
pub port: u16,
|
|
117
|
-
/// Number of worker threads (unused with tokio)
|
|
118
|
-
pub workers: usize,
|
|
119
|
-
|
|
120
|
-
/// Enable request ID generation and propagation
|
|
121
|
-
pub enable_request_id: bool,
|
|
122
|
-
/// Maximum request body size in bytes (None = unlimited, not recommended)
|
|
123
|
-
pub max_body_size: Option<usize>,
|
|
124
|
-
/// Request timeout in seconds (None = no timeout)
|
|
125
|
-
pub request_timeout: Option<u64>,
|
|
126
|
-
/// Enable compression middleware
|
|
127
|
-
pub compression: Option<CompressionConfig>,
|
|
128
|
-
/// Enable rate limiting
|
|
129
|
-
pub rate_limit: Option<RateLimitConfig>,
|
|
130
|
-
/// JWT authentication configuration
|
|
131
|
-
pub jwt_auth: Option<JwtConfig>,
|
|
132
|
-
/// API Key authentication configuration
|
|
133
|
-
pub api_key_auth: Option<ApiKeyConfig>,
|
|
134
|
-
/// Static file serving configuration
|
|
135
|
-
pub static_files: Vec<StaticFilesConfig>,
|
|
136
|
-
/// Enable graceful shutdown on SIGTERM/SIGINT
|
|
137
|
-
pub graceful_shutdown: bool,
|
|
138
|
-
/// Graceful shutdown timeout (seconds)
|
|
139
|
-
pub shutdown_timeout: u64,
|
|
140
|
-
/// OpenAPI documentation configuration
|
|
141
|
-
pub openapi: Option<crate::openapi::OpenApiConfig>,
|
|
142
|
-
/// Lifecycle hooks for request/response processing
|
|
143
|
-
pub lifecycle_hooks: Option<std::sync::Arc<LifecycleHooks>>,
|
|
144
|
-
/// Background task executor configuration
|
|
145
|
-
pub background_tasks: BackgroundTaskConfig,
|
|
146
|
-
/// Dependency injection container (requires 'di' feature)
|
|
147
|
-
#[cfg(feature = "di")]
|
|
148
|
-
pub di_container: Option<std::sync::Arc<spikard_core::di::DependencyContainer>>,
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
impl Default for ServerConfig {
|
|
152
|
-
fn default() -> Self {
|
|
153
|
-
Self {
|
|
154
|
-
host: "127.0.0.1".to_string(),
|
|
155
|
-
port: 8000,
|
|
156
|
-
workers: 1,
|
|
157
|
-
enable_request_id: true,
|
|
158
|
-
max_body_size: Some(10 * 1024 * 1024),
|
|
159
|
-
request_timeout: Some(30),
|
|
160
|
-
compression: Some(CompressionConfig::default()),
|
|
161
|
-
rate_limit: None,
|
|
162
|
-
jwt_auth: None,
|
|
163
|
-
api_key_auth: None,
|
|
164
|
-
static_files: Vec::new(),
|
|
165
|
-
graceful_shutdown: true,
|
|
166
|
-
shutdown_timeout: 30,
|
|
167
|
-
openapi: None,
|
|
168
|
-
lifecycle_hooks: None,
|
|
169
|
-
background_tasks: BackgroundTaskConfig::default(),
|
|
170
|
-
#[cfg(feature = "di")]
|
|
171
|
-
di_container: None,
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
impl ServerConfig {
|
|
177
|
-
/// Create a new builder for ServerConfig
|
|
178
|
-
///
|
|
179
|
-
/// # Example
|
|
180
|
-
///
|
|
181
|
-
/// ```ignorerust
|
|
182
|
-
/// use spikard_http::ServerConfig;
|
|
183
|
-
///
|
|
184
|
-
/// let config = ServerConfig::builder()
|
|
185
|
-
/// .port(3000)
|
|
186
|
-
/// .host("0.0.0.0")
|
|
187
|
-
/// .build();
|
|
188
|
-
/// ```
|
|
189
|
-
pub fn builder() -> ServerConfigBuilder {
|
|
190
|
-
ServerConfigBuilder::default()
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/// Builder for ServerConfig
|
|
195
|
-
///
|
|
196
|
-
/// Provides a fluent API for configuring a Spikard server with dependency injection support.
|
|
197
|
-
///
|
|
198
|
-
/// # Dependency Injection
|
|
199
|
-
///
|
|
200
|
-
/// The builder provides methods to register dependencies that will be injected into handlers:
|
|
201
|
-
///
|
|
202
|
-
/// ```ignorerust
|
|
203
|
-
/// # #[cfg(feature = "di")]
|
|
204
|
-
/// # {
|
|
205
|
-
/// use spikard_http::ServerConfig;
|
|
206
|
-
/// use std::sync::Arc;
|
|
207
|
-
///
|
|
208
|
-
/// let config = ServerConfig::builder()
|
|
209
|
-
/// .port(3000)
|
|
210
|
-
/// .provide_value("app_name", "MyApp".to_string())
|
|
211
|
-
/// .provide_value("max_connections", 100)
|
|
212
|
-
/// .build();
|
|
213
|
-
/// # }
|
|
214
|
-
/// ```
|
|
215
|
-
///
|
|
216
|
-
/// For factory dependencies that create values on-demand:
|
|
217
|
-
///
|
|
218
|
-
/// ```ignorerust
|
|
219
|
-
/// # #[cfg(feature = "di")]
|
|
220
|
-
/// # {
|
|
221
|
-
/// use spikard_http::ServerConfig;
|
|
222
|
-
///
|
|
223
|
-
/// let config = ServerConfig::builder()
|
|
224
|
-
/// .port(3000)
|
|
225
|
-
/// .provide_value("db_url", "postgresql://localhost/mydb".to_string())
|
|
226
|
-
/// .build();
|
|
227
|
-
/// # }
|
|
228
|
-
/// ```
|
|
229
|
-
#[derive(Debug, Clone, Default)]
|
|
230
|
-
pub struct ServerConfigBuilder {
|
|
231
|
-
config: ServerConfig,
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
impl ServerConfigBuilder {
|
|
235
|
-
/// Set the host address to bind to
|
|
236
|
-
pub fn host(mut self, host: impl Into<String>) -> Self {
|
|
237
|
-
self.config.host = host.into();
|
|
238
|
-
self
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/// Set the port to bind to
|
|
242
|
-
pub fn port(mut self, port: u16) -> Self {
|
|
243
|
-
self.config.port = port;
|
|
244
|
-
self
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/// Set the number of worker threads (unused with tokio, kept for compatibility)
|
|
248
|
-
pub fn workers(mut self, workers: usize) -> Self {
|
|
249
|
-
self.config.workers = workers;
|
|
250
|
-
self
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/// Enable or disable request ID generation and propagation
|
|
254
|
-
pub fn enable_request_id(mut self, enable: bool) -> Self {
|
|
255
|
-
self.config.enable_request_id = enable;
|
|
256
|
-
self
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/// Set maximum request body size in bytes (None = unlimited, not recommended)
|
|
260
|
-
pub fn max_body_size(mut self, size: Option<usize>) -> Self {
|
|
261
|
-
self.config.max_body_size = size;
|
|
262
|
-
self
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/// Set request timeout in seconds (None = no timeout)
|
|
266
|
-
pub fn request_timeout(mut self, timeout: Option<u64>) -> Self {
|
|
267
|
-
self.config.request_timeout = timeout;
|
|
268
|
-
self
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/// Set compression configuration
|
|
272
|
-
pub fn compression(mut self, compression: Option<CompressionConfig>) -> Self {
|
|
273
|
-
self.config.compression = compression;
|
|
274
|
-
self
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/// Set rate limiting configuration
|
|
278
|
-
pub fn rate_limit(mut self, rate_limit: Option<RateLimitConfig>) -> Self {
|
|
279
|
-
self.config.rate_limit = rate_limit;
|
|
280
|
-
self
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/// Set JWT authentication configuration
|
|
284
|
-
pub fn jwt_auth(mut self, jwt_auth: Option<JwtConfig>) -> Self {
|
|
285
|
-
self.config.jwt_auth = jwt_auth;
|
|
286
|
-
self
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/// Set API key authentication configuration
|
|
290
|
-
pub fn api_key_auth(mut self, api_key_auth: Option<ApiKeyConfig>) -> Self {
|
|
291
|
-
self.config.api_key_auth = api_key_auth;
|
|
292
|
-
self
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/// Add static file serving configuration
|
|
296
|
-
pub fn static_files(mut self, static_files: Vec<StaticFilesConfig>) -> Self {
|
|
297
|
-
self.config.static_files = static_files;
|
|
298
|
-
self
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/// Add a single static file serving configuration
|
|
302
|
-
pub fn add_static_files(mut self, static_file: StaticFilesConfig) -> Self {
|
|
303
|
-
self.config.static_files.push(static_file);
|
|
304
|
-
self
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/// Enable or disable graceful shutdown on SIGTERM/SIGINT
|
|
308
|
-
pub fn graceful_shutdown(mut self, enable: bool) -> Self {
|
|
309
|
-
self.config.graceful_shutdown = enable;
|
|
310
|
-
self
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/// Set graceful shutdown timeout in seconds
|
|
314
|
-
pub fn shutdown_timeout(mut self, timeout: u64) -> Self {
|
|
315
|
-
self.config.shutdown_timeout = timeout;
|
|
316
|
-
self
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/// Set OpenAPI documentation configuration
|
|
320
|
-
pub fn openapi(mut self, openapi: Option<crate::openapi::OpenApiConfig>) -> Self {
|
|
321
|
-
self.config.openapi = openapi;
|
|
322
|
-
self
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/// Set lifecycle hooks for request/response processing
|
|
326
|
-
pub fn lifecycle_hooks(mut self, hooks: Option<std::sync::Arc<LifecycleHooks>>) -> Self {
|
|
327
|
-
self.config.lifecycle_hooks = hooks;
|
|
328
|
-
self
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/// Set background task executor configuration
|
|
332
|
-
pub fn background_tasks(mut self, config: BackgroundTaskConfig) -> Self {
|
|
333
|
-
self.config.background_tasks = config;
|
|
334
|
-
self
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/// Register a value dependency (like Fastify decorate)
|
|
338
|
-
///
|
|
339
|
-
/// Value dependencies are static values that are cloned when injected into handlers.
|
|
340
|
-
/// Use this for configuration objects, constants, or small shared state.
|
|
341
|
-
///
|
|
342
|
-
/// # Example
|
|
343
|
-
///
|
|
344
|
-
/// ```ignorerust
|
|
345
|
-
/// # #[cfg(feature = "di")]
|
|
346
|
-
/// # {
|
|
347
|
-
/// use spikard_http::ServerConfig;
|
|
348
|
-
///
|
|
349
|
-
/// let config = ServerConfig::builder()
|
|
350
|
-
/// .provide_value("app_name", "MyApp".to_string())
|
|
351
|
-
/// .provide_value("version", "1.0.0".to_string())
|
|
352
|
-
/// .provide_value("max_connections", 100)
|
|
353
|
-
/// .build();
|
|
354
|
-
/// # }
|
|
355
|
-
/// ```
|
|
356
|
-
#[cfg(feature = "di")]
|
|
357
|
-
pub fn provide_value<T: Clone + Send + Sync + 'static>(mut self, key: impl Into<String>, value: T) -> Self {
|
|
358
|
-
use spikard_core::di::{DependencyContainer, ValueDependency};
|
|
359
|
-
use std::sync::Arc;
|
|
360
|
-
|
|
361
|
-
let key_str = key.into();
|
|
362
|
-
|
|
363
|
-
// Get or create DI container (mutable)
|
|
364
|
-
let container = if let Some(container) = self.config.di_container.take() {
|
|
365
|
-
// Try to get mutable access - this will only work if we're the only owner
|
|
366
|
-
Arc::try_unwrap(container).unwrap_or_else(|_arc| {
|
|
367
|
-
// If we can't unwrap, we lose existing dependencies
|
|
368
|
-
// This is a fallback that shouldn't happen in normal builder usage (linear chaining)
|
|
369
|
-
DependencyContainer::new()
|
|
370
|
-
})
|
|
371
|
-
} else {
|
|
372
|
-
DependencyContainer::new()
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
let mut container = container;
|
|
376
|
-
|
|
377
|
-
// Create ValueDependency
|
|
378
|
-
let dep = ValueDependency::new(key_str.clone(), value);
|
|
379
|
-
|
|
380
|
-
// Register (panic on error for builder pattern)
|
|
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
|
-
/// Register a factory dependency (like Litestar Provide)
|
|
390
|
-
///
|
|
391
|
-
/// Factory dependencies create values on-demand, optionally depending on other
|
|
392
|
-
/// registered dependencies. Factories are async and have access to resolved dependencies.
|
|
393
|
-
///
|
|
394
|
-
/// # Type Parameters
|
|
395
|
-
///
|
|
396
|
-
/// * `F` - Factory function type
|
|
397
|
-
/// * `Fut` - Future returned by the factory
|
|
398
|
-
/// * `T` - Type of value produced by the factory
|
|
399
|
-
///
|
|
400
|
-
/// # Arguments
|
|
401
|
-
///
|
|
402
|
-
/// * `key` - Unique identifier for this dependency
|
|
403
|
-
/// * `factory` - Async function that creates the dependency value
|
|
404
|
-
///
|
|
405
|
-
/// # Example
|
|
406
|
-
///
|
|
407
|
-
/// ```ignorerust
|
|
408
|
-
/// # #[cfg(feature = "di")]
|
|
409
|
-
/// # {
|
|
410
|
-
/// use spikard_http::ServerConfig;
|
|
411
|
-
/// use std::sync::Arc;
|
|
412
|
-
///
|
|
413
|
-
/// let config = ServerConfig::builder()
|
|
414
|
-
/// .provide_value("db_url", "postgresql://localhost/mydb".to_string())
|
|
415
|
-
/// .provide_factory("db_pool", |resolved| async move {
|
|
416
|
-
/// let url: Arc<String> = resolved.get("db_url").ok_or("Missing db_url")?;
|
|
417
|
-
/// // Create database pool...
|
|
418
|
-
/// Ok(format!("Pool: {}", url))
|
|
419
|
-
/// })
|
|
420
|
-
/// .build();
|
|
421
|
-
/// # }
|
|
422
|
-
/// ```
|
|
423
|
-
#[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
|
-
// Get or create DI container (mutable)
|
|
437
|
-
let container = if let Some(container) = self.config.di_container.take() {
|
|
438
|
-
Arc::try_unwrap(container).unwrap_or_else(|_| DependencyContainer::new())
|
|
439
|
-
} else {
|
|
440
|
-
DependencyContainer::new()
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
let mut container = container;
|
|
444
|
-
|
|
445
|
-
// Clone factory for the closure
|
|
446
|
-
let factory_clone = factory.clone();
|
|
447
|
-
|
|
448
|
-
// Create FactoryDependency using builder
|
|
449
|
-
let dep = FactoryDependency::builder(key_str.clone())
|
|
450
|
-
.factory(
|
|
451
|
-
move |_req: &axum::http::Request<()>,
|
|
452
|
-
_data: &spikard_core::RequestData,
|
|
453
|
-
resolved: &spikard_core::di::ResolvedDependencies| {
|
|
454
|
-
let factory = factory_clone.clone();
|
|
455
|
-
let factory_result = factory(resolved);
|
|
456
|
-
Box::pin(async move {
|
|
457
|
-
let result = factory_result
|
|
458
|
-
.await
|
|
459
|
-
.map_err(|e| DependencyError::ResolutionFailed { message: e })?;
|
|
460
|
-
Ok(Arc::new(result) as Arc<dyn std::any::Any + Send + Sync>)
|
|
461
|
-
})
|
|
462
|
-
as BoxFuture<'static, Result<Arc<dyn std::any::Any + Send + Sync>, DependencyError>>
|
|
463
|
-
},
|
|
464
|
-
)
|
|
465
|
-
.build();
|
|
466
|
-
|
|
467
|
-
container
|
|
468
|
-
.register(key_str, Arc::new(dep))
|
|
469
|
-
.expect("Failed to register dependency");
|
|
470
|
-
|
|
471
|
-
self.config.di_container = Some(Arc::new(container));
|
|
472
|
-
self
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/// Register a dependency with full control (advanced API)
|
|
476
|
-
///
|
|
477
|
-
/// This method allows you to register custom dependency implementations
|
|
478
|
-
/// that implement the `Dependency` trait. Use this for advanced use cases
|
|
479
|
-
/// where you need fine-grained control over dependency resolution.
|
|
480
|
-
///
|
|
481
|
-
/// # Example
|
|
482
|
-
///
|
|
483
|
-
/// ```ignorerust
|
|
484
|
-
/// # #[cfg(feature = "di")]
|
|
485
|
-
/// # {
|
|
486
|
-
/// use spikard_http::ServerConfig;
|
|
487
|
-
/// use spikard_core::di::ValueDependency;
|
|
488
|
-
/// use std::sync::Arc;
|
|
489
|
-
///
|
|
490
|
-
/// let dep = ValueDependency::new("custom", "value".to_string());
|
|
491
|
-
///
|
|
492
|
-
/// let config = ServerConfig::builder()
|
|
493
|
-
/// .provide(Arc::new(dep))
|
|
494
|
-
/// .build();
|
|
495
|
-
/// # }
|
|
496
|
-
/// ```
|
|
497
|
-
#[cfg(feature = "di")]
|
|
498
|
-
pub fn provide(mut self, dependency: std::sync::Arc<dyn spikard_core::di::Dependency>) -> Self {
|
|
499
|
-
use spikard_core::di::DependencyContainer;
|
|
500
|
-
use std::sync::Arc;
|
|
501
|
-
|
|
502
|
-
let key = dependency.key().to_string();
|
|
503
|
-
|
|
504
|
-
// Get or create DI container (mutable)
|
|
505
|
-
let container = if let Some(container) = self.config.di_container.take() {
|
|
506
|
-
Arc::try_unwrap(container).unwrap_or_else(|_| DependencyContainer::new())
|
|
507
|
-
} else {
|
|
508
|
-
DependencyContainer::new()
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
let mut container = container;
|
|
512
|
-
|
|
513
|
-
container
|
|
514
|
-
.register(key, dependency)
|
|
515
|
-
.expect("Failed to register dependency");
|
|
516
|
-
|
|
517
|
-
self.config.di_container = Some(Arc::new(container));
|
|
518
|
-
self
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
/// Build the ServerConfig
|
|
522
|
-
pub fn build(self) -> ServerConfig {
|
|
523
|
-
self.config
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
const fn default_true() -> bool {
|
|
528
|
-
true
|
|
529
|
-
}
|
|
1
|
+
//! Spikard HTTP Server
|
|
2
|
+
//!
|
|
3
|
+
//! Pure Rust HTTP server with language-agnostic handler trait.
|
|
4
|
+
//! Language bindings (Python, Node, WASM) implement the Handler trait.
|
|
5
|
+
|
|
6
|
+
pub mod auth;
|
|
7
|
+
pub mod background;
|
|
8
|
+
pub mod bindings;
|
|
9
|
+
pub mod body_metadata;
|
|
10
|
+
pub mod cors;
|
|
11
|
+
pub mod debug;
|
|
12
|
+
#[cfg(feature = "di")]
|
|
13
|
+
pub mod di_handler;
|
|
14
|
+
pub mod handler_response;
|
|
15
|
+
pub mod handler_trait;
|
|
16
|
+
pub mod lifecycle;
|
|
17
|
+
pub mod middleware;
|
|
18
|
+
pub mod openapi;
|
|
19
|
+
pub mod parameters;
|
|
20
|
+
pub mod problem;
|
|
21
|
+
pub mod query_parser;
|
|
22
|
+
pub mod response;
|
|
23
|
+
pub mod router;
|
|
24
|
+
pub mod schema_registry;
|
|
25
|
+
pub mod server;
|
|
26
|
+
pub mod sse;
|
|
27
|
+
pub mod testing;
|
|
28
|
+
pub mod type_hints;
|
|
29
|
+
pub mod validation;
|
|
30
|
+
pub mod websocket;
|
|
31
|
+
|
|
32
|
+
use serde::{Deserialize, Serialize};
|
|
33
|
+
|
|
34
|
+
#[cfg(test)]
|
|
35
|
+
mod handler_trait_tests;
|
|
36
|
+
|
|
37
|
+
pub use auth::{Claims, api_key_auth_middleware, jwt_auth_middleware};
|
|
38
|
+
pub use background::{
|
|
39
|
+
BackgroundHandle, BackgroundJobError, BackgroundJobMetadata, BackgroundRuntime, BackgroundSpawnError,
|
|
40
|
+
BackgroundTaskConfig,
|
|
41
|
+
};
|
|
42
|
+
pub use body_metadata::ResponseBodySize;
|
|
43
|
+
#[cfg(feature = "di")]
|
|
44
|
+
pub use di_handler::DependencyInjectingHandler;
|
|
45
|
+
pub use handler_response::HandlerResponse;
|
|
46
|
+
pub use handler_trait::{Handler, HandlerResult, RequestData, ValidatedParams};
|
|
47
|
+
pub use lifecycle::{HookResult, LifecycleHook, LifecycleHooks, LifecycleHooksBuilder, request_hook, response_hook};
|
|
48
|
+
pub use openapi::{ContactInfo, LicenseInfo, OpenApiConfig, SecuritySchemeInfo, ServerInfo};
|
|
49
|
+
pub use parameters::ParameterValidator;
|
|
50
|
+
pub use problem::{CONTENT_TYPE_PROBLEM_JSON, ProblemDetails};
|
|
51
|
+
pub use response::Response;
|
|
52
|
+
pub use router::{Route, RouteHandler, Router};
|
|
53
|
+
pub use schema_registry::SchemaRegistry;
|
|
54
|
+
pub use server::Server;
|
|
55
|
+
pub use spikard_core::{CompressionConfig, CorsConfig, Method, RateLimitConfig, RouteMetadata};
|
|
56
|
+
pub use sse::{SseEvent, SseEventProducer, SseState, sse_handler};
|
|
57
|
+
pub use testing::{ResponseSnapshot, SnapshotError, snapshot_response};
|
|
58
|
+
pub use validation::SchemaValidator;
|
|
59
|
+
pub use websocket::{WebSocketHandler, WebSocketState, websocket_handler};
|
|
60
|
+
|
|
61
|
+
/// JWT authentication configuration
|
|
62
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
63
|
+
pub struct JwtConfig {
|
|
64
|
+
/// Secret key for JWT verification
|
|
65
|
+
pub secret: String,
|
|
66
|
+
/// Required algorithm (HS256, HS384, HS512, RS256, etc.)
|
|
67
|
+
#[serde(default = "default_jwt_algorithm")]
|
|
68
|
+
pub algorithm: String,
|
|
69
|
+
/// Required audience claim
|
|
70
|
+
pub audience: Option<Vec<String>>,
|
|
71
|
+
/// Required issuer claim
|
|
72
|
+
pub issuer: Option<String>,
|
|
73
|
+
/// Leeway for expiration checks (seconds)
|
|
74
|
+
#[serde(default)]
|
|
75
|
+
pub leeway: u64,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fn default_jwt_algorithm() -> String {
|
|
79
|
+
"HS256".to_string()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// API Key authentication configuration
|
|
83
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
84
|
+
pub struct ApiKeyConfig {
|
|
85
|
+
/// Valid API keys
|
|
86
|
+
pub keys: Vec<String>,
|
|
87
|
+
/// Header name to check (e.g., "X-API-Key")
|
|
88
|
+
#[serde(default = "default_api_key_header")]
|
|
89
|
+
pub header_name: String,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn default_api_key_header() -> String {
|
|
93
|
+
"X-API-Key".to_string()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Static file serving configuration
|
|
97
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
98
|
+
pub struct StaticFilesConfig {
|
|
99
|
+
/// Directory path to serve
|
|
100
|
+
pub directory: String,
|
|
101
|
+
/// URL path prefix (e.g., "/static")
|
|
102
|
+
pub route_prefix: String,
|
|
103
|
+
/// Fallback to index.html for directories
|
|
104
|
+
#[serde(default = "default_true")]
|
|
105
|
+
pub index_file: bool,
|
|
106
|
+
/// Cache-Control header value
|
|
107
|
+
pub cache_control: Option<String>,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Server configuration
|
|
111
|
+
#[derive(Debug, Clone)]
|
|
112
|
+
pub struct ServerConfig {
|
|
113
|
+
/// Host to bind to
|
|
114
|
+
pub host: String,
|
|
115
|
+
/// Port to bind to
|
|
116
|
+
pub port: u16,
|
|
117
|
+
/// Number of worker threads (unused with tokio)
|
|
118
|
+
pub workers: usize,
|
|
119
|
+
|
|
120
|
+
/// Enable request ID generation and propagation
|
|
121
|
+
pub enable_request_id: bool,
|
|
122
|
+
/// Maximum request body size in bytes (None = unlimited, not recommended)
|
|
123
|
+
pub max_body_size: Option<usize>,
|
|
124
|
+
/// Request timeout in seconds (None = no timeout)
|
|
125
|
+
pub request_timeout: Option<u64>,
|
|
126
|
+
/// Enable compression middleware
|
|
127
|
+
pub compression: Option<CompressionConfig>,
|
|
128
|
+
/// Enable rate limiting
|
|
129
|
+
pub rate_limit: Option<RateLimitConfig>,
|
|
130
|
+
/// JWT authentication configuration
|
|
131
|
+
pub jwt_auth: Option<JwtConfig>,
|
|
132
|
+
/// API Key authentication configuration
|
|
133
|
+
pub api_key_auth: Option<ApiKeyConfig>,
|
|
134
|
+
/// Static file serving configuration
|
|
135
|
+
pub static_files: Vec<StaticFilesConfig>,
|
|
136
|
+
/// Enable graceful shutdown on SIGTERM/SIGINT
|
|
137
|
+
pub graceful_shutdown: bool,
|
|
138
|
+
/// Graceful shutdown timeout (seconds)
|
|
139
|
+
pub shutdown_timeout: u64,
|
|
140
|
+
/// OpenAPI documentation configuration
|
|
141
|
+
pub openapi: Option<crate::openapi::OpenApiConfig>,
|
|
142
|
+
/// Lifecycle hooks for request/response processing
|
|
143
|
+
pub lifecycle_hooks: Option<std::sync::Arc<LifecycleHooks>>,
|
|
144
|
+
/// Background task executor configuration
|
|
145
|
+
pub background_tasks: BackgroundTaskConfig,
|
|
146
|
+
/// Dependency injection container (requires 'di' feature)
|
|
147
|
+
#[cfg(feature = "di")]
|
|
148
|
+
pub di_container: Option<std::sync::Arc<spikard_core::di::DependencyContainer>>,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
impl Default for ServerConfig {
|
|
152
|
+
fn default() -> Self {
|
|
153
|
+
Self {
|
|
154
|
+
host: "127.0.0.1".to_string(),
|
|
155
|
+
port: 8000,
|
|
156
|
+
workers: 1,
|
|
157
|
+
enable_request_id: true,
|
|
158
|
+
max_body_size: Some(10 * 1024 * 1024),
|
|
159
|
+
request_timeout: Some(30),
|
|
160
|
+
compression: Some(CompressionConfig::default()),
|
|
161
|
+
rate_limit: None,
|
|
162
|
+
jwt_auth: None,
|
|
163
|
+
api_key_auth: None,
|
|
164
|
+
static_files: Vec::new(),
|
|
165
|
+
graceful_shutdown: true,
|
|
166
|
+
shutdown_timeout: 30,
|
|
167
|
+
openapi: None,
|
|
168
|
+
lifecycle_hooks: None,
|
|
169
|
+
background_tasks: BackgroundTaskConfig::default(),
|
|
170
|
+
#[cfg(feature = "di")]
|
|
171
|
+
di_container: None,
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
impl ServerConfig {
|
|
177
|
+
/// Create a new builder for ServerConfig
|
|
178
|
+
///
|
|
179
|
+
/// # Example
|
|
180
|
+
///
|
|
181
|
+
/// ```ignorerust
|
|
182
|
+
/// use spikard_http::ServerConfig;
|
|
183
|
+
///
|
|
184
|
+
/// let config = ServerConfig::builder()
|
|
185
|
+
/// .port(3000)
|
|
186
|
+
/// .host("0.0.0.0")
|
|
187
|
+
/// .build();
|
|
188
|
+
/// ```
|
|
189
|
+
pub fn builder() -> ServerConfigBuilder {
|
|
190
|
+
ServerConfigBuilder::default()
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Builder for ServerConfig
|
|
195
|
+
///
|
|
196
|
+
/// Provides a fluent API for configuring a Spikard server with dependency injection support.
|
|
197
|
+
///
|
|
198
|
+
/// # Dependency Injection
|
|
199
|
+
///
|
|
200
|
+
/// The builder provides methods to register dependencies that will be injected into handlers:
|
|
201
|
+
///
|
|
202
|
+
/// ```ignorerust
|
|
203
|
+
/// # #[cfg(feature = "di")]
|
|
204
|
+
/// # {
|
|
205
|
+
/// use spikard_http::ServerConfig;
|
|
206
|
+
/// use std::sync::Arc;
|
|
207
|
+
///
|
|
208
|
+
/// let config = ServerConfig::builder()
|
|
209
|
+
/// .port(3000)
|
|
210
|
+
/// .provide_value("app_name", "MyApp".to_string())
|
|
211
|
+
/// .provide_value("max_connections", 100)
|
|
212
|
+
/// .build();
|
|
213
|
+
/// # }
|
|
214
|
+
/// ```
|
|
215
|
+
///
|
|
216
|
+
/// For factory dependencies that create values on-demand:
|
|
217
|
+
///
|
|
218
|
+
/// ```ignorerust
|
|
219
|
+
/// # #[cfg(feature = "di")]
|
|
220
|
+
/// # {
|
|
221
|
+
/// use spikard_http::ServerConfig;
|
|
222
|
+
///
|
|
223
|
+
/// let config = ServerConfig::builder()
|
|
224
|
+
/// .port(3000)
|
|
225
|
+
/// .provide_value("db_url", "postgresql://localhost/mydb".to_string())
|
|
226
|
+
/// .build();
|
|
227
|
+
/// # }
|
|
228
|
+
/// ```
|
|
229
|
+
#[derive(Debug, Clone, Default)]
|
|
230
|
+
pub struct ServerConfigBuilder {
|
|
231
|
+
config: ServerConfig,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
impl ServerConfigBuilder {
|
|
235
|
+
/// Set the host address to bind to
|
|
236
|
+
pub fn host(mut self, host: impl Into<String>) -> Self {
|
|
237
|
+
self.config.host = host.into();
|
|
238
|
+
self
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// Set the port to bind to
|
|
242
|
+
pub fn port(mut self, port: u16) -> Self {
|
|
243
|
+
self.config.port = port;
|
|
244
|
+
self
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Set the number of worker threads (unused with tokio, kept for compatibility)
|
|
248
|
+
pub fn workers(mut self, workers: usize) -> Self {
|
|
249
|
+
self.config.workers = workers;
|
|
250
|
+
self
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/// Enable or disable request ID generation and propagation
|
|
254
|
+
pub fn enable_request_id(mut self, enable: bool) -> Self {
|
|
255
|
+
self.config.enable_request_id = enable;
|
|
256
|
+
self
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// Set maximum request body size in bytes (None = unlimited, not recommended)
|
|
260
|
+
pub fn max_body_size(mut self, size: Option<usize>) -> Self {
|
|
261
|
+
self.config.max_body_size = size;
|
|
262
|
+
self
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/// Set request timeout in seconds (None = no timeout)
|
|
266
|
+
pub fn request_timeout(mut self, timeout: Option<u64>) -> Self {
|
|
267
|
+
self.config.request_timeout = timeout;
|
|
268
|
+
self
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/// Set compression configuration
|
|
272
|
+
pub fn compression(mut self, compression: Option<CompressionConfig>) -> Self {
|
|
273
|
+
self.config.compression = compression;
|
|
274
|
+
self
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/// Set rate limiting configuration
|
|
278
|
+
pub fn rate_limit(mut self, rate_limit: Option<RateLimitConfig>) -> Self {
|
|
279
|
+
self.config.rate_limit = rate_limit;
|
|
280
|
+
self
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/// Set JWT authentication configuration
|
|
284
|
+
pub fn jwt_auth(mut self, jwt_auth: Option<JwtConfig>) -> Self {
|
|
285
|
+
self.config.jwt_auth = jwt_auth;
|
|
286
|
+
self
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/// Set API key authentication configuration
|
|
290
|
+
pub fn api_key_auth(mut self, api_key_auth: Option<ApiKeyConfig>) -> Self {
|
|
291
|
+
self.config.api_key_auth = api_key_auth;
|
|
292
|
+
self
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/// Add static file serving configuration
|
|
296
|
+
pub fn static_files(mut self, static_files: Vec<StaticFilesConfig>) -> Self {
|
|
297
|
+
self.config.static_files = static_files;
|
|
298
|
+
self
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/// Add a single static file serving configuration
|
|
302
|
+
pub fn add_static_files(mut self, static_file: StaticFilesConfig) -> Self {
|
|
303
|
+
self.config.static_files.push(static_file);
|
|
304
|
+
self
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/// Enable or disable graceful shutdown on SIGTERM/SIGINT
|
|
308
|
+
pub fn graceful_shutdown(mut self, enable: bool) -> Self {
|
|
309
|
+
self.config.graceful_shutdown = enable;
|
|
310
|
+
self
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/// Set graceful shutdown timeout in seconds
|
|
314
|
+
pub fn shutdown_timeout(mut self, timeout: u64) -> Self {
|
|
315
|
+
self.config.shutdown_timeout = timeout;
|
|
316
|
+
self
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// Set OpenAPI documentation configuration
|
|
320
|
+
pub fn openapi(mut self, openapi: Option<crate::openapi::OpenApiConfig>) -> Self {
|
|
321
|
+
self.config.openapi = openapi;
|
|
322
|
+
self
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/// Set lifecycle hooks for request/response processing
|
|
326
|
+
pub fn lifecycle_hooks(mut self, hooks: Option<std::sync::Arc<LifecycleHooks>>) -> Self {
|
|
327
|
+
self.config.lifecycle_hooks = hooks;
|
|
328
|
+
self
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/// Set background task executor configuration
|
|
332
|
+
pub fn background_tasks(mut self, config: BackgroundTaskConfig) -> Self {
|
|
333
|
+
self.config.background_tasks = config;
|
|
334
|
+
self
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/// Register a value dependency (like Fastify decorate)
|
|
338
|
+
///
|
|
339
|
+
/// Value dependencies are static values that are cloned when injected into handlers.
|
|
340
|
+
/// Use this for configuration objects, constants, or small shared state.
|
|
341
|
+
///
|
|
342
|
+
/// # Example
|
|
343
|
+
///
|
|
344
|
+
/// ```ignorerust
|
|
345
|
+
/// # #[cfg(feature = "di")]
|
|
346
|
+
/// # {
|
|
347
|
+
/// use spikard_http::ServerConfig;
|
|
348
|
+
///
|
|
349
|
+
/// let config = ServerConfig::builder()
|
|
350
|
+
/// .provide_value("app_name", "MyApp".to_string())
|
|
351
|
+
/// .provide_value("version", "1.0.0".to_string())
|
|
352
|
+
/// .provide_value("max_connections", 100)
|
|
353
|
+
/// .build();
|
|
354
|
+
/// # }
|
|
355
|
+
/// ```
|
|
356
|
+
#[cfg(feature = "di")]
|
|
357
|
+
pub fn provide_value<T: Clone + Send + Sync + 'static>(mut self, key: impl Into<String>, value: T) -> Self {
|
|
358
|
+
use spikard_core::di::{DependencyContainer, ValueDependency};
|
|
359
|
+
use std::sync::Arc;
|
|
360
|
+
|
|
361
|
+
let key_str = key.into();
|
|
362
|
+
|
|
363
|
+
// Get or create DI container (mutable)
|
|
364
|
+
let container = if let Some(container) = self.config.di_container.take() {
|
|
365
|
+
// Try to get mutable access - this will only work if we're the only owner
|
|
366
|
+
Arc::try_unwrap(container).unwrap_or_else(|_arc| {
|
|
367
|
+
// If we can't unwrap, we lose existing dependencies
|
|
368
|
+
// This is a fallback that shouldn't happen in normal builder usage (linear chaining)
|
|
369
|
+
DependencyContainer::new()
|
|
370
|
+
})
|
|
371
|
+
} else {
|
|
372
|
+
DependencyContainer::new()
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
let mut container = container;
|
|
376
|
+
|
|
377
|
+
// Create ValueDependency
|
|
378
|
+
let dep = ValueDependency::new(key_str.clone(), value);
|
|
379
|
+
|
|
380
|
+
// Register (panic on error for builder pattern)
|
|
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
|
+
/// Register a factory dependency (like Litestar Provide)
|
|
390
|
+
///
|
|
391
|
+
/// Factory dependencies create values on-demand, optionally depending on other
|
|
392
|
+
/// registered dependencies. Factories are async and have access to resolved dependencies.
|
|
393
|
+
///
|
|
394
|
+
/// # Type Parameters
|
|
395
|
+
///
|
|
396
|
+
/// * `F` - Factory function type
|
|
397
|
+
/// * `Fut` - Future returned by the factory
|
|
398
|
+
/// * `T` - Type of value produced by the factory
|
|
399
|
+
///
|
|
400
|
+
/// # Arguments
|
|
401
|
+
///
|
|
402
|
+
/// * `key` - Unique identifier for this dependency
|
|
403
|
+
/// * `factory` - Async function that creates the dependency value
|
|
404
|
+
///
|
|
405
|
+
/// # Example
|
|
406
|
+
///
|
|
407
|
+
/// ```ignorerust
|
|
408
|
+
/// # #[cfg(feature = "di")]
|
|
409
|
+
/// # {
|
|
410
|
+
/// use spikard_http::ServerConfig;
|
|
411
|
+
/// use std::sync::Arc;
|
|
412
|
+
///
|
|
413
|
+
/// let config = ServerConfig::builder()
|
|
414
|
+
/// .provide_value("db_url", "postgresql://localhost/mydb".to_string())
|
|
415
|
+
/// .provide_factory("db_pool", |resolved| async move {
|
|
416
|
+
/// let url: Arc<String> = resolved.get("db_url").ok_or("Missing db_url")?;
|
|
417
|
+
/// // Create database pool...
|
|
418
|
+
/// Ok(format!("Pool: {}", url))
|
|
419
|
+
/// })
|
|
420
|
+
/// .build();
|
|
421
|
+
/// # }
|
|
422
|
+
/// ```
|
|
423
|
+
#[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
|
+
// Get or create DI container (mutable)
|
|
437
|
+
let container = if let Some(container) = self.config.di_container.take() {
|
|
438
|
+
Arc::try_unwrap(container).unwrap_or_else(|_| DependencyContainer::new())
|
|
439
|
+
} else {
|
|
440
|
+
DependencyContainer::new()
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
let mut container = container;
|
|
444
|
+
|
|
445
|
+
// Clone factory for the closure
|
|
446
|
+
let factory_clone = factory.clone();
|
|
447
|
+
|
|
448
|
+
// Create FactoryDependency using builder
|
|
449
|
+
let dep = FactoryDependency::builder(key_str.clone())
|
|
450
|
+
.factory(
|
|
451
|
+
move |_req: &axum::http::Request<()>,
|
|
452
|
+
_data: &spikard_core::RequestData,
|
|
453
|
+
resolved: &spikard_core::di::ResolvedDependencies| {
|
|
454
|
+
let factory = factory_clone.clone();
|
|
455
|
+
let factory_result = factory(resolved);
|
|
456
|
+
Box::pin(async move {
|
|
457
|
+
let result = factory_result
|
|
458
|
+
.await
|
|
459
|
+
.map_err(|e| DependencyError::ResolutionFailed { message: e })?;
|
|
460
|
+
Ok(Arc::new(result) as Arc<dyn std::any::Any + Send + Sync>)
|
|
461
|
+
})
|
|
462
|
+
as BoxFuture<'static, Result<Arc<dyn std::any::Any + Send + Sync>, DependencyError>>
|
|
463
|
+
},
|
|
464
|
+
)
|
|
465
|
+
.build();
|
|
466
|
+
|
|
467
|
+
container
|
|
468
|
+
.register(key_str, Arc::new(dep))
|
|
469
|
+
.expect("Failed to register dependency");
|
|
470
|
+
|
|
471
|
+
self.config.di_container = Some(Arc::new(container));
|
|
472
|
+
self
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/// Register a dependency with full control (advanced API)
|
|
476
|
+
///
|
|
477
|
+
/// This method allows you to register custom dependency implementations
|
|
478
|
+
/// that implement the `Dependency` trait. Use this for advanced use cases
|
|
479
|
+
/// where you need fine-grained control over dependency resolution.
|
|
480
|
+
///
|
|
481
|
+
/// # Example
|
|
482
|
+
///
|
|
483
|
+
/// ```ignorerust
|
|
484
|
+
/// # #[cfg(feature = "di")]
|
|
485
|
+
/// # {
|
|
486
|
+
/// use spikard_http::ServerConfig;
|
|
487
|
+
/// use spikard_core::di::ValueDependency;
|
|
488
|
+
/// use std::sync::Arc;
|
|
489
|
+
///
|
|
490
|
+
/// let dep = ValueDependency::new("custom", "value".to_string());
|
|
491
|
+
///
|
|
492
|
+
/// let config = ServerConfig::builder()
|
|
493
|
+
/// .provide(Arc::new(dep))
|
|
494
|
+
/// .build();
|
|
495
|
+
/// # }
|
|
496
|
+
/// ```
|
|
497
|
+
#[cfg(feature = "di")]
|
|
498
|
+
pub fn provide(mut self, dependency: std::sync::Arc<dyn spikard_core::di::Dependency>) -> Self {
|
|
499
|
+
use spikard_core::di::DependencyContainer;
|
|
500
|
+
use std::sync::Arc;
|
|
501
|
+
|
|
502
|
+
let key = dependency.key().to_string();
|
|
503
|
+
|
|
504
|
+
// Get or create DI container (mutable)
|
|
505
|
+
let container = if let Some(container) = self.config.di_container.take() {
|
|
506
|
+
Arc::try_unwrap(container).unwrap_or_else(|_| DependencyContainer::new())
|
|
507
|
+
} else {
|
|
508
|
+
DependencyContainer::new()
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
let mut container = container;
|
|
512
|
+
|
|
513
|
+
container
|
|
514
|
+
.register(key, dependency)
|
|
515
|
+
.expect("Failed to register dependency");
|
|
516
|
+
|
|
517
|
+
self.config.di_container = Some(Arc::new(container));
|
|
518
|
+
self
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/// Build the ServerConfig
|
|
522
|
+
pub fn build(self) -> ServerConfig {
|
|
523
|
+
self.config
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const fn default_true() -> bool {
|
|
528
|
+
true
|
|
529
|
+
}
|