Skip to main content

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![(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            // External error with src code and url
215            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            // External error with src code
231            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            // Normal error
245            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            // Error error with a raw error value somewhere
267            ErrorInfo { raw: Some(v), .. } => ShellError::from_value(v).unwrap_or_else(|e| e),
268        }
269    }
270}