mirror of
https://github.com/fluencelabs/aquavm
synced 2024-12-04 23:20:18 +00:00
feat(execution-engine): a new :error: runtime attribute according with FLIP-11 [fixes VM-329] (#683)
* feat(execution-engine): a new :error: runtime attribute according with FLIP-11 [fixes VM-329]
This commit is contained in:
parent
2fc1686e19
commit
20afb79e3f
@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use super::ErrorAffectable;
|
||||
use super::Joinable;
|
||||
use super::LastErrorAffectable;
|
||||
use crate::execution_step::execution_context::errors::StreamMapError;
|
||||
use crate::execution_step::execution_context::LastErrorObjectError;
|
||||
use crate::execution_step::lambda_applier::LambdaError;
|
||||
@ -40,11 +40,11 @@ pub enum CatchableError {
|
||||
LocalServiceError(i32, Rc<String>),
|
||||
|
||||
/// This error type is produced by a match to notify xor that compared values aren't equal.
|
||||
#[error("match is used without corresponding xor")]
|
||||
#[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("mismatch is used without corresponding xor")]
|
||||
#[error("compared values do not mismatch")]
|
||||
MismatchValuesEqual,
|
||||
|
||||
/// Variable with such a name wasn't defined during AIR script execution.
|
||||
@ -119,13 +119,17 @@ impl ToErrorCode for CatchableError {
|
||||
}
|
||||
}
|
||||
|
||||
impl LastErrorAffectable for CatchableError {
|
||||
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 {
|
||||
|
@ -14,9 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// This trait is intended to figuring out whether a last error should be set or not.
|
||||
pub(crate) trait LastErrorAffectable {
|
||||
/// This trait controls whether to set %last_error% and :error: or not.
|
||||
pub(crate) trait ErrorAffectable {
|
||||
/// Return true, if this error type affects last error
|
||||
/// (meaning that it should be set after occurring such an error).
|
||||
fn affects_last_error(&self) -> bool;
|
||||
fn affects_error(&self) -> bool;
|
||||
}
|
@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
use super::CatchableError;
|
||||
use super::ErrorAffectable;
|
||||
use super::Joinable;
|
||||
use super::LastErrorAffectable;
|
||||
use super::UncatchableError;
|
||||
use crate::ToErrorCode;
|
||||
|
||||
@ -91,11 +91,18 @@ impl Joinable for ExecutionError {
|
||||
}
|
||||
}
|
||||
|
||||
impl LastErrorAffectable for ExecutionError {
|
||||
impl ErrorAffectable for ExecutionError {
|
||||
fn affects_last_error(&self) -> bool {
|
||||
match self {
|
||||
ExecutionError::Catchable(err) => err.affects_last_error(),
|
||||
ExecutionError::Uncatchable(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn affects_error(&self) -> bool {
|
||||
match self {
|
||||
ExecutionError::Catchable(_) => true,
|
||||
ExecutionError::Uncatchable(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,16 +15,16 @@
|
||||
*/
|
||||
|
||||
mod catchable_errors;
|
||||
mod error_effectable;
|
||||
mod execution_errors;
|
||||
mod joinable;
|
||||
mod last_error_affectable;
|
||||
mod uncatchable_errors;
|
||||
|
||||
pub use catchable_errors::CatchableError;
|
||||
pub use execution_errors::ExecutionError;
|
||||
pub use uncatchable_errors::UncatchableError;
|
||||
|
||||
pub(crate) use error_effectable::ErrorAffectable;
|
||||
pub(crate) use joinable::Joinable;
|
||||
pub(crate) use last_error_affectable::LastErrorAffectable;
|
||||
|
||||
use super::Stream;
|
||||
|
@ -14,12 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use super::ErrorDescriptor;
|
||||
use super::ExecutionCidState;
|
||||
use super::LastError;
|
||||
use super::InstructionError;
|
||||
use super::LastErrorDescriptor;
|
||||
use super::Scalars;
|
||||
use super::StreamMaps;
|
||||
use super::Streams;
|
||||
use crate::execution_step::ErrorAffectable;
|
||||
use crate::execution_step::RcSecurityTetraplet;
|
||||
use crate::ToErrorCode;
|
||||
|
||||
use air_execution_info_collector::InstructionTracker;
|
||||
use air_interpreter_cid::CID;
|
||||
@ -51,9 +55,12 @@ pub(crate) struct ExecutionCtx<'i> {
|
||||
pub(crate) run_parameters: RcRunParameters,
|
||||
|
||||
/// Last error produced by local service.
|
||||
/// None means that there weren't any error.
|
||||
/// There is the special not-an-error value means there was no error.
|
||||
pub(crate) last_error_descriptor: LastErrorDescriptor,
|
||||
|
||||
/// Error produced by some instructions, e.g. call, match, fail.
|
||||
pub(crate) error_descriptor: ErrorDescriptor,
|
||||
|
||||
/// Indicates that previous executed subgraph is complete.
|
||||
/// A subgraph treats as a complete if all subgraph elements satisfy the following rules:
|
||||
/// - at least one of par subgraphs is completed
|
||||
@ -116,8 +123,12 @@ impl<'i> ExecutionCtx<'i> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn last_error(&self) -> &LastError {
|
||||
self.last_error_descriptor.last_error()
|
||||
pub(crate) fn last_error(&self) -> &InstructionError {
|
||||
self.last_error_descriptor.error()
|
||||
}
|
||||
|
||||
pub(crate) fn error(&self) -> &InstructionError {
|
||||
self.error_descriptor.error()
|
||||
}
|
||||
|
||||
pub(crate) fn next_call_request_id(&mut self) -> u32 {
|
||||
@ -150,6 +161,41 @@ impl ExecutionCtx<'_> {
|
||||
pub(crate) fn flush_subgraph_completeness(&mut self) {
|
||||
self.subgraph_completeness = true;
|
||||
}
|
||||
|
||||
// This routine sets %last_error% and :error:.
|
||||
// Most instructions, except Call, Canon, CanonMapScalar does not set :error:.$.peer_id b/c
|
||||
// it would be a non-deterministic peer_id.
|
||||
pub(crate) fn set_errors(
|
||||
&mut self,
|
||||
error: &(impl ErrorAffectable + ToErrorCode + ToString),
|
||||
instruction: &str,
|
||||
tetraplet: Option<RcSecurityTetraplet>,
|
||||
use_tetraplet_and_log_peer_id: bool,
|
||||
) {
|
||||
let last_error_peer_id = match &tetraplet {
|
||||
// use tetraplet if they set, because an error could be propagated from data
|
||||
// (from CallServiceFailed state) and exec_ctx.run_parameters.current_peer_id won't mean
|
||||
// a peer where the error was occurred
|
||||
Some(tetraplet) if use_tetraplet_and_log_peer_id => Some(tetraplet.peer_pk.as_str()),
|
||||
_ => Some(self.run_parameters.current_peer_id.as_str()),
|
||||
};
|
||||
|
||||
self.last_error_descriptor.try_to_set_last_error_from_exec_error(
|
||||
error,
|
||||
instruction,
|
||||
last_error_peer_id,
|
||||
tetraplet.clone(),
|
||||
);
|
||||
|
||||
let peer_id = if use_tetraplet_and_log_peer_id {
|
||||
last_error_peer_id
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.error_descriptor
|
||||
.try_to_set_error_from_exec_error(error, instruction, peer_id, tetraplet.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct for ExecCtx construction.
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2023 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use super::no_error;
|
||||
use super::InstructionError;
|
||||
use crate::execution_step::ErrorAffectable;
|
||||
use crate::execution_step::RcSecurityTetraplet;
|
||||
use crate::ToErrorCode;
|
||||
|
||||
pub(crate) struct ErrorDescriptor {
|
||||
error: InstructionError,
|
||||
}
|
||||
|
||||
impl ErrorDescriptor {
|
||||
pub(crate) fn try_to_set_error_from_exec_error(
|
||||
&mut self,
|
||||
error: &(impl ErrorAffectable + ToErrorCode + ToString),
|
||||
instruction: &str,
|
||||
peer_id_option: Option<&str>,
|
||||
tetraplet: Option<RcSecurityTetraplet>,
|
||||
) {
|
||||
use super::get_instruction_error_from_exec_error;
|
||||
|
||||
if !error.affects_error() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.error = get_instruction_error_from_exec_error(error, instruction, peer_id_option, tetraplet);
|
||||
}
|
||||
|
||||
pub(crate) fn error(&self) -> &InstructionError {
|
||||
&self.error
|
||||
}
|
||||
|
||||
pub(crate) fn clear_error(&mut self) {
|
||||
self.error = no_error();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ErrorDescriptor {
|
||||
fn default() -> Self {
|
||||
let error = no_error();
|
||||
|
||||
Self { error }
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2023 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use air_interpreter_data::Provenance;
|
||||
|
||||
use super::instruction_error_definition::error_from_raw_fields;
|
||||
use super::instruction_error_definition::error_from_raw_fields_w_peerid;
|
||||
use super::InstructionError;
|
||||
use crate::execution_step::ErrorAffectable;
|
||||
use crate::execution_step::RcSecurityTetraplet;
|
||||
use crate::JValue;
|
||||
use crate::ToErrorCode;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
pub(crate) fn get_instruction_error_from_exec_error(
|
||||
error: &(impl ErrorAffectable + ToErrorCode + ToString),
|
||||
instruction: &str,
|
||||
peer_id_option: Option<&str>,
|
||||
tetraplet: Option<RcSecurityTetraplet>,
|
||||
) -> InstructionError {
|
||||
// it is not a call result, but generated from a limited set of unjoinable errors
|
||||
let provenance = Provenance::literal();
|
||||
|
||||
get_instruction_error_from_ingredients(
|
||||
error.to_error_code(),
|
||||
&error.to_string(),
|
||||
instruction,
|
||||
peer_id_option,
|
||||
tetraplet,
|
||||
provenance,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn get_instruction_error_from_ingredients(
|
||||
error_code: i64,
|
||||
error_message: &str,
|
||||
instruction: &str,
|
||||
peer_id_option: Option<&str>,
|
||||
tetraplet: Option<RcSecurityTetraplet>,
|
||||
provenance: Provenance,
|
||||
) -> InstructionError {
|
||||
let error_object = match peer_id_option {
|
||||
Some(peer_id) => error_from_raw_fields_w_peerid(error_code, error_message, instruction, peer_id),
|
||||
None => error_from_raw_fields(error_code, error_message, instruction),
|
||||
};
|
||||
get_instruction_error_from_error_object(Rc::new(error_object), tetraplet, provenance)
|
||||
}
|
||||
|
||||
pub(crate) fn get_instruction_error_from_error_object(
|
||||
error: Rc<JValue>,
|
||||
tetraplet: Option<RcSecurityTetraplet>,
|
||||
provenance: Provenance,
|
||||
) -> InstructionError {
|
||||
InstructionError {
|
||||
error,
|
||||
tetraplet,
|
||||
provenance,
|
||||
}
|
||||
}
|
@ -32,14 +32,13 @@ pub const PEER_ID_FIELD_NAME: &str = "peer_id";
|
||||
pub const NO_ERROR_MESSAGE: &str = "";
|
||||
pub const NO_ERROR_ERROR_CODE: i64 = 0;
|
||||
|
||||
/// This struct is intended to track the last arisen error.
|
||||
/// LastError is essentially a scalar value with support of lambda expressions.
|
||||
/// The only differences from a scalar are
|
||||
/// - it's accessed by %last_error% literal
|
||||
/// - if it's unset before the usage, JValue::Null will be used without join behaviour
|
||||
/// - it's a global scalar, meaning that fold and new scopes doesn't apply for it
|
||||
/// This struct tracks the last arisen error.
|
||||
/// :error: and %last_error% are special scalar values that support lenses.
|
||||
/// There are some differences b/w mentioned errors and an ordinary scalar:
|
||||
/// - they are set to not-an-error value by default
|
||||
/// - these are global scalars, meaning that fold and new scopes do not apply for it
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LastError {
|
||||
pub struct InstructionError {
|
||||
/// Error object that represents the last occurred error.
|
||||
pub error: Rc<JValue>,
|
||||
|
||||
@ -50,7 +49,12 @@ pub struct LastError {
|
||||
pub provenance: Provenance,
|
||||
}
|
||||
|
||||
pub(crate) fn error_from_raw_fields(error_code: i64, error_message: &str, instruction: &str, peer_id: &str) -> JValue {
|
||||
pub(crate) fn error_from_raw_fields_w_peerid(
|
||||
error_code: i64,
|
||||
error_message: &str,
|
||||
instruction: &str,
|
||||
peer_id: &str,
|
||||
) -> JValue {
|
||||
serde_json::json!({
|
||||
ERROR_CODE_FIELD_NAME: error_code,
|
||||
MESSAGE_FIELD_NAME: error_message,
|
||||
@ -59,6 +63,14 @@ pub(crate) fn error_from_raw_fields(error_code: i64, error_message: &str, instru
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn error_from_raw_fields(error_code: i64, error_message: &str, instruction: &str) -> JValue {
|
||||
serde_json::json!({
|
||||
ERROR_CODE_FIELD_NAME: error_code,
|
||||
MESSAGE_FIELD_NAME: error_message,
|
||||
INSTRUCTION_FIELD_NAME: instruction,
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks that a scalar is a value of an object types that contains at least two fields:
|
||||
/// - error_code
|
||||
/// - message
|
||||
@ -116,16 +128,16 @@ fn ensure_jvalue_is_string(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_error_last_error_object() -> JValue {
|
||||
pub fn no_error_object() -> JValue {
|
||||
json!({
|
||||
ERROR_CODE_FIELD_NAME: NO_ERROR_ERROR_CODE,
|
||||
MESSAGE_FIELD_NAME: NO_ERROR_MESSAGE,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn no_error_last_error() -> LastError {
|
||||
LastError {
|
||||
error: Rc::new(no_error_last_error_object()),
|
||||
pub fn no_error() -> InstructionError {
|
||||
InstructionError {
|
||||
error: Rc::new(no_error_object()),
|
||||
tetraplet: None,
|
||||
provenance: Provenance::literal(),
|
||||
}
|
@ -16,10 +16,9 @@
|
||||
|
||||
use air_interpreter_data::Provenance;
|
||||
|
||||
use super::last_error_definition::error_from_raw_fields;
|
||||
use super::no_error_last_error;
|
||||
use super::LastError;
|
||||
use crate::execution_step::LastErrorAffectable;
|
||||
use super::no_error;
|
||||
use super::InstructionError;
|
||||
use crate::execution_step::ErrorAffectable;
|
||||
use crate::execution_step::RcSecurityTetraplet;
|
||||
use crate::JValue;
|
||||
use crate::ToErrorCode;
|
||||
@ -27,7 +26,7 @@ use crate::ToErrorCode;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub(crate) struct LastErrorDescriptor {
|
||||
last_error: LastError,
|
||||
error: InstructionError,
|
||||
|
||||
/// True, if last error could be set. This flag is used to distinguish
|
||||
/// whether an error is being bubbled up from the bottom or just encountered.
|
||||
@ -36,44 +35,23 @@ pub(crate) struct LastErrorDescriptor {
|
||||
}
|
||||
|
||||
impl LastErrorDescriptor {
|
||||
pub(crate) fn try_to_set_from_error(
|
||||
pub(crate) fn try_to_set_last_error_from_exec_error(
|
||||
&mut self,
|
||||
error: &(impl LastErrorAffectable + ToErrorCode + ToString),
|
||||
error: &(impl ErrorAffectable + ToErrorCode + ToString),
|
||||
instruction: &str,
|
||||
peer_id: &str,
|
||||
peer_id_option: Option<&str>,
|
||||
tetraplet: Option<RcSecurityTetraplet>,
|
||||
) -> bool {
|
||||
// this check is optimization to prevent creation of an error object in case if error
|
||||
// couldn't be set
|
||||
) {
|
||||
use super::get_instruction_error_from_exec_error;
|
||||
|
||||
// This check is an optimization to prevent creation of an error object in case if error
|
||||
// must not be set.
|
||||
if !self.error_can_be_set || !error.affects_last_error() {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// it is not a call result, but generated from a limited set of unjoinable errors
|
||||
let provenance = Provenance::literal();
|
||||
|
||||
self.set_from_ingredients(
|
||||
error.to_error_code(),
|
||||
&error.to_string(),
|
||||
instruction,
|
||||
peer_id,
|
||||
tetraplet,
|
||||
provenance,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn set_from_ingredients(
|
||||
&mut self,
|
||||
error_code: i64,
|
||||
error_message: &str,
|
||||
instruction: &str,
|
||||
peer_id: &str,
|
||||
tetraplet: Option<RcSecurityTetraplet>,
|
||||
provenance: Provenance,
|
||||
) -> bool {
|
||||
let error_object = error_from_raw_fields(error_code, error_message, instruction, peer_id);
|
||||
self.set_from_error_object(Rc::new(error_object), tetraplet, provenance);
|
||||
true
|
||||
self.error = get_instruction_error_from_exec_error(error, instruction, peer_id_option, tetraplet);
|
||||
self.error_can_be_set = false;
|
||||
}
|
||||
|
||||
pub(crate) fn set_from_error_object(
|
||||
@ -82,16 +60,14 @@ impl LastErrorDescriptor {
|
||||
tetraplet: Option<RcSecurityTetraplet>,
|
||||
provenance: Provenance,
|
||||
) {
|
||||
self.last_error = LastError {
|
||||
error,
|
||||
tetraplet,
|
||||
provenance,
|
||||
};
|
||||
use super::get_instruction_error_from_error_object;
|
||||
|
||||
self.error = get_instruction_error_from_error_object(error, tetraplet, provenance);
|
||||
self.error_can_be_set = false;
|
||||
}
|
||||
|
||||
pub(crate) fn last_error(&self) -> &LastError {
|
||||
&self.last_error
|
||||
pub(crate) fn error(&self) -> &InstructionError {
|
||||
&self.error
|
||||
}
|
||||
|
||||
pub(crate) fn meet_xor_right_branch(&mut self) {
|
||||
@ -105,10 +81,10 @@ impl LastErrorDescriptor {
|
||||
|
||||
impl Default for LastErrorDescriptor {
|
||||
fn default() -> Self {
|
||||
let last_error = no_error_last_error();
|
||||
let error = no_error();
|
||||
|
||||
Self {
|
||||
last_error,
|
||||
error,
|
||||
error_can_be_set: true,
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2023 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
mod error_descriptor;
|
||||
mod errors;
|
||||
mod errors_utils;
|
||||
mod instruction_error_definition;
|
||||
mod last_error_descriptor;
|
||||
|
||||
pub use errors::LastErrorObjectError;
|
||||
pub use instruction_error_definition::no_error;
|
||||
pub use instruction_error_definition::no_error_object;
|
||||
pub use instruction_error_definition::InstructionError;
|
||||
pub use instruction_error_definition::ERROR_CODE_FIELD_NAME;
|
||||
pub use instruction_error_definition::INSTRUCTION_FIELD_NAME;
|
||||
pub use instruction_error_definition::MESSAGE_FIELD_NAME;
|
||||
pub use instruction_error_definition::NO_ERROR_ERROR_CODE;
|
||||
pub use instruction_error_definition::NO_ERROR_MESSAGE;
|
||||
|
||||
pub(crate) use error_descriptor::ErrorDescriptor;
|
||||
pub(super) use errors_utils::*;
|
||||
pub(crate) use instruction_error_definition::check_error_object;
|
||||
pub(crate) use instruction_error_definition::error_from_raw_fields_w_peerid;
|
||||
pub(crate) use last_error_descriptor::LastErrorDescriptor;
|
@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
mod errors;
|
||||
mod last_error_definition;
|
||||
mod last_error_descriptor;
|
||||
|
||||
pub use errors::LastErrorObjectError;
|
||||
pub use last_error_definition::no_error_last_error;
|
||||
pub use last_error_definition::no_error_last_error_object;
|
||||
pub use last_error_definition::LastError;
|
||||
pub use last_error_definition::ERROR_CODE_FIELD_NAME;
|
||||
pub use last_error_definition::INSTRUCTION_FIELD_NAME;
|
||||
pub use last_error_definition::MESSAGE_FIELD_NAME;
|
||||
pub use last_error_definition::NO_ERROR_ERROR_CODE;
|
||||
pub use last_error_definition::NO_ERROR_MESSAGE;
|
||||
|
||||
pub(crate) use last_error_definition::check_error_object;
|
||||
pub(crate) use last_error_definition::error_from_raw_fields;
|
||||
pub(crate) use last_error_descriptor::LastErrorDescriptor;
|
@ -16,12 +16,12 @@
|
||||
|
||||
mod cid_state;
|
||||
mod context;
|
||||
mod last_error;
|
||||
mod instruction_error;
|
||||
mod scalar_variables;
|
||||
mod stream_maps_variables;
|
||||
mod streams_variables;
|
||||
|
||||
pub use last_error::*;
|
||||
pub use instruction_error::*;
|
||||
|
||||
pub use cid_state::ExecutionCidState;
|
||||
pub(crate) use context::*;
|
||||
|
@ -23,8 +23,9 @@ use crate::execution_step::PEEK_ALLOWED_ON_NON_EMPTY;
|
||||
use crate::UncatchableError;
|
||||
|
||||
use air_interpreter_data::Provenance;
|
||||
use air_lambda_parser::LambdaAST;
|
||||
use air_lambda_ast::LambdaAST;
|
||||
use air_parser::ast;
|
||||
use air_parser::ast::InstructionErrorAST;
|
||||
|
||||
pub(crate) fn apply_to_arg(
|
||||
argument: &ast::ApArgument<'_>,
|
||||
@ -36,6 +37,7 @@ pub(crate) fn apply_to_arg(
|
||||
|
||||
let result = match argument {
|
||||
InitPeerId => apply_const(exec_ctx.run_parameters.init_peer_id.as_ref(), exec_ctx, trace_ctx),
|
||||
Error(instruction_error) => apply_error(instruction_error, exec_ctx, trace_ctx),
|
||||
LastError(error_accessor) => apply_last_error(error_accessor, exec_ctx, trace_ctx),
|
||||
Literal(value) => apply_const(*value, exec_ctx, trace_ctx),
|
||||
Timestamp => apply_const(exec_ctx.run_parameters.timestamp, exec_ctx, trace_ctx),
|
||||
@ -68,6 +70,21 @@ fn apply_const(
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn apply_error<'ctx>(
|
||||
instruction_error: &InstructionErrorAST<'ctx>,
|
||||
exec_ctx: &ExecutionCtx<'ctx>,
|
||||
trace_ctx: &TraceHandler,
|
||||
) -> ExecutionResult<ValueAggregate> {
|
||||
let (value, mut tetraplets, provenance) = instruction_error.resolve(exec_ctx)?;
|
||||
let value = Rc::new(value);
|
||||
// removing is safe because prepare_last_error always returns a vec with one element.
|
||||
let tetraplet = tetraplets.remove(0);
|
||||
let position = trace_ctx.trace_pos().map_err(UncatchableError::from)?;
|
||||
|
||||
let result = ValueAggregate::new(value, tetraplet, position, provenance);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn apply_last_error<'i>(
|
||||
error_accessor: &Option<LambdaAST<'i>>,
|
||||
exec_ctx: &ExecutionCtx<'i>,
|
||||
|
@ -41,44 +41,43 @@ impl<'i> super::ExecutableInstruction<'i> for Call<'i> {
|
||||
exec_ctx.tracker.meet_call();
|
||||
|
||||
let resolved_call = joinable!(ResolvedCall::new(self, exec_ctx), exec_ctx, ())
|
||||
.map_err(|e| set_last_error(self, exec_ctx, e, None))?;
|
||||
.map_err(|e| set_errors(self, exec_ctx, e, None))?;
|
||||
|
||||
let tetraplet = resolved_call.as_tetraplet();
|
||||
joinable!(resolved_call.execute(self, exec_ctx, trace_ctx), exec_ctx, ())
|
||||
.map_err(|e| set_last_error(self, exec_ctx, e, Some(tetraplet)))
|
||||
.map_err(|e| set_errors(self, exec_ctx, e, Some(tetraplet)))
|
||||
}
|
||||
}
|
||||
|
||||
fn set_last_error<'i>(
|
||||
fn set_errors<'i>(
|
||||
call: &Call<'i>,
|
||||
exec_ctx: &mut ExecutionCtx<'i>,
|
||||
execution_error: ExecutionError,
|
||||
tetraplet: Option<RcSecurityTetraplet>,
|
||||
) -> ExecutionError {
|
||||
use air_parser::ast::PeerIDErrorLogable;
|
||||
|
||||
let catchable_error = match execution_error {
|
||||
ExecutionError::Catchable(catchable) => catchable,
|
||||
ExecutionError::Uncatchable(_) => return execution_error,
|
||||
};
|
||||
|
||||
let current_peer_id = match &tetraplet {
|
||||
// use tetraplet if they set, because an error could be propagated from data
|
||||
// (from CallServiceFailed state) and exec_ctx.run_parameters.current_peer_id won't mean
|
||||
// a peer where the error was occurred
|
||||
Some(tetraplet) => tetraplet.peer_pk.clone(),
|
||||
None => exec_ctx.run_parameters.current_peer_id.to_string(),
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
"call failed with an error `{}`, peerId `{}`",
|
||||
catchable_error,
|
||||
current_peer_id
|
||||
);
|
||||
|
||||
let _ = exec_ctx.last_error_descriptor.try_to_set_from_error(
|
||||
exec_ctx.set_errors(
|
||||
catchable_error.as_ref(),
|
||||
&call.to_string(),
|
||||
¤t_peer_id,
|
||||
tetraplet,
|
||||
tetraplet.clone(),
|
||||
call.log_errors_with_peer_id(),
|
||||
);
|
||||
|
||||
let peer_id = match &tetraplet {
|
||||
// use tetraplet if it is set, because an error could be propagated from data
|
||||
// (from CallServiceFailed state) and exec_ctx.run_parameters.current_peer_id won't mean
|
||||
// a peer where the error was occurred
|
||||
Some(tetraplet) => tetraplet.peer_pk.as_str(),
|
||||
None => exec_ctx.run_parameters.current_peer_id.as_str(),
|
||||
};
|
||||
|
||||
log::debug!("call failed with an error `{}`, peerId `{}`", catchable_error, peer_id);
|
||||
|
||||
ExecutionError::Catchable(catchable_error)
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ use super::TraceHandler;
|
||||
use crate::execution_step::execution_context::check_error_object;
|
||||
use crate::execution_step::resolver::Resolvable;
|
||||
use crate::execution_step::CatchableError;
|
||||
use crate::execution_step::LastError;
|
||||
use crate::execution_step::RcSecurityTetraplet;
|
||||
use crate::log_instruction;
|
||||
use crate::ExecutionError;
|
||||
@ -75,7 +74,7 @@ fn fail_with_literals(
|
||||
fail: &Fail<'_>,
|
||||
exec_ctx: &mut ExecutionCtx<'_>,
|
||||
) -> ExecutionResult<()> {
|
||||
let error_object = crate::execution_step::execution_context::error_from_raw_fields(
|
||||
let error_object = crate::execution_step::execution_context::error_from_raw_fields_w_peerid(
|
||||
error_code,
|
||||
error_message,
|
||||
&fail.to_string(),
|
||||
@ -103,11 +102,13 @@ fn fail_with_canon_stream(
|
||||
}
|
||||
|
||||
fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> {
|
||||
let LastError {
|
||||
use crate::execution_step::InstructionError;
|
||||
|
||||
let InstructionError {
|
||||
error,
|
||||
tetraplet,
|
||||
provenance,
|
||||
} = exec_ctx.last_error_descriptor.last_error();
|
||||
} = exec_ctx.last_error_descriptor.error();
|
||||
|
||||
// to avoid warnings from https://github.com/rust-lang/rust/issues/59159
|
||||
let error = error.clone();
|
||||
|
@ -46,21 +46,14 @@ use super::ExecutionResult;
|
||||
use crate::execution_step::TraceHandler;
|
||||
|
||||
use air_parser::ast::Instruction;
|
||||
use air_parser::ast::PeerIDErrorLogable;
|
||||
|
||||
// TODO: move all error set logic from macros into the execution context
|
||||
|
||||
/// Executes instruction and updates last error if needed.
|
||||
/// Executes an instruction and updates %last_error% and :error: if necessary.
|
||||
macro_rules! execute {
|
||||
($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {{
|
||||
match $instr.execute($exec_ctx, $trace_ctx) {
|
||||
Err(e) => {
|
||||
$exec_ctx.last_error_descriptor.try_to_set_from_error(
|
||||
&e,
|
||||
// TODO: avoid excess copying here
|
||||
&$instr.to_string(),
|
||||
$exec_ctx.run_parameters.current_peer_id.as_ref(),
|
||||
None,
|
||||
);
|
||||
$exec_ctx.set_errors(&e, &$instr.to_string(), None, $instr.log_errors_with_peer_id());
|
||||
Err(e)
|
||||
}
|
||||
v => v,
|
||||
@ -76,13 +69,14 @@ impl<'i> ExecutableInstruction<'i> for Instruction<'i> {
|
||||
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut TraceHandler) -> ExecutionResult<()> {
|
||||
match self {
|
||||
// call isn't wrapped by the execute macro because
|
||||
// it internally sets last_error with resolved triplet
|
||||
// it internally maps some Catchables into %last_error%/:error: using resolved triplet.
|
||||
// Both canons and call set :error:.$.peer_id whilst other instructions do not.
|
||||
Instruction::Call(call) => call.execute(exec_ctx, trace_ctx),
|
||||
|
||||
Instruction::Ap(ap) => execute!(self, ap, exec_ctx, trace_ctx),
|
||||
Instruction::ApMap(ap_map) => execute!(self, ap_map, exec_ctx, trace_ctx),
|
||||
Instruction::Canon(canon) => execute!(self, canon, exec_ctx, trace_ctx),
|
||||
Instruction::CanonStreamMapScalar(canon) => execute!(self, canon, exec_ctx, trace_ctx),
|
||||
Instruction::Ap(ap) => execute!(self, ap, exec_ctx, trace_ctx),
|
||||
Instruction::ApMap(ap_map) => execute!(self, ap_map, exec_ctx, trace_ctx),
|
||||
Instruction::Fail(fail) => execute!(self, fail, exec_ctx, trace_ctx),
|
||||
Instruction::FoldScalar(fold) => execute!(self, fold, exec_ctx, trace_ctx),
|
||||
Instruction::FoldStream(fold) => execute!(self, fold, exec_ctx, trace_ctx),
|
||||
|
@ -33,7 +33,13 @@ impl<'i> super::ExecutableInstruction<'i> for Xor<'i> {
|
||||
|
||||
exec_ctx.flush_subgraph_completeness();
|
||||
exec_ctx.last_error_descriptor.meet_xor_right_branch();
|
||||
self.1.execute(exec_ctx, trace_ctx)
|
||||
|
||||
let right_subgraph_result = self.1.execute(exec_ctx, trace_ctx);
|
||||
// This sets :error: to a no-error state.
|
||||
// Please note the right_subgraph_result might be an Error that bubbles up to an :error:
|
||||
// above this execute().
|
||||
exec_ctx.error_descriptor.clear_error();
|
||||
right_subgraph_result
|
||||
}
|
||||
res => res,
|
||||
}
|
||||
|
@ -40,10 +40,10 @@ pub mod errors_prelude {
|
||||
|
||||
pub(super) use self::instructions::ExecutableInstruction;
|
||||
pub(super) use self::instructions::FoldState;
|
||||
pub(crate) use errors::ErrorAffectable;
|
||||
pub(crate) use errors::Joinable;
|
||||
pub(crate) use errors::LastErrorAffectable;
|
||||
pub(crate) use execution_context::ExecutionCtx;
|
||||
pub(crate) use execution_context::LastError;
|
||||
pub(crate) use execution_context::InstructionError;
|
||||
pub(super) use value_types::CanonResultAggregate;
|
||||
pub(super) use value_types::Generation;
|
||||
pub(super) use value_types::LiteralAggregate;
|
||||
|
@ -21,12 +21,13 @@ use crate::execution_step::lambda_applier::select_by_lambda_from_scalar;
|
||||
use crate::execution_step::value_types::JValuable;
|
||||
use crate::execution_step::ExecutionResult;
|
||||
use crate::JValue;
|
||||
use crate::LambdaAST;
|
||||
use crate::SecurityTetraplet;
|
||||
|
||||
use air_interpreter_data::Provenance;
|
||||
use air_lambda_ast::LambdaAST;
|
||||
use air_parser::ast;
|
||||
|
||||
use air_parser::ast::InstructionErrorAST;
|
||||
use serde_json::json;
|
||||
use std::rc::Rc;
|
||||
|
||||
@ -37,6 +38,7 @@ impl Resolvable for ast::ImmutableValue<'_> {
|
||||
|
||||
match self {
|
||||
InitPeerId => resolve_const(ctx.run_parameters.init_peer_id.as_ref(), ctx),
|
||||
Error(error_accessor) => error_accessor.resolve(ctx),
|
||||
LastError(error_accessor) => error_accessor.resolve(ctx),
|
||||
Literal(value) => resolve_const(value.to_string(), ctx),
|
||||
Timestamp => resolve_const(ctx.run_parameters.timestamp, ctx),
|
||||
@ -61,31 +63,47 @@ pub(crate) fn resolve_const(
|
||||
Ok((jvalue, vec![tetraplet], Provenance::literal()))
|
||||
}
|
||||
|
||||
fn resolve_errors(
|
||||
instruction_error: &crate::InstructionError,
|
||||
lens: &Option<LambdaAST<'_>>,
|
||||
ctx: &ExecutionCtx<'_>,
|
||||
) -> Result<(serde_json::Value, Vec<Rc<SecurityTetraplet>>, Provenance), crate::ExecutionError> {
|
||||
use crate::execution_step::InstructionError;
|
||||
|
||||
let InstructionError {
|
||||
error,
|
||||
tetraplet,
|
||||
provenance,
|
||||
} = instruction_error;
|
||||
|
||||
let jvalue = match lens {
|
||||
Some(error_accessor) => select_by_lambda_from_scalar(error.as_ref(), error_accessor, ctx)?.into_owned(),
|
||||
None => error.as_ref().clone(),
|
||||
};
|
||||
|
||||
let tetraplets = match tetraplet {
|
||||
Some(tetraplet) => vec![tetraplet.clone()],
|
||||
None => {
|
||||
let tetraplet = SecurityTetraplet::literal_tetraplet(ctx.run_parameters.init_peer_id.as_ref());
|
||||
let tetraplet = Rc::new(tetraplet);
|
||||
vec![tetraplet]
|
||||
}
|
||||
};
|
||||
|
||||
Ok((jvalue, tetraplets, provenance.clone()))
|
||||
}
|
||||
|
||||
impl<'lens> Resolvable for InstructionErrorAST<'lens> {
|
||||
fn resolve(&self, ctx: &ExecutionCtx<'_>) -> ExecutionResult<(JValue, RcSecurityTetraplets, Provenance)> {
|
||||
let instruction_error = ctx.error();
|
||||
resolve_errors(instruction_error, &self.lens, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolvable for Option<LambdaAST<'_>> {
|
||||
fn resolve(&self, ctx: &ExecutionCtx<'_>) -> ExecutionResult<(JValue, RcSecurityTetraplets, Provenance)> {
|
||||
use crate::LastError;
|
||||
|
||||
let LastError {
|
||||
error,
|
||||
tetraplet,
|
||||
provenance,
|
||||
} = ctx.last_error();
|
||||
|
||||
let jvalue = match self {
|
||||
Some(error_accessor) => select_by_lambda_from_scalar(error.as_ref(), error_accessor, ctx)?.into_owned(),
|
||||
None => error.as_ref().clone(),
|
||||
};
|
||||
|
||||
let tetraplets = match tetraplet {
|
||||
Some(tetraplet) => vec![tetraplet.clone()],
|
||||
None => {
|
||||
let tetraplet = SecurityTetraplet::literal_tetraplet(ctx.run_parameters.init_peer_id.as_ref());
|
||||
let tetraplet = Rc::new(tetraplet);
|
||||
vec![tetraplet]
|
||||
}
|
||||
};
|
||||
|
||||
Ok((jvalue, tetraplets, provenance.clone()))
|
||||
let instruction_error = ctx.last_error();
|
||||
resolve_errors(instruction_error, self, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,10 +37,10 @@ pub use air_interpreter_interface::RunParameters;
|
||||
pub use air_interpreter_interface::INTERPRETER_SUCCESS;
|
||||
pub use execution_step::execution_context::errors::unsupported_map_key_type;
|
||||
pub use execution_step::execution_context::errors::StreamMapError;
|
||||
pub use execution_step::execution_context::no_error_last_error;
|
||||
pub use execution_step::execution_context::no_error_last_error_object;
|
||||
pub use execution_step::execution_context::no_error;
|
||||
pub use execution_step::execution_context::no_error_object;
|
||||
pub use execution_step::execution_context::ExecutionCidState;
|
||||
pub use execution_step::execution_context::LastError;
|
||||
pub use execution_step::execution_context::InstructionError;
|
||||
pub use execution_step::execution_context::NO_ERROR_ERROR_CODE;
|
||||
pub use execution_step::execution_context::NO_ERROR_MESSAGE;
|
||||
pub use execution_step::CatchableError;
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use air::no_error_last_error_object;
|
||||
use air::no_error_object;
|
||||
use air::CatchableError;
|
||||
use air::ExecutionCidState;
|
||||
use air::ExecutionError;
|
||||
@ -124,7 +124,7 @@ fn not_clear_last_error_in_match() {
|
||||
let _ = checked_call_vm!(local_vm, <_>::default(), &script, "", result.data);
|
||||
|
||||
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
|
||||
assert_eq!(actual_value, no_error_last_error_object(),);
|
||||
assert_eq!(actual_value, no_error_object(),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -159,7 +159,7 @@ fn not_clear_last_error_in_mismatch() {
|
||||
let _ = checked_call_vm!(local_vm, <_>::default(), &script, "", result.data);
|
||||
|
||||
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
|
||||
assert_eq!(actual_value, no_error_last_error_object(),);
|
||||
assert_eq!(actual_value, no_error_object(),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -241,7 +241,7 @@ fn non_initialized_last_error() {
|
||||
let _ = checked_call_vm!(vm, test_params.clone(), script, "", "");
|
||||
|
||||
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
|
||||
assert_eq!(actual_value, no_error_last_error_object(),);
|
||||
assert_eq!(actual_value, no_error_object(),);
|
||||
|
||||
let actual_tetraplets = (*tetraplets.borrow()).as_ref().unwrap().clone();
|
||||
assert_eq!(
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use air::no_error_last_error_object;
|
||||
use air::no_error_object;
|
||||
use air::ExecutionCidState;
|
||||
use air_test_framework::AirScriptExecutor;
|
||||
use air_test_utils::prelude::*;
|
||||
@ -198,16 +198,57 @@ fn ap_with_last_error() {
|
||||
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "vm_1_peer_id", "service_id": ""},
|
||||
"values": [
|
||||
{
|
||||
"result": no_error_last_error_object(),
|
||||
"result": no_error_object(),
|
||||
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "", "service_id": ""},
|
||||
"trace_pos": 0
|
||||
}
|
||||
]
|
||||
})),
|
||||
unused!(
|
||||
json!([no_error_last_error_object()]),
|
||||
json!([no_error_object()]),
|
||||
peer = vm_1_peer_id,
|
||||
args = [no_error_last_error_object()]
|
||||
args = [no_error_object()]
|
||||
),
|
||||
];
|
||||
|
||||
assert_eq!(actual_trace, expected_state);
|
||||
assert!(result.next_peer_pks.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ap_with_error() {
|
||||
let vm_1_peer_id = "vm_1_peer_id";
|
||||
let mut vm_1 = create_avm(echo_call_service(), vm_1_peer_id);
|
||||
|
||||
let script = format!(
|
||||
r#"
|
||||
(seq
|
||||
(ap :error: $stream)
|
||||
(seq
|
||||
(canon "{vm_1_peer_id}" $stream #canon_stream)
|
||||
(call "{vm_1_peer_id}" ("" "") [#canon_stream])))
|
||||
"#
|
||||
);
|
||||
|
||||
let result = checked_call_vm!(vm_1, <_>::default(), script, "", "");
|
||||
|
||||
let actual_trace = trace_from_result(&result);
|
||||
let expected_state = vec![
|
||||
executed_state::ap(0),
|
||||
executed_state::canon(json!({
|
||||
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "vm_1_peer_id", "service_id": ""},
|
||||
"values": [
|
||||
{
|
||||
"result": no_error_object(),
|
||||
"tetraplet": {"function_name": "", "json_path": "", "peer_pk": "", "service_id": ""},
|
||||
"trace_pos": 0
|
||||
}
|
||||
]
|
||||
})),
|
||||
unused!(
|
||||
json!([no_error_object()]),
|
||||
peer = vm_1_peer_id,
|
||||
args = [no_error_object()]
|
||||
),
|
||||
];
|
||||
|
||||
@ -589,11 +630,11 @@ fn ap_stream_map_with_undefined_last_error() {
|
||||
SubTraceDesc::new(3.into(), 0),
|
||||
)]),
|
||||
unused!(
|
||||
no_error_last_error_object(),
|
||||
no_error_object(),
|
||||
peer = vm_1_peer_id,
|
||||
service = "m",
|
||||
function = "f",
|
||||
args = [no_error_last_error_object()]
|
||||
args = [no_error_object()]
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -451,3 +451,35 @@ fn match_with_undefined_last_error_message() {
|
||||
)]);
|
||||
assert_eq!(actual_trace, expected_trace);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_with_error() {
|
||||
let local_peer_id = "local_peer_id";
|
||||
let mut vm = create_avm(echo_call_service(), local_peer_id);
|
||||
|
||||
let script = format!(
|
||||
r#"
|
||||
(xor
|
||||
(match 1 2 (null))
|
||||
(call "local_peer_id" ("test" "error_code") [:error:.$.error_code] scalar)
|
||||
)
|
||||
"#
|
||||
);
|
||||
|
||||
let result = checked_call_vm!(vm, <_>::default(), script, "", "");
|
||||
|
||||
let actual_trace = trace_from_result(&result);
|
||||
|
||||
let mut cid_state = ExecutionCidState::new();
|
||||
let errcode_lambda_output = json!(10001);
|
||||
|
||||
let expected_trace = ExecutionTrace::from(vec![scalar_tracked!(
|
||||
errcode_lambda_output.clone(),
|
||||
cid_state,
|
||||
peer = local_peer_id,
|
||||
service = "test",
|
||||
function = "error_code",
|
||||
args = vec![errcode_lambda_output]
|
||||
)]);
|
||||
assert_eq!(actual_trace, expected_trace);
|
||||
}
|
||||
|
@ -203,3 +203,37 @@ fn mismatch_with_two_xors() {
|
||||
|
||||
assert_eq!(actual_trace.pop().unwrap(), expected_executed_call_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatch_with_error() {
|
||||
use air::ExecutionCidState;
|
||||
|
||||
let local_peer_id = "local_peer_id";
|
||||
let mut vm = create_avm(echo_call_service(), local_peer_id);
|
||||
|
||||
let script = format!(
|
||||
r#"
|
||||
(xor
|
||||
(mismatch 42 42 (null))
|
||||
(call "local_peer_id" ("test" "error_code") [:error:.$.error_code] scalar)
|
||||
)
|
||||
"#
|
||||
);
|
||||
|
||||
let result = checked_call_vm!(vm, <_>::default(), script, "", "");
|
||||
|
||||
let actual_trace = trace_from_result(&result);
|
||||
|
||||
let mut cid_state = ExecutionCidState::new();
|
||||
let errcode_lambda_output = json!(10002);
|
||||
|
||||
let expected_trace = ExecutionTrace::from(vec![scalar_tracked!(
|
||||
errcode_lambda_output.clone(),
|
||||
cid_state,
|
||||
peer = local_peer_id,
|
||||
service = "test",
|
||||
function = "error_code",
|
||||
args = vec![errcode_lambda_output]
|
||||
)]);
|
||||
assert_eq!(actual_trace, expected_trace);
|
||||
}
|
||||
|
@ -235,3 +235,34 @@ fn last_error_with_xor() {
|
||||
|
||||
assert_eq!(actual_trace[1.into()], expected_state);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_with_xor() {
|
||||
let faillible_peer_id = "failible_peer_id";
|
||||
let mut faillible_vm = create_avm(fallible_call_service("service_id_1"), faillible_peer_id);
|
||||
let local_peer_id = "local_peer_id";
|
||||
let mut vm = create_avm(echo_call_service(), local_peer_id);
|
||||
|
||||
let script = format!(
|
||||
r#"
|
||||
(xor
|
||||
(call "{faillible_peer_id}" ("service_id_1" "local_fn_name") [] result)
|
||||
(call "{local_peer_id}" ("service_id_2" "local_fn_name") [:error:.$.message] result)
|
||||
)"#
|
||||
);
|
||||
|
||||
let result = checked_call_vm!(faillible_vm, <_>::default(), script.clone(), "", "");
|
||||
let result = checked_call_vm!(vm, <_>::default(), script, "", result.data);
|
||||
|
||||
let actual_trace = trace_from_result(&result);
|
||||
let msg = r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#;
|
||||
let expected_state = scalar!(
|
||||
msg,
|
||||
peer = local_peer_id,
|
||||
service = "service_id_2",
|
||||
function = "local_fn_name",
|
||||
args = [msg]
|
||||
);
|
||||
|
||||
assert_eq!(actual_trace[1.into()], expected_state);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use air::no_error_last_error_object;
|
||||
use air::no_error_object;
|
||||
use air::unsupported_map_key_type;
|
||||
use air::CatchableError;
|
||||
use air::LambdaError;
|
||||
@ -608,7 +608,55 @@ fn undefined_last_error_functor() {
|
||||
.expect("invalid test AIR script");
|
||||
let result = executor.execute_all(local_peer_id).unwrap();
|
||||
|
||||
let expected_error = CatchableError::LengthFunctorAppliedToNotArray(no_error_last_error_object());
|
||||
let expected_error = CatchableError::LengthFunctorAppliedToNotArray(no_error_object());
|
||||
assert!(check_error(&result.last().unwrap(), expected_error));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undefined_error_functor() {
|
||||
let local_peer_id = "local_peer_id";
|
||||
let script = format!(
|
||||
r#"
|
||||
(xor
|
||||
(match 1 2
|
||||
(null)
|
||||
)
|
||||
(call "local_peer_id" ("test" "error_code") [:error:.length] scalar)
|
||||
)
|
||||
"#
|
||||
);
|
||||
|
||||
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(local_peer_id), &script)
|
||||
.expect("invalid test AIR script");
|
||||
let result = executor.execute_all(local_peer_id).unwrap();
|
||||
|
||||
let error_object = json!({"error_code":10001, "instruction":"match 1 2","message":"compared values do not match"});
|
||||
let expected_error = CatchableError::LengthFunctorAppliedToNotArray(error_object);
|
||||
assert!(check_error(&result.last().unwrap(), expected_error));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undefined_error_peerid() {
|
||||
let local_peer_id = "local_peer_id";
|
||||
let script = format!(
|
||||
r#"
|
||||
(xor
|
||||
(match 1 2
|
||||
(null)
|
||||
)
|
||||
(call "local_peer_id" ("test" "error_code") [:error:.$.peerid] scalar)
|
||||
)
|
||||
"#
|
||||
);
|
||||
|
||||
let executor = AirScriptExecutor::from_annotated(TestRunParameters::from_init_peer_id(local_peer_id), &script)
|
||||
.expect("invalid test AIR script");
|
||||
let result = executor.execute_all(local_peer_id).unwrap();
|
||||
|
||||
let value = json!({"error_code":10001, "instruction":"match 1 2", "message":"compared values do not match"});
|
||||
let field_name = "peerid".into();
|
||||
let expected_error =
|
||||
air::CatchableError::LambdaApplierError(air::LambdaError::ValueNotContainSuchField { value, field_name });
|
||||
assert!(check_error(&result.last().unwrap(), expected_error));
|
||||
}
|
||||
|
||||
@ -631,7 +679,7 @@ fn undefined_last_error_instruction() {
|
||||
let result = executor.execute_all(local_peer_id).unwrap();
|
||||
|
||||
let expected_error = CatchableError::LambdaApplierError(LambdaError::ValueNotContainSuchField {
|
||||
value: no_error_last_error_object(),
|
||||
value: no_error_object(),
|
||||
field_name: "instruction".to_string(),
|
||||
});
|
||||
assert!(check_error(&&result.last().unwrap(), expected_error));
|
||||
@ -655,7 +703,7 @@ fn undefined_last_error_peer_id() {
|
||||
let result = executor.execute_all(local_peer_id).unwrap();
|
||||
|
||||
let expected_error = CatchableError::LambdaApplierError(LambdaError::ValueNotContainSuchField {
|
||||
value: no_error_last_error_object(),
|
||||
value: no_error_object(),
|
||||
field_name: "peer_id".to_string(),
|
||||
});
|
||||
assert!(check_error(&&result.last().unwrap(), expected_error));
|
||||
|
@ -21,6 +21,7 @@ use super::CanonStream;
|
||||
use super::CanonStreamWithLambda;
|
||||
use super::ImmutableVariable;
|
||||
use super::ImmutableVariableWithLambda;
|
||||
use super::InstructionErrorAST;
|
||||
use super::Scalar;
|
||||
use super::ScalarWithLambda;
|
||||
use super::Stream;
|
||||
@ -68,6 +69,7 @@ pub struct Triplet<'i> {
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub enum ImmutableValue<'i> {
|
||||
InitPeerId,
|
||||
Error(InstructionErrorAST<'i>),
|
||||
LastError(Option<LambdaAST<'i>>),
|
||||
Timestamp,
|
||||
TTL,
|
||||
@ -93,6 +95,7 @@ pub enum ApArgument<'i> {
|
||||
InitPeerId,
|
||||
Timestamp,
|
||||
TTL,
|
||||
Error(InstructionErrorAST<'i>),
|
||||
LastError(Option<LambdaAST<'i>>),
|
||||
Literal(&'i str),
|
||||
Number(Number),
|
||||
|
@ -35,6 +35,7 @@ impl fmt::Display for ImmutableValue<'_> {
|
||||
|
||||
match self {
|
||||
InitPeerId => write!(f, "%init_peer_id%"),
|
||||
Error(error_accessor) => display_error(f, error_accessor),
|
||||
LastError(error_accessor) => display_last_error(f, error_accessor),
|
||||
Literal(literal) => write!(f, r#""{literal}""#),
|
||||
Timestamp => write!(f, "%timestamp%"),
|
||||
@ -93,6 +94,7 @@ impl fmt::Display for ApArgument<'_> {
|
||||
|
||||
match self {
|
||||
InitPeerId => write!(f, "%init_peer_id%"),
|
||||
Error(error_accessor) => display_error(f, error_accessor),
|
||||
LastError(error_accessor) => display_last_error(f, error_accessor),
|
||||
Literal(str) => write!(f, r#""{str}""#),
|
||||
Timestamp => write!(f, "%timestamp%"),
|
||||
@ -183,8 +185,21 @@ impl From<&Number> for serde_json::Value {
|
||||
}
|
||||
|
||||
fn display_last_error(f: &mut fmt::Formatter, lambda_ast: &Option<LambdaAST>) -> fmt::Result {
|
||||
use crate::parser::LAST_ERROR;
|
||||
|
||||
match lambda_ast {
|
||||
Some(lambda_ast) => write!(f, "%last_error%{lambda_ast}"),
|
||||
None => write!(f, "%last_error%"),
|
||||
Some(lambda_ast) => write!(f, "{LAST_ERROR}{lambda_ast}"),
|
||||
None => write!(f, "{LAST_ERROR}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn display_error(f: &mut fmt::Formatter, error: &InstructionErrorAST) -> fmt::Result {
|
||||
use crate::parser::ERROR;
|
||||
|
||||
let InstructionErrorAST { lens } = error;
|
||||
|
||||
match lens {
|
||||
Some(lens) => write!(f, "{ERROR}{lens}"),
|
||||
None => write!(f, "{ERROR}"),
|
||||
}
|
||||
}
|
||||
|
@ -188,3 +188,7 @@ pub struct New<'i> {
|
||||
/// (null)
|
||||
#[derive(Serialize, Debug, PartialEq, Eq)]
|
||||
pub struct Null;
|
||||
|
||||
pub trait PeerIDErrorLogable {
|
||||
fn log_errors_with_peer_id(&self) -> bool;
|
||||
}
|
||||
|
@ -175,3 +175,49 @@ impl fmt::Display for New<'_> {
|
||||
write!(f, "new {}", self.argument)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! peer_id_error_logable {
|
||||
($($t:ty),+) => {
|
||||
$(
|
||||
impl PeerIDErrorLogable for $t {
|
||||
#[inline]
|
||||
fn log_errors_with_peer_id(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! no_peer_id_error_logable {
|
||||
($($t:ty),+) => {
|
||||
$(
|
||||
impl PeerIDErrorLogable for $t {
|
||||
#[inline]
|
||||
fn log_errors_with_peer_id(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
peer_id_error_logable!(Call<'_>, Canon<'_>, CanonStreamMapScalar<'_>);
|
||||
|
||||
no_peer_id_error_logable!(
|
||||
Ap<'_>,
|
||||
ApMap<'_>,
|
||||
Fail<'_>,
|
||||
FoldScalar<'_>,
|
||||
FoldStream<'_>,
|
||||
FoldStreamMap<'_>,
|
||||
Seq<'_>,
|
||||
Par<'_>,
|
||||
Xor<'_>,
|
||||
Match<'_>,
|
||||
MisMatch<'_>,
|
||||
Never,
|
||||
Next<'_>,
|
||||
New<'_>,
|
||||
Null
|
||||
);
|
||||
|
@ -86,3 +86,10 @@ pub struct StreamMap<'i> {
|
||||
pub name: &'i str,
|
||||
pub position: AirPos,
|
||||
}
|
||||
|
||||
/// An error wrapper with an optional lens.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
pub struct InstructionErrorAST<'lens> {
|
||||
#[serde(borrow)]
|
||||
pub lens: Option<LambdaAST<'lens>>,
|
||||
}
|
||||
|
@ -125,3 +125,9 @@ impl<'i> StreamMap<'i> {
|
||||
Self { name, position }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lens> InstructionErrorAST<'lens> {
|
||||
pub fn new(lens: Option<LambdaAST<'lens>>) -> Self {
|
||||
Self { lens }
|
||||
}
|
||||
}
|
||||
|
@ -230,6 +230,8 @@ Value: ImmutableValue<'input> = {
|
||||
InitPeerId => ImmutableValue::InitPeerId,
|
||||
<LastError> => ImmutableValue::LastError(None),
|
||||
<le:LastErrorWithLambda> => ImmutableValue::LastError(Some(le)),
|
||||
<Error> => ImmutableValue::Error(InstructionErrorAST::new(None)),
|
||||
<le:ErrorWithLambda> => ImmutableValue::Error(InstructionErrorAST::new(Some(le))),
|
||||
<l:Literal> => ImmutableValue::Literal(l),
|
||||
Timestamp => ImmutableValue::Timestamp,
|
||||
TTL => ImmutableValue::TTL,
|
||||
@ -246,6 +248,8 @@ ApArgument: ApArgument<'input> = {
|
||||
InitPeerId => ApArgument::InitPeerId,
|
||||
<LastError> => ApArgument::LastError(None),
|
||||
<le:LastErrorWithLambda> => ApArgument::LastError(Some(le)),
|
||||
<Error> => ApArgument::Error(InstructionErrorAST::new(None)),
|
||||
<le:ErrorWithLambda> => ApArgument::Error(InstructionErrorAST::new(Some(le))),
|
||||
Timestamp => ApArgument::Timestamp,
|
||||
TTL => ApArgument::TTL,
|
||||
<l:Literal> => ApArgument::Literal(l),
|
||||
@ -295,6 +299,8 @@ extern {
|
||||
InitPeerId => Token::InitPeerId,
|
||||
LastError => Token::LastError,
|
||||
LastErrorWithLambda => Token::LastErrorWithLambda(<LambdaAST<'input>>),
|
||||
Error => Token::Error,
|
||||
ErrorWithLambda => Token::ErrorWithLambda(<LambdaAST<'input>>),
|
||||
Timestamp => Token::Timestamp,
|
||||
TTL => Token::TTL,
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -195,7 +195,10 @@ fn string_to_token(input: &str, start_pos: AirPos) -> LexerResult<Token> {
|
||||
MISMATCH_INSTR => Ok(Token::MisMatch),
|
||||
|
||||
INIT_PEER_ID => Ok(Token::InitPeerId),
|
||||
_ if input.starts_with(LAST_ERROR) => parse_last_error(input, start_pos),
|
||||
_ if input.starts_with(ERROR) => parse_error(input, start_pos, ERROR, Token::Error),
|
||||
_ if input.starts_with(LAST_ERROR) => {
|
||||
parse_error(input, start_pos, LAST_ERROR, Token::LastError)
|
||||
}
|
||||
TIMESTAMP => Ok(Token::Timestamp),
|
||||
TTL => Ok(Token::TTL),
|
||||
|
||||
@ -206,28 +209,37 @@ fn string_to_token(input: &str, start_pos: AirPos) -> LexerResult<Token> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_last_error(input: &str, start_pos: AirPos) -> LexerResult<Token<'_>> {
|
||||
let last_error_size = LAST_ERROR.len();
|
||||
if input.len() == last_error_size {
|
||||
return Ok(Token::LastError);
|
||||
fn parse_error<'input>(
|
||||
input: &'input str,
|
||||
start_pos: AirPos,
|
||||
token_str: &str,
|
||||
token_wo_lens: Token<'static>,
|
||||
) -> LexerResult<Token<'input>> {
|
||||
let token_wo_lens_len = token_str.len();
|
||||
|
||||
if input.len() == token_wo_lens_len {
|
||||
return Ok(token_wo_lens);
|
||||
}
|
||||
|
||||
if input.len() <= last_error_size {
|
||||
if input.len() <= token_wo_lens_len {
|
||||
return Err(LexerError::lambda_parser_error(
|
||||
start_pos + last_error_size..start_pos + input.len(),
|
||||
start_pos + token_wo_lens_len..start_pos + input.len(),
|
||||
"lambda AST applied to last error has not enough size",
|
||||
));
|
||||
}
|
||||
|
||||
let last_error_accessor = crate::parse_lambda(&input[last_error_size..]).map_err(|e| {
|
||||
let last_error_accessor = crate::parse_lambda(&input[token_wo_lens_len..]).map_err(|e| {
|
||||
LexerError::lambda_parser_error(
|
||||
start_pos + last_error_size..start_pos + input.len(),
|
||||
start_pos + token_wo_lens_len..start_pos + input.len(),
|
||||
e.to_string(),
|
||||
)
|
||||
})?;
|
||||
let last_error_token = Token::LastErrorWithLambda(last_error_accessor);
|
||||
|
||||
Ok(last_error_token)
|
||||
match token_wo_lens {
|
||||
Token::Error => Ok(Token::ErrorWithLambda(last_error_accessor)),
|
||||
Token::LastError => Ok(Token::LastErrorWithLambda(last_error_accessor)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
const CALL_INSTR: &str = "call";
|
||||
@ -246,7 +258,8 @@ const MATCH_INSTR: &str = "match";
|
||||
const MISMATCH_INSTR: &str = "mismatch";
|
||||
|
||||
const INIT_PEER_ID: &str = "%init_peer_id%";
|
||||
const LAST_ERROR: &str = "%last_error%";
|
||||
pub(crate) const LAST_ERROR: &str = "%last_error%";
|
||||
pub(crate) const ERROR: &str = ":error:";
|
||||
const TIMESTAMP: &str = "%timestamp%";
|
||||
const TTL: &str = "%ttl%";
|
||||
|
||||
|
@ -25,6 +25,8 @@ mod tests;
|
||||
pub mod text_pos;
|
||||
|
||||
pub use air_lexer::AIRLexer;
|
||||
pub(crate) use air_lexer::ERROR;
|
||||
pub(crate) use air_lexer::LAST_ERROR;
|
||||
pub use errors::LexerError;
|
||||
pub use text_pos::AirPos;
|
||||
pub use token::Token;
|
||||
|
@ -72,7 +72,9 @@ pub enum Token<'input> {
|
||||
|
||||
InitPeerId,
|
||||
LastError,
|
||||
Error,
|
||||
LastErrorWithLambda(LambdaAST<'input>),
|
||||
ErrorWithLambda(LambdaAST<'input>),
|
||||
Timestamp,
|
||||
TTL,
|
||||
|
||||
|
@ -33,6 +33,8 @@ pub mod tests;
|
||||
pub use self::air_parser::parse;
|
||||
pub use air::AIRParser;
|
||||
pub use lexer::AIRLexer;
|
||||
pub(crate) use lexer::ERROR;
|
||||
pub(crate) use lexer::LAST_ERROR;
|
||||
pub use span::Span;
|
||||
pub use validator::VariableValidator;
|
||||
|
||||
|
@ -155,6 +155,7 @@ impl<'i> VariableValidator<'i> {
|
||||
| ApArgument::Literal(_)
|
||||
| ApArgument::EmptyArray
|
||||
| ApArgument::LastError(_) => {}
|
||||
ApArgument::Error(_) => {}
|
||||
ApArgument::Scalar(scalar) => self.met_scalar(scalar, span),
|
||||
ApArgument::ScalarWithLambda(scalar) => self.met_scalar_wl(scalar, span),
|
||||
ApArgument::CanonStream(canon_stream) => self.met_canon_stream(canon_stream, span),
|
||||
@ -227,8 +228,8 @@ impl<'i> VariableValidator<'i> {
|
||||
use ImmutableValue::*;
|
||||
|
||||
match instr_arg_value {
|
||||
InitPeerId | LastError(_) | Timestamp | TTL | Literal(_) | Number(_) | Boolean(_)
|
||||
| EmptyArray => {}
|
||||
InitPeerId | Error(_) | LastError(_) | Timestamp | TTL | Literal(_) | Number(_)
|
||||
| Boolean(_) | EmptyArray => {}
|
||||
Variable(variable) => self.met_variable(variable, span),
|
||||
VariableWithLambda(variable) => self.met_variable_wl(variable, span),
|
||||
}
|
||||
@ -323,6 +324,7 @@ impl<'i> VariableValidator<'i> {
|
||||
| ImmutableValue::Number(_)
|
||||
| ImmutableValue::Boolean(_)
|
||||
| ImmutableValue::Literal(_)
|
||||
| ImmutableValue::Error(_)
|
||||
| ImmutableValue::LastError(_)
|
||||
| ImmutableValue::EmptyArray => {}
|
||||
ImmutableValue::Variable(variable) => self.met_variable(variable, span),
|
||||
|
@ -135,7 +135,10 @@ fn parse_sexp_string(inp: Input<'_>) -> IResult<Input<'_>, Sexp, ParseError<'_>>
|
||||
fn parse_sexp_symbol(inp: Input<'_>) -> IResult<Input<'_>, Sexp, ParseError<'_>> {
|
||||
map(
|
||||
recognize(pair(
|
||||
many1_count(alt((value((), alphanumeric1), value((), one_of("_-.$#%"))))),
|
||||
many1_count(alt((
|
||||
value((), alphanumeric1),
|
||||
value((), one_of("_-.:$#%")),
|
||||
))),
|
||||
opt(terminated(
|
||||
delimited(tag("["), parse_sexp_symbol, tag("]")),
|
||||
opt(tag("!")),
|
||||
|
Loading…
Reference in New Issue
Block a user