turbo_tasks_fetch/
error.rs1use anyhow::Result;
2use turbo_rcstr::{RcStr, rcstr};
3use turbo_tasks::{ResolvedVc, Vc};
4use turbo_tasks_fs::FileSystemPath;
5use turbopack_core::issue::{Issue, IssueSeverity, IssueStage, OptionStyledString, StyledString};
6
7#[derive(Debug)]
8#[turbo_tasks::value(shared)]
9pub enum FetchErrorKind {
10 Connect {
11 has_system_certs: bool,
12 has_rustls_cause: bool,
13 },
14 Timeout,
15 Status(u16),
16 Other,
17}
18
19#[turbo_tasks::value(shared)]
20pub struct FetchError {
21 pub url: ResolvedVc<RcStr>,
22 pub kind: ResolvedVc<FetchErrorKind>,
23 pub detail: ResolvedVc<StyledString>,
24}
25
26fn has_rustls_cause(err: &reqwest::Error) -> bool {
32 #[cfg(not(any(
34 all(target_os = "windows", target_arch = "aarch64"),
35 target_arch = "wasm32"
36 )))]
37 {
38 let mut source = std::error::Error::source(err);
39 while let Some(err) = source {
40 if err.downcast_ref::<rustls::Error>().is_some() {
41 return true;
42 }
43 if let Some(err) = err.downcast_ref::<std::io::Error>() {
44 source = err.get_ref().map(|e| e as &dyn std::error::Error);
48 } else {
49 source = std::error::Error::source(err);
50 }
51 }
52 return false;
53 };
54
55 #[cfg(all(target_os = "windows", target_arch = "aarch64"))]
57 return false;
58}
59
60impl FetchError {
61 pub(crate) fn from_reqwest_error(
62 error: &reqwest::Error,
63 url: &str,
64 webpki_certs_only: bool,
65 ) -> FetchError {
66 let kind = if error.is_connect() {
67 FetchErrorKind::Connect {
68 has_system_certs: webpki_certs_only,
69 has_rustls_cause: has_rustls_cause(error),
70 }
71 } else if error.is_timeout() {
72 FetchErrorKind::Timeout
73 } else if let Some(status) = error.status() {
74 FetchErrorKind::Status(status.as_u16())
75 } else {
76 FetchErrorKind::Other
77 };
78
79 FetchError {
80 detail: StyledString::Text(error.to_string().into()).resolved_cell(),
81 url: ResolvedVc::cell(url.into()),
82 kind: kind.resolved_cell(),
83 }
84 }
85}
86
87#[turbo_tasks::value_impl]
88impl FetchError {
89 #[turbo_tasks::function]
90 pub fn to_issue(
91 &self,
92 severity: IssueSeverity,
93 issue_context: FileSystemPath,
94 ) -> Vc<FetchIssue> {
95 FetchIssue {
96 issue_context,
97 severity,
98 url: self.url,
99 kind: self.kind,
100 detail: self.detail,
101 }
102 .cell()
103 }
104}
105
106#[turbo_tasks::value(shared)]
107pub struct FetchIssue {
108 pub issue_context: FileSystemPath,
109 pub severity: IssueSeverity,
110 pub url: ResolvedVc<RcStr>,
111 pub kind: ResolvedVc<FetchErrorKind>,
112 pub detail: ResolvedVc<StyledString>,
113}
114
115#[turbo_tasks::value_impl]
116impl Issue for FetchIssue {
117 #[turbo_tasks::function]
118 fn file_path(&self) -> Vc<FileSystemPath> {
119 self.issue_context.clone().cell()
120 }
121
122 fn severity(&self) -> IssueSeverity {
123 self.severity
124 }
125
126 #[turbo_tasks::function]
127 fn title(&self) -> Vc<StyledString> {
128 StyledString::Text(rcstr!("Error while requesting resource")).cell()
129 }
130
131 #[turbo_tasks::function]
132 fn stage(&self) -> Vc<IssueStage> {
133 IssueStage::Load.into()
134 }
135
136 #[turbo_tasks::function]
137 async fn description(&self) -> Result<Vc<OptionStyledString>> {
138 let url = &*self.url.await?;
139 let kind = &*self.kind.await?;
140
141 Ok(Vc::cell(Some(
142 match kind {
143 FetchErrorKind::Connect {
144 has_system_certs,
145 has_rustls_cause,
146 } => {
147 let base_message = StyledString::Line(vec![
148 StyledString::Text(rcstr!(
149 "There was an issue establishing a connection while requesting "
150 )),
151 StyledString::Code(url.clone()),
152 ]);
153 if !*has_system_certs && *has_rustls_cause {
154 StyledString::Stack(vec![
155 base_message,
156 StyledString::Line(vec![
157 StyledString::Strong(rcstr!("Hint: ")),
158 StyledString::Text(rcstr!(
159 "It looks like this error was TLS-related. Try enabling \
160 system TLS certificates with "
161 )),
162 StyledString::Code(rcstr!(
163 "NEXT_TURBOPACK_EXPERIMENTAL_USE_SYSTEM_TLS_CERTS=1"
164 )),
165 StyledString::Text(rcstr!(" as an environment variable, or set ")),
166 StyledString::Code(rcstr!(
167 "experimental.turbopackUseSystemTlsCerts"
168 )),
169 StyledString::Text(rcstr!(" in your ")),
170 StyledString::Code(rcstr!("next.config.js")),
171 StyledString::Text(rcstr!(" file.")),
172 ]),
173 ])
174 } else {
175 base_message
176 }
177 }
178 FetchErrorKind::Status(status) => StyledString::Line(vec![
179 StyledString::Text(rcstr!("Received response with status ")),
180 StyledString::Code(RcStr::from(status.to_string())),
181 StyledString::Text(rcstr!(" when requesting ")),
182 StyledString::Code(url.clone()),
183 ]),
184 FetchErrorKind::Timeout => StyledString::Line(vec![
185 StyledString::Text(rcstr!("Connection timed out when requesting ")),
186 StyledString::Code(url.clone()),
187 ]),
188 FetchErrorKind::Other => StyledString::Line(vec![
189 StyledString::Text(rcstr!("There was an issue requesting ")),
190 StyledString::Code(url.clone()),
191 ]),
192 }
193 .resolved_cell(),
194 )))
195 }
196
197 #[turbo_tasks::function]
198 fn detail(&self) -> Vc<OptionStyledString> {
199 Vc::cell(Some(self.detail))
200 }
201}