Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/cli/src/util/errors.rs
3314 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
use crate::{
6
constants::{APPLICATION_NAME, CONTROL_PORT, DOCUMENTATION_URL, QUALITYLESS_PRODUCT_NAME},
7
rpc::ResponseError,
8
};
9
use std::fmt::Display;
10
use thiserror::Error;
11
12
// Wraps another error with additional info.
13
#[derive(Debug, Clone)]
14
pub struct WrappedError {
15
message: String,
16
original: String,
17
}
18
19
impl std::fmt::Display for WrappedError {
20
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
21
write!(f, "{}: {}", self.message, self.original)
22
}
23
}
24
25
impl std::error::Error for WrappedError {
26
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
27
None
28
}
29
}
30
31
impl WrappedError {
32
// fn new(original: Box<dyn std::error::Error>, message: String) -> WrappedError {
33
// WrappedError { message, original }
34
// }
35
}
36
37
impl From<reqwest::Error> for WrappedError {
38
fn from(e: reqwest::Error) -> WrappedError {
39
WrappedError {
40
message: format!(
41
"error requesting {}",
42
e.url().map_or("<unknown>", |u| u.as_str())
43
),
44
original: format!("{e}"),
45
}
46
}
47
}
48
49
pub fn wrapdbg<T, S>(original: T, message: S) -> WrappedError
50
where
51
T: std::fmt::Debug,
52
S: Into<String>,
53
{
54
WrappedError {
55
message: message.into(),
56
original: format!("{original:?}"),
57
}
58
}
59
60
pub fn wrap<T, S>(original: T, message: S) -> WrappedError
61
where
62
T: Display,
63
S: Into<String>,
64
{
65
WrappedError {
66
message: message.into(),
67
original: format!("{original}"),
68
}
69
}
70
71
// Error generated by an unsuccessful HTTP response
72
#[derive(Debug)]
73
pub struct StatusError {
74
pub url: String,
75
pub status_code: u16,
76
pub body: String,
77
}
78
79
impl std::fmt::Display for StatusError {
80
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
81
write!(
82
f,
83
"error requesting {}: {} {}",
84
self.url, self.status_code, self.body
85
)
86
}
87
}
88
89
impl StatusError {
90
pub async fn from_res(res: reqwest::Response) -> Result<StatusError, AnyError> {
91
let status_code = res.status().as_u16();
92
let url = res.url().to_string();
93
let body = res.text().await.map_err(|e| {
94
wrap(
95
e,
96
format!("failed to read response body on {status_code} code from {url}"),
97
)
98
})?;
99
100
Ok(StatusError {
101
url,
102
status_code,
103
body,
104
})
105
}
106
}
107
108
// When the provided connection token doesn't match the one used to set up the original VS Code Server
109
// This is most likely due to a new user joining.
110
#[derive(Debug)]
111
pub struct MismatchConnectionToken(pub String);
112
113
impl std::fmt::Display for MismatchConnectionToken {
114
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
115
write!(f, "{}", self.0)
116
}
117
}
118
119
// When the VS Code server has an unrecognized extension (rather than zip or gz)
120
#[derive(Debug)]
121
pub struct InvalidServerExtensionError(pub String);
122
123
impl std::fmt::Display for InvalidServerExtensionError {
124
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
125
write!(f, "invalid server extension '{}'", self.0)
126
}
127
}
128
129
// When the tunnel fails to open
130
#[derive(Debug, Clone)]
131
pub struct DevTunnelError(pub String);
132
133
impl std::fmt::Display for DevTunnelError {
134
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
135
write!(f, "could not open tunnel: {}", self.0)
136
}
137
}
138
139
impl std::error::Error for DevTunnelError {
140
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
141
None
142
}
143
}
144
145
// When the server was downloaded, but the entrypoint scripts don't exist.
146
#[derive(Debug)]
147
pub struct MissingEntrypointError();
148
149
impl std::fmt::Display for MissingEntrypointError {
150
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
151
write!(f, "Missing entrypoints in server download. Most likely this is a corrupted download. Please retry")
152
}
153
}
154
155
#[derive(Debug)]
156
pub struct SetupError(pub String);
157
158
impl std::fmt::Display for SetupError {
159
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
160
write!(
161
f,
162
"{}\n\nMore info at {}/remote/linux",
163
DOCUMENTATION_URL.unwrap_or("<docs>"),
164
self.0
165
)
166
}
167
}
168
169
#[derive(Debug)]
170
pub struct NoHomeForLauncherError();
171
172
impl std::fmt::Display for NoHomeForLauncherError {
173
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
174
write!(
175
f,
176
"No $HOME variable was found in your environment. Either set it, or specify a `--data-dir` manually when invoking the launcher.",
177
)
178
}
179
}
180
181
#[derive(Debug)]
182
pub struct InvalidTunnelName(pub String);
183
184
impl std::fmt::Display for InvalidTunnelName {
185
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
186
write!(f, "{}", &self.0)
187
}
188
}
189
190
#[derive(Debug)]
191
pub struct TunnelCreationFailed(pub String, pub String);
192
193
impl std::fmt::Display for TunnelCreationFailed {
194
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
195
write!(
196
f,
197
"Could not create tunnel with name: {}\nReason: {}",
198
&self.0, &self.1
199
)
200
}
201
}
202
203
#[derive(Debug)]
204
pub struct TunnelHostFailed(pub String);
205
206
impl std::fmt::Display for TunnelHostFailed {
207
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
208
write!(f, "{}", &self.0)
209
}
210
}
211
212
#[derive(Debug)]
213
pub struct ExtensionInstallFailed(pub String);
214
215
impl std::fmt::Display for ExtensionInstallFailed {
216
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
217
write!(f, "Extension install failed: {}", &self.0)
218
}
219
}
220
221
#[derive(Debug)]
222
pub struct MismatchedLaunchModeError();
223
224
impl std::fmt::Display for MismatchedLaunchModeError {
225
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
226
write!(f, "A server is already running, but it was not launched in the same listening mode (port vs. socket) as this request")
227
}
228
}
229
230
#[derive(Debug)]
231
pub struct NoAttachedServerError();
232
233
impl std::fmt::Display for NoAttachedServerError {
234
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
235
write!(f, "No server is running")
236
}
237
}
238
239
#[derive(Debug)]
240
pub struct RefreshTokenNotAvailableError();
241
242
impl std::fmt::Display for RefreshTokenNotAvailableError {
243
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
244
write!(f, "Refresh token not available, authentication is required")
245
}
246
}
247
248
#[derive(Debug)]
249
pub struct NoInstallInUserProvidedPath(pub String);
250
251
impl std::fmt::Display for NoInstallInUserProvidedPath {
252
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
253
write!(
254
f,
255
"No {} installation could be found in {}. You can run `{} --use-quality=stable` to switch to the latest stable version of {}.",
256
QUALITYLESS_PRODUCT_NAME,
257
self.0,
258
APPLICATION_NAME,
259
QUALITYLESS_PRODUCT_NAME
260
)
261
}
262
}
263
264
#[derive(Debug)]
265
pub struct InvalidRequestedVersion();
266
267
impl std::fmt::Display for InvalidRequestedVersion {
268
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
269
write!(
270
f,
271
"The reqested version is invalid, expected one of 'stable', 'insiders', version number (x.y.z), or absolute path.",
272
)
273
}
274
}
275
276
#[derive(Debug)]
277
pub struct UserCancelledInstallation();
278
279
impl std::fmt::Display for UserCancelledInstallation {
280
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
281
write!(f, "Installation aborted.")
282
}
283
}
284
285
#[derive(Debug)]
286
pub struct CannotForwardControlPort();
287
288
impl std::fmt::Display for CannotForwardControlPort {
289
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
290
write!(f, "Cannot forward or unforward port {CONTROL_PORT}.")
291
}
292
}
293
294
#[derive(Debug)]
295
pub struct ServerHasClosed();
296
297
impl std::fmt::Display for ServerHasClosed {
298
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
299
write!(f, "Request cancelled because the server has closed")
300
}
301
}
302
303
#[derive(Debug)]
304
pub struct ServiceAlreadyRegistered();
305
306
impl std::fmt::Display for ServiceAlreadyRegistered {
307
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
308
write!(f, "Already registered the service. Run `{APPLICATION_NAME} tunnel service uninstall` to unregister it first")
309
}
310
}
311
312
#[derive(Debug)]
313
pub struct WindowsNeedsElevation(pub String);
314
315
impl std::fmt::Display for WindowsNeedsElevation {
316
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
317
writeln!(f, "{}", self.0)?;
318
writeln!(f)?;
319
writeln!(f, "You may need to run this command as an administrator:")?;
320
writeln!(f, " 1. Open the start menu and search for Powershell")?;
321
writeln!(f, " 2. Right click and 'Run as administrator'")?;
322
if let Ok(exe) = std::env::current_exe() {
323
writeln!(
324
f,
325
" 3. Run &'{}' '{}'",
326
exe.display(),
327
std::env::args().skip(1).collect::<Vec<_>>().join("' '")
328
)
329
} else {
330
writeln!(f, " 3. Run the same command again",)
331
}
332
}
333
}
334
335
#[derive(Debug)]
336
pub struct InvalidRpcDataError(pub String);
337
338
impl std::fmt::Display for InvalidRpcDataError {
339
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
340
write!(f, "parse error: {}", self.0)
341
}
342
}
343
344
#[derive(Debug)]
345
pub struct CorruptDownload(pub String);
346
347
impl std::fmt::Display for CorruptDownload {
348
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
349
write!(
350
f,
351
"Error updating the {} CLI: {}",
352
QUALITYLESS_PRODUCT_NAME, self.0
353
)
354
}
355
}
356
357
#[derive(Debug)]
358
pub struct MissingHomeDirectory();
359
360
impl std::fmt::Display for MissingHomeDirectory {
361
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
362
write!(f, "Could not find your home directory. Please ensure this command is running in the context of an normal user.")
363
}
364
}
365
366
#[derive(Debug)]
367
pub struct OAuthError {
368
pub error: String,
369
pub error_description: Option<String>,
370
}
371
372
impl std::fmt::Display for OAuthError {
373
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
374
write!(
375
f,
376
"Error getting authorization: {} {}",
377
self.error,
378
self.error_description.as_deref().unwrap_or("")
379
)
380
}
381
}
382
383
// Makes an "AnyError" enum that contains any of the given errors, in the form
384
// `enum AnyError { FooError(FooError) }` (when given `makeAnyError!(FooError)`).
385
// Useful to easily deal with application error types without making tons of "From"
386
// clauses.
387
macro_rules! makeAnyError {
388
($($e:ident),*) => {
389
390
#[derive(Debug)]
391
#[allow(clippy::enum_variant_names)]
392
pub enum AnyError {
393
$($e($e),)*
394
}
395
396
impl std::fmt::Display for AnyError {
397
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
398
match *self {
399
$(AnyError::$e(ref e) => e.fmt(f),)*
400
}
401
}
402
}
403
404
impl std::error::Error for AnyError {
405
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
406
None
407
}
408
}
409
410
$(impl From<$e> for AnyError {
411
fn from(e: $e) -> AnyError {
412
AnyError::$e(e)
413
}
414
})*
415
};
416
}
417
418
#[derive(Debug)]
419
pub struct DbusConnectFailedError(pub String);
420
421
impl Display for DbusConnectFailedError {
422
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
423
let mut str = String::new();
424
str.push_str("Error creating dbus session. This command uses systemd for managing services, you should check that systemd is installed and under your user.");
425
426
if std::env::var("WSL_DISTRO_NAME").is_ok() {
427
str.push_str("\n\nTo enable systemd on WSL, check out: https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/.\n\n");
428
}
429
430
str.push_str("If running `systemctl status` works, systemd is ok, but your session dbus may not be. You might need to:\n\n- Install the `dbus-user-session` package, and reboot if it was not installed\n- Start the user dbus session with `systemctl --user enable dbus --now`.\n\nThe error encountered was: ");
431
str.push_str(&self.0);
432
str.push('\n');
433
434
write!(f, "{str}")
435
}
436
}
437
438
/// Internal errors in the VS Code CLI.
439
/// Note: other error should be migrated to this type gradually
440
#[derive(Error, Debug)]
441
pub enum CodeError {
442
#[error("could not connect to socket/pipe: {0:?}")]
443
AsyncPipeFailed(std::io::Error),
444
#[error("could not listen on socket/pipe: {0:?}")]
445
AsyncPipeListenerFailed(std::io::Error),
446
#[error("could not create singleton lock file: {0:?}")]
447
SingletonLockfileOpenFailed(std::io::Error),
448
#[error("could not read singleton lock file: {0:?}")]
449
SingletonLockfileReadFailed(rmp_serde::decode::Error),
450
#[error("the process holding the singleton lock file (pid={0}) exited")]
451
SingletonLockedProcessExited(u32),
452
#[error("no tunnel process is currently running")]
453
NoRunningTunnel,
454
#[error("rpc call failed: {0:?}")]
455
TunnelRpcCallFailed(ResponseError),
456
#[cfg(windows)]
457
#[error("the windows app lock {0} already exists")]
458
AppAlreadyLocked(String),
459
#[cfg(windows)]
460
#[error("could not get windows app lock: {0:?}")]
461
AppLockFailed(std::io::Error),
462
#[error("failed to run command \"{command}\" (code {code}): {output}")]
463
CommandFailed {
464
command: String,
465
code: i32,
466
output: String,
467
},
468
469
#[error("platform not currently supported: {0}")]
470
UnsupportedPlatform(String),
471
#[error("This machine does not meet {name}'s prerequisites, expected either...\n{bullets}")]
472
PrerequisitesFailed { name: &'static str, bullets: String },
473
#[error("failed to spawn process: {0:?}")]
474
ProcessSpawnFailed(std::io::Error),
475
#[error("failed to handshake spawned process: {0:?}")]
476
ProcessSpawnHandshakeFailed(std::io::Error),
477
#[error("download appears corrupted, please retry ({0})")]
478
CorruptDownload(&'static str),
479
#[error("port forwarding is not available in this context")]
480
PortForwardingNotAvailable,
481
#[error("'auth' call required")]
482
ServerAuthRequired,
483
#[error("challenge not yet issued")]
484
AuthChallengeNotIssued,
485
#[error("challenge token is invalid")]
486
AuthChallengeBadToken,
487
#[error("unauthorized client refused")]
488
AuthMismatch,
489
#[error("keyring communication timed out after 5s")]
490
KeyringTimeout,
491
#[error("no host is connected to the tunnel relay")]
492
NoTunnelEndpoint,
493
#[error("could not parse `host`: {0}")]
494
InvalidHostAddress(std::net::AddrParseError),
495
#[error("could not start server on the given host/port: {0}")]
496
CouldNotListenOnInterface(hyper::Error),
497
#[error(
498
"Run this command again with --accept-server-license-terms to indicate your agreement."
499
)]
500
NeedsInteractiveLegalConsent,
501
#[error("Sorry, you cannot use this CLI without accepting the terms.")]
502
DeniedLegalConset,
503
#[error("The server is not yet downloaded, try again shortly.")]
504
ServerNotYetDownloaded,
505
#[error("An error was encountered downloading the server, please retry: {0}")]
506
ServerDownloadError(String),
507
#[error("Updates are are not available: {0}")]
508
UpdatesNotConfigured(&'static str),
509
// todo: can be specialized when update service is moved to CodeErrors
510
#[error("Could not check for update: {0}")]
511
UpdateCheckFailed(String),
512
#[error("Could not read connection token file: {0}")]
513
CouldNotReadConnectionTokenFile(std::io::Error),
514
#[error("Could not write connection token file: {0}")]
515
CouldNotCreateConnectionTokenFile(std::io::Error),
516
#[error("A tunnel with the name {0} exists and is in-use. Please pick a different name or stop the existing tunnel.")]
517
TunnelActiveAndInUse(String),
518
#[error("Timed out looking for port/socket")]
519
ServerOriginTimeout,
520
#[error("Server exited without writing port/socket: {0}")]
521
ServerUnexpectedExit(String),
522
}
523
524
makeAnyError!(
525
MismatchConnectionToken,
526
DevTunnelError,
527
StatusError,
528
WrappedError,
529
InvalidServerExtensionError,
530
MissingEntrypointError,
531
SetupError,
532
NoHomeForLauncherError,
533
TunnelCreationFailed,
534
TunnelHostFailed,
535
InvalidTunnelName,
536
ExtensionInstallFailed,
537
MismatchedLaunchModeError,
538
NoAttachedServerError,
539
RefreshTokenNotAvailableError,
540
NoInstallInUserProvidedPath,
541
UserCancelledInstallation,
542
InvalidRequestedVersion,
543
CannotForwardControlPort,
544
ServerHasClosed,
545
ServiceAlreadyRegistered,
546
WindowsNeedsElevation,
547
CorruptDownload,
548
MissingHomeDirectory,
549
OAuthError,
550
InvalidRpcDataError,
551
CodeError,
552
DbusConnectFailedError
553
);
554
555
impl From<reqwest::Error> for AnyError {
556
fn from(e: reqwest::Error) -> AnyError {
557
AnyError::WrappedError(WrappedError::from(e))
558
}
559
}
560
561