Introduce %last_error% (#63)

This commit is contained in:
vms 2021-02-11 15:39:37 +03:00 committed by GitHub
parent 709b5e0a52
commit 05499a6bc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1229 additions and 669 deletions

View File

@ -44,7 +44,7 @@ Instr: Box<Instruction<'input>> = {
! => { errors.push(<>); Box::new(Instruction::Error) },
}
Args: Vec<CallArgValue<'input>> = {
Args: Vec<CallInstrArgValue<'input>> = {
"[" <args:(<Arg>)*> "]" => args
}
@ -63,19 +63,31 @@ Output: CallOutputValue<'input> = {
<a:Accumulator> => CallOutputValue::Accumulator(a),
};
Function = CallArgValue;
PeerId = CallArgValue;
ServiceId = CallArgValue;
Arg = CallArgValue;
Function = CallInstrValue;
PeerId = CallInstrValue;
ServiceId = CallInstrValue;
CallArgValue: CallArgValue<'input> = {
<s:Literal> => CallArgValue::Literal(s),
<s:Alphanumeric> => CallArgValue::Variable(s),
CallInstrValue: CallInstrValue<'input> = {
<s:Literal> => CallInstrValue::Literal(s),
<s:Alphanumeric> => CallInstrValue::Variable(s),
<v:JsonPath> => {
let (variable, path) = into_variable_and_path(v.0, v.1);
CallArgValue::JsonPath { variable, path }
CallInstrValue::JsonPath { variable, path }
},
InitPeerId => CallArgValue::InitPeerId,
InitPeerId => CallInstrValue::InitPeerId,
}
Arg = CallInstrArgValue;
CallInstrArgValue: CallInstrArgValue<'input> = {
<s:Literal> => CallInstrArgValue::Literal(s),
<s:Alphanumeric> => CallInstrArgValue::Variable(s),
<v:JsonPath> => {
let (variable, path) = into_variable_and_path(v.0, v.1);
CallInstrArgValue::JsonPath { variable, path }
},
InitPeerId => CallInstrArgValue::InitPeerId,
LastError => CallInstrArgValue::LastError,
}
Iterable: IterableValue<'input> = {
@ -111,6 +123,7 @@ extern {
Accumulator => Token::Accumulator(<&'input str>),
InitPeerId => Token::InitPeerId,
LastError => Token::LastError,
call => Token::Call,
seq => Token::Seq,

File diff suppressed because it is too large Load Diff

View File

@ -36,32 +36,41 @@ pub enum Instruction<'i> {
#[derive(Serialize, Debug, PartialEq, Eq)]
pub enum PeerPart<'i> {
PeerPk(CallArgValue<'i>),
PeerPkWithServiceId(CallArgValue<'i>, CallArgValue<'i>),
PeerPk(CallInstrValue<'i>),
PeerPkWithServiceId(CallInstrValue<'i>, CallInstrValue<'i>),
}
#[derive(Serialize, Debug, PartialEq, Eq)]
pub enum FunctionPart<'i> {
FuncName(CallArgValue<'i>),
ServiceIdWithFuncName(CallArgValue<'i>, CallArgValue<'i>),
FuncName(CallInstrValue<'i>),
ServiceIdWithFuncName(CallInstrValue<'i>, CallInstrValue<'i>),
}
#[derive(Serialize, Debug, PartialEq, Eq)]
pub struct Call<'i> {
pub peer_part: PeerPart<'i>,
pub function_part: FunctionPart<'i>,
pub args: Rc<Vec<CallArgValue<'i>>>,
pub args: Rc<Vec<CallInstrArgValue<'i>>>,
pub output: CallOutputValue<'i>,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
pub enum CallArgValue<'i> {
pub enum CallInstrValue<'i> {
InitPeerId,
Literal(&'i str),
Variable(&'i str),
JsonPath { variable: &'i str, path: &'i str },
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
pub enum CallInstrArgValue<'i> {
InitPeerId,
LastError,
Literal(&'i str),
Variable(&'i str),
JsonPath { variable: &'i str, path: &'i str },
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
pub enum IterableValue<'i> {
Variable(&'i str),

View File

@ -184,6 +184,7 @@ fn string_to_token(input: &str, start_pos: usize) -> Result<Token, LexerError> {
MISMATCH_INSTR => Ok(Token::MisMatch),
INIT_PEER_ID => Ok(Token::InitPeerId),
LAST_ERROR => Ok(Token::LastError),
str if str.ends_with(ACC_END_TAG) => try_parse_accumulator(str, start_pos),
str => try_parse_call_variable(str, start_pos),
@ -240,6 +241,7 @@ const MATCH_INSTR: &str = "match";
const MISMATCH_INSTR: &str = "mismatch";
const INIT_PEER_ID: &str = "%init_peer_id%";
const LAST_ERROR: &str = "%last_error%";
const ACC_END_TAG: &str = "[]";

View File

@ -27,6 +27,7 @@ pub enum Token<'input> {
Accumulator(&'input str),
InitPeerId,
LastError,
Call,
Seq,

View File

@ -27,7 +27,8 @@ fn parse(source_code: &str) -> Instruction {
#[test]
fn parse_seq() {
use ast::Call;
use ast::CallArgValue::*;
use ast::CallInstrArgValue;
use ast::CallInstrValue;
use ast::CallOutputValue::*;
use ast::FunctionPart::*;
use ast::PeerPart::*;
@ -41,15 +42,18 @@ fn parse_seq() {
let instruction = parse(source_code);
let expected = seq(
Instruction::Call(Call {
peer_part: PeerPk(Variable("peerid")),
function_part: FuncName(Variable("function")),
peer_part: PeerPk(CallInstrValue::Variable("peerid")),
function_part: FuncName(CallInstrValue::Variable("function")),
args: Rc::new(vec![]),
output: Scalar("output"),
}),
Instruction::Call(Call {
peer_part: PeerPk(Literal("id")),
function_part: FuncName(Literal("f")),
args: Rc::new(vec![Literal("hello"), Variable("name")]),
peer_part: PeerPk(CallInstrValue::Literal("id")),
function_part: FuncName(CallInstrValue::Literal("f")),
args: Rc::new(vec![
CallInstrArgValue::Literal("hello"),
CallInstrArgValue::Variable("name"),
]),
output: None,
}),
);
@ -59,7 +63,8 @@ fn parse_seq() {
#[test]
fn parse_seq_seq() {
use ast::Call;
use ast::CallArgValue::*;
use ast::CallInstrArgValue;
use ast::CallInstrValue;
use ast::CallOutputValue::*;
use ast::FunctionPart::*;
use ast::PeerPart::*;
@ -77,22 +82,31 @@ fn parse_seq_seq() {
let expected = seq(
seq(
Instruction::Call(Call {
peer_part: PeerPk(Variable("peerid")),
function_part: FuncName(Variable("function")),
peer_part: PeerPk(CallInstrValue::Variable("peerid")),
function_part: FuncName(CallInstrValue::Variable("function")),
args: Rc::new(vec![]),
output: None,
}),
Instruction::Call(Call {
peer_part: PeerPkWithServiceId(Variable("peerid"), Variable("serviceA")),
function_part: ServiceIdWithFuncName(Literal("serviceB"), Variable("function")),
peer_part: PeerPkWithServiceId(
CallInstrValue::Variable("peerid"),
CallInstrValue::Variable("serviceA"),
),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal("serviceB"),
CallInstrValue::Variable("function"),
),
args: Rc::new(vec![]),
output: None,
}),
),
Instruction::Call(Call {
peer_part: PeerPk(Literal("id")),
function_part: FuncName(Literal("f")),
args: Rc::new(vec![Literal("hello"), Variable("name")]),
peer_part: PeerPk(CallInstrValue::Literal("id")),
function_part: FuncName(CallInstrValue::Literal("f")),
args: Rc::new(vec![
CallInstrArgValue::Literal("hello"),
CallInstrArgValue::Variable("name"),
]),
output: Accumulator("output"),
}),
);
@ -102,7 +116,8 @@ fn parse_seq_seq() {
#[test]
fn parse_json_path() {
use ast::Call;
use ast::CallArgValue::*;
use ast::CallInstrArgValue;
use ast::CallInstrValue;
use ast::CallOutputValue::*;
use ast::FunctionPart::*;
use ast::PeerPart::*;
@ -112,12 +127,15 @@ fn parse_json_path() {
"#;
let instruction = parse(source_code);
let expected = Instruction::Call(Call {
peer_part: PeerPk(JsonPath {
peer_part: PeerPk(CallInstrValue::JsonPath {
variable: "id",
path: "$.a",
}),
function_part: FuncName(Literal("f")),
args: Rc::new(vec![Literal("hello"), Variable("name")]),
function_part: FuncName(CallInstrValue::Literal("f")),
args: Rc::new(vec![
CallInstrArgValue::Literal("hello"),
CallInstrArgValue::Variable("name"),
]),
output: Accumulator("void"),
});
assert_eq!(instruction, expected);
@ -126,7 +144,7 @@ fn parse_json_path() {
#[test]
fn parse_json_path_complex() {
use ast::Call;
use ast::CallArgValue::*;
use ast::CallInstrValue;
use ast::CallOutputValue::*;
use ast::FunctionPart::*;
use ast::PeerPart::*;
@ -140,20 +158,20 @@ fn parse_json_path_complex() {
let instruction = parse(source_code);
let expected = seq(
Instruction::Call(Call {
peer_part: PeerPk(JsonPath {
peer_part: PeerPk(CallInstrValue::JsonPath {
variable: "m",
path: "$.[1]",
}),
function_part: FuncName(Literal("f")),
function_part: FuncName(CallInstrValue::Literal("f")),
args: Rc::new(vec![]),
output: Scalar("void"),
}),
Instruction::Call(Call {
peer_part: PeerPk(JsonPath {
peer_part: PeerPk(CallInstrValue::JsonPath {
variable: "m",
path: r#"$.abc["c"].cde[a][0].cde["bcd"]"#,
}),
function_part: FuncName(Literal("f")),
function_part: FuncName(CallInstrValue::Literal("f")),
args: Rc::new(vec![]),
output: Scalar("void"),
}),
@ -164,7 +182,8 @@ fn parse_json_path_complex() {
#[test]
fn json_path_square_braces() {
use ast::Call;
use ast::CallArgValue::*;
use ast::CallInstrArgValue;
use ast::CallInstrValue;
use ast::CallOutputValue::*;
use ast::FunctionPart::*;
use ast::PeerPart::*;
@ -174,17 +193,20 @@ fn json_path_square_braces() {
"#;
let instruction = parse(source_code);
let expected = Instruction::Call(Call {
peer_part: PeerPk(JsonPath {
peer_part: PeerPk(CallInstrValue::JsonPath {
variable: "u",
path: r#"$["peer_id"]"#,
}),
function_part: ServiceIdWithFuncName(Literal("return"), Literal("")),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal("return"),
CallInstrValue::Literal(""),
),
args: Rc::new(vec![
JsonPath {
CallInstrArgValue::JsonPath {
variable: "u",
path: r#"$["peer_id"].cde[0]["abc"].abc"#,
},
JsonPath {
CallInstrArgValue::JsonPath {
variable: "u",
path: r#"$["name"]"#,
},
@ -295,7 +317,7 @@ fn parse_fold_with_xor_par_seq() {
#[test]
fn parse_init_peer_id() {
use ast::Call;
use ast::CallArgValue::*;
use ast::CallInstrValue;
use ast::CallOutputValue::*;
use ast::FunctionPart::*;
use ast::PeerPart::*;
@ -313,17 +335,20 @@ fn parse_init_peer_id() {
let instruction = parse(&source_code.as_ref());
let expected = seq(
Instruction::Call(Call {
peer_part: PeerPk(Literal(&peer_id)),
peer_part: PeerPk(CallInstrValue::Literal(&peer_id)),
function_part: ServiceIdWithFuncName(
Literal("local_service_id"),
Literal("local_fn_name"),
CallInstrValue::Literal("local_service_id"),
CallInstrValue::Literal("local_fn_name"),
),
args: Rc::new(vec![]),
output: None,
}),
Instruction::Call(Call {
peer_part: PeerPk(InitPeerId),
function_part: ServiceIdWithFuncName(Literal("service_id"), Literal("fn_name")),
peer_part: PeerPk(CallInstrValue::InitPeerId),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("fn_name"),
),
args: Rc::new(vec![]),
output: None,
}),
@ -332,10 +357,44 @@ fn parse_init_peer_id() {
assert_eq!(instruction, expected);
}
#[test]
fn parse_last_error() {
use ast::Call;
use ast::CallInstrArgValue;
use ast::CallInstrValue;
use ast::CallOutputValue::*;
use ast::FunctionPart::*;
use ast::PeerPart::*;
let source_code = format!(
r#"
(seq
(call %init_peer_id% ("service_id" "fn_name") [%last_error%])
(null)
)"#,
);
let instruction = parse(&source_code.as_ref());
let expected = seq(
Instruction::Call(Call {
peer_part: PeerPk(CallInstrValue::InitPeerId),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("fn_name"),
),
args: Rc::new(vec![CallInstrArgValue::LastError]),
output: None,
}),
Instruction::Null(ast::Null),
);
assert_eq!(instruction, expected);
}
#[test]
fn seq_par_call() {
use ast::Call;
use ast::CallArgValue::*;
use ast::CallInstrValue;
use ast::CallOutputValue::*;
use ast::FunctionPart::*;
use ast::PeerPart::*;
@ -357,26 +416,29 @@ fn seq_par_call() {
let expected = seq(
par(
Instruction::Call(Call {
peer_part: PeerPk(Literal(&peer_id)),
peer_part: PeerPk(CallInstrValue::Literal(&peer_id)),
function_part: ServiceIdWithFuncName(
Literal("local_service_id"),
Literal("local_fn_name"),
CallInstrValue::Literal("local_service_id"),
CallInstrValue::Literal("local_fn_name"),
),
args: Rc::new(vec![]),
output: Scalar("result_1"),
}),
Instruction::Call(Call {
peer_part: PeerPk(Literal(&peer_id)),
function_part: ServiceIdWithFuncName(Literal("service_id"), Literal("fn_name")),
peer_part: PeerPk(CallInstrValue::Literal(&peer_id)),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal("service_id"),
CallInstrValue::Literal("fn_name"),
),
args: Rc::new(vec![]),
output: Scalar("g"),
}),
),
Instruction::Call(Call {
peer_part: PeerPk(Literal(&peer_id)),
peer_part: PeerPk(CallInstrValue::Literal(&peer_id)),
function_part: ServiceIdWithFuncName(
Literal("local_service_id"),
Literal("local_fn_name"),
CallInstrValue::Literal("local_service_id"),
CallInstrValue::Literal("local_fn_name"),
),
args: Rc::new(vec![]),
output: Scalar("result_2"),
@ -389,7 +451,8 @@ fn seq_par_call() {
#[test]
fn seq_with_empty_and_dash() {
use ast::Call;
use ast::CallArgValue::*;
use ast::CallInstrArgValue;
use ast::CallInstrValue;
use ast::CallOutputValue::*;
use ast::FunctionPart::*;
use ast::PeerPart::*;
@ -420,50 +483,74 @@ fn seq_with_empty_and_dash() {
seq(
seq(
Instruction::Call(Call {
peer_part: PeerPk(Literal("set_variables")),
function_part: ServiceIdWithFuncName(Literal(""), Literal("")),
args: Rc::new(vec![Literal("module-bytes")]),
peer_part: PeerPk(CallInstrValue::Literal("set_variables")),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
),
args: Rc::new(vec![CallInstrArgValue::Literal("module-bytes")]),
output: Scalar("module-bytes"),
}),
Instruction::Call(Call {
peer_part: PeerPk(Literal("set_variables")),
function_part: ServiceIdWithFuncName(Literal(""), Literal("")),
args: Rc::new(vec![Literal("module_config")]),
peer_part: PeerPk(CallInstrValue::Literal("set_variables")),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
),
args: Rc::new(vec![CallInstrArgValue::Literal("module_config")]),
output: Scalar("module_config"),
}),
),
Instruction::Call(Call {
peer_part: PeerPk(Literal("set_variables")),
function_part: ServiceIdWithFuncName(Literal(""), Literal("")),
args: Rc::new(vec![Literal("blueprint")]),
peer_part: PeerPk(CallInstrValue::Literal("set_variables")),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
),
args: Rc::new(vec![CallInstrArgValue::Literal("blueprint")]),
output: Scalar("blueprint"),
}),
),
seq(
Instruction::Call(Call {
peer_part: PeerPk(Literal("A")),
function_part: ServiceIdWithFuncName(Literal("add_module"), Literal("")),
args: Rc::new(vec![Variable("module-bytes"), Variable("module_config")]),
peer_part: PeerPk(CallInstrValue::Literal("A")),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal("add_module"),
CallInstrValue::Literal(""),
),
args: Rc::new(vec![
CallInstrArgValue::Variable("module-bytes"),
CallInstrArgValue::Variable("module_config"),
]),
output: Scalar("module"),
}),
seq(
Instruction::Call(Call {
peer_part: PeerPk(Literal("A")),
function_part: ServiceIdWithFuncName(Literal("add_blueprint"), Literal("")),
args: Rc::new(vec![Variable("blueprint")]),
peer_part: PeerPk(CallInstrValue::Literal("A")),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal("add_blueprint"),
CallInstrValue::Literal(""),
),
args: Rc::new(vec![CallInstrArgValue::Variable("blueprint")]),
output: Scalar("blueprint_id"),
}),
seq(
Instruction::Call(Call {
peer_part: PeerPk(Literal("A")),
function_part: ServiceIdWithFuncName(Literal("create"), Literal("")),
args: Rc::new(vec![Variable("blueprint_id")]),
peer_part: PeerPk(CallInstrValue::Literal("A")),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal("create"),
CallInstrValue::Literal(""),
),
args: Rc::new(vec![CallInstrArgValue::Variable("blueprint_id")]),
output: Scalar("service_id"),
}),
Instruction::Call(Call {
peer_part: PeerPk(Literal("remote_peer_id")),
function_part: ServiceIdWithFuncName(Literal(""), Literal("")),
args: Rc::new(vec![Variable("service_id")]),
peer_part: PeerPk(CallInstrValue::Literal("remote_peer_id")),
function_part: ServiceIdWithFuncName(
CallInstrValue::Literal(""),
CallInstrValue::Literal(""),
),
args: Rc::new(vec![CallInstrArgValue::Variable("service_id")]),
output: Scalar("client_result"),
}),
),
@ -477,7 +564,7 @@ fn seq_with_empty_and_dash() {
#[test]
fn no_output() {
use ast::Call;
use ast::CallArgValue::*;
use ast::CallInstrValue;
use ast::CallOutputValue::*;
use ast::FunctionPart::*;
use ast::PeerPart::*;
@ -487,8 +574,11 @@ fn no_output() {
"#;
let instruction = parse(&source_code.as_ref());
let expected = Instruction::Call(Call {
peer_part: PeerPk(Variable("peer")),
function_part: ServiceIdWithFuncName(Variable("service"), Variable("fname")),
peer_part: PeerPk(CallInstrValue::Variable("peer")),
function_part: ServiceIdWithFuncName(
CallInstrValue::Variable("service"),
CallInstrValue::Variable("fname"),
),
args: Rc::new(vec![]),
output: None,
});

View File

@ -49,4 +49,11 @@ impl SecurityTetraplet {
json_path: String::new(),
}
}
pub fn from_triplet(triplet: Rc<ResolvedTriplet>) -> Self {
Self {
triplet,
json_path: String::new(),
}
}
}

View File

@ -77,13 +77,10 @@ pub fn echo_string_call_service() -> CallServiceClosure {
};
let arg: Vec<String> = serde_json::from_str(arg).unwrap();
let arg = serde_json::to_string(&arg[0]).unwrap();
Some(IValue::Record(
NEVec::new(vec![
IValue::S32(0),
IValue::String(format!("\"{}\"", arg[0])),
])
.unwrap(),
NEVec::new(vec![IValue::S32(0), IValue::String(arg)]).unwrap(),
))
})
}

View File

@ -21,7 +21,9 @@ use crate::StepperOutcome;
use crate::STEPPER_SUCCESS;
use serde::Serialize;
use std::hash::Hash;
use std::rc::Rc;
const EXECUTION_ERRORS_START_ID: i32 = 1000;
@ -58,7 +60,7 @@ pub(crate) fn from_preparation_error(data: impl Into<Vec<u8>>, err: PreparationE
/// Create StepperOutcome from supplied data, next_peer_pks and error,
/// set ret_code based on the error.
pub(crate) fn from_execution_error<T>(data: &T, next_peer_pks: Vec<String>, err: ExecutionError) -> StepperOutcome
pub(crate) fn from_execution_error<T>(data: &T, next_peer_pks: Vec<String>, err: Rc<ExecutionError>) -> StepperOutcome
where
T: ?Sized + Serialize,
{

View File

@ -31,6 +31,12 @@ pub struct CallServiceResult {
pub result: String,
}
impl std::fmt::Display for CallServiceResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ret_code: {}, result: '{}'", self.ret_code, self.result)
}
}
#[cfg(feature = "fce")]
pub(crate) use fce_target::call_service;
#[cfg(feature = "fce")]

View File

@ -19,10 +19,19 @@ mod avalue;
pub(crate) use avalue::AValue;
pub(crate) use avalue::ResolvedCallResult;
use crate::execution::ExecutionError;
use crate::SecurityTetraplet;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::rc::Rc;
/// Contains all necessary state needed to execute aqua script.
#[derive(Default)]
pub(crate) struct ExecutionCtx<'i> {
/// Contains all set variables.
// TODO: use shared string (Rc<String>) to avoid copying.
@ -37,6 +46,10 @@ pub(crate) struct ExecutionCtx<'i> {
/// PeerId of a peer send this aqua script.
pub init_peer_id: String,
/// Last error produced by local service.
/// None means that there weren't any error.
pub last_error: Option<LastErrorDescriptor>,
/// Indicates that previous executed subtree is complete.
/// A subtree treats as a complete if all subtree elements satisfy the following rules:
/// - at least one of par subtrees is completed
@ -49,15 +62,46 @@ pub(crate) struct ExecutionCtx<'i> {
pub met_folds: VecDeque<&'i str>,
}
#[derive(Debug)]
pub(crate) struct LastErrorDescriptor {
pub(crate) error: Rc<ExecutionError>,
pub(crate) instruction: String,
pub(crate) tetraplet: Option<SecurityTetraplet>,
}
impl LastErrorDescriptor {
pub(crate) fn new(error: Rc<ExecutionError>, instruction: String, tetraplet: Option<SecurityTetraplet>) -> Self {
Self {
error,
instruction,
tetraplet,
}
}
pub(crate) fn serialize(&self) -> String {
#[derive(Serialize, Deserialize)]
pub(crate) struct LastError<'s> {
pub(crate) error: String,
pub(crate) instruction: &'s str,
}
let error = format!("{}", &self.error);
let error_with_location = LastError {
error,
instruction: &self.instruction,
};
serde_json::to_string(&error_with_location).expect("default serializer shouldn't fail")
}
}
impl<'i> ExecutionCtx<'i> {
pub(crate) fn new(current_peer_id: String, init_peer_id: String) -> Self {
Self {
data_cache: HashMap::new(),
next_peer_pks: vec![],
current_peer_id,
init_peer_id,
subtree_complete: true,
met_folds: VecDeque::new(),
..<_>::default()
}
}
}

View File

@ -30,7 +30,7 @@ pub enum CallResult {
Executed(Rc<JValue>),
/// call_service ended with a service error.
CallServiceFailed(String),
CallServiceFailed(i32, Rc<String>),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -49,7 +49,7 @@ impl std::fmt::Display for ExecutedState {
Par(left, right) => write!(f, "Par({}, {})", left, right),
Call(RequestSentBy(peer_id)) => write!(f, "RequestSentBy({})", peer_id),
Call(Executed(result)) => write!(f, "Executed({:?})", result),
Call(CallServiceFailed(err_msg)) => write!(f, "CallServiceFailed({})", err_msg),
Call(CallServiceFailed(ret_code, err_msg)) => write!(f, "CallServiceFailed({}, {})", ret_code, err_msg),
}
}
}

View File

@ -27,5 +27,6 @@ pub mod execution_trace {
pub(crate) mod execution {
pub(crate) use super::execution_context::AValue;
pub(crate) use super::execution_context::ExecutionCtx;
pub(crate) use super::execution_context::LastErrorDescriptor;
pub(crate) use super::execution_context::ResolvedCallResult;
}

View File

@ -24,7 +24,9 @@ use super::ExecutionCtx;
use super::ExecutionError;
use super::ExecutionResult;
use super::ExecutionTraceCtx;
use crate::contexts::execution::LastErrorDescriptor;
use crate::log_instruction;
use crate::SecurityTetraplet;
use air_parser::ast::Call;
@ -45,8 +47,23 @@ impl<'i> super::ExecutableInstruction<'i> for Call<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut ExecutionTraceCtx) -> ExecutionResult<()> {
log_instruction!(call, exec_ctx, trace_ctx);
let resolved_call = joinable!(ResolvedCall::new(self, exec_ctx), exec_ctx)?;
joinable!(resolved_call.execute(exec_ctx, trace_ctx), exec_ctx)
let resolved_call = joinable!(ResolvedCall::new(self, exec_ctx), exec_ctx).map_err(|e| {
let instruction = format!("{:?}", self);
let last_error = LastErrorDescriptor::new(e.clone(), instruction, None);
exec_ctx.last_error = Some(last_error);
e
})?;
let triplet = resolved_call.as_triplet();
joinable!(resolved_call.execute(exec_ctx, trace_ctx), exec_ctx).map_err(|e| {
let tetraplet = SecurityTetraplet::from_triplet(triplet);
let instruction = format!("{:?}", self);
let last_error = LastErrorDescriptor::new(e.clone(), instruction, Some(tetraplet));
exec_ctx.last_error = Some(last_error);
e
})
}
}

View File

@ -22,6 +22,7 @@ use super::Call;
use super::ExecutionCtx;
use super::ExecutionError;
use super::ExecutionResult;
use crate::build_targets::CallServiceResult;
use crate::build_targets::CALL_SERVICE_SUCCESS;
use crate::contexts::execution_trace::*;
use crate::log_targets::EXECUTED_STATE_CHANGING;
@ -29,7 +30,7 @@ use crate::JValue;
use crate::ResolvedTriplet;
use crate::SecurityTetraplet;
use air_parser::ast::{CallArgValue, CallOutputValue};
use air_parser::ast::{CallInstrArgValue, CallOutputValue};
use std::rc::Rc;
@ -37,7 +38,7 @@ use std::rc::Rc;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub(super) struct ResolvedCall<'i> {
triplet: Rc<ResolvedTriplet>,
function_arg_paths: Rc<Vec<CallArgValue<'i>>>,
function_arg_paths: Rc<Vec<CallInstrArgValue<'i>>>,
output: CallOutputValue<'i>,
}
@ -67,7 +68,7 @@ impl<'i> ResolvedCall<'i> {
exec_ctx: &mut ExecutionCtx<'i>,
trace_ctx: &mut ExecutionTraceCtx,
) -> ExecutionResult<()> {
use CallResult::*;
use CallResult::Executed;
use ExecutedState::Call;
use ExecutionError::CallServiceResultDeError as DeError;
@ -88,7 +89,7 @@ impl<'i> ResolvedCall<'i> {
tetraplets,
} = self.resolve_args(exec_ctx)?;
let tetraplets = serde_json::to_string(&tetraplets).expect("default serializer shouldn't fail");
let serialized_tetraplets = serde_json::to_string(&tetraplets).expect("default serializer shouldn't fail");
let service_result = unsafe {
crate::build_targets::call_service(
@ -96,17 +97,12 @@ impl<'i> ResolvedCall<'i> {
self.triplet.service_id.clone(),
self.triplet.function_name.clone(),
call_arguments,
tetraplets,
serialized_tetraplets,
)
};
// check that service call succeeded
if service_result.ret_code != CALL_SERVICE_SUCCESS {
trace_ctx
.new_trace
.push_back(Call(CallServiceFailed(service_result.result.clone())));
return Err(ExecutionError::LocalServiceError(service_result.result));
}
let service_result = handle_service_error(service_result, trace_ctx)?;
let result: JValue = serde_json::from_str(&service_result.result).map_err(|e| DeError(service_result, e))?;
let result = Rc::new(result);
@ -125,6 +121,10 @@ impl<'i> ResolvedCall<'i> {
Ok(())
}
pub(super) fn as_triplet(&self) -> Rc<ResolvedTriplet> {
self.triplet.clone()
}
/// Determine whether this call should be really called and adjust prev executed trace accordingly.
fn prepare_executed_state(
&self,
@ -177,3 +177,25 @@ impl<'i> ResolvedCall<'i> {
Ok(resolved_arguments)
}
}
fn handle_service_error<'i>(
service_result: CallServiceResult,
trace_ctx: &mut ExecutionTraceCtx,
) -> ExecutionResult<CallServiceResult> {
use CallResult::CallServiceFailed;
use ExecutedState::Call;
if service_result.ret_code == CALL_SERVICE_SUCCESS {
return Ok(service_result);
}
let error_message = Rc::new(service_result.result);
let error = ExecutionError::LocalServiceError(service_result.ret_code, error_message.clone());
let error = Rc::new(error);
trace_ctx
.new_trace
.push_back(Call(CallServiceFailed(service_result.ret_code, error_message)));
Err(error)
}

View File

@ -17,17 +17,18 @@
use super::ExecutionCtx;
use super::ExecutionError;
use super::ExecutionResult;
use crate::exec_err;
use crate::JValue;
use air_parser::ast::{CallArgValue, FunctionPart, PeerPart};
use air_parser::ast::{CallInstrValue, FunctionPart, PeerPart};
use polyplets::ResolvedTriplet;
/// Triplet represents a location of the executable code in the network.
/// It is build from `PeerPart` and `FunctionPart` of a `Call` instruction.
pub(super) struct Triplet<'a, 'i> {
pub(super) peer_pk: &'a CallArgValue<'i>,
pub(super) service_id: &'a CallArgValue<'i>,
pub(super) function_name: &'a CallArgValue<'i>,
pub(super) peer_pk: &'a CallInstrValue<'i>,
pub(super) service_id: &'a CallInstrValue<'i>,
pub(super) function_name: &'a CallInstrValue<'i>,
}
impl<'a, 'i> Triplet<'a, 'i> {
@ -44,7 +45,7 @@ impl<'a, 'i> Triplet<'a, 'i> {
Ok((peer_pk, peer_service_id, func_name))
}
(PeerPk(peer_pk), ServiceIdWithFuncName(service_id, func_name)) => Ok((peer_pk, service_id, func_name)),
(PeerPk(_), FuncName(_)) => Err(ExecutionError::InstructionError(String::from(
(PeerPk(_), FuncName(_)) => exec_err!(ExecutionError::InstructionError(String::from(
"call should have service id specified by peer part or function part",
))),
}?;
@ -77,18 +78,18 @@ impl<'a, 'i> Triplet<'a, 'i> {
/// Resolve value to string by either resolving variable from `ExecutionCtx`, taking literal value, or etc.
// TODO: return Rc<String> to avoid excess cloning
fn resolve_to_string<'i>(value: &CallArgValue<'i>, ctx: &ExecutionCtx<'i>) -> ExecutionResult<String> {
fn resolve_to_string<'i>(value: &CallInstrValue<'i>, ctx: &ExecutionCtx<'i>) -> ExecutionResult<String> {
use crate::execution::utils::resolve_to_jvaluable;
let resolved = match value {
CallArgValue::InitPeerId => ctx.init_peer_id.clone(),
CallArgValue::Literal(value) => value.to_string(),
CallArgValue::Variable(name) => {
CallInstrValue::InitPeerId => ctx.init_peer_id.clone(),
CallInstrValue::Literal(value) => value.to_string(),
CallInstrValue::Variable(name) => {
let resolved = resolve_to_jvaluable(name, ctx)?;
let jvalue = resolved.into_jvalue();
jvalue_to_string(jvalue)?
}
CallArgValue::JsonPath { variable, path } => {
CallInstrValue::JsonPath { variable, path } => {
let resolved = resolve_to_jvaluable(variable, ctx)?;
let resolved = resolved.apply_json_path(path)?;
vec_to_string(resolved, path)?
@ -103,17 +104,17 @@ fn jvalue_to_string(jvalue: JValue) -> ExecutionResult<String> {
match jvalue {
JValue::String(s) => Ok(s),
_ => Err(IncompatibleJValueType(jvalue, "string")),
_ => exec_err!(IncompatibleJValueType(jvalue, "string")),
}
}
fn vec_to_string(values: Vec<&JValue>, json_path: &str) -> ExecutionResult<String> {
if values.is_empty() {
return Err(ExecutionError::VariableNotFound(json_path.to_string()));
return exec_err!(ExecutionError::VariableNotFound(json_path.to_string()));
}
if values.len() != 1 {
return Err(ExecutionError::MultipleValuesInJsonPath(json_path.to_string()));
return exec_err!(ExecutionError::MultipleValuesInJsonPath(json_path.to_string()));
}
jvalue_to_string(values[0].clone())

View File

@ -19,6 +19,7 @@ use super::ExecutionError;
use super::ExecutionResult;
use crate::contexts::execution::ResolvedCallResult;
use crate::contexts::execution_trace::*;
use crate::exec_err;
use crate::log_targets::EXECUTED_STATE_CHANGING;
use crate::JValue;
@ -60,13 +61,13 @@ pub(super) fn set_local_call_result<'i>(
// check that current execution flow is inside a fold block
if exec_ctx.met_folds.is_empty() {
// shadowing is allowed only inside fold blocks
return Err(MultipleVariablesFound(entry.key().clone()));
return exec_err!(MultipleVariablesFound(entry.key().clone()));
}
match entry.get() {
AValue::JValueRef(_) => {}
// shadowing is allowed only for scalar values
_ => return Err(ShadowingError(entry.key().clone())),
_ => return exec_err!(ShadowingError(entry.key().clone())),
};
entry.insert(AValue::JValueRef(executed_result));
@ -78,7 +79,7 @@ pub(super) fn set_local_call_result<'i>(
Occupied(mut entry) => match entry.get_mut() {
// if result is an array, insert result to the end of the array
AValue::JValueAccumulatorRef(values) => values.borrow_mut().push(executed_result),
v => return Err(IncompatibleAValueType(format!("{}", v), String::from("Array"))),
v => return exec_err!(IncompatibleAValueType(format!("{}", v), String::from("Array"))),
},
Vacant(entry) => {
entry.insert(AValue::JValueAccumulatorRef(RefCell::new(vec![executed_result])));
@ -124,11 +125,12 @@ pub(super) fn handle_prev_state<'i>(
match &prev_state {
// this call was failed on one of the previous executions,
// here it's needed to bubble this special error up
Call(CallServiceFailed(err_msg)) => {
Call(CallServiceFailed(ret_code, err_msg)) => {
let ret_code = *ret_code;
let err_msg = err_msg.clone();
trace_ctx.new_trace.push_back(prev_state);
exec_ctx.subtree_complete = false;
Err(ExecutionError::LocalServiceError(err_msg))
exec_err!(ExecutionError::LocalServiceError(ret_code, err_msg))
}
Call(RequestSentBy(..)) => {
let peer_pk = triplet.peer_pk.as_str();
@ -149,7 +151,7 @@ pub(super) fn handle_prev_state<'i>(
Ok(false)
}
// state has inconsistent order - return a error, call shouldn't be executed
par_state @ Par(..) => Err(ExecutionError::InvalidExecutedState(
par_state @ Par(..) => exec_err!(ExecutionError::InvalidExecutedState(
String::from("call"),
par_state.clone(),
)),

View File

@ -23,6 +23,7 @@ use super::ExecutionTraceCtx;
use super::Instruction;
use crate::contexts::execution::AValue;
use crate::contexts::execution::ResolvedCallResult;
use crate::exec_err;
use crate::execution::boxed_value::*;
use crate::log_instruction;
@ -69,7 +70,7 @@ impl<'i> super::ExecutableInstruction<'i> for Fold<'i> {
.insert(self.iterator.to_string(), AValue::JValueFoldCursor(fold_state));
if previous_value.is_some() {
return Err(MultipleFoldStates(self.iterator.to_string()));
return exec_err!(MultipleFoldStates(self.iterator.to_string()));
}
exec_ctx.met_folds.push_back(self.iterator);
@ -99,7 +100,7 @@ impl<'i> super::ExecutableInstruction<'i> for Next<'i> {
v => {
// it's not possible to use unreachable here
// because at now next syntactically could be used without fold
return Err(IncompatibleAValueType(
return exec_err!(IncompatibleAValueType(
format!("{}", v),
String::from("JValueFoldCursor"),
));

View File

@ -15,6 +15,7 @@
*/
use super::*;
use crate::exec_err;
use crate::JValue;
use crate::ResolvedTriplet;
use crate::SecurityTetraplet;
@ -65,7 +66,7 @@ fn handle_instruction_variable<'ctx>(
let call_result = ResolvedCallResult { result, triplet };
from_call_result(call_result)?
}
_ => return Err(ExecutionError::VariableNotFound(variable_name.to_string())),
_ => return exec_err!(ExecutionError::VariableNotFound(variable_name.to_string())),
};
Ok(iterable)
@ -83,7 +84,7 @@ fn from_call_result(call_result: ResolvedCallResult) -> ExecutionResult<Option<I
}
array.len()
}
v => return Err(IncompatibleJValueType((*v).clone(), "array")),
v => return exec_err!(IncompatibleJValueType((*v).clone(), "array")),
};
let foldable = IterableResolvedCall::init(call_result, len);
@ -133,7 +134,7 @@ fn handle_instruction_json_path<'ctx>(
from_jvalues(jvalues, triplet, json_path)
}
_ => return Err(ExecutionError::VariableNotFound(variable_name.to_string())),
_ => return exec_err!(ExecutionError::VariableNotFound(variable_name.to_string())),
};
Ok(iterable)
@ -145,7 +146,7 @@ fn apply_json_path<'jvalue, 'str>(
) -> ExecutionResult<Vec<&'jvalue JValue>> {
use ExecutionError::JValueJsonPathError;
select(jvalue, json_path).map_err(|e| JValueJsonPathError(jvalue.clone(), json_path.to_string(), e))
select(jvalue, json_path).map_err(|e| Rc::new(JValueJsonPathError(jvalue.clone(), json_path.to_string(), e)))
}
/// Applies json_path to provided jvalues and construct IterableValue from the result and given triplet.

View File

@ -30,7 +30,7 @@ impl<'i> super::ExecutableInstruction<'i> for Match<'i> {
let are_values_equal = are_matchable_eq(&self.left_value, &self.right_value, exec_ctx)?;
if !are_values_equal {
return Err(ExecutionError::MatchWithoutXorError);
return crate::exec_err!(ExecutionError::MatchWithoutXorError);
}
self.instruction.execute(exec_ctx, trace_ctx)

View File

@ -30,7 +30,7 @@ impl<'i> super::ExecutableInstruction<'i> for MisMatch<'i> {
let are_values_equal = are_matchable_eq(&self.left_value, &self.right_value, exec_ctx)?;
if are_values_equal {
return Err(ExecutionError::MatchWithoutXorError);
return crate::exec_err!(ExecutionError::MismatchWithoutXorError);
}
self.instruction.execute(exec_ctx, trace_ctx)
@ -184,10 +184,10 @@ mod tests {
let res = call_vm!(set_variable_vm, "asd", script.clone(), "", "");
let res = call_vm!(vm, "asd", script.clone(), "", res.data);
assert_eq!(res.ret_code, 1015);
assert_eq!(res.ret_code, 1016);
let res = call_vm!(vm, "asd", script, "", res.data);
assert_eq!(res.ret_code, 1015);
assert_eq!(res.ret_code, 1016);
}
}

View File

@ -29,10 +29,26 @@ pub(crate) use fold::FoldState;
pub(self) use super::ExecutionError;
pub(self) use super::ExecutionResult;
pub(self) use crate::contexts::execution::ExecutionCtx;
pub(self) use crate::contexts::execution::LastErrorDescriptor;
pub(self) use crate::contexts::execution_trace::ExecutionTraceCtx;
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) => {
match $instr.execute($exec_ctx, $trace_ctx) {
Err(e) => {
let instruction = format!("{:?}", $self);
let last_error = LastErrorDescriptor::new(e.clone(), instruction, None);
$exec_ctx.last_error = Some(last_error);
Err(e)
}
v => v,
}
};
}
pub(crate) trait ExecutableInstruction<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut ExecutionTraceCtx) -> ExecutionResult<()>;
}
@ -40,15 +56,17 @@ pub(crate) trait ExecutableInstruction<'i> {
impl<'i> ExecutableInstruction<'i> for Instruction<'i> {
fn execute(&self, exec_ctx: &mut ExecutionCtx<'i>, trace_ctx: &mut ExecutionTraceCtx) -> ExecutionResult<()> {
match self {
// call isn't wrapped by the execute macro because
// it internally sets last_error with resolved triplet
Instruction::Call(call) => call.execute(exec_ctx, trace_ctx),
Instruction::Fold(fold) => fold.execute(exec_ctx, trace_ctx),
Instruction::Next(next) => next.execute(exec_ctx, trace_ctx),
Instruction::Null(null) => null.execute(exec_ctx, trace_ctx),
Instruction::Par(par) => par.execute(exec_ctx, trace_ctx),
Instruction::Seq(seq) => seq.execute(exec_ctx, trace_ctx),
Instruction::Xor(xor) => xor.execute(exec_ctx, trace_ctx),
Instruction::Match(match_) => match_.execute(exec_ctx, trace_ctx),
Instruction::MisMatch(mismatch) => mismatch.execute(exec_ctx, trace_ctx),
Instruction::Fold(fold) => execute!(self, fold, exec_ctx, trace_ctx),
Instruction::Next(next) => execute!(self, next, exec_ctx, trace_ctx),
Instruction::Null(null) => execute!(self, null, exec_ctx, trace_ctx),
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),
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

@ -83,7 +83,7 @@ fn extract_subtree_sizes(trace_ctx: &mut ExecutionTraceCtx) -> ExecutionResult<(
// unwrap is safe here because of length's been checked
match trace_ctx.current_trace.pop_front().unwrap() {
ExecutedState::Par(left, right) => Ok((left, right)),
state => Err(InvalidExecutedState(String::from("par"), state)),
state => crate::exec_err!(InvalidExecutedState(String::from("par"), state)),
}
}
@ -113,10 +113,10 @@ fn execute_subtree<'i>(
}
// if there is a service error, update already added Par state
// and then bubble the error up
err @ Err(LocalServiceError(_)) => {
Err(err) if matches!(&*err, LocalServiceError(..)) => {
update_par_state(trace_ctx, subtree_type, current_par_pos, before_new_path_len);
trace_ctx.current_subtree_size = before_subtree_size - subtree_size;
err
Err(err)
}
err @ Err(_) => err,
}

View File

@ -61,7 +61,9 @@ mod tests {
use std::rc::Rc;
fn fallible_call_service(fallible_service_id: String) -> CallServiceClosure {
fn fallible_call_service(fallible_service_id: impl Into<String>) -> CallServiceClosure {
let fallible_service_id = fallible_service_id.into();
Box::new(move |_, args| -> Option<IValue> {
let builtin_service = match &args[0] {
IValue::String(str) => str,
@ -71,7 +73,7 @@ mod tests {
// return a error for service with such id
if builtin_service == &fallible_service_id {
Some(IValue::Record(
NEVec::new(vec![IValue::S32(1), IValue::String(String::from(r#""error""#))]).unwrap(),
NEVec::new(vec![IValue::S32(1), IValue::String(String::from("error"))]).unwrap(),
))
} else {
// return success for services with other ids
@ -105,7 +107,10 @@ mod tests {
let executed_call_result = Call(Executed(Rc::new(JValue::String(String::from("res")))));
assert_eq!(actual_trace.len(), 2);
assert_eq!(actual_trace[0], Call(CallServiceFailed(String::from(r#""error""#))));
assert_eq!(
actual_trace[0],
Call(CallServiceFailed(1, Rc::new(String::from(r#"error"#))))
);
assert_eq!(actual_trace[1], executed_call_result);
let script = format!(
@ -223,7 +228,7 @@ mod tests {
Call(Executed(executed_call_result.clone())),
Call(Executed(executed_call_result.clone())),
Par(1, 0),
Call(CallServiceFailed(String::from(r#""error""#))),
Call(CallServiceFailed(1, Rc::new(String::from(r#"error"#)))),
Call(Executed(executed_call_result.clone())),
Call(Executed(executed_call_result.clone())),
];
@ -234,4 +239,35 @@ mod tests {
let actual_trace: ExecutionTrace = serde_json::from_slice(&result.data).expect("should be valid json");
assert_eq!(actual_trace, expected_trace);
}
#[test]
fn last_error_with_xor() {
use crate::contexts::execution_trace::CallResult::*;
use crate::contexts::execution_trace::ExecutedState::*;
use aqua_test_utils::echo_string_call_service;
let faillible_peer_id = "failible_peer_id";
let mut faillible_vm = create_aqua_vm(fallible_call_service("service_id_1"), faillible_peer_id);
let local_peer_id = "local_peer_id";
let mut vm = create_aqua_vm(echo_string_call_service(), local_peer_id);
let script = format!(
r#"
(xor
(call "{0}" ("service_id_1" "local_fn_name") [] result)
(call "{1}" ("service_id_2" "local_fn_name") [%last_error%] result)
)"#,
faillible_peer_id, local_peer_id,
);
let res = call_vm!(faillible_vm, "asd", script.clone(), "", "");
let res = call_vm!(vm, "asd", script, "", res.data);
let actual_trace: ExecutionTrace = serde_json::from_slice(&res.data).expect("should be valid json");
let expected_state = Call(Executed(Rc::new(JValue::String(String::from(
"{\"error\":\"Local service error: ret_code is 1, error message is \'error\'\",\"instruction\":\"Call { peer_part: PeerPk(Literal(\\\"failible_peer_id\\\")), function_part: ServiceIdWithFuncName(Literal(\\\"service_id_1\\\"), Literal(\\\"local_fn_name\\\")), args: [], output: Scalar(\\\"result\\\") }\"}"
)))));
assert_eq!(actual_trace[1], expected_state);
}
}

View File

@ -23,11 +23,13 @@ use jsonpath_lib::JsonPathError;
use serde_json::Error as SerdeJsonError;
use thiserror::Error as ThisError;
use std::rc::Rc;
/// Errors arised while executing AIR script.
#[derive(ThisError, Debug)]
pub(crate) enum ExecutionError {
/// Errors occurred while parsing returned by call_service value.
#[error("call_service result '{0:?}' can't be serialized or deserialized with an error: {1:?}")]
#[error("call_service result '{0}' can't be serialized or deserialized with an error: {1}")]
CallServiceResultDeError(CallServiceResult, SerdeJsonError),
/// Semantic errors in instructions.
@ -35,8 +37,8 @@ pub(crate) enum ExecutionError {
InstructionError(String),
/// An error is occurred while calling local service via call_service.
#[error("{0}")]
LocalServiceError(String),
#[error("Local service error: ret_code is {0}, error message is '{1}'")]
LocalServiceError(i32, Rc<String>),
/// Value for such name isn't presence in data.
#[error("variable with name '{0}' isn't present in data")]
@ -47,19 +49,19 @@ pub(crate) enum ExecutionError {
MultipleVariablesFound(String),
/// An error occurred while trying to apply json path to this JValue.
#[error("variable with path '{1}' not found in '{0:?}' with an error: '{2:?}'")]
#[error("variable with path '{1}' not found in '{0}' with an error: '{2}'")]
JValueJsonPathError(JValue, String, JsonPathError),
/// An error occurred while trying to apply json path to this accumulator with JValue's.
#[error("variable with path '{1}' not found in '{0:?}' with error: '{2:?}'")]
#[error("variable with path '{1}' not found in '{0:?}' with error: '{2}'")]
JValueAccJsonPathError(Vec<ResolvedCallResult>, String, JsonPathError),
/// Provided JValue has incompatible with target type.
#[error("expected JValue type '{1}', but got '{0:?}' JValue")]
#[error("expected JValue type '{1}', but got '{0}' JValue")]
IncompatibleJValueType(JValue, &'static str),
/// Provided AValue has incompatible with target type.
#[error("expected AValue type '{1}', but got '{0:?}' AValue")]
#[error("expected AValue type '{1}', but got '{0}' AValue")]
IncompatibleAValueType(String, String),
/// Multiple values found for such json path.
@ -85,6 +87,10 @@ pub(crate) enum ExecutionError {
/// 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,
/// 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,
}
impl ExecutionError {
@ -94,7 +100,7 @@ impl ExecutionError {
match self {
CallServiceResultDeError(..) => 1,
InstructionError(_) => 2,
LocalServiceError(_) => 3,
LocalServiceError(..) => 3,
VariableNotFound(_) => 4,
MultipleVariablesFound(_) => 5,
JValueJsonPathError(..) => 6,
@ -107,6 +113,7 @@ impl ExecutionError {
InvalidExecutedState(..) => 13,
ShadowingError(_) => 14,
MatchWithoutXorError => 15,
MismatchWithoutXorError => 16,
}
}
}

View File

@ -23,4 +23,6 @@ pub(super) use air::ExecutableInstruction;
pub(super) use air::FoldState;
pub(super) use errors::ExecutionError;
pub(self) type ExecutionResult<T> = std::result::Result<T, ExecutionError>;
use std::rc::Rc;
pub(self) type ExecutionResult<T> = std::result::Result<T, Rc<ExecutionError>>;

View File

@ -22,41 +22,73 @@ use crate::execution::ExecutionResult;
use crate::JValue;
use crate::SecurityTetraplet;
use air_parser::ast::CallArgValue;
use air_parser::ast::CallInstrArgValue;
/// Resolve value to called function arguments.
pub(crate) fn resolve_to_args<'i>(
value: &CallArgValue<'i>,
value: &CallInstrArgValue<'i>,
ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(JValue, Vec<SecurityTetraplet>)> {
fn handle_string_arg<'i>(arg: &str, ctx: &ExecutionCtx<'i>) -> ExecutionResult<(JValue, Vec<SecurityTetraplet>)> {
let jvalue = JValue::String(arg.to_string());
let tetraplet = SecurityTetraplet::literal_tetraplet(ctx.init_peer_id.clone());
Ok((jvalue, vec![tetraplet]))
}
match value {
CallArgValue::InitPeerId => handle_string_arg(ctx.init_peer_id.as_str(), ctx),
CallArgValue::Literal(value) => handle_string_arg(value, ctx),
CallArgValue::Variable(name) => {
let resolved = resolve_to_jvaluable(name, ctx)?;
let tetraplets = resolved.as_tetraplets();
let jvalue = resolved.into_jvalue();
Ok((jvalue, tetraplets))
}
CallArgValue::JsonPath { variable, path } => {
let resolved = resolve_to_jvaluable(variable, ctx)?;
let (jvalue, tetraplets) = resolved.apply_json_path_with_tetraplets(path)?;
let jvalue = jvalue.into_iter().cloned().collect::<Vec<_>>();
let jvalue = JValue::Array(jvalue);
Ok((jvalue, tetraplets))
}
CallInstrArgValue::InitPeerId => prepare_string_arg(ctx.init_peer_id.as_str(), ctx),
CallInstrArgValue::LastError => prepare_last_error(ctx),
CallInstrArgValue::Literal(value) => prepare_string_arg(value, ctx),
CallInstrArgValue::Variable(name) => prepare_variable(name, ctx),
CallInstrArgValue::JsonPath { variable, path } => prepare_json_path(variable, path, ctx),
}
}
fn prepare_string_arg<'i>(arg: &str, ctx: &ExecutionCtx<'i>) -> ExecutionResult<(JValue, Vec<SecurityTetraplet>)> {
let jvalue = JValue::String(arg.to_string());
let tetraplet = SecurityTetraplet::literal_tetraplet(ctx.init_peer_id.clone());
Ok((jvalue, vec![tetraplet]))
}
fn prepare_last_error<'i>(ctx: &ExecutionCtx<'i>) -> ExecutionResult<(JValue, Vec<SecurityTetraplet>)> {
let result = match &ctx.last_error {
Some(error) => {
let serialized_error = error.serialize();
let jvalue = JValue::String(serialized_error);
let tetraplets = error
.tetraplet
.clone()
.unwrap_or_else(|| SecurityTetraplet::literal_tetraplet(ctx.init_peer_id.clone()));
(jvalue, vec![tetraplets])
}
None => {
let jvalue = JValue::String(String::new());
let tetraplets = vec![];
(jvalue, tetraplets)
}
};
Ok(result)
}
fn prepare_variable<'i>(name: &str, ctx: &ExecutionCtx<'i>) -> ExecutionResult<(JValue, Vec<SecurityTetraplet>)> {
let resolved = resolve_to_jvaluable(name, ctx)?;
let tetraplets = resolved.as_tetraplets();
let jvalue = resolved.into_jvalue();
Ok((jvalue, tetraplets))
}
fn prepare_json_path<'i>(
name: &str,
json_path: &str,
ctx: &ExecutionCtx<'i>,
) -> ExecutionResult<(JValue, Vec<SecurityTetraplet>)> {
let resolved = resolve_to_jvaluable(name, ctx)?;
let (jvalue, tetraplets) = resolved.apply_json_path_with_tetraplets(json_path)?;
let jvalue = jvalue.into_iter().cloned().collect::<Vec<_>>();
let jvalue = JValue::Array(jvalue);
Ok((jvalue, tetraplets))
}
/// Constructs jvaluable result from `ExecutionCtx::data_cache` by name.
pub(crate) fn resolve_to_jvaluable<'name, 'i, 'ctx>(
name: &'name str,

View File

@ -57,3 +57,10 @@ pub mod parser {
}
pub(crate) type JValue = serde_json::Value;
#[macro_export]
macro_rules! exec_err {
($err:expr) => {
Err(std::rc::Rc::new($err))
};
}

View File

@ -128,14 +128,14 @@ fn merge_call(prev_call_result: CallResult, current_call_result: CallResult) ->
use super::DataMergingError::IncompatibleCallResults;
match (&prev_call_result, &current_call_result) {
(CallServiceFailed(prev_err_msg), CallServiceFailed(err_msg)) => {
if prev_err_msg != err_msg {
(CallServiceFailed(prev_ret_code, prev_err_msg), CallServiceFailed(ret_code, err_msg)) => {
if prev_ret_code != ret_code || prev_err_msg != err_msg {
return Err(IncompatibleCallResults(prev_call_result, current_call_result));
}
Ok(current_call_result)
}
(RequestSentBy(_), CallServiceFailed(_)) => Ok(current_call_result),
(CallServiceFailed(_), RequestSentBy(_)) => Ok(prev_call_result),
(RequestSentBy(_), CallServiceFailed(..)) => Ok(current_call_result),
(CallServiceFailed(..), RequestSentBy(_)) => Ok(prev_call_result),
(RequestSentBy(prev_sender), RequestSentBy(sender)) => {
if prev_sender != sender {
return Err(IncompatibleCallResults(prev_call_result, current_call_result));
@ -152,8 +152,8 @@ fn merge_call(prev_call_result: CallResult, current_call_result: CallResult) ->
Ok(prev_call_result)
}
(CallServiceFailed(_), Executed(..)) => Err(IncompatibleCallResults(prev_call_result, current_call_result)),
(Executed(..), CallServiceFailed(_)) => Err(IncompatibleCallResults(prev_call_result, current_call_result)),
(CallServiceFailed(..), Executed(..)) => Err(IncompatibleCallResults(prev_call_result, current_call_result)),
(Executed(..), CallServiceFailed(..)) => Err(IncompatibleCallResults(prev_call_result, current_call_result)),
}
}