spikard 0.3.4 → 0.3.6
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 +366 -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 +68 -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
- metadata +1 -79
- data/vendor/spikard-core/Cargo.toml +0 -40
- data/vendor/spikard-core/src/bindings/mod.rs +0 -3
- data/vendor/spikard-core/src/bindings/response.rs +0 -133
- data/vendor/spikard-core/src/debug.rs +0 -63
- data/vendor/spikard-core/src/di/container.rs +0 -726
- data/vendor/spikard-core/src/di/dependency.rs +0 -273
- data/vendor/spikard-core/src/di/error.rs +0 -118
- data/vendor/spikard-core/src/di/factory.rs +0 -538
- data/vendor/spikard-core/src/di/graph.rs +0 -545
- data/vendor/spikard-core/src/di/mod.rs +0 -192
- data/vendor/spikard-core/src/di/resolved.rs +0 -411
- data/vendor/spikard-core/src/di/value.rs +0 -283
- data/vendor/spikard-core/src/http.rs +0 -153
- data/vendor/spikard-core/src/lib.rs +0 -28
- data/vendor/spikard-core/src/lifecycle.rs +0 -422
- data/vendor/spikard-core/src/parameters.rs +0 -719
- data/vendor/spikard-core/src/problem.rs +0 -310
- data/vendor/spikard-core/src/request_data.rs +0 -189
- data/vendor/spikard-core/src/router.rs +0 -249
- data/vendor/spikard-core/src/schema_registry.rs +0 -183
- data/vendor/spikard-core/src/type_hints.rs +0 -304
- data/vendor/spikard-core/src/validation.rs +0 -699
- data/vendor/spikard-http/Cargo.toml +0 -58
- data/vendor/spikard-http/src/auth.rs +0 -247
- data/vendor/spikard-http/src/background.rs +0 -249
- data/vendor/spikard-http/src/bindings/mod.rs +0 -3
- data/vendor/spikard-http/src/bindings/response.rs +0 -1
- data/vendor/spikard-http/src/body_metadata.rs +0 -8
- data/vendor/spikard-http/src/cors.rs +0 -490
- data/vendor/spikard-http/src/debug.rs +0 -63
- data/vendor/spikard-http/src/di_handler.rs +0 -423
- data/vendor/spikard-http/src/handler_response.rs +0 -190
- data/vendor/spikard-http/src/handler_trait.rs +0 -228
- data/vendor/spikard-http/src/handler_trait_tests.rs +0 -284
- data/vendor/spikard-http/src/lib.rs +0 -529
- data/vendor/spikard-http/src/lifecycle/adapter.rs +0 -149
- data/vendor/spikard-http/src/lifecycle.rs +0 -428
- data/vendor/spikard-http/src/middleware/mod.rs +0 -285
- data/vendor/spikard-http/src/middleware/multipart.rs +0 -86
- data/vendor/spikard-http/src/middleware/urlencoded.rs +0 -147
- data/vendor/spikard-http/src/middleware/validation.rs +0 -287
- data/vendor/spikard-http/src/openapi/mod.rs +0 -309
- data/vendor/spikard-http/src/openapi/parameter_extraction.rs +0 -190
- data/vendor/spikard-http/src/openapi/schema_conversion.rs +0 -308
- data/vendor/spikard-http/src/openapi/spec_generation.rs +0 -195
- data/vendor/spikard-http/src/parameters.rs +0 -1
- data/vendor/spikard-http/src/problem.rs +0 -1
- data/vendor/spikard-http/src/query_parser.rs +0 -369
- data/vendor/spikard-http/src/response.rs +0 -399
- data/vendor/spikard-http/src/router.rs +0 -1
- data/vendor/spikard-http/src/schema_registry.rs +0 -1
- data/vendor/spikard-http/src/server/handler.rs +0 -80
- data/vendor/spikard-http/src/server/lifecycle_execution.rs +0 -98
- data/vendor/spikard-http/src/server/mod.rs +0 -805
- data/vendor/spikard-http/src/server/request_extraction.rs +0 -119
- data/vendor/spikard-http/src/sse.rs +0 -447
- data/vendor/spikard-http/src/testing/form.rs +0 -14
- data/vendor/spikard-http/src/testing/multipart.rs +0 -60
- data/vendor/spikard-http/src/testing/test_client.rs +0 -285
- data/vendor/spikard-http/src/testing.rs +0 -377
- data/vendor/spikard-http/src/type_hints.rs +0 -1
- data/vendor/spikard-http/src/validation.rs +0 -1
- data/vendor/spikard-http/src/websocket.rs +0 -324
- data/vendor/spikard-rb/Cargo.toml +0 -42
- data/vendor/spikard-rb/build.rs +0 -8
- data/vendor/spikard-rb/src/background.rs +0 -63
- data/vendor/spikard-rb/src/config.rs +0 -294
- data/vendor/spikard-rb/src/conversion.rs +0 -392
- data/vendor/spikard-rb/src/di.rs +0 -409
- data/vendor/spikard-rb/src/handler.rs +0 -534
- data/vendor/spikard-rb/src/lib.rs +0 -2020
- data/vendor/spikard-rb/src/lifecycle.rs +0 -267
- data/vendor/spikard-rb/src/server.rs +0 -283
- data/vendor/spikard-rb/src/sse.rs +0 -231
- data/vendor/spikard-rb/src/test_client.rs +0 -404
- data/vendor/spikard-rb/src/test_sse.rs +0 -143
- data/vendor/spikard-rb/src/test_websocket.rs +0 -221
- data/vendor/spikard-rb/src/websocket.rs +0 -233
|
@@ -1,529 +0,0 @@
|
|
|
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,149 +0,0 @@
|
|
|
1
|
-
//! Shared utilities for lifecycle hook implementations across language bindings.
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides common error messages, hook registration patterns, and
|
|
4
|
-
//! serialization utilities to eliminate duplication across Python, Node.js,
|
|
5
|
-
//! Ruby, and WASM bindings.
|
|
6
|
-
|
|
7
|
-
use crate::lifecycle::LifecycleHook;
|
|
8
|
-
use axum::body::Body;
|
|
9
|
-
use axum::http::{Request, Response};
|
|
10
|
-
use std::sync::Arc;
|
|
11
|
-
|
|
12
|
-
/// Standard error message formatters for lifecycle hooks.
|
|
13
|
-
/// These are used consistently across all language bindings.
|
|
14
|
-
pub mod error {
|
|
15
|
-
use std::fmt::Display;
|
|
16
|
-
|
|
17
|
-
/// Format error when a hook invocation fails
|
|
18
|
-
pub fn call_failed(hook_name: &str, reason: impl Display) -> String {
|
|
19
|
-
format!("Hook '{}' call failed: {}", hook_name, reason)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/// Format error when a task execution fails (tokio/threading)
|
|
23
|
-
pub fn task_error(hook_name: &str, reason: impl Display) -> String {
|
|
24
|
-
format!("Hook '{}' task error: {}", hook_name, reason)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/// Format error when a promise/future fails
|
|
28
|
-
pub fn promise_failed(hook_name: &str, reason: impl Display) -> String {
|
|
29
|
-
format!("Hook '{}' promise failed: {}", hook_name, reason)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/// Format error for Python-specific failures
|
|
33
|
-
pub fn python_error(hook_name: &str, reason: impl Display) -> String {
|
|
34
|
-
format!("Hook '{}' Python error: {}", hook_name, reason)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/// Format error when body reading fails
|
|
38
|
-
pub fn body_read_failed(direction: &str, reason: impl Display) -> String {
|
|
39
|
-
format!("Failed to read {} body: {}", direction, reason)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/// Format error when body writing fails
|
|
43
|
-
pub fn body_write_failed(reason: impl Display) -> String {
|
|
44
|
-
format!("Failed to write body: {}", reason)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/// Format error for serialization failures
|
|
48
|
-
pub fn serialize_failed(context: &str, reason: impl Display) -> String {
|
|
49
|
-
format!("Failed to serialize {}: {}", context, reason)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/// Format error for deserialization failures
|
|
53
|
-
pub fn deserialize_failed(context: &str, reason: impl Display) -> String {
|
|
54
|
-
format!("Failed to deserialize {}: {}", context, reason)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/// Format error when building HTTP objects fails
|
|
58
|
-
pub fn build_failed(what: &str, reason: impl Display) -> String {
|
|
59
|
-
format!("Failed to build {}: {}", what, reason)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/// Utilities for serializing/deserializing request and response bodies
|
|
64
|
-
pub mod serial {
|
|
65
|
-
use super::*;
|
|
66
|
-
|
|
67
|
-
/// Extract body bytes from an axum Body
|
|
68
|
-
pub async fn extract_body(body: Body) -> Result<bytes::Bytes, String> {
|
|
69
|
-
use axum::body::to_bytes;
|
|
70
|
-
to_bytes(body, usize::MAX)
|
|
71
|
-
.await
|
|
72
|
-
.map_err(|e| error::body_read_failed("request/response", e))
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/// Create a JSON-formatted response body
|
|
76
|
-
pub fn json_response_body(json: &serde_json::Value) -> Result<Body, String> {
|
|
77
|
-
serde_json::to_string(json)
|
|
78
|
-
.map(Body::from)
|
|
79
|
-
.map_err(|e| error::serialize_failed("response JSON", e))
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/// Parse a JSON value from bytes
|
|
83
|
-
pub fn parse_json(bytes: &[u8]) -> Result<serde_json::Value, String> {
|
|
84
|
-
if bytes.is_empty() {
|
|
85
|
-
return Ok(serde_json::Value::Null);
|
|
86
|
-
}
|
|
87
|
-
serde_json::from_slice(bytes)
|
|
88
|
-
.or_else(|_| Ok(serde_json::Value::String(String::from_utf8_lossy(bytes).to_string())))
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/// Re-export of the HTTP-specific lifecycle hooks type alias
|
|
93
|
-
pub use super::LifecycleHooks as HttpLifecycleHooks;
|
|
94
|
-
|
|
95
|
-
/// Helper for registering hooks with standard naming conventions
|
|
96
|
-
pub struct HookRegistry;
|
|
97
|
-
|
|
98
|
-
impl HookRegistry {
|
|
99
|
-
/// Extract hooks from a configuration and register them with a naming pattern
|
|
100
|
-
/// Used by bindings to standardize hook naming (e.g., "on_request_hook_0")
|
|
101
|
-
pub fn register_from_list<F>(
|
|
102
|
-
hooks: &mut HttpLifecycleHooks,
|
|
103
|
-
hook_list: Vec<Arc<dyn LifecycleHook<Request<Body>, Response<Body>>>>,
|
|
104
|
-
_hook_type: &str,
|
|
105
|
-
register_fn: F,
|
|
106
|
-
) where
|
|
107
|
-
F: Fn(&mut HttpLifecycleHooks, Arc<dyn LifecycleHook<Request<Body>, Response<Body>>>),
|
|
108
|
-
{
|
|
109
|
-
for hook in hook_list {
|
|
110
|
-
register_fn(hooks, hook);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
#[cfg(test)]
|
|
116
|
-
mod tests {
|
|
117
|
-
use super::*;
|
|
118
|
-
|
|
119
|
-
#[test]
|
|
120
|
-
fn test_error_messages() {
|
|
121
|
-
let call_err = error::call_failed("test_hook", "test reason");
|
|
122
|
-
assert!(call_err.contains("test_hook"));
|
|
123
|
-
assert!(call_err.contains("test reason"));
|
|
124
|
-
|
|
125
|
-
let task_err = error::task_error("task_hook", "spawn failed");
|
|
126
|
-
assert!(task_err.contains("task_hook"));
|
|
127
|
-
|
|
128
|
-
let promise_err = error::promise_failed("promise_hook", "rejected");
|
|
129
|
-
assert!(promise_err.contains("promise_hook"));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
#[test]
|
|
133
|
-
fn test_body_error_messages() {
|
|
134
|
-
let read_err = error::body_read_failed("request", "stream closed");
|
|
135
|
-
assert!(read_err.contains("request"));
|
|
136
|
-
|
|
137
|
-
let write_err = error::body_write_failed("allocation failed");
|
|
138
|
-
assert!(write_err.contains("allocation"));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
#[test]
|
|
142
|
-
fn test_json_error_messages() {
|
|
143
|
-
let ser_err = error::serialize_failed("request body", "invalid type");
|
|
144
|
-
assert!(ser_err.contains("request body"));
|
|
145
|
-
|
|
146
|
-
let deser_err = error::deserialize_failed("response", "malformed");
|
|
147
|
-
assert!(deser_err.contains("response"));
|
|
148
|
-
}
|
|
149
|
-
}
|