typeline_core/
typeline_error.rs

1use std::{
2    borrow::Cow,
3    fmt::{Debug, Display},
4    sync::Arc,
5};
6
7use thiserror::Error;
8
9use crate::{
10    chain::ChainId,
11    cli::{
12        call_expr::Span, CliArgumentError, MissingArgumentsError,
13        PrintInfoAndExitError,
14    },
15    context::SessionData,
16    operators::{
17        errors::{
18            OperatorApplicationError, OperatorCreationError,
19            OperatorSetupError,
20        },
21        operator::OperatorId,
22    },
23    options::{
24        chain_settings::SettingConversionError,
25        session_setup::{SessionSetupData, SetupOptions},
26        setting::{
27            CliArgIdx, SettingReassignmentError,
28            SETTING_REASSIGNMENT_ERROR_MESSAGE,
29        },
30    },
31    record_data::{field_data::FieldValueRepr, field_value::FieldValue},
32    utils::{index_slice::IndexSlice, indexing_type::IndexingType},
33};
34
35#[derive(Error, Debug, Clone, PartialEq, Eq)]
36#[error("in chain {chain_id}: {message}")]
37pub struct ChainSetupError {
38    pub chain_id: ChainId,
39    pub message: Cow<'static, str>,
40}
41
42impl ChainSetupError {
43    pub fn new(message: &'static str, chain_id: ChainId) -> Self {
44        Self {
45            message: message.into(),
46            chain_id,
47        }
48    }
49}
50
51#[derive(Error, Debug, Clone, PartialEq, Eq)]
52#[error("{message}")]
53pub struct ReplDisabledError {
54    pub message: &'static str,
55    pub span: Span,
56}
57
58#[derive(Error, Debug, Clone, PartialEq)]
59pub struct CollectTypeMissmatch {
60    pub index: usize,
61    pub expected: FieldValueRepr,
62    pub got: FieldValue,
63}
64
65impl Display for CollectTypeMissmatch {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        f.write_fmt(format_args!(
68            "expected {}, got {}",
69            self.expected,
70            self.got.kind()
71        ))
72    }
73}
74
75#[derive(Error, Debug, Clone, PartialEq)]
76pub enum TypelineError {
77    #[error(transparent)]
78    PrintInfoAndExitError(#[from] PrintInfoAndExitError),
79
80    #[error(transparent)]
81    ReplDisabledError(#[from] ReplDisabledError),
82
83    #[error(transparent)]
84    ArgumentReassignmentError(#[from] SettingReassignmentError),
85
86    #[error(transparent)]
87    MissingArgumentsError(#[from] MissingArgumentsError),
88
89    #[error(transparent)]
90    CliArgumentError(#[from] CliArgumentError),
91
92    #[error(transparent)]
93    OperationCreationError(#[from] OperatorCreationError),
94
95    #[error(transparent)]
96    OperationSetupError(#[from] OperatorSetupError),
97
98    #[error(transparent)]
99    ChainSetupError(#[from] ChainSetupError),
100
101    #[error(transparent)]
102    OperationApplicationError(#[from] OperatorApplicationError),
103
104    #[error(transparent)]
105    CollectTypeMissmatch(#[from] CollectTypeMissmatch),
106
107    #[error(transparent)]
108    SettingConversionError(#[from] SettingConversionError),
109}
110#[derive(Error, Debug, Clone)]
111#[error("{contextualized_message}")]
112pub struct ContextualizedTypelineError {
113    pub contextualized_message: String,
114    pub err: TypelineError,
115}
116
117impl ContextualizedTypelineError {
118    pub fn from_typeline_error(
119        err: TypelineError,
120        args: Option<&IndexSlice<CliArgIdx, Vec<u8>>>,
121        cli_opts: Option<&SetupOptions>,
122        setup_data: Option<&SessionSetupData>,
123        sess: Option<&SessionData>,
124    ) -> Self {
125        Self {
126            contextualized_message: err
127                .contextualize_message(args, cli_opts, setup_data, sess),
128            err,
129        }
130    }
131}
132
133impl From<ContextualizedTypelineError> for TypelineError {
134    fn from(value: ContextualizedTypelineError) -> Self {
135        value.err
136    }
137}
138
139impl From<Arc<OperatorApplicationError>> for TypelineError {
140    fn from(value: Arc<OperatorApplicationError>) -> Self {
141        TypelineError::from(
142            Arc::try_unwrap(value).unwrap_or_else(|v| (*v).clone()),
143        )
144    }
145}
146
147pub fn result_into<T, E, IntoE: Into<E>>(
148    result: Result<T, IntoE>,
149) -> Result<T, E> {
150    match result {
151        Ok(v) => Ok(v),
152        Err(e) => Err(e.into()),
153    }
154}
155fn contextualize_span(
156    msg: &str,
157    args: Option<&IndexSlice<CliArgIdx, Vec<u8>>>,
158    span: Span,
159    skipped_first_cli_arg: bool,
160) -> String {
161    let cli_arg_offset =
162        CliArgIdx::from_usize(usize::from(!skipped_first_cli_arg));
163    match span {
164        Span::CliArg {
165            start,
166            end,
167            offset_start,
168            offset_end,
169        } => {
170            if start == end {
171                if let Some(args) = args {
172                    format!(
173                        "in cli arg {} `{}`: {msg}",
174                        start + cli_arg_offset,
175                        String::from_utf8_lossy(
176                            &args[start]
177                                [offset_start as usize..offset_end as usize]
178                        ),
179                    )
180                } else {
181                    format!("in cli arg {}: {msg}", start + cli_arg_offset,)
182                }
183            } else {
184                format!(
185                    "in cli args {}:-{}: {msg}",
186                    start + cli_arg_offset,
187                    end + cli_arg_offset,
188                )
189            }
190        }
191        Span::MacroExpansion { op_id } => {
192            format!("in macro expansion of op {op_id}: {msg}")
193        }
194        Span::Generated | Span::Builtin | Span::FlagsObject => msg.to_string(),
195        Span::EnvVar {
196            compile_time,
197            var_name,
198        } => {
199            format!(
200                "in{}environment variable `{var_name}`: {msg}",
201                if compile_time { " compile-time " } else { " " }
202            )
203        }
204    }
205}
206
207fn was_first_cli_arg_skipped(
208    cli_opts: Option<&SetupOptions>,
209    setup_data: Option<&SessionSetupData>,
210    sess: Option<&SessionData>,
211) -> bool {
212    cli_opts.map(|o| o.skip_first_cli_arg).unwrap_or(
213        setup_data
214            .map(|o| o.setup_settings.skipped_first_cli_arg)
215            .unwrap_or(
216                sess.map(|s| s.settings.skipped_first_cli_arg)
217                    .unwrap_or(true),
218            ),
219    )
220}
221
222fn contextualize_op_id(
223    msg: &str,
224    op_id: OperatorId,
225    args: Option<&IndexSlice<CliArgIdx, Vec<u8>>>,
226    cli_opts: Option<&SetupOptions>,
227    setup_data: Option<&SessionSetupData>,
228    sess: Option<&SessionData>,
229) -> String {
230    let span = sess.map(|sess| sess.operator_bases[op_id].span);
231    if let (Some(args), Some(span)) = (args, span) {
232        let first_arg_skipped =
233            was_first_cli_arg_skipped(cli_opts, setup_data, sess);
234        contextualize_span(msg, Some(args), span, first_arg_skipped)
235    } else if let Some(sess) = sess {
236        let op_base = &sess.operator_bases[op_id];
237        let op_data = &sess.operator_data[op_base.op_data_id];
238        // TODO: stringify chain id
239        format!(
240            "in op {} '{}' of chain {}: {}",
241            // TODO: better message for aggregation members
242            op_base.offset_in_chain.base_chain_offset(sess),
243            op_data.default_name(),
244            op_base.chain_id,
245            msg
246        )
247    } else {
248        format!("in global op id {op_id}: {msg}")
249    }
250}
251
252impl TypelineError {
253    // PERF: could avoid allocations by taking a &impl Write
254    pub fn contextualize_message(
255        &self,
256        args: Option<&IndexSlice<CliArgIdx, Vec<u8>>>,
257        cli_opts: Option<&SetupOptions>,
258        setup_data: Option<&SessionSetupData>,
259        sess: Option<&SessionData>,
260    ) -> String {
261        let first_arg_skipped =
262            was_first_cli_arg_skipped(cli_opts, setup_data, sess);
263        let args_gathered = args
264            .or_else(|| setup_data.and_then(|o| o.cli_args.as_deref()))
265            .or_else(|| sess.and_then(|sess| sess.cli_args.as_deref()));
266        match self {
267            TypelineError::CliArgumentError(e) => contextualize_span(
268                &e.message,
269                args_gathered,
270                e.span,
271                first_arg_skipped,
272            ),
273            TypelineError::ArgumentReassignmentError(e) => contextualize_span(
274                SETTING_REASSIGNMENT_ERROR_MESSAGE,
275                args_gathered,
276                e.reassignment_span,
277                first_arg_skipped,
278            ),
279            TypelineError::ReplDisabledError(e) => contextualize_span(
280                e.message,
281                args_gathered,
282                e.span,
283                first_arg_skipped,
284            ),
285            TypelineError::ChainSetupError(e) => e.to_string(),
286            TypelineError::OperationCreationError(e) => e.message.to_string(),
287            TypelineError::SettingConversionError(e) => e.message.to_string(),
288            TypelineError::OperationSetupError(e) => contextualize_op_id(
289                &e.message,
290                e.op_id,
291                args_gathered,
292                cli_opts,
293                setup_data,
294                sess,
295            ),
296            TypelineError::OperationApplicationError(e) => {
297                contextualize_op_id(
298                    e.message(),
299                    e.op_id(),
300                    args_gathered,
301                    cli_opts,
302                    setup_data,
303                    sess,
304                )
305            }
306            TypelineError::PrintInfoAndExitError(e) => e.to_string(),
307            TypelineError::MissingArgumentsError(e) => e.to_string(),
308            TypelineError::CollectTypeMissmatch(e) => e.to_string(),
309        }
310    }
311}