@@ -124,7 +124,7 @@ func parameterDataSource() *schema.Resource {
124124 }
125125 var value string
126126 if parameter .Default != "" {
127- err := valueIsType (parameter .Type , parameter .Default )
127+ err := valueIsType (parameter .Type , parameter .Default , cty. Path {cty. GetAttrStep { Name : "default" }} )
128128 if err != nil {
129129 return err
130130 }
@@ -144,6 +144,8 @@ func parameterDataSource() *schema.Resource {
144144 return diag .Errorf ("ephemeral parameter requires the default property" )
145145 }
146146
147+ // TODO: Should we move this into the Valid() function on
148+ // Parameter?
147149 if len (parameter .Validation ) == 1 {
148150 validation := & parameter .Validation [0 ]
149151 err = 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 )
161159 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+ }
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.
167172 rd .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+
223179 return nil
224180 },
225181 Schema : map [string ]* schema.Schema {
@@ -433,7 +389,7 @@ func fixValidationResourceData(rawConfig cty.Value, validation interface{}) (int
433389 return 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 {
437393 switch typ {
438394 case OptionTypeNumber :
439395 _ , err := strconv .ParseFloat (value , 64 )
@@ -446,10 +402,9 @@ func valueIsType(typ OptionType, value string) diag.Diagnostics {
446402 return diag .Errorf ("%q is not a bool" , value )
447403 }
448404 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
453408 }
454409 case OptionTypeString :
455410 // Anything is a string!
@@ -459,6 +414,102 @@ func valueIsType(typ OptionType, value string) diag.Diagnostics {
459414 return 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 {
463514 if typ != OptionTypeNumber {
464515 if ! v .MinDisabled {
@@ -519,6 +570,22 @@ func (v *Validation) Valid(typ OptionType, value string) error {
519570 return 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.
0 commit comments