Skip to content

Commit 1e2a00d

Browse files
committed
skeleton implementation
1 parent d6d73b9 commit 1e2a00d

File tree

4 files changed

+225
-10
lines changed

4 files changed

+225
-10
lines changed

tftypes/refinement/nullness.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package refinement
2+
3+
type nullness struct {
4+
Value bool
5+
}
6+
7+
func (n nullness) Equal(Refinement) bool {
8+
return false
9+
}
10+
11+
func (n nullness) String() string {
12+
return "todo - nullness"
13+
}
14+
15+
func (n nullness) unimplementable() {}
16+
17+
func Nullness(value bool) Refinement {
18+
return nullness{
19+
Value: value,
20+
}
21+
}

tftypes/refinement/refinement.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package refinement
2+
3+
type Key int64
4+
5+
func (k Key) String() string {
6+
return "todo"
7+
}
8+
9+
const (
10+
KeyNullness = Key(1)
11+
// KeyStringPrefix = Key(2)
12+
// KeyNumberLowerBound = Key(3)
13+
// KeyNumberUpperBound = Key(4)
14+
// KeyCollectionLengthLowerBound = Key(5)
15+
// KeyCollectionLengthUpperBound = Key(6)
16+
)
17+
18+
type Refinement interface {
19+
Equal(Refinement) bool
20+
String() string
21+
unimplementable() // prevent external implementations
22+
}
23+
24+
type Refinements map[Key]Refinement
25+
26+
func (r Refinements) Equal(o Refinements) bool {
27+
return false
28+
}
29+
func (r Refinements) String() string {
30+
return "todo"
31+
}

tftypes/value.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"strconv"
1212
"strings"
1313

14+
"github.com/hashicorp/terraform-plugin-go/tftypes/refinement"
1415
msgpack "github.com/vmihailenco/msgpack/v5"
1516
)
1617

@@ -44,6 +45,8 @@ type ValueCreator interface {
4445
type Value struct {
4546
typ Type
4647
value interface{}
48+
49+
refinements refinement.Refinements //nolint
4750
}
4851

4952
func (val Value) String() string {
@@ -57,6 +60,8 @@ func (val Value) String() string {
5760
if val.IsNull() {
5861
return typ.String() + "<null>"
5962
}
63+
64+
// TODO: print refinements
6065
if !val.IsKnown() {
6166
return typ.String() + "<unknown>"
6267
}
@@ -221,6 +226,7 @@ func (val Value) Equal(o Value) bool {
221226
if !val.Type().Equal(o.Type()) {
222227
return false
223228
}
229+
// TODO: compare refinements
224230
deepEqual, err := val.deepEqual(o)
225231
if err != nil {
226232
return false
@@ -592,3 +598,19 @@ func (val Value) MarshalMsgPack(t Type) ([]byte, error) {
592598
func unexpectedValueTypeError(p *AttributePath, expected, got interface{}, typ Type) error {
593599
return p.NewErrorf("unexpected value type %T, %s values must be of type %T", got, typ, expected)
594600
}
601+
602+
// TODO: return error?
603+
func (val Value) Refine(refinements refinement.Refinements) Value {
604+
newVal := val.Copy()
605+
606+
if len(refinements) >= 0 {
607+
newVal.refinements = refinements
608+
}
609+
610+
return newVal
611+
}
612+
613+
func (val Value) Refinements() refinement.Refinements {
614+
valCopy := val.Copy()
615+
return valCopy.refinements
616+
}

tftypes/value_msgpack.go

Lines changed: 151 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@ package tftypes
66
import (
77
"bytes"
88
"fmt"
9+
"io"
910
"math"
1011
"math/big"
1112
"sort"
1213

14+
"github.com/hashicorp/terraform-plugin-go/tftypes/refinement"
1315
msgpack "github.com/vmihailenco/msgpack/v5"
1416
msgpackCodes "github.com/vmihailenco/msgpack/v5/msgpcode"
1517
)
1618

19+
// https://github.com/zclconf/go-cty/blob/main/cty/msgpack/unknown.go#L32
20+
const unknownWithRefinementsExt = 0x0c
21+
1722
type msgPackUnknownType struct{}
1823

1924
var msgPackUnknownVal = msgPackUnknownType{}
@@ -43,11 +48,7 @@ func msgpackUnmarshal(dec *msgpack.Decoder, typ Type, path *AttributePath) (Valu
4348
}
4449
if msgpackCodes.IsExt(peek) {
4550
// as with go-cty, assume all extensions are unknown values
46-
err := dec.Skip()
47-
if err != nil {
48-
return Value{}, path.NewErrorf("error skipping extension byte: %w", err)
49-
}
50-
return NewValue(typ, UnknownValue), nil
51+
return msgpackUnmarshalUnknown(dec, typ, path)
5152
}
5253
if typ.Is(DynamicPseudoType) {
5354
return msgpackUnmarshalDynamic(dec, path)
@@ -344,18 +345,99 @@ func msgpackUnmarshalDynamic(dec *msgpack.Decoder, path *AttributePath) (Value,
344345
}
345346
return msgpackUnmarshal(dec, typ, path)
346347
}
348+
func msgpackUnmarshalUnknown(dec *msgpack.Decoder, typ Type, path *AttributePath) (Value, error) {
349+
// The value is unknown, but we will check the extension header to see if any
350+
// type-specific refinements are applied to the value.
351+
typeCode, extLen, err := dec.DecodeExtHeader()
352+
if err != nil {
353+
return Value{}, path.NewErrorf("error decoding extension header: %w", err)
354+
}
355+
356+
if extLen <= 1 {
357+
// If the extension is zero or one-length, this is a wholly unknown value with no
358+
// refinements.
359+
360+
// TODO: Previous implementations always skipped the body, should we preserve that?
361+
if extLen > 0 {
362+
// Skip the body
363+
err = dec.Skip()
364+
if err != nil {
365+
return Value{}, path.NewErrorf("error skipping extension byte: %w", err)
366+
}
367+
}
368+
369+
return NewValue(typ, UnknownValue), nil
370+
}
371+
372+
// Check if the extension is the designated cty unknown refinement code
373+
if typeCode != unknownWithRefinementsExt {
374+
// TODO: cleanup error
375+
return Value{}, path.NewErrorf("unsupported extension type")
376+
}
377+
378+
if extLen > 1024 {
379+
// A refinement description greater than 1 kiB is unreasonable and
380+
// might be an abusive attempt to allocate large amounts of memory
381+
// in a system consuming this input.
382+
return Value{}, path.NewErrorf("unsupported extension type")
383+
}
384+
385+
body := make([]byte, extLen)
386+
_, err = io.ReadAtLeast(dec.Buffered(), body, len(body))
387+
if err != nil {
388+
return Value{}, path.NewErrorf("failed to read msgpack extension body: %s", err)
389+
}
390+
391+
rfnDec := msgpack.NewDecoder(bytes.NewReader(body))
392+
entryCount, err := rfnDec.DecodeMapLen()
393+
if err != nil {
394+
return Value{}, path.NewErrorf("failed to decode msgpack extension body: not a map")
395+
}
396+
397+
// Ignore all refinements for DynamicPseudoType for now, since go-cty also ignores this.
398+
//
399+
// We know that's invalid today but we might find a way to support it in the future and
400+
// if so will want to introduce that in a backward-compatible way.
401+
if typ.Is(DynamicPseudoType) {
402+
return NewValue(typ, UnknownValue), nil
403+
}
404+
405+
newVal := NewValue(typ, UnknownValue)
406+
newRefinements := make(refinement.Refinements, 0)
407+
408+
for i := 0; i < entryCount; i++ {
409+
keyCode, err := rfnDec.DecodeInt64()
410+
if err != nil {
411+
return Value{}, path.NewErrorf("failed to decode msgpack extension body: non-integer key in map")
412+
}
413+
414+
switch keyCode := refinement.Key(keyCode); keyCode {
415+
case refinement.KeyNullness:
416+
isNull, err := rfnDec.DecodeBool()
417+
if err != nil {
418+
return Value{}, path.NewErrorf("failed to decode msgpack extension body: null refinement is not boolean")
419+
}
420+
// The presence of this key means we're refining the null-ness one
421+
// way or another. If nullness is unknown then this key should not
422+
// be present at all.
423+
newRefinements[keyCode] = refinement.Nullness(isNull)
424+
default:
425+
// 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
427+
continue
428+
}
429+
}
430+
431+
return newVal.Refine(newRefinements), nil
432+
}
347433

348434
func marshalMsgPack(val Value, typ Type, p *AttributePath, enc *msgpack.Encoder) error {
349435
if typ.Is(DynamicPseudoType) && !val.Type().Is(DynamicPseudoType) {
350436
return marshalMsgPackDynamicPseudoType(val, typ, p, enc)
351437

352438
}
353439
if !val.IsKnown() {
354-
err := enc.Encode(msgPackUnknownVal)
355-
if err != nil {
356-
return p.NewErrorf("error encoding UnknownValue: %w", err)
357-
}
358-
return nil
440+
return marshalUnknownValue(val, typ, p, enc)
359441
}
360442
if val.IsNull() {
361443
err := enc.EncodeNil()
@@ -390,6 +472,65 @@ func marshalMsgPack(val Value, typ Type, p *AttributePath, enc *msgpack.Encoder)
390472
return fmt.Errorf("unknown type %s", typ)
391473
}
392474

475+
func marshalUnknownValue(val Value, typ Type, p *AttributePath, enc *msgpack.Encoder) error {
476+
// Use the representation of a wholly unknown value if there are no refinements. DynamicPseudoType
477+
// cannot have refinements, so it will also use the wholly unknown value.
478+
if len(val.refinements) == 0 || typ.Is(DynamicPseudoType) {
479+
err := enc.Encode(msgPackUnknownVal)
480+
if err != nil {
481+
return p.NewErrorf("error encoding UnknownValue: %w", err)
482+
}
483+
return nil
484+
}
485+
486+
var refnBuf bytes.Buffer
487+
refnEnc := msgpack.NewEncoder(&refnBuf)
488+
mapLen := 0
489+
490+
// TODO: Should the refinement interface define the encoding?
491+
for kind := range val.refinements {
492+
switch kind {
493+
case refinement.KeyNullness:
494+
mapLen++
495+
refnEnc.EncodeInt(int64(kind)) //nolint
496+
// An value that is definitely null cannot be unknown
497+
refnEnc.EncodeBool(false) //nolint
498+
default:
499+
continue
500+
}
501+
}
502+
503+
if mapLen == 0 {
504+
// Didn't find any refinements we know how to encode, use the wholly unknown value.
505+
err := enc.Encode(msgPackUnknownVal)
506+
if err != nil {
507+
return p.NewErrorf("error encoding UnknownValue: %w", err)
508+
}
509+
return nil
510+
}
511+
512+
// If we have at least one refinement to encode then we'll use the new
513+
// representation of unknown values where refinement information is in the
514+
// extension payload.
515+
var lenBuf bytes.Buffer
516+
lenEnc := msgpack.NewEncoder(&lenBuf)
517+
lenEnc.EncodeMapLen(mapLen) //nolint
518+
519+
err := enc.EncodeExtHeader(unknownWithRefinementsExt, lenBuf.Len()+refnBuf.Len())
520+
if err != nil {
521+
return p.NewErrorf("failed to write unknown value: %s", err)
522+
}
523+
_, err = enc.Writer().Write(lenBuf.Bytes())
524+
if err != nil {
525+
return p.NewErrorf("failed to write unknown value: %s", err)
526+
}
527+
_, err = enc.Writer().Write(refnBuf.Bytes())
528+
if err != nil {
529+
return p.NewErrorf("failed to write unknown value: %s", err)
530+
}
531+
return nil
532+
}
533+
393534
func marshalMsgPackDynamicPseudoType(val Value, _ Type, p *AttributePath, enc *msgpack.Encoder) error {
394535
typeJSON, err := val.Type().MarshalJSON()
395536
if err != nil {

0 commit comments

Comments
 (0)