|
| 1 | +package provider |
| 2 | + |
| 3 | +import ( |
| 4 | + "slices" |
| 5 | + |
| 6 | + "golang.org/x/xerrors" |
| 7 | +) |
| 8 | + |
| 9 | +// OptionType is a type of option that can be used in the 'type' argument of |
| 10 | +// a parameter. These should match types as defined in terraform: |
| 11 | +// |
| 12 | +// https://developer.hashicorp.com/terraform/language/expressions/types |
| 13 | +// |
| 14 | +// The value have to be string literals, as type constraint keywords are not |
| 15 | +// supported in providers. |
| 16 | +type OptionType = string |
| 17 | + |
| 18 | +const ( |
| 19 | + OptionTypeString OptionType = "string" |
| 20 | + OptionTypeNumber OptionType = "number" |
| 21 | + OptionTypeBoolean OptionType = "bool" |
| 22 | + OptionTypeListString OptionType = "list(string)" |
| 23 | +) |
| 24 | + |
| 25 | +func OptionTypes() []OptionType { |
| 26 | + return []OptionType{ |
| 27 | + OptionTypeString, |
| 28 | + OptionTypeNumber, |
| 29 | + OptionTypeBoolean, |
| 30 | + OptionTypeListString, |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +// ParameterFormType is the list of supported form types for display in |
| 35 | +// the Coder "create workspace" form. These form types are functional as well |
| 36 | +// as cosmetic. Refer to `formTypeTruthTable` for the allowed pairings. |
| 37 | +// For example, "multi-select" has the type "list(string)" but the option |
| 38 | +// values are "string". |
| 39 | +type ParameterFormType string |
| 40 | + |
| 41 | +const ( |
| 42 | + ParameterFormTypeDefault ParameterFormType = "" |
| 43 | + ParameterFormTypeRadio ParameterFormType = "radio" |
| 44 | + ParameterFormTypeSlider ParameterFormType = "slider" |
| 45 | + ParameterFormTypeInput ParameterFormType = "input" |
| 46 | + ParameterFormTypeDropdown ParameterFormType = "dropdown" |
| 47 | + ParameterFormTypeCheckbox ParameterFormType = "checkbox" |
| 48 | + ParameterFormTypeSwitch ParameterFormType = "switch" |
| 49 | + ParameterFormTypeMultiSelect ParameterFormType = "multi-select" |
| 50 | + ParameterFormTypeTagSelect ParameterFormType = "tag-select" |
| 51 | + ParameterFormTypeTextArea ParameterFormType = "textarea" |
| 52 | + ParameterFormTypeError ParameterFormType = "error" |
| 53 | +) |
| 54 | + |
| 55 | +// ParameterFormTypes should be kept in sync with the enum list above. |
| 56 | +func ParameterFormTypes() []ParameterFormType { |
| 57 | + return []ParameterFormType{ |
| 58 | + // Intentionally omit "ParameterFormTypeDefault" from this set. |
| 59 | + // It is a valid enum, but will always be mapped to a real value when |
| 60 | + // being used. |
| 61 | + ParameterFormTypeRadio, |
| 62 | + ParameterFormTypeSlider, |
| 63 | + ParameterFormTypeInput, |
| 64 | + ParameterFormTypeDropdown, |
| 65 | + ParameterFormTypeCheckbox, |
| 66 | + ParameterFormTypeSwitch, |
| 67 | + ParameterFormTypeMultiSelect, |
| 68 | + ParameterFormTypeTagSelect, |
| 69 | + ParameterFormTypeTextArea, |
| 70 | + ParameterFormTypeError, |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +// formTypeTruthTable is a map of [`type`][`optionCount` > 0] to `form_type`. |
| 75 | +// The first value in the slice is the default value assuming `form_type` is |
| 76 | +// not specified. |
| 77 | +// |
| 78 | +// The boolean key indicates whether the `options` field is specified. |
| 79 | +// | Type | Options | Specified Form Type | form_type | Notes | |
| 80 | +// |-------------------|---------|---------------------|----------------|--------------------------------| |
| 81 | +// | `string` `number` | Y | | `radio` | | |
| 82 | +// | `string` `number` | Y | `dropdown` | `dropdown` | | |
| 83 | +// | `string` `number` | N | | `input` | | |
| 84 | +// | `string` | N | 'textarea' | `textarea` | | |
| 85 | +// | `number` | N | 'slider' | `slider` | min/max validation | |
| 86 | +// | `bool` | Y | | `radio` | | |
| 87 | +// | `bool` | N | | `checkbox` | | |
| 88 | +// | `bool` | N | `switch` | `switch` | | |
| 89 | +// | `list(string)` | Y | | `radio` | | |
| 90 | +// | `list(string)` | N | | `tag-select` | | |
| 91 | +// | `list(string)` | Y | `multi-select` | `multi-select` | Option values will be `string` | |
| 92 | +var formTypeTruthTable = map[OptionType]map[bool][]ParameterFormType{ |
| 93 | + OptionTypeString: { |
| 94 | + true: {ParameterFormTypeRadio, ParameterFormTypeDropdown}, |
| 95 | + false: {ParameterFormTypeInput, ParameterFormTypeTextArea}, |
| 96 | + }, |
| 97 | + OptionTypeNumber: { |
| 98 | + true: {ParameterFormTypeRadio, ParameterFormTypeDropdown}, |
| 99 | + false: {ParameterFormTypeInput, ParameterFormTypeSlider}, |
| 100 | + }, |
| 101 | + OptionTypeBoolean: { |
| 102 | + true: {ParameterFormTypeRadio}, |
| 103 | + false: {ParameterFormTypeCheckbox, ParameterFormTypeSwitch}, |
| 104 | + }, |
| 105 | + OptionTypeListString: { |
| 106 | + true: {ParameterFormTypeRadio, ParameterFormTypeMultiSelect}, |
| 107 | + false: {ParameterFormTypeTagSelect}, |
| 108 | + }, |
| 109 | +} |
| 110 | + |
| 111 | +// ValidateFormType handles the truth table for the valid set of `type` and |
| 112 | +// `form_type` options. |
| 113 | +// The OptionType is also returned because it is possible the 'type' of the |
| 114 | +// 'value' & 'default' fields is different from the 'type' of the options. |
| 115 | +// The use case is when using multi-select. The options are 'string' and the |
| 116 | +// value is 'list(string)'. |
| 117 | +func ValidateFormType(paramType OptionType, optionCount int, specifiedFormType ParameterFormType) (OptionType, ParameterFormType, error) { |
| 118 | + optionsExist := optionCount > 0 |
| 119 | + allowed, ok := formTypeTruthTable[paramType][optionsExist] |
| 120 | + if !ok || len(allowed) == 0 { |
| 121 | + // This error should really never be hit, as the provider sdk does an enum validation. |
| 122 | + return paramType, specifiedFormType, xerrors.Errorf("\"type\" attribute=%q is not supported, choose one of %v", paramType, OptionTypes()) |
| 123 | + } |
| 124 | + |
| 125 | + if specifiedFormType == ParameterFormTypeDefault { |
| 126 | + // handle the default case |
| 127 | + specifiedFormType = allowed[0] |
| 128 | + } |
| 129 | + |
| 130 | + if !slices.Contains(allowed, specifiedFormType) { |
| 131 | + optionMsg := "" |
| 132 | + opposite := formTypeTruthTable[paramType][!optionsExist] |
| 133 | + |
| 134 | + // This extra message tells a user if they are using a valid form_type |
| 135 | + // for a 'type', but it is invalid because options do/do-not exist. |
| 136 | + // It serves as a more helpful error message. |
| 137 | + // |
| 138 | + // Eg: form_type=slider is valid for type=number, but invalid if options exist. |
| 139 | + // And this error message is more accurate than just saying "form_type=slider is |
| 140 | + // not valid for type=number". |
| 141 | + if slices.Contains(opposite, specifiedFormType) { |
| 142 | + if optionsExist { |
| 143 | + optionMsg = " when options exist" |
| 144 | + } else { |
| 145 | + optionMsg = " when options do not exist" |
| 146 | + } |
| 147 | + } |
| 148 | + return paramType, specifiedFormType, |
| 149 | + xerrors.Errorf("\"form_type\" attribute=%q is not supported for \"type\"=%q%s, choose one of %v", |
| 150 | + specifiedFormType, paramType, |
| 151 | + optionMsg, toStrings(allowed)) |
| 152 | + } |
| 153 | + |
| 154 | + // This is the only current special case. If 'multi-select' is selected, the type |
| 155 | + // of 'value' and an options 'value' are different. The type of the parameter is |
| 156 | + // `list(string)` but the type of the individual options is `string`. |
| 157 | + if paramType == OptionTypeListString && specifiedFormType == ParameterFormTypeMultiSelect { |
| 158 | + return OptionTypeString, ParameterFormTypeMultiSelect, nil |
| 159 | + } |
| 160 | + |
| 161 | + return paramType, specifiedFormType, nil |
| 162 | +} |
| 163 | + |
| 164 | +func toStrings[A ~string](l []A) []string { |
| 165 | + var r []string |
| 166 | + for _, v := range l { |
| 167 | + r = append(r, string(v)) |
| 168 | + } |
| 169 | + return r |
| 170 | +} |
0 commit comments