1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
 * AquaVM Workflow Engine
 *
 * Copyright (C) 2024 Fluence DAO
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation version 3 of the
 * License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use super::ErrorAffectable;
use super::Joinable;
use crate::execution_step::execution_context::errors::StreamMapError;
use crate::execution_step::execution_context::ErrorObjectError;
use crate::execution_step::lambda_applier::LambdaError;
use crate::JValue;
use crate::ToErrorCode;

use strum::IntoEnumIterator;
use strum_macros::EnumDiscriminants;
use strum_macros::EnumIter;
use thiserror::Error as ThisError;

use std::rc::Rc;

/// Catchable errors arisen during AIR script execution. Catchable here means that these errors
/// could be handled by a xor instruction and their error_code could be used in a match
/// instruction.
#[derive(ThisError, EnumDiscriminants, Debug, Clone)]
#[strum_discriminants(derive(EnumIter))]
pub enum CatchableError {
    /// An error is occurred while calling local service via call_service.
    #[error("Local service error, ret_code is {0}, error message is '{1}'")]
    LocalServiceError(i32, Rc<String>),

    /// This error type is produced by a match to notify xor that compared values aren't equal.
    #[error("compared values do not match")]
    MatchValuesNotEqual,

    /// This error type is produced by a mismatch to notify xor that compared values aren't equal.
    #[error("compared values do not mismatch")]
    MismatchValuesEqual,

    /// Variable with such a name wasn't defined during AIR script execution.
    /// This error type is used in order to support the join behaviour and
    /// it's ok if some variable hasn't been defined yet, due to the par nature of AIR.
    #[error("variable with name '{0}' wasn't defined during script execution")]
    VariableNotFound(String),

    /// Provided JValue has incompatible type with a requested one.
    #[error(
        "expected JValue type '{expected_value_type}' for the variable `{variable_name}`, but got '{actual_value}'"
    )]
    IncompatibleJValueType {
        variable_name: String,
        actual_value: JValue,
        expected_value_type: &'static str,
    },

    /// A fold instruction must iterate over array value.
    #[error("expression '{1}' returned non-array value '{0}' for fold iterable")]
    FoldIteratesOverNonArray(JValue, String),

    /// This error type is produced by a fail instruction.
    #[error("fail with '{error}' is used without corresponding xor")]
    UserError { error: JValue },

    /// An error occurred while trying to apply lambda to a value.
    #[error(transparent)]
    LambdaApplierError(#[from] LambdaError),

    /// This error type is produced by a fail instruction that tries to throw a scalar that have inappropriate type.
    #[error(transparent)]
    InvalidErrorObjectError(#[from] ErrorObjectError),

    /// A new with this variable name was met and right after that it was accessed
    /// that is prohibited.
    #[error("variable with name '{0}' was cleared by new and then wasn't set")]
    VariableWasNotInitializedAfterNew(String),

    /// This error type is occurred when the length functor applied to a value of non-array type.
    #[error("the length functor could applied only to an array-like value, but it's applied to '{0}'")]
    LengthFunctorAppliedToNotArray(JValue),

    /// Call gets non-string JValue resolving triplet parts.
    #[error("call cannot resolve non-String triplet variable part `{variable_name}` with value '{actual_value}'")]
    NonStringValueInTripletResolution {
        variable_name: String,
        actual_value: JValue,
    },

    /// Stream map related errors.
    #[error(transparent)]
    StreamMapError(#[from] StreamMapError),
}

impl From<LambdaError> for Rc<CatchableError> {
    fn from(e: LambdaError) -> Self {
        Rc::new(CatchableError::LambdaApplierError(e))
    }
}

impl ToErrorCode for Rc<CatchableError> {
    fn to_error_code(&self) -> i64 {
        self.as_ref().to_error_code()
    }
}

impl ToErrorCode for CatchableError {
    fn to_error_code(&self) -> i64 {
        use crate::utils::CATCHABLE_ERRORS_START_ID;
        crate::generate_to_error_code!(self, CatchableError, CATCHABLE_ERRORS_START_ID)
    }
}

impl ErrorAffectable for CatchableError {
    fn affects_last_error(&self) -> bool {
        !matches!(
            self,
            CatchableError::MatchValuesNotEqual | CatchableError::MismatchValuesEqual
        )
    }

    fn affects_error(&self) -> bool {
        true
    }
}

macro_rules! log_join {
    ($($args:tt)*) => {
        log::trace!(target: air_log_targets::JOIN_BEHAVIOUR, $($args)*)
    }
}

#[rustfmt::skip::macros(log_join)]
impl Joinable for CatchableError {
    /// Returns true, if supplied error is related to variable not found errors type.
    /// Print log if this is joinable error type.
    fn is_joinable(&self) -> bool {
        use CatchableError::*;

        match self {
            VariableNotFound(var_name) => {
                log_join!("  waiting for an argument with name '{}'", var_name);
                true
            }
            _ => false,
        }
    }
}