From 4208bfc7157c594f23ae1ba27c55f7fe69d1b0ac Mon Sep 17 00:00:00 2001 From: Giuseppe Baccini Date: Thu, 12 Oct 2023 17:59:35 +0200 Subject: [PATCH] add inline scripting feature for accessing out yaml tree It is now possible to evaluate a property in the user's yaml by referencing a property in the processing output yaml. The format for referencing a path in the output yaml is: - {{ .property }} - {{ .property[n] }} And all the arbitrary compositions of the above base cases. An abbreviated format is also possible: {{ .[n][m].property }} This is equivalent to: {{ .conversations[n].requests[m].property }} Example: conversations: host: localhost:7480 requests: - method: POST uri: test-5/mp.3 queryString: format=json&uploads - method: PUT uri: test-5/mp.3 queryString: format=json&partNumber=1&uploadId={{.[0][0].response.body.UploadId}} The queryString property in the second request is evaluated referencing the 'response.body.UploadId' path starting from the .conversations[0].requests[0] node in the output yaml. Signed-off-by: Giuseppe Baccini --- src/.astylerc | 4 +- src/CMakeLists.txt | 1 + src/conversation.cpp | 128 ++++++++++++++++++++++--- src/conversation.h | 6 ++ src/jsenv.cpp | 1 - src/jsenv.h | 30 +++--- src/request.cpp | 129 +++++++++++++++++++++---- src/request.h | 6 ++ src/scenario.cpp | 217 ++++++++++++++++++++++++++++++++++++++++++- src/scenario.h | 84 +++++++++++++++++ src/utils.cpp | 81 ++++++++++++++++ src/utils.h | 34 ++++++- test/.astylerc | 4 +- test/CMakeLists.txt | 2 +- 14 files changed, 675 insertions(+), 52 deletions(-) diff --git a/src/.astylerc b/src/.astylerc index f79b44c..28af5d4 100644 --- a/src/.astylerc +++ b/src/.astylerc @@ -15,7 +15,7 @@ --indent-switches --indent-col1-comments --min-conditional-indent=2 ---max-instatement-indent=100 ---add-brackets +--max-continuation-indent=100 +--add-braces --lineend=linux --convert-tabs diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ff1f803..cfd7a1b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ project(chatterbox_binary VERSION 0.0.0) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_options(-Wall -std=c++17) +# add_compile_options(-g -Wall -std=c++17) endif() add_definitions(-DV8_COMPRESS_POINTERS diff --git a/src/conversation.cpp b/src/conversation.cpp index cfe0d2b..e0d1812 100644 --- a/src/conversation.cpp +++ b/src/conversation.cpp @@ -33,6 +33,8 @@ void conversation::statistics::incr_categorization(const std::string &key) conversation::conversation(scenario &parent) : parent_(parent), + scen_out_p_resolv_(parent_.scen_out_p_resolv_), + scen_p_evaluator_(parent_.scen_p_evaluator_), js_env_(parent.js_env_), stats_(*this), event_log_(parent.event_log_) @@ -71,16 +73,30 @@ int conversation::process(ryml::NodeRef conversation_in, if(scope.enabled_) { parent_.stats_.incr_conversation_count(); - - auto raw_host = js_env_.eval_as(conversation_out, key_host); + bool further_eval = false; + auto raw_host = js_env_.eval_as(conversation_out, + key_host, + std::nullopt, + true, + nullptr, + PROP_EVAL_RGX, + &further_eval); if(!raw_host) { - event_log_->error(ERR_FAIL_READ_HOST); - return 1; - } else { - conversation_out.remove_child(key_host); - conversation_out[key_host] << *raw_host; + if(further_eval) { + raw_host = scen_p_evaluator_.eval_as(conversation_out, + key_host, + scen_out_p_resolv_); + } + if(!raw_host) { + event_log_->error(ERR_FAIL_READ_HOST); + utils::clear_map_node_put_key_val(conversation_out, key_error, ERR_FAIL_READ_HOST); + return 1; + } } + conversation_out.remove_child(key_host); + conversation_out[key_host] << *raw_host; + raw_host_ = *raw_host; auto host = raw_host_; utils::find_and_replace(host, "http://", ""); @@ -88,9 +104,27 @@ int conversation::process(ryml::NodeRef conversation_in, host = host.substr(0, (host.find(':') == std::string::npos ? host.length() : host.find(':'))); //auth + further_eval = false; if(conversation_out.has_child(key_auth)) { ryml::NodeRef auth_node = conversation_out[key_auth]; - auto service = js_env_.eval_as(auth_node, key_service, "s3"); + auto service = js_env_.eval_as(auth_node, + key_service, + "s3", + true, + nullptr, + PROP_EVAL_RGX, + &further_eval); + + if(further_eval) { + service = scen_p_evaluator_.eval_as(conversation_out, + key_service, + scen_out_p_resolv_); + if(!service) { + event_log_->error(ERR_FAIL_EVAL); + utils::clear_map_node_put_key_val(conversation_out, key_error, ERR_FAIL_EVAL); + return 1; + } + } if(service) { if(auth_node.has_child(key_service)) { auth_node.remove_child(key_service); @@ -98,21 +132,73 @@ int conversation::process(ryml::NodeRef conversation_in, auth_node[key_service] << *service; } - auto access_key = js_env_.eval_as(auth_node, key_access_key); + //access_key + further_eval = false; + auto access_key = js_env_.eval_as(auth_node, + key_access_key, + std::nullopt, + true, + nullptr, + PROP_EVAL_RGX, + &further_eval); + if(further_eval) { + access_key = scen_p_evaluator_.eval_as(conversation_out, + key_access_key, + scen_out_p_resolv_); + if(!access_key) { + event_log_->error(ERR_FAIL_EVAL); + utils::clear_map_node_put_key_val(conversation_out, key_error, ERR_FAIL_EVAL); + return 1; + } + } if(access_key) { auth_node.remove_child(key_access_key); auth_node[key_access_key] << *access_key; } - auto secret_key = js_env_.eval_as(auth_node, key_secret_key); + //secret_key + further_eval = false; + auto secret_key = js_env_.eval_as(auth_node, + key_secret_key, + std::nullopt, + true, + nullptr, + PROP_EVAL_RGX, + &further_eval); + if(further_eval) { + secret_key = scen_p_evaluator_.eval_as(conversation_out, + key_secret_key, + scen_out_p_resolv_); + if(!secret_key) { + event_log_->error(ERR_FAIL_EVAL); + utils::clear_map_node_put_key_val(conversation_out, key_error, ERR_FAIL_EVAL); + return 1; + } + } if(secret_key) { auth_node.remove_child(key_secret_key); auth_node[key_secret_key] << *secret_key; } + //signed_headers + further_eval = false; auto signed_headers = js_env_.eval_as(auth_node, key_signed_headers, - AUTH_AWS_DEF_SIGN_HDRS); + AUTH_AWS_DEF_SIGN_HDRS, + true, + nullptr, + PROP_EVAL_RGX, + &further_eval); + if(further_eval) { + signed_headers = scen_p_evaluator_.eval_as(conversation_out, + key_signed_headers, + scen_out_p_resolv_); + if(!signed_headers) { + event_log_->error(ERR_FAIL_EVAL); + utils::clear_map_node_put_key_val(conversation_out, key_error, ERR_FAIL_EVAL); + return 1; + } + } if(signed_headers) { if(auth_node.has_child(key_signed_headers)) { auth_node.remove_child(key_signed_headers); @@ -120,7 +206,25 @@ int conversation::process(ryml::NodeRef conversation_in, auth_node[key_signed_headers] << *signed_headers; } - auto region = js_env_.eval_as(auth_node, key_region, "US"); + //region + auto region = js_env_.eval_as(auth_node, + key_region, + "US", + true, + nullptr, + PROP_EVAL_RGX, + &further_eval); + + if(further_eval) { + region = scen_p_evaluator_.eval_as(conversation_out, + key_region, + scen_out_p_resolv_); + if(!region) { + event_log_->error(ERR_FAIL_EVAL); + utils::clear_map_node_put_key_val(conversation_out, key_error, ERR_FAIL_EVAL); + return 1; + } + } if(region) { if(auth_node.has_child(key_region)) { auth_node.remove_child(key_region); diff --git a/src/conversation.h b/src/conversation.h index da06717..adb664f 100644 --- a/src/conversation.h +++ b/src/conversation.h @@ -48,6 +48,12 @@ struct conversation { //parent scenario &parent_; + //scenario property resolver + scenario_property_resolver &scen_out_p_resolv_; + + //scenario property evaluator + scenario_property_evaluator &scen_p_evaluator_; + //js environment js::js_env &js_env_; diff --git a/src/jsenv.cpp b/src/jsenv.cpp index e1e7c76..800871a 100755 --- a/src/jsenv.cpp +++ b/src/jsenv.cpp @@ -70,7 +70,6 @@ int js_env::reset() v8::NewStringType::kNormal).ToLocalChecked(), v8::FunctionTemplate::New(isolate_, cbk_log)); - //bind cbk_load. global->Set(v8::String::NewFromUtf8(isolate_, "load", diff --git a/src/jsenv.h b/src/jsenv.h index dfe6fe1..a7505ea 100644 --- a/src/jsenv.h +++ b/src/jsenv.h @@ -18,8 +18,7 @@ struct converter { if(!val.is_keyval() && !val.is_val()) { return false; } - bool bval; - val >> bval; + //@todo return true; } static bool isType(const v8::Local &val) { @@ -44,8 +43,7 @@ struct converter { if(!val.is_keyval() && !val.is_val()) { return false; } - int32_t ival; - val >> ival; + //@todo return true; } static bool isType(const v8::Local &val) { @@ -70,8 +68,7 @@ struct converter { if(!val.is_keyval() && !val.is_val()) { return false; } - uint32_t uival; - val >> uival; + //@todo return true; } static bool isType(const v8::Local &val) { @@ -96,8 +93,7 @@ struct converter { if(!val.is_keyval() && !val.is_val()) { return false; } - double dval; - val >> dval; + //@todo return true; } static bool isType(const v8::Local &val) { @@ -122,8 +118,7 @@ struct converter { if(!val.is_keyval() && !val.is_val()) { return false; } - std::string sval; - val >> sval; + //@todo return true; } static bool isType(const v8::Local &val) { @@ -219,7 +214,7 @@ struct js_env { static void cbk_assert(const v8::FunctionCallbackInfo &args); // ---------------------------- - // --- Json Field Evaluator --- + // --- Yaml Field Evaluator --- // ---------------------------- template @@ -227,11 +222,22 @@ struct js_env { const char *key, const std::optional default_value = std::nullopt, bool log_errors = true, - bool *is_error = nullptr) { + bool *is_error = nullptr, + const char *check_regex = nullptr, + bool *further_eval = nullptr) { if(!from.has_child(ryml::to_csubstr(key))) { return default_value; } ryml::NodeRef val = from[ryml::to_csubstr(key)]; + + if(check_regex && (val.is_keyval() || val.is_val())) { + auto str_val = utils::converter::asType(val); + if(std::regex_search(utils::converter::asType(val), std::regex(check_regex))) { + *further_eval = true; + return std::nullopt; + } + } + if(utils::converter::isType(val)) { //eval as primitive value return utils::converter::asType(val); diff --git a/src/request.cpp b/src/request.cpp index 39cf1a8..f2dc895 100644 --- a/src/request.cpp +++ b/src/request.cpp @@ -13,6 +13,8 @@ const std::string algorithm = "AWS4-HMAC-SHA256"; namespace cbox { request::request(conversation &parent) : parent_(parent), + scen_out_p_resolv_(parent_.scen_out_p_resolv_), + scen_p_evaluator_(parent_.scen_p_evaluator_), js_env_(parent_.js_env_), event_log_(parent_.event_log_) {} @@ -39,10 +41,24 @@ int request::process(const std::string &raw_host, } // for - auto pfor = js_env_.eval_as(request_in, key_for, 1); + bool further_eval = false; + auto pfor = js_env_.eval_as(request_in, + key_for, + 1, + true, + nullptr, + PROP_EVAL_RGX, + &further_eval); if(!pfor) { - event_log_->error(ERR_FAIL_READ_FOR); - return 1; + if(further_eval) { + pfor = scen_p_evaluator_.eval_as(request_in, + key_for, + scen_out_p_resolv_); + } + if(!pfor) { + event_log_->error(ERR_FAIL_READ_FOR); + return 1; + } } for(uint32_t i = 0; i < pfor; ++i) { @@ -73,34 +89,77 @@ int request::process(const std::string &raw_host, parent_.parent_.stats_.incr_request_count(); // method - auto method = js_env_.eval_as(request_in, key_method); + further_eval = false; + auto method = js_env_.eval_as(request_in, + key_method, + std::nullopt, + true, + nullptr, + PROP_EVAL_RGX, + &further_eval); if(!method) { - res = 1; - event_log_->error(ERR_FAIL_READ_METHOD); - utils::clear_map_node_put_key_val(request_out, key_error, ERR_FAIL_READ_METHOD); - } else { - request_out.remove_child(key_method); - request_out[key_method] << *method; + if(further_eval) { + method = scen_p_evaluator_.eval_as(request_in, + key_method, + scen_out_p_resolv_); + } + if(!method) { + res = 1; + event_log_->error(ERR_FAIL_READ_METHOD); + utils::clear_map_node_put_key_val(request_out, key_error, ERR_FAIL_READ_METHOD); + } } + request_out.remove_child(key_method); + request_out[key_method] << *method; // uri + further_eval = false; std::optional uri; if(!res) { - uri = js_env_.eval_as(request_in, key_uri); + uri = js_env_.eval_as(request_in, + key_uri, + std::nullopt, + true, + nullptr, + PROP_EVAL_RGX, + &further_eval); if(!uri) { - res = 1; - event_log_->error(ERR_FAIL_READ_URI); - utils::clear_map_node_put_key_val(request_out, key_error, ERR_FAIL_READ_URI); - } else { - request_out.remove_child(key_uri); - request_out[key_uri] << *uri; + if(further_eval) { + uri = scen_p_evaluator_.eval_as(request_in, + key_uri, + scen_out_p_resolv_); + } + if(!uri) { + res = 1; + event_log_->error(ERR_FAIL_READ_URI); + utils::clear_map_node_put_key_val(request_out, key_error, ERR_FAIL_READ_URI); + } } + request_out.remove_child(key_uri); + request_out[key_uri] << *uri; } // query_string + further_eval = false; std::optional query_string; if(!res) { - query_string = js_env_.eval_as(request_in, key_query_string); + query_string = js_env_.eval_as(request_in, + key_query_string, + std::nullopt, + true, + nullptr, + PROP_EVAL_RGX, + &further_eval); + if(further_eval) { + query_string = scen_p_evaluator_.eval_as(request_in, + key_query_string, + scen_out_p_resolv_); + if(!query_string) { + res = 1; + event_log_->error(ERR_FAIL_EVAL); + utils::clear_map_node_put_key_val(request_out, key_error, ERR_FAIL_EVAL); + } + } if(query_string) { request_out.remove_child(key_query_string); request_out[key_query_string] << *query_string; @@ -108,6 +167,7 @@ int request::process(const std::string &raw_host, } // data + further_eval = false; std::optional data; bool is_error = false; if(!res) { @@ -115,7 +175,9 @@ int request::process(const std::string &raw_host, key_data, std::nullopt, false, - &is_error); + &is_error, + PROP_EVAL_RGX, + &further_eval); if(is_error && request_in.has_child(key_data)) { ryml::NodeRef node_data_in = request_in[key_data]; @@ -134,6 +196,16 @@ int request::process(const std::string &raw_host, os << node_data; data.emplace(os.str()); } + if(further_eval) { + data = scen_p_evaluator_.eval_as(request_in, + key_data, + scen_out_p_resolv_); + if(!data) { + res = 1; + event_log_->error(ERR_FAIL_EVAL); + utils::clear_map_node_put_key_val(request_out, key_error, ERR_FAIL_EVAL); + } + } if(data) { request_out.remove_child(key_data); request_out[key_data] << *data; @@ -141,9 +213,26 @@ int request::process(const std::string &raw_host, } // auth + further_eval = false; std::optional auth; if(!res) { - auth = js_env_.eval_as(request_in, key_auth); + auth = js_env_.eval_as(request_in, + key_auth, + std::nullopt, + true, + nullptr, + PROP_EVAL_RGX, + &further_eval); + if(further_eval) { + auth = scen_p_evaluator_.eval_as(request_in, + key_auth, + scen_out_p_resolv_); + if(!auth) { + res = 1; + event_log_->error(ERR_FAIL_EVAL); + utils::clear_map_node_put_key_val(request_out, key_error, ERR_FAIL_EVAL); + } + } if(auth) { request_out.remove_child(key_auth); request_out[key_auth] << *auth; diff --git a/src/request.h b/src/request.h index 6049d64..956eab8 100644 --- a/src/request.h +++ b/src/request.h @@ -108,6 +108,12 @@ struct request { //ryml request out support buffer std::vector ryml_request_out_buf_; + //scenario property resolver + scenario_property_resolver &scen_out_p_resolv_; + + //scenario property evaluator + scenario_property_evaluator &scen_p_evaluator_; + //js environment js::js_env &js_env_; diff --git a/src/scenario.cpp b/src/scenario.cpp index 40b1ec8..8a0e520 100644 --- a/src/scenario.cpp +++ b/src/scenario.cpp @@ -5,10 +5,212 @@ #define ERR_EXEC_BEF_HNDL "failed to execute the before handler in the current scope" #define ERR_EXEC_AFT_HNDL "failed to execute the after handler in the current scope" #define ERR_CONV_NOT_SEQ "'conversations' is not a sequence" +#define ERR_NO_SUCH_CONV "no such 'conversations'" +#define ERR_REQ_NOT_SEQ "'requests' is not a sequence" +#define ERR_NO_SUCH_REQ "no such 'requests'" #define ERR_FAIL_READ_ENABLED "failed to read 'enabled'" +#define ERR_MALFORMED_YAML_PATH "malformed yaml path" +#define ERR_IDX_NOT_NUMERIC "non-numeric index" +#define ERR_IDX_OUT_OF_BOUNDS "index out bounds" +#define ERR_UNEXPECTED_TKN "unexpected token" namespace cbox { +// ---------------------------------- +// --- SCENARIO PROPERTY RESOLVER --- +// ---------------------------------- + +// .[conversation_idx][request_idx]. +const char *quick_conv_req_access_syntax_rgx = "^(?:.\\[[0-9]{1,}\\]\\[[0-9]{1,}\\].)(.*)$"; +const char *rpr_delimits = ".[]"; + +int scenario_property_resolver::init(std::shared_ptr &event_log) +{ + event_log_ = event_log; + return 0; +} + +int scenario_property_resolver::reset(ryml::ConstNodeRef scenario_obj_root) +{ + scenario_obj_root_ = scenario_obj_root; + return 0; +} + +bool is_number(const std::string &s) +{ + return !s.empty() && std::find_if(s.begin(), + s.end(), [](unsigned char c) { + return !std::isdigit(c); + }) == s.end(); +} + +std::optional scenario_property_resolver::resolve(const std::string &path) const +{ + utils::str_tok tknz(path); + ryml::ConstNodeRef from; + + if(std::regex_search(path, std::regex(quick_conv_req_access_syntax_rgx))) { + //quick access path syntax + + if(!scenario_obj_root_.has_child(key_conversations)) { + event_log_->error(ERR_NO_SUCH_CONV); + return std::nullopt; + } + + auto convs_ref = scenario_obj_root_[key_conversations]; + if(!convs_ref.is_seq()) { + event_log_->error(ERR_CONV_NOT_SEQ); + return std::nullopt; + } + + std::string tkn; + if(!tknz.next_token(tkn, rpr_delimits)) { + return std::nullopt; + } + if(!is_number(tkn)) { + event_log_->error(ERR_IDX_NOT_NUMERIC); + return std::nullopt; + } + uint32_t idx = 0; + { + std::stringstream ss; + ss << tkn; + ss >> idx; + } + + if(idx >= convs_ref.num_children()) { + event_log_->error(ERR_IDX_OUT_OF_BOUNDS); + return std::nullopt; + } + + auto conv_ref = convs_ref[idx]; + if(!conv_ref.has_child(key_requests)) { + event_log_->error(ERR_NO_SUCH_REQ); + return std::nullopt; + } + + auto reqs_ref = conv_ref[key_requests]; + if(!reqs_ref.is_seq()) { + event_log_->error(ERR_REQ_NOT_SEQ); + return std::nullopt; + } + + if(!tknz.next_token(tkn, rpr_delimits)) { + return std::nullopt; + } + if(!is_number(tkn)) { + event_log_->error(ERR_IDX_NOT_NUMERIC); + return std::nullopt; + } + { + std::stringstream ss; + ss << tkn; + ss >> idx; + } + + if(idx >= reqs_ref.num_children()) { + event_log_->error(ERR_IDX_OUT_OF_BOUNDS); + return std::nullopt; + } + + if(!tknz.next_token(tkn, rpr_delimits, true) || tkn != "]") { + event_log_->error(ERR_UNEXPECTED_TKN); + return std::nullopt; + } + from = reqs_ref[idx]; + } else { + //regular path + from = scenario_obj_root_; + } + + return resolve_common(from, tknz); +} + +std::optional scenario_property_resolver::resolve_common(ryml::ConstNodeRef from, + utils::str_tok &tknz) const +{ + std::string tkn; + bool chase_prop = false, chase_idx = false; + + bool is_delimit; + while(tknz.next_token(tkn, rpr_delimits, true, &is_delimit)) { + if(!chase_prop && !chase_idx) { + if(tkn == ".") { + chase_prop = true; + continue; + } else if(tkn == "[") { + if(!from.is_seq()) { + goto fin_null; + } + chase_idx = true; + continue; + } else { + goto fin_malformed; + } + } + if(chase_prop) { + if(is_delimit) { + goto fin_malformed; + } + if(!from.has_child(ryml::to_csubstr(tkn.c_str()))) { + goto fin_null; + } + from = from[ryml::to_csubstr(tkn.c_str())]; + chase_prop = false; + continue; + } + if(chase_idx) { + if(is_delimit) { + goto fin_malformed; + } + if(!is_number(tkn)) { + goto fin_malformed; + } + std::stringstream ss; + ss << tkn; + uint32_t idx; + ss >> idx; + if(idx >= from.num_children()) { + goto fin_null; + } + from = from[idx]; + if(!tknz.next_token(tkn, rpr_delimits, true) || tkn != "]") { + goto fin_malformed; + } + chase_idx = false; + continue; + } + } + if(chase_prop || chase_idx) { + goto fin_malformed; + } + return from; + +fin_malformed: + event_log_->error(ERR_MALFORMED_YAML_PATH); + return std::nullopt; +fin_null: + return std::nullopt; +} + +// ----------------------------------- +// --- SCENARIO PROPERTY EVALUATOR --- +// ----------------------------------- + +const char *eval_rgx = ""; + +int scenario_property_evaluator::init(std::shared_ptr &event_log) +{ + event_log_ = event_log; + return 0; +} + +int scenario_property_evaluator::reset(ryml::ConstNodeRef scenario_obj_root) +{ + scenario_obj_root_ = scenario_obj_root; + return 0; +} + // ------------------- // --- STACK SCOPE --- // ------------------- @@ -254,6 +456,14 @@ int scenario::init(std::shared_ptr &event_log) int res = 0; event_log_ = event_log; + if((res = scen_out_p_resolv_.init(event_log_))) { + return res; + } + + if((res = scen_p_evaluator_.init(event_log_))) { + return res; + } + if((res = js_env_.init(event_log_))) { return res; } @@ -280,6 +490,12 @@ int scenario::reset(ryml::Tree &doc_in, scenario_out_.rootref(), ryml_scenario_out_buf_); + // reset scenario_out resolver + scen_out_p_resolv_.reset(scenario_out_.rootref()); + + // reset scenario property evaluator + scen_p_evaluator_.reset(scenario_out_.rootref()); + int res = js_env_.reset(); return res; } @@ -290,7 +506,6 @@ int scenario::process(ryml::Tree &doc_in, int res = 0; if((res = reset(doc_in, scenario_in))) { - event_log_->error("failed to reset scenario"); return res; } diff --git a/src/scenario.h b/src/scenario.h index 374b7a9..264ae64 100644 --- a/src/scenario.h +++ b/src/scenario.h @@ -1,8 +1,86 @@ #pragma once #include "cbox.h" +#define ERR_FAIL_EVAL "property evaluation failed" + +constexpr const char *PROP_EVAL_RGX = "\\{\\{.*?\\}\\}"; + namespace cbox { +// ---------------------------------- +// --- SCENARIO PROPERTY RESOLVER --- +// ---------------------------------- + +struct scenario_property_resolver { + + scenario_property_resolver() = default; + + int init(std::shared_ptr &event_log); + int reset(ryml::ConstNodeRef scenario_obj_root); + + std::optional resolve(const std::string &path) const; + + std::optional resolve_common(ryml::ConstNodeRef from, utils::str_tok &tknz) const; + + ryml::ConstNodeRef scenario_obj_root_; + + //event logger + std::shared_ptr event_log_; +}; + +// ----------------------------------- +// --- SCENARIO PROPERTY EVALUATOR --- +// ----------------------------------- + +struct scenario_property_evaluator { + + scenario_property_evaluator() = default; + + int init(std::shared_ptr &event_log); + int reset(ryml::ConstNodeRef scenario_obj_root); + + template + std::optional eval_as(ryml::ConstNodeRef from, + const char *key, + const scenario_property_resolver &spr) { + ryml::ConstNodeRef n_val = from[ryml::to_csubstr(key)]; + std::string str_val, str_res; + n_val >> str_val; + str_res = str_val; + std::regex rgx("\\{\\{.*?\\}\\}"); + + std::regex_iterator rit(str_val.cbegin(), str_val.cend(), rgx); + std::regex_iterator rend; + + while(rit!=rend) { + auto node_ref = spr.resolve(utils::trim(utils::find_and_replace(utils::find_and_replace(rit->str(), + "{{", ""), "}}", ""))); + if(node_ref && ((*node_ref).is_keyval() || (*node_ref).is_val())) { + auto node_val = utils::converter::asType(*node_ref); + str_res = std::regex_replace(str_res,rgx,node_val,std::regex_constants::format_first_only); + } else { + return std::nullopt; + } + ++rit; + } + + if constexpr(std::is_same_v) { + return str_res; + } else { + T res; + std::stringstream ss; + ss << str_res; + ss >> res; + return res; + } + } + + ryml::ConstNodeRef scenario_obj_root_; + + //event logger + std::shared_ptr event_log_; +}; + struct scenario { // ------------------- @@ -105,6 +183,12 @@ struct scenario { //ryml scenario out support buffer std::vector ryml_scenario_out_buf_; + //scenario property resolver + scenario_property_resolver scen_out_p_resolv_; + + //scenario property evaluator + scenario_property_evaluator scen_p_evaluator_; + //scenario statistics statistics stats_; diff --git a/src/utils.cpp b/src/utils.cpp index 4c1c560..f39598b 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -356,4 +356,85 @@ void aws_auth::aws_sign_v4_build(const char *method, reqHF["Authorization"] = aws_sign_v4_build_authorization(signature); } +static const char *def_delims = " \t\n\r\f"; + +str_tok::str_tok(const std::string &str) : + current_position_(0), + max_position_((long)str.length()), + new_position_(-1), + str_(str), + delimiters_(def_delims), + ret_delims_(false), + delims_changed_(false) +{ +} + +str_tok::~str_tok() +{} + +long str_tok::skip_delimit(long start_pos) +{ + long position = start_pos; + while(!ret_delims_ && position < max_position_) { + if(delimiters_.find(str_.at(position)) == std::string::npos) { + break; + } + position++; + } + return position; +} + +long str_tok::scan_token(long start_pos, bool *is_delimit) +{ + long position = start_pos; + while(position < max_position_) { + if(delimiters_.find(str_.at(position)) != std::string::npos) { + break; + } + position++; + } + if(ret_delims_ && (start_pos == position)) { + if(delimiters_.find(str_.at(position)) != std::string::npos) { + if(is_delimit) { + *is_delimit = true; + } + position++; + } + } else if(is_delimit) { + *is_delimit = false; + } + return position; +} + +bool str_tok::next_token(std::string &out, + const char *delimiters, + bool return_delimiters, + bool *is_delimit) +{ + if(delimiters) { + delimiters_.assign(delimiters); + delims_changed_ = true; + } + ret_delims_ = return_delimiters; + current_position_ = (new_position_ >= 0 && !delims_changed_) ? + new_position_ : + skip_delimit(current_position_); + delims_changed_ = false; + new_position_ = -1; + if(current_position_ >= max_position_) { + return false; + } + long start = current_position_; + current_position_ = scan_token(current_position_, is_delimit); + out = str_.substr(start, current_position_ - start); + return true; +} + +bool str_tok::has_more_tokens(bool return_delimiters) +{ + ret_delims_ = return_delimiters; + new_position_ = skip_delimit(current_position_); + return (new_position_ < max_position_); +} + } diff --git a/src/utils.h b/src/utils.h index f9fb1b2..13f4428 100644 --- a/src/utils.h +++ b/src/utils.h @@ -210,7 +210,7 @@ inline std::string &trim(std::string &str, const std::string &chars = "\t\n\v\f\ return ltrim(rtrim(str, chars), chars); } -inline std::string &find_and_replace(std::string &str, const char *find, const char *replace) +inline std::string &find_and_replace(std::string &&str, const char *find, const char *replace) { size_t f_len = strlen(find), r_len = strlen(replace); for(std::string::size_type i = 0; (i = str.find(find, i)) != std::string::npos;) { @@ -220,6 +220,11 @@ inline std::string &find_and_replace(std::string &str, const char *find, const c return str; } +inline std::string &find_and_replace(std::string &str, const char *find, const char *replace) +{ + return find_and_replace(std::move(str), find, replace); +} + inline bool ends_with(const std::string &str, const std::string &match) { if(str.size() >= match.size() && @@ -378,4 +383,31 @@ inline void clear_map_node_put_key_val(ryml::NodeRef map_node, map_node[ryml::to_csubstr(stable_key)] << val; } +class str_tok { + public: + explicit str_tok(const std::string &str); + ~str_tok(); + + bool next_token(std::string &out, + const char *delimiters = nullptr, + bool return_delimiters = false, + bool *is_delimit = nullptr); + + bool has_more_tokens(bool return_delimiters = false); + + void reset() { + current_position_ = 0; + } + + private: + long skip_delimit(long start_pos); + long scan_token(long start_pos, bool *is_delimit); + + private: + long current_position_, max_position_, new_position_; + const std::string &str_; + std::string delimiters_; + bool ret_delims_, delims_changed_; +}; + } diff --git a/test/.astylerc b/test/.astylerc index f79b44c..28af5d4 100644 --- a/test/.astylerc +++ b/test/.astylerc @@ -15,7 +15,7 @@ --indent-switches --indent-col1-comments --min-conditional-indent=2 ---max-instatement-indent=100 ---add-brackets +--max-continuation-indent=100 +--add-braces --lineend=linux --convert-tabs diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cf1fa5b..9bfd928 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.12) project(chatterbox_test VERSION 0.0.0) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - add_compile_options(-Wall -std=c++17) + add_compile_options(-g -Wall -std=c++17) endif() add_definitions(-DV8_COMPRESS_POINTERS