Skip to content

Instantly share code, notes, and snippets.

@connyay
Created October 10, 2025 19:58
Show Gist options
  • Select an option

  • Save connyay/a3e594bbe529416a269c68ce6311e4fb to your computer and use it in GitHub Desktop.

Select an option

Save connyay/a3e594bbe529416a269c68ce6311e4fb to your computer and use it in GitHub Desktop.
use std::future::Future;
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<F>(&self, future: F)
where
F: Future<Output = ()> + 'static,
{
self.inner
.wait_until(&future_to_promise(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<String>,
/// }
///
/// let props = ctx.props::<MyProps>()?;
/// ```
///
/// See: <https://developers.cloudflare.com/workers/runtime-apis/context/#props>
pub fn props<T: DeserializeOwned>(&self) -> Result<T> {
Ok(serde_wasm_bindgen::from_value(self.inner.props())?)
}
}
impl AsRef<JsContext> 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<String>,
}
#[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<TestProps> = 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<EmptyProps> = context.props();
assert!(result.is_ok());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment