Skip to content

Commit 946db5a

Browse files
authored
fix: coder_parameter: validation min and max are truly optional (#121)
1 parent dbc530b commit 946db5a

File tree

3 files changed

+119
-32
lines changed

3 files changed

+119
-32
lines changed

provider/decode_test.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/coder/terraform-provider-coder/provider"
77
"github.com/mitchellh/mapstructure"
8+
"github.com/stretchr/testify/assert"
89
"github.com/stretchr/testify/require"
910
)
1011

@@ -18,15 +19,25 @@ func TestDecode(t *testing.T) {
1819

1920
aMap := map[string]interface{}{
2021
"name": "Parameter Name",
22+
"type": "number",
2123
"display_name": displayName,
2224
"legacy_variable": legacyVariable,
2325
"legacy_variable_name": legacyVariableName,
26+
"min": nil,
27+
"validation": []map[string]interface{}{
28+
{
29+
"min": nil,
30+
"max": 5,
31+
},
32+
},
2433
}
2534

2635
var param provider.Parameter
2736
err := mapstructure.Decode(aMap, &param)
2837
require.NoError(t, err)
29-
require.Equal(t, displayName, param.DisplayName)
30-
require.Equal(t, legacyVariable, param.LegacyVariable)
31-
require.Equal(t, legacyVariableName, param.LegacyVariableName)
38+
assert.Equal(t, displayName, param.DisplayName)
39+
assert.Equal(t, legacyVariable, param.LegacyVariable)
40+
assert.Equal(t, legacyVariableName, param.LegacyVariableName)
41+
assert.Equal(t, (*int)(nil), param.Validation[0].Min)
42+
assert.Equal(t, 5, *param.Validation[0].Max)
3243
}

provider/parameter.go

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import (
1212
"strconv"
1313

1414
"github.com/google/uuid"
15+
"github.com/hashicorp/go-cty/cty"
1516
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1617
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1718
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1819
"github.com/mitchellh/mapstructure"
20+
"golang.org/x/xerrors"
1921
)
2022

2123
type Option struct {
@@ -26,8 +28,8 @@ type Option struct {
2628
}
2729

2830
type Validation struct {
29-
Min int
30-
Max int
31+
Min *int
32+
Max *int
3133
Monotonic string
3234

3335
Regex string
@@ -62,8 +64,18 @@ func parameterDataSource() *schema.Resource {
6264
ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
6365
rd.SetId(uuid.NewString())
6466

67+
fixedValidation, err := fixValidationResourceData(rd.GetRawConfig(), rd.Get("validation"))
68+
if err != nil {
69+
return diag.FromErr(err)
70+
}
71+
72+
err = rd.Set("validation", fixedValidation)
73+
if err != nil {
74+
return diag.FromErr(err)
75+
}
76+
6577
var parameter Parameter
66-
err := mapstructure.Decode(struct {
78+
err = mapstructure.Decode(struct {
6779
Value interface{}
6880
Name interface{}
6981
DisplayName interface{}
@@ -98,7 +110,7 @@ func parameterDataSource() *schema.Resource {
98110
}(),
99111
Icon: rd.Get("icon"),
100112
Option: rd.Get("option"),
101-
Validation: rd.Get("validation"),
113+
Validation: fixedValidation,
102114
Optional: func() bool {
103115
// This hack allows for checking if the "default" field is present in the .tf file.
104116
// If "default" is missing or is "null", then it means that this field is required,
@@ -272,17 +284,14 @@ func parameterDataSource() *schema.Resource {
272284
Elem: &schema.Resource{
273285
Schema: map[string]*schema.Schema{
274286
"min": {
275-
Type: schema.TypeInt,
276-
Optional: true,
277-
Default: 0,
278-
Description: "The minimum of a number parameter.",
279-
RequiredWith: []string{"validation.0.max"},
287+
Type: schema.TypeInt,
288+
Optional: true,
289+
Description: "The minimum of a number parameter.",
280290
},
281291
"max": {
282-
Type: schema.TypeInt,
283-
Optional: true,
284-
Description: "The maximum of a number parameter.",
285-
RequiredWith: []string{"validation.0.min"},
292+
Type: schema.TypeInt,
293+
Optional: true,
294+
Description: "The maximum of a number parameter.",
286295
},
287296
"monotonic": {
288297
Type: schema.TypeString,
@@ -325,6 +334,45 @@ func parameterDataSource() *schema.Resource {
325334
}
326335
}
327336

337+
func fixValidationResourceData(rawConfig cty.Value, validation interface{}) (interface{}, error) {
338+
// Read validation from raw config
339+
rawValidation, ok := rawConfig.AsValueMap()["validation"]
340+
if !ok {
341+
return validation, nil // no validation rules, nothing to fix
342+
}
343+
344+
rawValidationArr := rawValidation.AsValueSlice()
345+
if len(rawValidationArr) == 0 {
346+
return validation, nil // no validation rules, nothing to fix
347+
}
348+
349+
rawValidationRule := rawValidationArr[0].AsValueMap()
350+
351+
// Load validation from resource data
352+
vArr, ok := validation.([]interface{})
353+
if !ok {
354+
return nil, xerrors.New("validation should be an array")
355+
}
356+
357+
if len(vArr) == 0 {
358+
return validation, nil // no validation rules, nothing to fix
359+
}
360+
361+
validationRule, ok := vArr[0].(map[string]interface{})
362+
if !ok {
363+
return nil, xerrors.New("validation rule should be a map")
364+
}
365+
366+
// Fix the resource data
367+
if rawValidationRule["min"].IsNull() {
368+
validationRule["min"] = nil
369+
}
370+
if rawValidationRule["max"].IsNull() {
371+
validationRule["max"] = nil
372+
}
373+
return vArr, nil
374+
}
375+
328376
func valueIsType(typ, value string) diag.Diagnostics {
329377
switch typ {
330378
case "number":
@@ -353,10 +401,10 @@ func valueIsType(typ, value string) diag.Diagnostics {
353401

354402
func (v *Validation) Valid(typ, value string) error {
355403
if typ != "number" {
356-
if v.Min != 0 {
404+
if v.Min != nil {
357405
return fmt.Errorf("a min cannot be specified for a %s type", typ)
358406
}
359-
if v.Max != 0 {
407+
if v.Max != nil {
360408
return fmt.Errorf("a max cannot be specified for a %s type", typ)
361409
}
362410
}
@@ -389,10 +437,10 @@ func (v *Validation) Valid(typ, value string) error {
389437
if err != nil {
390438
return fmt.Errorf("value %q is not a number", value)
391439
}
392-
if num < v.Min {
440+
if v.Min != nil && num < *v.Min {
393441
return fmt.Errorf("value %d is less than the minimum %d", num, v.Min)
394442
}
395-
if num > v.Max {
443+
if v.Max != nil && num > *v.Max {
396444
return fmt.Errorf("value %d is more than the maximum %d", num, v.Max)
397445
}
398446
if v.Monotonic != "" && v.Monotonic != ValidationMonotonicIncreasing && v.Monotonic != ValidationMonotonicDecreasing {

provider/parameter_test.go

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,30 @@ func TestParameter(t *testing.T) {
109109
}
110110
}
111111
`,
112+
}, {
113+
Name: "NumberValidation_Min",
114+
Config: `
115+
data "coder_parameter" "region" {
116+
name = "Region"
117+
type = "number"
118+
default = 2
119+
validation {
120+
min = 1
121+
}
122+
}
123+
`,
124+
}, {
125+
Name: "NumberValidation_Max",
126+
Config: `
127+
data "coder_parameter" "region" {
128+
name = "Region"
129+
type = "number"
130+
default = 2
131+
validation {
132+
max = 9
133+
}
134+
}
135+
`,
112136
}, {
113137
Name: "DefaultNotNumber",
114138
Config: `
@@ -443,18 +467,18 @@ func TestValueValidatesType(t *testing.T) {
443467
Regex,
444468
RegexError string
445469
Min,
446-
Max int
470+
Max *int
447471
Monotonic string
448472
Error *regexp.Regexp
449473
}{{
450474
Name: "StringWithMin",
451475
Type: "string",
452-
Min: 1,
476+
Min: ptrNumber(1),
453477
Error: regexp.MustCompile("cannot be specified"),
454478
}, {
455479
Name: "StringWithMax",
456480
Type: "string",
457-
Max: 1,
481+
Max: ptrNumber(1),
458482
Error: regexp.MustCompile("cannot be specified"),
459483
}, {
460484
Name: "NonStringWithRegex",
@@ -474,13 +498,13 @@ func TestValueValidatesType(t *testing.T) {
474498
Name: "NumberBelowMin",
475499
Type: "number",
476500
Value: "0",
477-
Min: 1,
501+
Min: ptrNumber(1),
478502
Error: regexp.MustCompile("is less than the minimum"),
479503
}, {
480504
Name: "NumberAboveMax",
481505
Type: "number",
482-
Value: "1",
483-
Max: 0,
506+
Value: "2",
507+
Max: ptrNumber(1),
484508
Error: regexp.MustCompile("is more than the maximum"),
485509
}, {
486510
Name: "InvalidBool",
@@ -498,23 +522,23 @@ func TestValueValidatesType(t *testing.T) {
498522
Name: "InvalidMonotonicity",
499523
Type: "number",
500524
Value: "1",
501-
Min: 0,
502-
Max: 2,
525+
Min: ptrNumber(0),
526+
Max: ptrNumber(2),
503527
Monotonic: "foobar",
504528
Error: regexp.MustCompile(`number monotonicity can be either "increasing" or "decreasing"`),
505529
}, {
506530
Name: "IncreasingMonotonicity",
507531
Type: "number",
508532
Value: "1",
509-
Min: 0,
510-
Max: 2,
533+
Min: ptrNumber(0),
534+
Max: ptrNumber(2),
511535
Monotonic: "increasing",
512536
}, {
513537
Name: "DecreasingMonotonicity",
514538
Type: "number",
515539
Value: "1",
516-
Min: 0,
517-
Max: 2,
540+
Min: ptrNumber(0),
541+
Max: ptrNumber(2),
518542
Monotonic: "decreasing",
519543
}, {
520544
Name: "ValidListOfStrings",
@@ -550,3 +574,7 @@ func TestValueValidatesType(t *testing.T) {
550574
})
551575
}
552576
}
577+
578+
func ptrNumber(i int) *int {
579+
return &i
580+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy