spikard 0.12.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} +897 -451
- 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 -45
- data/lib/spikard_rb.so +0 -0
- data/sig/types.rbs +427 -0
- metadata +14 -242
- data/LICENSE +0 -1
- data/README.md +0 -267
- data/ext/spikard_rb/Cargo.toml +0 -17
- data/lib/spikard/app.rb +0 -428
- 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 -182
- data/lib/spikard/handler_wrapper.rb +0 -113
- data/lib/spikard/provide.rb +0 -214
- data/lib/spikard/response.rb +0 -173
- 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 -432
- data/lib/spikard/upload_file.rb +0 -131
- data/lib/spikard/websocket.rb +0 -59
- data/sig/spikard.rbs +0 -719
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +0 -80
- 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 -60
- 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 -702
- 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 -538
- 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 -87
- 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 -1860
- 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 -469
- data/vendor/crates/spikard-http/src/grpc/handler.rs +0 -1122
- data/vendor/crates/spikard-http/src/grpc/mod.rs +0 -434
- data/vendor/crates/spikard-http/src/grpc/service.rs +0 -622
- 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 -58
- 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 -548
- 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 -858
- 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 -1649
- 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 -787
- 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 -974
- 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 -314
- data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +0 -200
- 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 -421
- 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 -68
- 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 -375
- data/vendor/crates/spikard-rb/src/grpc/handler.rs +0 -834
- 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 -2264
- data/vendor/crates/spikard-rb/src/lifecycle.rs +0 -303
- 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 -344
- data/vendor/crates/spikard-rb/src/server.rs +0 -307
- 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 -475
- data/vendor/crates/spikard-rb-macros/Cargo.toml +0 -25
- data/vendor/crates/spikard-rb-macros/src/lib.rs +0 -51
|
@@ -1,475 +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, Ruby, Value, prelude::*, value::Opaque};
|
|
7
|
-
use serde_json::Value as JsonValue;
|
|
8
|
-
use spikard_http::WebSocketHandler;
|
|
9
|
-
use std::sync::mpsc;
|
|
10
|
-
use tokio::sync::oneshot;
|
|
11
|
-
use tracing::{debug, error};
|
|
12
|
-
|
|
13
|
-
/// Ruby implementation of WebSocketHandler
|
|
14
|
-
pub struct RubyWebSocketHandler {
|
|
15
|
-
/// Handler name for debugging
|
|
16
|
-
name: String,
|
|
17
|
-
/// Ruby proc/callable for handle_message (Opaque for Send safety)
|
|
18
|
-
#[allow(dead_code)]
|
|
19
|
-
handle_message_proc: Opaque<Value>,
|
|
20
|
-
/// Ruby proc/callable for on_connect (Opaque for Send safety)
|
|
21
|
-
on_connect_proc: Option<Opaque<Value>>,
|
|
22
|
-
/// Ruby proc/callable for on_disconnect (Opaque for Send safety)
|
|
23
|
-
on_disconnect_proc: Option<Opaque<Value>>,
|
|
24
|
-
/// Work queue for executing Ruby callbacks on a Ruby thread
|
|
25
|
-
work_tx: mpsc::Sender<WebSocketWorkItem>,
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
enum WebSocketWorkItem {
|
|
29
|
-
HandleMessage {
|
|
30
|
-
message: JsonValue,
|
|
31
|
-
reply: oneshot::Sender<Result<Option<JsonValue>, String>>,
|
|
32
|
-
},
|
|
33
|
-
OnConnect {
|
|
34
|
-
reply: oneshot::Sender<Result<(), String>>,
|
|
35
|
-
},
|
|
36
|
-
OnDisconnect {
|
|
37
|
-
reply: oneshot::Sender<Result<(), String>>,
|
|
38
|
-
},
|
|
39
|
-
Shutdown,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
enum WebSocketFactoryWorkItem {
|
|
43
|
-
Build {
|
|
44
|
-
reply: mpsc::Sender<Result<RubyWebSocketHandler, String>>,
|
|
45
|
-
},
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
impl RubyWebSocketHandler {
|
|
49
|
-
/// Create a new Ruby WebSocket handler
|
|
50
|
-
#[allow(dead_code)]
|
|
51
|
-
pub fn new(
|
|
52
|
-
ruby: &magnus::Ruby,
|
|
53
|
-
name: String,
|
|
54
|
-
handle_message_proc: Value,
|
|
55
|
-
on_connect_proc: Option<Value>,
|
|
56
|
-
on_disconnect_proc: Option<Value>,
|
|
57
|
-
) -> Self {
|
|
58
|
-
let handle_message_proc = Opaque::from(handle_message_proc);
|
|
59
|
-
let on_connect_proc = on_connect_proc.map(Opaque::from);
|
|
60
|
-
let on_disconnect_proc = on_disconnect_proc.map(Opaque::from);
|
|
61
|
-
let (work_tx, work_rx) = mpsc::channel();
|
|
62
|
-
let handler_name = name.clone();
|
|
63
|
-
|
|
64
|
-
let handle_message_proc_for_thread = handle_message_proc;
|
|
65
|
-
let on_connect_proc_for_thread = on_connect_proc;
|
|
66
|
-
let on_disconnect_proc_for_thread = on_disconnect_proc;
|
|
67
|
-
|
|
68
|
-
ruby.thread_create_from_fn(move |ruby| {
|
|
69
|
-
websocket_worker_loop(
|
|
70
|
-
ruby,
|
|
71
|
-
&handler_name,
|
|
72
|
-
handle_message_proc_for_thread,
|
|
73
|
-
on_connect_proc_for_thread,
|
|
74
|
-
on_disconnect_proc_for_thread,
|
|
75
|
-
work_rx,
|
|
76
|
-
);
|
|
77
|
-
ruby.qnil()
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
Self {
|
|
81
|
-
name,
|
|
82
|
-
handle_message_proc,
|
|
83
|
-
on_connect_proc,
|
|
84
|
-
on_disconnect_proc,
|
|
85
|
-
work_tx,
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/// Convert Ruby value to JSON
|
|
90
|
-
fn ruby_to_json(ruby: &magnus::Ruby, value: Value) -> Result<JsonValue, String> {
|
|
91
|
-
if value.is_nil() {
|
|
92
|
-
return Ok(JsonValue::Null);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
let json_module: Value = ruby
|
|
96
|
-
.class_object()
|
|
97
|
-
.const_get("JSON")
|
|
98
|
-
.map_err(|e| format!("JSON module not available: {}", e))?;
|
|
99
|
-
|
|
100
|
-
let json_str: String = json_module
|
|
101
|
-
.funcall("generate", (value,))
|
|
102
|
-
.map_err(|e| format!("Failed to generate JSON: {}", e))?;
|
|
103
|
-
|
|
104
|
-
serde_json::from_str(&json_str).map_err(|e| format!("Failed to parse JSON: {}", e))
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/// Convert JSON value to Ruby value
|
|
108
|
-
fn json_to_ruby(ruby: &magnus::Ruby, value: &JsonValue) -> Result<Value, String> {
|
|
109
|
-
match value {
|
|
110
|
-
JsonValue::Null => Ok(ruby.qnil().as_value()),
|
|
111
|
-
JsonValue::Bool(b) => Ok(if *b {
|
|
112
|
-
ruby.qtrue().as_value()
|
|
113
|
-
} else {
|
|
114
|
-
ruby.qfalse().as_value()
|
|
115
|
-
}),
|
|
116
|
-
JsonValue::Number(num) => {
|
|
117
|
-
if let Some(i) = num.as_i64() {
|
|
118
|
-
Ok(ruby.integer_from_i64(i).as_value())
|
|
119
|
-
} else if let Some(f) = num.as_f64() {
|
|
120
|
-
Ok(ruby.float_from_f64(f).as_value())
|
|
121
|
-
} else {
|
|
122
|
-
Ok(ruby.qnil().as_value())
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
JsonValue::String(s) => Ok(ruby.str_new(s).as_value()),
|
|
126
|
-
JsonValue::Array(arr) => {
|
|
127
|
-
let ruby_array = ruby.ary_new();
|
|
128
|
-
for item in arr {
|
|
129
|
-
ruby_array
|
|
130
|
-
.push(Self::json_to_ruby(ruby, item)?)
|
|
131
|
-
.map_err(|e| format!("Failed to push to array: {}", e))?;
|
|
132
|
-
}
|
|
133
|
-
Ok(ruby_array.as_value())
|
|
134
|
-
}
|
|
135
|
-
JsonValue::Object(obj) => {
|
|
136
|
-
let ruby_hash = RHash::new();
|
|
137
|
-
for (key, val) in obj {
|
|
138
|
-
ruby_hash
|
|
139
|
-
.aset(ruby.str_new(key), Self::json_to_ruby(ruby, val)?)
|
|
140
|
-
.map_err(|e| format!("Failed to set hash value: {}", e))?;
|
|
141
|
-
}
|
|
142
|
-
Ok(ruby_hash.as_value())
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
fn from_handler(ruby: &Ruby, handler_obj: Value) -> Result<Self, magnus::Error> {
|
|
148
|
-
let handle_message_proc: Value = handler_obj
|
|
149
|
-
.funcall("method", (ruby.to_symbol("handle_message"),))
|
|
150
|
-
.map_err(|e| {
|
|
151
|
-
magnus::Error::new(
|
|
152
|
-
ruby.exception_arg_error(),
|
|
153
|
-
format!("handle_message method not found: {}", e),
|
|
154
|
-
)
|
|
155
|
-
})?;
|
|
156
|
-
|
|
157
|
-
let on_connect_proc = handler_obj
|
|
158
|
-
.funcall::<_, _, Value>("method", (ruby.to_symbol("on_connect"),))
|
|
159
|
-
.ok();
|
|
160
|
-
|
|
161
|
-
let on_disconnect_proc = handler_obj
|
|
162
|
-
.funcall::<_, _, Value>("method", (ruby.to_symbol("on_disconnect"),))
|
|
163
|
-
.ok();
|
|
164
|
-
|
|
165
|
-
Ok(Self::new(
|
|
166
|
-
ruby,
|
|
167
|
-
"WebSocketHandler".to_string(),
|
|
168
|
-
handle_message_proc,
|
|
169
|
-
on_connect_proc,
|
|
170
|
-
on_disconnect_proc,
|
|
171
|
-
))
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
impl WebSocketHandler for RubyWebSocketHandler {
|
|
176
|
-
async fn handle_message(&self, message: JsonValue) -> Option<JsonValue> {
|
|
177
|
-
debug!("Ruby WebSocket handler '{}': handle_message", self.name);
|
|
178
|
-
|
|
179
|
-
let (reply_tx, reply_rx) = oneshot::channel();
|
|
180
|
-
if self
|
|
181
|
-
.work_tx
|
|
182
|
-
.send(WebSocketWorkItem::HandleMessage {
|
|
183
|
-
message,
|
|
184
|
-
reply: reply_tx,
|
|
185
|
-
})
|
|
186
|
-
.is_err()
|
|
187
|
-
{
|
|
188
|
-
error!("Ruby WebSocket handler '{}' worker thread closed", self.name);
|
|
189
|
-
return None;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
let result = if let Ok(result) = reply_rx.await {
|
|
193
|
-
result
|
|
194
|
-
} else {
|
|
195
|
-
error!("Ruby WebSocket handler '{}' response channel closed", self.name);
|
|
196
|
-
return None;
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
match result {
|
|
200
|
-
Ok(value) => value,
|
|
201
|
-
Err(e) => {
|
|
202
|
-
error!("Ruby error in handle_message: {}", e);
|
|
203
|
-
None
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async fn on_connect(&self) {
|
|
209
|
-
debug!("Ruby WebSocket handler '{}': on_connect", self.name);
|
|
210
|
-
|
|
211
|
-
if self.on_connect_proc.is_some() {
|
|
212
|
-
let (reply_tx, reply_rx) = oneshot::channel();
|
|
213
|
-
if self
|
|
214
|
-
.work_tx
|
|
215
|
-
.send(WebSocketWorkItem::OnConnect { reply: reply_tx })
|
|
216
|
-
.is_err()
|
|
217
|
-
{
|
|
218
|
-
error!("Ruby WebSocket handler '{}' worker thread closed", self.name);
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
let result = if let Ok(result) = reply_rx.await {
|
|
223
|
-
result
|
|
224
|
-
} else {
|
|
225
|
-
error!("Ruby WebSocket handler '{}' on_connect channel closed", self.name);
|
|
226
|
-
return;
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
if let Err(e) = result {
|
|
230
|
-
error!("on_connect error: {}", e);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
debug!("Ruby WebSocket handler '{}': on_connect completed", self.name);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async fn on_disconnect(&self) {
|
|
238
|
-
debug!("Ruby WebSocket handler '{}': on_disconnect", self.name);
|
|
239
|
-
|
|
240
|
-
if self.on_disconnect_proc.is_some() {
|
|
241
|
-
let (reply_tx, reply_rx) = oneshot::channel();
|
|
242
|
-
if self
|
|
243
|
-
.work_tx
|
|
244
|
-
.send(WebSocketWorkItem::OnDisconnect { reply: reply_tx })
|
|
245
|
-
.is_err()
|
|
246
|
-
{
|
|
247
|
-
error!("Ruby WebSocket handler '{}' worker thread closed", self.name);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
let result = if let Ok(result) = reply_rx.await {
|
|
252
|
-
result
|
|
253
|
-
} else {
|
|
254
|
-
error!("Ruby WebSocket handler '{}' on_disconnect channel closed", self.name);
|
|
255
|
-
return;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
if let Err(e) = result {
|
|
259
|
-
error!("on_disconnect error: {}", e);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
debug!("Ruby WebSocket handler '{}': on_disconnect completed", self.name);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
impl Drop for RubyWebSocketHandler {
|
|
268
|
-
fn drop(&mut self) {
|
|
269
|
-
let _ = self.work_tx.send(WebSocketWorkItem::Shutdown);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
fn websocket_worker_loop(
|
|
274
|
-
ruby: &magnus::Ruby,
|
|
275
|
-
handler_name: &str,
|
|
276
|
-
handle_message_proc: Opaque<Value>,
|
|
277
|
-
on_connect_proc: Option<Opaque<Value>>,
|
|
278
|
-
on_disconnect_proc: Option<Opaque<Value>>,
|
|
279
|
-
work_rx: mpsc::Receiver<WebSocketWorkItem>,
|
|
280
|
-
) {
|
|
281
|
-
let work_rx_ref = &work_rx;
|
|
282
|
-
loop {
|
|
283
|
-
let work = crate::call_without_gvl!(
|
|
284
|
-
recv_work_item,
|
|
285
|
-
args: (work_rx_ref, &mpsc::Receiver<WebSocketWorkItem>),
|
|
286
|
-
return_type: Option<WebSocketWorkItem>
|
|
287
|
-
);
|
|
288
|
-
let Some(work) = work else {
|
|
289
|
-
break;
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
match work {
|
|
293
|
-
WebSocketWorkItem::HandleMessage { message, reply } => {
|
|
294
|
-
let result = (|| {
|
|
295
|
-
let message_ruby = RubyWebSocketHandler::json_to_ruby(ruby, &message)?;
|
|
296
|
-
let proc_value = ruby.get_inner(handle_message_proc);
|
|
297
|
-
let result: Value = proc_value
|
|
298
|
-
.funcall("call", (message_ruby,))
|
|
299
|
-
.map_err(|e| format!("Handler '{}' call failed: {}", handler_name, e))?;
|
|
300
|
-
if result.is_nil() {
|
|
301
|
-
Ok(None)
|
|
302
|
-
} else {
|
|
303
|
-
RubyWebSocketHandler::ruby_to_json(ruby, result).map(Some)
|
|
304
|
-
}
|
|
305
|
-
})();
|
|
306
|
-
let _ = reply.send(result);
|
|
307
|
-
}
|
|
308
|
-
WebSocketWorkItem::OnConnect { reply } => {
|
|
309
|
-
let result = on_connect_proc
|
|
310
|
-
.map(|proc| {
|
|
311
|
-
let proc_value = ruby.get_inner(proc);
|
|
312
|
-
proc_value
|
|
313
|
-
.funcall::<_, _, Value>("call", ())
|
|
314
|
-
.map_err(|e| format!("on_connect '{}' call failed: {}", handler_name, e))?;
|
|
315
|
-
Ok(())
|
|
316
|
-
})
|
|
317
|
-
.unwrap_or(Ok(()));
|
|
318
|
-
let _ = reply.send(result);
|
|
319
|
-
}
|
|
320
|
-
WebSocketWorkItem::OnDisconnect { reply } => {
|
|
321
|
-
let result = on_disconnect_proc
|
|
322
|
-
.map(|proc| {
|
|
323
|
-
let proc_value = ruby.get_inner(proc);
|
|
324
|
-
proc_value
|
|
325
|
-
.funcall::<_, _, Value>("call", ())
|
|
326
|
-
.map_err(|e| format!("on_disconnect '{}' call failed: {}", handler_name, e))?;
|
|
327
|
-
Ok(())
|
|
328
|
-
})
|
|
329
|
-
.unwrap_or(Ok(()));
|
|
330
|
-
let _ = reply.send(result);
|
|
331
|
-
}
|
|
332
|
-
WebSocketWorkItem::Shutdown => {
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
fn recv_work_item(receiver: &mpsc::Receiver<WebSocketWorkItem>) -> Option<WebSocketWorkItem> {
|
|
340
|
-
receiver.recv().ok()
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
unsafe impl Send for RubyWebSocketHandler {}
|
|
344
|
-
unsafe impl Sync for RubyWebSocketHandler {}
|
|
345
|
-
|
|
346
|
-
/// Create WebSocketState from Ruby handler object
|
|
347
|
-
///
|
|
348
|
-
/// This function is designed to be called from Ruby to register WebSocket handlers.
|
|
349
|
-
/// Returns None if the factory returns nil (no handler for this endpoint).
|
|
350
|
-
#[allow(dead_code)]
|
|
351
|
-
pub fn create_websocket_state(
|
|
352
|
-
ruby: &magnus::Ruby,
|
|
353
|
-
handler_factory: Value,
|
|
354
|
-
) -> Result<Option<spikard_http::WebSocketState<RubyWebSocketHandler>>, magnus::Error> {
|
|
355
|
-
// Test the factory with an initial call to check if it returns nil
|
|
356
|
-
let handler_instance: Value = handler_factory.funcall("call", ()).map_err(|e| {
|
|
357
|
-
magnus::Error::new(
|
|
358
|
-
ruby.exception_runtime_error(),
|
|
359
|
-
format!("Failed to create WebSocket handler: {}", e),
|
|
360
|
-
)
|
|
361
|
-
})?;
|
|
362
|
-
|
|
363
|
-
// If factory returns nil, skip creating a handler (return None)
|
|
364
|
-
if handler_instance.is_nil() {
|
|
365
|
-
return Ok(None);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
let message_schema = handler_instance
|
|
369
|
-
.funcall::<_, _, Value>("instance_variable_get", (ruby.to_symbol("@_message_schema"),))
|
|
370
|
-
.ok()
|
|
371
|
-
.and_then(|v| {
|
|
372
|
-
if v.is_nil() {
|
|
373
|
-
None
|
|
374
|
-
} else {
|
|
375
|
-
RubyWebSocketHandler::ruby_to_json(ruby, v).ok()
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
let response_schema = handler_instance
|
|
380
|
-
.funcall::<_, _, Value>("instance_variable_get", (ruby.to_symbol("@_response_schema"),))
|
|
381
|
-
.ok()
|
|
382
|
-
.and_then(|v| {
|
|
383
|
-
if v.is_nil() {
|
|
384
|
-
None
|
|
385
|
-
} else {
|
|
386
|
-
RubyWebSocketHandler::ruby_to_json(ruby, v).ok()
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
let (message_schema, response_schema) = (message_schema, response_schema);
|
|
391
|
-
|
|
392
|
-
let handler_factory = Opaque::from(handler_factory);
|
|
393
|
-
let (factory_tx, factory_rx) = mpsc::channel();
|
|
394
|
-
|
|
395
|
-
let handler_factory_for_thread = handler_factory;
|
|
396
|
-
ruby.thread_create_from_fn(move |ruby| {
|
|
397
|
-
websocket_factory_worker_loop(ruby, handler_factory_for_thread, factory_rx);
|
|
398
|
-
ruby.qnil()
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
let handler_builder = move || {
|
|
402
|
-
let (reply_tx, reply_rx) = mpsc::channel();
|
|
403
|
-
if factory_tx
|
|
404
|
-
.send(WebSocketFactoryWorkItem::Build { reply: reply_tx })
|
|
405
|
-
.is_err()
|
|
406
|
-
{
|
|
407
|
-
return Err("WebSocket handler factory thread closed".to_string());
|
|
408
|
-
}
|
|
409
|
-
if magnus::Ruby::get().is_ok() {
|
|
410
|
-
let reply_rx_ref = &reply_rx;
|
|
411
|
-
let result = crate::call_without_gvl!(
|
|
412
|
-
recv_factory_reply,
|
|
413
|
-
args: (reply_rx_ref, &mpsc::Receiver<Result<RubyWebSocketHandler, String>>),
|
|
414
|
-
return_type: Option<Result<RubyWebSocketHandler, String>>
|
|
415
|
-
);
|
|
416
|
-
result.ok_or_else(|| "WebSocket handler factory response channel closed".to_string())?
|
|
417
|
-
} else {
|
|
418
|
-
reply_rx
|
|
419
|
-
.recv()
|
|
420
|
-
.map_err(|_| "WebSocket handler factory response channel closed".to_string())?
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
let ws_state = if message_schema.is_some() || response_schema.is_some() {
|
|
425
|
-
spikard_http::WebSocketState::with_factory(handler_builder, message_schema, response_schema)
|
|
426
|
-
.map_err(|e| magnus::Error::new(ruby.exception_runtime_error(), e))?
|
|
427
|
-
} else {
|
|
428
|
-
spikard_http::WebSocketState::with_factory(handler_builder, None, None)
|
|
429
|
-
.map_err(|e| magnus::Error::new(ruby.exception_runtime_error(), e))?
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
Ok(Some(ws_state))
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
fn websocket_factory_worker_loop(
|
|
436
|
-
ruby: &magnus::Ruby,
|
|
437
|
-
handler_factory: Opaque<Value>,
|
|
438
|
-
work_rx: mpsc::Receiver<WebSocketFactoryWorkItem>,
|
|
439
|
-
) {
|
|
440
|
-
let work_rx_ref = &work_rx;
|
|
441
|
-
loop {
|
|
442
|
-
let work = crate::call_without_gvl!(
|
|
443
|
-
recv_factory_item,
|
|
444
|
-
args: (work_rx_ref, &mpsc::Receiver<WebSocketFactoryWorkItem>),
|
|
445
|
-
return_type: Option<WebSocketFactoryWorkItem>
|
|
446
|
-
);
|
|
447
|
-
let Some(work) = work else {
|
|
448
|
-
break;
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
match work {
|
|
452
|
-
WebSocketFactoryWorkItem::Build { reply } => {
|
|
453
|
-
let result = (|| {
|
|
454
|
-
let factory_value = ruby.get_inner(handler_factory);
|
|
455
|
-
let handler_instance: Value = factory_value
|
|
456
|
-
.funcall("call", ())
|
|
457
|
-
.map_err(|e| format!("Failed to create WebSocket handler: {}", e))?;
|
|
458
|
-
|
|
459
|
-
RubyWebSocketHandler::from_handler(ruby, handler_instance).map_err(|e| e.to_string())
|
|
460
|
-
})();
|
|
461
|
-
let _ = reply.send(result);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
fn recv_factory_item(receiver: &mpsc::Receiver<WebSocketFactoryWorkItem>) -> Option<WebSocketFactoryWorkItem> {
|
|
468
|
-
receiver.recv().ok()
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
fn recv_factory_reply(
|
|
472
|
-
receiver: &mpsc::Receiver<Result<RubyWebSocketHandler, String>>,
|
|
473
|
-
) -> Option<Result<RubyWebSocketHandler, String>> {
|
|
474
|
-
receiver.recv().ok()
|
|
475
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
[package]
|
|
2
|
-
name = "spikard-rb-macros"
|
|
3
|
-
version = "0.12.0"
|
|
4
|
-
edition = "2024"
|
|
5
|
-
license = "MIT"
|
|
6
|
-
publish = false
|
|
7
|
-
|
|
8
|
-
[lib]
|
|
9
|
-
proc-macro = true
|
|
10
|
-
|
|
11
|
-
[lints.rust]
|
|
12
|
-
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] }
|
|
13
|
-
|
|
14
|
-
[lints.clippy]
|
|
15
|
-
all = { level = "deny", priority = 0 }
|
|
16
|
-
pedantic = { level = "deny", priority = 0 }
|
|
17
|
-
nursery = { level = "deny", priority = 0 }
|
|
18
|
-
|
|
19
|
-
[dependencies]
|
|
20
|
-
proc-macro2 = "1"
|
|
21
|
-
quote = "1"
|
|
22
|
-
syn = { version = "2", features = ["full"] }
|
|
23
|
-
|
|
24
|
-
[package.metadata.cargo-machete]
|
|
25
|
-
ignored = ["proc-macro2"]
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
use proc_macro::TokenStream;
|
|
2
|
-
use quote::quote;
|
|
3
|
-
use syn::{FnArg, ItemFn, parse_macro_input};
|
|
4
|
-
|
|
5
|
-
#[proc_macro_attribute]
|
|
6
|
-
pub fn without_gvl(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
7
|
-
let input = parse_macro_input!(item as ItemFn);
|
|
8
|
-
let attrs = &input.attrs;
|
|
9
|
-
let vis = &input.vis;
|
|
10
|
-
let sig = &input.sig;
|
|
11
|
-
let mut anon_sig = sig.clone();
|
|
12
|
-
anon_sig.ident = syn::Ident::new("__anon_wrapper", sig.ident.span());
|
|
13
|
-
|
|
14
|
-
let params = sig.inputs.iter().map(|arg| match arg {
|
|
15
|
-
FnArg::Typed(pat) => {
|
|
16
|
-
let arg = &pat.pat;
|
|
17
|
-
let ty = &pat.ty;
|
|
18
|
-
quote!(#arg, #ty)
|
|
19
|
-
}
|
|
20
|
-
FnArg::Receiver(recv) => {
|
|
21
|
-
let ty = if let Some((_and, lifetime)) = &recv.reference {
|
|
22
|
-
let mutability = recv.mutability;
|
|
23
|
-
lifetime.as_ref().map_or_else(
|
|
24
|
-
|| quote!(& #mutability Self),
|
|
25
|
-
|lifetime| quote!(&#lifetime #mutability Self),
|
|
26
|
-
)
|
|
27
|
-
} else {
|
|
28
|
-
quote!(Self)
|
|
29
|
-
};
|
|
30
|
-
quote!(self, #ty)
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
let return_ty = match &sig.output {
|
|
35
|
-
syn::ReturnType::Default => quote!(()),
|
|
36
|
-
syn::ReturnType::Type(_, ty) => quote!(#ty),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
let block = &input.block;
|
|
40
|
-
|
|
41
|
-
quote!(
|
|
42
|
-
#(#attrs)*
|
|
43
|
-
#vis #sig {
|
|
44
|
-
#anon_sig {
|
|
45
|
-
#block
|
|
46
|
-
}
|
|
47
|
-
crate::call_without_gvl!(__anon_wrapper, args: (#(#params),*), return_type: #return_ty)
|
|
48
|
-
}
|
|
49
|
-
)
|
|
50
|
-
.into()
|
|
51
|
-
}
|