Skip to content

Commit 06636a4

Browse files
author
Paddy Carver
committed
Handle marshaling of types and RawValues.
Handle the encoding of types and RawValues to the wire protocol. Types are always encoded as JSON. RawValues are always encoded as msgpack.
1 parent 22eb03c commit 06636a4

File tree

10 files changed

+387
-29
lines changed

10 files changed

+387
-29
lines changed

tfprotov5/internal/toproto/cty.go

Lines changed: 257 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,268 @@
11
package toproto
22

33
import (
4+
"bytes"
5+
"fmt"
6+
"math/big"
7+
"strings"
8+
49
"github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5"
510
"github.com/hashicorp/terraform-plugin-go/tfprotov5/tftypes"
11+
"github.com/vmihailenco/msgpack"
612
)
713

14+
type errWithPath struct {
15+
err error
16+
path []string
17+
}
18+
19+
func (e errWithPath) Error() string {
20+
return fmt.Errorf("error marshaling %s: %w", strings.Join(e.path, "."), e.err).Error()
21+
}
22+
23+
type unknownType struct{}
24+
25+
var unknownVal = unknownType{}
26+
27+
func (u unknownType) MarshalMsgpack() ([]byte, error) {
28+
return []byte{0xd4, 0, 0}, nil
29+
}
30+
831
func Cty(in tftypes.RawValue) (tfplugin5.DynamicValue, error) {
9-
// TODO: figure out how to marshal tftypes.RawValue into DynamicValue
10-
// always populate msgpack, as per https://github.com/hashicorp/terraform/blob/doc-provider-value-wire-protocol/docs/plugin-protocol/object-wire-format.md
11-
return tfplugin5.DynamicValue{}, nil
32+
// always populate msgpack, as per
33+
// https://github.com/hashicorp/terraform/blob/doc-provider-value-wire-protocol/docs/plugin-protocol/object-wire-format.md
34+
var buf bytes.Buffer
35+
enc := msgpack.NewEncoder(&buf)
36+
err := marshal(in.Value, in.Type, nil, enc)
37+
if err != nil {
38+
return tfplugin5.DynamicValue{}, err
39+
}
40+
return tfplugin5.DynamicValue{
41+
Msgpack: buf.Bytes(),
42+
}, nil
43+
}
44+
45+
func marshal(val interface{}, typ tftypes.Type, path []string, enc *msgpack.Encoder) error {
46+
if typ.Is(tftypes.DynamicPseudoType) {
47+
dst, ok := val.(tftypes.RawValue)
48+
if !ok {
49+
return unexpectedValueTypeError(path, tftypes.RawValue{}, val, typ)
50+
}
51+
typeJSON, err := dst.Type.MarshalJSON()
52+
if err != nil {
53+
return pathError(path, "error generating JSON for type %s: %w", dst.Type, err)
54+
}
55+
err = enc.EncodeArrayLen(2)
56+
if err != nil {
57+
return pathError(path, "error encoding array length: %w", err)
58+
}
59+
err = enc.EncodeBytes(typeJSON)
60+
if err != nil {
61+
return pathError(path, "error encoding JSON type info: %w", err)
62+
}
63+
err = marshal(dst.Value, dst.Type, path, enc)
64+
if err != nil {
65+
return pathError(path, "error marshaling DynamicPseudoType value: %w", err)
66+
}
67+
return nil
68+
69+
}
70+
if val == tftypes.UnknownValue {
71+
err := enc.Encode(unknownVal)
72+
if err != nil {
73+
return pathError(path, "error encoding UnknownValue: %w", err)
74+
}
75+
return nil
76+
}
77+
if val == nil {
78+
err := enc.EncodeNil()
79+
if err != nil {
80+
return pathError(path, "error encoding null value: %w", err)
81+
}
82+
return nil
83+
}
84+
switch {
85+
case typ.Is(tftypes.String):
86+
s, ok := val.(string)
87+
if !ok {
88+
return unexpectedValueTypeError(path, s, val, typ)
89+
}
90+
err := enc.EncodeString(s)
91+
if err != nil {
92+
return pathError(path, "error encoding string value: %w", err)
93+
}
94+
return nil
95+
case typ.Is(tftypes.Number):
96+
// TODO: can we accept other types besides *big.Float?
97+
// at the very least it would be nice to have built-in handling
98+
// for Go's int/float type variations
99+
n, ok := val.(*big.Float)
100+
if !ok {
101+
return unexpectedValueTypeError(path, n, val, typ)
102+
}
103+
if iv, acc := n.Int64(); acc == big.Exact {
104+
err := enc.EncodeInt(iv)
105+
if err != nil {
106+
return pathError(path, "error encoding int value: %w", err)
107+
}
108+
} else if fv, acc := n.Float64(); acc == big.Exact {
109+
err := enc.EncodeFloat64(fv)
110+
if err != nil {
111+
return pathError(path, "error encoding float value: %w", err)
112+
}
113+
} else {
114+
err := enc.EncodeString(n.Text('f', -1))
115+
if err != nil {
116+
return pathError(path, "error encoding number string value: %w", err)
117+
}
118+
}
119+
return nil
120+
case typ.Is(tftypes.Bool):
121+
b, ok := val.(bool)
122+
if !ok {
123+
return unexpectedValueTypeError(path, b, val, typ)
124+
}
125+
err := enc.EncodeBool(b)
126+
if err != nil {
127+
return pathError(path, "error encoding bool value: %w", err)
128+
}
129+
return nil
130+
case typ.Is(tftypes.List{}):
131+
// TODO: can we make it so you don't need to pass in an []interface{}
132+
// maybe we can at least special-case the builtin primitives and tftypes.RawValue?
133+
l, ok := val.([]interface{})
134+
if !ok {
135+
return unexpectedValueTypeError(path, l, val, typ)
136+
}
137+
err := enc.EncodeArrayLen(len(l))
138+
if err != nil {
139+
return pathError(path, "error encoding list length: %w", err)
140+
}
141+
for pos, i := range l {
142+
elemPath := newPath(path, pos)
143+
err := marshal(i, typ.(tftypes.List).ElementType, elemPath, enc)
144+
if err != nil {
145+
return pathError(path, "error encoding list element: %w", err)
146+
}
147+
}
148+
return nil
149+
case typ.Is(tftypes.Set{}):
150+
// TODO: can we make it so you don't need to pass in an []interface{}
151+
// maybe we can at least special-case the builtin primitives and tftypes.RawValue?
152+
s, ok := val.([]interface{})
153+
if !ok {
154+
return unexpectedValueTypeError(path, s, val, typ)
155+
}
156+
err := enc.EncodeArrayLen(len(s))
157+
if err != nil {
158+
return pathError(path, "error encoding set length: %w", err)
159+
}
160+
for _, i := range s {
161+
elemPath := newPath(path, i)
162+
err := marshal(i, typ.(tftypes.Set).ElementType, elemPath, enc)
163+
if err != nil {
164+
return pathError(path, "error encoding set element: %w", err)
165+
}
166+
}
167+
return nil
168+
case typ.Is(tftypes.Map{}):
169+
// TODO: can we make it so you don't need to pass in a map[string]interface{}
170+
// maybe we can at least special-case the built-in primitives and tftypes.RawValue?
171+
m, ok := val.(map[string]interface{})
172+
if !ok {
173+
return unexpectedValueTypeError(path, m, val, typ)
174+
}
175+
err := enc.EncodeMapLen(len(m))
176+
if err != nil {
177+
return pathError(path, "error encoding map length: %w", err)
178+
}
179+
for k, v := range m {
180+
attrPath := newPath(path, k)
181+
err := marshal(k, tftypes.String, attrPath, enc)
182+
if err != nil {
183+
return pathError(path, "error encoding map key: %w", err)
184+
}
185+
err = marshal(v, typ.(tftypes.Map).AttributeType, attrPath, enc)
186+
if err != nil {
187+
return pathError(path, "error encoding map value: %v", err)
188+
}
189+
}
190+
return nil
191+
case typ.Is(tftypes.Tuple{}):
192+
// TODO: can we make it so you don't need to pass in an []interface{}
193+
// maybe we can at least special-case tftypes.RawValue?
194+
t, ok := val.([]interface{})
195+
if !ok {
196+
return unexpectedValueTypeError(path, t, val, typ)
197+
}
198+
types := typ.(tftypes.Tuple).ElementTypes
199+
err := enc.EncodeArrayLen(len(t))
200+
if err != nil {
201+
return pathError(path, "error encoding tuple length: %w", err)
202+
}
203+
for pos, v := range t {
204+
ty := types[pos]
205+
elementPath := newPath(path, pos)
206+
err := marshal(v, ty, elementPath, enc)
207+
if err != nil {
208+
return pathError(path, "error encoding tuple element: %w", err)
209+
}
210+
}
211+
return nil
212+
case typ.Is(tftypes.Object{}):
213+
// TODO: can we make it so you don't need to pass in a map[string]interface{}
214+
// maybe we can at least special-case tftypes.RawValue?
215+
o, ok := val.(map[string]interface{})
216+
if !ok {
217+
return unexpectedValueTypeError(path, o, val, typ)
218+
}
219+
types := typ.(tftypes.Object).AttributeTypes
220+
err := enc.EncodeMapLen(len(o))
221+
if err != nil {
222+
return pathError(path, "error encoding object length: %w", err)
223+
}
224+
for k, v := range o {
225+
ty := types[k]
226+
attrPath := newPath(path, k)
227+
err := marshal(k, ty, attrPath, enc)
228+
if err != nil {
229+
return pathError(path, "error encoding object key: %w", err)
230+
}
231+
err = marshal(v, ty, attrPath, enc)
232+
if err != nil {
233+
return pathError(path, "error encoding object value: %w", err)
234+
}
235+
}
236+
return nil
237+
}
238+
return fmt.Errorf("unknown type %s", typ)
239+
}
240+
241+
func pathError(path []string, f string, args ...interface{}) error {
242+
return errWithPath{
243+
path: path,
244+
err: fmt.Errorf(f, args...),
245+
}
246+
}
247+
248+
func unexpectedValueTypeError(path []string, expected, got interface{}, typ tftypes.Type) error {
249+
return pathError(path, "unexpected value type %T, %s values must be of type %T", got, typ, expected)
250+
}
251+
252+
func newPath(path []string, v interface{}) []string {
253+
n := make([]string, len(path)+1)
254+
copy(path, n)
255+
n[len(n)-1] = fmt.Sprintf("%v", v)
256+
return n
12257
}
13258

14-
func CtyType(in tftypes.Type) []byte {
15-
// TODO: figure out how to marshal tftypes.Type into []byte
16-
return nil
259+
func CtyType(in tftypes.Type) ([]byte, error) {
260+
switch {
261+
case in.Is(tftypes.String), in.Is(tftypes.Bool), in.Is(tftypes.Number),
262+
in.Is(tftypes.List{}), in.Is(tftypes.Map{}),
263+
in.Is(tftypes.Set{}), in.Is(tftypes.Object{}),
264+
in.Is(tftypes.Tuple{}), in.Is(tftypes.DynamicPseudoType):
265+
return in.MarshalJSON()
266+
}
267+
return nil, fmt.Errorf("unknown type %s", in)
17268
}

tfprotov5/internal/toproto/provider.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@ func GetProviderSchema_Request(in tfprotov5.GetProviderSchemaRequest) (tfplugin5
1414
func GetProviderSchema_Response(in tfprotov5.GetProviderSchemaResponse) (tfplugin5.GetProviderSchema_Response, error) {
1515
var resp tfplugin5.GetProviderSchema_Response
1616
if in.Provider != nil {
17-
schema := Schema(*in.Provider)
17+
schema, err := Schema(*in.Provider)
18+
if err != nil {
19+
return resp, fmt.Errorf("error marshaling provider schema: %w", err)
20+
}
1821
resp.Provider = &schema
1922
}
2023
if in.ProviderMeta != nil {
21-
schema := Schema(*in.ProviderMeta)
24+
schema, err := Schema(*in.ProviderMeta)
25+
if err != nil {
26+
return resp, fmt.Errorf("error marshaling provider_meta schema: %w", err)
27+
}
2228
resp.ProviderMeta = &schema
2329
}
2430
resp.ResourceSchemas = make(map[string]*tfplugin5.Schema, len(in.ResourceSchemas))
@@ -27,7 +33,10 @@ func GetProviderSchema_Response(in tfprotov5.GetProviderSchemaResponse) (tfplugi
2733
resp.ResourceSchemas[k] = nil
2834
continue
2935
}
30-
schema := Schema(*v)
36+
schema, err := Schema(*v)
37+
if err != nil {
38+
return resp, fmt.Errorf("error marshaling resource schema for %q: %w", k, err)
39+
}
3140
resp.ResourceSchemas[k] = &schema
3241
}
3342
resp.DataSourceSchemas = make(map[string]*tfplugin5.Schema, len(in.DataSourceSchemas))
@@ -36,7 +45,10 @@ func GetProviderSchema_Response(in tfprotov5.GetProviderSchemaResponse) (tfplugi
3645
resp.DataSourceSchemas[k] = nil
3746
continue
3847
}
39-
schema := Schema(*v)
48+
schema, err := Schema(*v)
49+
if err != nil {
50+
return resp, fmt.Errorf("error marshaling data source schema for %q: %w", k, err)
51+
}
4052
resp.DataSourceSchemas[k] = &schema
4153
}
4254
diags, err := Diagnostics(in.Diagnostics)

0 commit comments

Comments
 (0)