worker/
request_init.rs

1use std::collections::HashMap;
2
3use crate::headers::Headers;
4use crate::http::Method;
5
6use js_sys::{self, Object};
7use serde::Serialize;
8use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
9
10/// Optional options struct that contains settings to apply to the `Request`.
11#[derive(Debug)]
12pub struct RequestInit {
13    /// Currently requires a manual conversion from your data into a [`wasm_bindgen::JsValue`].
14    pub body: Option<JsValue>,
15    /// Headers associated with the outbound `Request`.
16    pub headers: Headers,
17    /// Cloudflare-specific properties that can be set on the `Request` that control how Cloudflare’s
18    /// edge handles the request.
19    pub cf: CfProperties,
20    /// The HTTP Method used for this `Request`.
21    pub method: Method,
22    /// The redirect mode to use: follow, error, or manual. The default for a new Request object is
23    /// follow. Note, however, that the incoming Request property of a FetchEvent will have redirect
24    /// mode manual.
25    pub redirect: RequestRedirect,
26}
27
28impl RequestInit {
29    pub fn new() -> Self {
30        Default::default()
31    }
32
33    pub fn with_headers(&mut self, headers: Headers) -> &mut Self {
34        self.headers = headers;
35        self
36    }
37
38    pub fn with_method(&mut self, method: Method) -> &mut Self {
39        self.method = method;
40        self
41    }
42
43    pub fn with_redirect(&mut self, redirect: RequestRedirect) -> &mut Self {
44        self.redirect = redirect;
45        self
46    }
47
48    pub fn with_body(&mut self, body: Option<JsValue>) -> &mut Self {
49        self.body = body;
50        self
51    }
52
53    pub fn with_cf_properties(&mut self, props: CfProperties) -> &mut Self {
54        self.cf = props;
55        self
56    }
57}
58
59impl From<&RequestInit> for web_sys::RequestInit {
60    fn from(req: &RequestInit) -> Self {
61        let inner = web_sys::RequestInit::new();
62        inner.set_headers(req.headers.as_ref());
63        inner.set_method(req.method.as_ref());
64        inner.set_redirect(req.redirect.into());
65        if let Some(body) = req.body.as_ref() {
66            inner.set_body(body);
67        }
68
69        // set the Cloudflare-specific `cf` property on FFI RequestInit
70        let r = ::js_sys::Reflect::set(
71            inner.as_ref(),
72            &JsValue::from("cf"),
73            &JsValue::from(&req.cf),
74        );
75        debug_assert!(
76            r.is_ok(),
77            "setting properties should never fail on our dictionary objects"
78        );
79        let _ = r;
80
81        inner
82    }
83}
84
85impl Default for RequestInit {
86    fn default() -> Self {
87        Self {
88            body: None,
89            headers: Headers::new(),
90            cf: CfProperties::default(),
91            method: Method::Get,
92            redirect: RequestRedirect::default(),
93        }
94    }
95}
96
97/// <https://developers.cloudflare.com/workers/runtime-apis/request#requestinitcfproperties>
98#[derive(Debug)]
99pub struct CfProperties {
100    /// Whether Cloudflare Apps should be enabled for this request. Defaults to `true`.
101    pub apps: Option<bool>,
102    /// This option forces Cloudflare to cache the response for this request, regardless of what
103    /// headers are seen on the response. This is equivalent to setting the page rule “Cache Level”
104    /// (to “Cache Everything”). Defaults to `false`.
105    pub cache_everything: Option<bool>,
106    /// A request’s cache key is what determines if two requests are “the same” for caching
107    /// purposes. If a request has the same cache key as some previous request, then we can serve
108    /// the same cached response for both.
109    pub cache_key: Option<String>,
110    /// This option forces Cloudflare to cache the response for this request, regardless of what
111    /// headers are seen on the response. This is equivalent to setting two page rules: “Edge Cache
112    /// TTL” and “Cache Level” (to “Cache Everything”). The value must be zero or a positive number.
113    /// A value of 0 indicates that the cache asset expires immediately.
114    pub cache_ttl: Option<u32>,
115    /// This option is a version of the cacheTtl feature which chooses a TTL based on the response’s
116    /// status code. If the response to this request has a status code that matches, Cloudflare will
117    /// cache for the instructed time, and override cache directives sent by the origin. For
118    /// example: { "200-299": 86400, 404: 1, "500-599": 0 }. The value can be any integer, including
119    /// zero and negative integers. A value of 0 indicates that the cache asset expires immediately.
120    /// Any negative value instructs Cloudflare not to cache at all.
121    pub cache_ttl_by_status: Option<HashMap<String, i32>>,
122    /// Enables Image Resizing for this request.
123    pub image: Option<ResizeConfig>,
124    /// Enables or disables AutoMinify for various file types.
125    /// For example: `{ javascript: true, css: true, html: false }`.
126    pub minify: Option<MinifyConfig>,
127    /// Whether Mirage should be enabled for this request, if otherwise configured for this zone.
128    /// Defaults to true.
129    pub mirage: Option<bool>,
130    /// Sets Polish mode. The possible values are lossy, lossless or off.
131    pub polish: Option<PolishConfig>,
132    /// Directs the request to an alternate origin server by overriding the DNS lookup. The value of
133    /// `resolve_override` specifies an alternate hostname which will be used when determining the
134    /// origin IP address, instead of using the hostname specified in the URL. The Host header of
135    /// the request will still match what is in the URL. Thus, `resolve_override` allows a request  
136    /// to be sent to a different server than the URL / Host header specifies. However,
137    /// `resolve_override` will only take effect if both the URL host and the host specified by
138    /// `resolve_override` are within your zone. If either specifies a host from a different zone /
139    /// domain, then the option will be ignored for security reasons. If you need to direct a
140    /// request to a host outside your zone (while keeping the Host header pointing within your
141    /// zone), first create a CNAME record within your zone pointing to the outside host, and then
142    /// set `resolve_override` to point at the CNAME record.
143    ///
144    /// Note that, for security reasons, it is not possible to set the Host header to specify a host
145    /// outside of your zone unless the request is actually being sent to that host.
146    pub resolve_override: Option<String>,
147    /// Whether ScrapeShield should be enabled for this request, if otherwise configured for this
148    /// zone. Defaults to `true`.
149    pub scrape_shield: Option<bool>,
150}
151
152impl From<&CfProperties> for JsValue {
153    fn from(props: &CfProperties) -> Self {
154        let obj = js_sys::Object::new();
155        let defaults = CfProperties::default();
156        let serializer = serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true);
157
158        set_prop(
159            &obj,
160            &JsValue::from("apps"),
161            &JsValue::from(props.apps.unwrap_or(defaults.apps.unwrap_or_default())),
162        );
163
164        set_prop(
165            &obj,
166            &JsValue::from("cacheEverything"),
167            &JsValue::from(
168                props
169                    .cache_everything
170                    .unwrap_or(defaults.cache_everything.unwrap_or_default()),
171            ),
172        );
173
174        set_prop(
175            &obj,
176            &JsValue::from("cacheKey"),
177            &JsValue::from(
178                props
179                    .cache_key
180                    .clone()
181                    .unwrap_or(defaults.cache_key.unwrap_or_default()),
182            ),
183        );
184
185        set_prop(
186            &obj,
187            &JsValue::from("cacheTtl"),
188            &JsValue::from(
189                props
190                    .cache_ttl
191                    .unwrap_or(defaults.cache_ttl.unwrap_or_default()),
192            ),
193        );
194
195        let ttl_status_map = props
196            .cache_ttl_by_status
197            .clone()
198            .unwrap_or(defaults.cache_ttl_by_status.unwrap_or_default());
199        set_prop(
200            &obj,
201            &JsValue::from("cacheTtlByStatus"),
202            &ttl_status_map.serialize(&serializer).unwrap_or_default(),
203        );
204
205        set_prop(
206            &obj,
207            &JsValue::from("minify"),
208            &JsValue::from(props.minify.unwrap_or(defaults.minify.unwrap_or_default())),
209        );
210
211        set_prop(
212            &obj,
213            &JsValue::from("mirage"),
214            &JsValue::from(props.mirage.unwrap_or(defaults.mirage.unwrap_or_default())),
215        );
216
217        let polish_val = props.polish.unwrap_or(defaults.polish.unwrap_or_default());
218        set_prop(
219            &obj,
220            &JsValue::from("polish"),
221            &serde_wasm_bindgen::to_value(&polish_val).unwrap(),
222        );
223
224        set_prop(
225            &obj,
226            &JsValue::from("resolveOverride"),
227            &JsValue::from(
228                props
229                    .resolve_override
230                    .clone()
231                    .unwrap_or(defaults.resolve_override.unwrap_or_default()),
232            ),
233        );
234
235        set_prop(
236            &obj,
237            &JsValue::from("scrapeShield"),
238            &JsValue::from(
239                props
240                    .scrape_shield
241                    .unwrap_or(defaults.scrape_shield.unwrap_or_default()),
242            ),
243        );
244
245        if let Some(image) = &props.image {
246            set_prop(
247                &obj,
248                &JsValue::from("image"),
249                &serde_wasm_bindgen::to_value(&image).unwrap(),
250            );
251        }
252
253        obj.into()
254    }
255}
256
257fn set_prop(target: &Object, key: &JsValue, val: &JsValue) {
258    let r = ::js_sys::Reflect::set(target, key, val);
259    debug_assert!(
260        r.is_ok(),
261        "setting properties should never fail on our dictionary objects"
262    );
263    let _ = r;
264}
265
266impl CfProperties {
267    pub fn new() -> Self {
268        Default::default()
269    }
270}
271
272impl Default for CfProperties {
273    fn default() -> Self {
274        Self {
275            apps: Some(true),
276            cache_everything: Some(false),
277            cache_key: None,
278            cache_ttl: None,
279            cache_ttl_by_status: None,
280            minify: None,
281            mirage: Some(true),
282            image: None,
283            polish: None,
284            resolve_override: None,
285            scrape_shield: Some(true),
286        }
287    }
288}
289
290/// Configuration options for Cloudflare's minification features:
291/// <https://www.cloudflare.com/website-optimization/>
292#[wasm_bindgen]
293#[derive(Clone, Copy, Debug, Default, Serialize)]
294#[serde(rename_all = "kebab-case")]
295pub struct MinifyConfig {
296    pub js: bool,
297    pub html: bool,
298    pub css: bool,
299}
300
301/// Configuration options for Cloudflare's image optimization feature:
302/// <https://blog.cloudflare.com/introducing-polish-automatic-image-optimizati/>
303#[derive(Clone, Copy, Debug, Default, Serialize)]
304#[serde(rename_all = "kebab-case")]
305pub enum PolishConfig {
306    #[default]
307    Off,
308    Lossy,
309    Lossless,
310}
311
312#[derive(Clone, Debug, serde::Serialize)]
313#[serde(untagged)]
314pub enum ResizeBorder {
315    Uniform {
316        color: String,
317        width: usize,
318    },
319    Varying {
320        color: String,
321        top: usize,
322        right: usize,
323        bottom: usize,
324        left: usize,
325    },
326}
327
328#[derive(Clone, Debug, Serialize)]
329#[serde(rename_all = "kebab-case")]
330pub enum ResizeOriginAuth {
331    SharePublicly,
332}
333
334/// Configuration options for Cloudflare's image resizing feature:
335/// <https://developers.cloudflare.com/images/image-resizing/>
336#[derive(Clone, Debug, Default, Serialize)]
337#[serde(rename_all = "kebab-case")]
338pub struct ResizeConfig {
339    /// Whether to preserve animation frames from input files. Default is `true`. Setting it to `false` reduces animations to still images.
340    pub anim: Option<bool>,
341    /// Background color to add underneath the image. Applies to images with transparency (for example, PNG) and images resized with `fit=pad`.
342    pub background: Option<String>,
343    /// Blur radius between `1` (slight blur) and `250` (maximum). Be aware that you cannot use this option to reliably obscure image content.
344    pub blur: Option<u8>,
345    /// Adds a border around the image. The border is added after resizing.
346    pub border: Option<ResizeBorder>,
347    /// Increase brightness by a factor. A value of `1.0` equals no change, a value of `0.5` equals half brightness, and a value of `2.0` equals twice as bright.
348    pub brightness: Option<f64>,
349    /// Slightly reduces latency on a cache miss by selecting a quickest-to-compress file format, at a cost of increased file size and lower image quality.
350    pub compression: Option<ResizeCompression>,
351    /// Increase contrast by a factor. A value of `1.0` equals no change, a value of `0.5` equals low contrast, and a value of `2.0` equals high contrast.
352    pub contrast: Option<f64>,
353    /// Device Pixel Ratio. Default is `1`. Multiplier for `width`/`height` that makes it easier to specify higher-DPI sizes in `<img srcset>`.
354    pub dpr: Option<f64>,
355    /// Drawing operations to overlay on the image.
356    pub draw: Option<ResizeDraw>,
357    /// Affects interpretation of `width` and `height`. All resizing modes preserve aspect ratio.
358    pub fit: Option<ResizeFit>,
359    /// Flips the image horizontally, vertically, or both. Can be used with the `rotate` parameter to set the orientation of an image.
360    pub flip: Option<ResizeFlip>,
361    /// The `auto` option will serve the WebP or AVIF format to browsers that support it. If this option is not specified, a standard format like JPEG or PNG will be used.
362    pub format: Option<ResizeFormat>,
363    /// Increase exposure by a factor. A value of `1.0` equals no change, a value of `0.5` darkens the image, and a value of `2.0` lightens the image.
364    pub gamma: Option<f64>,
365    /// Specifies how an image should be cropped when used with `fit=cover` and `fit=crop`. Available options are `auto`, `face`, a side (`left`, `right`, `top`, `bottom`), and relative coordinates (`XxY` with a valid range of `0.0` to `1.0`).
366    pub gravity: Option<ResizeGravity>,
367    /// Specifies maximum height of the image in pixels. Exact behavior depends on the `fit` mode (described below).
368    pub height: Option<usize>,
369    /// Controls amount of invisible metadata (EXIF data) that should be preserved. Color profiles and EXIF rotation are applied to the image even if the metadata is discarded.
370    pub metadata: Option<ResizeMetadata>,
371    /// Authentication method for accessing the origin image.
372    pub origin_auth: Option<ResizeOriginAuth>,
373    /// In case of a fatal error that prevents the image from being resized, redirects to the unresized source image URL. This may be useful in case some images require user authentication and cannot be fetched anonymously via Worker.
374    pub onerror: Option<ResizeOnerror>,
375    /// Specifies quality for images in JPEG, WebP, and AVIF formats. The quality is in a 1-100 scale, but useful values are between `50` (low quality, small file size) and `90` (high quality, large file size).
376    pub quality: Option<ResizeQuality>,
377    /// Number of degrees (`90`, `180`, or `270`) to rotate the image by. `width` and `height` options refer to axes after rotation.
378    pub rotate: Option<usize>,
379    /// Increases saturation by a factor. A value of `1.0` equals no change, a value of `0.5` equals half saturation, and a value of `2.0` equals twice as saturated.
380    pub saturation: Option<f64>,
381    /// Specifies strength of sharpening filter to apply to the image. The value is a floating-point number between `0` (no sharpening, default) and `10` (maximum).
382    pub sharpen: Option<f64>,
383    /// Specifies a number of pixels to cut off on each side. Allows removal of borders or cutting out a specific fragment of an image.
384    pub trim: Option<ResizeTrim>,
385    /// Specifies maximum width of the image. Exact behavior depends on the `fit` mode; use the `fit=scale-down` option to ensure that the image will not be enlarged unnecessarily.
386    pub width: Option<usize>,
387}
388
389#[derive(Clone, Copy, Debug, Serialize)]
390#[serde(rename_all = "kebab-case")]
391pub enum ResizeCompression {
392    Fast,
393}
394
395#[derive(Clone, Debug, Serialize)]
396#[serde(untagged)]
397pub enum ResizeDrawRepeat {
398    Uniform(bool),
399    Axis(String),
400}
401
402#[derive(Clone, Debug, Serialize)]
403#[serde(rename_all = "kebab-case")]
404pub struct ResizeDraw {
405    url: String,
406    opacity: Option<f64>,
407    repeat: Option<ResizeDrawRepeat>,
408    top: Option<usize>,
409    bottom: Option<usize>,
410    left: Option<usize>,
411    right: Option<usize>,
412}
413
414#[derive(Clone, Copy, Debug, Serialize)]
415#[serde(rename_all = "kebab-case")]
416pub enum ResizeFit {
417    ScaleDown,
418    Contain,
419    Cover,
420    Crop,
421    Pad,
422}
423
424#[derive(Clone, Copy, Debug, Serialize)]
425pub enum ResizeFlip {
426    #[serde(rename = "h")]
427    Horizontally,
428    #[serde(rename = "v")]
429    Vertically,
430    #[serde(rename = "hv")]
431    Both,
432}
433
434#[derive(Clone, Copy, Debug, Serialize)]
435#[serde(rename_all = "kebab-case")]
436pub enum ResizeFormat {
437    Avif,
438    Webp,
439    Json,
440    Jpeg,
441    Png,
442    BaselineJpeg,
443    PngForce,
444    Svg,
445}
446
447#[derive(Clone, Copy, Debug, Serialize)]
448#[serde(rename_all = "kebab-case")]
449pub enum ResizeGravitySide {
450    Auto,
451    Left,
452    Right,
453    Top,
454    Bottom,
455}
456
457#[derive(Clone, Copy, Debug, Serialize)]
458#[serde(rename_all = "kebab-case")]
459#[serde(untagged)]
460pub enum ResizeGravity {
461    Side(ResizeGravitySide),
462    Coords { x: f64, y: f64 },
463}
464
465#[derive(Clone, Copy, Debug, Serialize)]
466#[serde(rename_all = "kebab-case")]
467pub enum ResizeQualityLiteral {
468    Low,
469    MediumLow,
470    MediumHigh,
471    High,
472}
473
474#[derive(Clone, Copy, Debug, Serialize)]
475#[serde(rename_all = "kebab-case")]
476#[serde(untagged)]
477pub enum ResizeQuality {
478    Literal(ResizeQualityLiteral),
479    Specific { value: usize },
480}
481
482#[derive(Clone, Copy, Debug, Serialize)]
483#[serde(rename_all = "kebab-case")]
484pub enum ResizeMetadata {
485    Keep,
486    Copyright,
487    None,
488}
489
490#[derive(Clone, Copy, Debug, Serialize)]
491#[serde(rename_all = "kebab-case")]
492pub enum ResizeOnerror {
493    Redirect,
494}
495
496#[derive(Clone, Copy, Debug, Serialize)]
497#[serde(rename_all = "kebab-case")]
498pub struct ResizeTrim {
499    #[serde(skip_serializing_if = "Option::is_none")]
500    pub top: Option<usize>,
501    #[serde(skip_serializing_if = "Option::is_none")]
502    pub bottom: Option<usize>,
503    #[serde(skip_serializing_if = "Option::is_none")]
504    pub left: Option<usize>,
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub right: Option<usize>,
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub width: Option<usize>,
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub height: Option<usize>,
511}
512
513#[derive(Clone, Copy, Debug, Default, Serialize)]
514#[serde(rename_all = "kebab-case")]
515pub enum RequestRedirect {
516    Error,
517    #[default]
518    Follow,
519    Manual,
520}
521
522impl From<RequestRedirect> for &str {
523    fn from(redirect: RequestRedirect) -> Self {
524        match redirect {
525            RequestRedirect::Error => "error",
526            RequestRedirect::Follow => "follow",
527            RequestRedirect::Manual => "manual",
528        }
529    }
530}
531
532impl From<RequestRedirect> for web_sys::RequestRedirect {
533    fn from(redir: RequestRedirect) -> Self {
534        match redir {
535            RequestRedirect::Error => web_sys::RequestRedirect::Error,
536            RequestRedirect::Follow => web_sys::RequestRedirect::Follow,
537            RequestRedirect::Manual => web_sys::RequestRedirect::Manual,
538        }
539    }
540}