Skip to content

Commit 390ea3c

Browse files
committed
add template helpers for timestamps
1 parent 49d755a commit 390ea3c

File tree

6 files changed

+1628
-892
lines changed

6 files changed

+1628
-892
lines changed

.generator/src/generator/templates/function_mappings.j2

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,19 @@ pub fn collect_function_calls(world: &mut DatadogWorld) {
5454
fn test_{{version}}_{{ operation['operationId'] | snake_case }}(world: &mut DatadogWorld, _parameters: &HashMap<String, Value>) {
5555
let api = world.api_instances.{{version}}_{{ apiName }}.as_ref().expect("api instance not found");
5656
{%- if operationParams|length > 0 -%}
57+
{%- for parameter in operationParams %}
58+
{%- if parameter[1].required %}
59+
let {{ parameter[0] | variable_name }} = serde_json::from_value(_parameters.get("{{ parameter[0] }}").unwrap().clone()).unwrap();
60+
{%- else %}
61+
let {{ parameter[0] | variable_name }} = match _parameters.get("{{ parameter[0] }}").is_some() {
62+
true => serde_json::from_value(_parameters.get("{{ parameter[0] }}").unwrap().clone()).unwrap(),
63+
false => None,
64+
};
65+
{%- endif %}
66+
{%- endfor %}
5767
let params = datadog{{ version.upper() }}::api::{{ apiName }}::{{ operation['operationId'] }}Params {
5868
{%- for param in operationParams %}
59-
{{ param[0] | variable_name }}: serde_json::from_value(_parameters.get("{{ param[0] }}").unwrap().clone()).unwrap(),
69+
{{ param[0] | variable_name }},
6070
{%- endfor %}
6171
};
6272
let response = match block_on(api.{{ operation['operationId'] | snake_case}}_with_http_info(params)) {

tests/scenarios/features/v2/fastly_integration.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,4 +243,4 @@ Feature: Fastly Integration
243243
And request contains "service_id" parameter from "REPLACE.ME"
244244
And body with value {"data": {"attributes": {"tags": ["myTag", "myTag2:myValue"]}, "id": "abc123", "type": "fastly-services"}}
245245
When the request is sent
246-
Then the response status is 200 OK
246+
Then the response status is 200 OK

tests/scenarios/features/v2/given.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,18 @@
306306
"tag": "Roles",
307307
"operationId": "ListPermissions"
308308
},
309+
{
310+
"parameters": [
311+
{
312+
"name": "body",
313+
"value": "{\n \"data\": {\n \"attributes\": {\n \"description\": \"Sample powerpack\",\n \"group_widget\": {\n \"definition\": {\n \"layout_type\": \"ordered\",\n \"show_title\": true,\n \"title\": \"Sample Powerpack\",\n \"type\": \"group\",\n \"widgets\": [\n {\n \"definition\": {\n \"content\": \"test\",\n \"type\": \"note\"\n }\n }\n ]\n },\n \"layout\": {\n \"height\": 3,\n \"width\": 12,\n \"x\": 0,\n \"y\": 0\n },\n \"live_span\": \"1h\"\n },\n \"name\": \"{{ unique }}\",\n \"tags\": [\n \"tag:sample\"\n ],\n \"template_variables\": [\n {\n \"defaults\": [\n \"*\"\n ],\n \"name\": \"sample\"\n }\n ]\n },\n \"type\": \"powerpack\"\n }\n}"
314+
}
315+
],
316+
"step": "there is a valid \"powerpack\" in the system",
317+
"key": "powerpack",
318+
"tag": "Powerpack",
319+
"operationId": "CreatePowerpack"
320+
},
309321
{
310322
"parameters": [
311323
{

tests/scenarios/features/v2/undo.json

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@
104104
"type": "idempotent"
105105
}
106106
},
107+
"GetApmRetentionFilter": {
108+
"tag": "APM Retention Filters",
109+
"undo": {
110+
"type": "safe"
111+
}
112+
},
107113
"UpdateApmRetentionFilter": {
108114
"tag": "APM Retention Filters",
109115
"undo": {
@@ -225,6 +231,18 @@
225231
"type": "safe"
226232
}
227233
},
234+
"ListContainerImages": {
235+
"tag": "Container Images",
236+
"undo": {
237+
"type": "safe"
238+
}
239+
},
240+
"ListContainers": {
241+
"tag": "Containers",
242+
"undo": {
243+
"type": "safe"
244+
}
245+
},
228246
"ListCurrentUserApplicationKeys": {
229247
"tag": "Key Management",
230248
"undo": {
@@ -1005,18 +1023,55 @@
10051023
"type": "safe"
10061024
}
10071025
},
1008-
"GetFinding": {
1026+
"MuteFindings": {
10091027
"tag": "Security Monitoring",
10101028
"undo": {
10111029
"type": "safe"
10121030
}
10131031
},
1014-
"UpdateFinding": {
1032+
"GetFinding": {
10151033
"tag": "Security Monitoring",
10161034
"undo": {
10171035
"type": "safe"
10181036
}
10191037
},
1038+
"ListPowerpacks": {
1039+
"tag": "Powerpack",
1040+
"undo": {
1041+
"type": "safe"
1042+
}
1043+
},
1044+
"CreatePowerpack": {
1045+
"tag": "Powerpack",
1046+
"undo": {
1047+
"operationId": "DeletePowerpack",
1048+
"parameters": [
1049+
{
1050+
"name": "powerpack_id",
1051+
"source": "data.id"
1052+
}
1053+
],
1054+
"type": "unsafe"
1055+
}
1056+
},
1057+
"DeletePowerpack": {
1058+
"tag": "Powerpack",
1059+
"undo": {
1060+
"type": "idempotent"
1061+
}
1062+
},
1063+
"GetPowerpack": {
1064+
"tag": "Powerpack",
1065+
"undo": {
1066+
"type": "safe"
1067+
}
1068+
},
1069+
"UpdatePowerpack": {
1070+
"tag": "Powerpack",
1071+
"undo": {
1072+
"type": "idempotent"
1073+
}
1074+
},
10201075
"ListProcesses": {
10211076
"tag": "Processes",
10221077
"undo": {

tests/scenarios/fixtures.rs

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use crate::scenarios::function_mappings::{
22
collect_function_calls, initialize_api_instance, ApiInstances,
33
};
4-
use chrono::DateTime;
4+
use chrono::{DateTime, Duration};
55
use cucumber::{
66
event::ScenarioFinished,
77
gherkin::{Feature, Rule, Scenario},
88
given, then, when, World,
99
};
1010
use datadog_api_client::datadog::configuration::Configuration;
11-
use handlebars::Handlebars;
11+
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
1212
use log::debug;
1313
use regex::Regex;
1414
use reqwest_middleware::ClientBuilder;
@@ -20,6 +20,7 @@ use std::{
2020
env,
2121
fs::{create_dir_all, read_to_string, File},
2222
io::BufReader,
23+
ops::Add,
2324
path::PathBuf,
2425
time::SystemTime,
2526
};
@@ -39,7 +40,7 @@ struct UndoOperation {
3940
parameters: HashMap<String, Value>,
4041
}
4142

42-
#[derive(Debug, Default, World)]
43+
#[derive(Default, World)]
4344
pub struct DatadogWorld {
4445
pub api_version: i32,
4546
pub config: Configuration,
@@ -54,6 +55,14 @@ pub struct DatadogWorld {
5455
undo_operations: Vec<UndoOperation>,
5556
}
5657

58+
// Workaround to suppress cucumber's debug output - the DatadogWorld
59+
// struct debug output is overly verbose and not useful
60+
impl std::fmt::Debug for DatadogWorld {
61+
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62+
Ok(())
63+
}
64+
}
65+
5766
pub async fn before_scenario(
5867
feature: &Feature,
5968
_rule: Option<&Rule>,
@@ -144,7 +153,7 @@ pub async fn before_scenario(
144153
// res.headers.remove_entry("content-security-policy");
145154
// });
146155
// vcr_client_builder.with(middleware).build()
147-
panic!("sdk's shouldn't be recording, that's the spec repo's job.");
156+
panic!("Use the cassette transform in datadog-api-spec instead. This recording mode is only here for completeness.");
148157
}
149158
_ => {
150159
frozen_time = DateTime::parse_from_rfc3339(
@@ -387,9 +396,79 @@ fn lookup(path: &String, object: &Value) -> Option<Value> {
387396
return object.pointer(&json_pointer).cloned();
388397
}
389398

399+
fn relative_time_helper(h: &Helper, c: &Context) -> DateTime<chrono::Utc> {
400+
// get parameter from helper or throw an error
401+
let v = h.param(0).unwrap().render();
402+
let time_helper_re = Regex::new(r"now(?: *([+-]) *(\d+)([smhdMy]))?").unwrap();
403+
let caps = time_helper_re
404+
.captures(&v)
405+
.expect("failed to parse timeISO template function");
406+
let time = chrono::DateTime::from_timestamp(c.data().get("now").unwrap().as_i64().unwrap(), 0)
407+
.unwrap();
408+
if caps.get(1).is_some() {
409+
let diff = str::parse::<i64>(
410+
&(caps.get(1).unwrap().as_str().to_string() + caps.get(2).unwrap().as_str()),
411+
)
412+
.unwrap();
413+
match caps.get(3).unwrap().as_str() {
414+
"s" => time.add(Duration::seconds(diff)),
415+
"m" => time.add(Duration::minutes(diff)),
416+
"h" => time.add(Duration::hours(diff)),
417+
"d" => time.add(Duration::days(diff)),
418+
"M" => time.add(Duration::weeks(diff * 4)),
419+
"y" => time.add(Duration::weeks(diff * 52)),
420+
_ => panic!("invalid time unit"),
421+
}
422+
} else {
423+
time
424+
}
425+
}
426+
427+
fn timeISO_helper(
428+
h: &Helper,
429+
_: &Handlebars,
430+
c: &Context,
431+
_: &mut RenderContext,
432+
out: &mut dyn Output,
433+
) -> Result<(), RenderError> {
434+
write!(out, "{}", relative_time_helper(h, c).to_rfc3339())?;
435+
Ok(())
436+
}
437+
438+
fn timestamp_helper(
439+
h: &Helper,
440+
_: &Handlebars,
441+
c: &Context,
442+
_: &mut RenderContext,
443+
out: &mut dyn Output,
444+
) -> Result<(), RenderError> {
445+
write!(
446+
out,
447+
"{}",
448+
relative_time_helper(h, c)
449+
.signed_duration_since(DateTime::UNIX_EPOCH)
450+
.num_seconds()
451+
)?;
452+
Ok(())
453+
}
454+
390455
fn template(string: String, fixtures: &Value) -> String {
391-
Handlebars::new()
392-
.render_template(string.as_str(), &fixtures)
456+
let time_helper_re = Regex::new(r"(?:timestamp|timeISO)\([^{}]*\)").unwrap();
457+
let helper_parsed_string = time_helper_re
458+
.replace_all(&string, |caps: &regex::Captures| {
459+
caps.get(0)
460+
.unwrap()
461+
.as_str()
462+
.replace('(', " ")
463+
.replace(')', "")
464+
})
465+
.to_string();
466+
467+
let mut handlebars = Handlebars::new();
468+
handlebars.register_helper("timeISO", Box::new(timeISO_helper));
469+
handlebars.register_helper("timestamp", Box::new(timestamp_helper));
470+
handlebars
471+
.render_template(helper_parsed_string.as_str(), &fixtures)
393472
.expect("failed to apply template")
394473
}
395474

0 commit comments

Comments
 (0)