mirror of
https://github.com/fluencelabs/aquavm
synced 2025-03-15 20:40:50 +00:00
Implement fail with scalars (#205)
This commit is contained in:
parent
91021d8b40
commit
e6193ea4de
@ -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())
|
||||||
|
@ -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,
|
¤t_peer_id,
|
||||||
tetraplet,
|
tetraplet,
|
||||||
);
|
);
|
||||||
ExecutionError::Catchable(catchable_error)
|
ExecutionError::Catchable(catchable_error)
|
||||||
|
@ -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)?;
|
||||||
|
@ -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) => {
|
||||||
|
@ -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<()> {
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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> {
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
},
|
||||||
|
}
|
@ -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",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
30
air/src/execution_step/execution_context/last_error/mod.rs
Normal file
30
air/src/execution_step/execution_context/last_error/mod.rs
Normal 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;
|
@ -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::*;
|
||||||
|
@ -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 {
|
||||||
|
@ -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>,
|
||||||
|
@ -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;
|
||||||
|
@ -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));
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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
@ -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,
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user