1use std::collections::BTreeMap;
2use std::path::{Path, PathBuf};
3
4#[deprecated]
5pub type ManifestMemory = MemoryOptions;
6
7#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
9#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
10#[serde(deny_unknown_fields)]
11pub struct MemoryOptions {
12    pub max_pages: Option<u32>,
14
15    #[serde(default)]
17    pub max_http_response_bytes: Option<u64>,
18
19    #[serde(default = "default_var_bytes")]
22    pub max_var_bytes: Option<u64>,
23}
24
25impl MemoryOptions {
26    pub fn new() -> Self {
28        Default::default()
29    }
30
31    pub fn with_max_pages(mut self, pages: u32) -> Self {
33        self.max_pages = Some(pages);
34        self
35    }
36
37    pub fn with_max_http_response_bytes(mut self, bytes: u64) -> Self {
39        self.max_http_response_bytes = Some(bytes);
40        self
41    }
42
43    pub fn with_max_var_bytes(mut self, bytes: u64) -> Self {
45        self.max_var_bytes = Some(bytes);
46        self
47    }
48}
49
50fn default_var_bytes() -> Option<u64> {
51    Some(1024 * 1024)
52}
53
54#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
56#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
57#[serde(deny_unknown_fields)]
58pub struct HttpRequest {
59    pub url: String,
61
62    #[serde(default)]
64    pub headers: std::collections::BTreeMap<String, String>,
65
66    pub method: Option<String>,
68}
69
70impl HttpRequest {
71    pub fn new(url: impl Into<String>) -> HttpRequest {
73        HttpRequest {
74            url: url.into(),
75            headers: Default::default(),
76            method: None,
77        }
78    }
79
80    pub fn with_method(mut self, method: impl Into<String>) -> HttpRequest {
82        self.method = Some(method.into());
83        self
84    }
85
86    pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> HttpRequest {
88        self.headers.insert(key.into(), value.into());
89        self
90    }
91}
92
93#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
95#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
96#[serde(deny_unknown_fields)]
97pub struct WasmMetadata {
98    pub name: Option<String>,
100
101    pub hash: Option<String>,
103}
104
105impl From<HttpRequest> for Wasm {
106    fn from(req: HttpRequest) -> Self {
107        Wasm::Url {
108            req,
109            meta: WasmMetadata::default(),
110        }
111    }
112}
113
114impl From<std::path::PathBuf> for Wasm {
115    fn from(path: std::path::PathBuf) -> Self {
116        Wasm::File {
117            path,
118            meta: WasmMetadata::default(),
119        }
120    }
121}
122
123impl From<Vec<u8>> for Wasm {
124    fn from(data: Vec<u8>) -> Self {
125        Wasm::Data {
126            data,
127            meta: WasmMetadata::default(),
128        }
129    }
130}
131
132#[deprecated]
133pub type ManifestWasm = Wasm;
134
135#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
137#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
138#[serde(untagged)]
139#[serde(deny_unknown_fields)]
140pub enum Wasm {
141    File {
143        path: PathBuf,
144        #[serde(flatten)]
145        meta: WasmMetadata,
146    },
147
148    Data {
150        #[serde(with = "wasmdata")]
151        #[cfg_attr(feature = "json_schema", schemars(schema_with = "wasmdata_schema"))]
152        data: Vec<u8>,
153        #[serde(flatten)]
154        meta: WasmMetadata,
155    },
156
157    Url {
159        #[serde(flatten)]
160        req: HttpRequest,
161        #[serde(flatten)]
162        meta: WasmMetadata,
163    },
164}
165
166impl Wasm {
167    pub fn file(path: impl AsRef<std::path::Path>) -> Self {
169        Wasm::File {
170            path: path.as_ref().to_path_buf(),
171            meta: Default::default(),
172        }
173    }
174
175    pub fn data(data: impl Into<Vec<u8>>) -> Self {
177        Wasm::Data {
178            data: data.into(),
179            meta: Default::default(),
180        }
181    }
182
183    pub fn url(url: impl Into<String>) -> Self {
185        Wasm::Url {
186            req: HttpRequest {
187                url: url.into(),
188                headers: Default::default(),
189                method: None,
190            },
191            meta: Default::default(),
192        }
193    }
194
195    pub fn http(req: impl Into<HttpRequest>) -> Self {
197        Wasm::Url {
198            req: req.into(),
199            meta: Default::default(),
200        }
201    }
202
203    pub fn meta(&self) -> &WasmMetadata {
205        match self {
206            Wasm::File { path: _, meta } => meta,
207            Wasm::Data { data: _, meta } => meta,
208            Wasm::Url { req: _, meta } => meta,
209        }
210    }
211
212    pub fn meta_mut(&mut self) -> &mut WasmMetadata {
214        match self {
215            Wasm::File { path: _, meta } => meta,
216            Wasm::Data { data: _, meta } => meta,
217            Wasm::Url { req: _, meta } => meta,
218        }
219    }
220
221    pub fn with_name(mut self, name: impl Into<String>) -> Self {
223        self.meta_mut().name = Some(name.into());
224        self
225    }
226
227    pub fn with_hash(mut self, hash: impl Into<String>) -> Self {
229        self.meta_mut().hash = Some(hash.into());
230        self
231    }
232}
233
234#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
235#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
236#[serde(deny_unknown_fields)]
237struct DataPtrLength {
238    ptr: u64,
239    len: u64,
240}
241
242#[cfg(feature = "json_schema")]
243fn wasmdata_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
244    use schemars::{schema::SchemaObject, JsonSchema};
245    let mut schema: SchemaObject = <String>::json_schema(gen).into();
246    let objschema: SchemaObject = <DataPtrLength>::json_schema(gen).into();
247    let types = schemars::schema::SingleOrVec::<schemars::schema::InstanceType>::Vec(vec![
248        schemars::schema::InstanceType::String,
249        schemars::schema::InstanceType::Object,
250    ]);
251    schema.instance_type = Some(types);
252    schema.object = objschema.object;
253    schema.into()
254}
255
256#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
258#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
259#[serde(deny_unknown_fields)]
260pub struct Manifest {
261    #[serde(default)]
263    pub wasm: Vec<Wasm>,
264
265    #[serde(default)]
267    pub memory: MemoryOptions,
268
269    #[serde(default)]
271    pub config: BTreeMap<String, String>,
272
273    #[serde(default)]
274    pub allowed_hosts: Option<Vec<String>>,
277
278    #[serde(default)]
282    pub allowed_paths: Option<BTreeMap<String, PathBuf>>,
283
284    #[serde(default)]
286    pub timeout_ms: Option<u64>,
287}
288
289impl Manifest {
290    pub fn new(wasm: impl IntoIterator<Item = impl Into<Wasm>>) -> Manifest {
292        Manifest {
293            wasm: wasm.into_iter().map(|x| x.into()).collect(),
294            ..Default::default()
295        }
296    }
297
298    pub fn with_wasm(mut self, wasm: impl Into<Wasm>) -> Self {
299        self.wasm.push(wasm.into());
300        self
301    }
302
303    pub fn disallow_all_hosts(mut self) -> Self {
305        self.allowed_hosts = Some(vec![]);
306        self
307    }
308
309    pub fn with_memory_options(mut self, memory: MemoryOptions) -> Self {
311        self.memory = memory;
312        self
313    }
314
315    pub fn with_memory_max(mut self, max: u32) -> Self {
317        self.memory.max_pages = Some(max);
318        self
319    }
320
321    pub fn with_allowed_host(mut self, host: impl Into<String>) -> Self {
323        match &mut self.allowed_hosts {
324            Some(h) => {
325                h.push(host.into());
326            }
327            None => self.allowed_hosts = Some(vec![host.into()]),
328        }
329
330        self
331    }
332
333    pub fn with_allowed_hosts(mut self, hosts: impl Iterator<Item = String>) -> Self {
335        self.allowed_hosts = Some(hosts.collect());
336        self
337    }
338
339    pub fn with_allowed_path(mut self, src: String, dest: impl AsRef<Path>) -> Self {
341        let dest = dest.as_ref().to_path_buf();
342        match &mut self.allowed_paths {
343            Some(p) => {
344                p.insert(src, dest);
345            }
346            None => {
347                let mut p = BTreeMap::new();
348                p.insert(src, dest);
349                self.allowed_paths = Some(p);
350            }
351        }
352
353        self
354    }
355
356    pub fn with_allowed_paths(mut self, paths: impl Iterator<Item = (String, PathBuf)>) -> Self {
358        self.allowed_paths = Some(paths.collect());
359        self
360    }
361
362    pub fn with_config(
364        mut self,
365        c: impl Iterator<Item = (impl Into<String>, impl Into<String>)>,
366    ) -> Self {
367        for (k, v) in c {
368            self.config.insert(k.into(), v.into());
369        }
370        self
371    }
372
373    pub fn with_config_key(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
375        self.config.insert(k.into(), v.into());
376        self
377    }
378
379    pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
383        self.timeout_ms = Some(timeout.as_millis() as u64);
384        self
385    }
386}
387
388mod wasmdata {
389    use crate::DataPtrLength;
390    use base64::{engine::general_purpose, Engine as _};
391    use serde::{Deserialize, Serialize};
392    use serde::{Deserializer, Serializer};
393    use std::slice;
394
395    pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
396        let base64 = general_purpose::STANDARD.encode(v.as_slice());
397        String::serialize(&base64, s)
398    }
399
400    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
401        #[derive(Deserialize)]
402        #[serde(untagged)]
403        enum WasmDataTypes {
404            String(String),
405            DataPtrLength(DataPtrLength),
406        }
407        Ok(match WasmDataTypes::deserialize(d)? {
408            WasmDataTypes::String(string) => general_purpose::STANDARD
409                .decode(string.as_bytes())
410                .map_err(serde::de::Error::custom)?,
411            WasmDataTypes::DataPtrLength(ptrlen) => {
412                let slice =
413                    unsafe { slice::from_raw_parts(ptrlen.ptr as *const u8, ptrlen.len as usize) };
414                slice.to_vec()
415            }
416        })
417    }
418}
419
420impl From<Manifest> for std::borrow::Cow<'_, [u8]> {
421    fn from(m: Manifest) -> Self {
422        let s = serde_json::to_vec(&m).unwrap();
423        std::borrow::Cow::Owned(s)
424    }
425}
426
427impl From<&Manifest> for std::borrow::Cow<'_, [u8]> {
428    fn from(m: &Manifest) -> Self {
429        let s = serde_json::to_vec(&m).unwrap();
430        std::borrow::Cow::Owned(s)
431    }
432}