Skip to content

Commit 24c1814

Browse files
committed
[cmd/mdatagen] Implement numeric validation in generated Go config code (#14806)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> Implements support for the JSON Schema numeric validation keywords 'minimum', 'maximum', 'exclusiveMaximum' and 'exclusiveMinimum' in mdatagen config generation pipeline. <!-- Issue number if applicable --> Fixes #14806 <!--Describe what testing was performed and which tests were added.--> * Added unit tests in generation_test.go * The samplescraper golden file (`generated_config.go`) is updated to reflect the new generated output.
1 parent 56e1235 commit 24c1814

9 files changed

Lines changed: 452 additions & 10 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp)
7+
component: cmd/mdatagen
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Handle numeric validators in generated config structs
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [14806]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: Supported validators include `minimum`, `maximum`, `exclusiveMinimum` and `exclusiveMaximum`.
19+
20+
# Optional: The change log or logs in which this entry should be included
21+
# e.g. '[user]' or '[user, api]'
22+
# Include 'user' if the change is relevant to end users
23+
# Include 'api' if there is a change to a library API
24+
# Default: '[user]'
25+
change_logs: [api]

cmd/mdatagen/internal/cfggen/generation.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -409,14 +409,19 @@ func ExtractValidators(md *ConfigMetadata) []Validator {
409409
}
410410

411411
type ValidationRules struct {
412-
MaxLength *int
413-
MinLength *int
414-
Pattern *string
415-
Required bool
412+
MaxLength *int
413+
MinLength *int
414+
Pattern *string
415+
Required bool
416+
Minimum *float64
417+
Maximum *float64
418+
ExclusiveMinimum *float64
419+
ExclusiveMaximum *float64
416420
}
417421

418422
func (vr *ValidationRules) HasValueRule() bool {
419-
return vr.MaxLength != nil || vr.MinLength != nil || vr.Pattern != nil
423+
return vr.MaxLength != nil || vr.MinLength != nil || vr.Pattern != nil ||
424+
vr.Minimum != nil || vr.Maximum != nil || vr.ExclusiveMinimum != nil || vr.ExclusiveMaximum != nil
420425
}
421426

422427
func (vr *ValidationRules) Enabled() bool {
@@ -436,8 +441,12 @@ func collectValidators(md *ConfigMetadata, validators *[]Validator) {
436441
for _, propName := range slices.Sorted(maps.Keys(md.Properties)) {
437442
prop := md.Properties[propName]
438443
rules := ValidationRules{
439-
MaxLength: prop.MaxLength,
440-
MinLength: prop.MinLength,
444+
MaxLength: prop.MaxLength,
445+
MinLength: prop.MinLength,
446+
Minimum: prop.Minimum,
447+
Maximum: prop.Maximum,
448+
ExclusiveMinimum: prop.ExclusiveMinimum,
449+
ExclusiveMaximum: prop.ExclusiveMaximum,
441450
}
442451

443452
rules.Required = slices.Contains(md.Required, propName)

cmd/mdatagen/internal/cfggen/generation_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,6 +1688,150 @@ func TestExtractValidators_StringValidators(t *testing.T) {
16881688
}
16891689
}
16901690

1691+
func TestExtractValidators_NumericValidators(t *testing.T) {
1692+
minVal := 0.0
1693+
maxVal := 100.0
1694+
exclMinVal := 5.0
1695+
exclMaxVal := 10.0
1696+
1697+
tests := []struct {
1698+
name string
1699+
metadata *ConfigMetadata
1700+
expected []Validator
1701+
}{
1702+
{
1703+
name: "minimum only",
1704+
metadata: &ConfigMetadata{
1705+
Type: "object",
1706+
Properties: map[string]*ConfigMetadata{
1707+
"count": {Type: "number", Minimum: &minVal},
1708+
},
1709+
},
1710+
expected: []Validator{
1711+
{
1712+
FieldName: "count",
1713+
FieldType: "number",
1714+
Rules: ValidationRules{Minimum: &minVal},
1715+
},
1716+
},
1717+
},
1718+
{
1719+
name: "maximum only",
1720+
metadata: &ConfigMetadata{
1721+
Type: "object",
1722+
Properties: map[string]*ConfigMetadata{
1723+
"count": {Type: "number", Maximum: &maxVal},
1724+
},
1725+
},
1726+
expected: []Validator{
1727+
{
1728+
FieldName: "count",
1729+
FieldType: "number",
1730+
Rules: ValidationRules{Maximum: &maxVal},
1731+
},
1732+
},
1733+
},
1734+
{
1735+
name: "exclusiveMinimum only",
1736+
metadata: &ConfigMetadata{
1737+
Type: "object",
1738+
Properties: map[string]*ConfigMetadata{
1739+
"score": {Type: "number", ExclusiveMinimum: &exclMinVal},
1740+
},
1741+
},
1742+
expected: []Validator{
1743+
{
1744+
FieldName: "score",
1745+
FieldType: "number",
1746+
Rules: ValidationRules{ExclusiveMinimum: &exclMinVal},
1747+
},
1748+
},
1749+
},
1750+
{
1751+
name: "exclusiveMaximum only",
1752+
metadata: &ConfigMetadata{
1753+
Type: "object",
1754+
Properties: map[string]*ConfigMetadata{
1755+
"score": {Type: "number", ExclusiveMaximum: &exclMaxVal},
1756+
},
1757+
},
1758+
expected: []Validator{
1759+
{
1760+
FieldName: "score",
1761+
FieldType: "number",
1762+
Rules: ValidationRules{ExclusiveMaximum: &exclMaxVal},
1763+
},
1764+
},
1765+
},
1766+
{
1767+
name: "all numeric validators",
1768+
metadata: &ConfigMetadata{
1769+
Type: "object",
1770+
Properties: map[string]*ConfigMetadata{
1771+
"value": {Type: "number", Minimum: &minVal, Maximum: &maxVal, ExclusiveMinimum: &exclMinVal, ExclusiveMaximum: &exclMaxVal},
1772+
},
1773+
},
1774+
expected: []Validator{
1775+
{
1776+
FieldName: "value",
1777+
FieldType: "number",
1778+
Rules: ValidationRules{Minimum: &minVal, Maximum: &maxVal, ExclusiveMinimum: &exclMinVal, ExclusiveMaximum: &exclMaxVal},
1779+
},
1780+
},
1781+
},
1782+
{
1783+
name: "integer type with numeric validators",
1784+
metadata: &ConfigMetadata{
1785+
Type: "object",
1786+
Properties: map[string]*ConfigMetadata{
1787+
"retry_count": {Type: "integer", Minimum: &minVal, Maximum: &maxVal},
1788+
},
1789+
},
1790+
expected: []Validator{
1791+
{
1792+
FieldName: "retry_count",
1793+
FieldType: "integer",
1794+
Rules: ValidationRules{Minimum: &minVal, Maximum: &maxVal},
1795+
},
1796+
},
1797+
},
1798+
{
1799+
name: "nil numeric validators produces no validator",
1800+
metadata: &ConfigMetadata{
1801+
Type: "object",
1802+
Properties: map[string]*ConfigMetadata{
1803+
"value": {Type: "number"},
1804+
},
1805+
},
1806+
expected: []Validator{},
1807+
},
1808+
{
1809+
name: "required combined with numeric validators",
1810+
metadata: &ConfigMetadata{
1811+
Type: "object",
1812+
Required: []string{"threshold"},
1813+
Properties: map[string]*ConfigMetadata{
1814+
"threshold": {Type: "number", Minimum: &minVal, Maximum: &maxVal},
1815+
},
1816+
},
1817+
expected: []Validator{
1818+
{
1819+
FieldName: "threshold",
1820+
FieldType: "number",
1821+
Rules: ValidationRules{Required: true, Minimum: &minVal, Maximum: &maxVal},
1822+
},
1823+
},
1824+
},
1825+
}
1826+
1827+
for _, tt := range tests {
1828+
t.Run(tt.name, func(t *testing.T) {
1829+
result := ExtractValidators(tt.metadata)
1830+
require.Equal(t, tt.expected, result)
1831+
})
1832+
}
1833+
}
1834+
16911835
func TestExtractValidators_InternalRefFromDefs_NoValidators(t *testing.T) {
16921836
md := &ConfigMetadata{
16931837
Ref: "plain_config",
@@ -1863,6 +2007,36 @@ func TestValidationRules_HasValueRule(t *testing.T) {
18632007
rules: ValidationRules{},
18642008
expected: false,
18652009
},
2010+
{
2011+
name: "minimum",
2012+
rules: ValidationRules{Minimum: Ptr(0.0)},
2013+
expected: true,
2014+
},
2015+
{
2016+
name: "maximum",
2017+
rules: ValidationRules{Maximum: Ptr(100.0)},
2018+
expected: true,
2019+
},
2020+
{
2021+
name: "exclusiveMinimum",
2022+
rules: ValidationRules{ExclusiveMinimum: Ptr(5.0)},
2023+
expected: true,
2024+
},
2025+
{
2026+
name: "exclusiveMaximum",
2027+
rules: ValidationRules{ExclusiveMaximum: Ptr(10.0)},
2028+
expected: true,
2029+
},
2030+
{
2031+
name: "all numeric validators",
2032+
rules: ValidationRules{Minimum: Ptr(0.0), Maximum: Ptr(100.0), ExclusiveMinimum: Ptr(5.0), ExclusiveMaximum: Ptr(10.0)},
2033+
expected: true,
2034+
},
2035+
{
2036+
name: "empty numeric validators",
2037+
rules: ValidationRules{},
2038+
expected: false,
2039+
},
18662040
}
18672041

18682042
for _, tt := range tests {

cmd/mdatagen/internal/samplescraper/config.schema.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,20 @@
840840
"additionalProperties": {
841841
"type": "string"
842842
}
843+
},
844+
"retry_count": {
845+
"description": "Number of retry attempts for failed scrapes.",
846+
"type": "integer",
847+
"default": 3,
848+
"maximum": 10,
849+
"minimum": 0
850+
},
851+
"timeout_seconds": {
852+
"description": "Timeout in seconds for each scrape request.",
853+
"type": "number",
854+
"default": 5,
855+
"exclusiveMaximum": 30,
856+
"exclusiveMinimum": 0
843857
}
844858
},
845859
"required": [

cmd/mdatagen/internal/samplescraper/generated_config.go

Lines changed: 22 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/mdatagen/internal/samplescraper/generated_config_test.go

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/mdatagen/internal/samplescraper/metadata.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,18 @@ config:
7171
default:
7272
option1: value1
7373
option2: value2
74+
retry_count:
75+
description: Number of retry attempts for failed scrapes.
76+
type: integer
77+
default: 3
78+
minimum: 0
79+
maximum: 10
80+
timeout_seconds:
81+
description: Timeout in seconds for each scrape request.
82+
type: number
83+
default: 5.0
84+
exclusiveMinimum: 0
85+
exclusiveMaximum: 30
7486
required: [labels]
7587
default:
7688
- {} # one item with default values

0 commit comments

Comments
 (0)