@@ -124,7 +124,7 @@ func parameterDataSource() *schema.Resource {
124124}
125125var value string
126126if parameter .Default != "" {
127- err := valueIsType (parameter .Type ,parameter .Default )
127+ err := valueIsType (parameter .Type ,parameter .Default , cty. Path {cty. GetAttrStep { Name : "default" }} )
128128if err != nil {
129129return err
130130}
@@ -144,6 +144,8 @@ func parameterDataSource() *schema.Resource {
144144return diag .Errorf ("ephemeral parameter requires the default property" )
145145}
146146
147+ // TODO: Should we move this into the Valid() function on
148+ // Parameter?
147149if len (parameter .Validation )== 1 {
148150validation := & parameter .Validation [0 ]
149151err = validation .Valid (parameter .Type ,value )
@@ -153,73 +155,27 @@ func parameterDataSource() *schema.Resource {
153155}
154156
155157// 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 )
161159if 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+ }
163168}
164169// Set the form_type back in case the value was changed.
165170// Eg via a default. If a user does not specify, a default value
166171// is used and saved.
167172rd .Set ("form_type" ,parameter .FormType )
168173
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
222177}
178+
223179return nil
224180},
225181Schema :map [string ]* schema.Schema {
@@ -433,7 +389,7 @@ func fixValidationResourceData(rawConfig cty.Value, validation interface{}) (int
433389return vArr ,nil
434390}
435391
436- func valueIsType (typ OptionType ,value string ) diag.Diagnostics {
392+ func valueIsType (typ OptionType ,value string , attrPath cty. Path ) diag.Diagnostics {
437393switch typ {
438394case OptionTypeNumber :
439395_ ,err := strconv .ParseFloat (value ,64 )
@@ -446,10 +402,9 @@ func valueIsType(typ OptionType, value string) diag.Diagnostics {
446402return diag .Errorf ("%q is not a bool" ,value )
447403}
448404case 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
453408}
454409case OptionTypeString :
455410// Anything is a string!
@@ -459,6 +414,102 @@ func valueIsType(typ OptionType, value string) diag.Diagnostics {
459414return nil
460415}
461416
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+
462513func (v * Validation )Valid (typ OptionType ,value string )error {
463514if typ != OptionTypeNumber {
464515if ! v .MinDisabled {
@@ -519,6 +570,22 @@ func (v *Validation) Valid(typ OptionType, value string) error {
519570return nil
520571}
521572
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+
522589// ParameterEnvironmentVariable returns the environment variable to specify for
523590// a parameter by it's name. It's hashed because spaces and special characters
524591// can be used in parameter names that may not be valid in env vars.