1use std::collections::HashMap;
2
3use crate::headers::Headers;
4use crate::http::Method;
5use crate::MtlsCertificate;
6
7use js_sys::{self, Object};
8use serde::Serialize;
9use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
10
11#[derive(Debug)]
13pub struct RequestInit {
14 pub body: Option<JsValue>,
16 pub headers: Headers,
18 pub cf: CfProperties,
21 pub method: Method,
23 pub redirect: RequestRedirect,
27 pub cache: Option<CacheMode>,
29}
30
31impl RequestInit {
32 pub fn new() -> Self {
33 Default::default()
34 }
35
36 pub fn with_headers(&mut self, headers: Headers) -> &mut Self {
37 self.headers = headers;
38 self
39 }
40
41 pub fn with_method(&mut self, method: Method) -> &mut Self {
42 self.method = method;
43 self
44 }
45
46 pub fn with_redirect(&mut self, redirect: RequestRedirect) -> &mut Self {
47 self.redirect = redirect;
48 self
49 }
50
51 pub fn with_cache(&mut self, cache: CacheMode) -> &mut Self {
52 self.cache = Some(cache);
53 self
54 }
55
56 pub fn with_body(&mut self, body: Option<JsValue>) -> &mut Self {
57 self.body = body;
58 self
59 }
60
61 pub fn with_cf_properties(&mut self, props: CfProperties) -> &mut Self {
62 self.cf = props;
63 self
64 }
65
66 pub fn with_mtls_certificate(&mut self, certificate: &MtlsCertificate) -> &mut Self {
67 self.cf.mtls_certificate = Some(certificate.as_request_cf_value());
68 self
69 }
70}
71
72impl From<&RequestInit> for web_sys::RequestInit {
73 fn from(req: &RequestInit) -> Self {
74 let inner = web_sys::RequestInit::new();
75
76 if !req.headers.is_empty() {
77 inner.set_headers(req.headers.as_ref());
78 }
79
80 inner.set_method(req.method.as_ref());
81 inner.set_redirect(req.redirect.into());
82 if let Some(cache) = req.cache {
83 inner.set_cache(cache.into());
84 }
85 if let Some(body) = req.body.as_ref() {
86 inner.set_body(body);
87 }
88
89 if !req.cf.is_default() {
91 let r = ::js_sys::Reflect::set(
92 inner.as_ref(),
93 &JsValue::from("cf"),
94 &JsValue::from(&req.cf),
95 );
96 debug_assert!(
97 r.is_ok(),
98 "setting properties should never fail on our dictionary objects"
99 );
100 let _ = r;
101 }
102
103 inner
104 }
105}
106
107impl Default for RequestInit {
108 fn default() -> Self {
109 Self {
110 body: None,
111 headers: Headers::new(),
112 cf: CfProperties::default(),
113 method: Method::Get,
114 redirect: RequestRedirect::default(),
115 cache: None,
116 }
117 }
118}
119
120#[derive(Debug)]
122pub struct CfProperties {
123 pub apps: Option<bool>,
125 pub cache_everything: Option<bool>,
129 pub cache_key: Option<String>,
133 pub cache_ttl: Option<i32>,
138 pub cache_ttl_by_status: Option<HashMap<String, i32>>,
145 pub image: Option<ResizeConfig>,
147 pub minify: Option<MinifyConfig>,
150 pub mirage: Option<bool>,
153 pub polish: Option<PolishConfig>,
155 pub resolve_override: Option<String>,
170 pub scrape_shield: Option<bool>,
173 pub mtls_certificate: Option<JsValue>,
175}
176
177impl From<&CfProperties> for JsValue {
178 fn from(props: &CfProperties) -> Self {
179 let obj = js_sys::Object::new();
180 let defaults = CfProperties::default();
181 let serializer = serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true);
182
183 set_prop(
184 &obj,
185 &JsValue::from("apps"),
186 &JsValue::from(props.apps.unwrap_or(defaults.apps.unwrap_or_default())),
187 );
188
189 set_prop(
190 &obj,
191 &JsValue::from("cacheEverything"),
192 &JsValue::from(
193 props
194 .cache_everything
195 .unwrap_or(defaults.cache_everything.unwrap_or_default()),
196 ),
197 );
198
199 set_prop(
200 &obj,
201 &JsValue::from("cacheKey"),
202 &JsValue::from(
203 props
204 .cache_key
205 .clone()
206 .unwrap_or(defaults.cache_key.unwrap_or_default()),
207 ),
208 );
209
210 set_prop(
211 &obj,
212 &JsValue::from("cacheTtl"),
213 &JsValue::from(
214 props
215 .cache_ttl
216 .unwrap_or(defaults.cache_ttl.unwrap_or_default()),
217 ),
218 );
219
220 let ttl_status_map = props
221 .cache_ttl_by_status
222 .clone()
223 .unwrap_or(defaults.cache_ttl_by_status.unwrap_or_default());
224 set_prop(
225 &obj,
226 &JsValue::from("cacheTtlByStatus"),
227 &ttl_status_map.serialize(&serializer).unwrap_or_default(),
228 );
229
230 set_prop(
231 &obj,
232 &JsValue::from("minify"),
233 &JsValue::from(props.minify.unwrap_or(defaults.minify.unwrap_or_default())),
234 );
235
236 set_prop(
237 &obj,
238 &JsValue::from("mirage"),
239 &JsValue::from(props.mirage.unwrap_or(defaults.mirage.unwrap_or_default())),
240 );
241
242 let polish_val = props.polish.unwrap_or(defaults.polish.unwrap_or_default());
243 set_prop(
244 &obj,
245 &JsValue::from("polish"),
246 &serde_wasm_bindgen::to_value(&polish_val).unwrap(),
247 );
248
249 set_prop(
250 &obj,
251 &JsValue::from("resolveOverride"),
252 &JsValue::from(
253 props
254 .resolve_override
255 .clone()
256 .unwrap_or(defaults.resolve_override.unwrap_or_default()),
257 ),
258 );
259
260 set_prop(
261 &obj,
262 &JsValue::from("scrapeShield"),
263 &JsValue::from(
264 props
265 .scrape_shield
266 .unwrap_or(defaults.scrape_shield.unwrap_or_default()),
267 ),
268 );
269
270 if let Some(image) = &props.image {
271 set_prop(
272 &obj,
273 &JsValue::from("image"),
274 &serde_wasm_bindgen::to_value(&image).unwrap(),
275 );
276 }
277
278 if let Some(mtls_certificate) = &props.mtls_certificate {
279 set_prop(&obj, &JsValue::from("mtlsCertificate"), mtls_certificate);
280 }
281
282 obj.into()
283 }
284}
285
286fn set_prop(target: &Object, key: &JsValue, val: &JsValue) {
287 let r = ::js_sys::Reflect::set(target, key, val);
288 debug_assert!(
289 r.is_ok(),
290 "setting properties should never fail on our dictionary objects"
291 );
292 let _ = r;
293}
294
295impl CfProperties {
296 pub fn new() -> Self {
297 Default::default()
298 }
299
300 pub fn is_default(&self) -> bool {
301 let de = CfProperties::default();
302 self.apps == de.apps
303 && self.cache_everything == de.cache_everything
304 && self.cache_key == de.cache_key
305 && self.cache_ttl == de.cache_ttl
306 && self.cache_ttl_by_status == de.cache_ttl_by_status
307 && self.minify == de.minify
308 && self.mirage == de.mirage
309 && self.image.is_none()
310 && self.polish == de.polish
311 && self.resolve_override == de.resolve_override
312 && self.scrape_shield == de.scrape_shield
313 && self.mtls_certificate.is_none()
314 }
315}
316
317impl Default for CfProperties {
318 fn default() -> Self {
319 Self {
320 apps: Some(true),
321 cache_everything: Some(false),
322 cache_key: None,
323 cache_ttl: None,
324 cache_ttl_by_status: None,
325 minify: None,
326 mirage: Some(true),
327 image: None,
328 polish: None,
329 resolve_override: None,
330 scrape_shield: Some(true),
331 mtls_certificate: None,
332 }
333 }
334}
335
336#[wasm_bindgen]
339#[derive(Clone, Copy, Debug, Default, Serialize, PartialEq)]
340#[serde(rename_all = "kebab-case")]
341pub struct MinifyConfig {
342 pub js: bool,
343 pub html: bool,
344 pub css: bool,
345}
346
347#[derive(Clone, Copy, Debug, Default, Serialize, PartialEq)]
350#[serde(rename_all = "kebab-case")]
351pub enum PolishConfig {
352 #[default]
353 Off,
354 Lossy,
355 Lossless,
356}
357
358#[derive(Clone, Debug, serde::Serialize)]
359#[serde(untagged)]
360pub enum ResizeBorder {
361 Uniform {
362 color: String,
363 width: usize,
364 },
365 Varying {
366 color: String,
367 top: usize,
368 right: usize,
369 bottom: usize,
370 left: usize,
371 },
372}
373
374#[derive(Clone, Debug, Serialize)]
375#[serde(rename_all = "kebab-case")]
376pub enum ResizeOriginAuth {
377 SharePublicly,
378}
379
380#[derive(Clone, Debug, Default, Serialize)]
383#[serde(rename_all = "kebab-case")]
384pub struct ResizeConfig {
385 pub anim: Option<bool>,
387 pub background: Option<String>,
389 pub blur: Option<u8>,
391 pub border: Option<ResizeBorder>,
393 pub brightness: Option<f64>,
395 pub compression: Option<ResizeCompression>,
397 pub contrast: Option<f64>,
399 pub dpr: Option<f64>,
401 pub draw: Option<ResizeDraw>,
403 pub fit: Option<ResizeFit>,
405 pub flip: Option<ResizeFlip>,
407 pub format: Option<ResizeFormat>,
409 pub gamma: Option<f64>,
411 pub gravity: Option<ResizeGravity>,
413 pub height: Option<usize>,
415 pub metadata: Option<ResizeMetadata>,
417 pub origin_auth: Option<ResizeOriginAuth>,
419 pub onerror: Option<ResizeOnerror>,
421 pub quality: Option<ResizeQuality>,
423 pub rotate: Option<usize>,
425 pub saturation: Option<f64>,
427 pub sharpen: Option<f64>,
429 pub trim: Option<ResizeTrim>,
431 pub width: Option<usize>,
433}
434
435#[derive(Clone, Copy, Debug, Serialize)]
436#[serde(rename_all = "kebab-case")]
437pub enum ResizeCompression {
438 Fast,
439}
440
441#[derive(Clone, Debug, Serialize)]
442#[serde(untagged)]
443pub enum ResizeDrawRepeat {
444 Uniform(bool),
445 Axis(String),
446}
447
448#[derive(Clone, Debug, Serialize)]
449#[serde(rename_all = "kebab-case")]
450pub struct ResizeDraw {
451 url: String,
452 opacity: Option<f64>,
453 repeat: Option<ResizeDrawRepeat>,
454 top: Option<usize>,
455 bottom: Option<usize>,
456 left: Option<usize>,
457 right: Option<usize>,
458}
459
460#[derive(Clone, Copy, Debug, Serialize)]
461#[serde(rename_all = "kebab-case")]
462pub enum ResizeFit {
463 ScaleDown,
464 Contain,
465 Cover,
466 Crop,
467 Pad,
468}
469
470#[derive(Clone, Copy, Debug, Serialize)]
471pub enum ResizeFlip {
472 #[serde(rename = "h")]
473 Horizontally,
474 #[serde(rename = "v")]
475 Vertically,
476 #[serde(rename = "hv")]
477 Both,
478}
479
480#[derive(Clone, Copy, Debug, Serialize)]
481#[serde(rename_all = "kebab-case")]
482pub enum ResizeFormat {
483 Avif,
484 Webp,
485 Json,
486 Jpeg,
487 Png,
488 BaselineJpeg,
489 PngForce,
490 Svg,
491}
492
493#[derive(Clone, Copy, Debug, Serialize)]
494#[serde(rename_all = "kebab-case")]
495pub enum ResizeGravitySide {
496 Auto,
497 Left,
498 Right,
499 Top,
500 Bottom,
501}
502
503#[derive(Clone, Copy, Debug, Serialize)]
504#[serde(rename_all = "kebab-case")]
505#[serde(untagged)]
506pub enum ResizeGravity {
507 Side(ResizeGravitySide),
508 Coords { x: f64, y: f64 },
509}
510
511#[derive(Clone, Copy, Debug, Serialize)]
512#[serde(rename_all = "kebab-case")]
513pub enum ResizeQualityLiteral {
514 Low,
515 MediumLow,
516 MediumHigh,
517 High,
518}
519
520#[derive(Clone, Copy, Debug, Serialize)]
521#[serde(rename_all = "kebab-case")]
522#[serde(untagged)]
523pub enum ResizeQuality {
524 Literal(ResizeQualityLiteral),
525 Specific { value: usize },
526}
527
528#[derive(Clone, Copy, Debug, Serialize)]
529#[serde(rename_all = "kebab-case")]
530pub enum ResizeMetadata {
531 Keep,
532 Copyright,
533 None,
534}
535
536#[derive(Clone, Copy, Debug, Serialize)]
537#[serde(rename_all = "kebab-case")]
538pub enum ResizeOnerror {
539 Redirect,
540}
541
542#[derive(Clone, Copy, Debug, Serialize)]
543#[serde(rename_all = "kebab-case")]
544pub struct ResizeTrim {
545 #[serde(skip_serializing_if = "Option::is_none")]
546 pub top: Option<usize>,
547 #[serde(skip_serializing_if = "Option::is_none")]
548 pub bottom: Option<usize>,
549 #[serde(skip_serializing_if = "Option::is_none")]
550 pub left: Option<usize>,
551 #[serde(skip_serializing_if = "Option::is_none")]
552 pub right: Option<usize>,
553 #[serde(skip_serializing_if = "Option::is_none")]
554 pub width: Option<usize>,
555 #[serde(skip_serializing_if = "Option::is_none")]
556 pub height: Option<usize>,
557}
558
559#[derive(Clone, Copy, Debug, Default, Serialize)]
560#[serde(rename_all = "kebab-case")]
561pub enum RequestRedirect {
562 Error,
563 #[default]
564 Follow,
565 Manual,
566}
567
568impl From<RequestRedirect> for &str {
569 fn from(redirect: RequestRedirect) -> Self {
570 match redirect {
571 RequestRedirect::Error => "error",
572 RequestRedirect::Follow => "follow",
573 RequestRedirect::Manual => "manual",
574 }
575 }
576}
577
578impl From<RequestRedirect> for web_sys::RequestRedirect {
579 fn from(redir: RequestRedirect) -> Self {
580 match redir {
581 RequestRedirect::Error => web_sys::RequestRedirect::Error,
582 RequestRedirect::Follow => web_sys::RequestRedirect::Follow,
583 RequestRedirect::Manual => web_sys::RequestRedirect::Manual,
584 }
585 }
586}
587
588#[derive(Clone, Copy, Debug, Eq, PartialEq)]
591pub enum CacheMode {
592 NoStore,
593 NoCache,
594 Reload,
595}
596
597impl From<CacheMode> for web_sys::RequestCache {
598 fn from(mode: CacheMode) -> Self {
599 match mode {
600 CacheMode::NoStore => web_sys::RequestCache::NoStore,
601 CacheMode::NoCache => web_sys::RequestCache::NoCache,
602 CacheMode::Reload => web_sys::RequestCache::Reload,
603 }
604 }
605}
606
607#[test]
608fn request_init_no_invalid_options() {
609 let mut init = RequestInit::new();
610 init.method = Method::Post;
611
612 let js_init: web_sys::RequestInit = (&init).into();
613
614 let _ = web_sys::Request::new_with_str_and_init("https://httpbin.org/post", &js_init).unwrap();
615}