spikard 0.8.2 → 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 +11 -6
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +8 -8
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +63 -25
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +20 -4
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +10 -4
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +25 -22
- data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +14 -12
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +24 -10
- 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 +17 -11
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +51 -73
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +442 -4
- data/vendor/crates/spikard-bindings-shared/src/response_interpreter.rs +944 -0
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +22 -10
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +28 -10
- 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 +11 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +6 -9
- data/vendor/crates/spikard-core/src/debug.rs +2 -2
- data/vendor/crates/spikard-core/src/di/container.rs +2 -2
- data/vendor/crates/spikard-core/src/di/error.rs +1 -1
- data/vendor/crates/spikard-core/src/di/factory.rs +9 -5
- data/vendor/crates/spikard-core/src/di/graph.rs +1 -0
- data/vendor/crates/spikard-core/src/di/resolved.rs +25 -2
- data/vendor/crates/spikard-core/src/di/value.rs +2 -1
- data/vendor/crates/spikard-core/src/errors.rs +3 -0
- data/vendor/crates/spikard-core/src/http.rs +94 -18
- data/vendor/crates/spikard-core/src/lifecycle.rs +85 -61
- data/vendor/crates/spikard-core/src/parameters.rs +75 -54
- data/vendor/crates/spikard-core/src/problem.rs +19 -5
- data/vendor/crates/spikard-core/src/request_data.rs +16 -24
- data/vendor/crates/spikard-core/src/router.rs +26 -6
- data/vendor/crates/spikard-core/src/schema_registry.rs +25 -11
- data/vendor/crates/spikard-core/src/type_hints.rs +14 -7
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +30 -16
- data/vendor/crates/spikard-core/src/validation/mod.rs +46 -33
- 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 +11 -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 +11 -1
- data/vendor/crates/spikard-rb/build.rs +1 -0
- 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 +502 -62
- data/vendor/crates/spikard-rb/src/lifecycle.rs +31 -3
- 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 +9 -1
- data/vendor/crates/spikard-rb-macros/src/lib.rs +4 -5
- 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
|
@@ -15,7 +15,7 @@ pub struct ErrorResponseBuilder;
|
|
|
15
15
|
impl ErrorResponseBuilder {
|
|
16
16
|
/// Create a structured error response with status code and error details
|
|
17
17
|
///
|
|
18
|
-
/// Returns a tuple of (StatusCode
|
|
18
|
+
/// Returns a tuple of (`StatusCode`, JSON body as String)
|
|
19
19
|
///
|
|
20
20
|
/// # Arguments
|
|
21
21
|
/// * `status` - HTTP status code
|
|
@@ -42,7 +42,7 @@ impl ErrorResponseBuilder {
|
|
|
42
42
|
|
|
43
43
|
/// Create an error response with additional details
|
|
44
44
|
///
|
|
45
|
-
/// Returns a tuple of (StatusCode
|
|
45
|
+
/// Returns a tuple of (`StatusCode`, JSON body as String)
|
|
46
46
|
///
|
|
47
47
|
/// # Arguments
|
|
48
48
|
/// * `status` - HTTP status code
|
|
@@ -79,17 +79,18 @@ impl ErrorResponseBuilder {
|
|
|
79
79
|
(status, body)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
/// Create an error response from a StructuredError
|
|
82
|
+
/// Create an error response from a `StructuredError`
|
|
83
83
|
///
|
|
84
|
-
/// Returns a tuple of (StatusCode
|
|
84
|
+
/// Returns a tuple of (`StatusCode`, JSON body as String)
|
|
85
85
|
///
|
|
86
86
|
/// # Arguments
|
|
87
87
|
/// * `error` - The structured error
|
|
88
88
|
///
|
|
89
89
|
/// # Note
|
|
90
|
-
/// Uses INTERNAL_SERVER_ERROR as the default status code. Override with
|
|
90
|
+
/// Uses `INTERNAL_SERVER_ERROR` as the default status code. Override with
|
|
91
91
|
/// `structured_error()` or `with_details()` for specific status codes.
|
|
92
|
-
|
|
92
|
+
#[must_use]
|
|
93
|
+
pub fn from_structured_error(error: &StructuredError) -> (StatusCode, String) {
|
|
93
94
|
let status = StatusCode::INTERNAL_SERVER_ERROR;
|
|
94
95
|
let body = serde_json::to_string(&error)
|
|
95
96
|
.unwrap_or_else(|_| r#"{"error":"serialization_failed","code":"internal_error","details":{}}"#.to_string());
|
|
@@ -98,8 +99,8 @@ impl ErrorResponseBuilder {
|
|
|
98
99
|
|
|
99
100
|
/// Create a validation error response
|
|
100
101
|
///
|
|
101
|
-
/// Converts ValidationError to RFC 9457 Problem Details format.
|
|
102
|
-
/// Returns (StatusCode::UNPROCESSABLE_ENTITY
|
|
102
|
+
/// Converts `ValidationError` to RFC 9457 Problem Details format.
|
|
103
|
+
/// Returns (`StatusCode::UNPROCESSABLE_ENTITY`, JSON body)
|
|
103
104
|
///
|
|
104
105
|
/// # Arguments
|
|
105
106
|
/// * `validation_error` - The validation error containing one or more details
|
|
@@ -124,6 +125,7 @@ impl ErrorResponseBuilder {
|
|
|
124
125
|
///
|
|
125
126
|
/// let (status, body) = ErrorResponseBuilder::validation_error(&validation_error);
|
|
126
127
|
/// ```
|
|
128
|
+
#[must_use]
|
|
127
129
|
pub fn validation_error(validation_error: &ValidationError) -> (StatusCode, String) {
|
|
128
130
|
let problem = ProblemDetails::from_validation_error(validation_error);
|
|
129
131
|
let status = problem.status_code();
|
|
@@ -136,7 +138,7 @@ impl ErrorResponseBuilder {
|
|
|
136
138
|
|
|
137
139
|
/// Create an RFC 9457 Problem Details response
|
|
138
140
|
///
|
|
139
|
-
/// Returns a tuple of (StatusCode
|
|
141
|
+
/// Returns a tuple of (`StatusCode`, JSON body as String)
|
|
140
142
|
///
|
|
141
143
|
/// # Arguments
|
|
142
144
|
/// * `problem` - The Problem Details object
|
|
@@ -150,6 +152,7 @@ impl ErrorResponseBuilder {
|
|
|
150
152
|
/// let problem = ProblemDetails::not_found("User with id 123 not found");
|
|
151
153
|
/// let (status, body) = ErrorResponseBuilder::problem_details_response(&problem);
|
|
152
154
|
/// ```
|
|
155
|
+
#[must_use]
|
|
153
156
|
pub fn problem_details_response(problem: &ProblemDetails) -> (StatusCode, String) {
|
|
154
157
|
let status = problem.status_code();
|
|
155
158
|
let body = serde_json::to_string(problem).unwrap_or_else(|_| {
|
|
@@ -161,70 +164,70 @@ impl ErrorResponseBuilder {
|
|
|
161
164
|
|
|
162
165
|
/// Create a generic bad request error
|
|
163
166
|
///
|
|
164
|
-
/// Returns (StatusCode::BAD_REQUEST
|
|
167
|
+
/// Returns (`StatusCode::BAD_REQUEST`, JSON body)
|
|
165
168
|
pub fn bad_request(message: impl Into<String>) -> (StatusCode, String) {
|
|
166
169
|
Self::structured_error(StatusCode::BAD_REQUEST, "bad_request", message)
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
/// Create a generic internal server error
|
|
170
173
|
///
|
|
171
|
-
/// Returns (StatusCode::INTERNAL_SERVER_ERROR
|
|
174
|
+
/// Returns (`StatusCode::INTERNAL_SERVER_ERROR`, JSON body)
|
|
172
175
|
pub fn internal_error(message: impl Into<String>) -> (StatusCode, String) {
|
|
173
176
|
Self::structured_error(StatusCode::INTERNAL_SERVER_ERROR, "internal_error", message)
|
|
174
177
|
}
|
|
175
178
|
|
|
176
179
|
/// Create an unauthorized error
|
|
177
180
|
///
|
|
178
|
-
/// Returns (StatusCode::UNAUTHORIZED
|
|
181
|
+
/// Returns (`StatusCode::UNAUTHORIZED`, JSON body)
|
|
179
182
|
pub fn unauthorized(message: impl Into<String>) -> (StatusCode, String) {
|
|
180
183
|
Self::structured_error(StatusCode::UNAUTHORIZED, "unauthorized", message)
|
|
181
184
|
}
|
|
182
185
|
|
|
183
186
|
/// Create a forbidden error
|
|
184
187
|
///
|
|
185
|
-
/// Returns (StatusCode::FORBIDDEN
|
|
188
|
+
/// Returns (`StatusCode::FORBIDDEN`, JSON body)
|
|
186
189
|
pub fn forbidden(message: impl Into<String>) -> (StatusCode, String) {
|
|
187
190
|
Self::structured_error(StatusCode::FORBIDDEN, "forbidden", message)
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
/// Create a not found error
|
|
191
194
|
///
|
|
192
|
-
/// Returns (StatusCode::NOT_FOUND
|
|
195
|
+
/// Returns (`StatusCode::NOT_FOUND`, JSON body)
|
|
193
196
|
pub fn not_found(message: impl Into<String>) -> (StatusCode, String) {
|
|
194
197
|
Self::structured_error(StatusCode::NOT_FOUND, "not_found", message)
|
|
195
198
|
}
|
|
196
199
|
|
|
197
200
|
/// Create a method not allowed error
|
|
198
201
|
///
|
|
199
|
-
/// Returns (StatusCode::METHOD_NOT_ALLOWED
|
|
202
|
+
/// Returns (`StatusCode::METHOD_NOT_ALLOWED`, JSON body)
|
|
200
203
|
pub fn method_not_allowed(message: impl Into<String>) -> (StatusCode, String) {
|
|
201
204
|
Self::structured_error(StatusCode::METHOD_NOT_ALLOWED, "method_not_allowed", message)
|
|
202
205
|
}
|
|
203
206
|
|
|
204
207
|
/// Create an unprocessable entity error (validation failed)
|
|
205
208
|
///
|
|
206
|
-
/// Returns (StatusCode::UNPROCESSABLE_ENTITY
|
|
209
|
+
/// Returns (`StatusCode::UNPROCESSABLE_ENTITY`, JSON body)
|
|
207
210
|
pub fn unprocessable_entity(message: impl Into<String>) -> (StatusCode, String) {
|
|
208
211
|
Self::structured_error(StatusCode::UNPROCESSABLE_ENTITY, "unprocessable_entity", message)
|
|
209
212
|
}
|
|
210
213
|
|
|
211
214
|
/// Create a conflict error
|
|
212
215
|
///
|
|
213
|
-
/// Returns (StatusCode::CONFLICT
|
|
216
|
+
/// Returns (`StatusCode::CONFLICT`, JSON body)
|
|
214
217
|
pub fn conflict(message: impl Into<String>) -> (StatusCode, String) {
|
|
215
218
|
Self::structured_error(StatusCode::CONFLICT, "conflict", message)
|
|
216
219
|
}
|
|
217
220
|
|
|
218
221
|
/// Create a service unavailable error
|
|
219
222
|
///
|
|
220
|
-
/// Returns (StatusCode::SERVICE_UNAVAILABLE
|
|
223
|
+
/// Returns (`StatusCode::SERVICE_UNAVAILABLE`, JSON body)
|
|
221
224
|
pub fn service_unavailable(message: impl Into<String>) -> (StatusCode, String) {
|
|
222
225
|
Self::structured_error(StatusCode::SERVICE_UNAVAILABLE, "service_unavailable", message)
|
|
223
226
|
}
|
|
224
227
|
|
|
225
228
|
/// Create a request timeout error
|
|
226
229
|
///
|
|
227
|
-
/// Returns (StatusCode::REQUEST_TIMEOUT
|
|
230
|
+
/// Returns (`StatusCode::REQUEST_TIMEOUT`, JSON body)
|
|
228
231
|
pub fn request_timeout(message: impl Into<String>) -> (StatusCode, String) {
|
|
229
232
|
Self::structured_error(StatusCode::REQUEST_TIMEOUT, "request_timeout", message)
|
|
230
233
|
}
|
|
@@ -257,7 +260,7 @@ mod tests {
|
|
|
257
260
|
StatusCode::BAD_REQUEST,
|
|
258
261
|
"validation_error",
|
|
259
262
|
"Invalid email format",
|
|
260
|
-
details
|
|
263
|
+
details,
|
|
261
264
|
);
|
|
262
265
|
assert_eq!(status, StatusCode::BAD_REQUEST);
|
|
263
266
|
let parsed: Value = serde_json::from_str(&body).unwrap();
|
|
@@ -270,7 +273,7 @@ mod tests {
|
|
|
270
273
|
#[test]
|
|
271
274
|
fn test_from_structured_error() {
|
|
272
275
|
let error = StructuredError::simple("test_error", "Something went wrong");
|
|
273
|
-
let (status, body) = ErrorResponseBuilder::from_structured_error(error);
|
|
276
|
+
let (status, body) = ErrorResponseBuilder::from_structured_error(&error);
|
|
274
277
|
assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
|
|
275
278
|
let parsed: Value = serde_json::from_str(&body).unwrap();
|
|
276
279
|
assert_eq!(parsed["code"], "test_error");
|
|
@@ -284,7 +287,7 @@ mod tests {
|
|
|
284
287
|
error_type: "missing".to_string(),
|
|
285
288
|
loc: vec!["body".to_string(), "username".to_string()],
|
|
286
289
|
msg: "Field required".to_string(),
|
|
287
|
-
input: Value::String(
|
|
290
|
+
input: Value::String(String::new()),
|
|
288
291
|
ctx: None,
|
|
289
292
|
}],
|
|
290
293
|
};
|
|
@@ -6,20 +6,20 @@
|
|
|
6
6
|
use std::collections::HashMap;
|
|
7
7
|
use tonic::metadata::{MetadataKey, MetadataMap, MetadataValue};
|
|
8
8
|
|
|
9
|
-
/// Extract metadata from gRPC MetadataMap to a simple HashMap
|
|
9
|
+
/// Extract metadata from gRPC `MetadataMap` to a simple `HashMap`.
|
|
10
10
|
///
|
|
11
|
-
/// This function converts gRPC metadata to a language-agnostic HashMap format
|
|
11
|
+
/// This function converts gRPC metadata to a language-agnostic `HashMap` format
|
|
12
12
|
/// that can be easily passed to language bindings. Only ASCII metadata is
|
|
13
13
|
/// included; binary metadata is skipped with optional logging.
|
|
14
14
|
///
|
|
15
15
|
/// # Arguments
|
|
16
16
|
///
|
|
17
|
-
/// * `metadata` - The gRPC MetadataMap to extract from
|
|
17
|
+
/// * `metadata` - The gRPC `MetadataMap` to extract from
|
|
18
18
|
/// * `log_binary_skip` - Whether to log when binary metadata is skipped
|
|
19
19
|
///
|
|
20
20
|
/// # Returns
|
|
21
21
|
///
|
|
22
|
-
/// A HashMap containing all ASCII metadata key-value pairs
|
|
22
|
+
/// A `HashMap` containing all ASCII metadata key-value pairs
|
|
23
23
|
///
|
|
24
24
|
/// # Examples
|
|
25
25
|
///
|
|
@@ -55,19 +55,19 @@ pub fn extract_metadata_to_hashmap(metadata: &MetadataMap, log_binary_skip: bool
|
|
|
55
55
|
map
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/// Convert a HashMap to gRPC MetadataMap
|
|
58
|
+
/// Convert a `HashMap` to gRPC `MetadataMap`.
|
|
59
59
|
///
|
|
60
|
-
/// This function converts a language-agnostic HashMap into a gRPC MetadataMap
|
|
60
|
+
/// This function converts a language-agnostic `HashMap` into a gRPC `MetadataMap`
|
|
61
61
|
/// that can be used in responses. All keys and values are validated and errors
|
|
62
62
|
/// are returned if any are invalid.
|
|
63
63
|
///
|
|
64
64
|
/// # Arguments
|
|
65
65
|
///
|
|
66
|
-
/// * `map` - The HashMap to convert
|
|
66
|
+
/// * `map` - The `HashMap` to convert
|
|
67
67
|
///
|
|
68
68
|
/// # Returns
|
|
69
69
|
///
|
|
70
|
-
/// A Result containing the MetadataMap or an error message
|
|
70
|
+
/// A Result containing the `MetadataMap` or an error message
|
|
71
71
|
///
|
|
72
72
|
/// # Errors
|
|
73
73
|
///
|
|
@@ -87,15 +87,17 @@ pub fn extract_metadata_to_hashmap(metadata: &MetadataMap, log_binary_skip: bool
|
|
|
87
87
|
/// let metadata = hashmap_to_metadata(&map).unwrap();
|
|
88
88
|
/// assert!(metadata.contains_key("content-type"));
|
|
89
89
|
/// ```
|
|
90
|
-
pub fn hashmap_to_metadata
|
|
90
|
+
pub fn hashmap_to_metadata<S: std::hash::BuildHasher>(
|
|
91
|
+
map: &std::collections::HashMap<String, String, S>,
|
|
92
|
+
) -> Result<MetadataMap, String> {
|
|
91
93
|
let mut metadata = MetadataMap::new();
|
|
92
94
|
|
|
93
95
|
for (key, value) in map {
|
|
94
|
-
let metadata_key =
|
|
95
|
-
.map_err(|err| format!("Invalid metadata key '{}': {}"
|
|
96
|
+
let metadata_key =
|
|
97
|
+
MetadataKey::from_bytes(key.as_bytes()).map_err(|err| format!("Invalid metadata key '{key}': {err}"))?;
|
|
96
98
|
|
|
97
99
|
let metadata_value =
|
|
98
|
-
MetadataValue::try_from(value).map_err(|err| format!("Invalid metadata value for '{}': {}"
|
|
100
|
+
MetadataValue::try_from(value).map_err(|err| format!("Invalid metadata value for '{key}': {err}"))?;
|
|
99
101
|
|
|
100
102
|
metadata.insert(metadata_key, metadata_value);
|
|
101
103
|
}
|
|
@@ -33,7 +33,9 @@ pub enum HandlerError {
|
|
|
33
33
|
|
|
34
34
|
impl From<ValidationError> for HandlerError {
|
|
35
35
|
fn from(err: ValidationError) -> Self {
|
|
36
|
-
|
|
36
|
+
// PERFORMANCE: Avoid format! allocation for debug representation.
|
|
37
|
+
// Most callers just need the error type, not the full debug output.
|
|
38
|
+
Self::Validation(err.to_string())
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -54,6 +56,10 @@ pub trait LanguageHandler: Send + Sync {
|
|
|
54
56
|
type Output: Send;
|
|
55
57
|
|
|
56
58
|
/// Prepare request data for passing to the language handler
|
|
59
|
+
///
|
|
60
|
+
/// # Errors
|
|
61
|
+
///
|
|
62
|
+
/// Returns an error if request preparation fails.
|
|
57
63
|
fn prepare_request(&self, request_data: &RequestData) -> Result<Self::Input, HandlerError>;
|
|
58
64
|
|
|
59
65
|
/// Invoke the language-specific handler with the prepared input
|
|
@@ -63,6 +69,10 @@ pub trait LanguageHandler: Send + Sync {
|
|
|
63
69
|
) -> Pin<Box<dyn Future<Output = Result<Self::Output, HandlerError>> + Send + '_>>;
|
|
64
70
|
|
|
65
71
|
/// Interpret the handler's output and convert it to an HTTP response
|
|
72
|
+
///
|
|
73
|
+
/// # Errors
|
|
74
|
+
///
|
|
75
|
+
/// Returns an error if response interpretation fails.
|
|
66
76
|
fn interpret_response(&self, output: Self::Output) -> Result<Response<Body>, HandlerError>;
|
|
67
77
|
}
|
|
68
78
|
|
|
@@ -81,7 +91,7 @@ pub struct HandlerExecutor<L: LanguageHandler> {
|
|
|
81
91
|
|
|
82
92
|
impl<L: LanguageHandler> HandlerExecutor<L> {
|
|
83
93
|
/// Create a new handler executor
|
|
84
|
-
pub fn new(language_handler: Arc<L>, request_validator: Option<Arc<SchemaValidator>>) -> Self {
|
|
94
|
+
pub const fn new(language_handler: Arc<L>, request_validator: Option<Arc<SchemaValidator>>) -> Self {
|
|
85
95
|
Self {
|
|
86
96
|
language_handler,
|
|
87
97
|
request_validator,
|
|
@@ -89,7 +99,7 @@ impl<L: LanguageHandler> HandlerExecutor<L> {
|
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
/// Create a handler executor with only a language handler
|
|
92
|
-
pub fn with_handler(language_handler: Arc<L>) -> Self {
|
|
102
|
+
pub const fn with_handler(language_handler: Arc<L>) -> Self {
|
|
93
103
|
Self {
|
|
94
104
|
language_handler,
|
|
95
105
|
request_validator: None,
|
|
@@ -97,6 +107,7 @@ impl<L: LanguageHandler> HandlerExecutor<L> {
|
|
|
97
107
|
}
|
|
98
108
|
|
|
99
109
|
/// Add request validation to this executor
|
|
110
|
+
#[must_use]
|
|
100
111
|
pub fn with_request_validator(mut self, validator: Arc<SchemaValidator>) -> Self {
|
|
101
112
|
self.request_validator = Some(validator);
|
|
102
113
|
self
|
|
@@ -116,21 +127,24 @@ impl<L: LanguageHandler + 'static> Handler for HandlerExecutor<L> {
|
|
|
116
127
|
return Err(ErrorResponseBuilder::validation_error(&validation_err));
|
|
117
128
|
}
|
|
118
129
|
|
|
130
|
+
// PERFORMANCE: Avoid format! allocations in the hot path. ErrorResponseBuilder
|
|
131
|
+
// can accept &dyn Display or construct error messages directly, reducing
|
|
132
|
+
// string allocation overhead in typical error handling paths.
|
|
119
133
|
let input = self
|
|
120
134
|
.language_handler
|
|
121
135
|
.prepare_request(&request_data)
|
|
122
|
-
.map_err(|e| ErrorResponseBuilder::internal_error(format!("Failed to prepare request: {}"
|
|
136
|
+
.map_err(|e| ErrorResponseBuilder::internal_error(format!("Failed to prepare request: {e}")))?;
|
|
123
137
|
|
|
124
138
|
let output = self
|
|
125
139
|
.language_handler
|
|
126
140
|
.invoke_handler(input)
|
|
127
141
|
.await
|
|
128
|
-
.map_err(|e| ErrorResponseBuilder::internal_error(format!("Handler execution failed: {}"
|
|
142
|
+
.map_err(|e| ErrorResponseBuilder::internal_error(format!("Handler execution failed: {e}")))?;
|
|
129
143
|
|
|
130
144
|
let response = self
|
|
131
145
|
.language_handler
|
|
132
146
|
.interpret_response(output)
|
|
133
|
-
.map_err(|e| ErrorResponseBuilder::internal_error(format!("Failed to interpret response: {}"
|
|
147
|
+
.map_err(|e| ErrorResponseBuilder::internal_error(format!("Failed to interpret response: {e}")))?;
|
|
134
148
|
|
|
135
149
|
Ok(response)
|
|
136
150
|
})
|
|
@@ -172,10 +186,10 @@ mod tests {
|
|
|
172
186
|
let request = Request::builder().body(Body::empty()).unwrap();
|
|
173
187
|
let request_data = RequestData {
|
|
174
188
|
path_params: Arc::new(std::collections::HashMap::new()),
|
|
175
|
-
query_params: json!({}),
|
|
189
|
+
query_params: Arc::new(json!({})),
|
|
176
190
|
validated_params: None,
|
|
177
191
|
raw_query_params: Arc::new(std::collections::HashMap::new()),
|
|
178
|
-
body: json!({}),
|
|
192
|
+
body: Arc::new(json!({})),
|
|
179
193
|
raw_body: None,
|
|
180
194
|
headers: Arc::new(std::collections::HashMap::new()),
|
|
181
195
|
cookies: Arc::new(std::collections::HashMap::new()),
|
|
@@ -196,10 +210,10 @@ mod tests {
|
|
|
196
210
|
let request = Request::builder().body(Body::empty()).unwrap();
|
|
197
211
|
let request_data = RequestData {
|
|
198
212
|
path_params: Arc::new(std::collections::HashMap::new()),
|
|
199
|
-
query_params: json!({}),
|
|
213
|
+
query_params: Arc::new(json!({})),
|
|
200
214
|
validated_params: None,
|
|
201
215
|
raw_query_params: Arc::new(std::collections::HashMap::new()),
|
|
202
|
-
body: json!({}),
|
|
216
|
+
body: Arc::new(json!({})),
|
|
203
217
|
raw_body: None,
|
|
204
218
|
headers: Arc::new(std::collections::HashMap::new()),
|
|
205
219
|
cookies: Arc::new(std::collections::HashMap::new()),
|