Skip to content

Commit b22c6cd

Browse files
authored
Dcarbone/v1.x (#10)
* v1.x work: - removing many panic calls as that was stupid of me - adding additional error types in relevant packages with constructors and testers - beginning implementation of OneOf with []string - working on more tests * fleshing out strings comparison and tests * adding comparison to []ints as well as some more internal cleanup * readme updates * CompareOp.Name() now returns underlying string value if "unknown"
1 parent cc04bd9 commit b22c6cd

File tree

11 files changed

+2146
-155
lines changed

11 files changed

+2146
-155
lines changed

.github/workflows/tests.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ jobs:
2121
- uses: actions/checkout@v3
2222
- uses: actions/setup-go@v3
2323
with:
24-
go-version: 1.17
24+
go-version-file: go.mod
2525

2626
- run: go mod tidy -compat=1.17 && go mod vendor
27+
2728
- working-directory: acctest
29+
run: go test -v
30+
31+
- working-directory: validation
2832
run: go test -v

README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,15 @@ your own [ComparisonFunc](validation/comparison.go#44) using [SetComparisonFunc]
109109
Validators: []tfsdk.AttributeValidator{
110110
// equal
111111
validation.Compare(validation.Equal, 5),
112+
// string comparisons are case sensitive by default
113+
validation.Compare(validation.Equal, "five")
114+
// passing true as the 3rd arg executes a case-insensitive comparison with strings
115+
validation.Compare(validation.Equal, "fIve", true)
116+
// you may also equate string slices
117+
validation.Compare(validation.Equal, []string{"one", "two"})
118+
validation.Compare(validation.Equal, []string{"oNe", "twO"}, true)
119+
// you can also assert that a list of ints is equivalent
120+
validation.Compare(validation.Equal, []int{1, 2})
112121

113122
// less than
114123
validation.Compare(validation.LessThan, 10),
@@ -123,7 +132,30 @@ your own [ComparisonFunc](validation/comparison.go#44) using [SetComparisonFunc]
123132
validation.Compare(validation.GreaterThanOrEqualTo, 5),
124133

125134
// not equal
126-
validation.Compare(validation.NotEqual, 10).
135+
validation.Compare(validation.NotEqual, 10),
136+
// string comparisons are case sensitive by default
137+
validation.Compare(validation.NotEqual, "ten")
138+
// passing true as the 3rd arg executes a case-insensitive comparison with strings
139+
validation.Compare(validation.NotEqual, "tEn", true)
140+
// you may also compare string slices
141+
validation.Compare(validation.NotEqual, []string{"one", "two"})
142+
validation.Compare(validation.NotEqual, []string{"oNe", "twO"}, true)
143+
// you can also assert that a list of ints is not equivalent
144+
validation.Compare(validation.NotEqual, []int{1, 2})
145+
146+
// one of
147+
// currently OneOf only works with strings and ints
148+
validation.Compare(validation.OneOf, []string{"one", "two"})
149+
// you can provide true for the 3rd parameter to perform a case-insensitive comparison
150+
validation.Compare(validation.OneOf, []string{"one", "two"}, true)
151+
validation.Compare(validation.OneOf []int{1, 2})
152+
153+
// not one of
154+
// currently NotOneOf only works with strings and ints
155+
validation.Compare(validation.NotOneOf, []string{"one", "two"})
156+
// you can provide true for the 3rd parameter to perform a case-insensitive comparison
157+
validation.Compare(validation.NotOneOf, []string{"one", "two"}, true)
158+
validation.Compare(validation.NotOneOf []int{1, 2})
127159
}
128160
}
129161
```

conv/errors.go

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,36 @@
11
package conv
22

3-
import "errors"
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/dcarbone/terraform-plugin-framework-utils/internal/util"
8+
"github.com/hashicorp/terraform-plugin-framework/attr"
9+
)
410

511
var (
6-
ErrValueIsNull = errors.New("value is nil")
7-
ErrValueIsUnknown = errors.New("value is unknown")
8-
ErrValueIsEmpty = errors.New("value is empty")
12+
ErrValueIsNull = errors.New("value is nil")
13+
ErrValueIsUnknown = errors.New("value is unknown")
14+
ErrValueIsEmpty = errors.New("value is empty")
15+
ErrValueTypeUnhandled = errors.New("value type is unhandled, this usually means this package is out of date with the upstream provider framework")
916
)
1017

1118
func IsValueIsNullError(err error) bool {
12-
for err != nil {
13-
if errors.Is(err, ErrValueIsNull) {
14-
return true
15-
}
16-
err = errors.Unwrap(err)
17-
}
18-
return false
19+
return util.MatchError(err, ErrValueIsNull)
1920
}
2021

2122
func IsValueIsUnknownError(err error) bool {
22-
for err != nil {
23-
if errors.Is(err, ErrValueIsUnknown) {
24-
return true
25-
}
26-
err = errors.Unwrap(err)
27-
}
28-
return false
23+
return util.MatchError(err, ErrValueIsUnknown)
2924
}
3025

3126
func IsValueIsEmptyError(err error) bool {
32-
for err != nil {
33-
if errors.Is(err, ErrValueIsEmpty) {
34-
return true
35-
}
36-
err = errors.Unwrap(err)
37-
}
38-
return false
27+
return util.MatchError(err, ErrValueIsEmpty)
28+
}
29+
30+
func ValueTypeUnhandledError(scope string, av attr.Value) error {
31+
return fmt.Errorf("%w: scope=%q; type=%T", ErrValueTypeUnhandled, scope, av)
32+
}
33+
34+
func IsValueTypeUnhandledError(err error) bool {
35+
return util.MatchError(err, ErrValueTypeUnhandled)
3936
}

conv/go.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ func GoNumberToString(num interface{}) string {
2121
return num.(*big.Float).String()
2222

2323
default:
24-
panic(fmt.Sprintf("no case for type %T", num))
24+
return fmt.Sprintf("%T", num)
2525
}
2626
}

conv/values.go

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import (
99
"github.com/hashicorp/terraform-plugin-framework/types"
1010
)
1111

12-
type TFTypeValueType uint8
13-
1412
// ValueToBoolType ensures we have a types.Bool literal
1513
func ValueToBoolType(v attr.Value) types.Bool {
1614
if vb, ok := v.(types.Bool); ok {
@@ -128,70 +126,69 @@ func ValueToStringType(v attr.Value) types.String {
128126
//
129127
// A 'nil' response from this function means the attribute's value was defined to a non-"empty" value at runtime. See
130128
// function body for a particular type if you're interested in what "empty" means.
131-
func TestAttributeValueState(v attr.Value) error {
129+
func TestAttributeValueState(av attr.Value) error {
132130
var (
133131
undefined bool
134132
null bool
135133
empty bool
136134
)
137135

138-
// first, check for unknown and null
139-
switch v.(type) {
136+
switch av.(type) {
137+
// bool values cannot be "empty"
140138
case types.Bool, *types.Bool:
141-
tv := ValueToBoolType(v)
139+
tv := ValueToBoolType(av)
142140
undefined = tv.Unknown
143141
null = tv.Null
144-
// bool values cannot be "empty"
145142

143+
// float values cannot be "empty"
146144
case types.Float64, *types.Float64:
147-
tv := ValueToFloat64Type(v)
145+
tv := ValueToFloat64Type(av)
148146
undefined = tv.Unknown
149147
null = tv.Null
150-
// float values cannot be "empty"
151148

149+
// int values cannot be "empty"
152150
case types.Int64, *types.Int64:
153-
tv := ValueToInt64Type(v)
151+
tv := ValueToInt64Type(av)
154152
undefined = tv.Unknown
155153
null = tv.Null
156-
// int values cannot be "empty"
157154

158155
case types.List, *types.List:
159-
tv := ValueToListType(v)
156+
tv := ValueToListType(av)
160157
undefined = tv.Unknown
161158
null = tv.Null
162-
empty = AttributeValueLength(v) == 0
159+
empty = AttributeValueLength(av) == 0
163160

164161
case types.Map, *types.Map:
165-
tv := ValueToMapType(v)
162+
tv := ValueToMapType(av)
166163
undefined = tv.Unknown
167164
null = tv.Null
168-
empty = AttributeValueLength(v) == 0
165+
empty = AttributeValueLength(av) == 0
169166

170167
case types.Number, *types.Number:
171-
tv := ValueToNumberType(v)
168+
tv := ValueToNumberType(av)
172169
undefined = tv.Unknown
173170
null = tv.Null
174171

172+
// todo: implement object "emptiness" check
175173
case types.Object, *types.Object:
176-
tv := ValueToObjectType(v)
174+
tv := ValueToObjectType(av)
177175
undefined = tv.Unknown
178176
null = tv.Null
179-
// todo: implement object "emptiness" check
180177

181178
case types.Set, *types.Set:
182-
tv := ValueToSetType(v)
179+
tv := ValueToSetType(av)
183180
undefined = tv.Unknown
184181
null = tv.Null
185-
empty = AttributeValueLength(v) > 0
182+
empty = AttributeValueLength(av) > 0
186183

187184
case types.String, *types.String:
188-
tv := ValueToStringType(v)
185+
tv := ValueToStringType(av)
189186
undefined = tv.Unknown
190187
null = tv.Null
191-
empty = StringValueToString(v) == ""
188+
empty = StringValueToString(av) == ""
192189

193190
default:
194-
panic(fmt.Sprintf("no way to test for valued state for types %T", v))
191+
return ValueTypeUnhandledError("length_check", av)
195192
}
196193

197194
if undefined {
@@ -257,7 +254,22 @@ func AttributeValueToString(v attr.Value) string {
257254
return StringValueToString(v)
258255

259256
default:
260-
panic(fmt.Sprintf("no stringer func registered for type %T", v))
257+
return fmt.Sprintf("%T", v)
258+
}
259+
}
260+
261+
// AttributeValueToStrings attempts to convert the provided attr.Value into a slice of strings.
262+
func AttributeValueToStrings(av attr.Value) []string {
263+
switch av.(type) {
264+
case types.List, *types.List:
265+
return StringListToStrings(av)
266+
267+
case types.Set, *types.Set:
268+
return StringSetToStrings(av)
269+
default:
270+
out := make([]string, 0)
271+
out = append(out, ValueToStringType(av).Value)
272+
return out
261273
}
262274
}
263275

@@ -341,6 +353,13 @@ func NumberValueToInt64(v attr.Value) (int64, big.Accuracy) {
341353
return vt.Value.Int64()
342354
}
343355

356+
// NumberValueToInt accepts either a types.Number or *types.Number, returning an int representation of the *big.Float
357+
// value within. It will return [0, big.Exact] if the value was not set
358+
func NumberValueToInt(v attr.Value) (int, big.Accuracy) {
359+
iv, acc := NumberValueToInt64(v)
360+
return int(iv), acc
361+
}
362+
344363
// NumberValueToFloat64 accepts either a types.Number or *types.Number, returning a float64 representation of the
345364
// *big.Float value within. It will return [0.0, big.Exact] of the value was not set.
346365
func NumberValueToFloat64(v attr.Value) (float64, big.Accuracy) {
@@ -410,13 +429,35 @@ func StringValueToStringPtr(v attr.Value) *string {
410429
return vPtr
411430
}
412431

432+
// StringListToStrings accepts an instance of either types.List or *types.List where ElemType MUST be types.StringType,
433+
// returning a slice of strings of the value of each element
434+
func StringListToStrings(v attr.Value) []string {
435+
vt := ValueToListType(v)
436+
out := make([]string, len(vt.Elems))
437+
for i, ve := range vt.Elems {
438+
out[i] = StringValueToString(ve)
439+
}
440+
return out
441+
}
442+
443+
// StringSetToStrings accepts an instance of either types.Set or *types.Set where ElemType MUST be types.StringType,
444+
// returning a slice of strings of the value of each element
445+
func StringSetToStrings(v attr.Value) []string {
446+
vt := ValueToSetType(v)
447+
out := make([]string, len(vt.Elems))
448+
for i, ve := range vt.Elems {
449+
out[i] = StringValueToString(ve)
450+
}
451+
return out
452+
}
453+
413454
// Int64ListToInts accepts an instance of either types.List or *types.List where ElemType MUST be types.Int64Type,
414455
// returning a slice of ints of the value of each element.
415456
func Int64ListToInts(v attr.Value) []int {
416457
vt := ValueToListType(v)
417458
out := make([]int, len(vt.Elems))
418459
for i, ve := range vt.Elems {
419-
out[i] = Int64ValueToInt(ve.(types.Int64))
460+
out[i] = Int64ValueToInt(ve)
420461
}
421462
return out
422463
}
@@ -427,7 +468,31 @@ func Int64SetToInts(v attr.Value) []int {
427468
vt := ValueToSetType(v)
428469
out := make([]int, len(vt.Elems))
429470
for i, ve := range vt.Elems {
430-
out[i] = Int64ValueToInt(ve.(types.Int64))
471+
out[i] = Int64ValueToInt(ve)
472+
}
473+
return out
474+
}
475+
476+
// NumberListToInts accepts either an instance of types.List or *types.List where ElemType MUST be types.NumberType
477+
// returning a slice of ints of the value of each element
478+
func NumberListToInts(v attr.Value) []int {
479+
vt := ValueToListType(v)
480+
out := make([]int, len(vt.Elems))
481+
for i, ve := range vt.Elems {
482+
iv, _ := NumberValueToInt(ve)
483+
out[i] = iv
484+
}
485+
return out
486+
}
487+
488+
// NumberSetToInts accepts either an instance of types.Set or *types.Set where ElemType MUST be types.NumberType
489+
// returning a slice of ints of the value of each element
490+
func NumberSetToInts(v attr.Value) []int {
491+
vt := ValueToSetType(v)
492+
out := make([]int, len(vt.Elems))
493+
for i, ve := range vt.Elems {
494+
iv, _ := NumberValueToInt(ve)
495+
out[i] = iv
431496
}
432497
return out
433498
}
@@ -451,7 +516,7 @@ func AttributeValueToFloat64(v attr.Value) (float64, big.Accuracy, error) {
451516
return f, big.Exact, err
452517

453518
default:
454-
panic(fmt.Sprintf("unable to determine float64 value of type %T", v))
519+
return 0, 0, ValueTypeUnhandledError("attr_to_float64", v)
455520
}
456521
}
457522

@@ -480,7 +545,7 @@ func AttributeValueToInt64(v attr.Value) (int64, big.Accuracy, error) {
480545
return int64(i), big.Exact, err
481546

482547
default:
483-
panic(fmt.Sprintf("unable to determine int64 value of type %T", v))
548+
return 0, 0, ValueTypeUnhandledError("attr_to_int64", v)
484549
}
485550
}
486551

@@ -502,7 +567,7 @@ func AttributeValueToBigFloat(v attr.Value) (*big.Float, error) {
502567
return bf, err
503568

504569
default:
505-
panic(fmt.Sprintf("unable to parse type %T into *big.Float", v))
570+
return nil, ValueTypeUnhandledError("attr_to_bigfloat", v)
506571
}
507572
}
508573

0 commit comments

Comments
 (0)