mirror of
https://github.com/fluencelabs/aquavm
synced 2025-03-15 12:30:50 +00:00
Refactor last error (#202)
This commit is contained in:
parent
c1ff7c0688
commit
63160dd0f0
@ -28,7 +28,7 @@ pub(super) fn apply_to_arg(
|
||||
|
||||
let result = match argument {
|
||||
InitPeerId => apply_const(exec_ctx.init_peer_id.clone(), exec_ctx, trace_ctx),
|
||||
LastError(error_path) => apply_last_error(error_path, 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),
|
||||
Number(value) => apply_const(value, exec_ctx, trace_ctx),
|
||||
Boolean(value) => apply_const(*value, exec_ctx, trace_ctx),
|
||||
@ -47,12 +47,12 @@ fn apply_const(value: impl Into<JValue>, exec_ctx: &ExecutionCtx<'_>, trace_ctx:
|
||||
ValueAggregate::new(value, tetraplet, trace_ctx.trace_pos())
|
||||
}
|
||||
|
||||
fn apply_last_error(
|
||||
error_path: &ast::LastErrorPath,
|
||||
exec_ctx: &ExecutionCtx<'_>,
|
||||
fn apply_last_error<'i>(
|
||||
error_accessor: &Option<LambdaAST<'i>>,
|
||||
exec_ctx: &ExecutionCtx<'i>,
|
||||
trace_ctx: &TraceHandler,
|
||||
) -> ExecutionResult<ValueAggregate> {
|
||||
let (value, mut tetraplets) = crate::execution_step::resolver::prepare_last_error(error_path, exec_ctx)?;
|
||||
let (value, mut tetraplets) = crate::execution_step::resolver::prepare_last_error(error_accessor, 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);
|
||||
|
@ -24,7 +24,6 @@ use resolved_call::ResolvedCall;
|
||||
use super::ExecutionCtx;
|
||||
use super::ExecutionError;
|
||||
use super::ExecutionResult;
|
||||
use super::LastErrorDescriptor;
|
||||
use super::TraceHandler;
|
||||
use crate::execution_step::Joinable;
|
||||
use crate::execution_step::RSecurityTetraplet;
|
||||
@ -74,10 +73,11 @@ fn set_last_error<'i>(
|
||||
current_peer_id
|
||||
);
|
||||
|
||||
let instruction = call.to_string();
|
||||
let last_error = LastErrorDescriptor::new(catchable_error.clone(), instruction, current_peer_id, tetraplet);
|
||||
exec_ctx.last_error = Some(last_error);
|
||||
exec_ctx.last_error_could_be_set = false;
|
||||
|
||||
let _ = exec_ctx.last_error_descriptor.try_to_set_from_ingredients(
|
||||
catchable_error.as_ref(),
|
||||
call.to_string(),
|
||||
current_peer_id,
|
||||
tetraplet,
|
||||
);
|
||||
ExecutionError::Catchable(catchable_error)
|
||||
}
|
||||
|
@ -37,8 +37,8 @@ pub(crate) fn are_matchable_eq<'ctx>(
|
||||
make_string_comparator(exec_ctx.init_peer_id.as_str()),
|
||||
),
|
||||
|
||||
(LastError(path), matchable) | (matchable, LastError(path)) => {
|
||||
let (value, _) = prepare_last_error(path, exec_ctx)?;
|
||||
(LastError(error_accessor), matchable) | (matchable, LastError(error_accessor)) => {
|
||||
let (value, _) = prepare_last_error(error_accessor, exec_ctx)?;
|
||||
compare_matchable(matchable, exec_ctx, make_object_comparator(value))
|
||||
}
|
||||
|
||||
@ -83,8 +83,8 @@ fn compare_matchable<'ctx>(
|
||||
let jvalue = init_peer_id.into();
|
||||
Ok(comparator(Cow::Owned(jvalue)))
|
||||
}
|
||||
LastError(error_path) => {
|
||||
let (jvalue, _) = prepare_last_error(error_path, exec_ctx)?;
|
||||
LastError(error_accessor) => {
|
||||
let (jvalue, _) = prepare_last_error(error_accessor, exec_ctx)?;
|
||||
Ok(comparator(Cow::Owned(jvalue)))
|
||||
}
|
||||
Literal(str) => {
|
||||
|
@ -16,10 +16,13 @@
|
||||
|
||||
use super::ExecutionCtx;
|
||||
use super::ExecutionResult;
|
||||
use super::LastErrorDescriptor;
|
||||
use super::TraceHandler;
|
||||
use crate::execution_step::CatchableError;
|
||||
use crate::execution_step::LastError;
|
||||
use crate::execution_step::RSecurityTetraplet;
|
||||
use crate::log_instruction;
|
||||
use crate::ExecutionError;
|
||||
use crate::JValue;
|
||||
|
||||
use air_parser::ast::Fail;
|
||||
use polyplets::SecurityTetraplet;
|
||||
@ -48,43 +51,40 @@ fn fail_with_literals<'i>(
|
||||
fail: &Fail<'_>,
|
||||
exec_ctx: &mut ExecutionCtx<'i>,
|
||||
) -> ExecutionResult<()> {
|
||||
let fail_error = CatchableError::FailWithoutXorError {
|
||||
ret_code,
|
||||
error_message: error_message.to_string(),
|
||||
};
|
||||
let fail_error = Rc::new(fail_error);
|
||||
let instruction = fail.to_string();
|
||||
// TODO: decouple error object creation into a separate function
|
||||
let error_object = serde_json::json!({
|
||||
"error_code": ret_code,
|
||||
"message": error_message,
|
||||
"instruction": fail.to_string(),
|
||||
});
|
||||
let error_object = Rc::new(error_object);
|
||||
|
||||
// TODO: wrap exec.init_peer_id in Rc
|
||||
let literal_tetraplet = SecurityTetraplet::literal_tetraplet(exec_ctx.init_peer_id.clone());
|
||||
let literal_tetraplet = Rc::new(RefCell::new(literal_tetraplet));
|
||||
|
||||
let last_error = LastErrorDescriptor::new(
|
||||
fail_error.clone(),
|
||||
instruction,
|
||||
// init_peer_id is used here in order to obtain determinism,
|
||||
// so %last_error%.peer_id will produce the same result on each peer
|
||||
exec_ctx.init_peer_id.clone(),
|
||||
Some(literal_tetraplet),
|
||||
);
|
||||
exec_ctx.last_error = Some(last_error);
|
||||
|
||||
update_context_state(exec_ctx);
|
||||
|
||||
Err(fail_error.into())
|
||||
fail_with_error_object(exec_ctx, error_object, Some(literal_tetraplet))
|
||||
}
|
||||
|
||||
fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> {
|
||||
let last_error = match &exec_ctx.last_error {
|
||||
Some(last_error) => last_error.error.clone(),
|
||||
None => return Ok(()),
|
||||
};
|
||||
let LastError { error, tetraplet } = exec_ctx.last_error_descriptor.last_error();
|
||||
|
||||
update_context_state(exec_ctx);
|
||||
Err(last_error.into())
|
||||
// to avoid warnings from https://github.com/rust-lang/rust/issues/59159
|
||||
let error = error.clone();
|
||||
let tetraplet = tetraplet.clone();
|
||||
|
||||
fail_with_error_object(exec_ctx, error, tetraplet)
|
||||
}
|
||||
|
||||
fn update_context_state(exec_ctx: &mut ExecutionCtx<'_>) {
|
||||
fn fail_with_error_object(
|
||||
exec_ctx: &mut ExecutionCtx<'_>,
|
||||
error: Rc<JValue>,
|
||||
tetraplet: Option<RSecurityTetraplet>,
|
||||
) -> ExecutionResult<()> {
|
||||
exec_ctx
|
||||
.last_error_descriptor
|
||||
.set_from_error_object(error.clone(), tetraplet);
|
||||
exec_ctx.subtree_complete = false;
|
||||
exec_ctx.last_error_could_be_set = false;
|
||||
|
||||
Err(ExecutionError::Catchable(Rc::new(CatchableError::UserError { error })))
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ impl<'i> super::ExecutableInstruction<'i> for Match<'i> {
|
||||
)?;
|
||||
|
||||
if !are_values_equal {
|
||||
return Err(CatchableError::MatchWithoutXorError.into());
|
||||
return Err(CatchableError::MatchValuesNotEqual.into());
|
||||
}
|
||||
|
||||
self.instruction.execute(exec_ctx, trace_ctx)
|
||||
|
@ -35,7 +35,7 @@ impl<'i> super::ExecutableInstruction<'i> for MisMatch<'i> {
|
||||
)?;
|
||||
|
||||
if are_values_equal {
|
||||
return Err(CatchableError::MismatchWithoutXorError.into());
|
||||
return Err(CatchableError::MismatchValuesEqual.into());
|
||||
}
|
||||
|
||||
self.instruction.execute(exec_ctx, trace_ctx)
|
||||
|
@ -34,7 +34,6 @@ pub(crate) use fold::FoldState;
|
||||
|
||||
use super::boxed_value::ScalarRef;
|
||||
use super::boxed_value::ValueAggregate;
|
||||
use super::execution_context::*;
|
||||
use super::ExecutionCtx;
|
||||
use super::ExecutionError;
|
||||
use super::ExecutionResult;
|
||||
@ -47,66 +46,18 @@ use air_parser::ast::Instruction;
|
||||
/// Executes instruction and updates last error if needed.
|
||||
macro_rules! execute {
|
||||
($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {{
|
||||
let result = $instr.execute($exec_ctx, $trace_ctx);
|
||||
|
||||
if !$exec_ctx.last_error_could_be_set {
|
||||
return result;
|
||||
match $instr.execute($exec_ctx, $trace_ctx) {
|
||||
Err(e) => {
|
||||
$exec_ctx.last_error_descriptor.try_to_set_from_ingredients(
|
||||
&e,
|
||||
$instr.to_string(),
|
||||
$exec_ctx.current_peer_id.to_string(),
|
||||
None,
|
||||
);
|
||||
Err(e)
|
||||
}
|
||||
v => v,
|
||||
}
|
||||
|
||||
let execution_error = match result {
|
||||
Err(e) => e,
|
||||
v => return v,
|
||||
};
|
||||
|
||||
let catchable_error = match execution_error {
|
||||
// handle only catchable errors
|
||||
crate::execution_step::ExecutionError::Catchable(e) => e,
|
||||
e => return Err(e),
|
||||
};
|
||||
|
||||
let instruction = $self.to_string();
|
||||
let last_error = LastErrorDescriptor::new(
|
||||
catchable_error.clone(),
|
||||
instruction,
|
||||
$exec_ctx.current_peer_id.to_string(),
|
||||
None,
|
||||
);
|
||||
$exec_ctx.last_error = Some(last_error);
|
||||
|
||||
Err(catchable_error.into())
|
||||
}};
|
||||
}
|
||||
|
||||
/// Executes match/mismatch instructions and updates last error if error type wasn't
|
||||
/// MatchWithoutXorError or MismatchWithoutXorError.
|
||||
macro_rules! execute_match_or_mismatch {
|
||||
($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {{
|
||||
let result = $instr.execute($exec_ctx, $trace_ctx);
|
||||
let execution_error = match result {
|
||||
Err(e) => e,
|
||||
v => return v,
|
||||
};
|
||||
|
||||
if !$exec_ctx.last_error_could_be_set || execution_error.is_match_or_mismatch() {
|
||||
return Err(execution_error);
|
||||
}
|
||||
|
||||
let catchable_error = match execution_error {
|
||||
// handle only catchable errors
|
||||
crate::execution_step::ExecutionError::Catchable(e) => e,
|
||||
e => return Err(e),
|
||||
};
|
||||
|
||||
let instruction = $self.to_string();
|
||||
let last_error = LastErrorDescriptor::new(
|
||||
catchable_error.clone(),
|
||||
instruction,
|
||||
$exec_ctx.current_peer_id.to_string(),
|
||||
None,
|
||||
);
|
||||
$exec_ctx.last_error = Some(last_error);
|
||||
|
||||
Err(catchable_error.into())
|
||||
}};
|
||||
}
|
||||
|
||||
@ -131,10 +82,8 @@ impl<'i> ExecutableInstruction<'i> for Instruction<'i> {
|
||||
Instruction::Par(par) => execute!(self, par, exec_ctx, trace_ctx),
|
||||
Instruction::Seq(seq) => execute!(self, seq, exec_ctx, trace_ctx),
|
||||
Instruction::Xor(xor) => execute!(self, xor, exec_ctx, trace_ctx),
|
||||
|
||||
// match/mismatch shouldn't rewrite last_error
|
||||
Instruction::Match(match_) => execute_match_or_mismatch!(self, match_, exec_ctx, trace_ctx),
|
||||
Instruction::MisMatch(mismatch) => execute_match_or_mismatch!(self, mismatch, exec_ctx, trace_ctx),
|
||||
Instruction::Match(match_) => execute!(self, match_, exec_ctx, trace_ctx),
|
||||
Instruction::MisMatch(mismatch) => execute!(self, mismatch, exec_ctx, trace_ctx),
|
||||
|
||||
Instruction::Error => unreachable!("should not execute if parsing succeeded. QED."),
|
||||
}
|
||||
|
@ -95,8 +95,7 @@ fn prepare_par_result(
|
||||
) -> ExecutionResult<()> {
|
||||
match (left_result, right_result) {
|
||||
(SubtreeResult::Succeeded, _) | (_, SubtreeResult::Succeeded) => {
|
||||
// clear the last error in case of par succeeded
|
||||
exec_ctx.last_error = None;
|
||||
exec_ctx.last_error_descriptor.meet_par_successed_end();
|
||||
Ok(())
|
||||
}
|
||||
(SubtreeResult::Failed(_), SubtreeResult::Failed(err)) => Err(err),
|
||||
|
@ -30,7 +30,7 @@ impl<'i> super::ExecutableInstruction<'i> for Xor<'i> {
|
||||
match self.0.execute(exec_ctx, trace_ctx) {
|
||||
Err(e) if e.is_catchable() => {
|
||||
exec_ctx.subtree_complete = true;
|
||||
exec_ctx.last_error_could_be_set = true;
|
||||
exec_ctx.last_error_descriptor.meet_xor_right_branch();
|
||||
print_xor_log(&e);
|
||||
|
||||
self.1.execute(exec_ctx, trace_ctx)
|
||||
|
@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
pub(crate) use super::Joinable;
|
||||
|
||||
use super::Joinable;
|
||||
use super::LastErrorAffectable;
|
||||
use super::Stream;
|
||||
use crate::execution_step::lambda_applier::LambdaError;
|
||||
use crate::JValue;
|
||||
@ -60,15 +60,15 @@ pub enum CatchableError {
|
||||
|
||||
/// This error type is produced by a match to notify xor that compared values aren't equal.
|
||||
#[error("match is used without corresponding xor")]
|
||||
MatchWithoutXorError,
|
||||
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")]
|
||||
MismatchWithoutXorError,
|
||||
MismatchValuesEqual,
|
||||
|
||||
/// This error type is produced by a fail instruction.
|
||||
#[error("fail with ret_code '{ret_code}' and error_message '{error_message}' is used without corresponding xor")]
|
||||
FailWithoutXorError { ret_code: i64, error_message: String },
|
||||
#[error("fail with '{error}' is used without corresponding xor")]
|
||||
UserError { error: Rc<JValue> },
|
||||
|
||||
/// An error occurred while trying to apply lambda to a value.
|
||||
#[error(transparent)]
|
||||
@ -98,6 +98,15 @@ impl ToErrorCode for CatchableError {
|
||||
}
|
||||
}
|
||||
|
||||
impl LastErrorAffectable for CatchableError {
|
||||
fn affects_last_error(&self) -> bool {
|
||||
!matches!(
|
||||
self,
|
||||
CatchableError::MatchValuesNotEqual | CatchableError::MismatchValuesEqual
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! log_join {
|
||||
($($args:tt)*) => {
|
||||
log::info!(target: air_log_targets::JOIN_BEHAVIOUR, $($args)*)
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
use super::CatchableError;
|
||||
use super::Joinable;
|
||||
use super::LastErrorAffectable;
|
||||
use super::UncatchableError;
|
||||
use crate::ToErrorCode;
|
||||
|
||||
@ -47,7 +48,7 @@ impl ExecutionError {
|
||||
match self {
|
||||
ExecutionError::Catchable(catchable) => matches!(
|
||||
catchable.as_ref(),
|
||||
CatchableError::MatchWithoutXorError | CatchableError::MismatchWithoutXorError
|
||||
CatchableError::MatchValuesNotEqual | CatchableError::MismatchValuesEqual
|
||||
),
|
||||
_ => false,
|
||||
}
|
||||
@ -71,6 +72,7 @@ macro_rules! trace_to_exec_err {
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
impl ToErrorCode for ExecutionError {
|
||||
fn to_error_code(&self) -> i64 {
|
||||
match self {
|
||||
@ -84,7 +86,16 @@ impl Joinable for ExecutionError {
|
||||
fn is_joinable(&self) -> bool {
|
||||
match self {
|
||||
ExecutionError::Catchable(err) => err.is_joinable(),
|
||||
_ => false,
|
||||
ExecutionError::Uncatchable(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LastErrorAffectable for ExecutionError {
|
||||
fn affects_last_error(&self) -> bool {
|
||||
match self {
|
||||
ExecutionError::Catchable(err) => err.affects_last_error(),
|
||||
ExecutionError::Uncatchable(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,19 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Display for LastErrorPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use LastErrorPath::*;
|
||||
|
||||
match self {
|
||||
Instruction => write!(f, ".$.instruction"),
|
||||
Message => write!(f, ".$.msg"),
|
||||
PeerId => write!(f, ".$.peer_id"),
|
||||
None => write!(f, ""),
|
||||
}
|
||||
}
|
||||
/// This trait is intended to figuring out whether a last error should be set or not.
|
||||
pub(crate) trait LastErrorAffectable {
|
||||
/// 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;
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
mod catchable_errors;
|
||||
mod execution_errors;
|
||||
mod joinable;
|
||||
mod last_error_affectable;
|
||||
mod uncatchable_errors;
|
||||
|
||||
pub use catchable_errors::CatchableError;
|
||||
@ -24,5 +25,6 @@ pub use execution_errors::ExecutionError;
|
||||
pub use uncatchable_errors::UncatchableError;
|
||||
|
||||
pub(crate) use joinable::Joinable;
|
||||
pub(crate) use last_error_affectable::LastErrorAffectable;
|
||||
|
||||
use super::Stream;
|
||||
|
@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use super::LastError;
|
||||
use super::LastErrorDescriptor;
|
||||
use super::LastErrorWithTetraplet;
|
||||
use super::Scalars;
|
||||
use super::Streams;
|
||||
|
||||
@ -44,11 +44,7 @@ pub(crate) struct ExecutionCtx<'i> {
|
||||
|
||||
/// Last error produced by local service.
|
||||
/// None means that there weren't any error.
|
||||
pub(crate) last_error: Option<LastErrorDescriptor>,
|
||||
|
||||
/// 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.
|
||||
pub(crate) last_error_could_be_set: bool,
|
||||
pub(crate) last_error_descriptor: LastErrorDescriptor,
|
||||
|
||||
/// Indicates that previous executed subtree is complete.
|
||||
/// A subtree treats as a complete if all subtree elements satisfy the following rules:
|
||||
@ -84,18 +80,14 @@ impl<'i> ExecutionCtx<'i> {
|
||||
current_peer_id,
|
||||
init_peer_id,
|
||||
subtree_complete: true,
|
||||
last_error_could_be_set: true,
|
||||
last_call_request_id,
|
||||
call_results,
|
||||
..<_>::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn last_error(&self) -> LastErrorWithTetraplet {
|
||||
match &self.last_error {
|
||||
Some(error_descriptor) => LastErrorWithTetraplet::from_error_descriptor(error_descriptor, self),
|
||||
None => <_>::default(),
|
||||
}
|
||||
pub(crate) fn last_error(&self) -> &LastError {
|
||||
self.last_error_descriptor.last_error()
|
||||
}
|
||||
|
||||
pub(crate) fn next_call_request_id(&mut self) -> u32 {
|
||||
|
@ -1,96 +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.
|
||||
*/
|
||||
|
||||
use super::ExecutionCtx;
|
||||
use crate::execution_step::CatchableError;
|
||||
use crate::execution_step::RSecurityTetraplet;
|
||||
use crate::SecurityTetraplet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// This struct is intended to track the last arisen error.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LastErrorDescriptor {
|
||||
pub(crate) error: Rc<CatchableError>,
|
||||
pub(crate) instruction: String,
|
||||
pub(crate) peer_id: String,
|
||||
pub(crate) tetraplet: Option<RSecurityTetraplet>,
|
||||
}
|
||||
|
||||
/// This type is a serialization target for last error. It means that on the AIR script side
|
||||
/// %last_error% will have such type.
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LastError {
|
||||
/// Text representation of an instruction that caused the last error.
|
||||
pub instruction: String,
|
||||
|
||||
/// Text representation of an error message.
|
||||
pub msg: String,
|
||||
|
||||
/// Id of a peer where an error occurred.
|
||||
pub peer_id: String,
|
||||
}
|
||||
|
||||
/// Helper struct to return last error with tetraplets from the last_error ExecutionCtx method.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct LastErrorWithTetraplet {
|
||||
pub(crate) last_error: LastError,
|
||||
pub(crate) tetraplet: RSecurityTetraplet,
|
||||
}
|
||||
|
||||
impl<'s> LastErrorWithTetraplet {
|
||||
pub(crate) fn from_error_descriptor(descriptor: &LastErrorDescriptor, ctx: &ExecutionCtx<'_>) -> Self {
|
||||
let last_error = descriptor.serialize();
|
||||
let tetraplet = descriptor.tetraplet.clone().unwrap_or_else(|| {
|
||||
Rc::new(RefCell::new(SecurityTetraplet::literal_tetraplet(
|
||||
ctx.init_peer_id.clone(),
|
||||
)))
|
||||
});
|
||||
|
||||
Self { last_error, tetraplet }
|
||||
}
|
||||
}
|
||||
|
||||
impl LastErrorDescriptor {
|
||||
pub(crate) fn new(
|
||||
error: Rc<CatchableError>,
|
||||
instruction: String,
|
||||
peer_id: String,
|
||||
tetraplet: Option<RSecurityTetraplet>,
|
||||
) -> Self {
|
||||
Self {
|
||||
error,
|
||||
instruction,
|
||||
peer_id,
|
||||
tetraplet,
|
||||
}
|
||||
}
|
||||
|
||||
// serialize error to LastError wrapped in JValue
|
||||
pub(crate) fn serialize(&self) -> LastError {
|
||||
let error = self.error.to_string();
|
||||
|
||||
LastError {
|
||||
msg: error,
|
||||
instruction: self.instruction.clone(),
|
||||
peer_id: self.peer_id.clone(),
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2021 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 crate::execution_step::LastErrorAffectable;
|
||||
use crate::execution_step::RSecurityTetraplet;
|
||||
use crate::JValue;
|
||||
use crate::ToErrorCode;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 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
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LastError {
|
||||
/// Error object that represents the last occurred error.
|
||||
pub error: Rc<JValue>,
|
||||
|
||||
/// Tetraplet that identify host where the error occurred.
|
||||
pub tetraplet: Option<RSecurityTetraplet>,
|
||||
}
|
||||
|
||||
pub(crate) struct LastErrorDescriptor {
|
||||
last_error: LastError,
|
||||
|
||||
/// 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.
|
||||
/// This allows to write a simple code to handle bubbling error up.
|
||||
error_can_be_set: bool,
|
||||
}
|
||||
|
||||
impl<'s> LastErrorDescriptor {
|
||||
pub(crate) fn try_to_set_from_ingredients(
|
||||
&mut self,
|
||||
error: &(impl ToString + LastErrorAffectable + ToErrorCode),
|
||||
instruction: impl Into<String>,
|
||||
peer_id: impl Into<String>,
|
||||
tetraplet: Option<RSecurityTetraplet>,
|
||||
) -> bool {
|
||||
// this check is optimization to prevent creation of an error object in case if error
|
||||
// couldn't be set
|
||||
if !self.error_can_be_set || !error.affects_last_error() {
|
||||
return false;
|
||||
}
|
||||
let error_object = serde_json::json!({
|
||||
"error_code": error.to_error_code(),
|
||||
"message": error.to_string(),
|
||||
"instruction": instruction.into(),
|
||||
"peer_id": peer_id.into(),
|
||||
});
|
||||
|
||||
self.set_from_error_object(Rc::new(error_object), tetraplet);
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn set_from_error_object(&mut self, error: Rc<JValue>, tetraplet: Option<RSecurityTetraplet>) {
|
||||
self.last_error = LastError { error, tetraplet };
|
||||
self.error_can_be_set = false;
|
||||
}
|
||||
|
||||
pub(crate) fn last_error(&self) -> &LastError {
|
||||
&self.last_error
|
||||
}
|
||||
|
||||
pub(crate) fn meet_xor_right_branch(&mut self) {
|
||||
self.error_can_be_set = true;
|
||||
}
|
||||
|
||||
pub(crate) fn meet_par_successed_end(&mut self) {
|
||||
self.error_can_be_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LastErrorDescriptor {
|
||||
fn default() -> Self {
|
||||
let last_error = LastError {
|
||||
error: Rc::new(JValue::Null),
|
||||
tetraplet: None,
|
||||
};
|
||||
|
||||
Self {
|
||||
last_error,
|
||||
error_can_be_set: true,
|
||||
}
|
||||
}
|
||||
}
|
@ -15,13 +15,13 @@
|
||||
*/
|
||||
|
||||
mod context;
|
||||
mod error_descriptor;
|
||||
mod last_error_descriptor;
|
||||
mod scalar_variables;
|
||||
mod streams_variables;
|
||||
|
||||
pub use last_error_descriptor::LastError;
|
||||
|
||||
pub(crate) use context::*;
|
||||
pub use error_descriptor::LastError;
|
||||
pub(crate) use error_descriptor::LastErrorDescriptor;
|
||||
pub(crate) use error_descriptor::LastErrorWithTetraplet;
|
||||
pub(crate) use last_error_descriptor::LastErrorDescriptor;
|
||||
pub(crate) use scalar_variables::*;
|
||||
pub(crate) use streams_variables::*;
|
||||
|
@ -39,7 +39,9 @@ pub(super) use boxed_value::ScalarRef;
|
||||
pub(super) use boxed_value::Stream;
|
||||
pub(super) use boxed_value::ValueAggregate;
|
||||
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 air_trace_handler::TraceHandler;
|
||||
|
||||
|
@ -18,7 +18,6 @@ use super::SecurityTetraplets;
|
||||
use crate::execution_step::boxed_value::JValuable;
|
||||
use crate::execution_step::boxed_value::Variable;
|
||||
use crate::execution_step::execution_context::ExecutionCtx;
|
||||
use crate::execution_step::execution_context::LastErrorWithTetraplet;
|
||||
use crate::execution_step::ExecutionResult;
|
||||
use crate::execution_step::RSecurityTetraplet;
|
||||
use crate::JValue;
|
||||
@ -26,8 +25,8 @@ use crate::LambdaAST;
|
||||
use crate::SecurityTetraplet;
|
||||
|
||||
use air_parser::ast;
|
||||
use air_parser::ast::LastErrorPath;
|
||||
|
||||
use crate::execution_step::lambda_applier::select;
|
||||
use serde_json::json;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
@ -41,7 +40,7 @@ pub(crate) fn resolve_to_args<'i>(
|
||||
|
||||
match value {
|
||||
InitPeerId => prepare_const(ctx.init_peer_id.clone(), ctx),
|
||||
LastError(path) => prepare_last_error(path, ctx),
|
||||
LastError(error_accessor) => prepare_last_error(error_accessor, ctx),
|
||||
Literal(value) => prepare_const(value.to_string(), ctx),
|
||||
Boolean(value) => prepare_const(*value, ctx),
|
||||
Number(value) => prepare_const(value, ctx),
|
||||
@ -63,22 +62,29 @@ pub(crate) fn prepare_const(
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn prepare_last_error(
|
||||
path: &LastErrorPath,
|
||||
ctx: &ExecutionCtx<'_>,
|
||||
pub(crate) fn prepare_last_error<'i>(
|
||||
error_accessor: &Option<LambdaAST<'i>>,
|
||||
ctx: &ExecutionCtx<'i>,
|
||||
) -> ExecutionResult<(JValue, SecurityTetraplets)> {
|
||||
let LastErrorWithTetraplet {
|
||||
last_error,
|
||||
tetraplet: tetraplets,
|
||||
} = ctx.last_error();
|
||||
let jvalue = match path {
|
||||
LastErrorPath::Instruction => JValue::String(last_error.instruction),
|
||||
LastErrorPath::Message => JValue::String(last_error.msg),
|
||||
LastErrorPath::PeerId => JValue::String(last_error.peer_id),
|
||||
LastErrorPath::None => json!(last_error),
|
||||
use crate::LastError;
|
||||
|
||||
let LastError { error, tetraplet } = ctx.last_error();
|
||||
|
||||
let jvalue = match error_accessor {
|
||||
Some(error_accessor) => select(error.as_ref(), error_accessor.iter(), ctx)?,
|
||||
None => error.as_ref(),
|
||||
};
|
||||
|
||||
Ok((jvalue, vec![tetraplets]))
|
||||
let tetraplets = match tetraplet {
|
||||
Some(tetraplet) => vec![tetraplet.clone()],
|
||||
None => {
|
||||
let tetraplet = SecurityTetraplet::literal_tetraplet(&ctx.init_peer_id);
|
||||
let tetraplet = Rc::new(RefCell::new(tetraplet));
|
||||
vec![tetraplet]
|
||||
}
|
||||
};
|
||||
|
||||
Ok((jvalue.clone(), tetraplets))
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_variable<'ctx, 'i>(
|
||||
|
@ -137,7 +137,7 @@ fn ap_with_last_error() {
|
||||
let script = format!(
|
||||
r#"
|
||||
(seq
|
||||
(ap %last_error%.$.msg $stream)
|
||||
(ap %last_error% $stream)
|
||||
(call "{}" ("" "") [$stream])
|
||||
)
|
||||
"#,
|
||||
@ -147,7 +147,7 @@ fn ap_with_last_error() {
|
||||
let result = checked_call_vm!(vm_1, "", script, "", "");
|
||||
|
||||
let actual_trace = trace_from_result(&result);
|
||||
let expected_state = vec![executed_state::ap(Some(0)), executed_state::scalar(json!([""]))];
|
||||
let expected_state = vec![executed_state::ap(Some(0)), executed_state::scalar(json!([null]))];
|
||||
|
||||
assert_eq!(actual_trace, expected_state);
|
||||
assert!(result.next_peer_pks.is_empty());
|
||||
|
@ -20,8 +20,6 @@ use air_test_utils::prelude::*;
|
||||
use fstrings::f;
|
||||
use fstrings::format_args_f;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
#[test]
|
||||
fn fail_with_last_error() {
|
||||
let local_peer_id = "local_peer_id";
|
||||
@ -36,8 +34,14 @@ fn fail_with_last_error() {
|
||||
|
||||
let result = call_vm!(vm, "", script, "", "");
|
||||
|
||||
let expected_error =
|
||||
CatchableError::LocalServiceError(1, Rc::new(r#""failed result from fallible_call_service""#.to_string()));
|
||||
let expected_error = CatchableError::UserError {
|
||||
error: rc!(json!({
|
||||
"error_code": 10000i64,
|
||||
"instruction": r#"call "local_peer_id" ("service_id_1" "local_fn_name") [] result_1"#,
|
||||
"message": r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#,
|
||||
"peer_id": "local_peer_id",
|
||||
})),
|
||||
};
|
||||
assert!(check_error(&result, expected_error));
|
||||
}
|
||||
|
||||
@ -56,9 +60,12 @@ fn fail_with_literals() {
|
||||
|
||||
let result = call_vm!(vm, "", script, "", "");
|
||||
|
||||
let expected_error = CatchableError::FailWithoutXorError {
|
||||
ret_code: 1337,
|
||||
error_message: "error message".to_string(),
|
||||
let expected_error = CatchableError::UserError {
|
||||
error: rc!(json!( {
|
||||
"error_code": 1337i64,
|
||||
"instruction": "fail 1337 error message",
|
||||
"message": "error message",
|
||||
})),
|
||||
};
|
||||
assert!(check_error(&result, expected_error));
|
||||
}
|
||||
|
@ -194,12 +194,12 @@ fn match_without_xor() {
|
||||
let result = call_vm!(set_variable_vm, "", &script, "", "");
|
||||
let result = call_vm!(vm, "", &script, "", result.data);
|
||||
|
||||
let expected_error = CatchableError::MatchWithoutXorError;
|
||||
let expected_error = CatchableError::MatchValuesNotEqual;
|
||||
assert!(check_error(&result, expected_error));
|
||||
|
||||
let result = call_vm!(vm, "", script, "", result.data);
|
||||
|
||||
let expected_error = CatchableError::MatchWithoutXorError;
|
||||
let expected_error = CatchableError::MatchValuesNotEqual;
|
||||
assert!(check_error(&result, expected_error));
|
||||
}
|
||||
|
||||
|
@ -144,12 +144,12 @@ fn mismatch_without_xor() {
|
||||
let result = call_vm!(set_variable_vm, "asd", &script, "", "");
|
||||
let result = call_vm!(vm, "asd", &script, "", result.data);
|
||||
|
||||
let expected_error = CatchableError::MismatchWithoutXorError;
|
||||
let expected_error = CatchableError::MismatchValuesEqual;
|
||||
assert!(check_error(&result, expected_error));
|
||||
|
||||
let result = call_vm!(vm, "asd", script, "", result.data);
|
||||
|
||||
let expected_error = CatchableError::MismatchWithoutXorError;
|
||||
let expected_error = CatchableError::MismatchValuesEqual;
|
||||
assert!(check_error(&result, expected_error));
|
||||
}
|
||||
|
||||
|
@ -176,7 +176,7 @@ fn last_error_with_xor() {
|
||||
r#"
|
||||
(xor
|
||||
(call "{0}" ("service_id_1" "local_fn_name") [] result)
|
||||
(call "{1}" ("service_id_2" "local_fn_name") [%last_error%.$.msg] result)
|
||||
(call "{1}" ("service_id_2" "local_fn_name") [%last_error%.$.message] result)
|
||||
)"#,
|
||||
faillible_peer_id, local_peer_id,
|
||||
);
|
||||
|
@ -14,8 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use air::LastError;
|
||||
use air::SecurityTetraplet;
|
||||
use air::{CatchableError, ExecutionError, LambdaError, SecurityTetraplet};
|
||||
use air_test_utils::prelude::*;
|
||||
|
||||
use fstrings::f;
|
||||
@ -27,11 +26,11 @@ use std::rc::Rc;
|
||||
type ArgToCheck<T> = Rc<RefCell<Option<T>>>;
|
||||
|
||||
fn create_check_service_closure(
|
||||
args_to_check: ArgToCheck<LastError>,
|
||||
args_to_check: ArgToCheck<JValue>,
|
||||
tetraplets_to_check: ArgToCheck<Vec<Vec<SecurityTetraplet>>>,
|
||||
) -> CallServiceClosure {
|
||||
Box::new(move |params| -> CallServiceResult {
|
||||
let mut call_args: Vec<LastError> =
|
||||
let mut call_args: Vec<JValue> =
|
||||
serde_json::from_value(JValue::Array(params.arguments)).expect("json deserialization shouldn't fail");
|
||||
|
||||
let result = json!(params.tetraplets);
|
||||
@ -69,13 +68,14 @@ fn last_error_tetraplets() {
|
||||
let _ = checked_call_vm!(local_vm, "asd", script, "", result.data);
|
||||
|
||||
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
|
||||
let last_error = actual_value.as_object().unwrap();
|
||||
assert_eq!(
|
||||
actual_value.instruction,
|
||||
last_error.get("instruction").unwrap(),
|
||||
r#"call "fallible_peer_id" ("fallible_call_service" "") [service_id] client_result"#
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
actual_value.msg,
|
||||
last_error.get("message").unwrap(),
|
||||
r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#
|
||||
);
|
||||
|
||||
@ -109,8 +109,8 @@ fn not_clear_last_error_in_match() {
|
||||
(call "unknown_peer" ("" "") [%last_error%])
|
||||
)
|
||||
(seq
|
||||
(null)
|
||||
(call "{1}" ("" "") [%last_error%])
|
||||
(null)
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -122,8 +122,7 @@ fn not_clear_last_error_in_match() {
|
||||
let _ = checked_call_vm!(local_vm, "asd", &script, "", result.data);
|
||||
|
||||
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
|
||||
assert_eq!(actual_value.instruction, "");
|
||||
assert_eq!(actual_value.msg, "");
|
||||
assert_eq!(actual_value, JValue::Null);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -162,8 +161,7 @@ fn not_clear_last_error_in_mismatch() {
|
||||
let _ = checked_call_vm!(local_vm, "asd", &script, "", result.data);
|
||||
|
||||
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
|
||||
assert_eq!(actual_value.instruction, "");
|
||||
assert_eq!(actual_value.msg, "");
|
||||
assert_eq!(actual_value, JValue::Null);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -194,7 +192,8 @@ fn track_current_peer_id() {
|
||||
let _ = checked_call_vm!(local_vm, "asd", script, "", result.data);
|
||||
|
||||
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
|
||||
assert_eq!(actual_value.peer_id, fallible_peer_id);
|
||||
let last_error = actual_value.as_object().unwrap();
|
||||
assert_eq!(last_error.get("peer_id").unwrap(), fallible_peer_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -211,7 +210,7 @@ fn variable_names_shown_in_error() {
|
||||
(call "{set_variable_vm_peer_id}" ("" "") [""] -relay-)
|
||||
(call -relay- ("" "") [])
|
||||
)
|
||||
(call "{echo_vm_peer_id}" ("" "") [%last_error%.$.msg])
|
||||
(call "{echo_vm_peer_id}" ("" "") [%last_error%.$.message])
|
||||
)
|
||||
"#);
|
||||
|
||||
@ -226,3 +225,102 @@ fn variable_names_shown_in_error() {
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_initialized_last_error() {
|
||||
let vm_peer_id = "vm_peer_id";
|
||||
let args = Rc::new(RefCell::new(None));
|
||||
let tetraplets = Rc::new(RefCell::new(None));
|
||||
let mut vm = create_avm(
|
||||
create_check_service_closure(args.clone(), tetraplets.clone()),
|
||||
vm_peer_id,
|
||||
);
|
||||
|
||||
let script = f!(r#"
|
||||
(seq
|
||||
(call "{vm_peer_id}" ("" "") [%last_error%])
|
||||
(null)
|
||||
)
|
||||
"#);
|
||||
|
||||
let init_peer_id = "init_peer_id";
|
||||
let _ = checked_call_vm!(vm, init_peer_id, script, "", "");
|
||||
|
||||
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
|
||||
assert_eq!(actual_value, JValue::Null);
|
||||
|
||||
let actual_tetraplets = (*tetraplets.borrow()).as_ref().unwrap().clone();
|
||||
assert_eq!(
|
||||
actual_tetraplets,
|
||||
vec![vec![SecurityTetraplet::new(init_peer_id, "", "", "")]]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn access_last_error_by_not_exists_field() {
|
||||
let fallible_peer_id = "fallible_peer_id";
|
||||
let mut fallible_vm = create_avm(fallible_call_service("fallible_call_service"), fallible_peer_id);
|
||||
|
||||
let local_peer_id = "local_peer_id";
|
||||
let mut local_vm = create_avm(echo_call_service(), local_peer_id);
|
||||
|
||||
let non_exists_field_name = "non_exists_field";
|
||||
let script = f!(r#"
|
||||
(xor
|
||||
(call "{fallible_peer_id}" ("fallible_call_service" "") [""])
|
||||
(call "{local_peer_id}" ("" "") [%last_error%.$.{non_exists_field_name}])
|
||||
)
|
||||
"#);
|
||||
|
||||
let result = checked_call_vm!(fallible_vm, "asd", &script, "", "");
|
||||
let result = call_vm!(local_vm, "asd", script, "", result.data);
|
||||
|
||||
let expected_error = ExecutionError::Catchable(rc!(CatchableError::LambdaApplierError(
|
||||
LambdaError::ValueNotContainSuchField {
|
||||
value: json!({
|
||||
"error_code": 10000i64,
|
||||
"instruction": r#"call "fallible_peer_id" ("fallible_call_service" "") [""] "#,
|
||||
"message": r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#,
|
||||
"peer_id": "fallible_peer_id",
|
||||
}),
|
||||
field_name: non_exists_field_name.to_string()
|
||||
}
|
||||
)));
|
||||
assert!(check_error(&result, expected_error));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_error_with_par_one_subtree_failed() {
|
||||
let fallible_peer_id = "fallible_peer_id";
|
||||
let fallible_call_service_name = "fallible_call_service";
|
||||
let mut fallible_vm = create_avm(fallible_call_service(fallible_call_service_name), fallible_peer_id);
|
||||
|
||||
let vm_peer_id = "local_peer_id";
|
||||
let args = Rc::new(RefCell::new(None));
|
||||
let tetraplets = Rc::new(RefCell::new(None));
|
||||
let mut vm = create_avm(
|
||||
create_check_service_closure(args.clone(), tetraplets.clone()),
|
||||
vm_peer_id,
|
||||
);
|
||||
let script = f!(r#"
|
||||
(seq
|
||||
(par
|
||||
(call "{fallible_peer_id}" ("{fallible_call_service_name}" "") [""])
|
||||
(call "{fallible_peer_id}" ("non_fallible_call_service" "") [""])
|
||||
)
|
||||
(call "{vm_peer_id}" ("" "") [%last_error%])
|
||||
)
|
||||
"#);
|
||||
|
||||
let result = checked_call_vm!(fallible_vm, "asd", &script, "", "");
|
||||
let _ = checked_call_vm!(vm, "asd", script, "", result.data);
|
||||
|
||||
let actual_value = (*args.borrow()).as_ref().unwrap().clone();
|
||||
let expected_value = json!({
|
||||
"error_code": 10000i64,
|
||||
"instruction": r#"call "fallible_peer_id" ("fallible_call_service" "") [""] "#,
|
||||
"message": r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#,
|
||||
"peer_id": fallible_peer_id
|
||||
});
|
||||
assert_eq!(actual_value, expected_value);
|
||||
}
|
||||
|
@ -251,9 +251,11 @@ fn fold_early_exit() {
|
||||
executed_state::scalar_string(unit_call_service_result),
|
||||
executed_state::scalar_string(unit_call_service_result),
|
||||
executed_state::service_failed(1, "failed result from fallible_call_service"),
|
||||
executed_state::scalar(
|
||||
json!({"instruction" : r#"call "error_trigger_id" ("error" "") [] "#, "msg": r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#, "peer_id": "error_trigger_id"}),
|
||||
),
|
||||
executed_state::scalar(json!({
|
||||
"error_code": 10000i64,
|
||||
"instruction" : r#"call "error_trigger_id" ("error" "") [] "#,
|
||||
"message": r#"Local service error, ret_code is 1, error message is '"failed result from fallible_call_service"'"#,
|
||||
"peer_id": "error_trigger_id"})),
|
||||
executed_state::scalar_string("last_peer"),
|
||||
];
|
||||
|
||||
|
@ -19,7 +19,8 @@ mod traits;
|
||||
use super::Variable;
|
||||
use super::VariableWithLambda;
|
||||
use crate::ast::ScalarWithLambda;
|
||||
use crate::parser::lexer::LastErrorPath;
|
||||
|
||||
use air_lambda_ast::LambdaAST;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@ -61,7 +62,7 @@ pub struct Triplet<'i> {
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub enum Value<'i> {
|
||||
InitPeerId,
|
||||
LastError(LastErrorPath),
|
||||
LastError(Option<LambdaAST<'i>>),
|
||||
Literal(&'i str),
|
||||
Number(Number),
|
||||
Boolean(bool),
|
||||
@ -78,7 +79,7 @@ pub enum CallOutputValue<'i> {
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub enum ApArgument<'i> {
|
||||
InitPeerId,
|
||||
LastError(LastErrorPath),
|
||||
LastError(Option<LambdaAST<'i>>),
|
||||
Literal(&'i str),
|
||||
Number(Number),
|
||||
Boolean(bool),
|
||||
|
@ -23,7 +23,7 @@ impl fmt::Display for Value<'_> {
|
||||
|
||||
match self {
|
||||
InitPeerId => write!(f, "%init_peer_id%"),
|
||||
LastError(error_path) => write!(f, "%last_error%{}", error_path),
|
||||
LastError(error_accessor) => display_last_error(f, error_accessor),
|
||||
Literal(literal) => write!(f, r#""{}""#, literal),
|
||||
Number(number) => write!(f, "{}", number),
|
||||
Boolean(bool) => write!(f, "{}", bool),
|
||||
@ -62,7 +62,7 @@ impl fmt::Display for ApArgument<'_> {
|
||||
|
||||
match self {
|
||||
InitPeerId => write!(f, "%init_peer_id%"),
|
||||
LastError(last_error) => write!(f, "{}", last_error),
|
||||
LastError(error_accessor) => display_last_error(f, error_accessor),
|
||||
Literal(str) => write!(f, r#""{}""#, str),
|
||||
Number(number) => write!(f, "{}", number),
|
||||
Boolean(bool) => write!(f, "{}", bool),
|
||||
@ -131,3 +131,13 @@ impl From<&Number> for serde_json::Value {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn display_last_error(
|
||||
f: &mut fmt::Formatter,
|
||||
last_error_accessor: &Option<LambdaAST>,
|
||||
) -> fmt::Result {
|
||||
match last_error_accessor {
|
||||
Some(accessor) => write!(f, "%last_error%.$.{}", air_lambda_ast::format_ast(accessor)),
|
||||
None => write!(f, "%last_error%"),
|
||||
}
|
||||
}
|
||||
|
@ -22,5 +22,4 @@ pub use instruction_arguments::*;
|
||||
pub use instructions::*;
|
||||
pub use values::*;
|
||||
|
||||
pub use crate::parser::lexer::LastErrorPath;
|
||||
pub use crate::parser::Span;
|
||||
|
@ -3,7 +3,6 @@ use crate::parser::ParserError;
|
||||
use crate::parser::VariableValidator;
|
||||
use crate::parser::Span;
|
||||
use crate::parser::lexer::Token;
|
||||
use crate::parser::lexer::LastErrorPath;
|
||||
use crate::parser::air_utils::*;
|
||||
use crate::make_user_error;
|
||||
|
||||
@ -141,11 +140,7 @@ FailBody: Fail<'input> = {
|
||||
ret_code,
|
||||
error_message,
|
||||
},
|
||||
<left: @L> <last_error_path:LastError> <right: @R> => {
|
||||
// it's possible to write smth like that `(fail %last_error%.msg)`, that would be ambigious
|
||||
if !matches!(last_error_path, LastErrorPath::None) {
|
||||
errors.push(make_user_error!(AmbiguousFailLastError, left, Token::Fail, right));
|
||||
}
|
||||
<left: @L> <l:LastError> <right: @R> => {
|
||||
Fail::LastError
|
||||
}
|
||||
}
|
||||
@ -177,7 +172,8 @@ Arg = Value;
|
||||
|
||||
Value: Value<'input> = {
|
||||
InitPeerId => Value::InitPeerId,
|
||||
<e:LastError> => Value::LastError(e),
|
||||
<LastError> => Value::LastError(None),
|
||||
<le:LastErrorWithLambda> => Value::LastError(Some(le)),
|
||||
<l:Literal> => Value::Literal(l),
|
||||
<n:Number> => Value::Number(n),
|
||||
<b:Boolean> => Value::Boolean(b),
|
||||
@ -190,7 +186,8 @@ Value: Value<'input> = {
|
||||
|
||||
ApArgument: ApArgument<'input> = {
|
||||
InitPeerId => ApArgument::InitPeerId,
|
||||
<e:LastError> => ApArgument::LastError(e),
|
||||
<LastError> => ApArgument::LastError(None),
|
||||
<le:LastErrorWithLambda> => ApArgument::LastError(Some(le)),
|
||||
<l:Literal> => ApArgument::Literal(l),
|
||||
<n:Number> => ApArgument::Number(n),
|
||||
<b:Boolean> => ApArgument::Boolean(b),
|
||||
@ -220,7 +217,8 @@ extern {
|
||||
Boolean => Token::Boolean(<bool>),
|
||||
|
||||
InitPeerId => Token::InitPeerId,
|
||||
LastError => Token::LastError(<LastErrorPath>),
|
||||
LastError => Token::LastError,
|
||||
LastErrorWithLambda => Token::LastErrorWithLambda(<LambdaAST<'input>>),
|
||||
|
||||
call => Token::Call,
|
||||
ap => Token::Ap,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
use super::errors::LexerError;
|
||||
use super::token::LastErrorPath;
|
||||
use super::token::Token;
|
||||
use super::LexerResult;
|
||||
|
||||
@ -201,23 +200,28 @@ fn string_to_token(input: &str, start_pos: usize) -> LexerResult<Token> {
|
||||
|
||||
fn parse_last_error(input: &str, start_pos: usize) -> LexerResult<Token<'_>> {
|
||||
let last_error_size = LAST_ERROR.len();
|
||||
let last_error_path = match &input[last_error_size..] {
|
||||
"" => LastErrorPath::None,
|
||||
// The second option with ! is needed for compatibility with flattening in "standard" lambda used in AIR.
|
||||
// However version without ! returns just a error, because the opposite is unsound.
|
||||
".$.instruction" | ".$.instruction!" => LastErrorPath::Instruction,
|
||||
".$.msg" | ".$.msg!" => LastErrorPath::Message,
|
||||
".$.peer_id" | ".$.peer_id!" => LastErrorPath::PeerId,
|
||||
path => {
|
||||
return Err(LexerError::LastErrorPathError(
|
||||
start_pos + last_error_size,
|
||||
start_pos + input.len(),
|
||||
path.to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
if input.len() == last_error_size {
|
||||
return Ok(Token::LastError);
|
||||
}
|
||||
|
||||
let last_error_size = last_error_size + 2;
|
||||
if input.len() <= last_error_size {
|
||||
return Err(LexerError::LambdaParserError(
|
||||
start_pos + last_error_size,
|
||||
start_pos + input.len(),
|
||||
"lambda AST applied to last error has not enough size".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let last_error_accessor = crate::parse_lambda(&input[last_error_size..]).map_err(|e| {
|
||||
LexerError::LambdaParserError(
|
||||
start_pos + last_error_size,
|
||||
start_pos + input.len(),
|
||||
e.to_string(),
|
||||
)
|
||||
})?;
|
||||
let last_error_token = Token::LastErrorWithLambda(last_error_accessor);
|
||||
|
||||
let last_error_token = Token::LastError(last_error_path);
|
||||
Ok(last_error_token)
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,6 @@ mod tests;
|
||||
|
||||
pub use air_lexer::AIRLexer;
|
||||
pub use errors::LexerError;
|
||||
pub use token::LastErrorPath;
|
||||
pub use token::Token;
|
||||
|
||||
pub(super) type LexerResult<T> = std::result::Result<T, LexerError>;
|
||||
|
@ -16,11 +16,11 @@
|
||||
|
||||
use super::air_lexer::Spanned;
|
||||
use super::AIRLexer;
|
||||
use super::LastErrorPath;
|
||||
use super::LexerError;
|
||||
use super::Token;
|
||||
|
||||
use air_lambda_parser::{LambdaAST, ValueAccessor};
|
||||
use air_lambda_parser::LambdaAST;
|
||||
use air_lambda_parser::ValueAccessor;
|
||||
|
||||
fn run_lexer(input: &str) -> Vec<Spanned<Token<'_>, usize, LexerError>> {
|
||||
let lexer = AIRLexer::new(input);
|
||||
@ -376,11 +376,7 @@ fn last_error() {
|
||||
|
||||
lexer_test(
|
||||
LAST_ERROR,
|
||||
Single(Ok((
|
||||
0,
|
||||
Token::LastError(LastErrorPath::None),
|
||||
LAST_ERROR.len(),
|
||||
))),
|
||||
Single(Ok((0, Token::LastError, LAST_ERROR.len()))),
|
||||
);
|
||||
}
|
||||
|
||||
@ -388,56 +384,49 @@ fn last_error() {
|
||||
fn last_error_instruction() {
|
||||
const LAST_ERROR: &str = r#"%last_error%.$.instruction"#;
|
||||
|
||||
lexer_test(
|
||||
LAST_ERROR,
|
||||
Single(Ok((
|
||||
0,
|
||||
Token::LastError(LastErrorPath::Instruction),
|
||||
LAST_ERROR.len(),
|
||||
))),
|
||||
);
|
||||
let token = Token::LastErrorWithLambda(unsafe {
|
||||
LambdaAST::new_unchecked(vec![ValueAccessor::FieldAccessByName {
|
||||
field_name: "instruction",
|
||||
}])
|
||||
});
|
||||
|
||||
lexer_test(LAST_ERROR, Single(Ok((0, token, LAST_ERROR.len()))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_error_msg() {
|
||||
const LAST_ERROR: &str = r#"%last_error%.$.msg"#;
|
||||
fn last_error_message() {
|
||||
const LAST_ERROR: &str = r#"%last_error%.$.message"#;
|
||||
|
||||
lexer_test(
|
||||
LAST_ERROR,
|
||||
Single(Ok((
|
||||
0,
|
||||
Token::LastError(LastErrorPath::Message),
|
||||
LAST_ERROR.len(),
|
||||
))),
|
||||
);
|
||||
let token = Token::LastErrorWithLambda(unsafe {
|
||||
LambdaAST::new_unchecked(vec![ValueAccessor::FieldAccessByName {
|
||||
field_name: "message",
|
||||
}])
|
||||
});
|
||||
lexer_test(LAST_ERROR, Single(Ok((0, token, LAST_ERROR.len()))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_error_peer_id() {
|
||||
const LAST_ERROR: &str = r#"%last_error%.$.peer_id"#;
|
||||
|
||||
lexer_test(
|
||||
LAST_ERROR,
|
||||
Single(Ok((
|
||||
0,
|
||||
Token::LastError(LastErrorPath::PeerId),
|
||||
LAST_ERROR.len(),
|
||||
))),
|
||||
);
|
||||
let token = Token::LastErrorWithLambda(unsafe {
|
||||
LambdaAST::new_unchecked(vec![ValueAccessor::FieldAccessByName {
|
||||
field_name: "peer_id",
|
||||
}])
|
||||
});
|
||||
lexer_test(LAST_ERROR, Single(Ok((0, token, LAST_ERROR.len()))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_error_incorrect_field() {
|
||||
fn last_error_non_standard_field() {
|
||||
const LAST_ERROR: &str = r#"%last_error%.$.asdasd"#;
|
||||
|
||||
lexer_test(
|
||||
LAST_ERROR,
|
||||
Single(Err(LexerError::LastErrorPathError(
|
||||
12,
|
||||
LAST_ERROR.len(),
|
||||
".$.asdasd".to_string(),
|
||||
))),
|
||||
);
|
||||
let token = Token::LastErrorWithLambda(unsafe {
|
||||
LambdaAST::new_unchecked(vec![ValueAccessor::FieldAccessByName {
|
||||
field_name: "asdasd",
|
||||
}])
|
||||
});
|
||||
lexer_test(LAST_ERROR, Single(Ok((0, token, LAST_ERROR.len()))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -14,8 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
mod traits;
|
||||
|
||||
use crate::LambdaAST;
|
||||
|
||||
use serde::Deserialize;
|
||||
@ -53,7 +51,8 @@ pub enum Token<'input> {
|
||||
Boolean(bool),
|
||||
|
||||
InitPeerId,
|
||||
LastError(LastErrorPath),
|
||||
LastError,
|
||||
LastErrorWithLambda(LambdaAST<'input>),
|
||||
|
||||
Call,
|
||||
Ap,
|
||||
@ -68,15 +67,3 @@ pub enum Token<'input> {
|
||||
Match,
|
||||
MisMatch,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub enum LastErrorPath {
|
||||
// %last_error%.instruction
|
||||
Instruction,
|
||||
// %last_error%.msg
|
||||
Message,
|
||||
// %last_error%.peer_id
|
||||
PeerId,
|
||||
// %last_error%
|
||||
None,
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ use super::dsl::*;
|
||||
use super::parse;
|
||||
use crate::ast::*;
|
||||
|
||||
use air_lambda_ast::{LambdaAST, ValueAccessor};
|
||||
|
||||
#[test]
|
||||
fn ap_with_literal() {
|
||||
let source_code = r#"
|
||||
@ -63,13 +65,17 @@ fn ap_with_bool() {
|
||||
#[test]
|
||||
fn ap_with_last_error() {
|
||||
let source_code = r#"
|
||||
(ap %last_error%.$.msg! $stream)
|
||||
(ap %last_error%.$.message! $stream)
|
||||
"#;
|
||||
|
||||
let actual = parse(source_code);
|
||||
let expected = ap(
|
||||
ApArgument::LastError(LastErrorPath::Message),
|
||||
Variable::stream("$stream", 33),
|
||||
ApArgument::LastError(Some(unsafe {
|
||||
LambdaAST::new_unchecked(vec![ValueAccessor::FieldAccessByName {
|
||||
field_name: "message",
|
||||
}])
|
||||
})),
|
||||
Variable::stream("$stream", 37),
|
||||
);
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -384,7 +384,7 @@ fn parse_last_error() {
|
||||
CallInstrValue::InitPeerId,
|
||||
CallInstrValue::Literal("service_id"),
|
||||
CallInstrValue::Literal("fn_name"),
|
||||
Rc::new(vec![Value::LastError(LastErrorPath::None)]),
|
||||
Rc::new(vec![Value::LastError(None)]),
|
||||
CallOutputValue::None,
|
||||
),
|
||||
null(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user