Skip to content

Commit 763b418

Browse files
author
Andrea Spacca
authored
Range from to for date type (#117)
* TDD config range from/to for date type * config range from/to for date type * TDD generator range from/to for date type * generator range from/to for date type * update docs * fix milliseconds resolution in custom template tests * cr fixes * fix tests * fix tests * fix docs * fix for real
1 parent 4af1b50 commit 763b418

File tree

9 files changed

+998
-39
lines changed

9 files changed

+998
-39
lines changed

docs/fields-configuration.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ For each config entry the following fields are available:
1212
- `name` *mandatory*: dotted path field, matching an entry in [Fields definition](./glossary.md#fields-definition)
1313
- `fuzziness` *optional (`long` and `double` type only)*: when generating data you could want generated values to change in a known interval. Fuzziness allow to specify the maximum delta a generated value can have from the previous value (for the same field), as a delta percentage; value must be between 0.0 and 1.0, where 0 is 0% and 1 is 100%. When not specified there is no constraint on the generated values, boundaries will be defined by the underlying field type
1414
- `range` *optional (`long` and `double` type only)*: value will be generated between `min` and `max`
15+
- `range` *optional (`date` type only)*: value will be generated between `from` and `to`. Only one between `from` and `to` can be set, in this case the dates will be generated between `from`/`to` and `time.Now()`. Progressive order of the generated dates is always assured regardless the interval involving `from`, `to` and `time.Now()` is positive or negative. If both at least one of `from` or `to` and `period` settings are defined an error will be returned and the generator will stop. The format of the date must be parsable by the following golang date format: `2006-01-02T15:04:05.999999999-07:00`.
1516
- `cardinality` *optional*: number of different values for the field; note that this value may not be respected if not enough events are generated. Es `cardinality: 1000` with `100` generated events would produce `100` different values, not `1000`.
16-
- `period` *optional (`date` type only)*: values will be evenly generated between `time.Now()` and `time.Now().Add(period)`, where period is expressed as `time.Duration`. It accepts also a negative duration: in this case values will be evenly generated between `time.Now().Add(period)` and `time.Now()`.
17+
- `period` *optional (`date` type only)*: values will be evenly generated between `time.Now()` and `time.Now().Add(period)`, where period is expressed as `time.Duration`. It accepts also a negative duration: in this case values will be evenly generated between `time.Now().Add(period)` and `time.Now()`. If both `period` and at least one of `from` or `to` settings are defined an error will be returned and the generator will stop.
1718
- `object_keys` *optional (`object` type only)*: list of field names to generate in a object field type; if not specified a random number of field names will be generated in the object filed type
1819
- `value` *optional*: hardcoded value to set for the field (any `cardinality` will be ignored)
1920
- `enum` *optional (`keyword` type only)*: list of strings to randomly chose from a value to set for the field (any `cardinality` will be applied limited to the size of the `enum` values)
@@ -26,6 +27,10 @@ If you have an `object` type field that you defined one or multiple `object_keys
2627
fields:
2728
- name: timestamp
2829
period: "1h"
30+
- name: lastSnapshot
31+
range:
32+
from: "2023-11-23T11:29:48-00:00"
33+
to: "2023-12-13T01:39:58-00:00"
2934
- name: aws.dynamodb.metrics.AccountMaxReads.max
3035
fuzziness: 0.1
3136
range:
@@ -61,6 +66,8 @@ Related [fields definition](./writing-templates.md#fieldsyml---fields-definition
6166
```yaml
6267
- name: timestamp
6368
type: date
69+
- name: lastSnapshot
70+
type: date
6471
- name: data_stream.type
6572
type: constant_keyword
6673
- name: data_stream.dataset

pkg/genlib/config/config.go

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,25 @@ import (
1212
)
1313

1414
var rangeBoundNotSet = errors.New("range bound not set")
15+
var rangeTimeNotSet = errors.New("range time not set")
16+
var rangeInvalidConfig = errors.New("range defining both `period` and `from`/`to`")
17+
18+
type TimeRange struct {
19+
time.Time
20+
}
21+
22+
func (ct *TimeRange) Unpack(t string) error {
23+
var err error
24+
ct.Time, err = time.Parse("2006-01-02T15:04:05.999999999-07:00", t)
25+
return err
26+
}
1527

1628
type Range struct {
17-
// NOTE: we want to distinguish when Min/Max are explicitly set to zero value or are not set at all. We use a pointer, such that when not set will be `nil`.
18-
Min *float64 `config:"min"`
19-
Max *float64 `config:"max"`
29+
// NOTE: we want to distinguish when Min/Max/From/To are explicitly set to zero value or are not set at all. We use a pointer, such that when not set will be `nil`.
30+
Min *float64 `config:"min"`
31+
Max *float64 `config:"max"`
32+
From *TimeRange `config:"from"`
33+
To *TimeRange `config:"to"`
2034
}
2135

2236
type Config struct {
@@ -34,6 +48,30 @@ type ConfigField struct {
3448
Value any `config:"value"`
3549
}
3650

51+
func (cf ConfigField) ValidForDateField() error {
52+
if cf.Period.Abs() > 0 && (cf.Range.From != nil || cf.Range.To != nil) {
53+
return rangeInvalidConfig
54+
}
55+
56+
return nil
57+
}
58+
59+
func (r Range) FromAsTime() (time.Time, error) {
60+
if r.From == nil {
61+
return time.Time{}, rangeTimeNotSet
62+
}
63+
64+
return r.From.Time, nil
65+
}
66+
67+
func (r Range) ToAsTime() (time.Time, error) {
68+
if r.To == nil {
69+
return time.Time{}, rangeTimeNotSet
70+
}
71+
72+
return r.To.Time, nil
73+
}
74+
3775
func (r Range) MinAsInt64() (int64, error) {
3876
if r.Min == nil {
3977
return 0, rangeBoundNotSet

pkg/genlib/config/config_test.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,118 @@ func TestLoadConfig(t *testing.T) {
3131
assert.Equal(t, "foobar", f.Value.(string))
3232
}
3333

34+
func TestIsValidForDateField(t *testing.T) {
35+
testCases := []struct {
36+
scenario string
37+
config string
38+
hasError bool
39+
}{
40+
{
41+
scenario: "no range",
42+
config: "name: field",
43+
hasError: false,
44+
},
45+
{
46+
scenario: "zero period",
47+
config: "name: field\nrange:\n period: 0",
48+
hasError: false,
49+
},
50+
{
51+
scenario: "positive period",
52+
config: "name: field\nrange:\n period: 1",
53+
hasError: false,
54+
},
55+
{
56+
scenario: "negative period",
57+
config: "name: field\nrange:\n period: -1",
58+
hasError: false,
59+
},
60+
{
61+
scenario: "form only",
62+
config: "name: field\nrange:\n from: \"2006-01-02T15:04:05+07:00\"",
63+
hasError: false,
64+
},
65+
{
66+
scenario: "form and zero period",
67+
config: "name: field\nrange:\n period: 0\n from: \"2006-01-02T15:04:05+07:00\"",
68+
hasError: false,
69+
},
70+
{
71+
scenario: "form and positive period",
72+
config: "name: field\nrange:\n period: 1\n from: \"2006-01-02T15:04:05+07:00\"",
73+
hasError: true,
74+
},
75+
{
76+
scenario: "form and negative period",
77+
config: "name: field\nrange:\n period: -1\n from: \"2006-01-02T15:04:05+07:00\"",
78+
hasError: true,
79+
},
80+
{
81+
scenario: "to only",
82+
config: "name: field\nrange:\n to: \"2006-01-02T15:04:05-07:00\"",
83+
hasError: false,
84+
},
85+
{
86+
scenario: "to and zero period",
87+
config: "name: field\nrange:\n period: 0\n to: \"2006-01-02T15:04:05-07:00\"",
88+
hasError: false,
89+
},
90+
{
91+
scenario: "to and positive period",
92+
config: "name: field\nrange:\n period: 1\n to: \"2006-01-02T15:04:05-07:00\"",
93+
hasError: true,
94+
},
95+
{
96+
scenario: "to and negative period",
97+
config: "name: field\nrange:\n period: -1\n to: \"2006-01-02T15:04:05-07:00\"",
98+
hasError: true,
99+
},
100+
{
101+
scenario: "from and to only",
102+
config: "name: field\nrange:\n from: \"2006-01-02T15:04:05-07:00\"\n to: \"2006-01-02T15:04:05+07:00\"",
103+
hasError: false,
104+
},
105+
{
106+
scenario: "from and to and zero period",
107+
config: "name: field\nrange:\n period: 0\n from: \"2006-01-02T15:04:05-07:00\"\n\n to: \"2006-01-02T15:04:05+07:00\"",
108+
hasError: false,
109+
},
110+
{
111+
scenario: "from and to and positive period",
112+
config: "name: field\nrange:\n period: 1\n from: \"2006-01-02T15:04:05-07:00\"\n to: \"2006-01-02T15:04:05+07:00\"",
113+
hasError: true,
114+
},
115+
{
116+
scenario: "from and to and negative period",
117+
config: "name: field\nrange:\n period: -1\n from: \"2006-01-02T15:04:05-07:00\"\n to: \"2006-01-02T15:04:05+07:00\"",
118+
hasError: true,
119+
},
120+
}
121+
for _, testCase := range testCases {
122+
t.Run(testCase.scenario, func(t *testing.T) {
123+
cfg, err := yaml.NewConfig([]byte(testCase.config))
124+
if err != nil {
125+
t.Fatal(err)
126+
}
127+
128+
var config ConfigField
129+
err = cfg.Unpack(&config)
130+
if err != nil {
131+
t.Fatal(err)
132+
}
133+
134+
err = config.ValidForDateField()
135+
if testCase.hasError && err == nil {
136+
137+
}
138+
139+
if !testCase.hasError && err != nil {
140+
141+
}
142+
})
143+
}
144+
}
145+
34146
func TestRange_MaxAsFloat64(t *testing.T) {
35147
testCases := []struct {
36148
scenario string
@@ -259,6 +371,112 @@ func TestRange_MinAsInt64(t *testing.T) {
259371
}
260372
}
261373

374+
func TestRange_FromAsTime(t *testing.T) {
375+
from, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", "2023-11-23T08:35:38+00:00")
376+
if err != nil {
377+
t.Fatal(err)
378+
}
379+
380+
testCases := []struct {
381+
scenario string
382+
rangeYaml string
383+
expected time.Time
384+
hasError bool
385+
}{
386+
{
387+
scenario: "from nil",
388+
rangeYaml: "to: 2023-11-23T08:35:38+00:00",
389+
expected: time.Time{},
390+
hasError: true,
391+
},
392+
{
393+
scenario: "from not nil",
394+
rangeYaml: "from: 2023-11-23T08:35:38-00:00",
395+
expected: from,
396+
hasError: false,
397+
},
398+
}
399+
400+
for _, testCase := range testCases {
401+
t.Run(testCase.scenario, func(t *testing.T) {
402+
cfg, err := yaml.NewConfig([]byte(testCase.rangeYaml))
403+
if err != nil {
404+
t.Fatal(err)
405+
}
406+
407+
var rangeCfg Range
408+
err = cfg.Unpack(&rangeCfg)
409+
if err != nil {
410+
t.Fatal(err)
411+
}
412+
413+
v, err := rangeCfg.FromAsTime()
414+
if testCase.hasError && err == nil {
415+
t.Fatal("expected error but got nil")
416+
}
417+
if !testCase.hasError && err != nil {
418+
t.Fatal("expected no error but got one")
419+
}
420+
if testCase.expected != v {
421+
t.Fatalf("expected %v, got %v", testCase.expected, v)
422+
}
423+
})
424+
}
425+
}
426+
427+
func TestRange_ToAsTime(t *testing.T) {
428+
to, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", "2023-11-23T08:35:38-00:00")
429+
if err != nil {
430+
t.Fatal(err)
431+
}
432+
433+
testCases := []struct {
434+
scenario string
435+
rangeYaml string
436+
expected time.Time
437+
hasError bool
438+
}{
439+
{
440+
scenario: "to nil",
441+
rangeYaml: "from: 2023-11-23T08:35:38+00:00",
442+
expected: time.Time{},
443+
hasError: true,
444+
},
445+
{
446+
scenario: "to not nil",
447+
rangeYaml: "to: 2023-11-23T08:35:38-00:00",
448+
expected: to,
449+
hasError: false,
450+
},
451+
}
452+
453+
for _, testCase := range testCases {
454+
t.Run(testCase.scenario, func(t *testing.T) {
455+
cfg, err := yaml.NewConfig([]byte(testCase.rangeYaml))
456+
if err != nil {
457+
t.Fatal(err)
458+
}
459+
460+
var rangeCfg Range
461+
err = cfg.Unpack(&rangeCfg)
462+
if err != nil {
463+
t.Fatal(err)
464+
}
465+
466+
v, err := rangeCfg.ToAsTime()
467+
if testCase.hasError && err == nil {
468+
t.Fatal("expected error but got nil")
469+
}
470+
if !testCase.hasError && err != nil {
471+
t.Fatal("expected no error but got one")
472+
}
473+
if testCase.expected != v {
474+
t.Fatalf("expected %v, got %v", testCase.expected, v)
475+
}
476+
})
477+
}
478+
}
479+
262480
func TestPeriod(t *testing.T) {
263481
testCases := []struct {
264482
scenario string

pkg/genlib/generator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ func generateTemplateFromField(cfg Config, fields Fields, templateEngine int) ([
121121
fieldVariableName += "Var"
122122
if field.Type == FieldTypeDate {
123123
if templateEngine == textTemplateEngine {
124-
fieldTemplate = fmt.Sprintf(`{{ $%s := generate "%s.%s" }}"%s.%s": %s{{$%s.Format "2006-01-02T15:04:05.999999Z07:00"}}%s%s`, fieldVariableName, fieldNameRoot, rNoun, fieldNameRoot, rNoun, fieldWrap, fieldVariableName, fieldWrap, fieldTrailer)
124+
fieldTemplate = fmt.Sprintf(`{{ $%s := generate "%s.%s" }}"%s.%s": %s{{$%s.Format "2006-01-02T15:04:05.999999999Z07:00"}}%s%s`, fieldVariableName, fieldNameRoot, rNoun, fieldNameRoot, rNoun, fieldWrap, fieldVariableName, fieldWrap, fieldTrailer)
125125
} else if templateEngine == customTemplateEngine {
126126
fieldTemplate = fmt.Sprintf(`"%s.%s": %s{{.%s.%s}}%s%s`, fieldNameRoot, rNoun, fieldWrap, fieldNameRoot, rNoun, fieldWrap, fieldTrailer)
127127
}
@@ -146,7 +146,7 @@ func generateTemplateFromField(cfg Config, fields Fields, templateEngine int) ([
146146
fieldVariableName += "Var"
147147
if field.Type == FieldTypeDate {
148148
if templateEngine == textTemplateEngine {
149-
fieldTemplate = fmt.Sprintf(`{{ $%s := generate "%s" }}"%s": %s{{$%s.Format "2006-01-02T15:04:05.999999Z07:00"}}%s%s`, fieldVariableName, field.Name, field.Name, fieldWrap, fieldVariableName, fieldWrap, fieldTrailer)
149+
fieldTemplate = fmt.Sprintf(`{{ $%s := generate "%s" }}"%s": %s{{$%s.Format "2006-01-02T15:04:05.999999999Z07:00"}}%s%s`, fieldVariableName, field.Name, field.Name, fieldWrap, fieldVariableName, fieldWrap, fieldTrailer)
150150
} else if templateEngine == customTemplateEngine {
151151
fieldTemplate = fmt.Sprintf(`"%s": %s{{.%s}}%s%s`, field.Name, fieldWrap, field.Name, fieldWrap, fieldTrailer)
152152
}

0 commit comments

Comments
 (0)