1use std::fmt::{Display, Formatter};
8use std::pin::Pin;
9use std::task::Poll;
10use std::{fmt, task};
11
12use bytes::Bytes;
13use form_urlencoded::Serializer;
14use http::header::CONTENT_TYPE;
15use http::{HeaderMap, HeaderValue};
16use http_body_util::{BodyExt as _, Full};
17use hyper::body::{Frame, Incoming, SizeHint};
18use ion::conversions::FromValue;
19use ion::{Context, Error, ErrorKind, Traceable, Value};
20use mozjs::jsapi::Heap;
21use mozjs::jsval::JSVal;
22use pin_project_lite::pin_project;
23
24use crate::globals::file::{Blob, BufferSource};
25use crate::globals::url::URLSearchParams;
26
27#[derive(Debug, Clone, Traceable)]
28#[non_exhaustive]
29enum FetchBodyInner {
30 None,
31 Bytes(#[trace(no_trace)] Bytes),
32}
33
34#[derive(Clone, Debug, Traceable)]
35#[non_exhaustive]
36pub enum FetchBodyKind {
37 String,
38 Blob(String),
39 URLSearchParams,
40}
41
42impl Display for FetchBodyKind {
43 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
44 match self {
45 FetchBodyKind::String => f.write_str("text/plain;charset=UTF-8"),
46 FetchBodyKind::Blob(mime) => f.write_str(mime),
47 FetchBodyKind::URLSearchParams => f.write_str("application/x-www-form-urlencoded;charset=UTF-8"),
48 }
49 }
50}
51
52#[derive(Debug, Traceable)]
53pub struct FetchBody {
54 body: FetchBodyInner,
55 source: Option<Box<Heap<JSVal>>>,
56 pub(crate) kind: Option<FetchBodyKind>,
57}
58
59impl FetchBody {
60 pub fn is_none(&self) -> bool {
61 matches!(&self.body, FetchBodyInner::None)
62 }
63
64 pub fn is_empty(&self) -> bool {
65 match &self.body {
66 FetchBodyInner::None => true,
67 FetchBodyInner::Bytes(bytes) => bytes.is_empty(),
68 }
69 }
70
71 pub fn len(&self) -> Option<usize> {
72 match &self.body {
73 FetchBodyInner::None => None,
74 FetchBodyInner::Bytes(bytes) => Some(bytes.len()),
75 }
76 }
77
78 pub fn is_stream(&self) -> bool {
79 !matches!(&self.body, FetchBodyInner::None | FetchBodyInner::Bytes(_))
80 }
81
82 pub fn to_http_body(&self) -> Body {
83 match &self.body {
84 FetchBodyInner::None => Body::Empty,
85 FetchBodyInner::Bytes(bytes) => Body::from(bytes.clone()),
86 }
87 }
88
89 pub async fn read_to_bytes(&self) -> ion::Result<Vec<u8>> {
90 Ok(self.to_http_body().collect().await?.to_bytes().to_vec())
91 }
92
93 pub(crate) fn add_content_type_header(&self, headers: &mut HeaderMap) {
94 if let Some(kind) = &self.kind
95 && !headers.contains_key(CONTENT_TYPE)
96 {
97 headers.append(CONTENT_TYPE, HeaderValue::from_str(&kind.to_string()).unwrap());
98 }
99 }
100}
101
102impl Clone for FetchBody {
103 fn clone(&self) -> FetchBody {
104 FetchBody {
105 body: self.body.clone(),
106 source: self.source.as_ref().map(|s| Heap::boxed(s.get())),
107 kind: self.kind.clone(),
108 }
109 }
110}
111
112impl Default for FetchBody {
113 fn default() -> FetchBody {
114 FetchBody {
115 body: FetchBodyInner::None,
116 source: None,
117 kind: None,
118 }
119 }
120}
121
122impl<'cx> FromValue<'cx> for FetchBody {
123 type Config = ();
124 fn from_value(cx: &'cx Context, value: &Value, strict: bool, _: ()) -> ion::Result<FetchBody> {
125 if value.handle().is_string() {
126 return Ok(FetchBody {
127 body: FetchBodyInner::Bytes(Bytes::from(String::from_value(cx, value, strict, ()).unwrap())),
128 source: Some(Heap::boxed(value.get())),
129 kind: Some(FetchBodyKind::String),
130 });
131 } else if value.handle().is_object() {
132 if let Ok(source) = BufferSource::from_value(cx, value, strict, false) {
133 return Ok(FetchBody {
134 body: FetchBodyInner::Bytes(source.to_bytes()),
135 source: Some(Heap::boxed(value.get())),
136 kind: None,
137 });
138 } else if let Ok(blob) = <&Blob>::from_value(cx, value, strict, ()) {
139 return Ok(FetchBody {
140 body: FetchBodyInner::Bytes(blob.bytes.clone()),
141 source: Some(Heap::boxed(value.get())),
142 kind: blob.kind.clone().map(FetchBodyKind::Blob),
143 });
144 } else if let Ok(search_params) = <&URLSearchParams>::from_value(cx, value, strict, ()) {
145 return Ok(FetchBody {
146 body: FetchBodyInner::Bytes(Bytes::from(
147 Serializer::new(String::new()).extend_pairs(search_params.pairs()).finish(),
148 )),
149 source: Some(Heap::boxed(value.get())),
150 kind: Some(FetchBodyKind::URLSearchParams),
151 });
152 }
153 }
154 Err(Error::new("Expected Valid Body", ErrorKind::Type))
155 }
156}
157
158pin_project! {
159 #[project = BodyProject]
160 #[derive(Default)]
161 pub enum Body {
162 #[default]
163 Empty,
164 Once {
165 #[pin]
166 bytes: Full<Bytes>
167 },
168 Incoming {
169 #[pin]
170 incoming: Incoming
171 },
172 }
173}
174
175impl hyper::body::Body for Body {
176 type Data = Bytes;
177 type Error = hyper::Error;
178
179 fn poll_frame(
180 self: Pin<&mut Self>, cx: &mut task::Context<'_>,
181 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
182 match self.project() {
183 BodyProject::Empty => Poll::Ready(None),
184 BodyProject::Once { bytes } => bytes.poll_frame(cx).map_err(|e| match e {}),
185 BodyProject::Incoming { incoming } => incoming.poll_frame(cx),
186 }
187 }
188
189 fn is_end_stream(&self) -> bool {
190 match self {
191 Body::Empty => true,
192 Body::Once { bytes } => bytes.is_end_stream(),
193 Body::Incoming { incoming } => incoming.is_end_stream(),
194 }
195 }
196
197 fn size_hint(&self) -> SizeHint {
198 match self {
199 Body::Empty => SizeHint::with_exact(0),
200 Body::Once { bytes } => bytes.size_hint(),
201 Body::Incoming { incoming } => incoming.size_hint(),
202 }
203 }
204}
205
206impl From<Bytes> for Body {
207 fn from(bytes: Bytes) -> Body {
208 Body::Once { bytes: Full::new(bytes) }
209 }
210}