nu_cmd_lang/core_commands/
error_make.rs

1use 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// Most of the parsing happens with FromValue
117#[derive(Debug, Default, Clone, FromValue, IntoValue)]
118struct Error {
119    msg: String,
120    labels: Option<Labels>,
121    // TODO: Deprecate and clean up the parsing
122    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// Labels are parse separately because they could be vectors or single values.
148#[derive(Debug, Default, Clone, FromValue, IntoValue)]
149struct Label {
150    text: String,
151    span: Option<Span>,
152}
153
154// Optional list or singleton label
155#[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            // Main error that will be returned
186            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            // Recurse into the inner errors
193            for inner in v.inner.unwrap_or_default() {
194                error = error.with_inner(make_other_error(&inner, Some(inner.span())));
195            }
196            // TODO: This could be enabled before `label` is set to be deprecated
197            // if !v.label.unwrap_or_default().list.is_empty() {
198            //     error = error.with_inner(make_other_error(
199            //         &Error {
200            //             msg: "`label` is going to be deprecated. Use `labels` instead.".into(),
201            //             ..Error::default()
202            //         }
203            //         .into_value(value.span()),
204            //         throw_span,
205            //     ));
206            // };
207            error.code = v.code;
208            error.help = v.help;
209            error.url = v.url;
210            error.into()
211        }
212    }
213}