spikard 0.13.0 → 0.15.2
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/Steepfile +6 -0
- data/ext/spikard_rb/extconf.rb +1 -2
- data/ext/spikard_rb/{Cargo.lock → native/Cargo.lock} +819 -424
- data/ext/spikard_rb/native/Cargo.toml +24 -0
- data/ext/spikard_rb/src/lib.rs +5366 -3
- data/lib/spikard/native.rb +86 -0
- data/lib/spikard/version.rb +6 -1
- data/lib/spikard.rb +8 -52
- data/lib/spikard_rb.so +0 -0
- data/sig/types.rbs +427 -0
- metadata +14 -243
- data/LICENSE +0 -1
- data/README.md +0 -285
- data/ext/spikard_rb/Cargo.toml +0 -17
- data/lib/spikard/app.rb +0 -458
- data/lib/spikard/background.rb +0 -58
- data/lib/spikard/config.rb +0 -506
- data/lib/spikard/converters.rb +0 -13
- data/lib/spikard/grpc.rb +0 -232
- data/lib/spikard/handler_wrapper.rb +0 -113
- data/lib/spikard/provide.rb +0 -315
- data/lib/spikard/response.rb +0 -198
- data/lib/spikard/schema.rb +0 -243
- data/lib/spikard/sse.rb +0 -111
- data/lib/spikard/streaming_response.rb +0 -44
- data/lib/spikard/testing.rb +0 -474
- data/lib/spikard/upload_file.rb +0 -131
- data/lib/spikard/websocket.rb +0 -59
- data/sig/spikard.rbs +0 -739
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +0 -75
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +0 -132
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +0 -905
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +0 -210
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +0 -252
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +0 -404
- data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +0 -199
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +0 -252
- data/vendor/crates/spikard-bindings-shared/src/json_conversion.rs +0 -829
- data/vendor/crates/spikard-bindings-shared/src/lazy_cache.rs +0 -587
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +0 -33
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +0 -298
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +0 -594
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +0 -743
- data/vendor/crates/spikard-bindings-shared/src/response_interpreter.rs +0 -944
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +0 -260
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +0 -369
- data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +0 -192
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +0 -383
- data/vendor/crates/spikard-bindings-shared/tests/full_coverage.rs +0 -459
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +0 -280
- data/vendor/crates/spikard-bindings-shared/tests/integration_tests.rs +0 -669
- data/vendor/crates/spikard-core/Cargo.toml +0 -55
- data/vendor/crates/spikard-core/src/bindings/mod.rs +0 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +0 -130
- data/vendor/crates/spikard-core/src/debug.rs +0 -127
- data/vendor/crates/spikard-core/src/di/container.rs +0 -711
- data/vendor/crates/spikard-core/src/di/dependency.rs +0 -273
- data/vendor/crates/spikard-core/src/di/error.rs +0 -118
- data/vendor/crates/spikard-core/src/di/factory.rs +0 -548
- data/vendor/crates/spikard-core/src/di/graph.rs +0 -507
- data/vendor/crates/spikard-core/src/di/mod.rs +0 -192
- data/vendor/crates/spikard-core/src/di/resolved.rs +0 -428
- data/vendor/crates/spikard-core/src/di/value.rs +0 -282
- data/vendor/crates/spikard-core/src/errors.rs +0 -72
- data/vendor/crates/spikard-core/src/http.rs +0 -492
- data/vendor/crates/spikard-core/src/lib.rs +0 -29
- data/vendor/crates/spikard-core/src/lifecycle.rs +0 -1273
- data/vendor/crates/spikard-core/src/metadata.rs +0 -378
- data/vendor/crates/spikard-core/src/parameters.rs +0 -2546
- data/vendor/crates/spikard-core/src/problem.rs +0 -358
- data/vendor/crates/spikard-core/src/request_data.rs +0 -1146
- data/vendor/crates/spikard-core/src/router.rs +0 -530
- data/vendor/crates/spikard-core/src/schema_registry.rs +0 -197
- data/vendor/crates/spikard-core/src/type_hints.rs +0 -311
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +0 -710
- data/vendor/crates/spikard-core/src/validation/mod.rs +0 -470
- data/vendor/crates/spikard-core/tests/bindings_response_tests.rs +0 -136
- data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +0 -37
- data/vendor/crates/spikard-core/tests/error_mapper.rs +0 -761
- data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +0 -106
- data/vendor/crates/spikard-core/tests/parameters_full.rs +0 -701
- data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +0 -301
- data/vendor/crates/spikard-core/tests/request_data_roundtrip.rs +0 -67
- data/vendor/crates/spikard-core/tests/validation_coverage.rs +0 -250
- data/vendor/crates/spikard-core/tests/validation_error_paths.rs +0 -45
- data/vendor/crates/spikard-http/Cargo.toml +0 -82
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +0 -148
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +0 -92
- data/vendor/crates/spikard-http/src/auth.rs +0 -301
- data/vendor/crates/spikard-http/src/background.rs +0 -1859
- data/vendor/crates/spikard-http/src/bindings/mod.rs +0 -3
- data/vendor/crates/spikard-http/src/bindings/response.rs +0 -1
- data/vendor/crates/spikard-http/src/body_metadata.rs +0 -8
- data/vendor/crates/spikard-http/src/cors.rs +0 -1026
- data/vendor/crates/spikard-http/src/debug.rs +0 -128
- data/vendor/crates/spikard-http/src/di_handler.rs +0 -1672
- data/vendor/crates/spikard-http/src/grpc/framing.rs +0 -653
- data/vendor/crates/spikard-http/src/grpc/handler.rs +0 -1211
- data/vendor/crates/spikard-http/src/grpc/mod.rs +0 -556
- data/vendor/crates/spikard-http/src/grpc/service.rs +0 -706
- data/vendor/crates/spikard-http/src/grpc/streaming.rs +0 -319
- data/vendor/crates/spikard-http/src/handler_response.rs +0 -901
- data/vendor/crates/spikard-http/src/handler_trait.rs +0 -1015
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +0 -290
- data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +0 -502
- data/vendor/crates/spikard-http/src/jsonrpc/method_registry.rs +0 -648
- data/vendor/crates/spikard-http/src/jsonrpc/mod.rs +0 -60
- data/vendor/crates/spikard-http/src/jsonrpc/openrpc.rs +0 -325
- data/vendor/crates/spikard-http/src/jsonrpc/protocol.rs +0 -1207
- data/vendor/crates/spikard-http/src/jsonrpc/router.rs +0 -2262
- data/vendor/crates/spikard-http/src/lib.rs +0 -566
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +0 -230
- data/vendor/crates/spikard-http/src/lifecycle.rs +0 -1193
- data/vendor/crates/spikard-http/src/middleware/mod.rs +0 -560
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +0 -912
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +0 -513
- data/vendor/crates/spikard-http/src/middleware/validation.rs +0 -768
- data/vendor/crates/spikard-http/src/openapi/mod.rs +0 -309
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +0 -535
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +0 -1363
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +0 -667
- data/vendor/crates/spikard-http/src/query_parser.rs +0 -793
- data/vendor/crates/spikard-http/src/response.rs +0 -720
- data/vendor/crates/spikard-http/src/server/fast_router.rs +0 -186
- data/vendor/crates/spikard-http/src/server/grpc_routing.rs +0 -1243
- data/vendor/crates/spikard-http/src/server/handler.rs +0 -1661
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +0 -253
- data/vendor/crates/spikard-http/src/server/mod.rs +0 -1717
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +0 -871
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +0 -618
- data/vendor/crates/spikard-http/src/sse.rs +0 -1409
- data/vendor/crates/spikard-http/src/testing/form.rs +0 -52
- data/vendor/crates/spikard-http/src/testing/multipart.rs +0 -64
- data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -825
- data/vendor/crates/spikard-http/src/testing.rs +0 -617
- data/vendor/crates/spikard-http/src/websocket.rs +0 -1477
- data/vendor/crates/spikard-http/tests/auth_integration.rs +0 -645
- data/vendor/crates/spikard-http/tests/background_behavior.rs +0 -832
- data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +0 -1012
- data/vendor/crates/spikard-http/tests/common/handlers.rs +0 -309
- data/vendor/crates/spikard-http/tests/common/mod.rs +0 -33
- data/vendor/crates/spikard-http/tests/common/test_builders.rs +0 -628
- data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +0 -162
- data/vendor/crates/spikard-http/tests/di_integration.rs +0 -192
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +0 -5
- data/vendor/crates/spikard-http/tests/grpc_bidirectional_streaming.rs +0 -430
- data/vendor/crates/spikard-http/tests/grpc_client_streaming.rs +0 -738
- data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +0 -652
- data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +0 -334
- data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +0 -532
- data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +0 -495
- data/vendor/crates/spikard-http/tests/grpc_server_streaming.rs +0 -975
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +0 -1093
- data/vendor/crates/spikard-http/tests/middleware_stack_integration.rs +0 -389
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +0 -656
- data/vendor/crates/spikard-http/tests/request_extraction_full.rs +0 -513
- data/vendor/crates/spikard-http/tests/server_auth_middleware_behavior.rs +0 -328
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +0 -335
- data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +0 -374
- data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +0 -83
- data/vendor/crates/spikard-http/tests/server_handler_wrappers.rs +0 -464
- data/vendor/crates/spikard-http/tests/server_method_router_additional_behavior.rs +0 -286
- data/vendor/crates/spikard-http/tests/server_method_router_coverage.rs +0 -118
- data/vendor/crates/spikard-http/tests/server_middleware_behavior.rs +0 -99
- data/vendor/crates/spikard-http/tests/server_middleware_branches.rs +0 -204
- data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +0 -427
- data/vendor/crates/spikard-http/tests/server_router_behavior.rs +0 -121
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +0 -620
- data/vendor/crates/spikard-http/tests/sse_full_behavior.rs +0 -584
- data/vendor/crates/spikard-http/tests/sse_handler_behavior.rs +0 -130
- data/vendor/crates/spikard-http/tests/test_client_requests.rs +0 -167
- data/vendor/crates/spikard-http/tests/testing_helpers.rs +0 -87
- data/vendor/crates/spikard-http/tests/testing_module_coverage.rs +0 -155
- data/vendor/crates/spikard-http/tests/urlencoded_content_type.rs +0 -82
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +0 -663
- data/vendor/crates/spikard-http/tests/websocket_full_behavior.rs +0 -440
- data/vendor/crates/spikard-http/tests/websocket_integration.rs +0 -150
- data/vendor/crates/spikard-rb/Cargo.toml +0 -63
- data/vendor/crates/spikard-rb/build.rs +0 -200
- data/vendor/crates/spikard-rb/src/background.rs +0 -63
- data/vendor/crates/spikard-rb/src/config/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/config/server_config.rs +0 -401
- data/vendor/crates/spikard-rb/src/conversion.rs +0 -688
- data/vendor/crates/spikard-rb/src/di/builder.rs +0 -100
- data/vendor/crates/spikard-rb/src/di/mod.rs +0 -410
- data/vendor/crates/spikard-rb/src/grpc/handler.rs +0 -875
- data/vendor/crates/spikard-rb/src/grpc/mod.rs +0 -13
- data/vendor/crates/spikard-rb/src/gvl.rs +0 -80
- data/vendor/crates/spikard-rb/src/handler.rs +0 -699
- data/vendor/crates/spikard-rb/src/integration/mod.rs +0 -3
- data/vendor/crates/spikard-rb/src/lib.rs +0 -2268
- data/vendor/crates/spikard-rb/src/lifecycle.rs +0 -334
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +0 -507
- data/vendor/crates/spikard-rb/src/request.rs +0 -439
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +0 -368
- data/vendor/crates/spikard-rb/src/server.rs +0 -304
- data/vendor/crates/spikard-rb/src/sse.rs +0 -231
- data/vendor/crates/spikard-rb/src/testing/client.rs +0 -698
- data/vendor/crates/spikard-rb/src/testing/mod.rs +0 -7
- data/vendor/crates/spikard-rb/src/testing/sse.rs +0 -108
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +0 -573
- data/vendor/crates/spikard-rb/src/websocket.rs +0 -521
- data/vendor/crates/spikard-rb-macros/Cargo.toml +0 -20
- data/vendor/crates/spikard-rb-macros/src/lib.rs +0 -51
|
@@ -1,108 +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
|
-
crate::conversion::json_to_ruby(ruby, value)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/// Initialize SSE test client bindings
|
|
95
|
-
pub fn init(ruby: &Ruby, module: &magnus::RModule) -> Result<(), Error> {
|
|
96
|
-
let native_module = module.define_module("Native")?;
|
|
97
|
-
|
|
98
|
-
let sse_stream_class = native_module.define_class("SseStream", ruby.class_object())?;
|
|
99
|
-
sse_stream_class.define_method("body", method!(SseStream::body, 0))?;
|
|
100
|
-
sse_stream_class.define_method("events", method!(SseStream::events, 0))?;
|
|
101
|
-
sse_stream_class.define_method("events_as_json", method!(SseStream::events_as_json, 0))?;
|
|
102
|
-
|
|
103
|
-
let sse_event_class = native_module.define_class("SseEvent", ruby.class_object())?;
|
|
104
|
-
sse_event_class.define_method("data", method!(SseEvent::data, 0))?;
|
|
105
|
-
sse_event_class.define_method("as_json", method!(SseEvent::as_json, 0))?;
|
|
106
|
-
|
|
107
|
-
Ok(())
|
|
108
|
-
}
|
|
@@ -1,573 +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 std::cell::RefCell;
|
|
7
|
-
use std::net::{TcpStream, ToSocketAddrs};
|
|
8
|
-
use std::time::Duration;
|
|
9
|
-
use tungstenite::stream::MaybeTlsStream;
|
|
10
|
-
use tungstenite::{Message, WebSocket};
|
|
11
|
-
use url::Url;
|
|
12
|
-
|
|
13
|
-
#[derive(Debug)]
|
|
14
|
-
pub enum WebSocketIoError {
|
|
15
|
-
Timeout,
|
|
16
|
-
Closed,
|
|
17
|
-
Other(String),
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
#[derive(Debug)]
|
|
21
|
-
pub struct WebSocketConnection {
|
|
22
|
-
stream: WebSocket<MaybeTlsStream<TcpStream>>,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
impl WebSocketConnection {
|
|
26
|
-
pub(crate) fn connect(url: Url, timeout: Duration) -> Result<Self, WebSocketIoError> {
|
|
27
|
-
if url.scheme() == "wss" {
|
|
28
|
-
return Err(WebSocketIoError::Other(
|
|
29
|
-
"wss is not supported for Ruby test sockets".to_string(),
|
|
30
|
-
));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let host = url
|
|
34
|
-
.host_str()
|
|
35
|
-
.ok_or_else(|| WebSocketIoError::Other("Missing WebSocket host".to_string()))?;
|
|
36
|
-
let port = url
|
|
37
|
-
.port_or_known_default()
|
|
38
|
-
.ok_or_else(|| WebSocketIoError::Other("Missing WebSocket port".to_string()))?;
|
|
39
|
-
let addr = (host, port)
|
|
40
|
-
.to_socket_addrs()
|
|
41
|
-
.map_err(|err| WebSocketIoError::Other(err.to_string()))?
|
|
42
|
-
.next()
|
|
43
|
-
.ok_or_else(|| WebSocketIoError::Other("Unable to resolve WebSocket host".to_string()))?;
|
|
44
|
-
let tcp_stream = TcpStream::connect_timeout(&addr, timeout).map_err(map_io_error)?;
|
|
45
|
-
tcp_stream
|
|
46
|
-
.set_read_timeout(Some(timeout))
|
|
47
|
-
.map_err(|err| WebSocketIoError::Other(err.to_string()))?;
|
|
48
|
-
tcp_stream
|
|
49
|
-
.set_write_timeout(Some(timeout))
|
|
50
|
-
.map_err(|err| WebSocketIoError::Other(err.to_string()))?;
|
|
51
|
-
|
|
52
|
-
let request = url.as_str();
|
|
53
|
-
let (stream, _) = tungstenite::client::client(request, MaybeTlsStream::Plain(tcp_stream))
|
|
54
|
-
.map_err(|err| WebSocketIoError::Other(err.to_string()))?;
|
|
55
|
-
Ok(Self { stream })
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
pub(crate) fn send_text(&mut self, text: String) -> Result<(), WebSocketIoError> {
|
|
59
|
-
self.stream
|
|
60
|
-
.write_message(Message::Text(text.into()))
|
|
61
|
-
.map_err(map_tungstenite_error)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
pub(crate) fn send_json(&mut self, json_value: &JsonValue) -> Result<(), WebSocketIoError> {
|
|
65
|
-
let text = serde_json::to_string(json_value).map_err(|err| WebSocketIoError::Other(err.to_string()))?;
|
|
66
|
-
self.send_text(text)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
pub(crate) fn receive_message(&mut self) -> Result<Message, WebSocketIoError> {
|
|
70
|
-
match self.stream.read_message() {
|
|
71
|
-
Ok(message) => Ok(message),
|
|
72
|
-
Err(err) => Err(map_tungstenite_error(err)),
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
pub(crate) fn receive_text(&mut self) -> Result<String, WebSocketIoError> {
|
|
77
|
-
let message = self.receive_message()?;
|
|
78
|
-
message_to_text(message)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
pub(crate) fn receive_json(&mut self) -> Result<JsonValue, WebSocketIoError> {
|
|
82
|
-
let text = self.receive_text()?;
|
|
83
|
-
serde_json::from_str(&text).map_err(|err| WebSocketIoError::Other(err.to_string()))
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
pub(crate) fn receive_bytes(&mut self) -> Result<bytes::Bytes, WebSocketIoError> {
|
|
87
|
-
let message = self.receive_message()?;
|
|
88
|
-
message_to_bytes(message)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
pub(crate) fn close(mut self) -> Result<(), WebSocketIoError> {
|
|
92
|
-
self.stream.close(None).map_err(map_tungstenite_error)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
fn map_tungstenite_error(err: tungstenite::Error) -> WebSocketIoError {
|
|
97
|
-
match err {
|
|
98
|
-
tungstenite::Error::ConnectionClosed | tungstenite::Error::AlreadyClosed => WebSocketIoError::Closed,
|
|
99
|
-
tungstenite::Error::Io(io_err) => match io_err.kind() {
|
|
100
|
-
std::io::ErrorKind::TimedOut | std::io::ErrorKind::WouldBlock => WebSocketIoError::Timeout,
|
|
101
|
-
_ => WebSocketIoError::Other(io_err.to_string()),
|
|
102
|
-
},
|
|
103
|
-
other => WebSocketIoError::Other(other.to_string()),
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
fn map_io_error(err: std::io::Error) -> WebSocketIoError {
|
|
108
|
-
match err.kind() {
|
|
109
|
-
std::io::ErrorKind::TimedOut | std::io::ErrorKind::WouldBlock => WebSocketIoError::Timeout,
|
|
110
|
-
_ => WebSocketIoError::Other(err.to_string()),
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
fn message_to_text(message: Message) -> Result<String, WebSocketIoError> {
|
|
115
|
-
match message {
|
|
116
|
-
Message::Text(text) => Ok(text.to_string()),
|
|
117
|
-
Message::Binary(bytes) => {
|
|
118
|
-
String::from_utf8(bytes.to_vec()).map_err(|err| WebSocketIoError::Other(err.to_string()))
|
|
119
|
-
}
|
|
120
|
-
Message::Close(frame) => Ok(frame.map(|f| f.reason.to_string()).unwrap_or_default()),
|
|
121
|
-
Message::Ping(_) | Message::Pong(_) => Ok(String::new()),
|
|
122
|
-
Message::Frame(_) => Err(WebSocketIoError::Other(
|
|
123
|
-
"Unexpected frame message while reading text".to_string(),
|
|
124
|
-
)),
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
fn message_to_bytes(message: Message) -> Result<bytes::Bytes, WebSocketIoError> {
|
|
129
|
-
match message {
|
|
130
|
-
Message::Text(text) => Ok(bytes::Bytes::from(text.to_string())),
|
|
131
|
-
Message::Binary(bytes) => Ok(bytes),
|
|
132
|
-
Message::Close(frame) => Ok(bytes::Bytes::from(
|
|
133
|
-
frame.map(|f| f.reason.to_string()).unwrap_or_default(),
|
|
134
|
-
)),
|
|
135
|
-
Message::Ping(data) => Ok(data),
|
|
136
|
-
Message::Pong(data) => Ok(data),
|
|
137
|
-
Message::Frame(_) => Err(WebSocketIoError::Other(
|
|
138
|
-
"Unexpected frame message while reading bytes".to_string(),
|
|
139
|
-
)),
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/// Ruby wrapper for WebSocket test client
|
|
144
|
-
#[derive(Default)]
|
|
145
|
-
#[magnus::wrap(class = "Spikard::Native::WebSocketTestConnection", free_immediately)]
|
|
146
|
-
pub struct WebSocketTestConnection {
|
|
147
|
-
inner: RefCell<Option<WebSocketConnection>>,
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
impl WebSocketTestConnection {
|
|
151
|
-
/// Create a new WebSocket test connection (public for lib.rs)
|
|
152
|
-
pub(crate) fn new(inner: WebSocketConnection) -> Self {
|
|
153
|
-
Self {
|
|
154
|
-
inner: RefCell::new(Some(inner)),
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/// Send a text message
|
|
159
|
-
fn send_text(&self, text: String) -> Result<(), Error> {
|
|
160
|
-
let mut inner = self.inner.borrow_mut();
|
|
161
|
-
let ws = inner
|
|
162
|
-
.as_mut()
|
|
163
|
-
.ok_or_else(|| Error::new(magnus::exception::runtime_error(), "WebSocket closed"))?;
|
|
164
|
-
|
|
165
|
-
let timeout_duration = websocket_timeout();
|
|
166
|
-
let result = crate::call_without_gvl!(
|
|
167
|
-
block_on_send_text,
|
|
168
|
-
args: (timeout_duration, Duration, ws, &mut WebSocketConnection, text, String),
|
|
169
|
-
return_type: Result<(), WebSocketIoError>
|
|
170
|
-
);
|
|
171
|
-
match result {
|
|
172
|
-
Ok(()) => Ok(()),
|
|
173
|
-
Err(WebSocketIoError::Timeout) => Err(Error::new(
|
|
174
|
-
magnus::exception::runtime_error(),
|
|
175
|
-
format!("WebSocket send timed out after {}ms", timeout_duration.as_millis()),
|
|
176
|
-
)),
|
|
177
|
-
Err(WebSocketIoError::Closed) => Err(Error::new(
|
|
178
|
-
magnus::exception::runtime_error(),
|
|
179
|
-
"WebSocket connection closed".to_string(),
|
|
180
|
-
)),
|
|
181
|
-
Err(WebSocketIoError::Other(message)) => Err(Error::new(
|
|
182
|
-
magnus::exception::runtime_error(),
|
|
183
|
-
format!("WebSocket send failed: {}", message),
|
|
184
|
-
)),
|
|
185
|
-
}?;
|
|
186
|
-
|
|
187
|
-
Ok(())
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/// Send a JSON message
|
|
191
|
-
fn send_json(ruby: &Ruby, this: &Self, obj: Value) -> Result<(), Error> {
|
|
192
|
-
let json_value = ruby_to_json(ruby, obj)?;
|
|
193
|
-
let mut inner = this.inner.borrow_mut();
|
|
194
|
-
let ws = inner
|
|
195
|
-
.as_mut()
|
|
196
|
-
.ok_or_else(|| Error::new(ruby.exception_runtime_error(), "WebSocket closed"))?;
|
|
197
|
-
|
|
198
|
-
let timeout_duration = websocket_timeout();
|
|
199
|
-
let json_value_ref = &json_value;
|
|
200
|
-
let result = crate::call_without_gvl!(
|
|
201
|
-
block_on_send_json,
|
|
202
|
-
args: (
|
|
203
|
-
timeout_duration, Duration,
|
|
204
|
-
ws, &mut WebSocketConnection,
|
|
205
|
-
json_value_ref, &JsonValue
|
|
206
|
-
),
|
|
207
|
-
return_type: Result<(), WebSocketIoError>
|
|
208
|
-
);
|
|
209
|
-
match result {
|
|
210
|
-
Ok(()) => Ok(()),
|
|
211
|
-
Err(WebSocketIoError::Timeout) => Err(Error::new(
|
|
212
|
-
ruby.exception_runtime_error(),
|
|
213
|
-
format!("WebSocket send timed out after {}ms", timeout_duration.as_millis()),
|
|
214
|
-
)),
|
|
215
|
-
Err(WebSocketIoError::Closed) => Err(Error::new(
|
|
216
|
-
ruby.exception_runtime_error(),
|
|
217
|
-
"WebSocket connection closed".to_string(),
|
|
218
|
-
)),
|
|
219
|
-
Err(WebSocketIoError::Other(message)) => Err(Error::new(
|
|
220
|
-
ruby.exception_runtime_error(),
|
|
221
|
-
format!("WebSocket send failed: {}", message),
|
|
222
|
-
)),
|
|
223
|
-
}?;
|
|
224
|
-
|
|
225
|
-
Ok(())
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/// Receive a text message
|
|
229
|
-
fn receive_text(&self) -> Result<String, Error> {
|
|
230
|
-
let mut inner = self.inner.borrow_mut();
|
|
231
|
-
let ws = inner
|
|
232
|
-
.as_mut()
|
|
233
|
-
.ok_or_else(|| Error::new(magnus::exception::runtime_error(), "WebSocket closed"))?;
|
|
234
|
-
|
|
235
|
-
let timeout_duration = websocket_timeout();
|
|
236
|
-
let text = crate::call_without_gvl!(
|
|
237
|
-
block_on_receive_text,
|
|
238
|
-
args: (
|
|
239
|
-
timeout_duration, Duration,
|
|
240
|
-
ws, &mut WebSocketConnection
|
|
241
|
-
),
|
|
242
|
-
return_type: Result<String, WebSocketIoError>
|
|
243
|
-
)
|
|
244
|
-
.map_err(|err| match err {
|
|
245
|
-
WebSocketIoError::Timeout => Error::new(
|
|
246
|
-
magnus::exception::runtime_error(),
|
|
247
|
-
format!("WebSocket receive timed out after {}ms", timeout_duration.as_millis()),
|
|
248
|
-
),
|
|
249
|
-
WebSocketIoError::Closed => Error::new(
|
|
250
|
-
magnus::exception::runtime_error(),
|
|
251
|
-
"WebSocket connection closed".to_string(),
|
|
252
|
-
),
|
|
253
|
-
WebSocketIoError::Other(message) => Error::new(
|
|
254
|
-
magnus::exception::runtime_error(),
|
|
255
|
-
format!("WebSocket receive failed: {}", message),
|
|
256
|
-
),
|
|
257
|
-
})?;
|
|
258
|
-
|
|
259
|
-
Ok(text)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/// Receive and parse a JSON message
|
|
263
|
-
fn receive_json(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
264
|
-
let mut inner = this.inner.borrow_mut();
|
|
265
|
-
let ws = inner
|
|
266
|
-
.as_mut()
|
|
267
|
-
.ok_or_else(|| Error::new(ruby.exception_runtime_error(), "WebSocket closed"))?;
|
|
268
|
-
|
|
269
|
-
let timeout_duration = websocket_timeout();
|
|
270
|
-
let json_value = crate::call_without_gvl!(
|
|
271
|
-
block_on_receive_json,
|
|
272
|
-
args: (
|
|
273
|
-
timeout_duration, Duration,
|
|
274
|
-
ws, &mut WebSocketConnection
|
|
275
|
-
),
|
|
276
|
-
return_type: Result<JsonValue, WebSocketIoError>
|
|
277
|
-
)
|
|
278
|
-
.map_err(|err| match err {
|
|
279
|
-
WebSocketIoError::Timeout => Error::new(
|
|
280
|
-
ruby.exception_runtime_error(),
|
|
281
|
-
format!("WebSocket receive timed out after {}ms", timeout_duration.as_millis()),
|
|
282
|
-
),
|
|
283
|
-
WebSocketIoError::Closed => Error::new(
|
|
284
|
-
ruby.exception_runtime_error(),
|
|
285
|
-
"WebSocket connection closed".to_string(),
|
|
286
|
-
),
|
|
287
|
-
WebSocketIoError::Other(message) => Error::new(
|
|
288
|
-
ruby.exception_runtime_error(),
|
|
289
|
-
format!("WebSocket receive failed: {}", message),
|
|
290
|
-
),
|
|
291
|
-
})?;
|
|
292
|
-
|
|
293
|
-
json_to_ruby(ruby, &json_value)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/// Receive raw bytes
|
|
297
|
-
fn receive_bytes(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
298
|
-
let mut inner = this.inner.borrow_mut();
|
|
299
|
-
let ws = inner
|
|
300
|
-
.as_mut()
|
|
301
|
-
.ok_or_else(|| Error::new(ruby.exception_runtime_error(), "WebSocket closed"))?;
|
|
302
|
-
|
|
303
|
-
let timeout_duration = websocket_timeout();
|
|
304
|
-
let result = crate::call_without_gvl!(
|
|
305
|
-
block_on_receive_bytes,
|
|
306
|
-
args: (
|
|
307
|
-
timeout_duration, Duration,
|
|
308
|
-
ws, &mut WebSocketConnection
|
|
309
|
-
),
|
|
310
|
-
return_type: Result<bytes::Bytes, WebSocketIoError>
|
|
311
|
-
);
|
|
312
|
-
let bytes = result.map_err(|err| match err {
|
|
313
|
-
WebSocketIoError::Timeout => Error::new(
|
|
314
|
-
ruby.exception_runtime_error(),
|
|
315
|
-
format!("WebSocket receive timed out after {}ms", timeout_duration.as_millis()),
|
|
316
|
-
),
|
|
317
|
-
WebSocketIoError::Closed => Error::new(
|
|
318
|
-
ruby.exception_runtime_error(),
|
|
319
|
-
"WebSocket connection closed".to_string(),
|
|
320
|
-
),
|
|
321
|
-
WebSocketIoError::Other(message) => Error::new(
|
|
322
|
-
ruby.exception_runtime_error(),
|
|
323
|
-
format!("WebSocket receive failed: {}", message),
|
|
324
|
-
),
|
|
325
|
-
})?;
|
|
326
|
-
|
|
327
|
-
Ok(ruby.str_from_slice(&bytes).as_value())
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/// Receive a message and return WebSocketMessage
|
|
331
|
-
fn receive_message(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
332
|
-
let mut inner = this.inner.borrow_mut();
|
|
333
|
-
let ws = inner
|
|
334
|
-
.as_mut()
|
|
335
|
-
.ok_or_else(|| Error::new(ruby.exception_runtime_error(), "WebSocket closed"))?;
|
|
336
|
-
|
|
337
|
-
let timeout_duration = websocket_timeout();
|
|
338
|
-
let result = crate::call_without_gvl!(
|
|
339
|
-
block_on_receive_message,
|
|
340
|
-
args: (
|
|
341
|
-
timeout_duration, Duration,
|
|
342
|
-
ws, &mut WebSocketConnection
|
|
343
|
-
),
|
|
344
|
-
return_type: Result<WebSocketMessageData, WebSocketIoError>
|
|
345
|
-
);
|
|
346
|
-
let msg = result.map_err(|err| match err {
|
|
347
|
-
WebSocketIoError::Timeout => Error::new(
|
|
348
|
-
ruby.exception_runtime_error(),
|
|
349
|
-
format!("WebSocket receive timed out after {}ms", timeout_duration.as_millis()),
|
|
350
|
-
),
|
|
351
|
-
WebSocketIoError::Closed => Error::new(
|
|
352
|
-
ruby.exception_runtime_error(),
|
|
353
|
-
"WebSocket connection closed".to_string(),
|
|
354
|
-
),
|
|
355
|
-
WebSocketIoError::Other(message) => Error::new(
|
|
356
|
-
ruby.exception_runtime_error(),
|
|
357
|
-
format!("WebSocket receive failed: {}", message),
|
|
358
|
-
),
|
|
359
|
-
})?;
|
|
360
|
-
|
|
361
|
-
let ws_msg = WebSocketMessage::new(msg);
|
|
362
|
-
Ok(ruby.obj_wrap(ws_msg).as_value())
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/// Close the WebSocket connection
|
|
366
|
-
fn close(&self) -> Result<(), Error> {
|
|
367
|
-
let mut inner = self.inner.borrow_mut();
|
|
368
|
-
let ws = inner
|
|
369
|
-
.take()
|
|
370
|
-
.ok_or_else(|| Error::new(magnus::exception::runtime_error(), "WebSocket closed"))?;
|
|
371
|
-
let result = crate::call_without_gvl!(
|
|
372
|
-
block_on_close,
|
|
373
|
-
args: (ws, WebSocketConnection),
|
|
374
|
-
return_type: Result<(), WebSocketIoError>
|
|
375
|
-
);
|
|
376
|
-
result.map_err(|err| match err {
|
|
377
|
-
WebSocketIoError::Timeout => Error::new(
|
|
378
|
-
magnus::exception::runtime_error(),
|
|
379
|
-
"WebSocket close timed out".to_string(),
|
|
380
|
-
),
|
|
381
|
-
WebSocketIoError::Closed => Error::new(
|
|
382
|
-
magnus::exception::runtime_error(),
|
|
383
|
-
"WebSocket connection closed".to_string(),
|
|
384
|
-
),
|
|
385
|
-
WebSocketIoError::Other(message) => Error::new(
|
|
386
|
-
magnus::exception::runtime_error(),
|
|
387
|
-
format!("WebSocket close failed: {}", message),
|
|
388
|
-
),
|
|
389
|
-
})
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
fn block_on_send_text(
|
|
394
|
-
timeout_duration: Duration,
|
|
395
|
-
ws: &mut WebSocketConnection,
|
|
396
|
-
text: String,
|
|
397
|
-
) -> Result<(), WebSocketIoError> {
|
|
398
|
-
set_stream_timeouts(ws, timeout_duration)?;
|
|
399
|
-
ws.send_text(text)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
fn block_on_send_json(
|
|
403
|
-
timeout_duration: Duration,
|
|
404
|
-
ws: &mut WebSocketConnection,
|
|
405
|
-
json_value: &JsonValue,
|
|
406
|
-
) -> Result<(), WebSocketIoError> {
|
|
407
|
-
set_stream_timeouts(ws, timeout_duration)?;
|
|
408
|
-
ws.send_json(json_value)
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
fn block_on_receive_bytes(
|
|
412
|
-
timeout_duration: Duration,
|
|
413
|
-
ws: &mut WebSocketConnection,
|
|
414
|
-
) -> Result<bytes::Bytes, WebSocketIoError> {
|
|
415
|
-
set_stream_timeouts(ws, timeout_duration)?;
|
|
416
|
-
ws.receive_bytes()
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
fn block_on_receive_text(timeout_duration: Duration, ws: &mut WebSocketConnection) -> Result<String, WebSocketIoError> {
|
|
420
|
-
set_stream_timeouts(ws, timeout_duration)?;
|
|
421
|
-
ws.receive_text()
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
fn block_on_receive_json(
|
|
425
|
-
timeout_duration: Duration,
|
|
426
|
-
ws: &mut WebSocketConnection,
|
|
427
|
-
) -> Result<JsonValue, WebSocketIoError> {
|
|
428
|
-
set_stream_timeouts(ws, timeout_duration)?;
|
|
429
|
-
ws.receive_json()
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
fn block_on_receive_message(
|
|
433
|
-
timeout_duration: Duration,
|
|
434
|
-
ws: &mut WebSocketConnection,
|
|
435
|
-
) -> Result<WebSocketMessageData, WebSocketIoError> {
|
|
436
|
-
set_stream_timeouts(ws, timeout_duration)?;
|
|
437
|
-
ws.receive_message().and_then(WebSocketMessageData::from_tungstenite)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
fn block_on_close(ws: WebSocketConnection) -> Result<(), WebSocketIoError> {
|
|
441
|
-
ws.close()
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
fn set_stream_timeouts(ws: &mut WebSocketConnection, timeout: Duration) -> Result<(), WebSocketIoError> {
|
|
445
|
-
if let MaybeTlsStream::Plain(stream) = ws.stream.get_mut() {
|
|
446
|
-
stream
|
|
447
|
-
.set_read_timeout(Some(timeout))
|
|
448
|
-
.map_err(|e| WebSocketIoError::Other(e.to_string()))?;
|
|
449
|
-
stream
|
|
450
|
-
.set_write_timeout(Some(timeout))
|
|
451
|
-
.map_err(|e| WebSocketIoError::Other(e.to_string()))?;
|
|
452
|
-
}
|
|
453
|
-
Ok(())
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
fn websocket_timeout() -> Duration {
|
|
457
|
-
const DEFAULT_TIMEOUT_MS: u64 = 30_000;
|
|
458
|
-
let timeout_ms = std::env::var("SPIKARD_RB_WS_TIMEOUT_MS")
|
|
459
|
-
.ok()
|
|
460
|
-
.and_then(|value| value.parse::<u64>().ok())
|
|
461
|
-
.unwrap_or(DEFAULT_TIMEOUT_MS);
|
|
462
|
-
Duration::from_millis(timeout_ms)
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/// Ruby wrapper for WebSocket messages
|
|
466
|
-
#[allow(dead_code)]
|
|
467
|
-
#[derive(Debug, Clone)]
|
|
468
|
-
pub enum WebSocketMessageData {
|
|
469
|
-
Text(String),
|
|
470
|
-
Binary(Vec<u8>),
|
|
471
|
-
Close(Option<String>),
|
|
472
|
-
Ping(Vec<u8>),
|
|
473
|
-
Pong(Vec<u8>),
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
impl WebSocketMessageData {
|
|
477
|
-
fn from_tungstenite(message: Message) -> Result<Self, WebSocketIoError> {
|
|
478
|
-
match message {
|
|
479
|
-
Message::Text(text) => Ok(Self::Text(text.to_string())),
|
|
480
|
-
Message::Binary(bytes) => Ok(Self::Binary(bytes.to_vec())),
|
|
481
|
-
Message::Close(frame) => Ok(Self::Close(frame.map(|f| f.reason.to_string()))),
|
|
482
|
-
Message::Ping(bytes) => Ok(Self::Ping(bytes.to_vec())),
|
|
483
|
-
Message::Pong(bytes) => Ok(Self::Pong(bytes.to_vec())),
|
|
484
|
-
Message::Frame(_) => Err(WebSocketIoError::Other(
|
|
485
|
-
"Unexpected frame message while reading WebSocket".to_string(),
|
|
486
|
-
)),
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
#[magnus::wrap(class = "Spikard::Native::WebSocketMessage", free_immediately)]
|
|
492
|
-
pub struct WebSocketMessage {
|
|
493
|
-
inner: WebSocketMessageData,
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
impl WebSocketMessage {
|
|
497
|
-
pub fn new(inner: WebSocketMessageData) -> Self {
|
|
498
|
-
Self { inner }
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/// Get message as text if it's a text message
|
|
502
|
-
fn as_text(&self) -> Result<Option<String>, Error> {
|
|
503
|
-
match &self.inner {
|
|
504
|
-
WebSocketMessageData::Text(text) => Ok(Some(text.clone())),
|
|
505
|
-
_ => Ok(None),
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/// Get message as JSON if it's a text message containing JSON
|
|
510
|
-
fn as_json(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
511
|
-
match &this.inner {
|
|
512
|
-
WebSocketMessageData::Text(text) => match serde_json::from_str::<JsonValue>(text) {
|
|
513
|
-
Ok(value) => json_to_ruby(ruby, &value),
|
|
514
|
-
Err(_) => Ok(ruby.qnil().as_value()),
|
|
515
|
-
},
|
|
516
|
-
_ => Ok(ruby.qnil().as_value()),
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/// Get message as binary if it's a binary message
|
|
521
|
-
fn as_binary(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
522
|
-
match &this.inner {
|
|
523
|
-
WebSocketMessageData::Binary(bytes) => Ok(ruby.str_from_slice(bytes).as_value()),
|
|
524
|
-
WebSocketMessageData::Ping(bytes) => Ok(ruby.str_from_slice(bytes).as_value()),
|
|
525
|
-
WebSocketMessageData::Pong(bytes) => Ok(ruby.str_from_slice(bytes).as_value()),
|
|
526
|
-
_ => Ok(ruby.qnil().as_value()),
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/// Check if this is a close message
|
|
531
|
-
fn is_close(&self) -> bool {
|
|
532
|
-
matches!(self.inner, WebSocketMessageData::Close(_))
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/// Helper to convert Ruby object to JSON
|
|
537
|
-
fn ruby_to_json(ruby: &Ruby, value: Value) -> Result<JsonValue, Error> {
|
|
538
|
-
let json_module = ruby.class_object().const_get::<_, magnus::RModule>("JSON")?;
|
|
539
|
-
let json_str: String = json_module.funcall("generate", (value,))?;
|
|
540
|
-
serde_json::from_str(&json_str).map_err(|e| {
|
|
541
|
-
Error::new(
|
|
542
|
-
magnus::exception::runtime_error(),
|
|
543
|
-
format!("Failed to parse JSON: {}", e),
|
|
544
|
-
)
|
|
545
|
-
})
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
/// Helper to convert JSON to Ruby object
|
|
549
|
-
fn json_to_ruby(ruby: &Ruby, value: &JsonValue) -> Result<Value, Error> {
|
|
550
|
-
crate::conversion::json_to_ruby(ruby, value)
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/// Initialize WebSocket test client bindings
|
|
554
|
-
pub fn init(ruby: &Ruby, module: &magnus::RModule) -> Result<(), Error> {
|
|
555
|
-
let native_module = module.define_module("Native")?;
|
|
556
|
-
|
|
557
|
-
let ws_conn_class = native_module.define_class("WebSocketTestConnection", ruby.class_object())?;
|
|
558
|
-
ws_conn_class.define_method("send_text", method!(WebSocketTestConnection::send_text, 1))?;
|
|
559
|
-
ws_conn_class.define_method("send_json", method!(WebSocketTestConnection::send_json, 1))?;
|
|
560
|
-
ws_conn_class.define_method("receive_text", method!(WebSocketTestConnection::receive_text, 0))?;
|
|
561
|
-
ws_conn_class.define_method("receive_json", method!(WebSocketTestConnection::receive_json, 0))?;
|
|
562
|
-
ws_conn_class.define_method("receive_bytes", method!(WebSocketTestConnection::receive_bytes, 0))?;
|
|
563
|
-
ws_conn_class.define_method("receive_message", method!(WebSocketTestConnection::receive_message, 0))?;
|
|
564
|
-
ws_conn_class.define_method("close", method!(WebSocketTestConnection::close, 0))?;
|
|
565
|
-
|
|
566
|
-
let ws_msg_class = native_module.define_class("WebSocketMessage", ruby.class_object())?;
|
|
567
|
-
ws_msg_class.define_method("as_text", method!(WebSocketMessage::as_text, 0))?;
|
|
568
|
-
ws_msg_class.define_method("as_json", method!(WebSocketMessage::as_json, 0))?;
|
|
569
|
-
ws_msg_class.define_method("as_binary", method!(WebSocketMessage::as_binary, 0))?;
|
|
570
|
-
ws_msg_class.define_method("is_close", method!(WebSocketMessage::is_close, 0))?;
|
|
571
|
-
|
|
572
|
-
Ok(())
|
|
573
|
-
}
|