use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{Error, ItemImpl, ItemStruct}; enum DurableObjectType { Fetch, Alarm, WebSocket, } impl syn::parse::Parse for DurableObjectType { fn parse(input: syn::parse::ParseStream) -> syn::Result { let ident = input.parse::()?; match &*ident.to_string() { "fetch" => Ok(Self::Fetch), "alarm" => Ok(Self::Alarm), "websocket" => Ok(Self::WebSocket), _ => Err(Error::new(ident.span(), "must have either 'fetch', 'alarm' or 'websocket' attribute, e.g. #[durable_object(websocket)]")) } } } mod bindgen_methods { use proc_macro2::TokenStream; use quote::quote; pub fn core() -> TokenStream { quote! { #[wasm_bindgen(constructor, wasm_bindgen=::worker::wasm_bindgen)] pub fn new( state: ::worker::worker_sys::DurableObjectState, env: ::worker::Env ) -> Self { ::new( ::worker::durable::State::from(state), env ) } #[wasm_bindgen(js_name = fetch, wasm_bindgen=::worker::wasm_bindgen)] pub fn fetch( &self, req: ::worker::worker_sys::web_sys::Request ) -> ::worker::js_sys::Promise { // SAFETY: // 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 Self = unsafe { &*(self as *const _) }; ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { ::fetch(static_self, req.into()).await .map(::worker::worker_sys::web_sys::Response::from) .map(::worker::wasm_bindgen::JsValue::from) .map_err(::worker::wasm_bindgen::JsValue::from) })) } } } pub fn alarm() -> TokenStream { quote! { #[wasm_bindgen(js_name = alarm, wasm_bindgen=::worker::wasm_bindgen)] pub fn alarm(&self) -> ::worker::js_sys::Promise { // SAFETY: // 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 Self = unsafe { &*(self as *const _) }; ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { ::alarm(static_self).await .map(::worker::worker_sys::web_sys::Response::from) .map(::worker::wasm_bindgen::JsValue::from) .map_err(::worker::wasm_bindgen::JsValue::from) })) } } } pub fn websocket() -> TokenStream { quote! { #[wasm_bindgen(js_name = webSocketMessage, wasm_bindgen=::worker::wasm_bindgen)] pub fn websocket_message( &self, ws: ::worker::worker_sys::web_sys::WebSocket, message: ::worker::wasm_bindgen::JsValue ) -> ::worker::js_sys::Promise { let message = match message.as_string() { Some(message) => ::worker::WebSocketIncomingMessage::String(message), None => ::worker::WebSocketIncomingMessage::Binary( ::worker::js_sys::Uint8Array::new(&message).to_vec() ) }; // SAFETY: // 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 Self = unsafe { &*(self as *const _) }; ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { ::websocket_message(static_self, ws.into(), message).await .map(|_| ::worker::wasm_bindgen::JsValue::NULL) .map_err(::worker::wasm_bindgen::JsValue::from) })) } #[wasm_bindgen(js_name = webSocketClose, wasm_bindgen=::worker::wasm_bindgen)] pub fn websocket_close( &self, ws: ::worker::worker_sys::web_sys::WebSocket, code: usize, reason: String, was_clean: bool ) -> ::worker::js_sys::Promise { // SAFETY: // 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 Self = unsafe { &*(self as *const _) }; ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { ::websocket_close(static_self, ws.into(), code, reason, was_clean).await .map(|_| ::worker::wasm_bindgen::JsValue::NULL) .map_err(::worker::wasm_bindgen::JsValue::from) })) } #[wasm_bindgen(js_name = webSocketError, wasm_bindgen=::worker::wasm_bindgen)] pub fn websocket_error( &self, ws: ::worker::worker_sys::web_sys::WebSocket, error: ::worker::wasm_bindgen::JsValue ) -> ::worker::js_sys::Promise { // SAFETY: // 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 Self = unsafe { &*(self as *const _) }; ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { ::websocket_error(static_self, ws.into(), error.into()).await .map(|_| ::worker::wasm_bindgen::JsValue::NULL) .map_err(::worker::wasm_bindgen::JsValue::from) })) } } } } pub fn expand_macro(attr: TokenStream, tokens: TokenStream) -> syn::Result { // Try to give a nice error for previous impl usage if syn::parse2::(tokens.clone()).is_ok() { return Err(syn::Error::new( proc_macro2::Span::call_site(), "The #[durable_object] macro is no longer required for `impl` blocks, and can be removed" )); } let target = syn::parse2::(tokens)?; let durable_object_type = (!attr.is_empty()) .then(|| syn::parse2::(attr)) .transpose()?; let bindgen_methods = match durable_object_type { // if not specified, bindgen all. // this is expected behavior, and is also required for #[durable_object] to compile and work None => vec![ bindgen_methods::core(), bindgen_methods::alarm(), bindgen_methods::websocket(), ], // if specified, bindgen only related methods. Some(DurableObjectType::Fetch) => vec![bindgen_methods::core()], Some(DurableObjectType::Alarm) => vec![bindgen_methods::core(), bindgen_methods::alarm()], Some(DurableObjectType::WebSocket) => { vec![bindgen_methods::core(), bindgen_methods::websocket()] } }; let target_name = &target.ident; Ok(quote! { #target impl ::worker::has_durable_object_attribute for #target_name {} const _: () = { use ::worker::wasm_bindgen::prelude::*; #[allow(unused_imports)] use ::worker::DurableObject; #[wasm_bindgen(wasm_bindgen=::worker::wasm_bindgen)] #[::worker::consume] #target #[wasm_bindgen(wasm_bindgen=::worker::wasm_bindgen)] impl #target_name { #(#bindgen_methods)* } }; }) }