Refactor last error (#202)

This commit is contained in:
Mike Voronov 2021-12-28 16:59:55 +03:00 committed by GitHub
parent c1ff7c0688
commit 63160dd0f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1386 additions and 1223 deletions

View File

@ -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);

View File

@ -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)
}

View File

@ -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) => {

View File

@ -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 })))
}

View File

@ -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)

View File

@ -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)

View File

@ -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."),
}

View File

@ -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),

View File

@ -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)

View File

@ -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)*)

View File

@ -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,
}
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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 {

View File

@ -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(),
}
}
}

View File

@ -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,
}
}
}

View File

@ -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::*;

View File

@ -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;

View File

@ -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>(

View File

@ -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());

View File

@ -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));
}

View File

@ -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));
}

View File

@ -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));
}

View File

@ -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,
);

View File

@ -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);
}

View File

@ -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"),
];

View File

@ -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),

View File

@ -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%"),
}
}

View File

@ -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;

View File

@ -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

View File

@ -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)
}

View File

@ -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>;

View File

@ -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]

View File

@ -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,
}

View File

@ -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);

View File

@ -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(),