spikard 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +659 -659
- data/ext/spikard_rb/Cargo.toml +17 -17
- data/ext/spikard_rb/extconf.rb +10 -10
- data/ext/spikard_rb/src/lib.rs +6 -6
- data/lib/spikard/app.rb +386 -386
- data/lib/spikard/background.rb +27 -27
- data/lib/spikard/config.rb +396 -396
- data/lib/spikard/converters.rb +13 -13
- data/lib/spikard/handler_wrapper.rb +113 -113
- data/lib/spikard/provide.rb +214 -214
- data/lib/spikard/response.rb +173 -173
- data/lib/spikard/schema.rb +243 -243
- data/lib/spikard/sse.rb +111 -111
- data/lib/spikard/streaming_response.rb +44 -44
- data/lib/spikard/testing.rb +221 -221
- data/lib/spikard/upload_file.rb +131 -131
- data/lib/spikard/version.rb +5 -5
- data/lib/spikard/websocket.rb +59 -59
- data/lib/spikard.rb +43 -43
- data/sig/spikard.rbs +366 -360
- data/vendor/crates/spikard-core/Cargo.toml +40 -40
- data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +133 -133
- data/vendor/crates/spikard-core/src/debug.rs +63 -63
- data/vendor/crates/spikard-core/src/di/container.rs +726 -726
- data/vendor/crates/spikard-core/src/di/dependency.rs +273 -273
- data/vendor/crates/spikard-core/src/di/error.rs +118 -118
- data/vendor/crates/spikard-core/src/di/factory.rs +538 -538
- data/vendor/crates/spikard-core/src/di/graph.rs +545 -545
- data/vendor/crates/spikard-core/src/di/mod.rs +192 -192
- data/vendor/crates/spikard-core/src/di/resolved.rs +411 -411
- data/vendor/crates/spikard-core/src/di/value.rs +283 -283
- data/vendor/crates/spikard-core/src/errors.rs +39 -39
- data/vendor/crates/spikard-core/src/http.rs +153 -153
- data/vendor/crates/spikard-core/src/lib.rs +29 -29
- data/vendor/crates/spikard-core/src/lifecycle.rs +422 -422
- data/vendor/crates/spikard-core/src/parameters.rs +722 -722
- data/vendor/crates/spikard-core/src/problem.rs +310 -310
- data/vendor/crates/spikard-core/src/request_data.rs +189 -189
- data/vendor/crates/spikard-core/src/router.rs +249 -249
- data/vendor/crates/spikard-core/src/schema_registry.rs +183 -183
- data/vendor/crates/spikard-core/src/type_hints.rs +304 -304
- data/vendor/crates/spikard-core/src/validation.rs +699 -699
- data/vendor/crates/spikard-http/Cargo.toml +68 -68
- data/vendor/crates/spikard-http/src/auth.rs +247 -247
- data/vendor/crates/spikard-http/src/background.rs +249 -249
- data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -3
- data/vendor/crates/spikard-http/src/bindings/response.rs +1 -1
- data/vendor/crates/spikard-http/src/body_metadata.rs +8 -8
- data/vendor/crates/spikard-http/src/cors.rs +490 -490
- data/vendor/crates/spikard-http/src/debug.rs +63 -63
- data/vendor/crates/spikard-http/src/di_handler.rs +423 -423
- data/vendor/crates/spikard-http/src/handler_response.rs +190 -190
- data/vendor/crates/spikard-http/src/handler_trait.rs +228 -228
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +284 -284
- data/vendor/crates/spikard-http/src/lib.rs +529 -529
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +149 -149
- data/vendor/crates/spikard-http/src/lifecycle.rs +428 -428
- data/vendor/crates/spikard-http/src/middleware/mod.rs +285 -285
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +86 -86
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +147 -147
- data/vendor/crates/spikard-http/src/middleware/validation.rs +287 -287
- data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -309
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +190 -190
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +308 -308
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +195 -195
- data/vendor/crates/spikard-http/src/parameters.rs +1 -1
- data/vendor/crates/spikard-http/src/problem.rs +1 -1
- data/vendor/crates/spikard-http/src/query_parser.rs +369 -369
- data/vendor/crates/spikard-http/src/response.rs +399 -399
- data/vendor/crates/spikard-http/src/router.rs +1 -1
- data/vendor/crates/spikard-http/src/schema_registry.rs +1 -1
- data/vendor/crates/spikard-http/src/server/handler.rs +87 -87
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +98 -98
- data/vendor/crates/spikard-http/src/server/mod.rs +805 -805
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +119 -119
- data/vendor/crates/spikard-http/src/sse.rs +447 -447
- data/vendor/crates/spikard-http/src/testing/form.rs +14 -14
- data/vendor/crates/spikard-http/src/testing/multipart.rs +60 -60
- data/vendor/crates/spikard-http/src/testing/test_client.rs +285 -285
- data/vendor/crates/spikard-http/src/testing.rs +377 -377
- data/vendor/crates/spikard-http/src/type_hints.rs +1 -1
- data/vendor/crates/spikard-http/src/validation.rs +1 -1
- data/vendor/crates/spikard-http/src/websocket.rs +324 -324
- data/vendor/crates/spikard-rb/Cargo.toml +42 -42
- data/vendor/crates/spikard-rb/build.rs +8 -8
- data/vendor/crates/spikard-rb/src/background.rs +63 -63
- data/vendor/crates/spikard-rb/src/config.rs +294 -294
- data/vendor/crates/spikard-rb/src/conversion.rs +453 -453
- data/vendor/crates/spikard-rb/src/di.rs +409 -409
- data/vendor/crates/spikard-rb/src/handler.rs +625 -625
- data/vendor/crates/spikard-rb/src/lib.rs +2771 -2771
- data/vendor/crates/spikard-rb/src/lifecycle.rs +274 -274
- data/vendor/crates/spikard-rb/src/server.rs +283 -283
- data/vendor/crates/spikard-rb/src/sse.rs +231 -231
- data/vendor/crates/spikard-rb/src/test_client.rs +404 -404
- data/vendor/crates/spikard-rb/src/test_sse.rs +143 -143
- data/vendor/crates/spikard-rb/src/test_websocket.rs +221 -221
- data/vendor/crates/spikard-rb/src/websocket.rs +233 -233
- metadata +1 -1
|
@@ -1,221 +1,221 @@
|
|
|
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
|
+
//! 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
|
+
}
|