diff --git a/uzumibi-cli/templates/cloudflare/__features__/enable-external/src/index.js b/uzumibi-cli/templates/cloudflare/__features__/enable-external/src/index.js index b0ec4f6..6c91ff7 100644 --- a/uzumibi-cli/templates/cloudflare/__features__/enable-external/src/index.js +++ b/uzumibi-cli/templates/cloudflare/__features__/enable-external/src/index.js @@ -165,6 +165,20 @@ export default { return 0; }, + // Secret.get(key) -> secret value from env bindings (secrets are accessed via env on Cloudflare Workers) + uzumibi_cf_secret_get: (keyPtr, keySize, resultPtr, resultMaxSize) => { + const memory = exports.memory; + const key = decoder.decode(new Uint8Array(memory.buffer, keyPtr, keySize)); + const value = env[key]; + if (value === undefined || value === null) { + return -1; + } + const valueBytes = encoder.encode(String(value)); + const length = Math.min(valueBytes.length, resultMaxSize); + new Uint8Array(memory.buffer, resultPtr, resultMaxSize).set(valueBytes.slice(0, length)); + return length; + }, + // Queue.send(queue_name, message) uzumibi_cf_queue_send: async (queueNamePtr, queueNameSize, messagePtr, messageSize) => { const memory = exports.memory; diff --git a/uzumibi-cloudflare-ext/src/lib.rs b/uzumibi-cloudflare-ext/src/lib.rs index 000d121..13624dd 100644 --- a/uzumibi-cloudflare-ext/src/lib.rs +++ b/uzumibi-cloudflare-ext/src/lib.rs @@ -65,6 +65,12 @@ unsafe extern "C" { message_ptr: *const u8, message_size: usize, ) -> i32; + unsafe fn uzumibi_cf_secret_get( + key_ptr: *const u8, + key_size: usize, + result_ptr: *mut u8, + result_max_size: usize, + ) -> i32; } // ---- Debug console ---- @@ -151,6 +157,30 @@ fn cf_durable_object_set(key: &str, value: &str) -> Result<(), String> { } } +#[cfg(feature = "enable-external")] +fn cf_secret_get(key: &str) -> Result, String> { + const BUFFER_SIZE: usize = 8192; + let mut buffer = vec![0u8; BUFFER_SIZE]; + + unsafe { + let result = + uzumibi_cf_secret_get(key.as_ptr(), key.len(), buffer.as_mut_ptr(), BUFFER_SIZE); + match result { + -1 => Ok(None), + len if len >= 0 => { + let len = len as usize; + let value = String::from_utf8(buffer[..len].to_vec()) + .map_err(|e| format!("Failed to decode UTF-8: {}", e))?; + Ok(Some(value)) + } + _ => Err(format!( + "Unexpected return value from secret_get: {}", + result + )), + } + } +} + #[cfg(feature = "enable-external")] fn cf_queue_send(queue_name: &str, message: &str) -> Result<(), String> { unsafe { @@ -375,6 +405,26 @@ fn uzumibi_kv_class_set( Ok(RObject::boolean(true).to_refcount_assigned()) } +/// Secret.get(key) -> String | nil +#[cfg(feature = "enable-external")] +fn uzumibi_secret_class_get( + vm: &mut VM, + args: &[Rc], +) -> Result, mrubyedge::Error> { + let key_obj = &args[0]; + let key = mrb_funcall(vm, key_obj.clone().into(), "to_s", &[])?; + let key: String = key.as_ref().try_into()?; + + match cf_secret_get(&key) { + Ok(Some(value)) => Ok(RObject::string(value).to_refcount_assigned()), + Ok(None) => Ok(RObject::nil().to_refcount_assigned()), + Err(e) => Err(mrubyedge::Error::RuntimeError(format!( + "Failed to get secret: {}", + e + ))), + } +} + /// Queue.send(queue_name, message) #[cfg(feature = "enable-external")] fn uzumibi_queue_class_send( @@ -678,6 +728,10 @@ pub fn init_cloudflare_ext(vm: &mut VM) { mrb_define_class_cmethod(vm, kv_class.clone(), "get", Box::new(uzumibi_kv_class_get)); mrb_define_class_cmethod(vm, kv_class, "set", Box::new(uzumibi_kv_class_set)); + // Uzumibi::Secret.get(key) + let secret_class = vm.define_class("Secret", None, Some(uzumibi_module.clone())); + mrb_define_class_cmethod(vm, secret_class, "get", Box::new(uzumibi_secret_class_get)); + // Uzumibi::Queue.send(queue_name, message) let queue_class = vm.define_class("Queue", None, Some(uzumibi_module.clone())); mrb_define_class_cmethod(vm, queue_class, "send", Box::new(uzumibi_queue_class_send)); diff --git a/uzumibi-on-cloudflare-spike/src/index.js b/uzumibi-on-cloudflare-spike/src/index.js index 8551be5..35de2e7 100644 --- a/uzumibi-on-cloudflare-spike/src/index.js +++ b/uzumibi-on-cloudflare-spike/src/index.js @@ -165,6 +165,20 @@ export default { return 0; }, + // Secret.get(key) -> secret value from env bindings (secrets are accessed via env on Cloudflare Workers) + uzumibi_cf_secret_get: (keyPtr, keySize, resultPtr, resultMaxSize) => { + const memory = exports.memory; + const key = decoder.decode(new Uint8Array(memory.buffer, keyPtr, keySize)); + const value = env[key]; + if (value === undefined || value === null) { + return -1; + } + const valueBytes = encoder.encode(String(value)); + const length = Math.min(valueBytes.length, resultMaxSize); + new Uint8Array(memory.buffer, resultPtr, resultMaxSize).set(valueBytes.slice(0, length)); + return length; + }, + // Queue.send(queue_name, message) uzumibi_cf_queue_send: async (queueNamePtr, queueNameSize, messagePtr, messageSize) => { const memory = exports.memory;