spikard 0.8.3 → 0.10.1
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/README.md +19 -10
- data/ext/spikard_rb/Cargo.lock +234 -162
- data/ext/spikard_rb/Cargo.toml +3 -3
- data/ext/spikard_rb/extconf.rb +4 -3
- data/lib/spikard/config.rb +88 -12
- data/lib/spikard/testing.rb +3 -1
- data/lib/spikard/version.rb +1 -1
- data/lib/spikard.rb +11 -0
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +3 -6
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +8 -8
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +2 -2
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +4 -4
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +10 -4
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +3 -3
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +10 -5
- data/vendor/crates/spikard-bindings-shared/src/json_conversion.rs +829 -0
- data/vendor/crates/spikard-bindings-shared/src/lazy_cache.rs +587 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +7 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +11 -11
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +9 -37
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +436 -3
- data/vendor/crates/spikard-bindings-shared/src/response_interpreter.rs +944 -0
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +4 -4
- data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +3 -2
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +13 -13
- data/vendor/crates/spikard-bindings-shared/tests/{comprehensive_coverage.rs → full_coverage.rs} +10 -5
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +14 -14
- data/vendor/crates/spikard-bindings-shared/tests/integration_tests.rs +669 -0
- data/vendor/crates/spikard-core/Cargo.toml +3 -3
- data/vendor/crates/spikard-core/src/di/container.rs +1 -1
- data/vendor/crates/spikard-core/src/di/factory.rs +2 -2
- data/vendor/crates/spikard-core/src/di/resolved.rs +2 -2
- data/vendor/crates/spikard-core/src/di/value.rs +1 -1
- data/vendor/crates/spikard-core/src/http.rs +75 -0
- data/vendor/crates/spikard-core/src/lifecycle.rs +43 -43
- data/vendor/crates/spikard-core/src/parameters.rs +14 -19
- data/vendor/crates/spikard-core/src/problem.rs +1 -1
- data/vendor/crates/spikard-core/src/request_data.rs +7 -16
- data/vendor/crates/spikard-core/src/router.rs +6 -0
- data/vendor/crates/spikard-core/src/schema_registry.rs +2 -3
- data/vendor/crates/spikard-core/src/type_hints.rs +3 -2
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +1 -1
- data/vendor/crates/spikard-core/src/validation/mod.rs +1 -1
- data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +1 -1
- data/vendor/crates/spikard-core/tests/error_mapper.rs +2 -2
- data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +1 -1
- data/vendor/crates/spikard-core/tests/parameters_full.rs +1 -1
- data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +1 -1
- data/vendor/crates/spikard-core/tests/validation_coverage.rs +4 -4
- data/vendor/crates/spikard-http/Cargo.toml +4 -2
- data/vendor/crates/spikard-http/src/cors.rs +32 -11
- data/vendor/crates/spikard-http/src/di_handler.rs +12 -8
- data/vendor/crates/spikard-http/src/grpc/framing.rs +469 -0
- data/vendor/crates/spikard-http/src/grpc/handler.rs +887 -25
- data/vendor/crates/spikard-http/src/grpc/mod.rs +114 -22
- data/vendor/crates/spikard-http/src/grpc/service.rs +232 -2
- data/vendor/crates/spikard-http/src/grpc/streaming.rs +80 -2
- data/vendor/crates/spikard-http/src/handler_trait.rs +204 -27
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +15 -15
- data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +2 -2
- data/vendor/crates/spikard-http/src/jsonrpc/router.rs +2 -2
- data/vendor/crates/spikard-http/src/lib.rs +1 -1
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +2 -2
- data/vendor/crates/spikard-http/src/lifecycle.rs +4 -4
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +2 -0
- data/vendor/crates/spikard-http/src/server/fast_router.rs +186 -0
- data/vendor/crates/spikard-http/src/server/grpc_routing.rs +324 -23
- data/vendor/crates/spikard-http/src/server/handler.rs +33 -22
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +21 -2
- data/vendor/crates/spikard-http/src/server/mod.rs +125 -20
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +126 -44
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +80 -69
- data/vendor/crates/spikard-http/tests/common/handlers.rs +2 -2
- data/vendor/crates/spikard-http/tests/common/test_builders.rs +12 -12
- data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +2 -2
- data/vendor/crates/spikard-http/tests/di_integration.rs +6 -6
- data/vendor/crates/spikard-http/tests/grpc_bidirectional_streaming.rs +430 -0
- data/vendor/crates/spikard-http/tests/grpc_client_streaming.rs +738 -0
- data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +13 -9
- data/vendor/crates/spikard-http/tests/grpc_server_streaming.rs +974 -0
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +2 -2
- data/vendor/crates/spikard-http/tests/request_extraction_full.rs +4 -4
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +2 -2
- data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +1 -0
- data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +140 -0
- data/vendor/crates/spikard-rb/Cargo.toml +3 -1
- data/vendor/crates/spikard-rb/src/conversion.rs +138 -4
- data/vendor/crates/spikard-rb/src/grpc/handler.rs +706 -229
- data/vendor/crates/spikard-rb/src/grpc/mod.rs +6 -2
- data/vendor/crates/spikard-rb/src/gvl.rs +2 -2
- data/vendor/crates/spikard-rb/src/handler.rs +169 -91
- data/vendor/crates/spikard-rb/src/lib.rs +444 -62
- data/vendor/crates/spikard-rb/src/lifecycle.rs +29 -1
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +108 -43
- data/vendor/crates/spikard-rb/src/request.rs +117 -20
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +52 -25
- data/vendor/crates/spikard-rb/src/server.rs +23 -14
- data/vendor/crates/spikard-rb/src/testing/client.rs +5 -4
- data/vendor/crates/spikard-rb/src/testing/sse.rs +1 -36
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +3 -38
- data/vendor/crates/spikard-rb/src/websocket.rs +32 -23
- data/vendor/crates/spikard-rb-macros/Cargo.toml +1 -1
- metadata +14 -4
- data/vendor/bundle/ruby/3.4.0/gems/diff-lcs-1.6.2/mise.toml +0 -5
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.10.0/build/buildkitd.toml +0 -2
|
@@ -5,14 +5,52 @@
|
|
|
5
5
|
|
|
6
6
|
use axum::{
|
|
7
7
|
body::Body,
|
|
8
|
-
http::{Request, Response, StatusCode},
|
|
8
|
+
http::{HeaderValue, Request, Response, StatusCode, header::HeaderName},
|
|
9
9
|
};
|
|
10
|
+
use bytes::Bytes;
|
|
10
11
|
use serde::{Deserialize, Serialize};
|
|
11
12
|
use serde_json::Value;
|
|
12
13
|
use std::collections::HashMap;
|
|
13
14
|
use std::future::Future;
|
|
14
15
|
use std::pin::Pin;
|
|
15
16
|
|
|
17
|
+
/// Pre-built response for routes that always return the same content.
|
|
18
|
+
///
|
|
19
|
+
/// When a route is registered with a `StaticResponse`, the server bypasses
|
|
20
|
+
/// handler execution, validation, lifecycle hooks, and request extraction —
|
|
21
|
+
/// returning the cached response directly. This is ideal for health-check
|
|
22
|
+
/// endpoints or any route whose output never changes at runtime.
|
|
23
|
+
#[derive(Clone, Debug)]
|
|
24
|
+
pub struct StaticResponse {
|
|
25
|
+
pub status: u16,
|
|
26
|
+
pub headers: Vec<(HeaderName, HeaderValue)>,
|
|
27
|
+
pub body: Bytes,
|
|
28
|
+
pub content_type: HeaderValue,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl StaticResponse {
|
|
32
|
+
/// Build an `axum::response::Response` from this static response.
|
|
33
|
+
///
|
|
34
|
+
/// This is the single canonical path for constructing HTTP responses from
|
|
35
|
+
/// static data — used by the FastRouter middleware, the Axum fallback
|
|
36
|
+
/// handler, and the `StaticResponseHandler::call` fallback.
|
|
37
|
+
pub fn to_response(&self) -> Response<Body> {
|
|
38
|
+
let status = StatusCode::from_u16(self.status).unwrap_or(StatusCode::OK);
|
|
39
|
+
let mut builder = Response::builder()
|
|
40
|
+
.status(status)
|
|
41
|
+
.header(axum::http::header::CONTENT_TYPE, self.content_type.clone());
|
|
42
|
+
for (name, value) in &self.headers {
|
|
43
|
+
builder = builder.header(name.clone(), value.clone());
|
|
44
|
+
}
|
|
45
|
+
builder.body(Body::from(self.body.clone())).unwrap_or_else(|_| {
|
|
46
|
+
Response::builder()
|
|
47
|
+
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
48
|
+
.body(Body::from("Failed to build static response"))
|
|
49
|
+
.expect("fallback response must build")
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
16
54
|
/// Request data extracted from HTTP request
|
|
17
55
|
/// This is the language-agnostic representation passed to handlers
|
|
18
56
|
///
|
|
@@ -25,11 +63,11 @@ use std::pin::Pin;
|
|
|
25
63
|
#[derive(Debug, Clone)]
|
|
26
64
|
pub struct RequestData {
|
|
27
65
|
pub path_params: std::sync::Arc<HashMap<String, String>>,
|
|
28
|
-
pub query_params: Value
|
|
66
|
+
pub query_params: std::sync::Arc<Value>,
|
|
29
67
|
/// Validated parameters produced by ParameterValidator (query/path/header/cookie combined).
|
|
30
|
-
pub validated_params: Option<Value
|
|
68
|
+
pub validated_params: Option<std::sync::Arc<Value>>,
|
|
31
69
|
pub raw_query_params: std::sync::Arc<HashMap<String, Vec<String>>>,
|
|
32
|
-
pub body: Value
|
|
70
|
+
pub body: std::sync::Arc<Value>,
|
|
33
71
|
pub raw_body: Option<bytes::Bytes>,
|
|
34
72
|
pub headers: std::sync::Arc<HashMap<String, String>>,
|
|
35
73
|
pub cookies: std::sync::Arc<HashMap<String, String>>,
|
|
@@ -53,10 +91,10 @@ impl Serialize for RequestData {
|
|
|
53
91
|
|
|
54
92
|
let mut state = serializer.serialize_struct("RequestData", field_count)?;
|
|
55
93
|
state.serialize_field("path_params", &*self.path_params)?;
|
|
56
|
-
state.serialize_field("query_params",
|
|
57
|
-
state.serialize_field("validated_params", &self.validated_params)?;
|
|
94
|
+
state.serialize_field("query_params", &*self.query_params)?;
|
|
95
|
+
state.serialize_field("validated_params", &self.validated_params.as_deref())?;
|
|
58
96
|
state.serialize_field("raw_query_params", &*self.raw_query_params)?;
|
|
59
|
-
state.serialize_field("body",
|
|
97
|
+
state.serialize_field("body", &*self.body)?;
|
|
60
98
|
state.serialize_field("raw_body", &self.raw_body.as_ref().map(|b| b.as_ref()))?;
|
|
61
99
|
state.serialize_field("headers", &*self.headers)?;
|
|
62
100
|
state.serialize_field("cookies", &*self.cookies)?;
|
|
@@ -124,16 +162,16 @@ impl<'de> Deserialize<'de> for RequestData {
|
|
|
124
162
|
path_params = Some(std::sync::Arc::new(map.next_value()?));
|
|
125
163
|
}
|
|
126
164
|
Field::QueryParams => {
|
|
127
|
-
query_params = Some(map.next_value()?);
|
|
165
|
+
query_params = Some(std::sync::Arc::new(map.next_value()?));
|
|
128
166
|
}
|
|
129
167
|
Field::RawQueryParams => {
|
|
130
168
|
raw_query_params = Some(std::sync::Arc::new(map.next_value()?));
|
|
131
169
|
}
|
|
132
170
|
Field::ValidatedParams => {
|
|
133
|
-
validated_params = Some(map.next_value()?);
|
|
171
|
+
validated_params = Some(std::sync::Arc::new(map.next_value()?));
|
|
134
172
|
}
|
|
135
173
|
Field::Body => {
|
|
136
|
-
body = Some(map.next_value()?);
|
|
174
|
+
body = Some(std::sync::Arc::new(map.next_value()?));
|
|
137
175
|
}
|
|
138
176
|
Field::RawBody => {
|
|
139
177
|
let bytes_vec: Option<Vec<u8>> = map.next_value()?;
|
|
@@ -269,6 +307,69 @@ pub trait Handler: Send + Sync {
|
|
|
269
307
|
fn wants_request_extensions(&self) -> bool {
|
|
270
308
|
false
|
|
271
309
|
}
|
|
310
|
+
|
|
311
|
+
/// Return a pre-built static response if this handler always produces the
|
|
312
|
+
/// same output. When `Some`, the server bypasses the full middleware
|
|
313
|
+
/// pipeline and serves the pre-built response directly.
|
|
314
|
+
fn static_response(&self) -> Option<StaticResponse> {
|
|
315
|
+
None
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// A no-op handler that declares a static response.
|
|
320
|
+
///
|
|
321
|
+
/// Language bindings create this handler when a route is registered with
|
|
322
|
+
/// `static_response` configuration. The handler's `call()` method is never
|
|
323
|
+
/// invoked — the server uses the `static_response()` return value instead.
|
|
324
|
+
pub struct StaticResponseHandler {
|
|
325
|
+
response: StaticResponse,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
impl StaticResponseHandler {
|
|
329
|
+
/// Create a new static response handler.
|
|
330
|
+
pub fn new(response: StaticResponse) -> Self {
|
|
331
|
+
Self { response }
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/// Build a `StaticResponse` from common parameters.
|
|
335
|
+
///
|
|
336
|
+
/// Convenience constructor for language bindings that pass status, body,
|
|
337
|
+
/// content-type, and optional extra headers.
|
|
338
|
+
pub fn from_parts(
|
|
339
|
+
status: u16,
|
|
340
|
+
body: impl Into<Bytes>,
|
|
341
|
+
content_type: Option<&str>,
|
|
342
|
+
extra_headers: Vec<(HeaderName, HeaderValue)>,
|
|
343
|
+
) -> Self {
|
|
344
|
+
let ct = content_type
|
|
345
|
+
.and_then(|s| HeaderValue::from_str(s).ok())
|
|
346
|
+
.unwrap_or_else(|| HeaderValue::from_static("text/plain; charset=utf-8"));
|
|
347
|
+
Self {
|
|
348
|
+
response: StaticResponse {
|
|
349
|
+
status,
|
|
350
|
+
headers: extra_headers,
|
|
351
|
+
body: body.into(),
|
|
352
|
+
content_type: ct,
|
|
353
|
+
},
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
impl Handler for StaticResponseHandler {
|
|
359
|
+
fn call(
|
|
360
|
+
&self,
|
|
361
|
+
_request: Request<Body>,
|
|
362
|
+
_request_data: RequestData,
|
|
363
|
+
) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
|
|
364
|
+
// This should never be called — the server fast-path intercepts first.
|
|
365
|
+
// Provide a working fallback just in case.
|
|
366
|
+
let resp = self.response.to_response();
|
|
367
|
+
Box::pin(async move { Ok(resp) })
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
fn static_response(&self) -> Option<StaticResponse> {
|
|
371
|
+
Some(self.response.clone())
|
|
372
|
+
}
|
|
272
373
|
}
|
|
273
374
|
|
|
274
375
|
/// Validated parameters from request (path, query, headers, cookies)
|
|
@@ -285,10 +386,10 @@ mod tests {
|
|
|
285
386
|
fn minimal_request_data() -> RequestData {
|
|
286
387
|
RequestData {
|
|
287
388
|
path_params: std::sync::Arc::new(HashMap::new()),
|
|
288
|
-
query_params: Value::Object(serde_json::Map::new()),
|
|
389
|
+
query_params: std::sync::Arc::new(Value::Object(serde_json::Map::new())),
|
|
289
390
|
validated_params: None,
|
|
290
391
|
raw_query_params: std::sync::Arc::new(HashMap::new()),
|
|
291
|
-
body: Value::Null,
|
|
392
|
+
body: std::sync::Arc::new(Value::Null),
|
|
292
393
|
raw_body: None,
|
|
293
394
|
headers: std::sync::Arc::new(HashMap::new()),
|
|
294
395
|
cookies: std::sync::Arc::new(HashMap::new()),
|
|
@@ -341,7 +442,7 @@ mod tests {
|
|
|
341
442
|
});
|
|
342
443
|
|
|
343
444
|
let data = RequestData {
|
|
344
|
-
query_params,
|
|
445
|
+
query_params: std::sync::Arc::new(query_params),
|
|
345
446
|
..minimal_request_data()
|
|
346
447
|
};
|
|
347
448
|
|
|
@@ -416,7 +517,7 @@ mod tests {
|
|
|
416
517
|
});
|
|
417
518
|
|
|
418
519
|
let data = RequestData {
|
|
419
|
-
body,
|
|
520
|
+
body: std::sync::Arc::new(body),
|
|
420
521
|
..minimal_request_data()
|
|
421
522
|
};
|
|
422
523
|
|
|
@@ -481,7 +582,7 @@ mod tests {
|
|
|
481
582
|
});
|
|
482
583
|
|
|
483
584
|
let data = RequestData {
|
|
484
|
-
body,
|
|
585
|
+
body: std::sync::Arc::new(body),
|
|
485
586
|
..minimal_request_data()
|
|
486
587
|
};
|
|
487
588
|
|
|
@@ -512,10 +613,10 @@ mod tests {
|
|
|
512
613
|
|
|
513
614
|
let data = RequestData {
|
|
514
615
|
path_params: std::sync::Arc::new(path_params),
|
|
515
|
-
query_params: serde_json::json!({"page": 1}),
|
|
616
|
+
query_params: std::sync::Arc::new(serde_json::json!({"page": 1})),
|
|
516
617
|
validated_params: None,
|
|
517
618
|
raw_query_params: std::sync::Arc::new(raw_query_params),
|
|
518
|
-
body,
|
|
619
|
+
body: std::sync::Arc::new(body),
|
|
519
620
|
raw_body: Some(raw_body),
|
|
520
621
|
headers: std::sync::Arc::new(headers),
|
|
521
622
|
cookies: std::sync::Arc::new(cookies),
|
|
@@ -642,14 +743,14 @@ mod tests {
|
|
|
642
743
|
map.insert("id".to_string(), "999".to_string());
|
|
643
744
|
map
|
|
644
745
|
}),
|
|
645
|
-
query_params: serde_json::json!({"limit": 50, "offset": 10}),
|
|
746
|
+
query_params: std::sync::Arc::new(serde_json::json!({"limit": 50, "offset": 10})),
|
|
646
747
|
validated_params: None,
|
|
647
748
|
raw_query_params: std::sync::Arc::new({
|
|
648
749
|
let mut map = HashMap::new();
|
|
649
750
|
map.insert("sort".to_string(), vec!["name".to_string(), "date".to_string()]);
|
|
650
751
|
map
|
|
651
752
|
}),
|
|
652
|
-
body: serde_json::json!({"title": "New Post", "content": "Hello World"}),
|
|
753
|
+
body: std::sync::Arc::new(serde_json::json!({"title": "New Post", "content": "Hello World"})),
|
|
653
754
|
raw_body: None,
|
|
654
755
|
headers: std::sync::Arc::new({
|
|
655
756
|
let mut map = HashMap::new();
|
|
@@ -689,7 +790,7 @@ mod tests {
|
|
|
689
790
|
}
|
|
690
791
|
|
|
691
792
|
let data = RequestData {
|
|
692
|
-
body: Value::Object(large_object),
|
|
793
|
+
body: std::sync::Arc::new(Value::Object(large_object)),
|
|
693
794
|
..minimal_request_data()
|
|
694
795
|
};
|
|
695
796
|
|
|
@@ -704,10 +805,10 @@ mod tests {
|
|
|
704
805
|
fn test_request_data_empty_collections() {
|
|
705
806
|
let data = RequestData {
|
|
706
807
|
path_params: std::sync::Arc::new(HashMap::new()),
|
|
707
|
-
query_params: Value::Object(serde_json::Map::new()),
|
|
808
|
+
query_params: std::sync::Arc::new(Value::Object(serde_json::Map::new())),
|
|
708
809
|
validated_params: None,
|
|
709
810
|
raw_query_params: std::sync::Arc::new(HashMap::new()),
|
|
710
|
-
body: Value::Object(serde_json::Map::new()),
|
|
811
|
+
body: std::sync::Arc::new(Value::Object(serde_json::Map::new())),
|
|
711
812
|
raw_body: None,
|
|
712
813
|
headers: std::sync::Arc::new(HashMap::new()),
|
|
713
814
|
cookies: std::sync::Arc::new(HashMap::new()),
|
|
@@ -738,7 +839,7 @@ mod tests {
|
|
|
738
839
|
method: "POST".to_string(),
|
|
739
840
|
path: "/api/v1/users\\test".to_string(),
|
|
740
841
|
headers: std::sync::Arc::new(headers),
|
|
741
|
-
body: serde_json::json!({"note": "Contains\nnewline"}),
|
|
842
|
+
body: std::sync::Arc::new(serde_json::json!({"note": "Contains\nnewline"})),
|
|
742
843
|
..minimal_request_data()
|
|
743
844
|
};
|
|
744
845
|
|
|
@@ -779,7 +880,7 @@ mod tests {
|
|
|
779
880
|
#[test]
|
|
780
881
|
fn test_request_data_serialization_null_body() {
|
|
781
882
|
let data = RequestData {
|
|
782
|
-
body: Value::Null,
|
|
883
|
+
body: std::sync::Arc::new(Value::Null),
|
|
783
884
|
..minimal_request_data()
|
|
784
885
|
};
|
|
785
886
|
|
|
@@ -791,7 +892,7 @@ mod tests {
|
|
|
791
892
|
#[test]
|
|
792
893
|
fn test_request_data_serialization_array_body() {
|
|
793
894
|
let data = RequestData {
|
|
794
|
-
body: serde_json::json!([1, 2, 3, "four", {"five": 5}]),
|
|
895
|
+
body: std::sync::Arc::new(serde_json::json!([1, 2, 3, "four", {"five": 5}])),
|
|
795
896
|
..minimal_request_data()
|
|
796
897
|
};
|
|
797
898
|
|
|
@@ -807,12 +908,12 @@ mod tests {
|
|
|
807
908
|
#[test]
|
|
808
909
|
fn test_request_data_serialization_numeric_edge_cases() {
|
|
809
910
|
let data = RequestData {
|
|
810
|
-
body: serde_json::json!({
|
|
911
|
+
body: std::sync::Arc::new(serde_json::json!({
|
|
811
912
|
"zero": 0,
|
|
812
913
|
"negative": -42,
|
|
813
914
|
"large": 9223372036854775807i64,
|
|
814
915
|
"float": 3.14159
|
|
815
|
-
}),
|
|
916
|
+
})),
|
|
816
917
|
..minimal_request_data()
|
|
817
918
|
};
|
|
818
919
|
|
|
@@ -835,4 +936,80 @@ mod tests {
|
|
|
835
936
|
assert_eq!(validated.params.get("id").unwrap(), &Value::String("123".to_string()));
|
|
836
937
|
assert_eq!(validated.params.get("active").unwrap(), &Value::Bool(true));
|
|
837
938
|
}
|
|
939
|
+
|
|
940
|
+
#[test]
|
|
941
|
+
fn test_static_response_handler_new() {
|
|
942
|
+
let sr = StaticResponse {
|
|
943
|
+
status: 200,
|
|
944
|
+
headers: vec![],
|
|
945
|
+
body: Bytes::from("OK"),
|
|
946
|
+
content_type: HeaderValue::from_static("text/plain"),
|
|
947
|
+
};
|
|
948
|
+
let handler = StaticResponseHandler::new(sr);
|
|
949
|
+
let resp = handler.static_response();
|
|
950
|
+
assert!(resp.is_some());
|
|
951
|
+
let resp = resp.unwrap();
|
|
952
|
+
assert_eq!(resp.status, 200);
|
|
953
|
+
assert_eq!(resp.body.as_ref(), b"OK");
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
#[test]
|
|
957
|
+
fn test_static_response_handler_from_parts_defaults() {
|
|
958
|
+
let handler = StaticResponseHandler::from_parts(204, "No Content", None, vec![]);
|
|
959
|
+
let resp = handler.static_response().unwrap();
|
|
960
|
+
assert_eq!(resp.status, 204);
|
|
961
|
+
assert_eq!(resp.body.as_ref(), b"No Content");
|
|
962
|
+
assert_eq!(resp.content_type, "text/plain; charset=utf-8");
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
#[test]
|
|
966
|
+
fn test_static_response_handler_from_parts_custom_content_type() {
|
|
967
|
+
let handler = StaticResponseHandler::from_parts(200, r#"{"ok":true}"#, Some("application/json"), vec![]);
|
|
968
|
+
let resp = handler.static_response().unwrap();
|
|
969
|
+
assert_eq!(resp.content_type, "application/json");
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
#[test]
|
|
973
|
+
fn test_static_response_handler_from_parts_extra_headers() {
|
|
974
|
+
let handler = StaticResponseHandler::from_parts(
|
|
975
|
+
200,
|
|
976
|
+
"OK",
|
|
977
|
+
None,
|
|
978
|
+
vec![(HeaderName::from_static("x-custom"), HeaderValue::from_static("value"))],
|
|
979
|
+
);
|
|
980
|
+
let resp = handler.static_response().unwrap();
|
|
981
|
+
assert_eq!(resp.headers.len(), 1);
|
|
982
|
+
assert_eq!(resp.headers[0].0, "x-custom");
|
|
983
|
+
assert_eq!(resp.headers[0].1, "value");
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
#[tokio::test]
|
|
987
|
+
async fn test_static_response_handler_call_fallback() {
|
|
988
|
+
use http_body_util::BodyExt;
|
|
989
|
+
|
|
990
|
+
let handler = StaticResponseHandler::from_parts(201, "created", Some("text/plain"), vec![]);
|
|
991
|
+
let request = Request::builder().body(Body::empty()).unwrap();
|
|
992
|
+
let result = handler.call(request, minimal_request_data()).await;
|
|
993
|
+
assert!(result.is_ok());
|
|
994
|
+
let response = result.unwrap();
|
|
995
|
+
assert_eq!(response.status(), StatusCode::CREATED);
|
|
996
|
+
assert_eq!(response.headers().get("content-type").unwrap(), "text/plain");
|
|
997
|
+
let body = response.into_body().collect().await.unwrap().to_bytes();
|
|
998
|
+
assert_eq!(body.as_ref(), b"created");
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
#[test]
|
|
1002
|
+
fn test_default_handler_static_response_is_none() {
|
|
1003
|
+
struct DummyHandler;
|
|
1004
|
+
impl Handler for DummyHandler {
|
|
1005
|
+
fn call(
|
|
1006
|
+
&self,
|
|
1007
|
+
_: Request<Body>,
|
|
1008
|
+
_: RequestData,
|
|
1009
|
+
) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
|
|
1010
|
+
Box::pin(async { Err((StatusCode::OK, String::new())) })
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
assert!(DummyHandler.static_response().is_none());
|
|
1014
|
+
}
|
|
838
1015
|
}
|
|
@@ -99,10 +99,10 @@ mod tests {
|
|
|
99
99
|
|
|
100
100
|
let request_data = RequestData {
|
|
101
101
|
path_params: Arc::new(path_params),
|
|
102
|
-
query_params: json!({"page": 1}),
|
|
102
|
+
query_params: Arc::new(json!({"page": 1})),
|
|
103
103
|
validated_params: None,
|
|
104
104
|
raw_query_params: Arc::new(HashMap::new()),
|
|
105
|
-
body: json!({"test": "data"}),
|
|
105
|
+
body: Arc::new(json!({"test": "data"})),
|
|
106
106
|
raw_body: None,
|
|
107
107
|
headers: Arc::new(headers),
|
|
108
108
|
cookies: Arc::new(HashMap::new()),
|
|
@@ -131,10 +131,10 @@ mod tests {
|
|
|
131
131
|
|
|
132
132
|
let request_data = RequestData {
|
|
133
133
|
path_params: Arc::new(HashMap::new()),
|
|
134
|
-
query_params: Value::Null,
|
|
134
|
+
query_params: Arc::new(Value::Null),
|
|
135
135
|
validated_params: None,
|
|
136
136
|
raw_query_params: Arc::new(HashMap::new()),
|
|
137
|
-
body: Value::Null,
|
|
137
|
+
body: Arc::new(Value::Null),
|
|
138
138
|
raw_body: None,
|
|
139
139
|
headers: Arc::new(HashMap::new()),
|
|
140
140
|
cookies: Arc::new(HashMap::new()),
|
|
@@ -170,10 +170,10 @@ mod tests {
|
|
|
170
170
|
|
|
171
171
|
let request_data = RequestData {
|
|
172
172
|
path_params: Arc::new(HashMap::new()),
|
|
173
|
-
query_params: json!({"api_key": "secret123"}),
|
|
173
|
+
query_params: Arc::new(json!({"api_key": "secret123"})),
|
|
174
174
|
validated_params: None,
|
|
175
175
|
raw_query_params: Arc::new(raw_query_params.clone()),
|
|
176
|
-
body: Value::Null,
|
|
176
|
+
body: Arc::new(Value::Null),
|
|
177
177
|
raw_body: None,
|
|
178
178
|
headers: Arc::new(HashMap::new()),
|
|
179
179
|
cookies: Arc::new(HashMap::new()),
|
|
@@ -194,10 +194,10 @@ mod tests {
|
|
|
194
194
|
|
|
195
195
|
let request_data_no_param = RequestData {
|
|
196
196
|
path_params: Arc::new(HashMap::new()),
|
|
197
|
-
query_params: Value::Null,
|
|
197
|
+
query_params: Arc::new(Value::Null),
|
|
198
198
|
validated_params: None,
|
|
199
199
|
raw_query_params: Arc::new(HashMap::new()),
|
|
200
|
-
body: Value::Null,
|
|
200
|
+
body: Arc::new(Value::Null),
|
|
201
201
|
raw_body: None,
|
|
202
202
|
headers: Arc::new(HashMap::new()),
|
|
203
203
|
cookies: Arc::new(HashMap::new()),
|
|
@@ -232,10 +232,10 @@ mod tests {
|
|
|
232
232
|
|
|
233
233
|
let request_data = RequestData {
|
|
234
234
|
path_params: Arc::new(path_params),
|
|
235
|
-
query_params: json!({"filter": "active"}),
|
|
235
|
+
query_params: Arc::new(json!({"filter": "active"})),
|
|
236
236
|
validated_params: None,
|
|
237
237
|
raw_query_params: Arc::new(raw_query_params),
|
|
238
|
-
body: json!({"name": "test"}),
|
|
238
|
+
body: Arc::new(json!({"name": "test"})),
|
|
239
239
|
raw_body: None,
|
|
240
240
|
headers: Arc::new(HashMap::new()),
|
|
241
241
|
cookies: Arc::new(HashMap::new()),
|
|
@@ -251,17 +251,17 @@ mod tests {
|
|
|
251
251
|
assert_eq!(deserialized.method, "PUT");
|
|
252
252
|
assert_eq!(deserialized.path, "/users/42");
|
|
253
253
|
assert_eq!(deserialized.path_params.get("user_id").unwrap(), "42");
|
|
254
|
-
assert_eq!(deserialized.body, json!({"name": "test"}));
|
|
254
|
+
assert_eq!(*deserialized.body, json!({"name": "test"}));
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
#[test]
|
|
258
258
|
fn test_request_data_default_values() {
|
|
259
259
|
let request_data = RequestData {
|
|
260
260
|
path_params: Arc::new(HashMap::new()),
|
|
261
|
-
query_params: Value::Null,
|
|
261
|
+
query_params: Arc::new(Value::Null),
|
|
262
262
|
validated_params: None,
|
|
263
263
|
raw_query_params: Arc::new(HashMap::new()),
|
|
264
|
-
body: Value::Null,
|
|
264
|
+
body: Arc::new(Value::Null),
|
|
265
265
|
raw_body: None,
|
|
266
266
|
headers: Arc::new(HashMap::new()),
|
|
267
267
|
cookies: Arc::new(HashMap::new()),
|
|
@@ -278,8 +278,8 @@ mod tests {
|
|
|
278
278
|
assert!(request_data.raw_query_params.is_empty());
|
|
279
279
|
assert!(request_data.headers.is_empty());
|
|
280
280
|
assert!(request_data.cookies.is_empty());
|
|
281
|
-
assert_eq!(request_data.body, Value::Null);
|
|
282
|
-
assert_eq!(request_data.query_params, Value::Null);
|
|
281
|
+
assert_eq!(*request_data.body, Value::Null);
|
|
282
|
+
assert_eq!(*request_data.query_params, Value::Null);
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
#[test]
|
|
@@ -159,10 +159,10 @@ pub async fn handle_jsonrpc(
|
|
|
159
159
|
fn create_jsonrpc_request_data(headers: &HeaderMap, uri: &axum::http::Uri) -> RequestData {
|
|
160
160
|
RequestData {
|
|
161
161
|
path_params: Arc::new(HashMap::new()),
|
|
162
|
-
query_params: serde_json::json!({}),
|
|
162
|
+
query_params: Arc::new(serde_json::json!({})),
|
|
163
163
|
validated_params: None,
|
|
164
164
|
raw_query_params: Arc::new(HashMap::new()),
|
|
165
|
-
body: serde_json::json!({}),
|
|
165
|
+
body: Arc::new(serde_json::json!({})),
|
|
166
166
|
raw_body: None,
|
|
167
167
|
headers: Arc::new(extract_headers(headers)),
|
|
168
168
|
cookies: Arc::new(HashMap::new()),
|
|
@@ -348,10 +348,10 @@ mod tests {
|
|
|
348
348
|
fn create_test_request_data() -> RequestData {
|
|
349
349
|
RequestData {
|
|
350
350
|
path_params: Arc::new(HashMap::new()),
|
|
351
|
-
query_params: Value::Object(serde_json::Map::new()),
|
|
351
|
+
query_params: Arc::new(Value::Object(serde_json::Map::new())),
|
|
352
352
|
validated_params: None,
|
|
353
353
|
raw_query_params: Arc::new(HashMap::new()),
|
|
354
|
-
body: Value::Null,
|
|
354
|
+
body: Arc::new(Value::Null),
|
|
355
355
|
raw_body: None,
|
|
356
356
|
headers: Arc::new(HashMap::new()),
|
|
357
357
|
cookies: Arc::new(HashMap::new()),
|
|
@@ -45,7 +45,7 @@ pub use grpc::{
|
|
|
45
45
|
StreamingRequest, StreamingResponse,
|
|
46
46
|
};
|
|
47
47
|
pub use handler_response::HandlerResponse;
|
|
48
|
-
pub use handler_trait::{Handler, HandlerResult, RequestData, ValidatedParams};
|
|
48
|
+
pub use handler_trait::{Handler, HandlerResult, RequestData, StaticResponse, StaticResponseHandler, ValidatedParams};
|
|
49
49
|
pub use jsonrpc::JsonRpcConfig;
|
|
50
50
|
pub use lifecycle::{HookResult, LifecycleHook, LifecycleHooks, LifecycleHooksBuilder, request_hook, response_hook};
|
|
51
51
|
pub use openapi::{ContactInfo, LicenseInfo, OpenApiConfig, SecuritySchemeInfo, ServerInfo};
|
|
@@ -183,7 +183,7 @@ mod tests {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
fn execute_request<'a>(
|
|
186
|
-
&
|
|
186
|
+
&self,
|
|
187
187
|
req: Request<Body>,
|
|
188
188
|
) -> Pin<Box<dyn Future<Output = Result<HookResult<Request<Body>, Response<Body>>, String>> + Send + 'a>>
|
|
189
189
|
{
|
|
@@ -191,7 +191,7 @@ mod tests {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
fn execute_response<'a>(
|
|
194
|
-
&
|
|
194
|
+
&self,
|
|
195
195
|
resp: Response<Body>,
|
|
196
196
|
) -> Pin<Box<dyn Future<Output = Result<HookResult<Response<Body>, Response<Body>>, String>> + Send + 'a>>
|
|
197
197
|
{
|
|
@@ -70,7 +70,7 @@ mod tests {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
fn execute_request<'a>(
|
|
73
|
-
&
|
|
73
|
+
&self,
|
|
74
74
|
req: Request<Body>,
|
|
75
75
|
) -> Pin<Box<dyn Future<Output = Result<HookResult<Request<Body>, Response<Body>>, String>> + Send + 'a>>
|
|
76
76
|
{
|
|
@@ -78,7 +78,7 @@ mod tests {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
fn execute_response<'a>(
|
|
81
|
-
&
|
|
81
|
+
&self,
|
|
82
82
|
resp: Response<Body>,
|
|
83
83
|
) -> Pin<Box<dyn Future<Output = Result<HookResult<Response<Body>, Response<Body>>, String>> + Send + 'a>>
|
|
84
84
|
{
|
|
@@ -97,7 +97,7 @@ mod tests {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
fn execute_request<'a>(
|
|
100
|
-
&
|
|
100
|
+
&self,
|
|
101
101
|
_req: Request<Body>,
|
|
102
102
|
) -> Pin<Box<dyn Future<Output = Result<HookResult<Request<Body>, Response<Body>>, String>> + Send + 'a>>
|
|
103
103
|
{
|
|
@@ -111,7 +111,7 @@ mod tests {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
fn execute_response<'a>(
|
|
114
|
-
&
|
|
114
|
+
&self,
|
|
115
115
|
_resp: Response<Body>,
|
|
116
116
|
) -> Pin<Box<dyn Future<Output = Result<HookResult<Response<Body>, Response<Body>>, String>> + Send + 'a>>
|
|
117
117
|
{
|
|
@@ -188,6 +188,7 @@ mod tests {
|
|
|
188
188
|
#[cfg(feature = "di")]
|
|
189
189
|
handler_dependencies: None,
|
|
190
190
|
jsonrpc_method: None,
|
|
191
|
+
static_response: None,
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
194
|
|
|
@@ -654,6 +655,7 @@ mod tests {
|
|
|
654
655
|
#[cfg(feature = "di")]
|
|
655
656
|
handler_dependencies: None,
|
|
656
657
|
jsonrpc_method: None,
|
|
658
|
+
static_response: None,
|
|
657
659
|
};
|
|
658
660
|
|
|
659
661
|
let result = route_to_operation(&route);
|