Convert more ShellError variants to named fields (#11173)

# Description

Convert these ShellError variants to named fields:
* CreateNotPossible
* MoveNotPossibleSingle
* DirectoryNotFoundCustom
* DirectoryNotFound
* NotADirectory
* OutOfMemoryError
* PermissionDeniedError
* IOErrorSpanned
* IOError
* IOInterrupted

Also place the `span` field of `DirectoryNotFound` last to match other
errors.

Part of #10700 (almost half done!)

# User-Facing Changes

None

# Tests + Formatting

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

N/A
This commit is contained in:
Eric Hodel 2023-11-28 04:43:51 -08:00 committed by GitHub
parent 182b0ab4fb
commit 8386bc0919
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 241 additions and 116 deletions

View File

@ -502,10 +502,10 @@ pub fn evaluate_repl(
report_error(
&working_set,
&ShellError::DirectoryNotFound(
tokens.0[0].span,
path.to_string_lossy().to_string(),
),
&ShellError::DirectoryNotFound {
dir: path.to_string_lossy().to_string(),
span: tokens.0[0].span,
},
);
}
let path = nu_path::canonicalize_with(path, &cwd)

View File

@ -83,10 +83,10 @@ impl Command for Cd {
let path = match nu_path::canonicalize_with(path.clone(), &cwd) {
Ok(p) => p,
Err(_) => {
return Err(ShellError::DirectoryNotFound(
v.span,
path.to_string_lossy().to_string(),
));
return Err(ShellError::DirectoryNotFound {
dir: path.to_string_lossy().to_string(),
span: v.span,
});
}
};
(path.to_string_lossy().to_string(), v.span)
@ -100,17 +100,17 @@ impl Command for Cd {
let path = match nu_path::canonicalize_with(path_no_whitespace, &cwd) {
Ok(p) => {
if !p.is_dir() {
return Err(ShellError::NotADirectory(v.span));
return Err(ShellError::NotADirectory { span: v.span });
};
p
}
// if canonicalize failed, let's check to see if it's abbreviated
Err(_) => {
return Err(ShellError::DirectoryNotFound(
v.span,
path_no_whitespace.to_string(),
));
return Err(ShellError::DirectoryNotFound {
dir: path_no_whitespace.to_string(),
span: v.span,
});
}
};
(path.to_string_lossy().to_string(), v.span)
@ -135,9 +135,9 @@ impl Command for Cd {
stack.add_env_var("PWD".into(), path_value);
Ok(PipelineData::empty())
}
PermissionResult::PermissionDenied(reason) => Err(ShellError::IOError(format!(
"Cannot change directory to {path}: {reason}"
))),
PermissionResult::PermissionDenied(reason) => Err(ShellError::IOError {
msg: format!("Cannot change directory to {path}: {reason}"),
}),
}
}

View File

@ -88,10 +88,10 @@ impl Command for Cp {
let path_last_char = destination.as_os_str().to_string_lossy().chars().last();
let is_directory = path_last_char == Some('/') || path_last_char == Some('\\');
if is_directory && !destination.exists() {
return Err(ShellError::DirectoryNotFound(
dst.span,
destination.to_string_lossy().to_string(),
));
return Err(ShellError::DirectoryNotFound {
dir: destination.to_string_lossy().to_string(),
span: dst.span,
});
}
let ctrlc = engine_state.ctrlc.clone();
let span = call.head;
@ -577,18 +577,36 @@ fn convert_io_error(error: std::io::Error, src: PathBuf, dst: PathBuf, span: Spa
ErrorKind::PermissionDenied => match std::fs::metadata(&dst) {
Ok(meta) => {
if meta.permissions().readonly() {
ShellError::PermissionDeniedError(message_dst, span)
ShellError::PermissionDeniedError {
msg: message_dst,
span,
}
} else {
ShellError::PermissionDeniedError(message_src, span)
ShellError::PermissionDeniedError {
msg: message_src,
span,
}
}
}
Err(_) => ShellError::PermissionDeniedError(message_dst, span),
Err(_) => ShellError::PermissionDeniedError {
msg: message_dst,
span,
},
},
ErrorKind::Interrupted => ShellError::IOInterrupted {
msg: message_src,
span,
},
ErrorKind::OutOfMemory => ShellError::OutOfMemoryError {
msg: message_src,
span,
},
ErrorKind::Interrupted => ShellError::IOInterrupted(message_src, span),
ErrorKind::OutOfMemory => ShellError::OutOfMemoryError(message_src, span),
// TODO: handle ExecutableFileBusy etc. when io_error_more is stabilized
// https://github.com/rust-lang/rust/issues/86442
_ => ShellError::IOErrorSpanned(message_src, span),
_ => ShellError::IOErrorSpanned {
msg: message_src,
span,
},
};
Value::error(shell_error, span)

View File

@ -69,12 +69,13 @@ impl Command for Mkdir {
let dir_res = std::fs::create_dir_all(&dir);
if let Err(reason) = dir_res {
return Err(ShellError::CreateNotPossible(
format!("failed to create directory: {reason}"),
call.positional_nth(i)
return Err(ShellError::CreateNotPossible {
msg: format!("failed to create directory: {reason}"),
span: call
.positional_nth(i)
.expect("already checked through directories")
.span,
));
});
}
if is_verbose {

View File

@ -110,10 +110,14 @@ impl Command for Mktemp {
};
let res = match uu_mktemp::mktemp(&options) {
Ok(res) => res
.into_os_string()
.into_string()
.map_err(|e| ShellError::IOErrorSpanned(e.to_string_lossy().to_string(), span))?,
Ok(res) => {
res.into_os_string()
.into_string()
.map_err(|e| ShellError::IOErrorSpanned {
msg: e.to_string_lossy().to_string(),
span,
})?
}
Err(e) => {
return Err(ShellError::GenericError(
format!("{}", e),

View File

@ -260,10 +260,10 @@ fn move_file(
};
if !destination_dir_exists {
return Err(ShellError::DirectoryNotFound(
to_span,
to.to_string_lossy().to_string(),
));
return Err(ShellError::DirectoryNotFound {
dir: to.to_string_lossy().to_string(),
span: to_span,
});
}
// This can happen when changing case on a case-insensitive filesystem (ex: changing foo to Foo on Windows)
@ -275,10 +275,10 @@ fn move_file(
let from_file_name = match from.file_name() {
Some(name) => name,
None => {
return Err(ShellError::DirectoryNotFound(
to_span,
from.to_string_lossy().to_string(),
))
return Err(ShellError::DirectoryNotFound {
dir: from.to_string_lossy().to_string(),
span: to_span,
})
}
};

View File

@ -105,7 +105,7 @@ impl Command for Open {
for path in nu_engine::glob_from(&path, &cwd, call_span, None)
.map_err(|err| match err {
ShellError::DirectoryNotFound(span, _) => ShellError::FileNotFound { span },
ShellError::DirectoryNotFound { span, .. } => ShellError::FileNotFound { span },
_ => err,
})?
.1

View File

@ -162,9 +162,13 @@ impl Command for Save {
)?;
for val in ls {
file.write_all(&value_to_bytes(val)?)
.map_err(|err| ShellError::IOError(err.to_string()))?;
.map_err(|err| ShellError::IOError {
msg: err.to_string(),
})?;
file.write_all("\n".as_bytes())
.map_err(|err| ShellError::IOError(err.to_string()))?;
.map_err(|err| ShellError::IOError {
msg: err.to_string(),
})?;
}
file.flush()?;
@ -184,8 +188,9 @@ impl Command for Save {
force,
)?;
file.write_all(&bytes)
.map_err(|err| ShellError::IOError(err.to_string()))?;
file.write_all(&bytes).map_err(|err| ShellError::IOError {
msg: err.to_string(),
})?;
file.flush()?;
@ -453,7 +458,9 @@ fn stream_to_file(
if let Err(err) = writer.write(&buf) {
*process_failed_p = true;
return Err(ShellError::IOError(err.to_string()));
return Err(ShellError::IOError {
msg: err.to_string(),
});
}
Ok(())
})

View File

@ -136,12 +136,13 @@ impl Command for Touch {
}
if let Err(err) = OpenOptions::new().write(true).create(true).open(&item) {
return Err(ShellError::CreateNotPossible(
format!("Failed to create file: {err}"),
call.positional_nth(index)
return Err(ShellError::CreateNotPossible {
msg: format!("Failed to create file: {err}"),
span: call
.positional_nth(index)
.expect("already checked positional")
.span,
));
});
};
if change_mtime {

View File

@ -83,10 +83,10 @@ impl Command for Watch {
let path = match nu_path::canonicalize_with(path_no_whitespace, cwd) {
Ok(p) => p,
Err(_) => {
return Err(ShellError::DirectoryNotFound(
path_arg.span,
path_no_whitespace.to_string(),
))
return Err(ShellError::DirectoryNotFound {
dir: path_no_whitespace.to_string(),
span: path_arg.span,
})
}
};
@ -153,15 +153,15 @@ impl Command for Watch {
let mut debouncer = match new_debouncer(debounce_duration, None, tx) {
Ok(d) => d,
Err(e) => {
return Err(ShellError::IOError(format!(
"Failed to create watcher: {e}"
)))
return Err(ShellError::IOError {
msg: format!("Failed to create watcher: {e}"),
})
}
};
if let Err(e) = debouncer.watcher().watch(&path, recursive_mode) {
return Err(ShellError::IOError(format!(
"Failed to create watcher: {e}"
)));
return Err(ShellError::IOError {
msg: format!("Failed to create watcher: {e}"),
});
}
// need to cache to make sure that rename event works.
debouncer.cache().add_root(&path, recursive_mode);
@ -275,14 +275,14 @@ impl Command for Watch {
}
}
Ok(Err(_)) => {
return Err(ShellError::IOError(
"Unexpected errors when receiving events".into(),
))
return Err(ShellError::IOError {
msg: "Unexpected errors when receiving events".into(),
})
}
Err(RecvTimeoutError::Disconnected) => {
return Err(ShellError::IOError(
"Unexpected disconnect from file watcher".into(),
));
return Err(ShellError::IOError {
msg: "Unexpected disconnect from file watcher".into(),
});
}
Err(RecvTimeoutError::Timeout) => {}
}

View File

@ -209,9 +209,9 @@ pub fn send_request(
}
Value::List { vals, .. } if body_type == BodyType::Form => {
if vals.len() % 2 != 0 {
return Err(ShellErrorOrRequestError::ShellError(ShellError::IOError(
"unsupported body input".into(),
)));
return Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
msg: "unsupported body input".into(),
}));
}
let data = vals
@ -233,9 +233,9 @@ pub fn send_request(
let data = value_to_json_value(&body)?;
send_cancellable_request(&request_url, Box::new(|| request.send_json(data)), ctrl_c)
}
_ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError(
"unsupported body input".into(),
))),
_ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
msg: "unsupported body input".into(),
})),
}
}

View File

@ -135,7 +135,13 @@ fn exists(path: &Path, span: Span, args: &Arguments) -> Value {
match path.try_exists() {
Ok(exists) => exists,
Err(err) => {
return Value::error(ShellError::IOErrorSpanned(err.to_string(), span), span)
return Value::error(
ShellError::IOErrorSpanned {
msg: err.to_string(),
span,
},
span,
)
}
},
span,

View File

@ -36,7 +36,10 @@ impl Command for Clear {
CommandSys::new("cmd")
.args(["/C", "cls"])
.status()
.map_err(|e| ShellError::IOErrorSpanned(e.to_string(), span))?;
.map_err(|e| ShellError::IOErrorSpanned {
msg: e.to_string(),
span,
})?;
} else if cfg!(unix) {
let mut cmd = CommandSys::new("/bin/sh");
@ -46,7 +49,10 @@ impl Command for Clear {
cmd.args(["-c", "clear"])
.status()
.map_err(|e| ShellError::IOErrorSpanned(e.to_string(), span))?;
.map_err(|e| ShellError::IOErrorSpanned {
msg: e.to_string(),
span,
})?;
}
Ok(Value::nothing(span).into_pipeline_data())

View File

@ -110,7 +110,9 @@ impl Command for Input {
{
if k.modifiers == KeyModifiers::CONTROL && c == 'c' {
crossterm::terminal::disable_raw_mode()?;
return Err(ShellError::IOError("SIGINT".to_string()));
return Err(ShellError::IOError {
msg: "SIGINT".to_string(),
});
}
continue;
}

View File

@ -196,7 +196,9 @@ impl Command for InputList {
.items(&options)
.report(false)
.interact_on_opt(&Term::stderr())
.map_err(|err| ShellError::IOError(format!("{}: {}", INTERACT_ERROR, err)))?,
.map_err(|err| ShellError::IOError {
msg: format!("{}: {}", INTERACT_ERROR, err),
})?,
)
} else if call.has_flag("fuzzy") {
let fuzzy_select = FuzzySelect::new(); //::with_theme(&theme);
@ -211,7 +213,9 @@ impl Command for InputList {
.default(0)
.report(false)
.interact_on_opt(&Term::stderr())
.map_err(|err| ShellError::IOError(format!("{}: {}", INTERACT_ERROR, err)))?,
.map_err(|err| ShellError::IOError {
msg: format!("{}: {}", INTERACT_ERROR, err),
})?,
)
} else {
let select = Select::new(); //::with_theme(&theme);
@ -225,7 +229,9 @@ impl Command for InputList {
.default(0)
.report(false)
.interact_on_opt(&Term::stderr())
.map_err(|err| ShellError::IOError(format!("{}: {}", INTERACT_ERROR, err)))?,
.map_err(|err| ShellError::IOError {
msg: format!("{}: {}", INTERACT_ERROR, err),
})?,
)
};

View File

@ -301,7 +301,9 @@ fn heuristic_parse_file(
}
}
} else {
Err(ShellError::IOError("Can not read input".to_string()))
Err(ShellError::IOError {
msg: "Can not read input".to_string(),
})
}
} else {
Err(ShellError::NotFound { span: call.head })
@ -402,7 +404,9 @@ fn parse_file_script(
call.head,
)
} else {
Err(ShellError::IOError("Can not read path".to_string()))
Err(ShellError::IOError {
msg: "Can not read path".to_string(),
})
}
} else {
Err(ShellError::NotFound { span: call.head })
@ -425,7 +429,9 @@ fn parse_file_module(
if let Ok(contents) = std::fs::read(path) {
parse_module(working_set, Some(filename), &contents, is_debug, call.head)
} else {
Err(ShellError::IOError("Can not read path".to_string()))
Err(ShellError::IOError {
msg: "Can not read path".to_string(),
})
}
} else {
Err(ShellError::NotFound { span: call.head })

View File

@ -72,10 +72,10 @@ pub fn glob_from(
let path = if let Ok(p) = canonicalize_with(path.clone(), cwd) {
p
} else {
return Err(ShellError::DirectoryNotFound(
pattern.span,
path.to_string_lossy().to_string(),
));
return Err(ShellError::DirectoryNotFound {
dir: path.to_string_lossy().to_string(),
span: pattern.span,
});
};
(path.parent().map(|parent| parent.to_path_buf()), path)
}

View File

@ -12,13 +12,17 @@ pub fn run_command_with_value(
stack: &mut Stack,
) -> Result<PipelineData, ShellError> {
if is_ignored_command(command) {
return Err(ShellError::IOError(String::from("the command is ignored")));
return Err(ShellError::IOError {
msg: String::from("the command is ignored"),
});
}
let pipeline = PipelineData::Value(input.clone(), None);
let pipeline = run_nu_command(engine_state, stack, command, pipeline)?;
if let PipelineData::Value(Value::Error { error, .. }, ..) = pipeline {
Err(ShellError::IOError(error.to_string()))
Err(ShellError::IOError {
msg: error.to_string(),
})
} else {
Ok(pipeline)
}
@ -63,7 +67,9 @@ fn eval_source2(
);
if let Some(err) = working_set.parse_errors.first() {
return Err(ShellError::IOError(err.to_string()));
return Err(ShellError::IOError {
msg: err.to_string(),
});
}
(output, working_set.render())
@ -71,7 +77,9 @@ fn eval_source2(
// We need to merge different info other wise things like PIPEs etc will not work.
if let Err(err) = engine_state.merge_delta(delta) {
return Err(ShellError::IOError(err.to_string()));
return Err(ShellError::IOError {
msg: err.to_string(),
});
}
// eval_block outputs all expressions except the last to STDOUT;

View File

@ -32,7 +32,9 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Valu
Value::string(path.to_string_lossy(), span)
} else {
Value::error(
ShellError::IOError("Could not get config directory".into()),
ShellError::IOError {
msg: "Could not get config directory".into(),
},
span,
)
},
@ -49,7 +51,9 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Valu
Value::string(path.to_string_lossy(), span)
} else {
Value::error(
ShellError::IOError("Could not get config directory".into()),
ShellError::IOError {
msg: "Could not get config directory".into(),
},
span,
)
},
@ -66,7 +70,9 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Valu
Value::string(path.to_string_lossy(), span)
} else {
Value::error(
ShellError::IOError("Could not find environment path".into()),
ShellError::IOError {
msg: "Could not find environment path".into(),
},
span,
)
},
@ -88,7 +94,9 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Valu
Value::string(canon_hist_path.to_string_lossy(), span)
} else {
Value::error(
ShellError::IOError("Could not find history path".into()),
ShellError::IOError {
msg: "Could not find history path".into(),
},
span,
)
},
@ -103,7 +111,9 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Valu
Value::string(canon_login_path.to_string_lossy(), span)
} else {
Value::error(
ShellError::IOError("Could not find login shell path".into()),
ShellError::IOError {
msg: "Could not find login shell path".into(),
},
span,
)
},
@ -123,7 +133,9 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Valu
Value::string(plugin_path.to_string_lossy(), span)
} else {
Value::error(
ShellError::IOError("Could not get plugin signature location".into()),
ShellError::IOError {
msg: "Could not get plugin signature location".into(),
},
span,
)
},
@ -136,7 +148,12 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Valu
let canon_home_path = canonicalize_path(engine_state, &path);
Value::string(canon_home_path.to_string_lossy(), span)
} else {
Value::error(ShellError::IOError("Could not get home path".into()), span)
Value::error(
ShellError::IOError {
msg: "Could not get home path".into(),
},
span,
)
},
);
@ -178,7 +195,9 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Valu
Value::string(current_exe.to_string_lossy(), span)
} else {
Value::error(
ShellError::IOError("Could not get current executable path".to_string()),
ShellError::IOError {
msg: "Could not get current executable path".to_string(),
},
span,
)
},

View File

@ -765,7 +765,11 @@ pub enum ShellError {
/// This is a generic error. Refer to the specific error message for further details.
#[error("I/O interrupted")]
#[diagnostic(code(nu::shell::io_interrupted))]
IOInterrupted(String, #[label("{0}")] Span),
IOInterrupted {
msg: String,
#[label("{msg}")]
span: Span,
},
/// An I/O operation failed.
///
@ -773,8 +777,8 @@ pub enum ShellError {
///
/// This is a generic error. Refer to the specific error message for further details.
#[error("I/O error")]
#[diagnostic(code(nu::shell::io_error), help("{0}"))]
IOError(String),
#[diagnostic(code(nu::shell::io_error), help("{msg}"))]
IOError { msg: String },
/// An I/O operation failed.
///
@ -783,7 +787,11 @@ pub enum ShellError {
/// This is a generic error. Refer to the specific error message for further details.
#[error("I/O error")]
#[diagnostic(code(nu::shell::io_error))]
IOErrorSpanned(String, #[label("{0}")] Span),
IOErrorSpanned {
msg: String,
#[label("{msg}")]
span: Span,
},
/// Permission for an operation was denied.
///
@ -792,7 +800,11 @@ pub enum ShellError {
/// This is a generic error. Refer to the specific error message for further details.
#[error("Permission Denied")]
#[diagnostic(code(nu::shell::permission_denied))]
PermissionDeniedError(String, #[label("{0}")] Span),
PermissionDeniedError {
msg: String,
#[label("{msg}")]
span: Span,
},
/// Out of memory.
///
@ -801,7 +813,11 @@ pub enum ShellError {
/// This is a generic error. Refer to the specific error message for further details.
#[error("Out of memory")]
#[diagnostic(code(nu::shell::out_of_memory))]
OutOfMemoryError(String, #[label("{0}")] Span),
OutOfMemoryError {
msg: String,
#[label("{msg}")]
span: Span,
},
/// Tried to `cd` to a path that isn't a directory.
///
@ -810,7 +826,10 @@ pub enum ShellError {
/// Make sure the path is a directory. It currently exists, but is of some other type, like a file.
#[error("Cannot change to directory")]
#[diagnostic(code(nu::shell::cannot_cd_to_directory))]
NotADirectory(#[label("is not a directory")] Span),
NotADirectory {
#[label("is not a directory")]
span: Span,
},
/// Attempted to perform an operation on a directory that doesn't exist.
///
@ -818,8 +837,12 @@ pub enum ShellError {
///
/// Make sure the directory in the error message actually exists before trying again.
#[error("Directory not found")]
#[diagnostic(code(nu::shell::directory_not_found), help("{1} does not exist"))]
DirectoryNotFound(#[label("directory not found")] Span, String),
#[diagnostic(code(nu::shell::directory_not_found), help("{dir} does not exist"))]
DirectoryNotFound {
dir: String,
#[label("directory not found")]
span: Span,
},
/// Attempted to perform an operation on a directory that doesn't exist.
///
@ -828,7 +851,11 @@ pub enum ShellError {
/// Make sure the directory in the error message actually exists before trying again.
#[error("Directory not found")]
#[diagnostic(code(nu::shell::directory_not_found_custom))]
DirectoryNotFoundCustom(String, #[label("{0}")] Span),
DirectoryNotFoundCustom {
msg: String,
#[label("{msg}")]
span: Span,
},
/// The requested move operation cannot be completed. This is typically because both paths exist,
/// but are of different types. For example, you might be trying to overwrite an existing file with
@ -858,7 +885,11 @@ pub enum ShellError {
#[error("Move not possible")]
#[diagnostic(code(nu::shell::move_not_possible_single))]
// NOTE: Currently not actively used.
MoveNotPossibleSingle(String, #[label("{0}")] Span),
MoveNotPossibleSingle {
msg: String,
#[label("{msg}")]
span: Span,
},
/// Failed to create either a file or directory.
///
@ -867,7 +898,11 @@ pub enum ShellError {
/// This is a fairly generic error. Refer to the specific error message for further details.
#[error("Create not possible")]
#[diagnostic(code(nu::shell::create_not_possible))]
CreateNotPossible(String, #[label("{0}")] Span),
CreateNotPossible {
msg: String,
#[label("{msg}")]
span: Span,
},
/// Changing the access time ("atime") of this file is not possible.
///
@ -1201,19 +1236,25 @@ impl ShellError {
impl From<std::io::Error> for ShellError {
fn from(input: std::io::Error) -> ShellError {
ShellError::IOError(format!("{input:?}"))
ShellError::IOError {
msg: format!("{input:?}"),
}
}
}
impl std::convert::From<Box<dyn std::error::Error>> for ShellError {
fn from(input: Box<dyn std::error::Error>) -> ShellError {
ShellError::IOError(input.to_string())
ShellError::IOError {
msg: input.to_string(),
}
}
}
impl From<Box<dyn std::error::Error + Send + Sync>> for ShellError {
fn from(input: Box<dyn std::error::Error + Send + Sync>) -> ShellError {
ShellError::IOError(format!("{input:?}"))
ShellError::IOError {
msg: format!("{input:?}"),
}
}
}

View File

@ -30,7 +30,7 @@ impl<R: Read> Iterator for BufferedReader<R> {
Some(Ok(result))
}
}
Err(e) => Some(Err(ShellError::IOError(e.to_string()))),
Err(e) => Some(Err(ShellError::IOError { msg: e.to_string() })),
}
}
}