nu_cmd_lang/core_commands/
error_make.rs1use nu_engine::command_prelude::*;
2use nu_protocol::{FromValue, LabeledError};
3
4#[derive(Clone)]
5pub struct ErrorMake;
6
7impl Command for ErrorMake {
8 fn name(&self) -> &str {
9 "error make"
10 }
11
12 fn signature(&self) -> Signature {
13 Signature::build("error make")
14 .input_output_types(vec![(Type::Nothing, Type::Error)])
15 .required(
16 "error_struct",
17 SyntaxShape::Record(vec![]),
18 "The error to create.",
19 )
20 .switch(
21 "unspanned",
22 "remove the origin label from the error",
23 Some('u'),
24 )
25 .category(Category::Core)
26 }
27
28 fn description(&self) -> &str {
29 "Create an error."
30 }
31
32 fn extra_description(&self) -> &str {
33 r#"Errors are defined by an `error_record`, which is a record with a specific
34structure. (`*`) indicates a required key:
35
36 * `msg: string` (`*`)
37 * `code: string`
38 * `labels: oneof<table, record>`
39 * `help: string`
40 * `url: string`
41 * `inner: table`
42
43The `labels` key allow for placing arrows to points in the code, optionally
44using `span` to choose where it points (see `metadata`). `label` can be a table
45(list of records) or a single record. There is an example of both in the
46examples. Each record has the following structure:
47
48 * `text: string` (`*`)
49 * `span: record<start: int end: int>`
50
51The `inner` table takes a list of `error_struct` records, and can be used to
52have detail the errors that happened in a previous `try {} catch {}` statement
53or can be manually created. To use them from a `catch` statement, see the
54example below."#
55 }
56
57 fn search_terms(&self) -> Vec<&str> {
58 vec!["panic", "crash", "throw"]
59 }
60
61 fn run(
62 &self,
63 engine_state: &EngineState,
64 stack: &mut Stack,
65 call: &Call,
66 _input: PipelineData,
67 ) -> Result<PipelineData, ShellError> {
68 let value: Value = call.req(engine_state, stack, 0)?;
69
70 let throw_span = if call.has_flag(engine_state, stack, "unspanned")? {
71 None
72 } else {
73 Some(call.head)
74 };
75
76 Err(make_other_error(&value, throw_span))
77 }
78
79 fn examples(&self) -> Vec<Example<'_>> {
80 vec![
81 Example {
82 description: "Create a simple custom error",
83 example: r#"error make {msg: "my custom error message"}"#,
84 result: None,
85 },
86 Example {
87 description: "Create a complex error for a custom command that shows a full `error_struct`",
88 example: r#"def foo [x] {
89 error make {
90 msg: "this is fishy"
91 code: "my::error" # optional error type to use
92 labels: { # optional, a table or single record
93 text: "fish right here" # Required if $.labels exists
94 # use (metadata $var).span to get the {start: x end: y} of the variable
95 span: (metadata $x).span # optional
96 }
97 help: "something to tell the user as help" # optional
98 url: "https://nushell.sh" # optional
99 }
100 }"#,
101 result: None,
102 },
103 Example {
104 description: "Create a nested error from a try/catch statement with multiple labels",
105 example: r#"try {
106 error make {msg: "foo" labels: [{text: one} {text: two}]}
107 } catch {|err|
108 error make {msg: "bar", inner: [($err.json | from json)]}
109 }"#,
110 result: None,
111 },
112 ]
113 }
114}
115
116#[derive(Debug, Default, Clone, FromValue, IntoValue)]
118struct Error {
119 msg: String,
120 labels: Option<Labels>,
121 label: Option<Labels>,
123 inner: Option<Vec<Value>>,
124 help: Option<String>,
125 url: Option<String>,
126 code: Option<String>,
127}
128
129impl Error {
130 pub fn combined_labels(&self, span: Option<Span>) -> Vec<Label> {
131 let included = [
132 self.labels.clone().unwrap_or_default().list,
133 self.label.clone().unwrap_or_default().list,
134 ]
135 .concat();
136 if included.is_empty() {
137 vec![Label {
138 text: "originates from here".into(),
139 span,
140 }]
141 } else {
142 included
143 }
144 }
145}
146
147#[derive(Debug, Default, Clone, FromValue, IntoValue)]
149struct Label {
150 text: String,
151 span: Option<Span>,
152}
153
154#[derive(Debug, Default, Clone, IntoValue)]
156struct Labels {
157 list: Vec<Label>,
158}
159
160impl FromValue for Labels {
161 fn from_value(v: Value) -> std::result::Result<Self, ShellError> {
162 match v.get_type() {
163 Type::Record(_) => match Label::from_value(v) {
164 Ok(o) => Ok(Self { list: vec![o] }),
165 Err(o) => Err(o),
166 },
167 Type::Table(_) => match Vec::<Label>::from_value(v) {
168 Ok(o) => Ok(Self { list: o }),
169 Err(o) => Err(o),
170 },
171 _ => Err(ShellError::CantConvert {
172 to_type: Self::expected_type().to_string(),
173 from_type: v.get_type().to_string(),
174 span: v.span(),
175 help: None,
176 }),
177 }
178 }
179}
180
181fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
182 match Error::from_value(value.clone()) {
183 Err(e) => e,
184 Ok(v) => {
185 let mut error = LabeledError::new(v.msg.clone());
187 if let Some(ts) = throw_span {
188 for label in v.combined_labels(throw_span) {
189 error = error.with_label(label.text, label.span.unwrap_or(ts));
190 }
191 }
192 for inner in v.inner.unwrap_or_default() {
194 error = error.with_inner(make_other_error(&inner, Some(inner.span())));
195 }
196 error.code = v.code;
208 error.help = v.help;
209 error.url = v.url;
210 error.into()
211 }
212 }
213}