-
Notifications
You must be signed in to change notification settings - Fork 89
Expand file tree
/
Copy pathconversion.go
More file actions
359 lines (310 loc) · 11.3 KB
/
Copy pathconversion.go
File metadata and controls
359 lines (310 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
package conversion
import (
"context"
"fmt"
"sort"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
)
func ToString(ctx context.Context, v attr.Value) (string, error) {
if t := v.Type(ctx); t != types.StringType {
return "", fmt.Errorf("type mismatch. expected 'types.StringType' but got '%s'", t.String())
}
if v.IsNull() || v.IsUnknown() {
return "", fmt.Errorf("value is unknown or null")
}
tv, err := v.ToTerraformValue(ctx)
if err != nil {
return "", err
}
var s string
if err := tv.Copy().As(&s); err != nil {
return "", err
}
return s, nil
}
func ToOptStringMap(tfMap map[string]attr.Value) (*map[string]string, error) { //nolint: gocritic //pointer needed to map optional fields
labels := make(map[string]string, len(tfMap))
for l, v := range tfMap {
valueString, ok := v.(types.String)
if !ok {
return nil, fmt.Errorf("error converting map value: expected to string, got %v", v)
}
labels[l] = valueString.ValueString()
}
labelsPointer := &labels
if len(labels) == 0 {
labelsPointer = nil
}
return labelsPointer, nil
}
func ToOptStringPointerMap(tfMap map[string]attr.Value) (*map[string]*string, error) { //nolint: gocritic //pointer needed to map optional fields
labels := make(map[string]*string, len(tfMap))
for l, v := range tfMap {
valueString, ok := v.(types.String)
if !ok {
return nil, fmt.Errorf("error converting map value: expected to string, got %v", v)
}
labels[l] = valueString.ValueStringPointer()
}
labelsPointer := &labels
if len(labels) == 0 {
labelsPointer = nil
}
return labelsPointer, nil
}
func ToTerraformStringMap(ctx context.Context, m map[string]string) (basetypes.MapValue, error) {
labels := make(map[string]attr.Value, len(m))
for l, v := range m {
stringValue := types.StringValue(v)
labels[l] = stringValue
}
res, diags := types.MapValueFrom(ctx, types.StringType, m)
if diags.HasError() {
return types.MapNull(types.StringType), fmt.Errorf("converting to MapValue: %v", diags.Errors())
}
return res, nil
}
// ToStringInterfaceMap converts a basetypes.MapValue of Strings to a map[string]interface{}.
func ToStringInterfaceMap(ctx context.Context, m basetypes.MapValue) (map[string]any, error) {
labels := map[string]string{}
diags := m.ElementsAs(ctx, &labels, false)
if diags.HasError() {
return nil, fmt.Errorf("converting from MapValue: %w", core.DiagsToError(diags))
}
interfaceMap := make(map[string]any, len(labels))
for k, v := range labels {
interfaceMap[k] = v
}
return interfaceMap, nil
}
// StringValueToPointer converts basetypes.StringValue to a pointer to string.
// It returns nil if the value is null or unknown.
func StringValueToPointer(s basetypes.StringValue) *string {
if s.IsNull() || s.IsUnknown() {
return nil
}
return new(s.ValueString())
}
// StringPointerValueNullIfEmpty converts a string pointer to types.String, treating an empty
// string as null. Some APIs return "" instead of omitting unset optional fields, which would
// otherwise mismatch a null config for Optional, non-Computed attributes.
func StringPointerValueNullIfEmpty(s *string) types.String {
if s == nil || *s == "" {
return types.StringNull()
}
return types.StringValue(*s)
}
// StringValueToPointer converts basetypes.StringValue to a pointer to enum.
// It returns nil if the value is null or unknown.
func StringValueToEnumPointer[T ~string](s basetypes.StringValue) *T {
if s.IsNull() || s.IsUnknown() {
return nil
}
return new(T(s.ValueString()))
}
// Int32ValueToPointer converts basetypes.Int32Value to a pointer to int32.
// It returns nil if the value is null or unknown.
func Int32ValueToPointer(s basetypes.Int32Value) *int32 {
if s.IsNull() || s.IsUnknown() {
return nil
}
value := s.ValueInt32()
return &value
}
// Int64ValueToPointer converts basetypes.Int64Value to a pointer to int64.
// It returns nil if the value is null or unknown.
func Int64ValueToPointer(s basetypes.Int64Value) *int64 {
if s.IsNull() || s.IsUnknown() {
return nil
}
value := s.ValueInt64()
return &value
}
// Float32ValueToPointer converts basetypes.Float32Value to a pointer to float32.
// It returns nil if the value is null or unknown.
func Float32ValueToPointer(s basetypes.Float32Value) *float32 {
if s.IsNull() || s.IsUnknown() {
return nil
}
value := s.ValueFloat32()
return &value
}
// Float64ValueToPointer converts basetypes.Float64Value to a pointer to float64.
// It returns nil if the value is null or unknown.
func Float64ValueToPointer(s basetypes.Float64Value) *float64 {
if s.IsNull() || s.IsUnknown() {
return nil
}
value := s.ValueFloat64()
return &value
}
// BoolValueToPointer converts basetypes.BoolValue to a pointer to bool.
// It returns nil if the value is null or unknown.
func BoolValueToPointer(s basetypes.BoolValue) *bool {
if s.IsNull() || s.IsUnknown() {
return nil
}
value := s.ValueBool()
return &value
}
// StringListToPointer converts basetypes.ListValue to a pointer to a list of strings.
// It returns nil if the value is null or unknown.
func StringListToPointer(list basetypes.ListValue) (*[]string, error) {
result, err := StringListToSlice(list)
if result == nil {
return nil, err
}
return &result, err
}
// StringListToSlice converts basetypes.ListValue to a list of strings.
// It returns nil if the value is null or unknown.
func StringListToSlice(list basetypes.ListValue) ([]string, error) {
return StringListToEnumSlice[string](list)
}
// StringListToEnumSlice converts basetypes.ListValue to a list of enums.
// It returns nil if the value is null or unknown.
func StringListToEnumSlice[T ~string](list basetypes.ListValue) ([]T, error) {
if list.IsNull() || list.IsUnknown() {
return nil, nil
}
// Instantiate an empty slice to ensure the slice is not nil
listStr := []T{}
for i, el := range list.Elements() {
elStr, ok := el.(types.String)
if !ok {
return nil, fmt.Errorf("element %d is not a string", i)
}
listStr = append(listStr, T(elStr.ValueString()))
}
return listStr, nil
}
// StringSetToPointer converts basetypes.SetValue to a pointer to a list of strings.
// It returns nil if the value is null or unknown.
// Note: It sorts the resulting slice to ensure deterministic behavior.
func StringSetToPointer(set basetypes.SetValue) (*[]string, error) {
result, err := StringSetToSlice(set)
return &result, err
}
// StringSetToSlice converts basetypes.SetValue to a slice of strings.
// It returns nil if the value is null or unknown.
// Note: It sorts the resulting slice to ensure deterministic behavior.
func StringSetToSlice(set basetypes.SetValue) ([]string, error) {
if set.IsNull() || set.IsUnknown() {
return nil, nil
}
elements := set.Elements()
result := make([]string, 0, len(elements))
for i, el := range elements {
elStr, ok := el.(types.String)
if !ok {
return nil, fmt.Errorf("element %d in set is not a string (type: %T)", i, el)
}
result = append(result, elStr.ValueString())
}
// Because Sets are unordered in Terraform, we sort here to
// prevent non-deterministic behavior in the provider logic or API calls.
sort.Strings(result)
return result, nil
}
// ToJSONMApPartialUpdatePayload returns a map[string]interface{} to be used in a PATCH request payload.
// It takes a current map as it is in the terraform state and a desired map as it is in the user configuratiom
// and builds a map which sets to null keys that should be removed, updates the values of existing keys and adds new keys
// This method is needed because in partial updates, e.g. if the key is not provided it is ignored and not removed
func ToJSONMapPartialUpdatePayload(ctx context.Context, current, desired types.Map) (map[string]any, error) {
currentMap, err := ToStringInterfaceMap(ctx, current)
if err != nil {
return nil, fmt.Errorf("converting to Go map: %w", err)
}
desiredMap, err := ToStringInterfaceMap(ctx, desired)
if err != nil {
return nil, fmt.Errorf("converting to Go map: %w", err)
}
mapPayload := map[string]any{}
// Update and remove existing keys
for k := range currentMap {
if desiredValue, ok := desiredMap[k]; ok {
mapPayload[k] = desiredValue
} else {
mapPayload[k] = nil
}
}
// Add new keys
for k, desiredValue := range desiredMap {
if _, ok := mapPayload[k]; !ok {
mapPayload[k] = desiredValue
}
}
return mapPayload, nil
}
func ParseProviderData(ctx context.Context, providerData any, diags *diag.Diagnostics) (core.ProviderData, bool) {
// Prevent panic if the provider has not been configured.
if providerData == nil {
return core.ProviderData{}, false
}
stackitProviderData, ok := providerData.(core.ProviderData)
if !ok {
core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Expected configure type core.ProviderData, got %T", providerData))
return core.ProviderData{}, false
}
return stackitProviderData, true
}
func ParseEphemeralProviderData(ctx context.Context, providerData any, diags *diag.Diagnostics) (core.EphemeralProviderData, bool) {
// Prevent panic if the provider has not been configured.
if providerData == nil {
return core.EphemeralProviderData{}, false
}
stackitProviderData, ok := providerData.(core.EphemeralProviderData)
if !ok {
core.LogAndAddError(ctx, diags, "Error configuring API client", "Expected configure type core.EphemeralProviderData")
return core.EphemeralProviderData{}, false
}
return stackitProviderData, true
}
// StringListToSet safely converts a Go slice of strings into a Terraform framework types.Set.
//
// By accepting a pointer to diag.Diagnostics, it enables clean, inline assignments within
// map literals (avoiding verbose error checking per field) while correctly accumulating
// framework diagnostics.
//
// State mapping behavior:
// - Nil inputs map directly to types.SetNull to accurately reflect unconfigured states.
// - On conversion failure, it mutates the provided diags and returns an Unknown set
// (basetypes.NewSetUnknown) to prevent state corruption and allow Terraform to handle
// the error gracefully.
func StringListToSet(ctx context.Context, stringList []string, diags *diag.Diagnostics) types.Set {
if stringList == nil {
return types.SetNull(types.StringType)
}
setVal, d := types.SetValueFrom(ctx, types.StringType, stringList)
diags.Append(d...)
if d.HasError() {
return basetypes.NewSetUnknown(types.StringType)
}
return setVal
}
// TerraformStringSetToList safely converts a Go slice of strings into a Terraform framework types.Set.
//
// By accepting a pointer to diag.Diagnostics, it enables clean, inline assignments within
// map literals (avoiding verbose error checking per field) while correctly accumulating
// framework diagnostics.
//
// State mapping behavior:
// - Nil inputs or IsUnknown return nil.
// - On conversion failure, it mutates the provided diags and returns an nil
func TerraformStringSetToList(ctx context.Context, tfSet basetypes.SetValue, diags *diag.Diagnostics) []string {
if utils.IsUndefined(tfSet) {
return nil
}
var elements []string
d := tfSet.ElementsAs(ctx, &elements, true)
diags.Append(d...)
if d.HasError() {
return nil
}
return elements
}