nu_cmd_lang/core_commands/
error_make.rs1use nu_engine::command_prelude::*;
2use nu_protocol::{ErrorLabel, ErrorSource, FromValue, IntoValue, 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 .category(Category::Core)
15 .input_output_types(vec![
16 (Type::Nothing, Type::Error),
18 (Type::String, Type::Error),
20 (Type::record(), Type::Error),
23 ])
24 .optional(
25 "error_struct",
26 SyntaxShape::OneOf(vec![SyntaxShape::Record(vec![]), SyntaxShape::String]),
27 "The error to create.",
28 )
29 .switch("unspanned", "remove the labels from the error", Some('u'))
30 }
31
32 fn description(&self) -> &str {
33 "Create an error."
34 }
35
36 fn extra_description(&self) -> &str {
37 "Use either as a command with an `error_struct` or string as an input. The
38`error_struct` is detailed below:
39
40 * `msg: string` (required)
41 * `code: string`
42 * `labels: table<error_label>`
43 * `help: string`
44 * `url: string`
45 * `inner: table<error_struct>`
46 * `src: src_record`
47
48The `error_label` should contain the following keys:
49
50 * `text: string`
51 * `span: record<start: int end: int>`
52
53External errors (referencing external sources, not the default nu spans) are
54created using the `src` column with the `src_record` record. This only changes
55where the labels are placed. For this, the `code` key is ignored, and will
56always be `nu::shell::outside`. Errors cannot use labels that reference both
57inside and outside sources, to do that use an `inner` error.
58
59 * `name: string` - name of the source
60 * `text: string` - the raw text to place the spans in
61 * `path: string` - a file path to place the spans in
62
63Errors can be chained together using the `inner` key, and multiple spans can be
64specified to give more detailed error outputs.
65
66If a string is passed it will be the `msg` part of the `error_struct`.
67
68Errors can also be chained using `try {} catch {}`, allowing for related errors
69to be printed out more easily. The code block for `catch` passes a record of the
70`try` block's error into the catch block, which can be used in `error make`
71either as the input or as an argument. These will be added as `inner` errors to
72the most recent `error make`."
73 }
74
75 fn examples(&self) -> Vec<Example<'_>> {
76 vec![
77 Example {
78 description: "Create a simple, default error",
79 example: "error make",
80 result: None,
81 },
82 Example {
83 description: "Create a simple error from a string",
84 example: "error make 'my error message'",
85 result: None,
86 },
87 Example {
88 description: "Create a simple error from an `error_struct` record",
89 example: "error make {msg: 'my error message'}",
90 result: None,
91 },
92 Example {
93 description: "A complex error utilizing spans and inners",
94 example: r#"def foo [x: int, y: int] {
95 let z = $x + $y
96 error make {
97 msg: "an error for foo just occurred"
98 labels: [
99 {text: "one" span: (metadata $x).span}
100 {text: "two" span: (metadata $y).span}
101 ]
102 help: "some help for the user"
103 inner: [
104 {msg: "an inner error" labels: [{text: "" span: (metadata $y).span}]}
105 ]
106 }
107 }"#,
108 result: None,
109 },
110 Example {
111 description: "Chain errors using a pipeline",
112 example: r#"try {error make "foo"} catch {error make "bar"}"#,
113 result: None,
114 },
115 Example {
116 description: "Chain errors using arguments (note the extra command in `catch`)",
117 example: r#"try {
118 error make "foo"
119 } catch {|err|
120 print 'We got an error that will be chained!'
121 error make {msg: "bar" inner: [$err]}
122 }"#,
123 result: None,
124 },
125 ]
126 }
127
128 fn run(
129 &self,
130 engine_state: &EngineState,
131 stack: &mut Stack,
132 call: &Call,
133 input: PipelineData,
134 ) -> Result<PipelineData, ShellError> {
135 let value = match call.opt(engine_state, stack, 0) {
136 Ok(Some(v @ Value::Record { .. } | v @ Value::String { .. })) => v,
137 Ok(_) => Value::string("originates from here", call.head),
138 Err(e) => return Err(e),
139 };
140 let show_labels: bool = !call.has_flag(engine_state, stack, "unspanned")?;
141
142 let inners = match ErrorInfo::from_value(input.into_value(call.head)?) {
143 Ok(v) => vec![v.into_value(call.head)],
144 Err(_) => vec![],
145 };
146
147 Err(match (inners, value) {
148 (inner, Value::String { val, .. }) => ErrorInfo {
149 msg: val,
150 inner,
151 ..ErrorInfo::default()
152 }
153 .labeled(call.head, show_labels),
154 (
155 inner,
156 Value::Record {
157 val, internal_span, ..
158 },
159 ) => {
160 let mut ei = ErrorInfo::from_value((*val).clone().into_value(internal_span))?;
161 ei.inner = [ei.inner, inner].concat();
162
163 ei.labeled(internal_span, show_labels)
164 }
165 (_, Value::Error { error, .. }) => *error,
166 _ => todo!(),
167 })
168 }
169}
170
171#[derive(Debug, Clone, IntoValue, FromValue)]
172struct ErrorInfo {
173 msg: String,
174 code: Option<String>,
175 help: Option<String>,
176 url: Option<String>,
177 #[nu_value(default)]
178 labels: Vec<ErrorLabel>,
179 label: Option<ErrorLabel>,
180 #[nu_value(default)]
181 inner: Vec<Value>,
182 raw: Option<Value>,
183 src: Option<ErrorSource>,
184}
185
186impl Default for ErrorInfo {
187 fn default() -> Self {
188 Self {
189 msg: "Originates from here".into(),
190 code: Some("nu::shell::error".into()),
191 help: None,
192 url: None,
193 labels: Vec::default(),
194 label: None,
195 inner: Vec::default(),
196 raw: None,
197 src: None,
198 }
199 }
200}
201
202impl ErrorInfo {
203 pub fn labels(self) -> Vec<ErrorLabel> {
204 match self.label {
205 None => self.labels,
206 Some(label) => [self.labels, vec![label]].concat(),
207 }
208 }
209 pub fn labeled(self, span: Span, show_labels: bool) -> ShellError {
210 let inner: Vec<ShellError> = self
211 .inner
212 .clone()
213 .into_iter()
214 .map(|i| match ErrorInfo::from_value(i) {
215 Ok(e) => e.labeled(span, show_labels),
216 Err(err) => err,
217 })
218 .collect();
219 let labels = self.clone().labels();
220
221 match self {
222 ErrorInfo {
224 src: Some(src),
225 url: Some(url),
226 msg,
227 help,
228 raw: None,
229 ..
230 } => ShellError::OutsideSource {
231 src: src.into(),
232 labels: labels.into_iter().map(|l| l.into()).collect(),
233 msg,
234 url,
235 help,
236 inner,
237 },
238 ErrorInfo {
240 src: Some(src),
241 msg,
242 help,
243 raw: None,
244 ..
245 } => ShellError::OutsideSourceNoUrl {
246 src: src.into(),
247 labels: labels.into_iter().map(|l| l.into()).collect(),
248 msg,
249 help,
250 inner,
251 },
252 ei @ ErrorInfo {
254 src: None,
255 raw: None,
256 ..
257 } => LabeledError {
258 labels: match (show_labels, labels.as_slice()) {
259 (true, []) => vec![ErrorLabel {
260 text: "".into(),
261 span,
262 }],
263 (true, labels) => labels.to_vec(),
264 (false, _) => vec![],
265 }
266 .into(),
267 msg: ei.msg,
268 code: ei.code,
269 url: ei.url,
270 help: ei.help,
271 inner: inner.into(),
272 }
273 .into(),
274 ErrorInfo { raw: Some(v), .. } => ShellError::from_value(v).unwrap_or_else(|e| e),
276 }
277 }
278}