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;
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}