Line data Source code
1 : /*
2 : ___________________________________________
3 : | _ ___ _ |
4 : | | | |__ \ | | |
5 : | | |__ ) |__ _ __ _ ___ _ __ | |_ |
6 : | | '_ \ / // _` |/ _` |/ _ \ '_ \| __| | HTTP/2 AGENT FOR MOCK TESTING
7 : | | | | |/ /| (_| | (_| | __/ | | | |_ | Version 0.0.z
8 : | |_| |_|____\__,_|\__, |\___|_| |_|\__| | https://github.com/testillano/h2agent
9 : | __/ | |
10 : | |___/ |
11 : |___________________________________________|
12 :
13 : Licensed under the MIT License <http://opensource.org/licenses/MIT>.
14 : SPDX-License-Identifier: MIT
15 : Copyright (c) 2021 Eduardo Ramos
16 :
17 : Permission is hereby granted, free of charge, to any person obtaining a copy
18 : of this software and associated documentation files (the "Software"), to deal
19 : in the Software without restriction, including without limitation the rights
20 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 : copies of the Software, and to permit persons to whom the Software is
22 : furnished to do so, subject to the following conditions:
23 :
24 : The above copyright notice and this permission notice shall be included in all
25 : copies or substantial portions of the Software.
26 :
27 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33 : SOFTWARE.
34 : */
35 :
36 :
37 : #include <ert/tracing/Logger.hpp>
38 :
39 : #include <Transformation.hpp>
40 : #include <functions.hpp>
41 :
42 : #include <sstream>
43 : #include <algorithm>
44 :
45 : namespace h2agent
46 : {
47 : namespace model
48 : {
49 :
50 : namespace {
51 :
52 0 : std::string validPrefixes(const std::vector<std::string> &prefixes) {
53 0 : std::string result;
54 0 : for (size_t i = 0; i < prefixes.size(); ++i) {
55 0 : if (i) result += ", ";
56 0 : result += prefixes[i];
57 : }
58 0 : return result;
59 0 : }
60 :
61 : const std::vector<std::string> VALID_SOURCE_PREFIXES = {
62 : "request", "response", "eraser", "math", "random", "randomset",
63 : "timestamp", "strftime", "recvseq", "sendseq",
64 : "var", "vault", "value", "serverEvent", "clientEvent",
65 : "inState", "txtFile", "binFile", "command"
66 : };
67 :
68 : const std::vector<std::string> VALID_TARGET_PREFIXES = {
69 : "response", "request", "var", "vault", "outState",
70 : "txtFile", "binFile", "udpSocket", "serverEvent", "clientEvent",
71 : "clientProvision", "break"
72 : };
73 :
74 : } // anonymous namespace
75 :
76 835 : void Transformation::collectVariablePatterns(const std::string &str, std::map<std::string, std::string> &patterns) {
77 :
78 835 : static std::regex re("@\\{[^\\{\\}]*\\}", std::regex::optimize); // @{[^{}]*} with curly braces escaped
79 : // or: R"(@\{[^\{\}]*\})"
80 :
81 835 : std::string::const_iterator it(str.cbegin());
82 835 : std::smatch matches;
83 835 : std::string pattern;
84 835 : patterns.clear();
85 848 : while (std::regex_search(it, str.cend(), matches, re)) {
86 13 : it = matches.suffix().first;
87 13 : pattern = matches[0];
88 13 : patterns[pattern] = pattern.substr(2, pattern.size()-3); // @{foo} -> foo
89 : }
90 835 : }
91 :
92 277 : bool Transformation::load(const nlohmann::json &j) {
93 :
94 277 : bool collectFilterPatterns = false;
95 :
96 : // Mandatory
97 277 : auto source_it = j.find("source");
98 277 : std::string sourceSpec = *source_it;
99 :
100 277 : auto target_it = j.find("target");
101 277 : std::string targetSpec = *target_it;
102 :
103 : // Optional
104 277 : auto it = j.find("filter");
105 277 : filter_ = "";
106 277 : has_filter_ = false;
107 277 : if (it != j.end()) {
108 71 : has_filter_ = true;
109 :
110 : // [filter_type_]
111 : // RegexCapture regular expression literal -> [filter_rgx_]
112 : // RegexReplace regular expression literal (rgx) -> [filter_rgx_] / replace format (fmt) -> [filter_]
113 : // Append suffix value -> [filter_]
114 : // Prepend prefix value -> [filter_]
115 : // Sum amount -> [filter_i_/filter_u_/filter_f_/filter_number_type_]
116 : // Multiply amount -> [filter_i_/filter_u_/filter_f_/filter_number_type_]
117 : // ConditionVar variable name -> [filter_]
118 : // EqualTo value -> [filter_]
119 : // DifferentFrom value -> [filter_]
120 : // JsonConstraint value -> [filter_object_]
121 : // SchemaId value -> [filter_]
122 : // Split size -> [filter_i_], count -> [filter_u_], sep -> [filter_], filler -> [filter_filler_], numeric -> [filter_number_type_]
123 : // BaseConvert in -> [filter_i_], out -> [filter_u_], capital -> [filter_number_type_]
124 : // Strptime fmt -> [filter_], unit -> [filter_number_type_] (0:s,1:ms,2:us,3:ns)
125 : // Strftime fmt -> [filter_], unit -> [filter_number_type_] (0:s,1:ms,2:us,3:ns)
126 : // Size (parameterless, string filter)
127 :
128 : // Parameterless string filters:
129 71 : if (it->is_string() && it->get<std::string>() == "Size") {
130 3 : filter_type_ = FilterType::Size;
131 : }
132 : else {
133 :
134 68 : auto f_it = it->find("RegexCapture");
135 :
136 : try {
137 68 : if (f_it != it->end()) {
138 6 : filter_ = *f_it;
139 6 : filter_rgx_.assign(filter_, std::regex::optimize);
140 4 : filter_type_ = FilterType::RegexCapture;
141 : }
142 62 : else if ((f_it = it->find("RegexReplace")) != it->end()) {
143 2 : filter_rgx_.assign(std::string(*(f_it->find("rgx"))), std::regex::optimize);
144 2 : filter_ = *(f_it->find("fmt"));
145 2 : filter_type_ = FilterType::RegexReplace;
146 : }
147 60 : else if ((f_it = it->find("Append")) != it->end()) {
148 2 : filter_ = *f_it;
149 2 : filter_type_ = FilterType::Append;
150 2 : collectFilterPatterns = true;
151 : }
152 58 : else if ((f_it = it->find("Prepend")) != it->end()) {
153 2 : filter_ = *f_it;
154 2 : filter_type_ = FilterType::Prepend;
155 2 : collectFilterPatterns = true;
156 : }
157 56 : else if ((f_it = it->find("Sum")) != it->end()) {
158 6 : if (f_it->is_number_unsigned()) { // first unsigned, because positive would be integer
159 4 : filter_u_ = *f_it;
160 4 : filter_number_type_ = 1 ;
161 : }
162 2 : else if (f_it->is_number_integer()) {
163 1 : filter_i_ = *f_it;
164 1 : filter_number_type_ = 0 ;
165 : }
166 1 : else if (f_it->is_number_float()) {
167 1 : filter_f_ = *f_it;
168 1 : filter_number_type_ = 2 ;
169 : }
170 6 : filter_type_ = FilterType::Sum;
171 : }
172 50 : else if ((f_it = it->find("Multiply")) != it->end()) {
173 3 : if (f_it->is_number_unsigned()) { // first unsigned, because positive would be integer
174 1 : filter_u_ = *f_it;
175 1 : filter_number_type_ = 1 ;
176 : }
177 2 : else if (f_it->is_number_integer()) {
178 1 : filter_i_ = *f_it;
179 1 : filter_number_type_ = 0 ;
180 : }
181 1 : else if (f_it->is_number_float()) {
182 1 : filter_f_ = *f_it;
183 1 : filter_number_type_ = 2 ;
184 : }
185 3 : filter_type_ = FilterType::Multiply;
186 : }
187 47 : else if ((f_it = it->find("ConditionVar")) != it->end()) {
188 6 : filter_ = *f_it;
189 6 : filter_type_ = FilterType::ConditionVar;
190 : }
191 41 : else if ((f_it = it->find("EqualTo")) != it->end()) {
192 6 : filter_ = *f_it;
193 6 : filter_type_ = FilterType::EqualTo;
194 6 : collectFilterPatterns = true;
195 : }
196 35 : else if ((f_it = it->find("DifferentFrom")) != it->end()) {
197 3 : filter_ = *f_it;
198 3 : filter_type_ = FilterType::DifferentFrom;
199 3 : collectFilterPatterns = true;
200 : }
201 32 : else if ((f_it = it->find("JsonConstraint")) != it->end()) {
202 3 : filter_object_ = *f_it;
203 3 : filter_type_ = FilterType::JsonConstraint;
204 : }
205 29 : else if ((f_it = it->find("SchemaId")) != it->end()) {
206 2 : filter_ = *f_it;
207 2 : filter_type_ = FilterType::SchemaId;
208 : }
209 27 : else if ((f_it = it->find("Split")) != it->end()) {
210 6 : auto size_it = f_it->find("size");
211 6 : auto count_it = f_it->find("count");
212 6 : auto sep_it = f_it->find("sep");
213 6 : auto filler_it = f_it->find("filler");
214 6 : auto numeric_it = f_it->find("numeric");
215 6 : filter_i_ = (size_it != f_it->end()) ? size_it->get<std::int64_t>() : 2;
216 6 : filter_u_ = (count_it != f_it->end()) ? count_it->get<std::uint64_t>() : 4;
217 11 : filter_ = (sep_it != f_it->end()) ? sep_it->get<std::string>() : ".";
218 11 : filter_filler_ = (filler_it != f_it->end()) ? filler_it->get<std::string>() : "0";
219 6 : filter_number_type_ = (numeric_it != f_it->end() && numeric_it->get<bool>()) ? 1 : 0;
220 6 : filter_type_ = FilterType::Split;
221 : }
222 21 : else if ((f_it = it->find("BaseConvert")) != it->end()) {
223 6 : filter_i_ = f_it->find("in")->get<std::int64_t>();
224 6 : filter_u_ = f_it->find("out")->get<std::uint64_t>();
225 6 : auto capital_it = f_it->find("capital");
226 6 : filter_number_type_ = (capital_it != f_it->end() && capital_it->get<bool>()) ? 1 : 0;
227 6 : filter_type_ = FilterType::BaseConvert;
228 : }
229 15 : else if ((f_it = it->find("Strptime")) != it->end()) {
230 4 : filter_ = f_it->find("fmt")->get<std::string>();
231 7 : std::string unit = (f_it->find("unit") != f_it->end()) ? f_it->find("unit")->get<std::string>() : "s";
232 4 : filter_number_type_ = (unit == "ms") ? 1 : (unit == "us") ? 2 : (unit == "ns") ? 3 : 0;
233 4 : filter_type_ = FilterType::FStrptime;
234 4 : }
235 11 : else if ((f_it = it->find("Strftime")) != it->end()) {
236 4 : filter_ = f_it->find("fmt")->get<std::string>();
237 7 : std::string unit = (f_it->find("unit") != f_it->end()) ? f_it->find("unit")->get<std::string>() : "s";
238 4 : filter_number_type_ = (unit == "ms") ? 1 : (unit == "us") ? 2 : (unit == "ns") ? 3 : 0;
239 4 : filter_type_ = FilterType::FStrftime;
240 4 : }
241 7 : else if ((f_it = it->find("RegexKey")) != it->end()) {
242 7 : filter_ = *f_it;
243 7 : filter_rgx_.assign(filter_, std::regex::optimize);
244 7 : filter_type_ = FilterType::RegexKey;
245 : }
246 : }
247 2 : catch (std::regex_error &e) {
248 2 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
249 2 : return false;
250 2 : }
251 : } // else (object filters)
252 : }
253 :
254 : // Interpret source/target:
255 :
256 : // SOURCE (enum SourceType { RequestUri = 0, RequestUriPath, RequestUriParam, RequestBody, ResponseBody, RequestHeader, Eraser,
257 : // Math, Random, RandomSet, Timestamp, Strftime, Recvseq, SVar, SGVar, Value, ServerEvent, InState };)
258 275 : source_ = ""; // empty by default (-), as many cases are only work modes and no parameters(+) are included in their transformation configuration
259 :
260 : // Source specifications:
261 : // - request.uri: whole `url-decoded` request *URI* (path together with possible query parameters). This is the unmodified original *URI*, not necessarily the same as the classification *URI*.
262 : // - request.uri.path: `url-decoded` request *URI* path part.
263 : // + request.uri.param.<name>: request URI specific parameter `<name>`.
264 : // - request.body: request body document.
265 : // + request.body./<node1>/../<nodeN>: request body node path.
266 : // + request.header.<hname>: request header component (i.e. *content-type*).
267 : // - eraser: this is used to indicate that the *target* specified (next section) must be removed or reset.
268 : // + math.`<expression>`: this source is based in Arash Partow's exprtk math library compilation.
269 : // + random.<min>.<max>: integer number in range `[min, max]`. Negatives allowed, i.e.: `"-3.+4"`.
270 : // + timestamp.<unit>: UNIX epoch time in `s` (seconds), `ms` (milliseconds), `us` (microseconds) or `ns` (nanoseconds).
271 : // + strftime.<format>: current date/time formatted by [strftime](https://www.cplusplus.com/reference/ctime/strftime/).
272 : // - recvseq: sequence id increased for every mock reception (starts on *1* when the *h2agent* is started).
273 : // + var.<id>: general purpose variable.
274 : // + vault.<id>: general purpose vault entry.
275 : // - value.<value>: free string value. Even convertible types are allowed, for example: integer string, unsigned integer string, float number string, boolean string (true if non-empty string), will be converted to the target type.
276 : // - inState: current processing state.
277 : // + serverEvent.`<server event address in query parameters format>`: access server context indexed by request *method* (`requestMethod`), *URI* (`requestUri`), events *number* (`eventNumber`) and events number *path* (`eventPath`).
278 : // + txtFile.`<path>`: reads text content from file with the path provided.
279 : // + binFile.`<path>`: reads binary content from file with the path provided.
280 : // + command.`<command>`: executes command on process shell and captures the standard output.
281 :
282 : // Regex needed:
283 275 : static std::regex requestUriParam("^request.uri.param.(.*)", std::regex::optimize); // no need to escape dots as this is validated in schema
284 275 : static std::regex requestBodyNode("^request.body.(.*)", std::regex::optimize);
285 275 : static std::regex responseBodyNode("^response.body.(.*)", std::regex::optimize);
286 275 : static std::regex requestHeader("^request.header.(.*)", std::regex::optimize);
287 275 : static std::regex math("^math.(.*)", std::regex::optimize);
288 275 : static std::regex random("^random\\.([-+]{0,1}[0-9]+)\\.([-+]{0,1}[0-9]+)$", std::regex::optimize); // no need to validate min/max as it was done at schema
289 275 : static std::regex randomSet("^randomset.(.*)", std::regex::optimize);
290 275 : static std::regex timestamp("^timestamp.(.*)", std::regex::optimize); // no need to validate s/ms/us/ns as it was done at schema
291 275 : static std::regex strftime("^strftime.(.*)", std::regex::optimize); // free format, errors captured
292 275 : static std::regex varId("^var.(.*)", std::regex::optimize);
293 275 : static std::regex gvarJsonObject("^vault\\.([^.]+)\\.json\\.object(?:\\.(/.+))?$", std::regex::optimize);
294 275 : static std::regex gvarJsonJsonString("^vault\\.([^.]+)\\.json\\.jsonstring(?:\\.(/.+))?$", std::regex::optimize);
295 275 : static std::regex gvarId("^vault\\.([^.]+)(?:\\.(/.+))?$", std::regex::optimize);
296 275 : static std::regex value("^value.([.\\s\\S]*)", std::regex::optimize); // added support for special characters: \n \t \r
297 275 : static std::regex serverEvent("^serverEvent.(.*)", std::regex::optimize);
298 275 : static std::regex clientEvent("^clientEvent.(.*)", std::regex::optimize);
299 275 : static std::regex responseHeader_source("^response.header.(.*)", std::regex::optimize);
300 275 : static std::regex txtFile("^txtFile.(.*)", std::regex::optimize);
301 275 : static std::regex binFile("^binFile.(.*)", std::regex::optimize);
302 275 : static std::regex command("^command.(.*)", std::regex::optimize);
303 :
304 275 : std::smatch matches; // to capture regex group(s)
305 : // BE CAREFUL!: https://stackoverflow.com/a/51709911/2576671
306 : // In this case, it is not a problem, as we store the match from sourceSpec or targetSpec before changing them.
307 :
308 : // no need to try (controlled regex)
309 : //try {
310 275 : if (sourceSpec == "request.uri") {
311 5 : source_type_ = SourceType::RequestUri;
312 : }
313 270 : else if (sourceSpec == "request.uri.path") {
314 3 : source_type_ = SourceType::RequestUriPath;
315 : }
316 267 : else if (std::regex_match(sourceSpec, matches, requestUriParam)) { // parameter name
317 2 : source_ = matches.str(1);
318 2 : source_type_ = SourceType::RequestUriParam;
319 : }
320 265 : else if (sourceSpec == "request.body") { // whole document
321 16 : source_type_ = SourceType::RequestBody;
322 : }
323 249 : else if (std::regex_match(sourceSpec, matches, requestBodyNode)) { // nlohmann::json_pointer path
324 7 : source_ = matches.str(1);
325 7 : source_type_ = SourceType::RequestBody;
326 : }
327 242 : else if (sourceSpec == "response.body") { // whole document
328 2 : source_type_ = SourceType::ResponseBody;
329 : }
330 240 : else if (std::regex_match(sourceSpec, matches, responseBodyNode)) { // nlohmann::json_pointer path
331 2 : source_ = matches.str(1);
332 2 : source_type_ = SourceType::ResponseBody;
333 : }
334 : // IMPORTANT: literal "request.headers"/"response.headers" must precede the regex "request.header.(.*)"
335 : // because the dot in the regex also matches 's', causing "request.headers" to be misclassified as RequestHeader.
336 238 : else if (sourceSpec == "request.headers") {
337 2 : source_type_ = SourceType::RequestHeaders;
338 : }
339 236 : else if (sourceSpec == "response.headers") {
340 1 : source_type_ = SourceType::ResponseHeaders;
341 : }
342 235 : else if (std::regex_match(sourceSpec, matches, requestHeader)) { // header name
343 2 : source_ = matches.str(1);
344 2 : source_type_ = SourceType::RequestHeader;
345 : }
346 233 : else if (sourceSpec == "eraser") {
347 13 : source_type_ = SourceType::Eraser;
348 : }
349 220 : else if (std::regex_match(sourceSpec, matches, math)) { // math expression, i.e. "2*sqrt(2)"
350 4 : source_ = matches.str(1);
351 4 : source_type_ = SourceType::Math;
352 : }
353 216 : else if (std::regex_match(sourceSpec, matches, random)) { // range "<min>.<max>", i.e.: "-3.8", "0.100", "-15.+2", etc. These go to -> [source_i1_] and [source_i2_]
354 8 : source_i1_ = stoi(matches.str(1));
355 8 : source_i2_ = stoi(matches.str(2));
356 8 : source_type_ = SourceType::Random;
357 : }
358 208 : else if (std::regex_match(sourceSpec, matches, randomSet)) { // random set given by tokenized pipe-separated list of values
359 1 : source_ = matches.str(1) + "|"; // add pipe, just to allow getting empty part after trailing pipe ("foo|" => 'foo' ''), because the algorithm below ignores latest pipe and its trailing token.
360 1 : static std::regex pipedRgx(R"(\|)", std::regex::optimize);
361 2 : source_tokenized_ = std::vector<std::string>(
362 4 : std::sregex_token_iterator{begin(source_), end(source_), pipedRgx, -1},
363 2 : std::sregex_token_iterator{}
364 1 : );
365 1 : source_type_ = SourceType::RandomSet;
366 1 : source_.pop_back(); // remove added pipe
367 : }
368 207 : else if (std::regex_match(sourceSpec, matches, timestamp)) { // unit (s: seconds, ms: milliseconds, us: microseconds, ns: nanoseconds)
369 5 : source_ = matches.str(1);
370 5 : source_type_ = SourceType::Timestamp;
371 : }
372 202 : else if (std::regex_match(sourceSpec, matches, strftime)) { // current date/time formatted by as described in https://www.cplusplus.com/reference/ctime/strftime/
373 1 : source_ = matches.str(1);
374 1 : source_type_ = SourceType::Strftime;
375 : }
376 201 : else if (sourceSpec == "recvseq") {
377 2 : source_type_ = SourceType::Recvseq;
378 : }
379 199 : else if (sourceSpec == "sendseq") {
380 0 : source_type_ = SourceType::Sendseq;
381 : }
382 199 : else if (std::regex_match(sourceSpec, matches, varId)) { // variable id
383 30 : source_ = matches.str(1);
384 30 : source_type_ = SourceType::SVar;
385 : }
386 169 : else if (std::regex_match(sourceSpec, matches, gvarId)) { // vault entry id
387 34 : source_ = matches.str(1); // key name
388 34 : if (matches[2].matched) source2_ = matches.str(2); // optional json path
389 34 : source_type_ = SourceType::SGVar;
390 : }
391 135 : else if (std::regex_match(sourceSpec, matches, value)) { // value content
392 111 : source_ = matches.str(1);
393 111 : source_type_ = SourceType::Value;
394 : }
395 24 : else if (std::regex_match(sourceSpec, matches, serverEvent)) { // value content
396 5 : source_ = matches.str(1); // i.e. requestMethod=GET&requestUri=/app/v1/foo/bar%3Fid%3D5%26name%3Dtest&eventNumber=3&eventPath=/requestBody
397 5 : source_type_ = SourceType::ServerEvent;
398 5 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(source_);
399 5 : std::map<std::string, std::string>::const_iterator it;
400 30 : for (auto const & qp: {
401 : "requestMethod", "requestUri", "eventNumber", "eventPath", "recvseq"
402 35 : }) { // tokenized vector order
403 25 : it = qmap.find(qp);
404 49 : source_tokenized_.push_back((it != qmap.end()) ? it->second:"");
405 : }
406 5 : }
407 19 : else if (std::regex_match(sourceSpec, matches, clientEvent)) { // client event data
408 4 : source_ = matches.str(1); // i.e. clientEndpointId=myEndpoint&requestMethod=GET&requestUri=/app/v1/foo/bar&eventNumber=3&eventPath=/responseBody
409 4 : source_type_ = SourceType::ClientEvent;
410 4 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(source_);
411 4 : std::map<std::string, std::string>::const_iterator it;
412 28 : for (auto const & qp: {
413 : "clientEndpointId", "requestMethod", "requestUri", "eventNumber", "eventPath", "sendseq"
414 32 : }) { // tokenized vector order
415 24 : it = qmap.find(qp);
416 40 : source_tokenized_.push_back((it != qmap.end()) ? it->second:"");
417 : }
418 4 : }
419 15 : else if (sourceSpec == "response.statusCode") {
420 2 : source_type_ = SourceType::ResponseStatusCode;
421 : }
422 13 : else if (std::regex_match(sourceSpec, matches, responseHeader_source)) { // response header name
423 1 : source_ = matches.str(1);
424 1 : source_type_ = SourceType::ResponseHeader;
425 : }
426 12 : else if (sourceSpec == "inState") {
427 4 : source_type_ = SourceType::InState;
428 : }
429 8 : else if (std::regex_match(sourceSpec, matches, txtFile)) { // path file
430 3 : source_ = matches.str(1);
431 3 : source_type_ = SourceType::STxtFile;
432 : }
433 5 : else if (std::regex_match(sourceSpec, matches, binFile)) { // path file
434 2 : source_ = matches.str(1);
435 2 : source_type_ = SourceType::SBinFile;
436 : }
437 3 : else if (std::regex_match(sourceSpec, matches, command)) { // command string
438 3 : source_ = matches.str(1);
439 3 : source_type_ = SourceType::Command;
440 : }
441 : else {
442 0 : std::string hint = validPrefixes(VALID_SOURCE_PREFIXES);
443 0 : ert::tracing::Logger::error(ert::tracing::Logger::asString("Cannot identify source type for: '%s'. Valid prefixes: %s", sourceSpec.c_str(), hint.c_str()), ERT_FILE_LOCATION);
444 0 : return false;
445 0 : }
446 :
447 : // TARGET (enum TargetType { ResponseBodyString = 0, ..., ResponseHeader_t, ResponseStatusCode_t, ..., RequestHeader, RequestDelayMs, RequestTimeoutMs, ClientEventToPurge };)
448 275 : target_ = ""; // empty by default (-), as many cases are only work modes and no parameters(+) are included in their transformation configuration
449 275 : target2_ = ""; // same
450 :
451 : // Target specifications:
452 : // SERVER MODE
453 : // - response.body.string *[string]*: response body storing expected string processed.
454 : // - response.body.hexstring *[string]*: response body storing expected string processed from hexadecimal representation, for example `0x8001` (prefix `0x` is optional).
455 : // - response.body.json.string *[string]*: response body document storing expected string.
456 : // - response.body.json.integer *[number]*: response body document storing expected integer.
457 : // - response.body.json.unsigned *[unsigned number]*: response body document storing expected unsigned integer.
458 : // - response.body.json.float *[float number]*: response body document storing expected float number.
459 : // - response.body.json.boolean *[boolean]*: response body document storing expected booolean.
460 : // - response.body.json.object *[json object]*: response body document storing expected object.
461 : // - response.body.json.jsonstring *[json string]*: response body document storing expected object, extracted from json-parsed string, as root node.
462 : // + response.body.json.string./<n1>/../<nN> *[string]*: response body node path storing expected string.
463 : // + response.body.json.integer./<n1>/../<nN> *[number]*: response body node path storing expected integer.
464 : // + response.body.json.unsigned./<n1>/../<nN> *[unsigned number]*: response body node path storing expected unsigned integer.
465 : // + response.body.json.float./<n1>/../<nN> *[float number]*: response body node path storing expected float number.
466 : // + response.body.json.boolean./<n1>/../<nN> *[boolean]*: response body node path storing expected booblean.
467 : // + response.body.json.object./<n1>/../<nN> *[json object]*: response body node path storing expected object.
468 : // + response.body.json.jsonstring./<n1>/../<nN> *[json string]*: response body node path storing expected object, extracted from json-parsed string, under provided path.
469 : // + response.header.<hname> *[string (or number as string)]*: response header component (i.e. *location*).
470 : // - response.statusCode *[unsigned integer]*: response status code.
471 : // - response.delayMs *[unsigned integer]*: simulated delay to respond.
472 : // + var.<id> *[string (or number as string)]*: general purpose variable.
473 : // + vault.<id> *[string (or number as string)]*: general purpose vault entry.
474 : // - outState *[string (or number as string)]*: next processing state. This overrides the default provisioned one.
475 : // + outState.`[POST|GET|PUT|DELETE|HEAD][.<uri>]` *[string (or number as string)]*: next processing state for specific method (virtual server data will be created if needed: this way we could modify the flow for other methods different than the one which is managing the current provision). This target **admits variables substitution** in the `uri` part.
476 : // + txtFile.`<path>` *[string]*: dumps source (as string) over text file with the path provided.
477 : // + binFile.`<path>` *[string]*: dumps source (as string) over binary file with the path provided.
478 : // + udpSocket.`<path>[|<milliseconds delay>]` *[string]*: sends source (as string) towards the UDP unix socket with the path provided.
479 : // + serverEvent.`<server event address in query parameters format>`: this target is always used in conjunction with `eraser`.
480 : // - break *[string]*: when non-empty string is transferred, the transformations list is interrupted. Empty string (or undefined source) ignores the action.
481 : //
482 : // CLIENT MODE ADDITIONAL TARGET TYPES:
483 : // - request.body.string *[string]*: request body storing expected string processed.
484 : // - request.body.hexstring *[string]*: request body storing expected string processed from hexadecimal representation, for example `0x8001` (prefix `0x` is optional).
485 : // - request.body.json.string *[string]*: request body document storing expected string.
486 : // - request.body.json.integer *[number]*: request body document storing expected integer.
487 : // - request.body.json.unsigned *[unsigned number]*: request body document storing expected unsigned integer.
488 : // - request.body.json.float *[float number]*: request body document storing expected float number.
489 : // - request.body.json.boolean *[boolean]*: request body document storing expected booolean.
490 : // - request.body.json.object *[json object]*: request body document storing expected object.
491 : // - request.body.json.jsonstring *[json string]*: request body document storing expected object, extracted from json-parsed string, as root node.
492 : // + request.body.json.string./<n1>/../<nN> *[string]*: request body node path storing expected string.
493 : // + request.body.json.integer./<n1>/../<nN> *[number]*: request body node path storing expected integer.
494 : // + request.body.json.unsigned./<n1>/../<nN> *[unsigned number]*: request body node path storing expected unsigned integer.
495 : // + request.body.json.float./<n1>/../<nN> *[float number]*: request body node path storing expected float number.
496 : // + request.body.json.boolean./<n1>/../<nN> *[boolean]*: request body node path storing expected booblean.
497 : // + request.body.json.object./<n1>/../<nN> *[json object]*: request body node path storing expected object.
498 : // + request.body.json.jsonstring./<n1>/../<nN> *[json string]*: request body node path storing expected object, extracted from json-parsed string, under provided path.
499 :
500 : // Regex needed:
501 : // SERVER MODE:
502 275 : static std::regex responseBodyJson_StringNode("^response.body.json.string.(.*)", std::regex::optimize);
503 275 : static std::regex responseBodyJson_IntegerNode("^response.body.json.integer.(.*)", std::regex::optimize);
504 275 : static std::regex responseBodyJson_UnsignedNode("^response.body.json.unsigned.(.*)", std::regex::optimize);
505 275 : static std::regex responseBodyJson_FloatNode("^response.body.json.float.(.*)", std::regex::optimize);
506 275 : static std::regex responseBodyJson_BooleanNode("^response.body.json.boolean.(.*)", std::regex::optimize);
507 275 : static std::regex responseBodyJson_ObjectNode("^response.body.json.object.(.*)", std::regex::optimize);
508 275 : static std::regex responseBodyJson_JsonStringNode("^response.body.json.jsonstring.(.*)", std::regex::optimize);
509 275 : static std::regex responseHeader("^response.header.(.*)", std::regex::optimize);
510 275 : static std::regex outStateMethodUri("^outState.(POST|GET|PUT|DELETE|HEAD)(\\..+)?", std::regex::optimize);
511 :
512 : // CLIENT MODE:
513 275 : static std::regex requestBodyJson_StringNode("^request.body.json.string.(.*)", std::regex::optimize);
514 275 : static std::regex requestBodyJson_IntegerNode("^request.body.json.integer.(.*)", std::regex::optimize);
515 275 : static std::regex requestBodyJson_UnsignedNode("^request.body.json.unsigned.(.*)", std::regex::optimize);
516 275 : static std::regex requestBodyJson_FloatNode("^request.body.json.float.(.*)", std::regex::optimize);
517 275 : static std::regex requestBodyJson_BooleanNode("^request.body.json.boolean.(.*)", std::regex::optimize);
518 275 : static std::regex requestBodyJson_ObjectNode("^request.body.json.object.(.*)", std::regex::optimize);
519 275 : static std::regex requestBodyJson_JsonStringNode("^request.body.json.jsonstring.(.*)", std::regex::optimize);
520 :
521 : // Only target
522 275 : static std::regex requestHeader_target("^request.header.(.*)", std::regex::optimize);
523 275 : static std::regex udpSocket("^udpSocket.(.*)", std::regex::optimize);
524 275 : static std::regex clientProvision("^clientProvision\\.([^.]+)(?:\\.(.+))?$", std::regex::optimize);
525 :
526 : // no need to try (controlled regex)
527 : //try {
528 : // SERVER_MODE
529 275 : if (targetSpec == "response.body.string") {
530 94 : target_type_ = TargetType::ResponseBodyString;
531 : }
532 181 : else if (targetSpec == "response.body.hexstring") {
533 1 : target_type_ = TargetType::ResponseBodyHexString;
534 : }
535 180 : else if (targetSpec == "response.body.json.string") { // whole document
536 2 : target_type_ = TargetType::ResponseBodyJson_String;
537 : }
538 178 : else if (std::regex_match(targetSpec, matches, responseBodyJson_StringNode)) { // nlohmann::json_pointer path
539 32 : target_ = matches.str(1);
540 32 : target_type_ = TargetType::ResponseBodyJson_String;
541 : }
542 146 : else if (targetSpec == "response.body.json.integer") { // whole document
543 1 : target_type_ = TargetType::ResponseBodyJson_Integer;
544 : }
545 145 : else if (std::regex_match(targetSpec, matches, responseBodyJson_IntegerNode)) { // nlohmann::json_pointer path
546 11 : target_ = matches.str(1);
547 11 : target_type_ = TargetType::ResponseBodyJson_Integer;
548 : }
549 134 : else if (targetSpec == "response.body.json.unsigned") { // whole document
550 1 : target_type_ = TargetType::ResponseBodyJson_Unsigned;
551 : }
552 133 : else if (std::regex_match(targetSpec, matches, responseBodyJson_UnsignedNode)) { // nlohmann::json_pointer path
553 4 : target_ = matches.str(1);
554 4 : target_type_ = TargetType::ResponseBodyJson_Unsigned;
555 : }
556 129 : else if (targetSpec == "response.body.json.float") { // whole document
557 1 : target_type_ = TargetType::ResponseBodyJson_Float;
558 : }
559 128 : else if (std::regex_match(targetSpec, matches, responseBodyJson_FloatNode)) { // nlohmann::json_pointer path
560 1 : target_ = matches.str(1);
561 1 : target_type_ = TargetType::ResponseBodyJson_Float;
562 : }
563 127 : else if (targetSpec == "response.body.json.boolean") { // whole document
564 1 : target_type_ = TargetType::ResponseBodyJson_Boolean;
565 : }
566 126 : else if (std::regex_match(targetSpec, matches, responseBodyJson_BooleanNode)) { // nlohmann::json_pointer path
567 1 : target_ = matches.str(1);
568 1 : target_type_ = TargetType::ResponseBodyJson_Boolean;
569 : }
570 125 : else if (targetSpec == "response.body.json.object") { // whole document
571 7 : target_type_ = TargetType::ResponseBodyJson_Object;
572 : }
573 118 : else if (std::regex_match(targetSpec, matches, responseBodyJson_ObjectNode)) { // nlohmann::json_pointer path
574 12 : target_ = matches.str(1);
575 12 : target_type_ = TargetType::ResponseBodyJson_Object;
576 : }
577 106 : else if (targetSpec == "response.body.json.jsonstring") { // whole document
578 2 : target_type_ = TargetType::ResponseBodyJson_JsonString;
579 : }
580 104 : else if (std::regex_match(targetSpec, matches, responseBodyJson_JsonStringNode)) { // nlohmann::json_pointer path
581 1 : target_ = matches.str(1);
582 1 : target_type_ = TargetType::ResponseBodyJson_JsonString;
583 : }
584 : // CLIENT MODE
585 103 : else if (targetSpec == "request.body.string") {
586 0 : target_type_ = TargetType::RequestBodyString;
587 : }
588 103 : else if (targetSpec == "request.body.hexstring") {
589 0 : target_type_ = TargetType::RequestBodyHexString;
590 : }
591 103 : else if (targetSpec == "request.body.json.string") { // whole document
592 0 : target_type_ = TargetType::RequestBodyJson_String;
593 : }
594 103 : else if (std::regex_match(targetSpec, matches, requestBodyJson_StringNode)) { // nlohmann::json_pointer path
595 1 : target_ = matches.str(1);
596 1 : target_type_ = TargetType::RequestBodyJson_String;
597 : }
598 102 : else if (targetSpec == "request.body.json.integer") { // whole document
599 0 : target_type_ = TargetType::RequestBodyJson_Integer;
600 : }
601 102 : else if (std::regex_match(targetSpec, matches, requestBodyJson_IntegerNode)) { // nlohmann::json_pointer path
602 1 : target_ = matches.str(1);
603 1 : target_type_ = TargetType::RequestBodyJson_Integer;
604 : }
605 101 : else if (targetSpec == "request.body.json.unsigned") { // whole document
606 0 : target_type_ = TargetType::RequestBodyJson_Unsigned;
607 : }
608 101 : else if (std::regex_match(targetSpec, matches, requestBodyJson_UnsignedNode)) { // nlohmann::json_pointer path
609 0 : target_ = matches.str(1);
610 0 : target_type_ = TargetType::RequestBodyJson_Unsigned;
611 : }
612 101 : else if (targetSpec == "request.body.json.float") { // whole document
613 0 : target_type_ = TargetType::RequestBodyJson_Float;
614 : }
615 101 : else if (std::regex_match(targetSpec, matches, requestBodyJson_FloatNode)) { // nlohmann::json_pointer path
616 0 : target_ = matches.str(1);
617 0 : target_type_ = TargetType::RequestBodyJson_Float;
618 : }
619 101 : else if (targetSpec == "request.body.json.boolean") { // whole document
620 0 : target_type_ = TargetType::RequestBodyJson_Boolean;
621 : }
622 101 : else if (std::regex_match(targetSpec, matches, requestBodyJson_BooleanNode)) { // nlohmann::json_pointer path
623 0 : target_ = matches.str(1);
624 0 : target_type_ = TargetType::RequestBodyJson_Boolean;
625 : }
626 101 : else if (targetSpec == "request.body.json.object") { // whole document
627 0 : target_type_ = TargetType::RequestBodyJson_Object;
628 : }
629 101 : else if (std::regex_match(targetSpec, matches, requestBodyJson_ObjectNode)) { // nlohmann::json_pointer path
630 0 : target_ = matches.str(1);
631 0 : target_type_ = TargetType::RequestBodyJson_Object;
632 : }
633 101 : else if (targetSpec == "request.body.json.jsonstring") { // whole document
634 0 : target_type_ = TargetType::RequestBodyJson_JsonString;
635 : }
636 101 : else if (std::regex_match(targetSpec, matches, requestBodyJson_JsonStringNode)) { // nlohmann::json_pointer path
637 0 : target_ = matches.str(1);
638 0 : target_type_ = TargetType::RequestBodyJson_JsonString;
639 : }
640 :
641 : // CLIENT MODE ADDITIONAL TARGET TYPES:
642 101 : else if (std::regex_match(targetSpec, matches, requestHeader_target)) { // request header name
643 2 : target_ = matches.str(1);
644 2 : target_type_ = TargetType::RequestHeader_t;
645 : }
646 99 : else if (targetSpec == "request.delayMs") {
647 4 : target_type_ = TargetType::RequestDelayMs;
648 : }
649 95 : else if (targetSpec == "request.timeoutMs") {
650 1 : target_type_ = TargetType::RequestTimeoutMs;
651 : }
652 94 : else if (targetSpec == "request.uri") {
653 11 : target_type_ = TargetType::RequestUri_t;
654 : }
655 83 : else if (targetSpec == "request.method") {
656 1 : target_type_ = TargetType::RequestMethod_t;
657 : }
658 :
659 82 : else if (std::regex_match(targetSpec, matches, responseHeader)) { // header name
660 1 : target_ = matches.str(1);
661 1 : target_type_ = TargetType::ResponseHeader_t;
662 : }
663 81 : else if (targetSpec == "response.statusCode") {
664 6 : target_type_ = TargetType::ResponseStatusCode_t;
665 : }
666 75 : else if (targetSpec == "response.delayMs") {
667 1 : target_type_ = TargetType::ResponseDelayMs;
668 : }
669 74 : else if (std::regex_match(targetSpec, matches, varId)) { // variable id
670 21 : target_ = matches.str(1);
671 21 : target_type_ = TargetType::TVar;
672 : }
673 53 : else if (std::regex_match(targetSpec, matches, gvarJsonObject)) { // vault entry json object
674 3 : target_ = matches.str(1); // key name
675 3 : if (matches[2].matched) target2_ = matches.str(2); // optional json path
676 3 : target_type_ = TargetType::TGVarJson_Object;
677 : }
678 50 : else if (std::regex_match(targetSpec, matches, gvarJsonJsonString)) { // vault entry json-parsed string
679 4 : target_ = matches.str(1); // key name
680 4 : if (matches[2].matched) target2_ = matches.str(2); // optional json path
681 4 : target_type_ = TargetType::TGVarJson_JsonString;
682 : }
683 46 : else if (std::regex_match(targetSpec, matches, gvarId)) { // vault entry id
684 18 : target_ = matches.str(1); // key name
685 18 : if (matches[2].matched) target2_ = matches.str(2); // optional json path
686 18 : target_type_ = TargetType::TGVar;
687 : }
688 28 : else if (targetSpec == "outState") {
689 6 : target_type_ = TargetType::OutState;
690 : }
691 22 : else if (std::regex_match(targetSpec, matches, outStateMethodUri)) { // method
692 2 : target_ = matches.str(1); // <method>
693 2 : target2_ = matches.str(2); // .<uri>
694 2 : if (!target2_.empty()) {
695 2 : target2_ = target2_.substr(1); // remove the initial dot to store the uri
696 : }
697 2 : target_type_ = TargetType::OutState;
698 : }
699 20 : else if (std::regex_match(targetSpec, matches, txtFile)) { // path file
700 3 : target_ = matches.str(1);
701 3 : target_type_ = TargetType::TTxtFile;
702 : }
703 17 : else if (std::regex_match(targetSpec, matches, binFile)) { // path file
704 2 : target_ = matches.str(1);
705 2 : target_type_ = TargetType::TBinFile;
706 : }
707 15 : else if (std::regex_match(targetSpec, matches, udpSocket)) { // path file
708 1 : target_ = matches.str(1);
709 1 : target_type_ = TargetType::UDPSocket;
710 : }
711 14 : else if (std::regex_match(targetSpec, matches, serverEvent)) { // value content
712 4 : target_ = matches.str(1); // i.e. requestMethod=GET&requestUri=/app/v1/foo/bar%3Fid%3D5%26name%3Dtest&eventNumber=3
713 4 : target_type_ = TargetType::ServerEventToPurge;
714 4 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(target_);
715 4 : std::map<std::string, std::string>::const_iterator it;
716 20 : for (auto const & qp: {
717 : "requestMethod", "requestUri", "eventNumber", "recvseq"
718 24 : }) { // tokenized vector order
719 16 : it = qmap.find(qp);
720 32 : target_tokenized_.push_back((it != qmap.end()) ? it->second:"");
721 : }
722 4 : }
723 10 : else if (std::regex_match(targetSpec, matches, clientEvent)) { // client event purge
724 1 : target_ = matches.str(1); // i.e. clientEndpointId=myEndpoint&requestMethod=GET&requestUri=/app/v1/foo/bar&eventNumber=3
725 1 : target_type_ = TargetType::ClientEventToPurge;
726 1 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(target_);
727 1 : std::map<std::string, std::string>::const_iterator it;
728 6 : for (auto const & qp: {
729 : "clientEndpointId", "requestMethod", "requestUri", "eventNumber", "sendseq"
730 7 : }) { // tokenized vector order
731 5 : it = qmap.find(qp);
732 15 : target_tokenized_.push_back((it != qmap.end()) ? it->second:"");
733 : }
734 1 : }
735 9 : else if (targetSpec == "break") {
736 4 : target_type_ = TargetType::Break;
737 : }
738 5 : else if (std::regex_match(targetSpec, matches, clientProvision)) { // clientProvision.<id>.<inState>
739 5 : target_ = matches.str(1);
740 5 : target2_ = matches.str(2);
741 5 : if (target2_.empty()) {
742 1 : ert::tracing::Logger::error(ert::tracing::Logger::asString("Missing inState in clientProvision target: '%s'. Use 'clientProvision.<id>.<inState>'", targetSpec.c_str()), ERT_FILE_LOCATION);
743 1 : return false;
744 : }
745 4 : target_type_ = TargetType::ClientProvision_t;
746 : }
747 : else {
748 0 : std::string hint = validPrefixes(VALID_TARGET_PREFIXES);
749 0 : ert::tracing::Logger::error(ert::tracing::Logger::asString("Cannot identify target type for: '%s'. Valid prefixes: %s", targetSpec.c_str(), hint.c_str()), ERT_FILE_LOCATION);
750 0 : return false;
751 0 : }
752 :
753 : //LOGDEBUG(ert::tracing::Logger::debug(asString(), ERT_FILE_LOCATION));
754 :
755 : // Variable patterns:
756 274 : collectVariablePatterns(source_, source_patterns_);
757 274 : if (collectFilterPatterns) collectVariablePatterns(filter_, filter_patterns_); // protected to avoid possible gathering of false patterns (i.e. complex regexp's)
758 274 : collectVariablePatterns(target_, target_patterns_);
759 274 : collectVariablePatterns(target2_, target2_patterns_);
760 :
761 : // onFilterFail:
762 274 : auto off_it = j.find("onFilterFail");
763 274 : if (off_it != j.end() && off_it->is_array()) {
764 10 : for (const auto &item : *off_it) {
765 5 : auto t = std::make_shared<Transformation>();
766 5 : if (!t->load(item)) return false;
767 5 : on_filter_fail_.push_back(std::move(t));
768 5 : }
769 : }
770 :
771 274 : return true;
772 277 : }
773 :
774 12 : std::string Transformation::asString() const {
775 :
776 12 : std::stringstream ss;
777 :
778 :
779 : // SOURCE
780 12 : ss << "SourceType: " << SourceTypeAsText(source_type_);
781 12 : if (source_type_ != SourceType::RequestUri && source_type_ != SourceType::RequestUriPath && source_type_ != SourceType::Eraser && source_type_ != SourceType::Recvseq && source_type_ != SourceType::Sendseq && source_type_ != SourceType::InState && source_type_ != SourceType::ResponseStatusCode) {
782 10 : ss << " | source_: " << source_;
783 :
784 10 : if (source_type_ == SourceType::RequestBody || source_type_ == SourceType::ResponseBody) {
785 1 : ss << " (empty: whole, path: node)";
786 : }
787 9 : else if (source_type_ == SourceType::Random) {
788 1 : ss << " | source_i1_: " << source_i1_ << " (Random min)" << " | source_i2_: " << source_i2_ << " (Random max)";
789 : }
790 8 : else if (source_type_ == SourceType::RandomSet || source_type_ == SourceType::ServerEvent || source_type_ == SourceType::ClientEvent) {
791 0 : ss << " | source_tokenized_:";
792 0 : for(auto it: source_tokenized_) {
793 0 : ss << " '" << it << "'";
794 0 : }
795 0 : }
796 8 : else if (source_type_ == SourceType::STxtFile || source_type_ == SourceType::SBinFile) {
797 1 : ss << " (path file)";
798 : }
799 7 : else if (source_type_ == SourceType::Command) {
800 1 : ss << " (shell command expression)";
801 : }
802 :
803 10 : if (!source_patterns_.empty()) {
804 1 : ss << " | source variables:";
805 2 : for (auto it = source_patterns_.begin(); it != source_patterns_.end(); it ++) {
806 1 : ss << " " << it->second;
807 : }
808 : }
809 : }
810 :
811 : // TARGET
812 12 : ss << " | TargetType: " << TargetTypeAsText(target_type_);
813 12 : if (target_type_ != TargetType::ResponseStatusCode_t &&
814 12 : target_type_ != TargetType::ResponseDelayMs &&
815 12 : target_type_ != TargetType::ResponseBodyString &&
816 4 : target_type_ != TargetType::ResponseBodyHexString &&
817 4 : target_type_ != TargetType::RequestDelayMs &&
818 4 : target_type_ != TargetType::RequestTimeoutMs &&
819 4 : target_type_ != TargetType::RequestBodyString &&
820 4 : target_type_ != TargetType::RequestBodyHexString ) {
821 :
822 4 : ss << " | target_: " << target_;
823 :
824 4 : if (target_type_ == TargetType::ResponseBodyJson_String || target_type_ == TargetType::ResponseBodyJson_Integer || target_type_ == TargetType::ResponseBodyJson_Unsigned || target_type_ == TargetType::ResponseBodyJson_Float || target_type_ == TargetType::ResponseBodyJson_Boolean || target_type_ == TargetType::ResponseBodyJson_Object ||
825 3 : target_type_ == TargetType::RequestBodyJson_String || target_type_ == TargetType::RequestBodyJson_Integer || target_type_ == TargetType::RequestBodyJson_Unsigned || target_type_ == TargetType::RequestBodyJson_Float || target_type_ == TargetType::RequestBodyJson_Boolean || target_type_ == TargetType::RequestBodyJson_Object) {
826 1 : ss << " (empty: whole, path: node)";
827 : }
828 3 : else if (target_type_ == TargetType::OutState) {
829 1 : ss << " (empty: current method, method: another)" << " | target2_: " << target2_ << "(empty: current uri, uri: another)";
830 1 : if (!target2_patterns_.empty()) {
831 1 : ss << " | target2 variables:";
832 2 : for (auto it = target2_patterns_.begin(); it != target2_patterns_.end(); it ++) {
833 1 : ss << " " << it->second;
834 : }
835 : }
836 : }
837 2 : else if (target_type_ == TargetType::TTxtFile || target_type_ == TargetType::TBinFile) {
838 1 : ss << " (path file)";
839 : }
840 1 : else if (target_type_ == TargetType::ClientProvision_t && !target2_.empty()) {
841 0 : ss << " | target2_: " << target2_ << " (explicit inState)";
842 0 : if (!target2_patterns_.empty()) {
843 0 : ss << " | target2 variables:";
844 0 : for (auto it = target2_patterns_.begin(); it != target2_patterns_.end(); it ++) {
845 0 : ss << " " << it->second;
846 : }
847 : }
848 : }
849 1 : else if (target_type_ == TargetType::UDPSocket) {
850 0 : ss << " (<path file>[|<write delay ms>])";
851 : }
852 :
853 4 : if (!target_patterns_.empty()) {
854 1 : ss << " | target variables:";
855 2 : for (auto it = target_patterns_.begin(); it != target_patterns_.end(); it ++) {
856 1 : ss << " " << it->second;
857 : }
858 : }
859 : }
860 :
861 : // FILTER
862 12 : if (has_filter_) {
863 5 : ss << " | FilterType: " << FilterTypeAsText(filter_type_);
864 5 : if (filter_type_ != FilterType::Sum && filter_type_ != FilterType::Multiply && filter_type_ != FilterType::Split && filter_type_ != FilterType::BaseConvert && filter_type_ != FilterType::FStrptime && filter_type_ != FilterType::FStrftime && filter_type_ != FilterType::Size) {
865 : /*<< " | filter_rgx_: ?"*/
866 3 : ss << " | filter_ " << filter_;
867 :
868 3 : if (filter_type_ == FilterType::RegexReplace) {
869 1 : ss << " (fmt)";
870 : }
871 2 : else if (filter_type_ == FilterType::RegexCapture) {
872 1 : ss << " (literal, although not actually needed, but useful to access & print on traces)";
873 : }
874 : }
875 2 : else if (filter_type_ == FilterType::Split) {
876 1 : ss << " | size: " << filter_i_ << " | count: " << filter_u_ << " | sep: '" << filter_ << "' | filler: '" << filter_filler_ << "' | numeric: " << (filter_number_type_ ? "true" : "false");
877 : }
878 1 : else if (filter_type_ == FilterType::BaseConvert) {
879 0 : ss << " | in: " << filter_i_ << " | out: " << filter_u_ << " | capital: " << (filter_number_type_ ? "true" : "false");
880 : }
881 1 : else if (filter_type_ == FilterType::FStrptime || filter_type_ == FilterType::FStrftime) {
882 : static const char* units[] = {"s", "ms", "us", "ns"};
883 0 : ss << " | fmt: '" << filter_ << "' | unit: " << units[filter_number_type_];
884 0 : }
885 : else {
886 1 : ss << " | filter_number_type_: " << filter_number_type_ << " (0: integer, 1: unsigned, 2: float)"
887 1 : << " | filter_i_: " << filter_i_ << " | filter_u_: " << filter_u_ << " | filter_f_: " << filter_f_;
888 : }
889 :
890 5 : if (!filter_patterns_.empty()) {
891 1 : ss << " | filter variables:";
892 2 : for (auto it = filter_patterns_.begin(); it != filter_patterns_.end(); it ++) {
893 1 : ss << " " << it->second;
894 : }
895 : }
896 : }
897 :
898 24 : return ss.str();
899 12 : }
900 :
901 :
902 : }
903 : }
904 :
|