worker/
dynamic_dispatch.rs1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value as JsonValue;
5use wasm_bindgen::{JsCast, JsValue};
6use worker_sys::DynamicDispatcher as DynamicDispatcherSys;
7
8use crate::{env::EnvBinding, Fetcher, Result};
9
10#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
11pub struct DispatchGetOptions {
12 #[serde(flatten)]
13 pub extra: HashMap<String, JsonValue>,
14}
15
16#[derive(Debug, Clone)]
28pub struct DynamicDispatcher(DynamicDispatcherSys);
29
30impl DynamicDispatcher {
31 pub fn get(&self, name: impl Into<String>) -> Result<Fetcher> {
33 let fetcher_sys = self
34 .0
35 .get(name.into(), JsValue::undefined(), JsValue::undefined())?;
36 Ok(fetcher_sys.into())
37 }
38
39 pub fn get_with_args<T: Serialize>(&self, name: impl Into<String>, args: T) -> Result<Fetcher> {
41 let args_js = to_js_value(&args)?;
42 let fetcher_sys = self.0.get(name.into(), args_js, JsValue::undefined())?;
43 Ok(fetcher_sys.into())
44 }
45
46 pub fn get_with_options<T: Serialize>(
48 &self,
49 name: impl Into<String>,
50 args: T,
51 options: DispatchGetOptions,
52 ) -> Result<Fetcher> {
53 let args_js = to_js_value(&args)?;
54 let options_js = match dispatch_options_json(options) {
55 Some(options) => to_js_value(&options)?,
56 None => JsValue::undefined(),
57 };
58 let fetcher_sys = self.0.get(name.into(), args_js, options_js)?;
59 Ok(fetcher_sys.into())
60 }
61}
62
63fn to_js_value<T: Serialize>(value: &T) -> Result<JsValue> {
64 value
65 .serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true))
66 .map_err(Into::into)
67}
68
69fn dispatch_options_json(options: DispatchGetOptions) -> Option<JsonValue> {
70 if options.extra.is_empty() {
71 None
72 } else {
73 Some(JsonValue::Object(options.extra.into_iter().collect()))
74 }
75}
76
77impl EnvBinding for DynamicDispatcher {
78 const TYPE_NAME: &'static str = "Object";
79
80 fn get(val: JsValue) -> Result<Self> {
81 if !val.is_object() {
82 return Err("Binding cannot be cast to DynamicDispatcher from non-object value".into());
83 }
84
85 let has_get = js_sys::Reflect::has(&val, &JsValue::from("get"))?;
86 if !has_get {
87 return Err("Binding cannot be cast to DynamicDispatcher: missing `get` method".into());
88 }
89
90 Ok(Self(val.unchecked_into()))
91 }
92}
93
94impl JsCast for DynamicDispatcher {
95 fn instanceof(val: &JsValue) -> bool {
96 val.is_object()
97 }
98
99 fn unchecked_from_js(val: JsValue) -> Self {
100 DynamicDispatcher(val.unchecked_into())
101 }
102
103 fn unchecked_from_js_ref(val: &JsValue) -> &Self {
104 unsafe { &*(val as *const JsValue as *const Self) }
105 }
106}
107
108impl AsRef<JsValue> for DynamicDispatcher {
109 fn as_ref(&self) -> &wasm_bindgen::JsValue {
110 &self.0
111 }
112}
113
114impl From<JsValue> for DynamicDispatcher {
115 fn from(val: JsValue) -> Self {
116 DynamicDispatcher(val.unchecked_into())
117 }
118}
119
120impl From<DynamicDispatcher> for JsValue {
121 fn from(sec: DynamicDispatcher) -> Self {
122 sec.0.into()
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use std::collections::HashMap;
129
130 use super::{dispatch_options_json, DispatchGetOptions};
131
132 #[test]
133 fn empty_options_serialize_to_none() {
134 assert!(dispatch_options_json(DispatchGetOptions::default()).is_none());
135 }
136
137 #[test]
138 fn dispatch_options_preserve_extra_fields() {
139 let mut extra = HashMap::new();
140 extra.insert("trace".to_string(), serde_json::json!(true));
141
142 let options = dispatch_options_json(DispatchGetOptions { extra })
143 .expect("options should be present");
144
145 assert_eq!(options["trace"], serde_json::json!(true));
146 }
147}