spikard 0.3.6 → 0.5.0
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 +21 -6
- data/ext/spikard_rb/Cargo.toml +2 -2
- data/lib/spikard/app.rb +33 -14
- data/lib/spikard/testing.rb +47 -12
- data/lib/spikard/version.rb +1 -1
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -0
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +132 -0
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +752 -0
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +194 -0
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +246 -0
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +401 -0
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +238 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +24 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +292 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +616 -0
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +305 -0
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -0
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +351 -0
- data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +454 -0
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +383 -0
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +280 -0
- data/vendor/crates/spikard-core/Cargo.toml +4 -4
- data/vendor/crates/spikard-core/src/debug.rs +64 -0
- data/vendor/crates/spikard-core/src/di/container.rs +3 -27
- data/vendor/crates/spikard-core/src/di/factory.rs +1 -5
- data/vendor/crates/spikard-core/src/di/graph.rs +8 -47
- data/vendor/crates/spikard-core/src/di/mod.rs +1 -1
- data/vendor/crates/spikard-core/src/di/resolved.rs +1 -7
- data/vendor/crates/spikard-core/src/di/value.rs +2 -4
- data/vendor/crates/spikard-core/src/errors.rs +30 -0
- data/vendor/crates/spikard-core/src/http.rs +262 -0
- data/vendor/crates/spikard-core/src/lib.rs +1 -1
- data/vendor/crates/spikard-core/src/lifecycle.rs +764 -0
- data/vendor/crates/spikard-core/src/metadata.rs +389 -0
- data/vendor/crates/spikard-core/src/parameters.rs +1962 -159
- data/vendor/crates/spikard-core/src/problem.rs +34 -0
- data/vendor/crates/spikard-core/src/request_data.rs +966 -1
- data/vendor/crates/spikard-core/src/router.rs +263 -2
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +688 -0
- data/vendor/crates/spikard-core/src/{validation.rs → validation/mod.rs} +26 -268
- data/vendor/crates/spikard-http/Cargo.toml +12 -16
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +148 -0
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +92 -0
- data/vendor/crates/spikard-http/src/auth.rs +65 -16
- data/vendor/crates/spikard-http/src/background.rs +1614 -3
- data/vendor/crates/spikard-http/src/cors.rs +515 -0
- data/vendor/crates/spikard-http/src/debug.rs +65 -0
- data/vendor/crates/spikard-http/src/di_handler.rs +1322 -77
- data/vendor/crates/spikard-http/src/handler_response.rs +711 -0
- data/vendor/crates/spikard-http/src/handler_trait.rs +607 -5
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +6 -0
- data/vendor/crates/spikard-http/src/lib.rs +33 -28
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +81 -0
- data/vendor/crates/spikard-http/src/lifecycle.rs +765 -0
- data/vendor/crates/spikard-http/src/middleware/mod.rs +372 -117
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +836 -10
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +409 -43
- data/vendor/crates/spikard-http/src/middleware/validation.rs +513 -65
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +345 -0
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +1055 -0
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +473 -3
- data/vendor/crates/spikard-http/src/query_parser.rs +455 -31
- data/vendor/crates/spikard-http/src/response.rs +321 -0
- data/vendor/crates/spikard-http/src/server/handler.rs +1572 -9
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +136 -0
- data/vendor/crates/spikard-http/src/server/mod.rs +875 -178
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +674 -23
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +599 -0
- data/vendor/crates/spikard-http/src/sse.rs +983 -21
- data/vendor/crates/spikard-http/src/testing/form.rs +38 -0
- data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -2
- data/vendor/crates/spikard-http/src/testing.rs +7 -7
- data/vendor/crates/spikard-http/src/websocket.rs +1055 -4
- data/vendor/crates/spikard-http/tests/background_behavior.rs +832 -0
- data/vendor/crates/spikard-http/tests/common/handlers.rs +309 -0
- data/vendor/crates/spikard-http/tests/common/mod.rs +26 -0
- data/vendor/crates/spikard-http/tests/di_integration.rs +192 -0
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +5 -0
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1093 -0
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +656 -0
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +314 -0
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +620 -0
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +663 -0
- data/vendor/crates/spikard-rb/Cargo.toml +10 -4
- data/vendor/crates/spikard-rb/build.rs +196 -5
- data/vendor/crates/spikard-rb/src/config/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/{config.rs → config/server_config.rs} +100 -109
- data/vendor/crates/spikard-rb/src/conversion.rs +121 -20
- data/vendor/crates/spikard-rb/src/di/builder.rs +100 -0
- data/vendor/crates/spikard-rb/src/{di.rs → di/mod.rs} +12 -46
- data/vendor/crates/spikard-rb/src/handler.rs +100 -107
- data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -0
- data/vendor/crates/spikard-rb/src/lib.rs +467 -1428
- data/vendor/crates/spikard-rb/src/lifecycle.rs +1 -0
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +447 -0
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +324 -0
- data/vendor/crates/spikard-rb/src/server.rs +47 -22
- data/vendor/crates/spikard-rb/src/{test_client.rs → testing/client.rs} +187 -40
- data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -0
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +635 -0
- data/vendor/crates/spikard-rb/src/websocket.rs +178 -37
- metadata +46 -13
- data/vendor/crates/spikard-http/src/parameters.rs +0 -1
- data/vendor/crates/spikard-http/src/problem.rs +0 -1
- data/vendor/crates/spikard-http/src/router.rs +0 -1
- data/vendor/crates/spikard-http/src/schema_registry.rs +0 -1
- data/vendor/crates/spikard-http/src/type_hints.rs +0 -1
- data/vendor/crates/spikard-http/src/validation.rs +0 -1
- data/vendor/crates/spikard-rb/src/test_websocket.rs +0 -221
- /data/vendor/crates/spikard-rb/src/{test_sse.rs → testing/sse.rs} +0 -0
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
//! and implements Spikard's `Handler` trait for async request processing.
|
|
5
5
|
|
|
6
6
|
#![allow(dead_code)]
|
|
7
|
+
#![deny(clippy::unwrap_used)]
|
|
7
8
|
|
|
8
9
|
use axum::body::Body;
|
|
9
10
|
use axum::http::{HeaderName, HeaderValue, Request, StatusCode};
|
|
10
11
|
use magnus::prelude::*;
|
|
12
|
+
use magnus::value::LazyId;
|
|
11
13
|
use magnus::value::{InnerValue, Opaque};
|
|
12
14
|
use magnus::{Error, RHash, RString, Ruby, TryConvert, Value, gc::Marker};
|
|
13
|
-
use serde_json::
|
|
14
|
-
use
|
|
15
|
-
use
|
|
15
|
+
use serde_json::Value as JsonValue;
|
|
16
|
+
use spikard_bindings_shared::ErrorResponseBuilder;
|
|
17
|
+
use spikard_core::problem::ProblemDetails;
|
|
16
18
|
use spikard_http::SchemaValidator;
|
|
17
|
-
use spikard_http::problem::ProblemDetails;
|
|
18
19
|
use spikard_http::{Handler, HandlerResponse, HandlerResult, RequestData};
|
|
19
20
|
use std::collections::HashMap;
|
|
20
21
|
use std::panic::AssertUnwindSafe;
|
|
@@ -25,6 +26,17 @@ use crate::conversion::{
|
|
|
25
26
|
json_to_ruby, json_to_ruby_with_uploads, map_to_ruby_hash, multimap_to_ruby_hash, ruby_value_to_json,
|
|
26
27
|
};
|
|
27
28
|
|
|
29
|
+
static KEY_METHOD: LazyId = LazyId::new("method");
|
|
30
|
+
static KEY_PATH: LazyId = LazyId::new("path");
|
|
31
|
+
static KEY_PATH_PARAMS: LazyId = LazyId::new("path_params");
|
|
32
|
+
static KEY_QUERY: LazyId = LazyId::new("query");
|
|
33
|
+
static KEY_RAW_QUERY: LazyId = LazyId::new("raw_query");
|
|
34
|
+
static KEY_HEADERS: LazyId = LazyId::new("headers");
|
|
35
|
+
static KEY_COOKIES: LazyId = LazyId::new("cookies");
|
|
36
|
+
static KEY_BODY: LazyId = LazyId::new("body");
|
|
37
|
+
static KEY_RAW_BODY: LazyId = LazyId::new("raw_body");
|
|
38
|
+
static KEY_PARAMS: LazyId = LazyId::new("params");
|
|
39
|
+
|
|
28
40
|
/// Response payload with status, headers, and body data.
|
|
29
41
|
pub struct HandlerResponsePayload {
|
|
30
42
|
pub status: u16,
|
|
@@ -51,8 +63,8 @@ impl StreamingResponsePayload {
|
|
|
51
63
|
pub fn into_response(self) -> Result<HandlerResponse, Error> {
|
|
52
64
|
let ruby = Ruby::get().map_err(|_| {
|
|
53
65
|
Error::new(
|
|
54
|
-
|
|
55
|
-
"Ruby VM unavailable
|
|
66
|
+
magnus::exception::runtime_error(),
|
|
67
|
+
"Ruby VM became unavailable during streaming response construction",
|
|
56
68
|
)
|
|
57
69
|
})?;
|
|
58
70
|
|
|
@@ -127,10 +139,10 @@ pub struct RubyHandlerInner {
|
|
|
127
139
|
pub handler_name: String,
|
|
128
140
|
pub method: String,
|
|
129
141
|
pub path: String,
|
|
142
|
+
method_value: Opaque<Value>,
|
|
143
|
+
path_value: Opaque<Value>,
|
|
130
144
|
pub json_module: Opaque<Value>,
|
|
131
|
-
pub request_validator: Option<Arc<SchemaValidator>>,
|
|
132
145
|
pub response_validator: Option<Arc<SchemaValidator>>,
|
|
133
|
-
pub parameter_validator: Option<ParameterValidator>,
|
|
134
146
|
pub upload_file_class: Option<Opaque<Value>>,
|
|
135
147
|
}
|
|
136
148
|
|
|
@@ -143,17 +155,33 @@ pub struct RubyHandler {
|
|
|
143
155
|
impl RubyHandler {
|
|
144
156
|
/// Create a new RubyHandler from a route and handler Proc.
|
|
145
157
|
pub fn new(route: &spikard_http::Route, handler_value: Value, json_module: Value) -> Result<Self, Error> {
|
|
146
|
-
let upload_file_class =
|
|
158
|
+
let upload_file_class = if route.file_params.is_some() {
|
|
159
|
+
lookup_upload_file_class()?
|
|
160
|
+
} else {
|
|
161
|
+
None
|
|
162
|
+
};
|
|
163
|
+
let method = route.method.as_str().to_string();
|
|
164
|
+
let path = route.path.clone();
|
|
165
|
+
|
|
166
|
+
let Ok(ruby) = Ruby::get() else {
|
|
167
|
+
return Err(Error::new(
|
|
168
|
+
magnus::exception::runtime_error(),
|
|
169
|
+
"Ruby VM unavailable while creating handler",
|
|
170
|
+
));
|
|
171
|
+
};
|
|
172
|
+
let method_value = Opaque::from(ruby.str_new(&method).as_value());
|
|
173
|
+
let path_value = Opaque::from(ruby.str_new(&path).as_value());
|
|
174
|
+
|
|
147
175
|
Ok(Self {
|
|
148
176
|
inner: Arc::new(RubyHandlerInner {
|
|
149
177
|
handler_proc: Opaque::from(handler_value),
|
|
150
178
|
handler_name: route.handler_name.clone(),
|
|
151
|
-
method
|
|
152
|
-
path
|
|
179
|
+
method,
|
|
180
|
+
path,
|
|
181
|
+
method_value,
|
|
182
|
+
path_value,
|
|
153
183
|
json_module: Opaque::from(json_module),
|
|
154
|
-
request_validator: route.request_validator.clone(),
|
|
155
184
|
response_validator: route.response_validator.clone(),
|
|
156
|
-
parameter_validator: route.parameter_validator.clone(),
|
|
157
185
|
upload_file_class,
|
|
158
186
|
}),
|
|
159
187
|
})
|
|
@@ -171,17 +199,30 @@ impl RubyHandler {
|
|
|
171
199
|
json_module: Value,
|
|
172
200
|
route: &spikard_http::Route,
|
|
173
201
|
) -> Result<Self, Error> {
|
|
174
|
-
let upload_file_class =
|
|
202
|
+
let upload_file_class = if route.file_params.is_some() {
|
|
203
|
+
lookup_upload_file_class()?
|
|
204
|
+
} else {
|
|
205
|
+
None
|
|
206
|
+
};
|
|
207
|
+
let Ok(ruby) = Ruby::get() else {
|
|
208
|
+
return Err(Error::new(
|
|
209
|
+
magnus::exception::runtime_error(),
|
|
210
|
+
"Ruby VM unavailable while creating handler",
|
|
211
|
+
));
|
|
212
|
+
};
|
|
213
|
+
let method_value = Opaque::from(ruby.str_new(&method).as_value());
|
|
214
|
+
let path_value = Opaque::from(ruby.str_new(&path).as_value());
|
|
215
|
+
|
|
175
216
|
Ok(Self {
|
|
176
217
|
inner: Arc::new(RubyHandlerInner {
|
|
177
218
|
handler_proc: Opaque::from(handler_value),
|
|
178
219
|
handler_name,
|
|
179
220
|
method,
|
|
180
221
|
path,
|
|
222
|
+
method_value,
|
|
223
|
+
path_value,
|
|
181
224
|
json_module: Opaque::from(json_module),
|
|
182
|
-
request_validator: route.request_validator.clone(),
|
|
183
225
|
response_validator: route.response_validator.clone(),
|
|
184
|
-
parameter_validator: route.parameter_validator.clone(),
|
|
185
226
|
upload_file_class,
|
|
186
227
|
}),
|
|
187
228
|
})
|
|
@@ -193,16 +234,17 @@ impl RubyHandler {
|
|
|
193
234
|
if let Ok(ruby) = Ruby::get() {
|
|
194
235
|
let proc_val = self.inner.handler_proc.get_inner_with(&ruby);
|
|
195
236
|
marker.mark(proc_val);
|
|
237
|
+
marker.mark(self.inner.method_value.get_inner_with(&ruby));
|
|
238
|
+
marker.mark(self.inner.path_value.get_inner_with(&ruby));
|
|
196
239
|
}
|
|
197
240
|
}
|
|
198
241
|
|
|
199
242
|
/// Handle a request synchronously.
|
|
200
243
|
pub fn handle(&self, request_data: RequestData) -> HandlerResult {
|
|
201
|
-
let
|
|
202
|
-
let result = std::panic::catch_unwind(AssertUnwindSafe(|| self.handle_inner(cloned)));
|
|
244
|
+
let result = std::panic::catch_unwind(AssertUnwindSafe(|| self.handle_inner(request_data)));
|
|
203
245
|
match result {
|
|
204
246
|
Ok(res) => res,
|
|
205
|
-
Err(_) => Err(structured_error(
|
|
247
|
+
Err(_) => Err(ErrorResponseBuilder::structured_error(
|
|
206
248
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
207
249
|
"panic",
|
|
208
250
|
"Unexpected panic while executing Ruby handler",
|
|
@@ -211,33 +253,10 @@ impl RubyHandler {
|
|
|
211
253
|
}
|
|
212
254
|
|
|
213
255
|
fn handle_inner(&self, request_data: RequestData) -> HandlerResult {
|
|
214
|
-
|
|
215
|
-
&& let Err(errors) = validator.validate(&request_data.body)
|
|
216
|
-
{
|
|
217
|
-
let problem = ProblemDetails::from_validation_error(&errors);
|
|
218
|
-
return Err(validation_error_response(&problem));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
let validated_params = if let Some(validator) = &self.inner.parameter_validator {
|
|
222
|
-
match validator.validate_and_extract(
|
|
223
|
-
&request_data.query_params,
|
|
224
|
-
request_data.raw_query_params.as_ref(),
|
|
225
|
-
request_data.path_params.as_ref(),
|
|
226
|
-
request_data.headers.as_ref(),
|
|
227
|
-
request_data.cookies.as_ref(),
|
|
228
|
-
) {
|
|
229
|
-
Ok(value) => Some(value),
|
|
230
|
-
Err(errors) => {
|
|
231
|
-
let problem = ProblemDetails::from_validation_error(&errors);
|
|
232
|
-
return Err(validation_error_response(&problem));
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
} else {
|
|
236
|
-
None
|
|
237
|
-
};
|
|
256
|
+
let validated_params = request_data.validated_params.clone();
|
|
238
257
|
|
|
239
258
|
let ruby = Ruby::get().map_err(|_| {
|
|
240
|
-
structured_error(
|
|
259
|
+
ErrorResponseBuilder::structured_error(
|
|
241
260
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
242
261
|
"ruby_vm_unavailable",
|
|
243
262
|
"Ruby VM unavailable while invoking handler",
|
|
@@ -252,7 +271,7 @@ impl RubyHandler {
|
|
|
252
271
|
let response_value = match handler_result {
|
|
253
272
|
Ok(value) => value,
|
|
254
273
|
Err(err) => {
|
|
255
|
-
return Err(structured_error(
|
|
274
|
+
return Err(ErrorResponseBuilder::structured_error(
|
|
256
275
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
257
276
|
"handler_failed",
|
|
258
277
|
format!("Handler '{}' failed: {}", self.inner.handler_name, err),
|
|
@@ -261,7 +280,7 @@ impl RubyHandler {
|
|
|
261
280
|
};
|
|
262
281
|
|
|
263
282
|
let handler_result = interpret_handler_response(&ruby, &self.inner, response_value).map_err(|err| {
|
|
264
|
-
structured_error(
|
|
283
|
+
ErrorResponseBuilder::structured_error(
|
|
265
284
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
266
285
|
"response_interpret_error",
|
|
267
286
|
format!(
|
|
@@ -274,7 +293,7 @@ impl RubyHandler {
|
|
|
274
293
|
let payload = match handler_result {
|
|
275
294
|
RubyHandlerResult::Streaming(streaming) => {
|
|
276
295
|
let response = streaming.into_response().map_err(|err| {
|
|
277
|
-
structured_error(
|
|
296
|
+
ErrorResponseBuilder::structured_error(
|
|
278
297
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
279
298
|
"streaming_response_error",
|
|
280
299
|
format!("Failed to build streaming response: {}", err),
|
|
@@ -291,7 +310,7 @@ impl RubyHandler {
|
|
|
291
310
|
None => match try_parse_raw_body(&payload.raw_body) {
|
|
292
311
|
Ok(parsed) => parsed,
|
|
293
312
|
Err(err) => {
|
|
294
|
-
return Err(structured_error(
|
|
313
|
+
return Err(ErrorResponseBuilder::structured_error(
|
|
295
314
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
296
315
|
"response_body_decode_error",
|
|
297
316
|
err,
|
|
@@ -304,11 +323,11 @@ impl RubyHandler {
|
|
|
304
323
|
Some(json_body) => {
|
|
305
324
|
if let Err(errors) = validator.validate(&json_body) {
|
|
306
325
|
let problem = ProblemDetails::from_validation_error(&errors);
|
|
307
|
-
return Err(
|
|
326
|
+
return Err(ErrorResponseBuilder::problem_details_response(&problem));
|
|
308
327
|
}
|
|
309
328
|
}
|
|
310
329
|
None => {
|
|
311
|
-
return Err(structured_error(
|
|
330
|
+
return Err(ErrorResponseBuilder::structured_error(
|
|
312
331
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
313
332
|
"response_validation_failed",
|
|
314
333
|
"Response validator requires JSON body but handler returned raw bytes",
|
|
@@ -387,24 +406,6 @@ impl Handler for RubyHandler {
|
|
|
387
406
|
}
|
|
388
407
|
}
|
|
389
408
|
|
|
390
|
-
fn structured_error(status: StatusCode, code: &str, message: impl Into<String>) -> (StatusCode, String) {
|
|
391
|
-
let payload = StructuredError::simple(code.to_string(), message.into());
|
|
392
|
-
let body = serde_json::to_string(&payload)
|
|
393
|
-
.unwrap_or_else(|_| r#"{"error":"internal_error","code":"internal_error","details":{}}"#.to_string());
|
|
394
|
-
(status, body)
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
fn validation_error_response(problem: &ProblemDetails) -> (StatusCode, String) {
|
|
398
|
-
let payload = StructuredError::new(
|
|
399
|
-
"validation_error".to_string(),
|
|
400
|
-
problem.title.clone(),
|
|
401
|
-
serde_json::to_value(problem).unwrap_or_else(|_| serde_json::json!({})),
|
|
402
|
-
);
|
|
403
|
-
let body = serde_json::to_string(&payload)
|
|
404
|
-
.unwrap_or_else(|_| r#"{"error":"validation_error","code":"validation_error","details":{}}"#.to_string());
|
|
405
|
-
(problem.status_code(), body)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
409
|
fn try_parse_raw_body(raw_body: &Option<Vec<u8>>) -> Result<Option<JsonValue>, String> {
|
|
409
410
|
let Some(bytes) = raw_body else {
|
|
410
411
|
return Ok(None);
|
|
@@ -435,69 +436,70 @@ fn build_ruby_request(
|
|
|
435
436
|
request_data: &RequestData,
|
|
436
437
|
validated_params: Option<&JsonValue>,
|
|
437
438
|
) -> Result<Value, Error> {
|
|
438
|
-
let hash = ruby.
|
|
439
|
+
let hash = ruby.hash_new_capa(9);
|
|
439
440
|
|
|
440
|
-
hash.aset(
|
|
441
|
-
hash.aset(
|
|
441
|
+
hash.aset(*KEY_METHOD, handler.method_value.get_inner_with(ruby))?;
|
|
442
|
+
hash.aset(*KEY_PATH, handler.path_value.get_inner_with(ruby))?;
|
|
442
443
|
|
|
443
444
|
let path_params = map_to_ruby_hash(ruby, request_data.path_params.as_ref())?;
|
|
444
|
-
hash.aset(
|
|
445
|
+
hash.aset(*KEY_PATH_PARAMS, path_params)?;
|
|
445
446
|
|
|
446
447
|
let query_value = json_to_ruby(ruby, &request_data.query_params)?;
|
|
447
|
-
hash.aset(
|
|
448
|
+
hash.aset(*KEY_QUERY, query_value)?;
|
|
448
449
|
|
|
449
450
|
let raw_query = multimap_to_ruby_hash(ruby, request_data.raw_query_params.as_ref())?;
|
|
450
|
-
hash.aset(
|
|
451
|
+
hash.aset(*KEY_RAW_QUERY, raw_query)?;
|
|
451
452
|
|
|
452
453
|
let headers = map_to_ruby_hash(ruby, request_data.headers.as_ref())?;
|
|
453
|
-
hash.aset(
|
|
454
|
+
hash.aset(*KEY_HEADERS, headers)?;
|
|
454
455
|
|
|
455
456
|
let cookies = map_to_ruby_hash(ruby, request_data.cookies.as_ref())?;
|
|
456
|
-
hash.aset(
|
|
457
|
+
hash.aset(*KEY_COOKIES, cookies)?;
|
|
457
458
|
|
|
458
459
|
let upload_class_value = handler.upload_file_class.as_ref().map(|cls| cls.get_inner_with(ruby));
|
|
459
460
|
let body_value = json_to_ruby_with_uploads(ruby, &request_data.body, upload_class_value.as_ref())?;
|
|
460
|
-
hash.aset(
|
|
461
|
+
hash.aset(*KEY_BODY, body_value)?;
|
|
461
462
|
if let Some(raw) = &request_data.raw_body {
|
|
462
463
|
let raw_str = ruby.str_from_slice(raw);
|
|
463
|
-
hash.aset(
|
|
464
|
+
hash.aset(*KEY_RAW_BODY, raw_str)?;
|
|
464
465
|
} else {
|
|
465
|
-
hash.aset(
|
|
466
|
+
hash.aset(*KEY_RAW_BODY, ruby.qnil())?;
|
|
466
467
|
}
|
|
467
468
|
|
|
468
469
|
let params_value = if let Some(validated) = validated_params {
|
|
469
470
|
json_to_ruby(ruby, validated)?
|
|
470
471
|
} else {
|
|
471
|
-
|
|
472
|
+
build_default_params_from_converted(ruby, path_params, query_value, headers, cookies)?
|
|
472
473
|
};
|
|
473
|
-
hash.aset(
|
|
474
|
+
hash.aset(*KEY_PARAMS, params_value)?;
|
|
474
475
|
|
|
475
476
|
Ok(hash.as_value())
|
|
476
477
|
}
|
|
477
478
|
|
|
478
|
-
/// Build default params from
|
|
479
|
-
fn
|
|
480
|
-
|
|
479
|
+
/// Build default params from already converted Ruby values, avoiding double conversion.
|
|
480
|
+
fn build_default_params_from_converted(
|
|
481
|
+
ruby: &Ruby,
|
|
482
|
+
path_params: Value,
|
|
483
|
+
query: Value,
|
|
484
|
+
headers: Value,
|
|
485
|
+
cookies: Value,
|
|
486
|
+
) -> Result<Value, Error> {
|
|
487
|
+
let params = ruby.hash_new();
|
|
481
488
|
|
|
482
|
-
|
|
483
|
-
|
|
489
|
+
if let Some(hash) = RHash::from_value(path_params) {
|
|
490
|
+
let _: Value = params.funcall("merge!", (hash,))?;
|
|
484
491
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
for (key, value) in obj {
|
|
488
|
-
map.insert(key.clone(), value.clone());
|
|
489
|
-
}
|
|
492
|
+
if let Some(hash) = RHash::from_value(query) {
|
|
493
|
+
let _: Value = params.funcall("merge!", (hash,))?;
|
|
490
494
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
map.insert(key.clone(), JsonValue::String(value.clone()));
|
|
495
|
+
if let Some(hash) = RHash::from_value(headers) {
|
|
496
|
+
let _: Value = params.funcall("merge!", (hash,))?;
|
|
494
497
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
map.insert(key.clone(), JsonValue::String(value.clone()));
|
|
498
|
+
if let Some(hash) = RHash::from_value(cookies) {
|
|
499
|
+
let _: Value = params.funcall("merge!", (hash,))?;
|
|
498
500
|
}
|
|
499
501
|
|
|
500
|
-
|
|
502
|
+
Ok(params.as_value())
|
|
501
503
|
}
|
|
502
504
|
|
|
503
505
|
/// Interpret a Ruby handler response into our response types.
|
|
@@ -555,11 +557,6 @@ fn interpret_handler_response(
|
|
|
555
557
|
let body = if content_value.is_nil() {
|
|
556
558
|
None
|
|
557
559
|
} else if let Ok(str_value) = RString::try_convert(content_value) {
|
|
558
|
-
// SAFETY: Magnus RString::as_slice() yields a valid byte slice for the
|
|
559
|
-
// lifetime of the RString value. We immediately call .to_vec() which copies
|
|
560
|
-
// the bytes into owned memory, so the result does not retain any references
|
|
561
|
-
// to the underlying RString. This is safe because the copy happens before
|
|
562
|
-
// the RString reference is released.
|
|
563
560
|
let slice = unsafe { str_value.as_slice() };
|
|
564
561
|
raw_body = Some(slice.to_vec());
|
|
565
562
|
None
|
|
@@ -580,10 +577,6 @@ fn interpret_handler_response(
|
|
|
580
577
|
}
|
|
581
578
|
|
|
582
579
|
if let Ok(str_value) = RString::try_convert(value) {
|
|
583
|
-
// SAFETY: Magnus RString::as_slice() returns a valid byte slice that remains
|
|
584
|
-
// valid for the lifetime of the RString. We call .to_vec() to copy the bytes
|
|
585
|
-
// into owned storage, ensuring the returned HandlerResponsePayload does not
|
|
586
|
-
// hold any references back to the RString. This copy-then-own pattern is safe.
|
|
587
580
|
let slice = unsafe { str_value.as_slice() };
|
|
588
581
|
return Ok(RubyHandlerResult::Payload(HandlerResponsePayload {
|
|
589
582
|
status: 200,
|