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