spikard 0.3.3 → 0.3.5
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/ext/spikard_rb/Cargo.toml +1 -1
- data/lib/spikard/version.rb +1 -1
- data/vendor/crates/spikard-core/Cargo.toml +13 -13
- data/vendor/crates/spikard-http/Cargo.toml +31 -21
- data/vendor/crates/spikard-rb/Cargo.toml +1 -1
- 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,143 +0,0 @@
|
|
|
1
|
-
//! SSE test client bindings for Ruby
|
|
2
|
-
|
|
3
|
-
use magnus::prelude::*;
|
|
4
|
-
use magnus::{Error, Ruby, Value, method};
|
|
5
|
-
use serde_json::Value as JsonValue;
|
|
6
|
-
use spikard_http::testing::{ResponseSnapshot, SseEvent as RustSseEvent, SseStream as RustSseStream};
|
|
7
|
-
|
|
8
|
-
/// Ruby wrapper for SSE stream
|
|
9
|
-
#[magnus::wrap(class = "Spikard::Native::SseStream", free_immediately)]
|
|
10
|
-
pub struct SseStream {
|
|
11
|
-
inner: RustSseStream,
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
impl SseStream {
|
|
15
|
-
pub fn new(inner: RustSseStream) -> Self {
|
|
16
|
-
Self { inner }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/// Get the raw body of the SSE response
|
|
20
|
-
fn body(&self) -> String {
|
|
21
|
-
self.inner.body().to_string()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/// Get all events from the stream
|
|
25
|
-
fn events(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
26
|
-
let ruby_arr = ruby.ary_new();
|
|
27
|
-
for event in this.inner.events() {
|
|
28
|
-
let sse_event = SseEvent::new(event.clone());
|
|
29
|
-
let ruby_event = ruby.obj_wrap(sse_event);
|
|
30
|
-
ruby_arr.push(ruby_event)?;
|
|
31
|
-
}
|
|
32
|
-
Ok(ruby_arr.as_value())
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/// Get events as JSON values
|
|
36
|
-
fn events_as_json(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
37
|
-
let json_events = this.inner.events_as_json().map_err(|e| {
|
|
38
|
-
Error::new(
|
|
39
|
-
ruby.exception_runtime_error(),
|
|
40
|
-
format!("Failed to parse events as JSON: {}", e),
|
|
41
|
-
)
|
|
42
|
-
})?;
|
|
43
|
-
|
|
44
|
-
let ruby_arr = ruby.ary_new();
|
|
45
|
-
for value in json_events {
|
|
46
|
-
let ruby_val = json_to_ruby(ruby, &value)?;
|
|
47
|
-
ruby_arr.push(ruby_val)?;
|
|
48
|
-
}
|
|
49
|
-
Ok(ruby_arr.as_value())
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/// Ruby wrapper for SSE event
|
|
54
|
-
#[magnus::wrap(class = "Spikard::Native::SseEvent", free_immediately)]
|
|
55
|
-
pub struct SseEvent {
|
|
56
|
-
inner: RustSseEvent,
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
impl SseEvent {
|
|
60
|
-
pub fn new(inner: RustSseEvent) -> Self {
|
|
61
|
-
Self { inner }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/// Get the data field of the event
|
|
65
|
-
fn data(&self) -> String {
|
|
66
|
-
self.inner.data.clone()
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/// Parse the event data as JSON
|
|
70
|
-
fn as_json(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
71
|
-
let value = this
|
|
72
|
-
.inner
|
|
73
|
-
.as_json()
|
|
74
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), format!("Failed to parse JSON: {}", e)))?;
|
|
75
|
-
|
|
76
|
-
json_to_ruby(ruby, &value)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/// Create an SSE stream from a response snapshot
|
|
81
|
-
pub fn sse_stream_from_response(ruby: &Ruby, response: &ResponseSnapshot) -> Result<Value, Error> {
|
|
82
|
-
let stream = RustSseStream::from_response(response)
|
|
83
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), format!("Failed to parse SSE: {}", e)))?;
|
|
84
|
-
|
|
85
|
-
let sse_stream = SseStream::new(stream);
|
|
86
|
-
Ok(ruby.obj_wrap(sse_stream).as_value())
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/// Helper to convert JSON to Ruby object
|
|
90
|
-
fn json_to_ruby(ruby: &Ruby, value: &JsonValue) -> Result<Value, Error> {
|
|
91
|
-
match value {
|
|
92
|
-
JsonValue::Null => Ok(ruby.qnil().as_value()),
|
|
93
|
-
JsonValue::Bool(b) => Ok(if *b {
|
|
94
|
-
ruby.qtrue().as_value()
|
|
95
|
-
} else {
|
|
96
|
-
ruby.qfalse().as_value()
|
|
97
|
-
}),
|
|
98
|
-
JsonValue::Number(n) => {
|
|
99
|
-
if let Some(i) = n.as_i64() {
|
|
100
|
-
Ok(ruby.integer_from_i64(i).as_value())
|
|
101
|
-
} else if let Some(u) = n.as_u64() {
|
|
102
|
-
Ok(ruby.integer_from_i64(u as i64).as_value())
|
|
103
|
-
} else if let Some(f) = n.as_f64() {
|
|
104
|
-
Ok(ruby.float_from_f64(f).as_value())
|
|
105
|
-
} else {
|
|
106
|
-
Ok(ruby.qnil().as_value())
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
JsonValue::String(s) => Ok(ruby.str_new(s).as_value()),
|
|
110
|
-
JsonValue::Array(arr) => {
|
|
111
|
-
let ruby_arr = ruby.ary_new();
|
|
112
|
-
for item in arr {
|
|
113
|
-
let ruby_val = json_to_ruby(ruby, item)?;
|
|
114
|
-
ruby_arr.push(ruby_val)?;
|
|
115
|
-
}
|
|
116
|
-
Ok(ruby_arr.as_value())
|
|
117
|
-
}
|
|
118
|
-
JsonValue::Object(obj) => {
|
|
119
|
-
let ruby_hash = ruby.hash_new();
|
|
120
|
-
for (key, val) in obj {
|
|
121
|
-
let ruby_val = json_to_ruby(ruby, val)?;
|
|
122
|
-
ruby_hash.aset(ruby.str_new(key), ruby_val)?;
|
|
123
|
-
}
|
|
124
|
-
Ok(ruby_hash.as_value())
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/// Initialize SSE test client bindings
|
|
130
|
-
pub fn init(ruby: &Ruby, module: &magnus::RModule) -> Result<(), Error> {
|
|
131
|
-
let native_module = module.define_module("Native")?;
|
|
132
|
-
|
|
133
|
-
let sse_stream_class = native_module.define_class("SseStream", ruby.class_object())?;
|
|
134
|
-
sse_stream_class.define_method("body", method!(SseStream::body, 0))?;
|
|
135
|
-
sse_stream_class.define_method("events", method!(SseStream::events, 0))?;
|
|
136
|
-
sse_stream_class.define_method("events_as_json", method!(SseStream::events_as_json, 0))?;
|
|
137
|
-
|
|
138
|
-
let sse_event_class = native_module.define_class("SseEvent", ruby.class_object())?;
|
|
139
|
-
sse_event_class.define_method("data", method!(SseEvent::data, 0))?;
|
|
140
|
-
sse_event_class.define_method("as_json", method!(SseEvent::as_json, 0))?;
|
|
141
|
-
|
|
142
|
-
Ok(())
|
|
143
|
-
}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
//! WebSocket test client bindings for Ruby
|
|
2
|
-
|
|
3
|
-
use magnus::prelude::*;
|
|
4
|
-
use magnus::{Error, Ruby, Value, method};
|
|
5
|
-
use serde_json::Value as JsonValue;
|
|
6
|
-
use spikard_http::testing::{WebSocketConnection as RustWebSocketConnection, WebSocketMessage as RustWebSocketMessage};
|
|
7
|
-
use std::cell::RefCell;
|
|
8
|
-
|
|
9
|
-
use crate::server::GLOBAL_RUNTIME;
|
|
10
|
-
|
|
11
|
-
/// Ruby wrapper for WebSocket test client
|
|
12
|
-
#[derive(Default)]
|
|
13
|
-
#[magnus::wrap(class = "Spikard::Native::WebSocketTestConnection", free_immediately)]
|
|
14
|
-
pub struct WebSocketTestConnection {
|
|
15
|
-
inner: RefCell<Option<RustWebSocketConnection>>,
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
impl WebSocketTestConnection {
|
|
19
|
-
/// Create a new WebSocket test connection (public for lib.rs)
|
|
20
|
-
pub(crate) fn new(inner: RustWebSocketConnection) -> Self {
|
|
21
|
-
Self {
|
|
22
|
-
inner: RefCell::new(Some(inner)),
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/// Send a text message
|
|
27
|
-
fn send_text(&self, text: String) -> Result<(), Error> {
|
|
28
|
-
let mut inner = self.inner.borrow_mut();
|
|
29
|
-
let ws = inner
|
|
30
|
-
.as_mut()
|
|
31
|
-
.ok_or_else(|| Error::new(magnus::exception::runtime_error(), "WebSocket closed"))?;
|
|
32
|
-
|
|
33
|
-
GLOBAL_RUNTIME.block_on(async {
|
|
34
|
-
ws.send_text(text).await;
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
Ok(())
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/// Send a JSON message
|
|
41
|
-
fn send_json(ruby: &Ruby, this: &Self, obj: Value) -> Result<(), Error> {
|
|
42
|
-
let json_value = ruby_to_json(ruby, obj)?;
|
|
43
|
-
let mut inner = this.inner.borrow_mut();
|
|
44
|
-
let ws = inner
|
|
45
|
-
.as_mut()
|
|
46
|
-
.ok_or_else(|| Error::new(ruby.exception_runtime_error(), "WebSocket closed"))?;
|
|
47
|
-
|
|
48
|
-
GLOBAL_RUNTIME.block_on(async {
|
|
49
|
-
ws.send_json(&json_value).await;
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
Ok(())
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/// Receive a text message
|
|
56
|
-
fn receive_text(&self) -> Result<String, Error> {
|
|
57
|
-
let mut inner = self.inner.borrow_mut();
|
|
58
|
-
let ws = inner
|
|
59
|
-
.as_mut()
|
|
60
|
-
.ok_or_else(|| Error::new(magnus::exception::runtime_error(), "WebSocket closed"))?;
|
|
61
|
-
|
|
62
|
-
let text = GLOBAL_RUNTIME.block_on(async { ws.receive_text().await });
|
|
63
|
-
|
|
64
|
-
Ok(text)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/// Receive and parse a JSON message
|
|
68
|
-
fn receive_json(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
69
|
-
let mut inner = this.inner.borrow_mut();
|
|
70
|
-
let ws = inner
|
|
71
|
-
.as_mut()
|
|
72
|
-
.ok_or_else(|| Error::new(ruby.exception_runtime_error(), "WebSocket closed"))?;
|
|
73
|
-
|
|
74
|
-
let json_value: JsonValue = GLOBAL_RUNTIME.block_on(async { ws.receive_json().await });
|
|
75
|
-
|
|
76
|
-
json_to_ruby(ruby, &json_value)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/// Receive raw bytes
|
|
80
|
-
fn receive_bytes(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
81
|
-
let mut inner = this.inner.borrow_mut();
|
|
82
|
-
let ws = inner
|
|
83
|
-
.as_mut()
|
|
84
|
-
.ok_or_else(|| Error::new(ruby.exception_runtime_error(), "WebSocket closed"))?;
|
|
85
|
-
|
|
86
|
-
let bytes = GLOBAL_RUNTIME.block_on(async { ws.receive_bytes().await });
|
|
87
|
-
|
|
88
|
-
Ok(ruby.str_from_slice(&bytes).as_value())
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/// Receive a message and return WebSocketMessage
|
|
92
|
-
fn receive_message(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
93
|
-
let mut inner = this.inner.borrow_mut();
|
|
94
|
-
let ws = inner
|
|
95
|
-
.as_mut()
|
|
96
|
-
.ok_or_else(|| Error::new(ruby.exception_runtime_error(), "WebSocket closed"))?;
|
|
97
|
-
|
|
98
|
-
let msg = GLOBAL_RUNTIME.block_on(async { ws.receive_message().await });
|
|
99
|
-
|
|
100
|
-
let ws_msg = WebSocketMessage::new(msg);
|
|
101
|
-
Ok(ruby.obj_wrap(ws_msg).as_value())
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/// Close the WebSocket connection
|
|
105
|
-
fn close(&self) -> Result<(), Error> {
|
|
106
|
-
self.inner.borrow_mut().take();
|
|
107
|
-
Ok(())
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/// Ruby wrapper for WebSocket messages
|
|
112
|
-
#[magnus::wrap(class = "Spikard::Native::WebSocketMessage", free_immediately)]
|
|
113
|
-
pub struct WebSocketMessage {
|
|
114
|
-
inner: RustWebSocketMessage,
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
impl WebSocketMessage {
|
|
118
|
-
pub fn new(inner: RustWebSocketMessage) -> Self {
|
|
119
|
-
Self { inner }
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/// Get message as text if it's a text message
|
|
123
|
-
fn as_text(&self) -> Result<Option<String>, Error> {
|
|
124
|
-
Ok(self.inner.as_text().map(|s| s.to_string()))
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/// Get message as JSON if it's a text message containing JSON
|
|
128
|
-
fn as_json(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
129
|
-
match this.inner.as_json() {
|
|
130
|
-
Ok(value) => json_to_ruby(ruby, &value),
|
|
131
|
-
Err(_) => Ok(ruby.qnil().as_value()),
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/// Get message as binary if it's a binary message
|
|
136
|
-
fn as_binary(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
137
|
-
match this.inner.as_binary() {
|
|
138
|
-
Some(bytes) => Ok(ruby.str_from_slice(bytes).as_value()),
|
|
139
|
-
None => Ok(ruby.qnil().as_value()),
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/// Check if this is a close message
|
|
144
|
-
fn is_close(&self) -> bool {
|
|
145
|
-
self.inner.is_close()
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/// Helper to convert Ruby object to JSON
|
|
150
|
-
fn ruby_to_json(ruby: &Ruby, value: Value) -> Result<JsonValue, Error> {
|
|
151
|
-
let json_module = ruby.class_object().const_get::<_, magnus::RModule>("JSON")?;
|
|
152
|
-
let json_str: String = json_module.funcall("generate", (value,))?;
|
|
153
|
-
serde_json::from_str(&json_str).map_err(|e| {
|
|
154
|
-
Error::new(
|
|
155
|
-
magnus::exception::runtime_error(),
|
|
156
|
-
format!("Failed to parse JSON: {}", e),
|
|
157
|
-
)
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/// Helper to convert JSON to Ruby object
|
|
162
|
-
fn json_to_ruby(ruby: &Ruby, value: &JsonValue) -> Result<Value, Error> {
|
|
163
|
-
match value {
|
|
164
|
-
JsonValue::Null => Ok(ruby.qnil().as_value()),
|
|
165
|
-
JsonValue::Bool(b) => Ok(if *b {
|
|
166
|
-
ruby.qtrue().as_value()
|
|
167
|
-
} else {
|
|
168
|
-
ruby.qfalse().as_value()
|
|
169
|
-
}),
|
|
170
|
-
JsonValue::Number(n) => {
|
|
171
|
-
if let Some(i) = n.as_i64() {
|
|
172
|
-
Ok(ruby.integer_from_i64(i).as_value())
|
|
173
|
-
} else if let Some(u) = n.as_u64() {
|
|
174
|
-
Ok(ruby.integer_from_i64(u as i64).as_value())
|
|
175
|
-
} else if let Some(f) = n.as_f64() {
|
|
176
|
-
Ok(ruby.float_from_f64(f).as_value())
|
|
177
|
-
} else {
|
|
178
|
-
Ok(ruby.qnil().as_value())
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
JsonValue::String(s) => Ok(ruby.str_new(s).as_value()),
|
|
182
|
-
JsonValue::Array(arr) => {
|
|
183
|
-
let ruby_arr = ruby.ary_new();
|
|
184
|
-
for item in arr {
|
|
185
|
-
let ruby_val = json_to_ruby(ruby, item)?;
|
|
186
|
-
ruby_arr.push(ruby_val)?;
|
|
187
|
-
}
|
|
188
|
-
Ok(ruby_arr.as_value())
|
|
189
|
-
}
|
|
190
|
-
JsonValue::Object(obj) => {
|
|
191
|
-
let ruby_hash = ruby.hash_new();
|
|
192
|
-
for (key, val) in obj {
|
|
193
|
-
let ruby_val = json_to_ruby(ruby, val)?;
|
|
194
|
-
ruby_hash.aset(ruby.str_new(key), ruby_val)?;
|
|
195
|
-
}
|
|
196
|
-
Ok(ruby_hash.as_value())
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/// Initialize WebSocket test client bindings
|
|
202
|
-
pub fn init(ruby: &Ruby, module: &magnus::RModule) -> Result<(), Error> {
|
|
203
|
-
let native_module = module.define_module("Native")?;
|
|
204
|
-
|
|
205
|
-
let ws_conn_class = native_module.define_class("WebSocketTestConnection", ruby.class_object())?;
|
|
206
|
-
ws_conn_class.define_method("send_text", method!(WebSocketTestConnection::send_text, 1))?;
|
|
207
|
-
ws_conn_class.define_method("send_json", method!(WebSocketTestConnection::send_json, 1))?;
|
|
208
|
-
ws_conn_class.define_method("receive_text", method!(WebSocketTestConnection::receive_text, 0))?;
|
|
209
|
-
ws_conn_class.define_method("receive_json", method!(WebSocketTestConnection::receive_json, 0))?;
|
|
210
|
-
ws_conn_class.define_method("receive_bytes", method!(WebSocketTestConnection::receive_bytes, 0))?;
|
|
211
|
-
ws_conn_class.define_method("receive_message", method!(WebSocketTestConnection::receive_message, 0))?;
|
|
212
|
-
ws_conn_class.define_method("close", method!(WebSocketTestConnection::close, 0))?;
|
|
213
|
-
|
|
214
|
-
let ws_msg_class = native_module.define_class("WebSocketMessage", ruby.class_object())?;
|
|
215
|
-
ws_msg_class.define_method("as_text", method!(WebSocketMessage::as_text, 0))?;
|
|
216
|
-
ws_msg_class.define_method("as_json", method!(WebSocketMessage::as_json, 0))?;
|
|
217
|
-
ws_msg_class.define_method("as_binary", method!(WebSocketMessage::as_binary, 0))?;
|
|
218
|
-
ws_msg_class.define_method("is_close", method!(WebSocketMessage::is_close, 0))?;
|
|
219
|
-
|
|
220
|
-
Ok(())
|
|
221
|
-
}
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
//! Ruby WebSocket handler bindings
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides the bridge between Ruby blocks/procs and Rust's WebSocket system.
|
|
4
|
-
//! Uses magnus to safely call Ruby code from Rust async tasks.
|
|
5
|
-
|
|
6
|
-
use magnus::{RHash, Value, prelude::*, value::Opaque};
|
|
7
|
-
use serde_json::Value as JsonValue;
|
|
8
|
-
use spikard_http::WebSocketHandler;
|
|
9
|
-
use tracing::{debug, error};
|
|
10
|
-
|
|
11
|
-
/// Ruby implementation of WebSocketHandler
|
|
12
|
-
pub struct RubyWebSocketHandler {
|
|
13
|
-
/// Handler name for debugging
|
|
14
|
-
name: String,
|
|
15
|
-
/// Ruby proc/callable for handle_message (Opaque for Send safety)
|
|
16
|
-
handle_message_proc: Opaque<Value>,
|
|
17
|
-
/// Ruby proc/callable for on_connect (Opaque for Send safety)
|
|
18
|
-
on_connect_proc: Option<Opaque<Value>>,
|
|
19
|
-
/// Ruby proc/callable for on_disconnect (Opaque for Send safety)
|
|
20
|
-
on_disconnect_proc: Option<Opaque<Value>>,
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
impl RubyWebSocketHandler {
|
|
24
|
-
/// Create a new Ruby WebSocket handler
|
|
25
|
-
#[allow(dead_code)]
|
|
26
|
-
pub fn new(
|
|
27
|
-
name: String,
|
|
28
|
-
handle_message_proc: Value,
|
|
29
|
-
on_connect_proc: Option<Value>,
|
|
30
|
-
on_disconnect_proc: Option<Value>,
|
|
31
|
-
) -> Self {
|
|
32
|
-
Self {
|
|
33
|
-
name,
|
|
34
|
-
handle_message_proc: handle_message_proc.into(),
|
|
35
|
-
on_connect_proc: on_connect_proc.map(|v| v.into()),
|
|
36
|
-
on_disconnect_proc: on_disconnect_proc.map(|v| v.into()),
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/// Convert Ruby value to JSON
|
|
41
|
-
fn ruby_to_json(ruby: &magnus::Ruby, value: Value) -> Result<JsonValue, String> {
|
|
42
|
-
if value.is_nil() {
|
|
43
|
-
return Ok(JsonValue::Null);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
let json_module: Value = ruby
|
|
47
|
-
.class_object()
|
|
48
|
-
.const_get("JSON")
|
|
49
|
-
.map_err(|e| format!("JSON module not available: {}", e))?;
|
|
50
|
-
|
|
51
|
-
let json_str: String = json_module
|
|
52
|
-
.funcall("generate", (value,))
|
|
53
|
-
.map_err(|e| format!("Failed to generate JSON: {}", e))?;
|
|
54
|
-
|
|
55
|
-
serde_json::from_str(&json_str).map_err(|e| format!("Failed to parse JSON: {}", e))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/// Convert JSON value to Ruby value
|
|
59
|
-
fn json_to_ruby(ruby: &magnus::Ruby, value: &JsonValue) -> Result<Value, String> {
|
|
60
|
-
match value {
|
|
61
|
-
JsonValue::Null => Ok(ruby.qnil().as_value()),
|
|
62
|
-
JsonValue::Bool(b) => Ok(if *b {
|
|
63
|
-
ruby.qtrue().as_value()
|
|
64
|
-
} else {
|
|
65
|
-
ruby.qfalse().as_value()
|
|
66
|
-
}),
|
|
67
|
-
JsonValue::Number(num) => {
|
|
68
|
-
if let Some(i) = num.as_i64() {
|
|
69
|
-
Ok(ruby.integer_from_i64(i).as_value())
|
|
70
|
-
} else if let Some(f) = num.as_f64() {
|
|
71
|
-
Ok(ruby.float_from_f64(f).as_value())
|
|
72
|
-
} else {
|
|
73
|
-
Ok(ruby.qnil().as_value())
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
JsonValue::String(s) => Ok(ruby.str_new(s).as_value()),
|
|
77
|
-
JsonValue::Array(arr) => {
|
|
78
|
-
let ruby_array = ruby.ary_new();
|
|
79
|
-
for item in arr {
|
|
80
|
-
ruby_array
|
|
81
|
-
.push(Self::json_to_ruby(ruby, item)?)
|
|
82
|
-
.map_err(|e| format!("Failed to push to array: {}", e))?;
|
|
83
|
-
}
|
|
84
|
-
Ok(ruby_array.as_value())
|
|
85
|
-
}
|
|
86
|
-
JsonValue::Object(obj) => {
|
|
87
|
-
let ruby_hash = RHash::new();
|
|
88
|
-
for (key, val) in obj {
|
|
89
|
-
ruby_hash
|
|
90
|
-
.aset(ruby.str_new(key), Self::json_to_ruby(ruby, val)?)
|
|
91
|
-
.map_err(|e| format!("Failed to set hash value: {}", e))?;
|
|
92
|
-
}
|
|
93
|
-
Ok(ruby_hash.as_value())
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
impl WebSocketHandler for RubyWebSocketHandler {
|
|
100
|
-
async fn handle_message(&self, message: JsonValue) -> Option<JsonValue> {
|
|
101
|
-
debug!("Ruby WebSocket handler '{}': handle_message", self.name);
|
|
102
|
-
|
|
103
|
-
match magnus::Ruby::get()
|
|
104
|
-
.map_err(|e| format!("Failed to get Ruby: {}", e))
|
|
105
|
-
.and_then(|ruby| {
|
|
106
|
-
let message_ruby = Self::json_to_ruby(&ruby, &message)?;
|
|
107
|
-
|
|
108
|
-
let proc_value = ruby.get_inner(self.handle_message_proc);
|
|
109
|
-
let result: Value = proc_value
|
|
110
|
-
.funcall("call", (message_ruby,))
|
|
111
|
-
.map_err(|e| format!("Handler '{}' call failed: {}", self.name, e))?;
|
|
112
|
-
|
|
113
|
-
if result.is_nil() {
|
|
114
|
-
Ok(None)
|
|
115
|
-
} else {
|
|
116
|
-
Self::ruby_to_json(&ruby, result).map(Some)
|
|
117
|
-
}
|
|
118
|
-
}) {
|
|
119
|
-
Ok(value) => value,
|
|
120
|
-
Err(e) => {
|
|
121
|
-
error!("Ruby error in handle_message: {}", e);
|
|
122
|
-
None
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async fn on_connect(&self) {
|
|
128
|
-
debug!("Ruby WebSocket handler '{}': on_connect", self.name);
|
|
129
|
-
|
|
130
|
-
if let Some(on_connect_proc) = self.on_connect_proc {
|
|
131
|
-
if let Err(e) = magnus::Ruby::get()
|
|
132
|
-
.map_err(|e| format!("Failed to get Ruby: {}", e))
|
|
133
|
-
.and_then(|ruby| {
|
|
134
|
-
let proc_value = ruby.get_inner(on_connect_proc);
|
|
135
|
-
proc_value
|
|
136
|
-
.funcall::<_, _, Value>("call", ())
|
|
137
|
-
.map_err(|e| format!("on_connect '{}' call failed: {}", self.name, e))?;
|
|
138
|
-
Ok(())
|
|
139
|
-
})
|
|
140
|
-
{
|
|
141
|
-
error!("on_connect error: {}", e);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
debug!("Ruby WebSocket handler '{}': on_connect completed", self.name);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async fn on_disconnect(&self) {
|
|
149
|
-
debug!("Ruby WebSocket handler '{}': on_disconnect", self.name);
|
|
150
|
-
|
|
151
|
-
if let Some(on_disconnect_proc) = self.on_disconnect_proc {
|
|
152
|
-
if let Err(e) = magnus::Ruby::get()
|
|
153
|
-
.map_err(|e| format!("Failed to get Ruby: {}", e))
|
|
154
|
-
.and_then(|ruby| {
|
|
155
|
-
let proc_value = ruby.get_inner(on_disconnect_proc);
|
|
156
|
-
proc_value
|
|
157
|
-
.funcall::<_, _, Value>("call", ())
|
|
158
|
-
.map_err(|e| format!("on_disconnect '{}' call failed: {}", self.name, e))?;
|
|
159
|
-
Ok(())
|
|
160
|
-
})
|
|
161
|
-
{
|
|
162
|
-
error!("on_disconnect error: {}", e);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
debug!("Ruby WebSocket handler '{}': on_disconnect completed", self.name);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
unsafe impl Send for RubyWebSocketHandler {}
|
|
171
|
-
unsafe impl Sync for RubyWebSocketHandler {}
|
|
172
|
-
|
|
173
|
-
/// Create WebSocketState from Ruby handler object
|
|
174
|
-
///
|
|
175
|
-
/// This function is designed to be called from Ruby to register WebSocket handlers.
|
|
176
|
-
#[allow(dead_code)]
|
|
177
|
-
pub fn create_websocket_state(
|
|
178
|
-
ruby: &magnus::Ruby,
|
|
179
|
-
handler_obj: Value,
|
|
180
|
-
) -> Result<spikard_http::WebSocketState<RubyWebSocketHandler>, magnus::Error> {
|
|
181
|
-
let handle_message_proc: Value = handler_obj
|
|
182
|
-
.funcall("method", (ruby.to_symbol("handle_message"),))
|
|
183
|
-
.map_err(|e| {
|
|
184
|
-
magnus::Error::new(
|
|
185
|
-
ruby.exception_arg_error(),
|
|
186
|
-
format!("handle_message method not found: {}", e),
|
|
187
|
-
)
|
|
188
|
-
})?;
|
|
189
|
-
|
|
190
|
-
let on_connect_proc = handler_obj
|
|
191
|
-
.funcall::<_, _, Value>("method", (ruby.to_symbol("on_connect"),))
|
|
192
|
-
.ok();
|
|
193
|
-
|
|
194
|
-
let on_disconnect_proc = handler_obj
|
|
195
|
-
.funcall::<_, _, Value>("method", (ruby.to_symbol("on_disconnect"),))
|
|
196
|
-
.ok();
|
|
197
|
-
|
|
198
|
-
let message_schema = handler_obj
|
|
199
|
-
.funcall::<_, _, Value>("instance_variable_get", (ruby.to_symbol("@_message_schema"),))
|
|
200
|
-
.ok()
|
|
201
|
-
.and_then(|v| {
|
|
202
|
-
if v.is_nil() {
|
|
203
|
-
None
|
|
204
|
-
} else {
|
|
205
|
-
RubyWebSocketHandler::ruby_to_json(ruby, v).ok()
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
let response_schema = handler_obj
|
|
210
|
-
.funcall::<_, _, Value>("instance_variable_get", (ruby.to_symbol("@_response_schema"),))
|
|
211
|
-
.ok()
|
|
212
|
-
.and_then(|v| {
|
|
213
|
-
if v.is_nil() {
|
|
214
|
-
None
|
|
215
|
-
} else {
|
|
216
|
-
RubyWebSocketHandler::ruby_to_json(ruby, v).ok()
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
let ruby_handler = RubyWebSocketHandler::new(
|
|
221
|
-
"WebSocketHandler".to_string(),
|
|
222
|
-
handle_message_proc,
|
|
223
|
-
on_connect_proc,
|
|
224
|
-
on_disconnect_proc,
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
if message_schema.is_some() || response_schema.is_some() {
|
|
228
|
-
spikard_http::WebSocketState::with_schemas(ruby_handler, message_schema, response_schema)
|
|
229
|
-
.map_err(|e| magnus::Error::new(ruby.exception_runtime_error(), e))
|
|
230
|
-
} else {
|
|
231
|
-
Ok(spikard_http::WebSocketState::new(ruby_handler))
|
|
232
|
-
}
|
|
233
|
-
}
|