From 2629acab023b647efa70a8e6cbf9ab82bce8f278 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Thu, 29 May 2025 11:34:51 -0700 Subject: [PATCH 1/3] Breaking Change: Use non-mutable methods for DurableObject, FormData, Headers & Response --- Cargo.lock | 13 ++ worker-macros/src/durable_object.rs | 132 ++++++++++-------- worker-sandbox/Cargo.toml | 1 + worker-sandbox/src/alarm.rs | 22 ++- worker-sandbox/src/counter.rs | 33 +++-- .../src/test/export_durable_object.rs | 20 +-- worker-sandbox/src/test/put_raw.rs | 10 +- worker-sandbox/tests/durable.spec.ts | 1 - worker-sandbox/tests/mf.ts | 1 + worker-sandbox/wrangler.toml | 1 + worker/src/durable.rs | 36 ++--- worker/src/formdata.rs | 8 +- worker/src/headers.rs | 10 +- worker/src/response.rs | 10 +- 14 files changed, 170 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8dc38916b..a896180cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -959,6 +959,18 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.4.10" @@ -4021,6 +4033,7 @@ dependencies = [ "futures-channel", "futures-util", "getrandom 0.3.3", + "gloo-timers", "hex", "http", "md5", diff --git a/worker-macros/src/durable_object.rs b/worker-macros/src/durable_object.rs index 865453b3f..120a8bce5 100644 --- a/worker-macros/src/durable_object.rs +++ b/worker-macros/src/durable_object.rs @@ -9,14 +9,16 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { let impl_token = imp.impl_token; let trai = imp.trait_.clone(); let (_, trai, _) = trai.ok_or_else(|| Error::new_spanned(impl_token, "Must be a DurableObject trait impl"))?; - if !trai.segments.last().map(|x| x.ident == "DurableObject").unwrap_or(false) { - return Err(Error::new(trai.span(), "Must be a DurableObject trait impl")) + + // Check for the correct trait based on shared parameter + let expected_trait = "DurableObject"; + if !trai.segments.last().map(|x| x.ident == expected_trait).unwrap_or(false) { + return Err(Error::new(trai.span(), format!("Must be a {} trait impl", expected_trait))); } let pound = syn::Token![#](imp.span()).to_token_stream(); let wasm_bindgen_attr = quote! {#pound[wasm_bindgen::prelude::wasm_bindgen]}; - let struct_name = imp.self_ty; let items = imp.items; let mut tokenized = vec![]; @@ -31,6 +33,10 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { let mut optional_methods = OptionalMethods::default(); + let self_name = quote! { DurableObject }; + let self_param = quote! { &self }; + let self_cast = quote! { let static_self: &'static Self = unsafe {&*(self as *const _)}; }; + for item in items { let impl_method = match item { ImplItem::Fn(func) => func, @@ -45,7 +51,6 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { method.sig.ident = Ident::new("_new", method.sig.ident.span()); method.vis = Visibility::Inherited; - // modify the `state` argument so it is type ObjectState let arg_tokens = method.sig.inputs.first_mut().expect("DurableObject `new` method must have 2 arguments: state and env").into_token_stream(); match syn::parse2::(arg_tokens)? { @@ -80,16 +85,22 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { method.sig.ident = Ident::new("_fetch_raw", method.sig.ident.span()); method.vis = Visibility::Inherited; + // For shared objects, we need to use &self instead of &mut self + // Update the method signature to use &self + if let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first_mut() { + receiver.mutability = None; + } + Ok(quote! { #pound[wasm_bindgen::prelude::wasm_bindgen(js_name = fetch)] - pub fn _fetch(&mut self, req: worker::worker_sys::web_sys::Request) -> worker::js_sys::Promise { + pub fn _fetch(#self_param, req: worker::worker_sys::web_sys::Request) -> worker::js_sys::Promise { // SAFETY: // On the surface, this is unsound because the Durable Object could be dropped // while JavaScript still has possession of the future. However, // we know something that Rust doesn't: that the Durable Object will never be destroyed // while there is still a running promise inside of it, therefore we can let a reference // to the durable object escape into a static-lifetime future. - let static_self: &'static mut Self = unsafe {&mut *(self as *mut _)}; + #self_cast wasm_bindgen_futures::future_to_promise(async move { static_self._fetch_raw(req.into()).await.map(worker::worker_sys::web_sys::Response::from).map(wasm_bindgen::JsValue::from) @@ -107,16 +118,14 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { method.sig.ident = Ident::new("_alarm_raw", method.sig.ident.span()); method.vis = Visibility::Inherited; + if let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first_mut() { + receiver.mutability = None; + } + Ok(quote! { #pound[wasm_bindgen::prelude::wasm_bindgen(js_name = alarm)] - pub fn _alarm(&mut self) -> worker::js_sys::Promise { - // SAFETY: - // On the surface, this is unsound because the Durable Object could be dropped - // while JavaScript still has possession of the future. However, - // we know something that Rust doesn't: that the Durable Object will never be destroyed - // while there is still a running promise inside of it, therefore we can let a reference - // to the durable object escape into a static-lifetime future. - let static_self: &'static mut Self = unsafe {&mut *(self as *mut _)}; + pub fn _alarm(#self_param) -> worker::js_sys::Promise { + #self_cast wasm_bindgen_futures::future_to_promise(async move { static_self._alarm_raw().await.map(worker::worker_sys::web_sys::Response::from).map(wasm_bindgen::JsValue::from) @@ -134,9 +143,11 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { method.sig.ident = Ident::new("_websocket_message_raw", method.sig.ident.span()); method.vis = Visibility::Inherited; + + Ok(quote! { #pound[wasm_bindgen::prelude::wasm_bindgen(js_name = webSocketMessage)] - pub fn _websocket_message(&mut self, ws: worker::worker_sys::web_sys::WebSocket, message: wasm_bindgen::JsValue) -> worker::js_sys::Promise { + pub fn _websocket_message(#self_param, ws: worker::worker_sys::web_sys::WebSocket, message: wasm_bindgen::JsValue) -> worker::js_sys::Promise { let ws_message = if let Some(string_message) = message.as_string() { worker::WebSocketIncomingMessage::String(string_message) } else { @@ -144,13 +155,7 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { worker::WebSocketIncomingMessage::Binary(v) }; - // SAFETY: - // On the surface, this is unsound because the Durable Object could be dropped - // while JavaScript still has possession of the future. However, - // we know something that Rust doesn't: that the Durable Object will never be destroyed - // while there is still a running promise inside of it, therefore we can let a reference - // to the durable object escape into a static-lifetime future. - let static_self: &'static mut Self = unsafe {&mut *(self as *mut _)}; + #self_cast wasm_bindgen_futures::future_to_promise(async move { static_self._websocket_message_raw(ws.into(), ws_message).await.map(|_| wasm_bindgen::JsValue::NULL) @@ -168,16 +173,14 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { method.sig.ident = Ident::new("_websocket_close_raw", method.sig.ident.span()); method.vis = Visibility::Inherited; + if let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first_mut() { + receiver.mutability = None; + } + Ok(quote! { #pound[wasm_bindgen::prelude::wasm_bindgen(js_name = webSocketClose)] - pub fn _websocket_close(&mut self, ws: worker::worker_sys::web_sys::WebSocket, code: usize, reason: String, was_clean: bool) -> worker::js_sys::Promise { - // SAFETY: - // On the surface, this is unsound because the Durable Object could be dropped - // while JavaScript still has possession of the future. However, - // we know something that Rust doesn't: that the Durable Object will never be destroyed - // while there is still a running promise inside of it, therefore we can let a reference - // to the durable object escape into a static-lifetime future. - let static_self: &'static mut Self = unsafe {&mut *(self as *mut _)}; + pub fn _websocket_close(#self_param, ws: worker::worker_sys::web_sys::WebSocket, code: usize, reason: String, was_clean: bool) -> worker::js_sys::Promise { + #self_cast wasm_bindgen_futures::future_to_promise(async move { static_self._websocket_close_raw(ws.into(), code, reason, was_clean).await.map(|_| wasm_bindgen::JsValue::NULL) @@ -195,16 +198,14 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { method.sig.ident = Ident::new("_websocket_error_raw", method.sig.ident.span()); method.vis = Visibility::Inherited; + if let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first_mut() { + receiver.mutability = None; + } + Ok(quote! { #pound[wasm_bindgen::prelude::wasm_bindgen(js_name = webSocketError)] - pub fn _websocket_error(&mut self, ws: worker::worker_sys::web_sys::WebSocket, error: wasm_bindgen::JsValue) -> worker::js_sys::Promise { - // SAFETY: - // On the surface, this is unsound because the Durable Object could be dropped - // while JavaScript still has possession of the future. However, - // we know something that Rust doesn't: that the Durable Object will never be destroyed - // while there is still a running promise inside of it, therefore we can let a reference - // to the durable object escape into a static-lifetime future. - let static_self: &'static mut Self = unsafe {&mut *(self as *mut _)}; + pub fn _websocket_error(#self_param, ws: worker::worker_sys::web_sys::WebSocket, error: wasm_bindgen::JsValue) -> worker::js_sys::Promise { + #self_cast wasm_bindgen_futures::future_to_promise(async move { static_self._websocket_error_raw(ws.into(), error.into()).await.map(|_| wasm_bindgen::JsValue::NULL) @@ -220,43 +221,40 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { tokenized.push(tokens?); } + // Generate optional method implementations with correct self type let alarm_tokens = optional_methods.has_alarm.then(|| quote! { - async fn alarm(&mut self) -> ::worker::Result { - self._alarm_raw().await - } - }); + async fn alarm(#self_param) -> ::worker::Result { + self._alarm_raw().await + } + }); let websocket_message_tokens = optional_methods.has_websocket_message.then(|| quote! { - async fn websocket_message(&mut self, ws: ::worker::WebSocket, message: ::worker::WebSocketIncomingMessage) -> ::worker::Result<()> { - self._websocket_message_raw(ws, message).await - } - }); + async fn websocket_message(#self_param, ws: ::worker::WebSocket, message: ::worker::WebSocketIncomingMessage) -> ::worker::Result<()> { + self._websocket_message_raw(ws, message).await + } + }); let websocket_close_tokens = optional_methods.has_websocket_close.then(|| quote! { - async fn websocket_close(&mut self, ws: ::worker::WebSocket, code: usize, reason: String, was_clean: bool) -> ::worker::Result<()> { - self._websocket_close_raw(ws, code, reason, was_clean).await - } - }); + async fn websocket_close(#self_param, ws: ::worker::WebSocket, code: usize, reason: String, was_clean: bool) -> ::worker::Result<()> { + self._websocket_close_raw(ws, code, reason, was_clean).await + } + }); let websocket_error_tokens = optional_methods.has_websocket_error.then(|| quote! { - async fn websocket_error(&mut self, ws: ::worker::WebSocket, error: ::worker::Error) -> ::worker::Result<()> { - self._websocket_error_raw(ws, error).await - } - }); - - Ok(quote! { - #wasm_bindgen_attr - impl #struct_name { - #(#tokenized)* - } + async fn websocket_error(#self_param, ws: ::worker::WebSocket, error: ::worker::Error) -> ::worker::Result<()> { + self._websocket_error_raw(ws, error).await + } + }); + // Generate the appropriate trait implementation + let trait_impl = quote! { #pound[async_trait::async_trait(?Send)] - impl ::worker::durable::DurableObject for #struct_name { + impl ::worker::durable::#self_name for #struct_name { fn new(state: ::worker::durable::State, env: ::worker::Env) -> Self { Self::_new(state._inner(), env) } - async fn fetch(&mut self, req: ::worker::Request) -> ::worker::Result { + async fn fetch(#self_param, req: ::worker::Request) -> ::worker::Result { self._fetch_raw(req).await } @@ -271,12 +269,22 @@ pub fn expand_macro(tokens: TokenStream) -> syn::Result { trait __Need_Durable_Object_Trait_Impl_With_durable_object_Attribute { const MACROED: bool = true; } impl __Need_Durable_Object_Trait_Impl_With_durable_object_Attribute for #struct_name {} + }; + + Ok(quote! { + #wasm_bindgen_attr + impl #struct_name { + #(#tokenized)* + } + + #trait_impl }) }, Item::Struct(struc) => { let tokens = struc.to_token_stream(); let pound = syn::Token![#](struc.span()).to_token_stream(); let struct_name = struc.ident; + Ok(quote! { #pound[wasm_bindgen::prelude::wasm_bindgen] #tokens diff --git a/worker-sandbox/Cargo.toml b/worker-sandbox/Cargo.toml index 5f1af3315..9e35d079e 100644 --- a/worker-sandbox/Cargo.toml +++ b/worker-sandbox/Cargo.toml @@ -33,6 +33,7 @@ chrono = { version = "0.4", default-features = false, features = [ cfg-if = "1.0" console_error_panic_hook = { version = "0.1.7", optional = true } getrandom = { version = "0.3", features = ["wasm_js"] } +gloo-timers = { version = "0.3.0", features = ["futures"] } hex = "0.4" http.workspace = true regex = "1.8.4" diff --git a/worker-sandbox/src/alarm.rs b/worker-sandbox/src/alarm.rs index 49864b195..439232a32 100644 --- a/worker-sandbox/src/alarm.rs +++ b/worker-sandbox/src/alarm.rs @@ -16,7 +16,7 @@ impl DurableObject for AlarmObject { Self { state } } - async fn fetch(&mut self, _: Request) -> Result { + async fn fetch(&self, _: Request) -> Result { let alarmed: bool = match self.state.storage().get("alarmed").await { Ok(alarmed) => alarmed, Err(e) if e.to_string() == "No such value in storage." => { @@ -34,7 +34,7 @@ impl DurableObject for AlarmObject { Response::ok(alarmed.to_string()) } - async fn alarm(&mut self) -> Result { + async fn alarm(&self) -> Result { self.state.storage().put("alarmed", true).await?; console_log!("Alarm has been triggered!"); @@ -54,8 +54,13 @@ pub async fn handle_alarm(_req: Request, env: Env, _data: SomeSharedData) -> Res } #[worker::send] -pub async fn handle_id(_req: Request, env: Env, _data: SomeSharedData) -> Result { - let namespace = env.durable_object("COUNTER").expect("DAWJKHDAD"); +pub async fn handle_id(req: Request, env: Env, _data: SomeSharedData) -> Result { + let durable_object_name = if req.path().contains("shared") { + "SHARED_COUNTER" + } else { + "COUNTER" + }; + let namespace = env.durable_object(durable_object_name).expect("DAWJKHDAD"); let stub = namespace.id_from_name("A")?.get_stub()?; // when calling fetch to a Durable Object, a full URL must be used. Alternatively, a // compatibility flag can be provided in wrangler.toml to opt-in to older behavior: @@ -72,7 +77,12 @@ pub async fn handle_put_raw(req: Request, env: Env, _data: SomeSharedData) -> Re } #[worker::send] -pub async fn handle_websocket(_req: Request, env: Env, _data: SomeSharedData) -> Result { +pub async fn handle_websocket(req: Request, env: Env, _data: SomeSharedData) -> Result { + let durable_object_name = if req.path().contains("shared") { + "SHARED_COUNTER" + } else { + "COUNTER" + }; // Accept / handle a websocket connection let pair = WebSocketPair::new()?; let server = pair.server; @@ -80,7 +90,7 @@ pub async fn handle_websocket(_req: Request, env: Env, _data: SomeSharedData) -> // Connect to Durable Object via WS let namespace = env - .durable_object("COUNTER") + .durable_object(durable_object_name) .expect("failed to get namespace"); let stub = namespace.id_from_name("A")?.get_stub()?; let mut req = Request::new("https://fake-host/ws", Method::Get)?; diff --git a/worker-sandbox/src/counter.rs b/worker-sandbox/src/counter.rs index d421efba4..a964bae82 100644 --- a/worker-sandbox/src/counter.rs +++ b/worker-sandbox/src/counter.rs @@ -1,10 +1,12 @@ +use std::cell::RefCell; + use worker::*; #[durable_object] pub struct Counter { - count: usize, + count: RefCell, state: State, - initialized: bool, + initialized: RefCell, env: Env, } @@ -12,17 +14,17 @@ pub struct Counter { impl DurableObject for Counter { fn new(state: State, env: Env) -> Self { Self { - count: 0, - initialized: false, + count: RefCell::new(0), + initialized: RefCell::new(false), state, env, } } - async fn fetch(&mut self, req: Request) -> Result { - if !self.initialized { - self.initialized = true; - self.count = self.state.storage().get("count").await.unwrap_or(0); + async fn fetch(&self, req: Request) -> Result { + if !*self.initialized.borrow() { + *self.initialized.borrow_mut() = true; + *self.count.borrow_mut() = self.state.storage().get("count").await.unwrap_or(0); } if req.path().eq("/ws") { @@ -40,18 +42,21 @@ impl DurableObject for Counter { .empty()); } - self.count += 10; - self.state.storage().put("count", self.count).await?; + *self.count.borrow_mut() += 10; + self.state + .storage() + .put("count", *self.count.borrow()) + .await?; Response::ok(format!( "[durable_object]: self.count: {}, secret value: {}", - self.count, + self.count.borrow(), self.env.secret("SOME_SECRET")? )) } async fn websocket_message( - &mut self, + &self, ws: WebSocket, _message: WebSocketIncomingMessage, ) -> Result<()> { @@ -69,7 +74,7 @@ impl DurableObject for Counter { } async fn websocket_close( - &mut self, + &self, _ws: WebSocket, _code: usize, _reason: String, @@ -78,7 +83,7 @@ impl DurableObject for Counter { Ok(()) } - async fn websocket_error(&mut self, _ws: WebSocket, _error: Error) -> Result<()> { + async fn websocket_error(&self, _ws: WebSocket, _error: Error) -> Result<()> { Ok(()) } } diff --git a/worker-sandbox/src/test/export_durable_object.rs b/worker-sandbox/src/test/export_durable_object.rs index efd7cb115..751ae4d24 100644 --- a/worker-sandbox/src/test/export_durable_object.rs +++ b/worker-sandbox/src/test/export_durable_object.rs @@ -1,5 +1,5 @@ use serde::Serialize; -use std::collections::HashMap; +use std::{cell::RefCell, collections::HashMap}; use worker::{js_sys::Uint8Array, wasm_bindgen::JsValue, *}; @@ -8,21 +8,24 @@ use crate::ensure; #[durable_object] pub struct MyClass { state: State, - number: usize, + number: RefCell, } #[durable_object] impl DurableObject for MyClass { fn new(state: State, _env: Env) -> Self { - Self { state, number: 0 } + Self { + state, + number: RefCell::new(0), + } } - async fn fetch(&mut self, req: Request) -> Result { + async fn fetch(&self, req: Request) -> Result { let handler = async move { match req.path().as_str() { "/hello" => Response::ok("Hello!"), "/storage" => { - let mut storage = self.state.storage(); + let storage = self.state.storage(); let map = [("one".to_string(), 1), ("two".to_string(), 2)] .iter() .cloned() @@ -116,12 +119,13 @@ impl DurableObject for MyClass { ); } - self.number = storage.get("count").await.unwrap_or(0) + 1; + *self.number.borrow_mut() = storage.get("count").await.unwrap_or(0) + 1; storage.delete_all().await?; - storage.put("count", self.number).await?; - Response::ok(self.number.to_string()) + let count = *self.number.borrow(); + storage.put("count", count).await?; + Response::ok(self.number.borrow().to_string()) } "/transaction" => { Response::error("transactional storage API is still unstable", 501) diff --git a/worker-sandbox/src/test/put_raw.rs b/worker-sandbox/src/test/put_raw.rs index 38e5835e4..1c86d19f6 100644 --- a/worker-sandbox/src/test/put_raw.rs +++ b/worker-sandbox/src/test/put_raw.rs @@ -6,8 +6,8 @@ pub struct PutRawTestObject { } impl PutRawTestObject { - async fn put_raw(&mut self) -> Result<()> { - let mut storage = self.state.storage(); + async fn put_raw(&self) -> Result<()> { + let storage = self.state.storage(); let bytes = Uint8Array::new_with_length(3); bytes.copy_from(b"123"); storage.put_raw("bytes", bytes).await?; @@ -20,8 +20,8 @@ impl PutRawTestObject { Ok(()) } - async fn put_multiple_raw(&mut self) -> Result<()> { - let mut storage = self.state.storage(); + async fn put_multiple_raw(&self) -> Result<()> { + let storage = self.state.storage(); let obj = js_sys::Object::new(); const BAR: &[u8] = b"bar"; let value = Uint8Array::new_with_length(BAR.len() as _); @@ -45,7 +45,7 @@ impl DurableObject for PutRawTestObject { Self { state } } - async fn fetch(&mut self, _: Request) -> Result { + async fn fetch(&self, _: Request) -> Result { self.put_raw().await?; self.put_multiple_raw().await?; diff --git a/worker-sandbox/tests/durable.spec.ts b/worker-sandbox/tests/durable.spec.ts index b7ad20ea5..589ecabc6 100644 --- a/worker-sandbox/tests/durable.spec.ts +++ b/worker-sandbox/tests/durable.spec.ts @@ -43,4 +43,3 @@ describe("durable", () => { expect(closeHandlerWrapper).toBeCalled(); }); }); - diff --git a/worker-sandbox/tests/mf.ts b/worker-sandbox/tests/mf.ts index 7886c5b46..029e3a90b 100644 --- a/worker-sandbox/tests/mf.ts +++ b/worker-sandbox/tests/mf.ts @@ -58,6 +58,7 @@ export const mf = new Miniflare({ }, durableObjects: { COUNTER: "Counter", + SHARED_COUNTER: "SharedCounter", PUT_RAW_TEST_OBJECT: "PutRawTestObject", }, kvNamespaces: ["SOME_NAMESPACE", "FILE_SIZES", "TEST"], diff --git a/worker-sandbox/wrangler.toml b/worker-sandbox/wrangler.toml index 31d821163..026191eb9 100644 --- a/worker-sandbox/wrangler.toml +++ b/worker-sandbox/wrangler.toml @@ -27,6 +27,7 @@ remote-service = "./remote-service" [durable_objects] bindings = [ { name = "COUNTER", class_name = "Counter" }, + { name = "SHARED_COUNTER", class_name = "SharedCounter" }, { name = "ALARM", class_name = "AlarmObject" }, { name = "PUT_RAW_TEST_OBJECT", class_name = "PutRawTestObject" }, ] diff --git a/worker/src/durable.rs b/worker/src/durable.rs index b6751e16c..017f22cf1 100644 --- a/worker/src/durable.rs +++ b/worker/src/durable.rs @@ -309,12 +309,12 @@ impl Storage { } /// Stores the value and associates it with the given key. - pub async fn put(&mut self, key: &str, value: T) -> Result<()> { + pub async fn put(&self, key: &str, value: T) -> Result<()> { self.put_raw(key, serde_wasm_bindgen::to_value(&value)?) .await } - pub async fn put_raw(&mut self, key: &str, value: impl Into) -> Result<()> { + pub async fn put_raw(&self, key: &str, value: impl Into) -> Result<()> { JsFuture::from(self.inner.put(key, value.into())?) .await .map_err(Error::from) @@ -322,7 +322,7 @@ impl Storage { } /// Takes a serializable struct and stores each of its keys and values to storage. - pub async fn put_multiple(&mut self, values: T) -> Result<()> { + pub async fn put_multiple(&self, values: T) -> Result<()> { let values = serde_wasm_bindgen::to_value(&values)?; if !values.is_object() { return Err("Must pass in a struct type".to_string().into()); @@ -343,7 +343,7 @@ impl Storage { /// /// storage.put_multiple_raw(obj); /// ``` - pub async fn put_multiple_raw(&mut self, values: Object) -> Result<()> { + pub async fn put_multiple_raw(&self, values: Object) -> Result<()> { JsFuture::from(self.inner.put_multiple(values.into())?) .await .map_err(Error::from) @@ -351,7 +351,7 @@ impl Storage { } /// Deletes the key and associated value. Returns true if the key existed or false if it didn't. - pub async fn delete(&mut self, key: &str) -> Result { + pub async fn delete(&self, key: &str) -> Result { let fut: JsFuture = self.inner.delete(key)?.into(); fut.await .and_then(|jsv| { @@ -363,7 +363,7 @@ impl Storage { /// Deletes the provided keys and their associated values. Returns a count of the number of /// key-value pairs deleted. - pub async fn delete_multiple(&mut self, keys: Vec>) -> Result { + pub async fn delete_multiple(&self, keys: Vec>) -> Result { let fut: JsFuture = self .inner .delete_multiple( @@ -384,7 +384,7 @@ impl Storage { /// Deletes all keys and associated values, effectively deallocating all storage used by the /// Durable Object. In the event of a failure while the operation is still in flight, it may be /// that only a subset of the data is properly deleted. - pub async fn delete_all(&mut self) -> Result<()> { + pub async fn delete_all(&self) -> Result<()> { let fut: JsFuture = self.inner.delete_all()?.into(); fut.await.map(|_| ()).map_err(Error::from) } @@ -531,7 +531,7 @@ impl Transaction { keys.dyn_into::().map_err(Error::from) } - pub async fn put(&mut self, key: &str, value: T) -> Result<()> { + pub async fn put(&self, key: &str, value: T) -> Result<()> { JsFuture::from(self.inner.put(key, serde_wasm_bindgen::to_value(&value)?)?) .await .map_err(Error::from) @@ -539,7 +539,7 @@ impl Transaction { } // Each key-value pair in the serialized object will be added to the storage - pub async fn put_multiple(&mut self, values: T) -> Result<()> { + pub async fn put_multiple(&self, values: T) -> Result<()> { let values = serde_wasm_bindgen::to_value(&values)?; if !values.is_object() { return Err("Must pass in a struct type".to_string().into()); @@ -550,7 +550,7 @@ impl Transaction { .map(|_| ()) } - pub async fn delete(&mut self, key: &str) -> Result { + pub async fn delete(&self, key: &str) -> Result { let fut: JsFuture = self.inner.delete(key)?.into(); fut.await .and_then(|jsv| { @@ -560,7 +560,7 @@ impl Transaction { .map_err(Error::from) } - pub async fn delete_multiple(&mut self, keys: Vec>) -> Result { + pub async fn delete_multiple(&self, keys: Vec>) -> Result { let fut: JsFuture = self .inner .delete_multiple( @@ -578,7 +578,7 @@ impl Transaction { .map_err(Error::from) } - pub async fn delete_all(&mut self) -> Result<()> { + pub async fn delete_all(&self) -> Result<()> { let fut: JsFuture = self.inner.delete_all()?.into(); fut.await.map(|_| ()).map_err(Error::from) } @@ -802,7 +802,7 @@ impl DurableObject for Chatroom { } } - async fn fetch(&mut self, _req: Request) -> Result { + async fn fetch(&self, _req: Request) -> Result { // do some work when a worker makes a request to this DO Response::ok(&format!("{} active users.", self.users.len())) } @@ -814,16 +814,16 @@ impl DurableObject for Chatroom { pub trait DurableObject { fn new(state: State, env: Env) -> Self; - async fn fetch(&mut self, req: Request) -> Result; + async fn fetch(&self, req: Request) -> Result; #[allow(clippy::diverging_sub_expression)] - async fn alarm(&mut self) -> Result { + async fn alarm(&self) -> Result { unimplemented!("alarm() handler not implemented") } #[allow(unused_variables, clippy::diverging_sub_expression)] async fn websocket_message( - &mut self, + &self, ws: WebSocket, message: WebSocketIncomingMessage, ) -> Result<()> { @@ -832,7 +832,7 @@ pub trait DurableObject { #[allow(unused_variables, clippy::diverging_sub_expression)] async fn websocket_close( - &mut self, + &self, ws: WebSocket, code: usize, reason: String, @@ -842,7 +842,7 @@ pub trait DurableObject { } #[allow(unused_variables, clippy::diverging_sub_expression)] - async fn websocket_error(&mut self, ws: WebSocket, error: Error) -> Result<()> { + async fn websocket_error(&self, ws: WebSocket, error: Error) -> Result<()> { unimplemented!("websocket_error() handler not implemented") } } diff --git a/worker/src/formdata.rs b/worker/src/formdata.rs index a322b4704..13a23c6f5 100644 --- a/worker/src/formdata.rs +++ b/worker/src/formdata.rs @@ -95,18 +95,18 @@ impl FormData { /// Appends a new value onto an existing key inside a `FormData` object, or adds the key if it /// does not already exist. - pub fn append(&mut self, name: &str, value: &str) -> Result<()> { + pub fn append(&self, name: &str, value: &str) -> Result<()> { self.0.append_with_str(name, value).map_err(Error::from) } /// Sets a new value for an existing key inside a `FormData` object, or adds the key/value if it /// does not already exist. - pub fn set(&mut self, name: &str, value: &str) -> Result<()> { + pub fn set(&self, name: &str, value: &str) -> Result<()> { self.0.set_with_str(name, value).map_err(Error::from) } /// Deletes a key/value pair from a `FormData` object. - pub fn delete(&mut self, name: &str) { + pub fn delete(&self, name: &str) { self.0.delete(name) } } @@ -119,7 +119,7 @@ impl From for FormData { impl From, &dyn AsRef<&str>>> for FormData { fn from(m: HashMap<&dyn AsRef<&str>, &dyn AsRef<&str>>) -> Self { - let mut formdata = FormData::new(); + let formdata = FormData::new(); for (k, v) in m { // TODO: determine error case and consider how to handle formdata.set(k.as_ref(), v.as_ref()).unwrap(); diff --git a/worker/src/headers.rs b/worker/src/headers.rs index 7cd977327..ea7205dfe 100644 --- a/worker/src/headers.rs +++ b/worker/src/headers.rs @@ -44,20 +44,20 @@ impl Headers { } /// Returns an error if the name is invalid (e.g. contains spaces) - pub fn append(&mut self, name: &str, value: &str) -> Result<()> { + pub fn append(&self, name: &str, value: &str) -> Result<()> { self.0.append(name, value).map_err(Error::from) } /// Sets a new value for an existing header inside a `Headers` object, or adds the header if it does not already exist. /// Returns an error if the name is invalid (e.g. contains spaces) - pub fn set(&mut self, name: &str, value: &str) -> Result<()> { + pub fn set(&self, name: &str, value: &str) -> Result<()> { self.0.set(name, value).map_err(Error::from) } /// Deletes a header from a `Headers` object. /// Returns an error if the name is invalid (e.g. contains spaces) /// or if the JS Headers object's guard is immutable (e.g. for an incoming request) - pub fn delete(&mut self, name: &str) -> Result<()> { + pub fn delete(&self, name: &str) -> Result<()> { self.0.delete(name).map_err(Error::from) } @@ -127,7 +127,7 @@ impl IntoIterator for &Headers { impl> FromIterator<(T, T)> for Headers { fn from_iter>(iter: U) -> Self { - let mut headers = Headers::new(); + let headers = Headers::new(); iter.into_iter().for_each(|(name, value)| { headers.append(name.as_ref(), value.as_ref()).ok(); }); @@ -137,7 +137,7 @@ impl> FromIterator<(T, T)> for Headers { impl<'a, T: AsRef> FromIterator<&'a (T, T)> for Headers { fn from_iter>(iter: U) -> Self { - let mut headers = Headers::new(); + let headers = Headers::new(); iter.into_iter().for_each(|(name, value)| { headers.append(name.as_ref(), value.as_ref()).ok(); }); diff --git a/worker/src/response.rs b/worker/src/response.rs index cf3c73599..4ffac586b 100644 --- a/worker/src/response.rs +++ b/worker/src/response.rs @@ -376,7 +376,7 @@ impl ResponseBuilder { } /// Set a single header on this response. - pub fn with_header(mut self, key: &str, value: &str) -> Result { + pub fn with_header(self, key: &str, value: &str) -> Result { self.headers.set(key, value)?; Ok(self) } @@ -458,7 +458,7 @@ impl ResponseBuilder { /// Create a `Response` using `B` as the body encoded as JSON. Sets the associated /// `Content-Type` header for the `Response` as `application/json`. - pub fn from_json(mut self, value: &B) -> Result { + pub fn from_json(self, value: &B) -> Result { if let Ok(data) = serde_json::to_string(value) { self.headers.set(CONTENT_TYPE, "application/json")?; Ok(self.fixed(data.into_bytes())) @@ -469,7 +469,7 @@ impl ResponseBuilder { /// Create a `Response` using the body encoded as HTML. Sets the associated `Content-Type` /// header for the `Response` as `text/html; charset=utf-8`. - pub fn from_html(mut self, html: impl AsRef) -> Result { + pub fn from_html(self, html: impl AsRef) -> Result { self.headers.set(CONTENT_TYPE, "text/html; charset=utf-8")?; let data = html.as_ref().as_bytes().to_vec(); Ok(self.fixed(data)) @@ -477,7 +477,7 @@ impl ResponseBuilder { /// Create a `Response` using unprocessed bytes provided. Sets the associated `Content-Type` /// header for the `Response` as `application/octet-stream`. - pub fn from_bytes(mut self, bytes: Vec) -> Result { + pub fn from_bytes(self, bytes: Vec) -> Result { self.headers.set(CONTENT_TYPE, "application/octet-stream")?; Ok(self.fixed(bytes)) } @@ -510,7 +510,7 @@ impl ResponseBuilder { /// Create a `Response` using unprocessed text provided. Sets the associated `Content-Type` /// header for the `Response` as `text/plain; charset=utf-8`. - pub fn ok(mut self, body: impl Into) -> Result { + pub fn ok(self, body: impl Into) -> Result { self.headers .set(CONTENT_TYPE, "text/plain; charset=utf-8")?; From b5d612b956d19fa76b7a7af76a5e71059653dba0 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 17 Jun 2025 11:47:25 -0700 Subject: [PATCH 2/3] storage fixups --- worker/src/durable.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worker/src/durable.rs b/worker/src/durable.rs index 017f22cf1..e2335b4df 100644 --- a/worker/src/durable.rs +++ b/worker/src/durable.rs @@ -481,7 +481,7 @@ impl Storage { fut.await.map(|_| ()).map_err(Error::from) } - pub async fn transaction(&mut self, mut closure: F) -> Result<()> + pub async fn transaction(&self, mut closure: F) -> Result<()> where F: FnMut(Transaction) -> Fut + Copy + 'static, Fut: Future> + 'static, @@ -600,7 +600,7 @@ impl Transaction { .map_err(Error::from) } - pub fn rollback(&mut self) -> Result<()> { + pub fn rollback(&self) -> Result<()> { self.inner.rollback().map_err(Error::from) } } From 39497731824d19922b49ed99c9f62ca6a1aa8db3 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 17 Jun 2025 11:49:13 -0700 Subject: [PATCH 3/3] clippy --- worker-sandbox/src/counter.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/worker-sandbox/src/counter.rs b/worker-sandbox/src/counter.rs index a964bae82..d6578e460 100644 --- a/worker-sandbox/src/counter.rs +++ b/worker-sandbox/src/counter.rs @@ -43,10 +43,8 @@ impl DurableObject for Counter { } *self.count.borrow_mut() += 10; - self.state - .storage() - .put("count", *self.count.borrow()) - .await?; + let count = *self.count.borrow(); + self.state.storage().put("count", count).await?; Response::ok(format!( "[durable_object]: self.count: {}, secret value: {}",