1#![allow(static_mut_refs)]
2extern crate mrubyedge;
3extern crate uzumibi_gem;
4
5use std::rc::Rc;
6
7#[cfg(feature = "queue")]
8use mrubyedge::yamrb::value::RSym;
9#[expect(unused_imports)]
10use mrubyedge::yamrb::{
11 helpers::{mrb_define_class_cmethod, mrb_define_cmethod, mrb_funcall},
12 prelude::hash::{mrb_hash_new, mrb_hash_set_index},
13 value::{RObject, RValue},
14 vm::VM,
15};
16
17pub const PASS_ASSETS: u64 = 0xFEFFFFFF;
19
20unsafe extern "C" {
23 unsafe fn debug_console_log(ptr: *const u8, len: usize);
24}
25
26#[cfg(feature = "queue")]
27unsafe extern "C" {
28 unsafe fn uzumibi_cf_message_ack(message_id_ptr: *const u8, message_id_size: usize) -> i32;
29 unsafe fn uzumibi_cf_message_retry(
30 message_id_ptr: *const u8,
31 message_id_size: usize,
32 delay_seconds: i32,
33 ) -> i32;
34}
35
36#[cfg(feature = "enable-external")]
37unsafe extern "C" {
38 unsafe fn uzumibi_cf_fetch(
39 url_ptr: *const u8,
40 url_size: usize,
41 method_ptr: *const u8,
42 method_size: usize,
43 body_ptr: *const u8,
44 body_size: usize,
45 headers_ptr: *const u8,
46 headers_size: usize,
47 result_ptr: *mut u8,
48 result_max_size: usize,
49 ) -> i32;
50 unsafe fn uzumibi_cf_durable_object_get(
51 key_ptr: *const u8,
52 key_size: usize,
53 result_ptr: *mut u8,
54 result_max_size: usize,
55 ) -> i32;
56 unsafe fn uzumibi_cf_durable_object_set(
57 key_ptr: *const u8,
58 key_size: usize,
59 value_ptr: *const u8,
60 value_size: usize,
61 ) -> i32;
62 unsafe fn uzumibi_cf_queue_send(
63 queue_name_ptr: *const u8,
64 queue_name_size: usize,
65 message_ptr: *const u8,
66 message_size: usize,
67 ) -> i32;
68 unsafe fn uzumibi_cf_secret_get(
69 key_ptr: *const u8,
70 key_size: usize,
71 result_ptr: *mut u8,
72 result_max_size: usize,
73 ) -> i32;
74}
75
76pub fn debug_console_log_internal(message: &str) {
79 unsafe {
80 debug_console_log(message.as_ptr(), message.len());
81 }
82}
83
84#[cfg(feature = "enable-external")]
93fn cf_fetch(url: &str, method: &str, body: &str, headers: &[u8]) -> Result<Vec<u8>, String> {
94 const BUFFER_SIZE: usize = 65536;
95 let mut buffer = vec![0u8; BUFFER_SIZE];
96
97 unsafe {
98 let result = uzumibi_cf_fetch(
99 url.as_ptr(),
100 url.len(),
101 method.as_ptr(),
102 method.len(),
103 body.as_ptr(),
104 body.len(),
105 headers.as_ptr(),
106 headers.len(),
107 buffer.as_mut_ptr(),
108 BUFFER_SIZE,
109 );
110 match result {
111 len if len >= 0 => {
112 let len = len as usize;
113 Ok(buffer[..len].to_vec())
114 }
115 _ => Err(format!("Fetch failed with return code: {}", result)),
116 }
117 }
118}
119
120#[cfg(feature = "enable-external")]
121fn cf_durable_object_get(key: &str) -> Result<Option<String>, String> {
122 const BUFFER_SIZE: usize = 65536;
123 let mut buffer = vec![0u8; BUFFER_SIZE];
124
125 unsafe {
126 let result = uzumibi_cf_durable_object_get(
127 key.as_ptr(),
128 key.len(),
129 buffer.as_mut_ptr(),
130 BUFFER_SIZE,
131 );
132 match result {
133 -1 => Ok(None),
134 len if len >= 0 => {
135 let len = len as usize;
136 let value = String::from_utf8(buffer[..len].to_vec())
137 .map_err(|e| format!("Failed to decode UTF-8: {}", e))?;
138 Ok(Some(value))
139 }
140 _ => Err(format!(
141 "Unexpected return value from durable_object_get: {}",
142 result
143 )),
144 }
145 }
146}
147
148#[cfg(feature = "enable-external")]
149fn cf_durable_object_set(key: &str, value: &str) -> Result<(), String> {
150 unsafe {
151 let result =
152 uzumibi_cf_durable_object_set(key.as_ptr(), key.len(), value.as_ptr(), value.len());
153 match result {
154 0 => Ok(()),
155 _ => Err(format!("Failed to set value: return code {}", result)),
156 }
157 }
158}
159
160#[cfg(feature = "enable-external")]
161fn cf_secret_get(key: &str) -> Result<Option<String>, String> {
162 const BUFFER_SIZE: usize = 8192;
163 let mut buffer = vec![0u8; BUFFER_SIZE];
164
165 unsafe {
166 let result =
167 uzumibi_cf_secret_get(key.as_ptr(), key.len(), buffer.as_mut_ptr(), BUFFER_SIZE);
168 match result {
169 -1 => Ok(None),
170 len if len >= 0 => {
171 let len = len as usize;
172 let value = String::from_utf8(buffer[..len].to_vec())
173 .map_err(|e| format!("Failed to decode UTF-8: {}", e))?;
174 Ok(Some(value))
175 }
176 _ => Err(format!(
177 "Unexpected return value from secret_get: {}",
178 result
179 )),
180 }
181 }
182}
183
184#[cfg(feature = "enable-external")]
185fn cf_queue_send(queue_name: &str, message: &str) -> Result<(), String> {
186 unsafe {
187 let result = uzumibi_cf_queue_send(
188 queue_name.as_ptr(),
189 queue_name.len(),
190 message.as_ptr(),
191 message.len(),
192 );
193 match result {
194 0 => Ok(()),
195 _ => Err(format!(
196 "Failed to send queue message: return code {}",
197 result
198 )),
199 }
200 }
201}
202
203fn uzumibi_kernel_debug_console_log(
206 vm: &mut VM,
207 args: &[Rc<RObject>],
208) -> Result<Rc<RObject>, mrubyedge::Error> {
209 let msg_obj = &args[0];
210 let msg = mrb_funcall(vm, msg_obj.clone().into(), "to_s", &[])?;
211 let msg: String = msg.as_ref().try_into()?;
212 unsafe {
213 debug_console_log(msg.as_ptr(), msg.len());
214 }
215 Ok(RObject::nil().to_refcount_assigned())
216}
217
218#[cfg(feature = "enable-external")]
220fn uzumibi_fetch_class_fetch(
221 vm: &mut VM,
222 args: &[Rc<RObject>],
223) -> Result<Rc<RObject>, mrubyedge::Error> {
224 let url_obj = &args[0];
225 let url = mrb_funcall(vm, url_obj.clone().into(), "to_s", &[])?;
226 let url: String = url.as_ref().try_into()?;
227
228 let method = if args.len() > 1 {
229 let m = mrb_funcall(vm, args[1].clone().into(), "to_s", &[])?;
230 let m: String = m.as_ref().try_into()?;
231 m
232 } else {
233 "GET".to_string()
234 };
235
236 let body = if args.len() > 2 {
237 let b = mrb_funcall(vm, args[2].clone().into(), "to_s", &[])?;
238 let b: String = b.as_ref().try_into()?;
239 b
240 } else {
241 String::new()
242 };
243
244 let packed_headers = if args.len() > 3 {
246 pack_headers_from_hash(vm, &args[3])?
247 } else {
248 vec![0u8; 2] };
250
251 let packed = cf_fetch(&url, &method, &body, &packed_headers)
252 .map_err(|e| mrubyedge::Error::RuntimeError(format!("Fetch failed: {}", e)))?;
253
254 unpack_response_to_robject(vm, &packed)
256}
257
258#[cfg(feature = "enable-external")]
262fn pack_headers_from_hash(
263 vm: &mut VM,
264 hash_obj: &Rc<RObject>,
265) -> Result<Vec<u8>, mrubyedge::Error> {
266 match &hash_obj.as_ref().value {
267 RValue::Hash(h) => {
268 let hash = h.borrow();
269 let mut buf = Vec::new();
270 let count = hash.len() as u16;
271 buf.extend_from_slice(&count.to_le_bytes());
272 for (_, (key_obj, value_obj)) in hash.iter() {
273 let key = mrb_funcall(vm, key_obj.clone().into(), "to_s", &[])?;
274 let key: String = key.as_ref().try_into()?;
275 let value = mrb_funcall(vm, value_obj.clone().into(), "to_s", &[])?;
276 let value: String = value.as_ref().try_into()?;
277 buf.extend_from_slice(&(key.len() as u16).to_le_bytes());
278 buf.extend_from_slice(key.as_bytes());
279 buf.extend_from_slice(&(value.len() as u16).to_le_bytes());
280 buf.extend_from_slice(value.as_bytes());
281 }
282 Ok(buf)
283 }
284 RValue::Nil => {
285 Ok(vec![0u8; 2]) }
287 _ => Err(mrubyedge::Error::RuntimeError(
288 "headers argument must be a Hash".to_string(),
289 )),
290 }
291}
292
293#[cfg(feature = "enable-external")]
295fn unpack_response_to_robject(vm: &mut VM, buf: &[u8]) -> Result<Rc<RObject>, mrubyedge::Error> {
296 let mut offset = 0;
297
298 let status_code = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
300 offset += 2;
301
302 let headers_count = u16::from_le_bytes([buf[offset], buf[offset + 1]]) as usize;
304 offset += 2;
305
306 let headers_hash = mrb_hash_new(vm, &[])?;
308 for _ in 0..headers_count {
309 let key_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]) as usize;
310 offset += 2;
311 let key = String::from_utf8_lossy(&buf[offset..offset + key_size]).to_string();
312 offset += key_size;
313
314 let value_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]) as usize;
315 offset += 2;
316 let value = String::from_utf8_lossy(&buf[offset..offset + value_size]).to_string();
317 offset += value_size;
318
319 mrb_hash_set_index(
320 headers_hash.clone(),
321 RObject::string(key).to_refcount_assigned(),
322 RObject::string(value).to_refcount_assigned(),
323 )?;
324 }
325
326 let body_size = u32::from_le_bytes([
328 buf[offset],
329 buf[offset + 1],
330 buf[offset + 2],
331 buf[offset + 3],
332 ]) as usize;
333 offset += 4;
334
335 let body = String::from_utf8_lossy(&buf[offset..offset + body_size]).to_string();
337
338 let uzumibi = vm
340 .get_const_by_name("Uzumibi")
341 .ok_or_else(|| mrubyedge::Error::RuntimeError("Uzumibi module not found".to_string()))?;
342 let uzumibi_module = match &uzumibi.as_ref().value {
343 RValue::Module(m) => m.clone(),
344 _ => {
345 return Err(mrubyedge::Error::RuntimeError(
346 "Uzumibi must be a module".to_string(),
347 ));
348 }
349 };
350 let response_class = uzumibi_module
351 .get_const_by_name("Response")
352 .ok_or_else(|| {
353 mrubyedge::Error::RuntimeError("Uzumibi::Response class not found".to_string())
354 })?;
355 let response = mrb_funcall(vm, Some(response_class), "new", &[])?;
356
357 response.set_ivar(
358 "@status_code",
359 RObject::integer(status_code as i64).to_refcount_assigned(),
360 );
361 response.set_ivar("@headers", headers_hash);
362 response.set_ivar("@body", RObject::string(body).to_refcount_assigned());
363
364 Ok(response)
365}
366
367#[cfg(feature = "enable-external")]
369fn uzumibi_kv_class_get(
370 vm: &mut VM,
371 args: &[Rc<RObject>],
372) -> Result<Rc<RObject>, mrubyedge::Error> {
373 let key_obj = &args[0];
374 let key = mrb_funcall(vm, key_obj.clone().into(), "to_s", &[])?;
375 let key: String = key.as_ref().try_into()?;
376
377 match cf_durable_object_get(&key) {
378 Ok(Some(value)) => Ok(RObject::string(value).to_refcount_assigned()),
379 Ok(None) => Ok(RObject::nil().to_refcount_assigned()),
380 Err(e) => Err(mrubyedge::Error::RuntimeError(format!(
381 "Failed to access storage value: {}",
382 e
383 ))),
384 }
385}
386
387#[cfg(feature = "enable-external")]
389fn uzumibi_kv_class_set(
390 vm: &mut VM,
391 args: &[Rc<RObject>],
392) -> Result<Rc<RObject>, mrubyedge::Error> {
393 let key_obj = &args[0];
394 let key = mrb_funcall(vm, key_obj.clone().into(), "to_s", &[])?;
395 let key: String = key.as_ref().try_into()?;
396
397 let value_obj = &args[1];
398 let value = mrb_funcall(vm, value_obj.clone().into(), "to_s", &[])?;
399 let value: String = value.as_ref().try_into()?;
400
401 cf_durable_object_set(&key, &value).map_err(|e| {
402 mrubyedge::Error::RuntimeError(format!("Failed to set storage value: {}", e))
403 })?;
404
405 Ok(RObject::boolean(true).to_refcount_assigned())
406}
407
408#[cfg(feature = "enable-external")]
410fn uzumibi_secret_class_get(
411 vm: &mut VM,
412 args: &[Rc<RObject>],
413) -> Result<Rc<RObject>, mrubyedge::Error> {
414 let key_obj = &args[0];
415 let key = mrb_funcall(vm, key_obj.clone().into(), "to_s", &[])?;
416 let key: String = key.as_ref().try_into()?;
417
418 match cf_secret_get(&key) {
419 Ok(Some(value)) => Ok(RObject::string(value).to_refcount_assigned()),
420 Ok(None) => Ok(RObject::nil().to_refcount_assigned()),
421 Err(e) => Err(mrubyedge::Error::RuntimeError(format!(
422 "Failed to get secret: {}",
423 e
424 ))),
425 }
426}
427
428#[cfg(feature = "enable-external")]
430fn uzumibi_queue_class_send(
431 vm: &mut VM,
432 args: &[Rc<RObject>],
433) -> Result<Rc<RObject>, mrubyedge::Error> {
434 let queue_name_obj = &args[0];
435 let queue_name = mrb_funcall(vm, queue_name_obj.clone().into(), "to_s", &[])?;
436 let queue_name: String = queue_name.as_ref().try_into()?;
437
438 let message_obj = &args[1];
439 let message = mrb_funcall(vm, message_obj.clone().into(), "to_s", &[])?;
440 let message: String = message.as_ref().try_into()?;
441
442 cf_queue_send(&queue_name, &message).map_err(|e| {
443 mrubyedge::Error::RuntimeError(format!("Failed to send queue message: {}", e))
444 })?;
445
446 Ok(RObject::boolean(true).to_refcount_assigned())
447}
448
449#[cfg(feature = "queue")]
453fn uzumibi_message_ack(
454 vm: &mut VM,
455 _args: &[Rc<RObject>],
456) -> Result<Rc<RObject>, mrubyedge::Error> {
457 let self_obj = vm.getself()?;
458 let id_obj = self_obj.get_ivar("@id");
459 if matches!(id_obj.as_ref().value, RValue::Nil) {
460 return Err(mrubyedge::Error::RuntimeError(
461 "Message object does not have @id".to_string(),
462 ));
463 }
464 let id = mrb_funcall(vm, id_obj.into(), "to_s", &[])?;
465 let id: String = id.as_ref().try_into()?;
466
467 unsafe {
468 let result = uzumibi_cf_message_ack(id.as_ptr(), id.len());
469 if result != 0 {
470 return Err(mrubyedge::Error::RuntimeError(format!(
471 "Failed to ack message: return code {}",
472 result
473 )));
474 }
475 }
476 Ok(RObject::boolean(true).to_refcount_assigned())
477}
478
479#[cfg(feature = "queue")]
481fn uzumibi_message_retry(
482 vm: &mut VM,
483 _args: &[Rc<RObject>],
484) -> Result<Rc<RObject>, mrubyedge::Error> {
485 let self_obj = vm.getself()?;
486 let id_obj = self_obj.get_ivar("@id");
487 if matches!(id_obj.as_ref().value, RValue::Nil) {
488 return Err(mrubyedge::Error::RuntimeError(
489 "Message object does not have @id".to_string(),
490 ));
491 }
492 let id = mrb_funcall(vm, id_obj.into(), "to_s", &[])?;
493 let id: String = id.as_ref().try_into()?;
494
495 let delay_seconds: i32 = match vm.get_kwargs() {
496 Some(kwargs) => match kwargs.get("delay_seconds") {
497 Some(val) => {
498 let v: i64 = val.as_ref().try_into()?;
499 v as i32
500 }
501 None => 0,
502 },
503 None => 0,
504 };
505
506 unsafe {
507 let result = uzumibi_cf_message_retry(id.as_ptr(), id.len(), delay_seconds);
508 if result != 0 {
509 return Err(mrubyedge::Error::RuntimeError(format!(
510 "Failed to retry message: return code {}",
511 result
512 )));
513 }
514 }
515 Ok(RObject::boolean(true).to_refcount_assigned())
516}
517
518#[cfg(feature = "queue")]
520fn uzumibi_message_nack(
521 vm: &mut VM,
522 _args: &[Rc<RObject>],
523) -> Result<Rc<RObject>, mrubyedge::Error> {
524 let self_obj = vm.getself()?;
525 let id_obj = self_obj.get_ivar("@id");
526 if matches!(id_obj.as_ref().value, RValue::Nil) {
527 return Err(mrubyedge::Error::RuntimeError(
528 "Message object does not have @id".to_string(),
529 ));
530 }
531 let id = mrb_funcall(vm, id_obj.into(), "to_s", &[])?;
532 let id: String = id.as_ref().try_into()?;
533
534 unsafe {
535 let result = uzumibi_cf_message_retry(id.as_ptr(), id.len(), 0);
536 if result != 0 {
537 return Err(mrubyedge::Error::RuntimeError(format!(
538 "Failed to nack message: return code {}",
539 result
540 )));
541 }
542 }
543 Ok(RObject::boolean(true).to_refcount_assigned())
544}
545
546#[cfg(feature = "queue")]
548fn uzumibi_consumer_on_receive(
549 _vm: &mut VM,
550 _args: &[Rc<RObject>],
551) -> Result<Rc<RObject>, mrubyedge::Error> {
552 Err(mrubyedge::Error::RuntimeError(
553 "on_receive must be implemented by subclass of Uzumibi::Consumer".to_string(),
554 ))
555}
556
557#[cfg(feature = "enable-external")]
560static mut ACCESS_TEAM: Option<String> = None;
561
562#[cfg(feature = "enable-external")]
564fn unpack_response_body(buf: &[u8]) -> Result<String, mrubyedge::Error> {
565 let mut offset = 0;
566 offset += 2;
568 let headers_count = u16::from_le_bytes([buf[offset], buf[offset + 1]]) as usize;
570 offset += 2;
571 for _ in 0..headers_count {
573 let key_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]) as usize;
574 offset += 2 + key_size;
575 let value_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]) as usize;
576 offset += 2 + value_size;
577 }
578 let body_size = u32::from_le_bytes([
580 buf[offset],
581 buf[offset + 1],
582 buf[offset + 2],
583 buf[offset + 3],
584 ]) as usize;
585 offset += 4;
586 Ok(String::from_utf8_lossy(&buf[offset..offset + body_size]).to_string())
588}
589
590#[cfg(feature = "enable-external")]
592fn uzumibi_access_set_team(
593 vm: &mut VM,
594 args: &[Rc<RObject>],
595) -> Result<Rc<RObject>, mrubyedge::Error> {
596 let team = mrb_funcall(vm, args[0].clone().into(), "to_s", &[])?;
597 let team: String = team.as_ref().try_into()?;
598 unsafe {
599 ACCESS_TEAM = Some(team);
600 }
601 Ok(args[0].clone())
602}
603
604#[cfg(feature = "enable-external")]
606fn uzumibi_access_get_identity(
607 vm: &mut VM,
608 args: &[Rc<RObject>],
609) -> Result<Rc<RObject>, mrubyedge::Error> {
610 let token = mrb_funcall(vm, args[0].clone().into(), "to_s", &[])?;
611 let token: String = token.as_ref().try_into()?;
612
613 let team = unsafe {
614 ACCESS_TEAM.as_ref().ok_or_else(|| {
615 mrubyedge::Error::RuntimeError("Uzumibi::Access.team is not set".to_string())
616 })?
617 };
618
619 let url = format!(
620 "https://{}.cloudflareaccess.com/cdn-cgi/access/get-identity",
621 team
622 );
623
624 let cookie_value = format!("CF_Authorization={}", token);
626 let mut headers_buf = Vec::new();
627 let count: u16 = 1;
628 headers_buf.extend_from_slice(&count.to_le_bytes());
629 let key = b"cookie";
630 headers_buf.extend_from_slice(&(key.len() as u16).to_le_bytes());
631 headers_buf.extend_from_slice(key);
632 let val = cookie_value.as_bytes();
633 headers_buf.extend_from_slice(&(val.len() as u16).to_le_bytes());
634 headers_buf.extend_from_slice(val);
635
636 let packed = cf_fetch(&url, "GET", "", &headers_buf)
637 .map_err(|e| mrubyedge::Error::RuntimeError(format!("Access fetch failed: {}", e)))?;
638
639 let body = unpack_response_body(&packed)?;
640
641 let body_robj = RObject::string(body).to_refcount_assigned();
643 let json_value = mrubyedge_serde_json::mrb_json_class_load(vm, &[body_robj])?;
644
645 let uzumibi = vm
647 .get_const_by_name("Uzumibi")
648 .ok_or_else(|| mrubyedge::Error::RuntimeError("Uzumibi module not found".to_string()))?;
649 let uzumibi_module = match &uzumibi.as_ref().value {
650 RValue::Module(m) => m.clone(),
651 _ => {
652 return Err(mrubyedge::Error::RuntimeError(
653 "Uzumibi must be a module".to_string(),
654 ));
655 }
656 };
657 let identity_class = uzumibi_module
658 .get_const_by_name("AccessIdentity")
659 .ok_or_else(|| {
660 mrubyedge::Error::RuntimeError("Uzumibi::AccessIdentity class not found".to_string())
661 })?;
662 let identity = mrb_funcall(vm, Some(identity_class), "new", &[])?;
663
664 let field_mappings = [("user_uuid", "@user_uuid"), ("email", "@email")];
666 for (json_key, ivar_key) in &field_mappings {
667 let val = mrb_funcall(
668 vm,
669 Some(json_value.clone()),
670 "[]",
671 &[RObject::string(json_key.to_string()).to_refcount_assigned()],
672 )?;
673 identity.set_ivar(ivar_key, val);
674 }
675
676 identity.set_ivar("@raw_data", json_value);
678
679 Ok(identity)
680}
681
682fn uzumibi_fetch_assets(
685 _vm: &mut VM,
686 _args: &[Rc<RObject>],
687) -> Result<Rc<RObject>, mrubyedge::Error> {
688 Err(mrubyedge::Error::TaggedError(
689 "UzumibiPassAssets",
690 "pass assets to platform".to_string(),
691 ))
692}
693
694pub fn init_cloudflare_ext(vm: &mut VM) {
699 let runtime_error = vm.get_class_by_name("RuntimeError");
701 vm.define_class("UzumibiPassAssets", Some(runtime_error), None);
702
703 let object = vm.object_class.clone();
705 mrb_define_cmethod(
706 vm,
707 object.clone(),
708 "debug_console",
709 Box::new(uzumibi_kernel_debug_console_log),
710 );
711 mrb_define_cmethod(vm, object, "fetch_assets", Box::new(uzumibi_fetch_assets));
712
713 #[cfg(feature = "enable-external")]
714 {
715 let uzumibi_module = vm.get_module_by_name("Uzumibi");
716
717 let fetch_class = vm.define_class("Fetch", None, Some(uzumibi_module.clone()));
719 mrb_define_class_cmethod(
720 vm,
721 fetch_class,
722 "fetch",
723 Box::new(uzumibi_fetch_class_fetch),
724 );
725
726 let kv_class = vm.define_class("KV", None, Some(uzumibi_module.clone()));
728 mrb_define_class_cmethod(vm, kv_class.clone(), "get", Box::new(uzumibi_kv_class_get));
729 mrb_define_class_cmethod(vm, kv_class, "set", Box::new(uzumibi_kv_class_set));
730
731 let secret_class = vm.define_class("Secret", None, Some(uzumibi_module.clone()));
733 mrb_define_class_cmethod(vm, secret_class, "get", Box::new(uzumibi_secret_class_get));
734
735 let queue_class = vm.define_class("Queue", None, Some(uzumibi_module.clone()));
737 mrb_define_class_cmethod(vm, queue_class, "send", Box::new(uzumibi_queue_class_send));
738
739 let access_class = vm.define_class("Access", None, Some(uzumibi_module.clone()));
741 mrb_define_class_cmethod(
742 vm,
743 access_class.clone(),
744 "team=",
745 Box::new(uzumibi_access_set_team),
746 );
747 mrb_define_class_cmethod(
748 vm,
749 access_class,
750 "get_identity",
751 Box::new(uzumibi_access_get_identity),
752 );
753
754 let identity_class = vm.define_class("AccessIdentity", None, Some(uzumibi_module));
756 let identity_class_obj = RObject::class(identity_class, vm);
757 for attr in ["user_uuid", "email", "raw_data"] {
758 mrb_funcall(
759 vm,
760 Some(identity_class_obj.clone()),
761 "attr_accessor",
762 &[
763 RObject::symbol(mrubyedge::yamrb::value::RSym::new(attr.to_string()))
764 .to_refcount_assigned(),
765 ],
766 )
767 .expect("attr_accessor failed");
768 }
769 }
770
771 #[cfg(feature = "queue")]
772 {
773 let uzumibi_module = vm.get_module_by_name("Uzumibi");
774
775 let consumer_class = vm.define_class("Consumer", None, Some(uzumibi_module.clone()));
777 mrb_define_cmethod(
778 vm,
779 consumer_class,
780 "on_receive",
781 Box::new(uzumibi_consumer_on_receive),
782 );
783
784 let message_class = vm.define_class("Message", None, Some(uzumibi_module));
786 let message_class_obj = RObject::class(message_class.clone(), vm);
787 for attr in ["id", "timestamp", "body", "attempts"] {
788 mrb_funcall(
789 vm,
790 Some(message_class_obj.clone()),
791 "attr_accessor",
792 &[RObject::symbol(RSym::new(attr.to_string())).to_refcount_assigned()],
793 )
794 .expect("attr_accessor failed");
795 }
796 mrb_define_cmethod(
797 vm,
798 message_class.clone(),
799 "ack!",
800 Box::new(uzumibi_message_ack),
801 );
802 mrb_define_cmethod(
803 vm,
804 message_class.clone(),
805 "nack!",
806 Box::new(uzumibi_message_nack),
807 );
808 mrb_define_cmethod(vm, message_class, "retry", Box::new(uzumibi_message_retry));
809 }
810}
811
812#[cfg(feature = "queue")]
820pub fn dispatch_queue_message(vm: &mut VM, buf: &[u8]) -> Result<(), mrubyedge::Error> {
821 let mut offset = 0;
822
823 let id_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]) as usize;
825 offset += 2;
826 let id = String::from_utf8_lossy(&buf[offset..offset + id_size]).to_string();
827 offset += id_size;
828
829 let ts_size = u16::from_le_bytes([buf[offset], buf[offset + 1]]) as usize;
831 offset += 2;
832 let timestamp = String::from_utf8_lossy(&buf[offset..offset + ts_size]).to_string();
833 offset += ts_size;
834
835 let body_size = u32::from_le_bytes([
837 buf[offset],
838 buf[offset + 1],
839 buf[offset + 2],
840 buf[offset + 3],
841 ]) as usize;
842 offset += 4;
843 let body = String::from_utf8_lossy(&buf[offset..offset + body_size]).to_string();
844 offset += body_size;
845
846 let attempts = u32::from_le_bytes([
848 buf[offset],
849 buf[offset + 1],
850 buf[offset + 2],
851 buf[offset + 3],
852 ]) as i64;
853
854 let uzumibi = vm
856 .get_const_by_name("Uzumibi")
857 .ok_or_else(|| mrubyedge::Error::RuntimeError("Uzumibi module not found".to_string()))?;
858 let uzumibi_module = match &uzumibi.as_ref().value {
859 RValue::Module(m) => m.clone(),
860 _ => {
861 return Err(mrubyedge::Error::RuntimeError(
862 "Uzumibi must be a module".to_string(),
863 ));
864 }
865 };
866 let message_class = uzumibi_module.get_const_by_name("Message").ok_or_else(|| {
867 mrubyedge::Error::RuntimeError("Uzumibi::Message class not found".to_string())
868 })?;
869 let message = mrb_funcall(vm, Some(message_class), "new", &[])?;
870
871 message.set_ivar("@id", RObject::string(id).to_refcount_assigned());
872 message.set_ivar(
873 "@timestamp",
874 RObject::string(timestamp).to_refcount_assigned(),
875 );
876 message.set_ivar("@body", RObject::string(body).to_refcount_assigned());
877 message.set_ivar(
878 "@attempts",
879 RObject::integer(attempts).to_refcount_assigned(),
880 );
881
882 let consumer = vm
884 .globals
885 .get("$CONSUMER")
886 .ok_or_else(|| mrubyedge::Error::RuntimeError("$CONSUMER is not defined".to_string()))?;
887 mrb_funcall(vm, consumer.clone().into(), "on_receive", &[message])?;
888
889 Ok(())
890}