surrealdb_core/fnc/script/fetch/classes/
form_data.rs1use std::collections::HashMap;
4use std::string::String as StdString;
5
6use js::class::{Class, Trace};
7use js::function::{Opt, Rest};
8use js::prelude::Coerced;
9use js::{Ctx, Exception, FromJs, JsLifetime, Result, String, Value};
10use reqwest::multipart::{Form, Part};
11
12use crate::fnc::script::fetch::classes::Blob;
13
14#[derive(Clone, JsLifetime)]
15pub enum FormDataValue<'js> {
16 String(String<'js>),
17 Blob {
18 data: Class<'js, Blob>,
19 filename: Option<String<'js>>,
20 },
21}
22
23impl<'js> FormDataValue<'js> {
24 fn from_arguments(
25 ctx: &Ctx<'js>,
26 value: Value<'js>,
27 filename: Opt<Coerced<String<'js>>>,
28 error: &'static str,
29 ) -> Result<Self> {
30 if let Some(blob) = value.as_object().and_then(Class::<Blob>::from_object) {
31 let filename = filename.into_inner().map(|x| x.0);
32
33 Ok(FormDataValue::Blob {
34 data: blob,
35 filename,
36 })
37 } else if filename.into_inner().is_some() {
38 return Err(Exception::throw_type(ctx, error));
39 } else {
40 let value = Coerced::<String>::from_js(ctx, value)?;
41 Ok(FormDataValue::String(value.0))
42 }
43 }
44}
45
46#[js::class]
47#[derive(Clone, Trace, JsLifetime)]
48pub struct FormData<'js> {
49 #[qjs(skip_trace)]
50 pub(crate) values: HashMap<StdString, Vec<FormDataValue<'js>>>,
51}
52
53#[js::methods]
54impl<'js> FormData<'js> {
55 #[qjs(constructor)]
63 pub fn new(ctx: Ctx<'js>, args: Rest<()>) -> Result<Self> {
64 if !args.is_empty() {
65 return Err(Exception::throw_internal(
66 &ctx,
67 "Cant call FormData with arguments as the dom elements required are not available",
68 ));
69 }
70 Ok(FormData {
71 values: HashMap::new(),
72 })
73 }
74
75 pub fn append(
76 &mut self,
77 ctx: Ctx<'js>,
78 name: Coerced<StdString>,
79 value: Value<'js>,
80 filename: Opt<Coerced<String<'js>>>,
81 ) -> Result<()> {
82 let value = FormDataValue::from_arguments(
83 &ctx,
84 value,
85 filename,
86 "Can't call `append` on `FormData` with a filename when value isn't of type `Blob`",
87 )?;
88
89 self.values.entry(name.0).or_default().push(value);
90
91 Ok(())
92 }
93
94 pub fn set(
95 &mut self,
96 ctx: Ctx<'js>,
97 name: Coerced<StdString>,
98 value: Value<'js>,
99 filename: Opt<Coerced<String<'js>>>,
100 ) -> Result<()> {
101 let value = FormDataValue::from_arguments(
102 &ctx,
103 value,
104 filename,
105 "Can't call `set` on `FormData` with a filename when value isn't of type `Blob`",
106 )?;
107
108 self.values.insert(name.0, vec![value]);
109
110 Ok(())
111 }
112
113 pub fn has(&self, name: Coerced<StdString>) -> bool {
114 self.values.contains_key(&name.0)
115 }
116
117 pub fn delete(&mut self, name: Coerced<StdString>) {
118 self.values.remove(&name.0);
119 }
120
121 #[qjs(skip)]
122 pub fn to_form(&self) -> Result<Form> {
123 let mut res = Form::new();
124 for (k, v) in self.values.iter() {
125 for v in v {
126 match v {
127 FormDataValue::String(x) => {
128 res = res.text(k.clone(), x.to_string()?);
129 }
130 FormDataValue::Blob {
131 data,
132 filename,
133 } => {
134 let mut part = Part::bytes(data.borrow().data.to_vec());
135 if let Some(filename) = filename {
136 let filename = filename.to_string()?;
137 part = part.file_name(filename);
138 }
139 res = res.part(k.clone(), part);
140 }
141 }
142 }
143 }
144 Ok(res)
145 }
146}