turbo_tasks_fetch/
lib.rs

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}