nu_cmd_lang/core_commands/
error_make.rs

1use 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                // original nothing input type
17                (Type::Nothing, Type::Error),
18                // "foo" | error make
19                (Type::String, Type::Error),
20                // {...} | error make
21                // try {...} catch {error make}
22                (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            // External error with src code and url
223            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            // External error with src code
239            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            // Normal error
253            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            // Error error with a raw error value somewhere
275            ErrorInfo { raw: Some(v), .. } => ShellError::from_value(v).unwrap_or_else(|e| e),
276        }
277    }
278}