1#![feature(min_specialization)]
2#![feature(arbitrary_self_types)]
3#![feature(arbitrary_self_types_pointers)]
4
5use anyhow::Result;
6use turbo_rcstr::RcStr;
7use turbo_tasks::{ResolvedVc, Vc, duration_span, mark_session_dependent};
8use turbo_tasks_fs::FileSystemPath;
9use turbopack_core::issue::{Issue, IssueSeverity, IssueStage, OptionStyledString, StyledString};
10
11pub fn register() {
12 turbo_tasks::register();
13 turbo_tasks_fs::register();
14 turbopack_core::register();
15 include!(concat!(env!("OUT_DIR"), "/register.rs"));
16}
17
18#[turbo_tasks::value(transparent)]
19pub struct FetchResult(Result<ResolvedVc<HttpResponse>, ResolvedVc<FetchError>>);
20
21#[turbo_tasks::value(shared)]
22#[derive(Debug)]
23pub struct HttpResponse {
24 pub status: u16,
25 pub body: ResolvedVc<HttpResponseBody>,
26}
27
28#[turbo_tasks::value(shared)]
29#[derive(Debug)]
30pub struct HttpResponseBody(pub Vec<u8>);
31
32#[turbo_tasks::value_impl]
33impl HttpResponseBody {
34 #[turbo_tasks::function]
35 pub async fn to_string(self: Vc<Self>) -> Result<Vc<RcStr>> {
36 let this = &*self.await?;
37 Ok(Vc::cell(std::str::from_utf8(&this.0)?.into()))
38 }
39}
40
41#[turbo_tasks::value(shared)]
42#[derive(Debug)]
43pub enum ProxyConfig {
44 Http(String),
45 Https(String),
46}
47
48#[turbo_tasks::value(transparent)]
49pub struct OptionProxyConfig(Option<ProxyConfig>);
50
51#[turbo_tasks::function(network)]
52pub async fn fetch(
53 url: Vc<RcStr>,
54 user_agent: Vc<Option<RcStr>>,
55 proxy_option: Vc<OptionProxyConfig>,
56) -> Result<Vc<FetchResult>> {
57 let url = &*url.await?;
58 let user_agent = &*user_agent.await?;
59 let proxy_option = &*proxy_option.await?;
60
61 let client_builder = reqwest::Client::builder();
62 let client_builder = match proxy_option {
63 Some(ProxyConfig::Http(proxy)) => client_builder.proxy(reqwest::Proxy::http(proxy)?),
64 Some(ProxyConfig::Https(proxy)) => client_builder.proxy(reqwest::Proxy::https(proxy)?),
65 _ => client_builder,
66 };
67
68 let client = client_builder.build()?;
69
70 let mut builder = client.get(url.as_str());
71 if let Some(user_agent) = user_agent {
72 builder = builder.header("User-Agent", user_agent.as_str());
73 }
74
75 let response = {
76 let _span = duration_span!("fetch request", url = url.as_str());
77 builder.send().await
78 }
79 .and_then(|r| r.error_for_status());
80 match response {
81 Ok(response) => {
82 let status = response.status().as_u16();
83
84 let body = {
85 let _span = duration_span!("fetch response", url = url.as_str());
86 response.bytes().await?
87 }
88 .to_vec();
89
90 Ok(Vc::cell(Ok(HttpResponse {
91 status,
92 body: HttpResponseBody::resolved_cell(HttpResponseBody(body)),
93 }
94 .resolved_cell())))
95 }
96 Err(err) => {
97 mark_session_dependent();
98 Ok(Vc::cell(Err(
99 FetchError::from_reqwest_error(&err, url).resolved_cell()
100 )))
101 }
102 }
103}
104
105#[derive(Debug)]
106#[turbo_tasks::value(shared)]
107pub enum FetchErrorKind {
108 Connect,
109 Timeout,
110 Status(u16),
111 Other,
112}
113
114#[turbo_tasks::value(shared)]
115pub struct FetchError {
116 pub url: ResolvedVc<RcStr>,
117 pub kind: ResolvedVc<FetchErrorKind>,
118 pub detail: ResolvedVc<StyledString>,
119}
120
121impl FetchError {
122 fn from_reqwest_error(error: &reqwest::Error, url: &str) -> FetchError {
123 let kind = if error.is_connect() {
124 FetchErrorKind::Connect
125 } else if error.is_timeout() {
126 FetchErrorKind::Timeout
127 } else if let Some(status) = error.status() {
128 FetchErrorKind::Status(status.as_u16())
129 } else {
130 FetchErrorKind::Other
131 };
132
133 FetchError {
134 detail: StyledString::Text(error.to_string().into()).resolved_cell(),
135 url: ResolvedVc::cell(url.into()),
136 kind: kind.resolved_cell(),
137 }
138 }
139}
140
141#[turbo_tasks::value_impl]
142impl FetchError {
143 #[turbo_tasks::function]
144 pub async fn to_issue(
145 self: Vc<Self>,
146 severity: ResolvedVc<IssueSeverity>,
147 issue_context: ResolvedVc<FileSystemPath>,
148 ) -> Result<Vc<FetchIssue>> {
149 let this = &*self.await?;
150 Ok(FetchIssue {
151 issue_context,
152 severity,
153 url: this.url,
154 kind: this.kind,
155 detail: this.detail,
156 }
157 .into())
158 }
159}
160
161#[turbo_tasks::value(shared)]
162pub struct FetchIssue {
163 pub issue_context: ResolvedVc<FileSystemPath>,
164 pub severity: ResolvedVc<IssueSeverity>,
165 pub url: ResolvedVc<RcStr>,
166 pub kind: ResolvedVc<FetchErrorKind>,
167 pub detail: ResolvedVc<StyledString>,
168}
169
170#[turbo_tasks::value_impl]
171impl Issue for FetchIssue {
172 #[turbo_tasks::function]
173 fn file_path(&self) -> Vc<FileSystemPath> {
174 *self.issue_context
175 }
176
177 #[turbo_tasks::function]
178 fn severity(&self) -> Vc<IssueSeverity> {
179 *self.severity
180 }
181
182 #[turbo_tasks::function]
183 fn title(&self) -> Vc<StyledString> {
184 StyledString::Text("Error while requesting resource".into()).cell()
185 }
186
187 #[turbo_tasks::function]
188 fn stage(&self) -> Vc<IssueStage> {
189 IssueStage::Load.into()
190 }
191
192 #[turbo_tasks::function]
193 async fn description(&self) -> Result<Vc<OptionStyledString>> {
194 let url = &*self.url.await?;
195 let kind = &*self.kind.await?;
196
197 Ok(Vc::cell(Some(
198 StyledString::Text(match kind {
199 FetchErrorKind::Connect => {
200 format!("There was an issue establishing a connection while requesting {url}.")
201 .into()
202 }
203 FetchErrorKind::Status(status) => {
204 format!("Received response with status {status} when requesting {url}").into()
205 }
206 FetchErrorKind::Timeout => {
207 format!("Connection timed out when requesting {url}").into()
208 }
209 FetchErrorKind::Other => format!("There was an issue requesting {url}").into(),
210 })
211 .resolved_cell(),
212 )))
213 }
214
215 #[turbo_tasks::function]
216 fn detail(&self) -> Vc<OptionStyledString> {
217 Vc::cell(Some(self.detail))
218 }
219}