Skip to content

Commit 55b26ba

Browse files
committed
add string prefix refinement
1 parent 1e2a00d commit 55b26ba

File tree

4 files changed

+91
-8
lines changed

4 files changed

+91
-8
lines changed

tftypes/refinement/nullness.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
package refinement
22

3+
import "github.com/vmihailenco/msgpack/v5"
4+
35
type nullness struct {
46
Value bool
57
}
68

9+
func (n nullness) Encode(enc *msgpack.Encoder) error {
10+
err := enc.EncodeInt(int64(KeyNullness))
11+
if err != nil {
12+
return err
13+
}
14+
15+
// A value that is definitely null cannot be unknown
16+
return enc.EncodeBool(false)
17+
}
18+
719
func (n nullness) Equal(Refinement) bool {
820
return false
921
}
@@ -14,6 +26,8 @@ func (n nullness) String() string {
1426

1527
func (n nullness) unimplementable() {}
1628

29+
// TODO: Should this accept a value? If a value is unknown and the it's refined to be null
30+
// then the value should be a known value of null instead.
1731
func Nullness(value bool) Refinement {
1832
return nullness{
1933
Value: value,

tftypes/refinement/refinement.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package refinement
22

3+
import "github.com/vmihailenco/msgpack/v5"
4+
35
type Key int64
46

57
func (k Key) String() string {
68
return "todo"
79
}
810

911
const (
10-
KeyNullness = Key(1)
11-
// KeyStringPrefix = Key(2)
12+
KeyNullness = Key(1)
13+
KeyStringPrefix = Key(2)
1214
// KeyNumberLowerBound = Key(3)
1315
// KeyNumberUpperBound = Key(4)
1416
// KeyCollectionLengthLowerBound = Key(5)
@@ -17,6 +19,7 @@ const (
1719

1820
type Refinement interface {
1921
Equal(Refinement) bool
22+
Encode(*msgpack.Encoder) error
2023
String() string
2124
unimplementable() // prevent external implementations
2225
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package refinement
2+
3+
import "github.com/vmihailenco/msgpack/v5"
4+
5+
type stringPrefix struct {
6+
Value string
7+
}
8+
9+
// TODO: What if the prefix is empty? Should we skip encoding? Throw an error earlier? Throw an error here?
10+
func (s stringPrefix) Encode(enc *msgpack.Encoder) error {
11+
// Matching go-cty for the max prefix length allowed here
12+
//
13+
// This ensures the total size of the refinements blob does not exceed the limit
14+
// set by the decoder (1024).
15+
maxPrefixLength := 256
16+
prefix := s.Value
17+
if len(s.Value) > maxPrefixLength {
18+
prefix = prefix[:maxPrefixLength-1]
19+
}
20+
21+
err := enc.EncodeInt(int64(KeyStringPrefix))
22+
if err != nil {
23+
return err
24+
}
25+
26+
return enc.EncodeString(prefix)
27+
}
28+
29+
func (s stringPrefix) Equal(Refinement) bool {
30+
return false
31+
}
32+
33+
func (s stringPrefix) String() string {
34+
return "todo - stringPrefix"
35+
}
36+
37+
func (s stringPrefix) unimplementable() {}
38+
39+
func StringPrefix(value string) Refinement {
40+
return stringPrefix{
41+
Value: value,
42+
}
43+
}

tftypes/value_msgpack.go

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"math"
1111
"math/big"
1212
"sort"
13+
"unicode/utf8"
1314

1415
"github.com/hashicorp/terraform-plugin-go/tftypes/refinement"
1516
msgpack "github.com/vmihailenco/msgpack/v5"
@@ -421,9 +422,22 @@ func msgpackUnmarshalUnknown(dec *msgpack.Decoder, typ Type, path *AttributePath
421422
// way or another. If nullness is unknown then this key should not
422423
// be present at all.
423424
newRefinements[keyCode] = refinement.Nullness(isNull)
425+
case refinement.KeyStringPrefix:
426+
if !typ.Is(String) {
427+
return Value{}, path.NewErrorf("failed to decode msgpack extension body: string prefix refinement for non-string type")
428+
}
429+
prefix, err := rfnDec.DecodeString()
430+
if err != nil {
431+
return Value{}, path.NewErrorf("failed to decode msgpack extension body: string prefix refinement is not string")
432+
}
433+
if !utf8.ValidString(prefix) {
434+
return Value{}, path.NewErrorf("failed to decode msgpack extension body: string prefix refinement is not valid UTF-8")
435+
}
436+
437+
newRefinements[keyCode] = refinement.StringPrefix(prefix)
424438
default:
425439
// We don't want to error here, as go-cty could introduce new refinements that we'd
426-
// want to just ignore until this code is updated
440+
// want to just ignore until this logic is updated
427441
continue
428442
}
429443
}
@@ -487,14 +501,23 @@ func marshalUnknownValue(val Value, typ Type, p *AttributePath, enc *msgpack.Enc
487501
refnEnc := msgpack.NewEncoder(&refnBuf)
488502
mapLen := 0
489503

490-
// TODO: Should the refinement interface define the encoding?
491-
for kind := range val.refinements {
504+
// TODO: Should the refinement interface be defining the encoding? Should we export the refinement implementations?
505+
for kind, refn := range val.refinements {
492506
switch kind {
493507
case refinement.KeyNullness:
508+
err := refn.Encode(refnEnc)
509+
if err != nil {
510+
return p.NewErrorf("error encoding Nullness value refinement: %w", err)
511+
}
512+
mapLen++
513+
case refinement.KeyStringPrefix:
514+
// TODO: If the prefix is empty, we shouldn't encode a refinement. This should
515+
// probably be reflected in the interface.
516+
err := refn.Encode(refnEnc)
517+
if err != nil {
518+
return p.NewErrorf("error encoding StringPrefix value refinement: %w", err)
519+
}
494520
mapLen++
495-
refnEnc.EncodeInt(int64(kind)) //nolint
496-
// An value that is definitely null cannot be unknown
497-
refnEnc.EncodeBool(false) //nolint
498521
default:
499522
continue
500523
}

0 commit comments

Comments
 (0)