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