omq-rs 0.1.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.
@@ -0,0 +1,494 @@
1
+ use std::ffi::{CStr, CString, c_char, c_long, c_void};
2
+ use std::panic::{AssertUnwindSafe, catch_unwind};
3
+ use std::ptr;
4
+
5
+ use rb_sys::{VALUE, rb_data_type_t};
6
+
7
+ pub type RbResult<T = VALUE> = Result<T, RubyErr>;
8
+
9
+ #[derive(Debug)]
10
+ pub enum RubyErr {
11
+ Exception(VALUE),
12
+ Error { class: VALUE, message: String },
13
+ }
14
+
15
+ impl RubyErr {
16
+ pub fn new(class: VALUE, message: impl Into<String>) -> Self {
17
+ Self::Error {
18
+ class,
19
+ message: message.into(),
20
+ }
21
+ }
22
+
23
+ pub fn arg(message: impl Into<String>) -> Self {
24
+ Self::new(unsafe { rb_sys::rb_eArgError }, message)
25
+ }
26
+
27
+ pub fn io(message: impl Into<String>) -> Self {
28
+ Self::new(unsafe { rb_sys::rb_eIOError }, message)
29
+ }
30
+
31
+ pub fn runtime(message: impl Into<String>) -> Self {
32
+ Self::new(unsafe { rb_sys::rb_eRuntimeError }, message)
33
+ }
34
+
35
+ pub fn type_error(message: impl Into<String>) -> Self {
36
+ Self::new(unsafe { rb_sys::rb_eTypeError }, message)
37
+ }
38
+
39
+ fn current_exception() -> Self {
40
+ let err = unsafe { rb_sys::rb_errinfo() };
41
+ if err == qnil() {
42
+ Self::runtime("Ruby exception")
43
+ } else {
44
+ Self::Exception(err)
45
+ }
46
+ }
47
+ }
48
+
49
+ pub fn wrap<F>(f: F) -> VALUE
50
+ where
51
+ F: FnOnce() -> RbResult<VALUE>,
52
+ {
53
+ match catch_unwind(AssertUnwindSafe(f)) {
54
+ Ok(Ok(value)) => value,
55
+ Ok(Err(err)) => raise(err),
56
+ Err(_) => raise(RubyErr::runtime("native Rust panic")),
57
+ }
58
+ }
59
+
60
+ pub fn wrap_init<F>(f: F)
61
+ where
62
+ F: FnOnce() -> RbResult<()>,
63
+ {
64
+ match catch_unwind(AssertUnwindSafe(f)) {
65
+ Ok(Ok(())) => {}
66
+ Ok(Err(err)) => raise(err),
67
+ Err(_) => raise(RubyErr::runtime("native Rust panic")),
68
+ }
69
+ }
70
+
71
+ pub fn raise(err: RubyErr) -> ! {
72
+ match err {
73
+ RubyErr::Exception(exc) => unsafe { rb_sys::rb_exc_raise(exc) },
74
+ RubyErr::Error { class, message } => {
75
+ let message = message.replace('\0', "\\0");
76
+ let c_message =
77
+ CString::new(message).unwrap_or_else(|_| CString::new("Ruby error").unwrap());
78
+ let exc = unsafe { rb_sys::rb_exc_new_cstr(class, c_message.as_ptr()) };
79
+ unsafe { rb_sys::rb_exc_raise(exc) }
80
+ }
81
+ }
82
+ }
83
+
84
+ struct ProtectData<F> {
85
+ func: Option<F>,
86
+ panicked: bool,
87
+ }
88
+
89
+ pub fn protect_value<F>(func: F) -> RbResult<VALUE>
90
+ where
91
+ F: FnOnce() -> VALUE,
92
+ {
93
+ unsafe extern "C" fn call<F>(arg: VALUE) -> VALUE
94
+ where
95
+ F: FnOnce() -> VALUE,
96
+ {
97
+ let data = unsafe { &mut *(arg as *mut ProtectData<F>) };
98
+ let Some(func) = data.func.take() else {
99
+ data.panicked = true;
100
+ return qnil();
101
+ };
102
+
103
+ if let Ok(value) = catch_unwind(AssertUnwindSafe(func)) {
104
+ value
105
+ } else {
106
+ data.panicked = true;
107
+ qnil()
108
+ }
109
+ }
110
+
111
+ let mut data = ProtectData {
112
+ func: Some(func),
113
+ panicked: false,
114
+ };
115
+ let mut state = 0;
116
+ let value = unsafe {
117
+ rb_sys::rb_protect(
118
+ Some(call::<F>),
119
+ (&raw mut data).cast::<ProtectData<F>>() as VALUE,
120
+ &raw mut state,
121
+ )
122
+ };
123
+
124
+ if state != 0 {
125
+ Err(RubyErr::current_exception())
126
+ } else if data.panicked {
127
+ Err(RubyErr::runtime("native Rust panic"))
128
+ } else {
129
+ Ok(value)
130
+ }
131
+ }
132
+
133
+ pub fn protect_unit<F>(func: F) -> RbResult<()>
134
+ where
135
+ F: FnOnce(),
136
+ {
137
+ protect_value(|| {
138
+ func();
139
+ qnil()
140
+ })?;
141
+ Ok(())
142
+ }
143
+
144
+ pub const fn qnil() -> VALUE {
145
+ rb_sys::ruby_special_consts::RUBY_Qnil as VALUE
146
+ }
147
+
148
+ pub const fn qtrue() -> VALUE {
149
+ rb_sys::ruby_special_consts::RUBY_Qtrue as VALUE
150
+ }
151
+
152
+ pub const fn qfalse() -> VALUE {
153
+ rb_sys::ruby_special_consts::RUBY_Qfalse as VALUE
154
+ }
155
+
156
+ pub const fn qundef() -> VALUE {
157
+ rb_sys::ruby_special_consts::RUBY_Qundef as VALUE
158
+ }
159
+
160
+ pub fn bool_value(value: bool) -> VALUE {
161
+ if value { qtrue() } else { qfalse() }
162
+ }
163
+
164
+ pub fn check_hash(value: VALUE) -> RbResult<()> {
165
+ let is_hash = unsafe { rb_sys::rb_obj_is_kind_of(value, rb_sys::rb_cHash) };
166
+ if is_hash == qtrue() {
167
+ Ok(())
168
+ } else {
169
+ Err(RubyErr::type_error("expected Hash"))
170
+ }
171
+ }
172
+
173
+ pub fn check_array(value: VALUE) -> RbResult<()> {
174
+ let is_array = unsafe { rb_sys::rb_obj_is_kind_of(value, rb_sys::rb_cArray) };
175
+ if is_array == qtrue() {
176
+ Ok(())
177
+ } else {
178
+ Err(RubyErr::type_error("expected Array"))
179
+ }
180
+ }
181
+
182
+ pub fn hash_get(hash: VALUE, key: &str) -> RbResult<Option<VALUE>> {
183
+ check_hash(hash)?;
184
+ let key = new_utf8_string(key)?;
185
+ let value = protect_value(|| unsafe { rb_sys::rb_hash_lookup2(hash, key, qundef()) })?;
186
+ if value == qundef() {
187
+ Ok(None)
188
+ } else {
189
+ Ok(Some(value))
190
+ }
191
+ }
192
+
193
+ pub fn hash_new() -> RbResult<VALUE> {
194
+ protect_value(|| unsafe { rb_sys::rb_hash_new() })
195
+ }
196
+
197
+ pub fn hash_aset(hash: VALUE, key: VALUE, value: VALUE) -> RbResult<()> {
198
+ protect_value(|| unsafe { rb_sys::rb_hash_aset(hash, key, value) })?;
199
+ Ok(())
200
+ }
201
+
202
+ pub fn array_new() -> RbResult<VALUE> {
203
+ protect_value(|| unsafe { rb_sys::rb_ary_new() })
204
+ }
205
+
206
+ pub fn array_new_capa(capacity: usize) -> RbResult<VALUE> {
207
+ let capacity = c_long_len(capacity)?;
208
+ protect_value(|| unsafe { rb_sys::rb_ary_new_capa(capacity) })
209
+ }
210
+
211
+ pub fn array_len(array: VALUE) -> RbResult<usize> {
212
+ check_array(array)?;
213
+ let len = unsafe { rb_sys::RARRAY_LEN(array) };
214
+ if len < 0 {
215
+ Err(RubyErr::runtime("negative Array length"))
216
+ } else {
217
+ Ok(len as usize)
218
+ }
219
+ }
220
+
221
+ pub fn array_entry(array: VALUE, index: usize) -> RbResult<VALUE> {
222
+ check_array(array)?;
223
+ let index = c_long_len(index)?;
224
+ protect_value(|| unsafe { rb_sys::rb_ary_entry(array, index) })
225
+ }
226
+
227
+ pub fn array_push(array: VALUE, value: VALUE) -> RbResult<()> {
228
+ protect_value(|| unsafe { rb_sys::rb_ary_push(array, value) })?;
229
+ Ok(())
230
+ }
231
+
232
+ pub fn symbol(name: &str) -> RbResult<VALUE> {
233
+ let len = c_long_len(name.len())?;
234
+ protect_value(|| unsafe {
235
+ let id = rb_sys::rb_intern2(name.as_ptr().cast::<c_char>(), len);
236
+ rb_sys::rb_id2sym(id)
237
+ })
238
+ }
239
+
240
+ pub fn new_binary_string(bytes: &[u8]) -> RbResult<VALUE> {
241
+ let len = c_long_len(bytes.len())?;
242
+ let ptr = if bytes.is_empty() {
243
+ ptr::null()
244
+ } else {
245
+ bytes.as_ptr().cast::<c_char>()
246
+ };
247
+ let value = protect_value(|| unsafe { rb_sys::rb_str_new(ptr, len) })?;
248
+ protect_unit(|| unsafe { rb_sys::RB_OBJ_FREEZE(value) })?;
249
+ Ok(value)
250
+ }
251
+
252
+ pub fn new_utf8_string(text: &str) -> RbResult<VALUE> {
253
+ let len = c_long_len(text.len())?;
254
+ protect_value(|| unsafe { rb_sys::rb_utf8_str_new(text.as_ptr().cast::<c_char>(), len) })
255
+ }
256
+
257
+ pub fn string_value(value: VALUE) -> RbResult<VALUE> {
258
+ protect_value(|| unsafe { rb_sys::rb_str_to_str(value) })
259
+ }
260
+
261
+ pub fn value_to_bytes(value: VALUE) -> RbResult<Vec<u8>> {
262
+ let string = string_value(value)?;
263
+ let len = unsafe { rb_sys::RSTRING_LEN(string) };
264
+ if len < 0 {
265
+ return Err(RubyErr::runtime("negative String length"));
266
+ }
267
+ if len == 0 {
268
+ return Ok(Vec::new());
269
+ }
270
+
271
+ let ptr = unsafe { rb_sys::RSTRING_PTR(string) };
272
+ if ptr.is_null() {
273
+ return Err(RubyErr::runtime("null String pointer"));
274
+ }
275
+
276
+ let bytes = unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize) };
277
+ Ok(bytes.to_vec())
278
+ }
279
+
280
+ pub fn value_to_string(value: VALUE) -> RbResult<String> {
281
+ let bytes = value_to_bytes(value)?;
282
+ String::from_utf8(bytes).map_err(|_| RubyErr::type_error("expected UTF-8 String"))
283
+ }
284
+
285
+ pub fn value_to_i64(value: VALUE) -> RbResult<i64> {
286
+ let mut out = 0i64;
287
+ protect_value(|| {
288
+ out = unsafe { rb_sys::rb_num2long(value) as i64 };
289
+ qnil()
290
+ })?;
291
+ Ok(out)
292
+ }
293
+
294
+ pub fn value_to_u32(value: VALUE) -> RbResult<u32> {
295
+ let mut out = 0;
296
+ protect_value(|| {
297
+ out = unsafe { rb_sys::NUM2ULONG(value) };
298
+ qnil()
299
+ })?;
300
+ u32::try_from(out).map_err(|_| RubyErr::arg("routing_id must fit in a 32-bit integer"))
301
+ }
302
+
303
+ pub fn value_to_f64(value: VALUE) -> RbResult<f64> {
304
+ let mut out = 0.0f64;
305
+ protect_value(|| {
306
+ out = unsafe { rb_sys::rb_num2dbl(value) };
307
+ qnil()
308
+ })?;
309
+ Ok(out)
310
+ }
311
+
312
+ pub fn value_to_bool(value: VALUE) -> RbResult<bool> {
313
+ if value == qtrue() {
314
+ Ok(true)
315
+ } else if value == qfalse() {
316
+ Ok(false)
317
+ } else {
318
+ Err(RubyErr::type_error("expected true or false"))
319
+ }
320
+ }
321
+
322
+ pub fn int_value(value: i32) -> VALUE {
323
+ unsafe { rb_sys::rb_int2inum(value as isize) }
324
+ }
325
+
326
+ pub fn uint_value(value: u32) -> VALUE {
327
+ rb_sys::ULONG2NUM(value.into())
328
+ }
329
+
330
+ pub fn u64_value(value: u64) -> VALUE {
331
+ rb_sys::ULONG2NUM(value)
332
+ }
333
+
334
+ pub unsafe fn wrap_typed_data<T>(
335
+ class: VALUE,
336
+ value: Box<T>,
337
+ data_type: *const rb_data_type_t,
338
+ ) -> RbResult<VALUE> {
339
+ let raw = Box::into_raw(value);
340
+ match protect_value(|| unsafe {
341
+ rb_sys::rb_data_typed_object_wrap(class, raw.cast::<c_void>(), data_type)
342
+ }) {
343
+ Ok(value) => Ok(value),
344
+ Err(err) => {
345
+ unsafe { drop(Box::from_raw(raw)) };
346
+ Err(err)
347
+ }
348
+ }
349
+ }
350
+
351
+ pub unsafe fn typed_data_ref<T>(
352
+ value: VALUE,
353
+ data_type: *const rb_data_type_t,
354
+ type_name: &str,
355
+ ) -> RbResult<&'static T> {
356
+ let mut ptr = std::ptr::null_mut();
357
+ protect_unit(|| unsafe {
358
+ ptr = rb_sys::rb_check_typeddata(value, data_type);
359
+ })
360
+ .map_err(|_| RubyErr::type_error(format!("expected {type_name}")))?;
361
+ if ptr.is_null() {
362
+ return Err(RubyErr::runtime(format!(
363
+ "{type_name} data pointer is null"
364
+ )));
365
+ }
366
+
367
+ Ok(unsafe { &*(ptr as *const T) })
368
+ }
369
+
370
+ pub unsafe fn define_module(name: &CStr) -> RbResult<VALUE> {
371
+ protect_value(|| unsafe { rb_sys::rb_define_module(name.as_ptr()) })
372
+ }
373
+
374
+ pub unsafe fn define_module_under(outer: VALUE, name: &CStr) -> RbResult<VALUE> {
375
+ protect_value(|| unsafe { rb_sys::rb_define_module_under(outer, name.as_ptr()) })
376
+ }
377
+
378
+ pub unsafe fn define_class_under(outer: VALUE, name: &CStr, superclass: VALUE) -> RbResult<VALUE> {
379
+ protect_value(|| unsafe { rb_sys::rb_define_class_under(outer, name.as_ptr(), superclass) })
380
+ }
381
+
382
+ pub unsafe fn undef_alloc_func(class: VALUE) -> RbResult<()> {
383
+ protect_unit(|| unsafe { rb_sys::rb_undef_alloc_func(class) })
384
+ }
385
+
386
+ pub unsafe fn define_module_function_1(
387
+ module: VALUE,
388
+ name: &CStr,
389
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
390
+ ) -> RbResult<()> {
391
+ protect_unit(|| unsafe {
392
+ rb_sys::rb_define_module_function(module, name.as_ptr(), Some(transmute_1(func)), 1);
393
+ })
394
+ }
395
+
396
+ pub unsafe fn define_module_function_0(
397
+ module: VALUE,
398
+ name: &CStr,
399
+ func: unsafe extern "C" fn(VALUE) -> VALUE,
400
+ ) -> RbResult<()> {
401
+ protect_unit(|| unsafe {
402
+ rb_sys::rb_define_module_function(module, name.as_ptr(), Some(transmute_0(func)), 0);
403
+ })
404
+ }
405
+
406
+ pub fn call_method_1(receiver: VALUE, name: &CStr, argument: VALUE) -> RbResult<VALUE> {
407
+ protect_value(|| unsafe {
408
+ let method = rb_sys::rb_intern(name.as_ptr());
409
+ rb_sys::rb_funcallv(receiver, method, 1, &raw const argument)
410
+ })
411
+ }
412
+
413
+ pub fn call_method_0(receiver: VALUE, name: &CStr) -> RbResult<VALUE> {
414
+ protect_value(|| unsafe {
415
+ let method = rb_sys::rb_intern(name.as_ptr());
416
+ rb_sys::rb_funcallv(receiver, method, 0, std::ptr::null())
417
+ })
418
+ }
419
+
420
+ pub unsafe fn define_singleton_method_1(
421
+ object: VALUE,
422
+ name: &CStr,
423
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
424
+ ) -> RbResult<()> {
425
+ protect_unit(|| unsafe {
426
+ rb_sys::rb_define_singleton_method(object, name.as_ptr(), Some(transmute_1(func)), 1);
427
+ })
428
+ }
429
+
430
+ pub unsafe fn define_method_0(
431
+ class: VALUE,
432
+ name: &CStr,
433
+ func: unsafe extern "C" fn(VALUE) -> VALUE,
434
+ ) -> RbResult<()> {
435
+ protect_unit(|| unsafe {
436
+ rb_sys::rb_define_method(class, name.as_ptr(), Some(transmute_0(func)), 0);
437
+ })
438
+ }
439
+
440
+ pub unsafe fn define_method_1(
441
+ class: VALUE,
442
+ name: &CStr,
443
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
444
+ ) -> RbResult<()> {
445
+ protect_unit(|| unsafe {
446
+ rb_sys::rb_define_method(class, name.as_ptr(), Some(transmute_1(func)), 1);
447
+ })
448
+ }
449
+
450
+ pub unsafe fn define_method_2(
451
+ class: VALUE,
452
+ name: &CStr,
453
+ func: unsafe extern "C" fn(VALUE, VALUE, VALUE) -> VALUE,
454
+ ) -> RbResult<()> {
455
+ protect_unit(|| unsafe {
456
+ rb_sys::rb_define_method(class, name.as_ptr(), Some(transmute_2(func)), 2);
457
+ })
458
+ }
459
+
460
+ unsafe fn transmute_0(
461
+ func: unsafe extern "C" fn(VALUE) -> VALUE,
462
+ ) -> unsafe extern "C" fn() -> VALUE {
463
+ unsafe {
464
+ std::mem::transmute::<unsafe extern "C" fn(VALUE) -> VALUE, unsafe extern "C" fn() -> VALUE>(
465
+ func,
466
+ )
467
+ }
468
+ }
469
+
470
+ unsafe fn transmute_1(
471
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
472
+ ) -> unsafe extern "C" fn() -> VALUE {
473
+ unsafe {
474
+ std::mem::transmute::<
475
+ unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
476
+ unsafe extern "C" fn() -> VALUE,
477
+ >(func)
478
+ }
479
+ }
480
+
481
+ unsafe fn transmute_2(
482
+ func: unsafe extern "C" fn(VALUE, VALUE, VALUE) -> VALUE,
483
+ ) -> unsafe extern "C" fn() -> VALUE {
484
+ unsafe {
485
+ std::mem::transmute::<
486
+ unsafe extern "C" fn(VALUE, VALUE, VALUE) -> VALUE,
487
+ unsafe extern "C" fn() -> VALUE,
488
+ >(func)
489
+ }
490
+ }
491
+
492
+ fn c_long_len(len: usize) -> RbResult<c_long> {
493
+ c_long::try_from(len).map_err(|_| RubyErr::arg("length too large"))
494
+ }