branch: main
durable_object.rs
8800 bytesRaw
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<Self> {
        let ident = input.parse::<Ident>()?;
        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 {
                <Self as ::worker::DurableObject>::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 {
                    <Self as ::worker::DurableObject>::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 {
                    <Self as ::worker::DurableObject>::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 {
                    <Self as ::worker::DurableObject>::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 {
                    <Self as ::worker::DurableObject>::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 {
                    <Self as ::worker::DurableObject>::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<TokenStream> {
    // Try to give a nice error for previous impl usage
    if syn::parse2::<ItemImpl>(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::<ItemStruct>(tokens)?;

    let durable_object_type = (!attr.is_empty())
        .then(|| syn::parse2::<DurableObjectType>(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)*
            }
        };
    })
}