omq-backend-rust 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/ext/omq_backend_rust/Cargo.toml +3 -4
- data/ext/omq_backend_rust/build.rs +23 -0
- data/ext/omq_backend_rust/src/error.rs +11 -10
- data/ext/omq_backend_rust/src/lib.rs +35 -10
- data/ext/omq_backend_rust/src/options.rs +117 -104
- data/ext/omq_backend_rust/src/rb.rs +433 -0
- data/ext/omq_backend_rust/src/runtime.rs +45 -99
- data/ext/omq_backend_rust/src/socket.rs +395 -166
- data/lib/omq/rust/engine.rb +64 -24
- data/lib/omq/rust/fd_watcher.rb +184 -0
- data/lib/omq/rust/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ec3e72f8903532375903dac4637b6f4ebc1c9b09a0f2d1d1475420797464e800
|
|
4
|
+
data.tar.gz: d4cb76d8140f6f10d3b4eda7f9f720a7901407dcf0ea45da3e7a48989a137ca4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5921c0fcc3fd18908cbc142ae481aa4e61d8005ab68f2ebc61fd316f65089f8d6453acddb76e073a2c06e0a47581900878a47d5c0dc2b7899bb2bd815a500fec
|
|
7
|
+
data.tar.gz: 7401afef781cc1000b175dd1e28f89241e2002abfc0a415d8bedd1d5a341398dce599574a6ced626f17a8c94ab3add861699f254e4f32a2d8f439322add19272
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.0] - 2026-08-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added TruffleRuby support for the Rust backend.
|
|
10
|
+
- Added a shared fallback file descriptor watcher for Rubies without a
|
|
11
|
+
native `Fiber.scheduler`.
|
|
12
|
+
- Added TruffleRuby CI coverage for the Rust backend.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Updated to `omq-tokio` 0.21.1 and `omq-proto` 0.25.1.
|
|
17
|
+
- Replaced Magnus with a small `rb-sys` helper layer that uses public
|
|
18
|
+
Ruby C API calls.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Raised Ruby exceptions through public C API entry points for
|
|
23
|
+
TruffleRuby compatibility.
|
|
24
|
+
- Rejected invalid negative numeric options before they can overflow Rust
|
|
25
|
+
sizes or panic on invalid durations.
|
|
26
|
+
|
|
5
27
|
## [0.1.7] - 2026-08-02
|
|
6
28
|
|
|
7
29
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "omq_backend_rust"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
license = "ISC"
|
|
6
6
|
authors = ["Patrik Wenger <paddor@gmail.com>"]
|
|
@@ -18,15 +18,14 @@ lz4 = ["omq-tokio/lz4"]
|
|
|
18
18
|
zstd = ["omq-tokio/zstd"]
|
|
19
19
|
|
|
20
20
|
[dependencies]
|
|
21
|
-
omq-proto = { version = "=0.25.
|
|
22
|
-
omq-tokio = { version = "=0.21.
|
|
21
|
+
omq-proto = { version = "=0.25.1", default-features = false }
|
|
22
|
+
omq-tokio = { version = "=0.21.1", default-features = false }
|
|
23
23
|
tokio = { version = "1.52.0", features = ["rt", "rt-multi-thread", "time", "sync", "io-util", "net"] }
|
|
24
24
|
yring = { version = "0.3.11", features = ["async"] }
|
|
25
25
|
|
|
26
26
|
bytes = "1.12.0"
|
|
27
27
|
flume = { version = "0.12", default-features = false, features = ["async"] }
|
|
28
28
|
futures = { version = "0.3", default-features = false, features = ["std", "async-await"] }
|
|
29
|
-
magnus = "0.8"
|
|
30
29
|
rb-sys = "0.9"
|
|
31
30
|
libc = "0.2"
|
|
32
31
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
use std::process::Command;
|
|
2
|
+
|
|
3
|
+
fn main() {
|
|
4
|
+
println!("cargo:rerun-if-env-changed=RUBY");
|
|
5
|
+
println!("cargo:rustc-check-cfg=cfg(ruby_engine, values(\"mri\", \"truffleruby\"))");
|
|
6
|
+
|
|
7
|
+
let ruby = std::env::var("RUBY").unwrap_or_else(|_| "ruby".to_string());
|
|
8
|
+
let output = Command::new(&ruby)
|
|
9
|
+
.arg("-rrbconfig")
|
|
10
|
+
.arg("-e")
|
|
11
|
+
.arg("print RbConfig::CONFIG.fetch('ruby_install_name')")
|
|
12
|
+
.output()
|
|
13
|
+
.unwrap_or_else(|err| panic!("failed to run {ruby}: {err}"));
|
|
14
|
+
|
|
15
|
+
if !output.status.success() {
|
|
16
|
+
panic!("failed to query Ruby engine with {ruby}");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
match String::from_utf8_lossy(&output.stdout).trim() {
|
|
20
|
+
"truffleruby" => println!("cargo:rustc-cfg=ruby_engine=\"truffleruby\""),
|
|
21
|
+
_ => println!("cargo:rustc-cfg=ruby_engine=\"mri\""),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
use magnus::{Error, Ruby};
|
|
2
1
|
use omq_proto::error::Error as OmqError;
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
use crate::rb::RubyErr;
|
|
4
|
+
|
|
5
|
+
pub fn map_err(e: OmqError) -> RubyErr {
|
|
5
6
|
match e {
|
|
6
|
-
OmqError::Closed =>
|
|
7
|
-
OmqError::Timeout =>
|
|
8
|
-
OmqError::Unroutable =>
|
|
9
|
-
OmqError::InvalidEndpoint(msg) =>
|
|
10
|
-
OmqError::Protocol(msg) =>
|
|
11
|
-
OmqError::Io(e) =>
|
|
12
|
-
OmqError::HandshakeFailed(msg) =>
|
|
13
|
-
_ =>
|
|
7
|
+
OmqError::Closed => RubyErr::io("socket closed"),
|
|
8
|
+
OmqError::Timeout => RubyErr::runtime("operation timed out"),
|
|
9
|
+
OmqError::Unroutable => RubyErr::runtime("no route to peer"),
|
|
10
|
+
OmqError::InvalidEndpoint(msg) => RubyErr::arg(msg),
|
|
11
|
+
OmqError::Protocol(msg) => RubyErr::runtime(msg),
|
|
12
|
+
OmqError::Io(e) => RubyErr::runtime(e.to_string()),
|
|
13
|
+
OmqError::HandshakeFailed(msg) => RubyErr::runtime(msg),
|
|
14
|
+
_ => RubyErr::runtime(format!("{e}")),
|
|
14
15
|
}
|
|
15
16
|
}
|
|
@@ -1,26 +1,51 @@
|
|
|
1
1
|
mod error;
|
|
2
2
|
mod notify;
|
|
3
3
|
mod options;
|
|
4
|
+
mod rb;
|
|
4
5
|
mod runtime;
|
|
5
6
|
mod socket;
|
|
6
7
|
|
|
7
|
-
use
|
|
8
|
+
use rb_sys::VALUE;
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
use crate::rb::{RbResult, RubyErr};
|
|
11
|
+
|
|
12
|
+
fn set_io_threads_impl(n: VALUE) -> RbResult<VALUE> {
|
|
13
|
+
let n = rb::value_to_i64(n)?;
|
|
14
|
+
if n < 0 {
|
|
15
|
+
return Err(RubyErr::arg("io_threads must be non-negative"));
|
|
16
|
+
}
|
|
17
|
+
let n = usize::try_from(n).map_err(|_| RubyErr::arg("io_threads too large"))?;
|
|
10
18
|
socket::set_io_threads(n);
|
|
19
|
+
Ok(rb::qnil())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
unsafe extern "C" fn set_io_threads(_module: VALUE, n: VALUE) -> VALUE {
|
|
23
|
+
rb::wrap(|| set_io_threads_impl(n))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#[unsafe(no_mangle)]
|
|
27
|
+
/// # Safety
|
|
28
|
+
///
|
|
29
|
+
/// Ruby calls this once while loading the native extension.
|
|
30
|
+
pub unsafe extern "C" fn Init_omq_backend_rust() {
|
|
31
|
+
rb::wrap_init(init);
|
|
11
32
|
}
|
|
12
33
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
unsafe {
|
|
34
|
+
fn init() -> RbResult<()> {
|
|
35
|
+
#[cfg(ruby_engine = "mri")]
|
|
36
|
+
unsafe {
|
|
37
|
+
rb_sys::rb_ext_ractor_safe(true);
|
|
38
|
+
}
|
|
16
39
|
|
|
17
|
-
let omq =
|
|
18
|
-
let rust = omq
|
|
19
|
-
let native = rust
|
|
40
|
+
let omq = unsafe { rb::define_module(c"OMQ")? };
|
|
41
|
+
let rust = unsafe { rb::define_module_under(omq, c"Rust")? };
|
|
42
|
+
let native = unsafe { rb::define_module_under(rust, c"Native")? };
|
|
20
43
|
|
|
21
|
-
|
|
44
|
+
unsafe {
|
|
45
|
+
rb::define_module_function_1(native, c"io_threads=", set_io_threads)?;
|
|
46
|
+
}
|
|
22
47
|
|
|
23
|
-
socket::register(
|
|
48
|
+
socket::register(native)?;
|
|
24
49
|
|
|
25
50
|
Ok(())
|
|
26
51
|
}
|
|
@@ -1,129 +1,124 @@
|
|
|
1
1
|
use std::time::Duration;
|
|
2
2
|
|
|
3
3
|
use bytes::Bytes;
|
|
4
|
-
use magnus::{Error, Ruby, TryConvert, r_hash::RHash, value::ReprValue};
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
use rb_sys::VALUE;
|
|
6
|
+
|
|
7
|
+
use crate::rb::{self, RbResult, RubyErr};
|
|
8
|
+
|
|
9
|
+
pub fn build_options(hash: VALUE) -> RbResult<omq_tokio::Options> {
|
|
10
|
+
rb::check_hash(hash)?;
|
|
11
|
+
|
|
7
12
|
let mut opts = omq_tokio::Options::default();
|
|
8
13
|
|
|
9
|
-
if let Some(v) =
|
|
14
|
+
if let Some(v) = get_opt_i64(hash, "send_hwm")? {
|
|
10
15
|
opts.send_hwm = v.max(0) as u32;
|
|
11
16
|
}
|
|
12
|
-
if let Some(v) =
|
|
17
|
+
if let Some(v) = get_opt_i64(hash, "recv_hwm")? {
|
|
13
18
|
opts.recv_hwm = v.max(0) as u32;
|
|
14
19
|
}
|
|
15
|
-
if let Some(v) =
|
|
16
|
-
opts.linger = if v.is_infinite() {
|
|
20
|
+
if let Some(v) = get_opt_f64(hash, "linger")? {
|
|
21
|
+
opts.linger = if v.is_infinite() && v.is_sign_positive() {
|
|
17
22
|
None
|
|
18
23
|
} else {
|
|
19
|
-
Some(
|
|
24
|
+
Some(duration_from_seconds("linger", v)?)
|
|
20
25
|
};
|
|
21
26
|
}
|
|
22
|
-
if let Some(v) = get_opt_bytes(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
if let Some(v) = get_opt_bytes(hash, "identity")?
|
|
28
|
+
&& !v.is_empty()
|
|
29
|
+
{
|
|
30
|
+
opts.identity = Bytes::from(v);
|
|
26
31
|
}
|
|
27
|
-
if let Some(v) =
|
|
32
|
+
if let Some(v) = get_opt_bool(hash, "router_mandatory")? {
|
|
28
33
|
opts.router_mandatory = v;
|
|
29
34
|
}
|
|
30
|
-
if let Some(v) =
|
|
35
|
+
if let Some(v) = get_opt_bool(hash, "conflate")? {
|
|
31
36
|
opts.conflate = v;
|
|
32
37
|
}
|
|
33
|
-
if let Some(v) = get_opt_duration(
|
|
38
|
+
if let Some(v) = get_opt_duration(hash, "heartbeat_interval")? {
|
|
34
39
|
opts.heartbeat_interval = Some(v);
|
|
35
40
|
}
|
|
36
|
-
if let Some(v) = get_opt_duration(
|
|
41
|
+
if let Some(v) = get_opt_duration(hash, "heartbeat_ttl")? {
|
|
37
42
|
opts.heartbeat_ttl = Some(v);
|
|
38
43
|
}
|
|
39
|
-
if let Some(v) = get_opt_duration(
|
|
44
|
+
if let Some(v) = get_opt_duration(hash, "heartbeat_timeout")? {
|
|
40
45
|
opts.heartbeat_timeout = Some(v);
|
|
41
46
|
}
|
|
42
|
-
if let Some(v) =
|
|
43
|
-
opts.max_message_size = Some(v
|
|
47
|
+
if let Some(v) = get_opt_usize(hash, "max_message_size")? {
|
|
48
|
+
opts.max_message_size = Some(v);
|
|
44
49
|
}
|
|
45
|
-
if let Some(v) =
|
|
46
|
-
opts.send_buffer_size = Some(v
|
|
50
|
+
if let Some(v) = get_opt_usize(hash, "sndbuf")? {
|
|
51
|
+
opts.send_buffer_size = Some(v);
|
|
47
52
|
}
|
|
48
|
-
if let Some(v) =
|
|
49
|
-
opts.recv_buffer_size = Some(v
|
|
53
|
+
if let Some(v) = get_opt_usize(hash, "rcvbuf")? {
|
|
54
|
+
opts.recv_buffer_size = Some(v);
|
|
50
55
|
}
|
|
51
|
-
if let Some(v) = get_opt_bytes(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
if let Some(v) = get_opt_bytes(hash, "compression_dict")?
|
|
57
|
+
&& !v.is_empty()
|
|
58
|
+
{
|
|
59
|
+
opts.compression_dict = Some(Bytes::from(v));
|
|
55
60
|
}
|
|
56
|
-
if let Some(v) =
|
|
61
|
+
if let Some(v) = get_opt_bool(hash, "compression_auto_train")? {
|
|
57
62
|
opts.compression_auto_train = v;
|
|
58
63
|
}
|
|
59
|
-
if let Some(v) =
|
|
60
|
-
opts.compression_threshold = Some(v
|
|
64
|
+
if let Some(v) = get_opt_usize(hash, "compression_threshold")? {
|
|
65
|
+
opts.compression_threshold = Some(v);
|
|
61
66
|
}
|
|
62
|
-
if let Some(v) =
|
|
67
|
+
if let Some(v) = get_opt_i64(hash, "compression_level")? {
|
|
63
68
|
opts.compression_level = Some(v as i32);
|
|
64
69
|
}
|
|
65
|
-
if let Some(v) =
|
|
66
|
-
opts.compression_dict_capacity = Some(v
|
|
70
|
+
if let Some(v) = get_opt_usize(hash, "compression_dict_capacity")? {
|
|
71
|
+
opts.compression_dict_capacity = Some(v);
|
|
67
72
|
}
|
|
68
|
-
if let Some(v) =
|
|
69
|
-
opts.max_recv_dict_size = Some(v
|
|
73
|
+
if let Some(v) = get_opt_usize(hash, "max_recv_dict_size")? {
|
|
74
|
+
opts.max_recv_dict_size = Some(v);
|
|
70
75
|
}
|
|
71
|
-
if let Some(v) =
|
|
76
|
+
if let Some(v) = get_opt_i64(hash, "compression_offload_threshold")? {
|
|
72
77
|
opts.compression_offload_threshold = if v < 0 { None } else { Some(v as usize) };
|
|
73
78
|
}
|
|
74
|
-
if let Some(v) =
|
|
79
|
+
if let Some(v) = get_opt_string(hash, "on_mute")? {
|
|
75
80
|
opts.on_mute = match v.as_str() {
|
|
76
81
|
"drop_newest" | "drop" => omq_tokio::OnMute::DropNewest,
|
|
77
82
|
_ => omq_tokio::OnMute::Block,
|
|
78
83
|
};
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
if let Some(v) =
|
|
82
|
-
opts.reconnect = omq_proto::options::ReconnectPolicy::Fixed(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
if let Some(v) = get_opt_f64(hash, "reconnect_interval")? {
|
|
87
|
+
opts.reconnect = omq_proto::options::ReconnectPolicy::Fixed(duration_from_seconds(
|
|
88
|
+
"reconnect_interval",
|
|
89
|
+
v,
|
|
90
|
+
)?);
|
|
91
|
+
}
|
|
92
|
+
if let Some(min) = get_opt_f64(hash, "reconnect_interval_min")? {
|
|
93
|
+
let max = get_opt_f64(hash, "reconnect_interval_max")?.unwrap_or(min * 16.0);
|
|
86
94
|
opts.reconnect = omq_proto::options::ReconnectPolicy::Exponential {
|
|
87
|
-
min:
|
|
88
|
-
max:
|
|
95
|
+
min: duration_from_seconds("reconnect_interval min", min)?,
|
|
96
|
+
max: duration_from_seconds("reconnect_interval max", max)?,
|
|
89
97
|
};
|
|
90
98
|
}
|
|
91
99
|
|
|
92
|
-
if let Some(mech_type) =
|
|
93
|
-
apply_mechanism(
|
|
100
|
+
if let Some(mech_type) = get_opt_string(hash, "mechanism_type")? {
|
|
101
|
+
apply_mechanism(hash, &mech_type, &mut opts)?;
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
Ok(opts)
|
|
97
105
|
}
|
|
98
106
|
|
|
99
|
-
fn apply_mechanism(
|
|
100
|
-
ruby: &Ruby,
|
|
101
|
-
hash: RHash,
|
|
102
|
-
mech_type: &str,
|
|
103
|
-
opts: &mut omq_tokio::Options,
|
|
104
|
-
) -> Result<(), Error> {
|
|
107
|
+
fn apply_mechanism(hash: VALUE, mech_type: &str, opts: &mut omq_tokio::Options) -> RbResult<()> {
|
|
105
108
|
match mech_type {
|
|
106
109
|
"null" => {}
|
|
107
110
|
|
|
108
111
|
#[cfg(feature = "curve")]
|
|
109
112
|
"curve" => {
|
|
110
|
-
let is_server =
|
|
111
|
-
let pub_key = get_opt_bytes(
|
|
112
|
-
let sec_key = get_opt_bytes(
|
|
113
|
+
let is_server = get_opt_bool(hash, "mechanism_server")?.unwrap_or(false);
|
|
114
|
+
let pub_key = get_opt_bytes(hash, "mechanism_public_key")?;
|
|
115
|
+
let sec_key = get_opt_bytes(hash, "mechanism_secret_key")?;
|
|
113
116
|
|
|
114
117
|
if is_server {
|
|
115
118
|
if let (Some(pk), Some(sk)) = (pub_key, sec_key) {
|
|
116
119
|
let keypair = omq_proto::CurveKeypair {
|
|
117
|
-
public: omq_proto::CurvePublicKey::from_bytes(to_32(
|
|
118
|
-
|
|
119
|
-
&pk,
|
|
120
|
-
"public key",
|
|
121
|
-
)?),
|
|
122
|
-
secret: omq_proto::CurveSecretKey::from_bytes(to_32(
|
|
123
|
-
ruby,
|
|
124
|
-
&sk,
|
|
125
|
-
"secret key",
|
|
126
|
-
)?),
|
|
120
|
+
public: omq_proto::CurvePublicKey::from_bytes(to_32(&pk, "public key")?),
|
|
121
|
+
secret: omq_proto::CurveSecretKey::from_bytes(to_32(&sk, "secret key")?),
|
|
127
122
|
};
|
|
128
123
|
opts.mechanism = omq_proto::MechanismSetup::CurveServer {
|
|
129
124
|
our_keypair: keypair,
|
|
@@ -131,24 +126,15 @@ fn apply_mechanism(
|
|
|
131
126
|
};
|
|
132
127
|
}
|
|
133
128
|
} else {
|
|
134
|
-
let srv_key = get_opt_bytes(
|
|
129
|
+
let srv_key = get_opt_bytes(hash, "mechanism_server_key")?;
|
|
135
130
|
if let (Some(pk), Some(sk), Some(svk)) = (pub_key, sec_key, srv_key) {
|
|
136
131
|
let keypair = omq_proto::CurveKeypair {
|
|
137
|
-
public: omq_proto::CurvePublicKey::from_bytes(to_32(
|
|
138
|
-
|
|
139
|
-
&pk,
|
|
140
|
-
"public key",
|
|
141
|
-
)?),
|
|
142
|
-
secret: omq_proto::CurveSecretKey::from_bytes(to_32(
|
|
143
|
-
ruby,
|
|
144
|
-
&sk,
|
|
145
|
-
"secret key",
|
|
146
|
-
)?),
|
|
132
|
+
public: omq_proto::CurvePublicKey::from_bytes(to_32(&pk, "public key")?),
|
|
133
|
+
secret: omq_proto::CurveSecretKey::from_bytes(to_32(&sk, "secret key")?),
|
|
147
134
|
};
|
|
148
135
|
opts.mechanism = omq_proto::MechanismSetup::CurveClient {
|
|
149
136
|
our_keypair: keypair,
|
|
150
137
|
server_public: omq_proto::CurvePublicKey::from_bytes(to_32(
|
|
151
|
-
ruby,
|
|
152
138
|
&svk,
|
|
153
139
|
"server key",
|
|
154
140
|
)?),
|
|
@@ -162,46 +148,73 @@ fn apply_mechanism(
|
|
|
162
148
|
Ok(())
|
|
163
149
|
}
|
|
164
150
|
|
|
165
|
-
fn to_32(
|
|
151
|
+
fn to_32(bytes: &[u8], label: &str) -> RbResult<[u8; 32]> {
|
|
166
152
|
bytes.try_into().map_err(|_| {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
153
|
+
RubyErr::arg(format!(
|
|
154
|
+
"{label} must be exactly 32 bytes, got {}",
|
|
155
|
+
bytes.len()
|
|
156
|
+
))
|
|
171
157
|
})
|
|
172
158
|
}
|
|
173
159
|
|
|
174
|
-
fn get_opt_bytes(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
Some(v) =>
|
|
178
|
-
if v.is_nil() {
|
|
179
|
-
return Ok(None);
|
|
180
|
-
}
|
|
181
|
-
let s = magnus::r_string::RString::try_convert(v)?;
|
|
182
|
-
Ok(Some(unsafe { s.as_slice() }.to_vec()))
|
|
183
|
-
}
|
|
160
|
+
fn get_opt_bytes(hash: VALUE, key: &str) -> RbResult<Option<Vec<u8>>> {
|
|
161
|
+
match rb::hash_get(hash, key)? {
|
|
162
|
+
Some(v) if v == rb::qnil() => Ok(None),
|
|
163
|
+
Some(v) => Ok(Some(rb::value_to_bytes(v)?)),
|
|
184
164
|
None => Ok(None),
|
|
185
165
|
}
|
|
186
166
|
}
|
|
187
167
|
|
|
188
|
-
fn
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
Some(v) =>
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
168
|
+
fn get_opt_string(hash: VALUE, key: &str) -> RbResult<Option<String>> {
|
|
169
|
+
match rb::hash_get(hash, key)? {
|
|
170
|
+
Some(v) if v == rb::qnil() => Ok(None),
|
|
171
|
+
Some(v) => Ok(Some(rb::value_to_string(v)?)),
|
|
172
|
+
None => Ok(None),
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fn get_opt_i64(hash: VALUE, key: &str) -> RbResult<Option<i64>> {
|
|
177
|
+
match rb::hash_get(hash, key)? {
|
|
178
|
+
Some(v) if v == rb::qnil() => Ok(None),
|
|
179
|
+
Some(v) => Ok(Some(rb::value_to_i64(v)?)),
|
|
180
|
+
None => Ok(None),
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fn get_opt_f64(hash: VALUE, key: &str) -> RbResult<Option<f64>> {
|
|
185
|
+
match rb::hash_get(hash, key)? {
|
|
186
|
+
Some(v) if v == rb::qnil() => Ok(None),
|
|
187
|
+
Some(v) => Ok(Some(rb::value_to_f64(v)?)),
|
|
188
|
+
None => Ok(None),
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
fn get_opt_usize(hash: VALUE, key: &str) -> RbResult<Option<usize>> {
|
|
193
|
+
let Some(v) = get_opt_i64(hash, key)? else {
|
|
194
|
+
return Ok(None);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
usize::try_from(v)
|
|
198
|
+
.map(Some)
|
|
199
|
+
.map_err(|_| RubyErr::arg(format!("{key} must be non-negative")))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fn get_opt_bool(hash: VALUE, key: &str) -> RbResult<Option<bool>> {
|
|
203
|
+
match rb::hash_get(hash, key)? {
|
|
204
|
+
Some(v) if v == rb::qnil() => Ok(None),
|
|
205
|
+
Some(v) => Ok(Some(rb::value_to_bool(v)?)),
|
|
198
206
|
None => Ok(None),
|
|
199
207
|
}
|
|
200
208
|
}
|
|
201
209
|
|
|
202
|
-
fn get_opt_duration(
|
|
203
|
-
match
|
|
204
|
-
Some(v) => Ok(Some(
|
|
210
|
+
fn get_opt_duration(hash: VALUE, key: &str) -> RbResult<Option<Duration>> {
|
|
211
|
+
match get_opt_f64(hash, key)? {
|
|
212
|
+
Some(v) => Ok(Some(duration_from_seconds(key, v)?)),
|
|
205
213
|
None => Ok(None),
|
|
206
214
|
}
|
|
207
215
|
}
|
|
216
|
+
|
|
217
|
+
fn duration_from_seconds(label: &str, value: f64) -> RbResult<Duration> {
|
|
218
|
+
Duration::try_from_secs_f64(value)
|
|
219
|
+
.map_err(|_| RubyErr::arg(format!("{label} must be finite and non-negative")))
|
|
220
|
+
}
|