Implement fail with scalars (#205)

This commit is contained in:
Mike Voronov 2021-12-29 19:51:18 +03:00 committed by GitHub
parent 91021d8b40
commit e6193ea4de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1448 additions and 939 deletions

View File

@ -27,7 +27,7 @@ pub(super) fn apply_to_arg(
use ast::ApArgument::*; use ast::ApArgument::*;
let result = match argument { let result = match argument {
InitPeerId => apply_const(exec_ctx.init_peer_id.clone(), exec_ctx, trace_ctx), InitPeerId => apply_const(exec_ctx.init_peer_id.as_str(), exec_ctx, trace_ctx),
LastError(error_accessor) => apply_last_error(error_accessor, 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), Literal(value) => apply_const(*value, exec_ctx, trace_ctx),
Number(value) => apply_const(value, exec_ctx, trace_ctx), Number(value) => apply_const(value, exec_ctx, trace_ctx),
@ -41,7 +41,7 @@ pub(super) fn apply_to_arg(
fn apply_const(value: impl Into<JValue>, exec_ctx: &ExecutionCtx<'_>, trace_ctx: &TraceHandler) -> ValueAggregate { fn apply_const(value: impl Into<JValue>, exec_ctx: &ExecutionCtx<'_>, trace_ctx: &TraceHandler) -> ValueAggregate {
let value = Rc::new(value.into()); let value = Rc::new(value.into());
let tetraplet = SecurityTetraplet::literal_tetraplet(exec_ctx.init_peer_id.clone()); let tetraplet = SecurityTetraplet::literal_tetraplet(exec_ctx.init_peer_id.as_ref());
let tetraplet = Rc::new(RefCell::new(tetraplet)); let tetraplet = Rc::new(RefCell::new(tetraplet));
ValueAggregate::new(value, tetraplet, trace_ctx.trace_pos()) ValueAggregate::new(value, tetraplet, trace_ctx.trace_pos())

View File

@ -73,10 +73,10 @@ fn set_last_error<'i>(
current_peer_id current_peer_id
); );
let _ = exec_ctx.last_error_descriptor.try_to_set_from_ingredients( let _ = exec_ctx.last_error_descriptor.try_to_set_from_error(
catchable_error.as_ref(), catchable_error.as_ref(),
call.to_string(), &call.to_string(),
current_peer_id, &current_peer_id,
tetraplet, tetraplet,
); );
ExecutionError::Catchable(catchable_error) ExecutionError::Catchable(catchable_error)

View File

@ -48,7 +48,7 @@ fn resolve_to_string<'i>(value: &ast::CallInstrValue<'i>, ctx: &ExecutionCtx<'i>
use ast::CallInstrValue::*; use ast::CallInstrValue::*;
let resolved = match value { let resolved = match value {
InitPeerId => ctx.init_peer_id.clone(), InitPeerId => ctx.init_peer_id.to_string(),
Literal(value) => value.to_string(), Literal(value) => value.to_string(),
Variable(variable) => { Variable(variable) => {
let (resolved, _) = resolve_ast_variable_wl(variable, ctx)?; let (resolved, _) = resolve_ast_variable_wl(variable, ctx)?;

View File

@ -80,7 +80,7 @@ fn compare_matchable<'ctx>(
match matchable { match matchable {
InitPeerId => { InitPeerId => {
let init_peer_id = exec_ctx.init_peer_id.clone(); let init_peer_id = exec_ctx.init_peer_id.clone();
let jvalue = init_peer_id.into(); let jvalue = init_peer_id.as_str().into();
Ok(comparator(Cow::Owned(jvalue))) Ok(comparator(Cow::Owned(jvalue)))
} }
LastError(error_accessor) => { LastError(error_accessor) => {

View File

@ -17,6 +17,8 @@
use super::ExecutionCtx; use super::ExecutionCtx;
use super::ExecutionResult; use super::ExecutionResult;
use super::TraceHandler; use super::TraceHandler;
use crate::execution_step::execution_context::check_error_object;
use crate::execution_step::resolver::resolve_ast_scalar_wl;
use crate::execution_step::CatchableError; use crate::execution_step::CatchableError;
use crate::execution_step::LastError; use crate::execution_step::LastError;
use crate::execution_step::RSecurityTetraplet; use crate::execution_step::RSecurityTetraplet;
@ -24,6 +26,7 @@ use crate::log_instruction;
use crate::ExecutionError; use crate::ExecutionError;
use crate::JValue; use crate::JValue;
use air_parser::ast;
use air_parser::ast::Fail; use air_parser::ast::Fail;
use polyplets::SecurityTetraplet; use polyplets::SecurityTetraplet;
@ -35,6 +38,7 @@ impl<'i> super::ExecutableInstruction<'i> for Fail<'i> {
log_instruction!(fail, exec_ctx, trace_ctx); log_instruction!(fail, exec_ctx, trace_ctx);
match self { match self {
Fail::Scalar(scalar) => fail_with_scalar(scalar, exec_ctx),
&Fail::Literal { &Fail::Literal {
ret_code, ret_code,
error_message, error_message,
@ -45,25 +49,32 @@ impl<'i> super::ExecutableInstruction<'i> for Fail<'i> {
} }
} }
fn fail_with_scalar<'i>(scalar: &ast::ScalarWithLambda<'i>, exec_ctx: &mut ExecutionCtx<'i>) -> ExecutionResult<()> {
let (value, mut tetraplet) = resolve_ast_scalar_wl(scalar, exec_ctx)?;
// tetraplets always have one element here and it'll be refactored after boxed value
let tetraplet = tetraplet.remove(0);
check_error_object(&value).map_err(CatchableError::InvalidLastErrorObjectError)?;
fail_with_error_object(exec_ctx, Rc::new(value), Some(tetraplet))
}
fn fail_with_literals<'i>( fn fail_with_literals<'i>(
ret_code: i64, error_code: i64,
error_message: &str, error_message: &str,
fail: &Fail<'_>, fail: &Fail<'_>,
exec_ctx: &mut ExecutionCtx<'i>, exec_ctx: &mut ExecutionCtx<'i>,
) -> ExecutionResult<()> { ) -> ExecutionResult<()> {
// TODO: decouple error object creation into a separate function let error_object = crate::execution_step::execution_context::error_from_raw_fields(
let error_object = serde_json::json!({ error_code,
"error_code": ret_code, error_message,
"message": error_message, &fail.to_string(),
"instruction": fail.to_string(), exec_ctx.init_peer_id.as_ref(),
}); );
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.as_ref());
let literal_tetraplet = SecurityTetraplet::literal_tetraplet(exec_ctx.init_peer_id.clone());
let literal_tetraplet = Rc::new(RefCell::new(literal_tetraplet)); let literal_tetraplet = Rc::new(RefCell::new(literal_tetraplet));
fail_with_error_object(exec_ctx, error_object, Some(literal_tetraplet)) fail_with_error_object(exec_ctx, Rc::new(error_object), Some(literal_tetraplet))
} }
fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> { fn fail_with_last_error(exec_ctx: &mut ExecutionCtx<'_>) -> ExecutionResult<()> {

View File

@ -48,10 +48,11 @@ macro_rules! execute {
($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {{ ($self:expr, $instr:expr, $exec_ctx:ident, $trace_ctx:ident) => {{
match $instr.execute($exec_ctx, $trace_ctx) { match $instr.execute($exec_ctx, $trace_ctx) {
Err(e) => { Err(e) => {
$exec_ctx.last_error_descriptor.try_to_set_from_ingredients( $exec_ctx.last_error_descriptor.try_to_set_from_error(
&e, &e,
$instr.to_string(), // TODO: avoid excess copying here
$exec_ctx.current_peer_id.to_string(), &$instr.to_string(),
$exec_ctx.current_peer_id.as_ref(),
None, None,
); );
Err(e) Err(e)

View File

@ -38,10 +38,10 @@ impl<'i> ExecutableInstruction<'i> for Par<'i> {
trace_to_exec_err!(trace_ctx.meet_par_start(), self)?; trace_to_exec_err!(trace_ctx.meet_par_start(), self)?;
// execute a left subtree of par // execute a left subtree of par
let left_result = execute_subtree(&self, exec_ctx, trace_ctx, &mut completeness_updater, SubtreeType::Left)?; let left_result = execute_subtree(self, exec_ctx, trace_ctx, &mut completeness_updater, SubtreeType::Left)?;
// execute a right subtree of par // execute a right subtree of par
let right_result = execute_subtree(&self, exec_ctx, trace_ctx, &mut completeness_updater, SubtreeType::Right)?; let right_result = execute_subtree(self, exec_ctx, trace_ctx, &mut completeness_updater, SubtreeType::Right)?;
completeness_updater.set_completeness(exec_ctx); completeness_updater.set_completeness(exec_ctx);
prepare_par_result(left_result, right_result, exec_ctx) prepare_par_result(left_result, right_result, exec_ctx)

View File

@ -17,6 +17,7 @@
use super::Joinable; use super::Joinable;
use super::LastErrorAffectable; use super::LastErrorAffectable;
use super::Stream; use super::Stream;
use crate::execution_step::execution_context::LastErrorObjectError;
use crate::execution_step::lambda_applier::LambdaError; use crate::execution_step::lambda_applier::LambdaError;
use crate::JValue; use crate::JValue;
use crate::ToErrorCode; use crate::ToErrorCode;
@ -77,6 +78,10 @@ pub enum CatchableError {
/// Errors occurred while insertion of a value inside stream that doesn't have corresponding generation. /// Errors occurred while insertion of a value inside stream that doesn't have corresponding generation.
#[error("stream {0:?} doesn't have generation with number {1}, probably a supplied to the interpreter data is corrupted")] #[error("stream {0:?} doesn't have generation with number {1}, probably a supplied to the interpreter data is corrupted")]
StreamDontHaveSuchGeneration(Stream, usize), StreamDontHaveSuchGeneration(Stream, usize),
/// This error type is produced by a fail instruction that tries to throw a scalar that have inappropriate type.
#[error(transparent)]
InvalidLastErrorObjectError(#[from] LastErrorObjectError),
} }
impl From<LambdaError> for Rc<CatchableError> { impl From<LambdaError> for Rc<CatchableError> {

View File

@ -40,7 +40,7 @@ pub(crate) struct ExecutionCtx<'i> {
pub(crate) current_peer_id: Rc<String>, pub(crate) current_peer_id: Rc<String>,
/// PeerId of a peer send this AIR script. /// PeerId of a peer send this AIR script.
pub(crate) init_peer_id: String, pub(crate) init_peer_id: Rc<String>,
/// Last error produced by local service. /// Last error produced by local service.
/// None means that there weren't any error. /// None means that there weren't any error.
@ -78,7 +78,7 @@ impl<'i> ExecutionCtx<'i> {
Self { Self {
current_peer_id, current_peer_id,
init_peer_id, init_peer_id: Rc::new(init_peer_id),
subtree_complete: true, subtree_complete: true,
last_call_request_id, last_call_request_id,
call_results, call_results,

View File

@ -0,0 +1,36 @@
/*
* 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::JValue;
use thiserror::Error as ThisError;
/// Describes errors related to converting a scalar into error object.
#[derive(Debug, Clone, ThisError)]
pub enum LastErrorObjectError {
#[error("scalar should have an object type to be converted into error object, but '{0}' doesn't have")]
ScalarMustBeObject(JValue),
#[error("scalar '{scalar}' must have field with name '{field_name}'")]
ScalarMustContainField { scalar: JValue, field_name: &'static str },
#[error("{field_name} of scalar '{scalar}' must have {expected_type} type")]
ScalarFieldIsWrongType {
scalar: JValue,
field_name: &'static str,
expected_type: &'static str,
},
}

View File

@ -0,0 +1,110 @@
/*
* 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 super::LastErrorObjectError;
use crate::execution_step::RSecurityTetraplet;
use crate::JValue;
use serde::Deserialize;
use serde::Serialize;
use std::rc::Rc;
pub const ERROR_CODE_FIELD_NAME: &str = "error_code";
pub const MESSAGE_FIELD_NAME: &str = "message";
pub const INSTRUCTION_FIELD_NAME: &str = "instruction";
pub const PEER_ID_FIELD_NAME: &str = "peer_id";
/// 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) fn error_from_raw_fields(error_code: i64, error_message: &str, instruction: &str, peer_id: &str) -> JValue {
serde_json::json!({
ERROR_CODE_FIELD_NAME: error_code,
MESSAGE_FIELD_NAME: error_message,
INSTRUCTION_FIELD_NAME: instruction,
PEER_ID_FIELD_NAME: peer_id,
})
}
/// Checks that a scalar is a value of an object types that contains at least two fields:
/// - error_code
/// - message
pub(crate) fn check_error_object(scalar: &JValue) -> Result<(), LastErrorObjectError> {
let fields = match scalar {
JValue::Object(fields) => fields,
_ => return Err(LastErrorObjectError::ScalarMustBeObject(scalar.clone())),
};
let check_field = |field_name| {
fields
.get(field_name)
.ok_or_else(|| LastErrorObjectError::ScalarMustContainField {
scalar: scalar.clone(),
field_name,
})
};
let error_code = check_field(ERROR_CODE_FIELD_NAME)?;
ensure_jvalue_is_integer(scalar, error_code, ERROR_CODE_FIELD_NAME)?;
let message = check_field(MESSAGE_FIELD_NAME)?;
ensure_jvalue_is_string(scalar, message, MESSAGE_FIELD_NAME)?;
Ok(())
}
fn ensure_jvalue_is_integer(
scalar: &JValue,
value: &JValue,
field_name: &'static str,
) -> Result<(), LastErrorObjectError> {
match value {
JValue::Number(number) if number.is_i64() || number.is_u64() => Ok(()),
_ => Err(LastErrorObjectError::ScalarFieldIsWrongType {
scalar: scalar.clone(),
field_name,
expected_type: "integer",
}),
}
}
fn ensure_jvalue_is_string(
scalar: &JValue,
value: &JValue,
field_name: &'static str,
) -> Result<(), LastErrorObjectError> {
match value {
JValue::String(_) => Ok(()),
_ => Err(LastErrorObjectError::ScalarFieldIsWrongType {
scalar: scalar.clone(),
field_name,
expected_type: "string",
}),
}
}

View File

@ -14,31 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
use super::last_error_definition::error_from_raw_fields;
use super::LastError;
use crate::execution_step::LastErrorAffectable; use crate::execution_step::LastErrorAffectable;
use crate::execution_step::RSecurityTetraplet; use crate::execution_step::RSecurityTetraplet;
use crate::JValue; use crate::JValue;
use crate::ToErrorCode; use crate::ToErrorCode;
use serde::Deserialize;
use serde::Serialize;
use std::rc::Rc; 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 { pub(crate) struct LastErrorDescriptor {
last_error: LastError, last_error: LastError,
@ -49,11 +33,11 @@ pub(crate) struct LastErrorDescriptor {
} }
impl<'s> LastErrorDescriptor { impl<'s> LastErrorDescriptor {
pub(crate) fn try_to_set_from_ingredients( pub(crate) fn try_to_set_from_error(
&mut self, &mut self,
error: &(impl ToString + LastErrorAffectable + ToErrorCode), error: &(impl LastErrorAffectable + ToErrorCode + ToString),
instruction: impl Into<String>, instruction: &str,
peer_id: impl Into<String>, peer_id: &str,
tetraplet: Option<RSecurityTetraplet>, tetraplet: Option<RSecurityTetraplet>,
) -> bool { ) -> bool {
// this check is optimization to prevent creation of an error object in case if error // this check is optimization to prevent creation of an error object in case if error
@ -61,13 +45,25 @@ impl<'s> LastErrorDescriptor {
if !self.error_can_be_set || !error.affects_last_error() { if !self.error_can_be_set || !error.affects_last_error() {
return false; 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_ingredients(
error.to_error_code(),
&error.to_string(),
instruction,
peer_id,
tetraplet,
)
}
pub(crate) fn set_from_ingredients(
&mut self,
error_code: i64,
error_message: &str,
instruction: &str,
peer_id: &str,
tetraplet: Option<RSecurityTetraplet>,
) -> bool {
let error_object = error_from_raw_fields(error_code, error_message, instruction, peer_id);
self.set_from_error_object(Rc::new(error_object), tetraplet); self.set_from_error_object(Rc::new(error_object), tetraplet);
true true
} }

View File

@ -0,0 +1,30 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
mod errors;
mod last_error_definition;
mod last_error_descriptor;
pub use errors::LastErrorObjectError;
pub use last_error_definition::LastError;
pub use last_error_definition::ERROR_CODE_FIELD_NAME;
pub use last_error_definition::INSTRUCTION_FIELD_NAME;
pub use last_error_definition::MESSAGE_FIELD_NAME;
pub use last_error_definition::PEER_ID_FIELD_NAME;
pub(crate) use last_error_definition::check_error_object;
pub(crate) use last_error_definition::error_from_raw_fields;
pub(crate) use last_error_descriptor::LastErrorDescriptor;

View File

@ -15,13 +15,12 @@
*/ */
mod context; mod context;
mod last_error_descriptor; mod last_error;
mod scalar_variables; mod scalar_variables;
mod streams_variables; mod streams_variables;
pub use last_error_descriptor::LastError; pub use last_error::*;
pub(crate) use context::*; pub(crate) use context::*;
pub(crate) use last_error_descriptor::LastErrorDescriptor;
pub(crate) use scalar_variables::*; pub(crate) use scalar_variables::*;
pub(crate) use streams_variables::*; pub(crate) use streams_variables::*;

View File

@ -24,6 +24,7 @@ mod resolver;
pub use errors::CatchableError; pub use errors::CatchableError;
pub use errors::ExecutionError; pub use errors::ExecutionError;
pub use errors::UncatchableError; pub use errors::UncatchableError;
pub use execution_context::LastErrorObjectError;
pub use lambda_applier::LambdaError; pub use lambda_applier::LambdaError;
pub mod errors_prelude { pub mod errors_prelude {

View File

@ -39,7 +39,7 @@ pub(crate) fn resolve_to_args<'i>(
use ast::Value::*; use ast::Value::*;
match value { match value {
InitPeerId => prepare_const(ctx.init_peer_id.clone(), ctx), InitPeerId => prepare_const(ctx.init_peer_id.as_str(), ctx),
LastError(error_accessor) => prepare_last_error(error_accessor, ctx), LastError(error_accessor) => prepare_last_error(error_accessor, ctx),
Literal(value) => prepare_const(value.to_string(), ctx), Literal(value) => prepare_const(value.to_string(), ctx),
Boolean(value) => prepare_const(*value, ctx), Boolean(value) => prepare_const(*value, ctx),
@ -55,7 +55,7 @@ pub(crate) fn prepare_const(
ctx: &ExecutionCtx<'_>, ctx: &ExecutionCtx<'_>,
) -> ExecutionResult<(JValue, SecurityTetraplets)> { ) -> ExecutionResult<(JValue, SecurityTetraplets)> {
let jvalue = arg.into(); let jvalue = arg.into();
let tetraplet = SecurityTetraplet::literal_tetraplet(ctx.init_peer_id.clone()); let tetraplet = SecurityTetraplet::literal_tetraplet(ctx.init_peer_id.as_ref());
let tetraplet = Rc::new(RefCell::new(tetraplet)); let tetraplet = Rc::new(RefCell::new(tetraplet));
Ok((jvalue, vec![tetraplet])) Ok((jvalue, vec![tetraplet]))
@ -78,7 +78,7 @@ pub(crate) fn prepare_last_error<'i>(
let tetraplets = match tetraplet { let tetraplets = match tetraplet {
Some(tetraplet) => vec![tetraplet.clone()], Some(tetraplet) => vec![tetraplet.clone()],
None => { None => {
let tetraplet = SecurityTetraplet::literal_tetraplet(&ctx.init_peer_id); let tetraplet = SecurityTetraplet::literal_tetraplet(ctx.init_peer_id.as_ref());
let tetraplet = Rc::new(RefCell::new(tetraplet)); let tetraplet = Rc::new(RefCell::new(tetraplet));
vec![tetraplet] vec![tetraplet]
} }
@ -128,6 +128,15 @@ pub(crate) fn resolve_ast_variable_wl<'ctx, 'i>(
} }
} }
pub(crate) fn resolve_ast_scalar_wl<'ctx, 'i>(
ast_scalar: &ast::ScalarWithLambda<'_>,
exec_ctx: &'ctx ExecutionCtx<'i>,
) -> ExecutionResult<(JValue, SecurityTetraplets)> {
// TODO: wrap lambda path with Rc to make this clone cheaper
let variable = ast::VariableWithLambda::Scalar(ast_scalar.clone());
resolve_ast_variable_wl(&variable, exec_ctx)
}
pub(crate) fn apply_lambda<'i>( pub(crate) fn apply_lambda<'i>(
variable: Variable<'_>, variable: Variable<'_>,
lambda: &LambdaAST<'i>, lambda: &LambdaAST<'i>,

View File

@ -38,6 +38,7 @@ pub use execution_step::execution_context::LastError;
pub use execution_step::CatchableError; pub use execution_step::CatchableError;
pub use execution_step::ExecutionError; pub use execution_step::ExecutionError;
pub use execution_step::LambdaError; pub use execution_step::LambdaError;
pub use execution_step::LastErrorObjectError;
pub use execution_step::UncatchableError; pub use execution_step::UncatchableError;
pub use polyplets::ResolvedTriplet; pub use polyplets::ResolvedTriplet;
pub use polyplets::SecurityTetraplet; pub use polyplets::SecurityTetraplet;

View File

@ -58,13 +58,15 @@ fn fail_with_literals() {
)"#, )"#,
); );
let result = call_vm!(vm, "", script, "", ""); let init_peer_id = "init_peer_id";
let result = call_vm!(vm, init_peer_id, script, "", "");
let expected_error = CatchableError::UserError { let expected_error = CatchableError::UserError {
error: rc!(json!( { error: rc!(json!( {
"error_code": 1337i64, "error_code": 1337i64,
"instruction": "fail 1337 error message", "instruction": "fail 1337 error message",
"message": "error message", "message": "error message",
"peer_id": init_peer_id,
})), })),
}; };
assert!(check_error(&result, expected_error)); assert!(check_error(&result, expected_error));

View File

@ -14,7 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
use air::{CatchableError, ExecutionError, LambdaError, SecurityTetraplet}; use air::CatchableError;
use air::ExecutionError;
use air::LambdaError;
use air::LastErrorObjectError;
use air::SecurityTetraplet;
use air_test_utils::prelude::*; use air_test_utils::prelude::*;
use fstrings::f; use fstrings::f;
@ -324,3 +328,169 @@ fn last_error_with_par_one_subtree_failed() {
}); });
assert_eq!(actual_value, expected_value); assert_eq!(actual_value, expected_value);
} }
#[test]
fn fail_with_scalar_rebubble_error() {
let fallible_peer_id = "fallible_peer_id";
let mut fallible_vm = create_avm(fallible_call_service("fallible_call_service"), fallible_peer_id);
let script = f!(r#"
(xor
(call "{fallible_peer_id}" ("fallible_call_service" "") [""])
(seq
(ap %last_error% scalar)
(fail scalar)
)
)
"#);
let result = call_vm!(fallible_vm, "", &script, "", "");
let expected_error = CatchableError::UserError {
error: rc!(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!(check_error(&result, expected_error));
}
#[test]
fn fail_with_scalar_from_call() {
let vm_peer_id = "vm_peer_id";
let error_code = 1337;
let error_message = "error message";
let service_result = json!({"error_code": error_code, "message": error_message});
let mut vm = create_avm(set_variable_call_service(service_result), vm_peer_id);
let script = f!(r#"
(seq
(call "{vm_peer_id}" ("" "") [""] scalar)
(fail scalar)
)
"#);
let result = call_vm!(vm, "", &script, "", "");
let expected_error = CatchableError::UserError {
error: rc!(json!({
"error_code": error_code,
"message": error_message,
})),
};
assert!(check_error(&result, expected_error));
}
#[test]
fn fail_with_scalar_with_lambda_from_call() {
let vm_peer_id = "vm_peer_id";
let error_code = 1337;
let error_message = "error message";
let service_result = json!({"error": {"error_code": error_code, "message": error_message}});
let mut vm = create_avm(set_variable_call_service(service_result), vm_peer_id);
let script = f!(r#"
(seq
(call "{vm_peer_id}" ("" "") [""] scalar)
(fail scalar.$.error)
)
"#);
let result = call_vm!(vm, "", &script, "", "");
let expected_error = CatchableError::UserError {
error: rc!(json!({
"error_code": error_code,
"message": error_message,
})),
};
assert!(check_error(&result, expected_error));
}
#[test]
fn fail_with_scalar_from_call_not_enough_fields() {
let vm_peer_id = "vm_peer_id";
let error_code = 1337;
let service_result = json!({ "error_code": error_code });
let mut vm = create_avm(set_variable_call_service(service_result.clone()), vm_peer_id);
let script = f!(r#"
(seq
(call "{vm_peer_id}" ("" "") [""] scalar)
(fail scalar)
)
"#);
let result = call_vm!(vm, "", &script, "", "");
let expected_error = CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarMustContainField {
scalar: service_result,
field_name: "message",
});
assert!(check_error(&result, expected_error));
}
#[test]
fn fail_with_scalar_from_call_not_right_type() {
let vm_peer_id = "vm_peer_id";
let service_result = json!([]);
let mut vm = create_avm(set_variable_call_service(service_result.clone()), vm_peer_id);
let script = f!(r#"
(seq
(call "{vm_peer_id}" ("" "") [""] scalar)
(fail scalar)
)
"#);
let result = call_vm!(vm, "", &script, "", "");
let expected_error =
CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarMustBeObject(service_result));
assert!(check_error(&result, expected_error));
}
#[test]
fn fail_with_scalar_from_call_field_not_right_type() {
let vm_peer_id = "vm_peer_id";
let service_result = json!({"error_code": "error_code", "message": "error message"});
let mut vm = create_avm(set_variable_call_service(service_result.clone()), vm_peer_id);
let script = f!(r#"
(seq
(call "{vm_peer_id}" ("" "") [""] scalar)
(fail scalar)
)
"#);
let result = call_vm!(vm, "", &script, "", "");
let expected_error = CatchableError::InvalidLastErrorObjectError(LastErrorObjectError::ScalarFieldIsWrongType {
scalar: service_result.clone(),
field_name: "error_code",
expected_type: "integer",
});
assert!(check_error(&result, expected_error));
}
#[test]
fn last_error_with_match() {
let vm_peer_id = "vm_peer_id";
let mut vm = create_avm(fallible_call_service("fallible_call_service"), vm_peer_id);
let script = f!(r#"
(xor
(call "{vm_peer_id}" ("fallible_call_service" "") [""])
(match %last_error%.$.error_code 10000
(call "{vm_peer_id}" ("" "") [%last_error%])
)
)
"#);
let result = checked_call_vm!(vm, "asd", &script, "", "");
let trace = trace_from_result(&result);
assert_eq!(trace.len(), 2); // if match works there will be 2 calls in a resulted trace
}

View File

@ -88,6 +88,7 @@ pub struct MisMatch<'i> {
/// (fail %last_error%) /// (fail %last_error%)
#[derive(Serialize, Debug, PartialEq)] #[derive(Serialize, Debug, PartialEq)]
pub enum Fail<'i> { pub enum Fail<'i> {
Scalar(ScalarWithLambda<'i>),
Literal { Literal {
ret_code: i64, ret_code: i64,
error_message: &'i str, error_message: &'i str,

View File

@ -36,7 +36,7 @@ impl fmt::Display for Instruction<'_> {
Next(next) => write!(f, "{}", next), Next(next) => write!(f, "{}", next),
New(new) => write!(f, "{}", new), New(new) => write!(f, "{}", new),
Null(null) => write!(f, "{}", null), Null(null) => write!(f, "{}", null),
Error => Ok(()), Error => write!(f, "error"),
} }
} }
} }
@ -59,6 +59,7 @@ impl fmt::Display for Ap<'_> {
impl fmt::Display for Fail<'_> { impl fmt::Display for Fail<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Fail::Scalar(scalar) => write!(f, "fail {}", scalar),
Fail::Literal { Fail::Literal {
ret_code, ret_code,
error_message, error_message,

View File

@ -136,6 +136,8 @@ ScriptVariable: Variable<'input> = {
}; };
FailBody: Fail<'input> = { FailBody: Fail<'input> = {
<scalar:Scalar> => Fail::Scalar(ScalarWithLambda::new(scalar.0, None, scalar.1)),
<scalar:ScalarWithLambda> => Fail::Scalar(ScalarWithLambda::new(scalar.0, Some(scalar.1), scalar.2)),
<ret_code:I64> <error_message:Literal> => Fail::Literal { <ret_code:I64> <error_message:Literal> => Fail::Literal {
ret_code, ret_code,
error_message, error_message,

File diff suppressed because it is too large Load Diff

View File

@ -69,6 +69,10 @@ pub(super) fn null() -> Instruction<'static> {
Instruction::Null(Null) Instruction::Null(Null)
} }
pub(super) fn fail_scalar(scalar: ScalarWithLambda) -> Instruction<'_> {
Instruction::Fail(Fail::Scalar(scalar))
}
pub(super) fn fail_literals(ret_code: i64, error_message: &str) -> Instruction<'_> { pub(super) fn fail_literals(ret_code: i64, error_message: &str) -> Instruction<'_> {
Instruction::Fail(Fail::Literal { Instruction::Fail(Fail::Literal {
ret_code, ret_code,

View File

@ -16,6 +16,10 @@
use super::dsl::*; use super::dsl::*;
use super::parse; use super::parse;
use crate::ast::ScalarWithLambda;
use air_lambda_ast::LambdaAST;
use air_lambda_ast::ValueAccessor;
#[test] #[test]
fn parse_fail_last_error() { fn parse_fail_last_error() {
@ -36,3 +40,31 @@ fn parse_fail_literals() {
let expected = fail_literals(1, "error message"); let expected = fail_literals(1, "error message");
assert_eq!(instruction, expected) assert_eq!(instruction, expected)
} }
#[test]
fn parse_fail_scalars() {
let source_code = r#"
(fail scalar)
"#;
let instruction = parse(source_code);
let expected = fail_scalar(ScalarWithLambda::new("scalar", None, 18));
assert_eq!(instruction, expected)
}
#[test]
fn parse_fail_scalar_with_lambda() {
let source_code = r#"
(fail scalar.$.field_accessor)
"#;
let instruction = parse(source_code);
let expected = fail_scalar(ScalarWithLambda::new(
"scalar",
Some(unsafe {
LambdaAST::new_unchecked(vec![ValueAccessor::FieldAccessByName {
field_name: "field_accessor",
}])
}),
18,
));
assert_eq!(instruction, expected)
}