@@ -124,7 +124,7 @@ func parameterDataSource() *schema.Resource {
124
124
}
125
125
var value string
126
126
if parameter .Default != "" {
127
- err := valueIsType (parameter .Type , parameter .Default )
127
+ err := valueIsType (parameter .Type , parameter .Default , cty. Path {cty. GetAttrStep { Name : "default" }} )
128
128
if err != nil {
129
129
return err
130
130
}
@@ -144,6 +144,8 @@ func parameterDataSource() *schema.Resource {
144
144
return diag .Errorf ("ephemeral parameter requires the default property" )
145
145
}
146
146
147
+ // TODO: Should we move this into the Valid() function on
148
+ // Parameter?
147
149
if len (parameter .Validation ) == 1 {
148
150
validation := & parameter .Validation [0 ]
149
151
err = validation .Valid (parameter .Type , value )
@@ -153,73 +155,27 @@ func parameterDataSource() *schema.Resource {
153
155
}
154
156
155
157
// Validate options
156
-
157
- // optionType might differ from parameter.Type. This is ok, and parameter.Type
158
- // should be used for the value type, and optionType for options.
159
- var optionType OptionType
160
- optionType , parameter .FormType , err = ValidateFormType (parameter .Type , len (parameter .Option ), parameter .FormType )
158
+ _ , parameter .FormType , err = ValidateFormType (parameter .Type , len (parameter .Option ), parameter .FormType )
161
159
if err != nil {
162
- return diag .FromErr (err )
160
+ return diag.Diagnostics {
161
+ {
162
+ Severity : diag .Error ,
163
+ Summary : "Invalid form_type for parameter" ,
164
+ Detail : err .Error (),
165
+ AttributePath : cty.Path {cty.GetAttrStep {Name : "form_type" }},
166
+ },
167
+ }
163
168
}
164
169
// Set the form_type back in case the value was changed.
165
170
// Eg via a default. If a user does not specify, a default value
166
171
// is used and saved.
167
172
rd .Set ("form_type" , parameter .FormType )
168
173
169
- if len (parameter .Option ) > 0 {
170
- names := map [string ]interface {}{}
171
- values := map [string ]interface {}{}
172
- for _ , option := range parameter .Option {
173
- _ , exists := names [option .Name ]
174
- if exists {
175
- return diag .Errorf ("multiple options cannot have the same name %q" , option .Name )
176
- }
177
- _ , exists = values [option .Value ]
178
- if exists {
179
- return diag .Errorf ("multiple options cannot have the same value %q" , option .Value )
180
- }
181
- err := valueIsType (optionType , option .Value )
182
- if err != nil {
183
- return err
184
- }
185
- values [option .Value ] = nil
186
- names [option .Name ] = nil
187
- }
188
-
189
- if parameter .Default != "" {
190
- if parameter .Type == OptionTypeListString && optionType == OptionTypeString {
191
- // If the type is list(string) and optionType is string, we have
192
- // to ensure all elements of the default exist as options.
193
- var defaultValues []string
194
- // TODO: We do this unmarshal in a few spots. It should be standardized.
195
- err = json .Unmarshal ([]byte (parameter .Default ), & defaultValues )
196
- if err != nil {
197
- return diag .Errorf ("default value %q is not a list of strings" , parameter .Default )
198
- }
199
-
200
- // missing is used to construct a more helpful error message
201
- var missing []string
202
- for _ , defaultValue := range defaultValues {
203
- _ , defaultIsValid := values [defaultValue ]
204
- if ! defaultIsValid {
205
- missing = append (missing , defaultValue )
206
- }
207
- }
208
-
209
- if len (missing ) > 0 {
210
- return diag .Errorf (
211
- "default value %q is not a valid option, values %q are missing from the option" ,
212
- parameter .Default , strings .Join (missing , ", " ),
213
- )
214
- }
215
- } else {
216
- _ , defaultIsValid := values [parameter .Default ]
217
- if ! defaultIsValid {
218
- return diag .Errorf ("%q default value %q must be defined as one of options" , parameter .FormType , parameter .Default )
219
- }
220
- }
221
- }
174
+ diags := parameter .Valid ()
175
+ if diags .HasError () {
176
+ return diags
222
177
}
178
+
223
179
return nil
224
180
},
225
181
Schema : map [string ]* schema.Schema {
@@ -433,7 +389,7 @@ func fixValidationResourceData(rawConfig cty.Value, validation interface{}) (int
433
389
return vArr , nil
434
390
}
435
391
436
- func valueIsType (typ OptionType , value string ) diag.Diagnostics {
392
+ func valueIsType (typ OptionType , value string , attrPath cty. Path ) diag.Diagnostics {
437
393
switch typ {
438
394
case OptionTypeNumber :
439
395
_ , err := strconv .ParseFloat (value , 64 )
@@ -446,10 +402,9 @@ func valueIsType(typ OptionType, value string) diag.Diagnostics {
446
402
return diag .Errorf ("%q is not a bool" , value )
447
403
}
448
404
case OptionTypeListString :
449
- var items []string
450
- err := json .Unmarshal ([]byte (value ), & items )
451
- if err != nil {
452
- return diag .Errorf ("%q is not an array of strings" , value )
405
+ _ , diags := valueIsListString (value , attrPath )
406
+ if diags .HasError () {
407
+ return diags
453
408
}
454
409
case OptionTypeString :
455
410
// Anything is a string!
@@ -459,6 +414,102 @@ func valueIsType(typ OptionType, value string) diag.Diagnostics {
459
414
return nil
460
415
}
461
416
417
+ func (v * Parameter ) Valid () diag.Diagnostics {
418
+ // optionType might differ from parameter.Type. This is ok, and parameter.Type
419
+ // should be used for the value type, and optionType for options.
420
+ optionType , _ , err := ValidateFormType (v .Type , len (v .Option ), v .FormType )
421
+ if err != nil {
422
+ return diag.Diagnostics {
423
+ {
424
+ Severity : diag .Error ,
425
+ Summary : "Invalid form_type for parameter" ,
426
+ Detail : err .Error (),
427
+ AttributePath : cty.Path {cty.GetAttrStep {Name : "form_type" }},
428
+ },
429
+ }
430
+ }
431
+
432
+ optionNames := map [string ]any {}
433
+ optionValues := map [string ]any {}
434
+ if len (v .Option ) > 0 {
435
+ for _ , option := range v .Option {
436
+ _ , exists := optionNames [option .Name ]
437
+ if exists {
438
+ return diag.Diagnostics {{
439
+ Severity : diag .Error ,
440
+ Summary : "Option names must be unique." ,
441
+ Detail : fmt .Sprintf ("multiple options found with the same name %q" , option .Name ),
442
+ },
443
+ }
444
+ }
445
+ _ , exists = optionValues [option .Value ]
446
+ if exists {
447
+ return diag.Diagnostics {
448
+ {
449
+ Severity : diag .Error ,
450
+ Summary : "Option values must be unique." ,
451
+ Detail : fmt .Sprintf ("multiple options found with the same value %q" , option .Value ),
452
+ },
453
+ }
454
+ }
455
+ diags := valueIsType (optionType , option .Value , cty.Path {})
456
+ if diags .HasError () {
457
+ return diags
458
+ }
459
+ optionValues [option .Value ] = nil
460
+ optionNames [option .Name ] = nil
461
+ }
462
+ }
463
+
464
+ if v .Default != "" && len (v .Option ) > 0 {
465
+ if v .Type == OptionTypeListString && optionType == OptionTypeString {
466
+ // If the type is list(string) and optionType is string, we have
467
+ // to ensure all elements of the default exist as options.
468
+ defaultValues , diags := valueIsListString (v .Default , cty.Path {cty.GetAttrStep {Name : "default" }})
469
+ if diags .HasError () {
470
+ return diags
471
+ }
472
+
473
+ // missing is used to construct a more helpful error message
474
+ var missing []string
475
+ for _ , defaultValue := range defaultValues {
476
+ _ , defaultIsValid := optionValues [defaultValue ]
477
+ if ! defaultIsValid {
478
+ missing = append (missing , defaultValue )
479
+ }
480
+ }
481
+
482
+ if len (missing ) > 0 {
483
+ return diag.Diagnostics {
484
+ {
485
+ Severity : diag .Error ,
486
+ Summary : "Default values must be a valid option" ,
487
+ Detail : fmt .Sprintf (
488
+ "default value %q is not a valid option, values %q are missing from the options" ,
489
+ v .Default , strings .Join (missing , ", " ),
490
+ ),
491
+ AttributePath : cty.Path {cty.GetAttrStep {Name : "default" }},
492
+ },
493
+ }
494
+ }
495
+ } else {
496
+ _ , defaultIsValid := optionValues [v .Default ]
497
+ if ! defaultIsValid {
498
+ return diag.Diagnostics {
499
+ {
500
+ Severity : diag .Error ,
501
+ Summary : "Default value must be a valid option" ,
502
+ Detail : fmt .Sprintf ("the value %q must be defined as one of options" , v .Default ),
503
+ AttributePath : cty.Path {cty.GetAttrStep {Name : "default" }},
504
+ },
505
+ }
506
+ }
507
+ }
508
+ }
509
+
510
+ return nil
511
+ }
512
+
462
513
func (v * Validation ) Valid (typ OptionType , value string ) error {
463
514
if typ != OptionTypeNumber {
464
515
if ! v .MinDisabled {
@@ -519,6 +570,22 @@ func (v *Validation) Valid(typ OptionType, value string) error {
519
570
return nil
520
571
}
521
572
573
+ func valueIsListString (value string , path cty.Path ) ([]string , diag.Diagnostics ) {
574
+ var items []string
575
+ err := json .Unmarshal ([]byte (value ), & items )
576
+ if err != nil {
577
+ return nil , diag.Diagnostics {
578
+ {
579
+ Severity : diag .Error ,
580
+ Summary : "When using list(string) type, value must be a json encoded list of strings" ,
581
+ Detail : fmt .Sprintf ("value %q is not a valid list of strings" , value ),
582
+ AttributePath : path ,
583
+ },
584
+ }
585
+ }
586
+ return items , nil
587
+ }
588
+
522
589
// ParameterEnvironmentVariable returns the environment variable to specify for
523
590
// a parameter by it's name. It's hashed because spaces and special characters
524
591
// can be used in parameter names that may not be valid in env vars.
0 commit comments