use std::future::Future; use std::panic::AssertUnwindSafe; use crate::worker_sys::Context as JsContext; use crate::Result; use serde::de::DeserializeOwned; use wasm_bindgen::JsValue; use wasm_bindgen_futures::future_to_promise; /// A context bound to a `fetch` event. #[derive(Debug)] pub struct Context { inner: JsContext, } unsafe impl Send for Context {} unsafe impl Sync for Context {} impl Context { /// Constructs a context from an underlying JavaScript context object. pub fn new(inner: JsContext) -> Self { Self { inner } } /// Extends the lifetime of the "fetch" event which this context is bound to, /// until the given future has been completed. The future is executed before the handler /// terminates but does not block the response. For example, this is ideal for caching /// responses or handling logging. /// ```no_run /// context.wait_until(async move { /// let _ = cache.put(request, response).await; /// }); /// ``` pub fn wait_until(&self, future: F) where F: Future + 'static, { self.inner .wait_until(&future_to_promise(AssertUnwindSafe(async { future.await; Ok(JsValue::UNDEFINED) }))) .unwrap() } /// Prevents a runtime error response when the Worker script throws an unhandled exception. /// Instead, the script will "fail open", which will proxy the request to the origin server /// as though the Worker was never invoked. pub fn pass_through_on_exception(&self) { self.inner.pass_through_on_exception().unwrap() } /// Get the props passed to this worker execution context. /// /// Props provide a way to pass additional configuration to a worker based on the context /// in which it was invoked. For example, when your Worker is called by another Worker via /// a Service Binding, props can provide information about the calling worker. /// /// Props are configured in your wrangler.toml when setting up Service Bindings: /// ```toml /// [[services]] /// binding = "MY_SERVICE" /// service = "my-worker" /// props = { clientId = "frontend", permissions = ["read", "write"] } /// ``` /// /// Then deserialize them to your custom type: /// ```no_run /// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct MyProps { /// clientId: String, /// permissions: Vec, /// } /// /// let props = ctx.props::()?; /// ``` /// /// See: pub fn props(&self) -> Result { Ok(serde_wasm_bindgen::from_value(self.inner.props())?) } } impl AsRef for Context { fn as_ref(&self) -> &JsContext { &self.inner } } #[cfg(test)] mod tests { use super::*; use serde::Deserialize; use wasm_bindgen::JsCast; use wasm_bindgen_test::*; #[derive(Debug, Deserialize, PartialEq)] struct TestProps { #[serde(rename = "clientId")] client_id: String, permissions: Vec, } #[wasm_bindgen_test] fn test_props_deserialization() { // Create a mock ExecutionContext with props let obj = js_sys::Object::new(); // Add props to the object let props = js_sys::Object::new(); js_sys::Reflect::set(&props, &"clientId".into(), &"frontend-worker".into()).unwrap(); let permissions = js_sys::Array::new(); permissions.push(&"read".into()); permissions.push(&"write".into()); js_sys::Reflect::set(&props, &"permissions".into(), &permissions.into()).unwrap(); js_sys::Reflect::set(&obj, &"props".into(), &props.into()).unwrap(); // Create a Context from the mock object let js_context: JsContext = obj.unchecked_into(); let context = Context::new(js_context); // Test that props can be deserialized let result: Result = context.props(); assert!(result.is_ok()); let props = result.unwrap(); assert_eq!(props.client_id, "frontend-worker"); assert_eq!(props.permissions, vec!["read", "write"]); } #[wasm_bindgen_test] fn test_props_empty_object() { // Create a mock ExecutionContext with empty props let obj = js_sys::Object::new(); let props = js_sys::Object::new(); js_sys::Reflect::set(&obj, &"props".into(), &props.into()).unwrap(); let js_context: JsContext = obj.unchecked_into(); let context = Context::new(js_context); #[derive(Debug, Deserialize, PartialEq)] struct EmptyProps {} let result: Result = context.props(); assert!(result.is_ok()); } }