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 format!(
240 "in op {} '{}' of chain {}: {}",
241 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 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}