|  | 
|  | 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