Skip to content

Commit 70e3716

Browse files
author
Paddy Carver
committed
Complicate type system.
The old type system was simple, but insufficient. We erased some necessary complexity by ignoring unknown values. You could represent them as tftypes.RawValues, but the intended usage of a tftypes.RawValue is to call its Unmarshal method as close to the boundary of your provider as possible. Unfortunately, in that situation, we never really thought through what would happen with unknown values, which can't be represented in a strongly-typed Go type, they would need to be an interface or struct of some sort. So that approach wouldn't work, in practice. This new approach is to expose tfprotov5.DynamicValues from the protocol methods, and they have an Unmarshal method that gives you a tftypes.Value. The DynamicValue is just the msgpack/json bytes, the Unmarshal method takes the tftypes.Type you want them decoded into, and returns a tftypes.Value of that type if possible. tftypes.Value gained some new helper methods, most notably As(), which lets you convert from the canonical types that JSON and msgpack will store in the Value into a more friendly type, if that conversion is possible. So far, the only conversions that are possible is calling the UnmarshalTerraform5Type method if the destination has one. A future improvement would be to build in some default conversions for the builtin Go types and maybe some collection types for tftypes.Values, like []tftypes.Value or map[string]tftypes.Value. This approach gives us a more explicit interface that's decoupled from the type we store in the Value, and it allows us to massage things if we change details or introduce new types, so there's not a backwards compatibility concern. Finally, we added a tftypes.Path type, which is used to keep track of pathing information into values so we can return more useful, specific errors. It was integrated into the JSON unmarshaling and msgpack marshaling, but probably could stand to be integrated into the msgpack unmarshaling and to have an easy, handy conversion into a tfprotov5.AttributePath. That may require some minor reworking.
1 parent e860d96 commit 70e3716

19 files changed

+1439
-1095
lines changed

tfprotov5/data_source.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package tfprotov5
22

33
import (
44
"context"
5-
6-
"github.com/hashicorp/terraform-plugin-go/tfprotov5/tftypes"
75
)
86

97
type DataSourceServer interface {
@@ -13,7 +11,7 @@ type DataSourceServer interface {
1311

1412
type ValidateDataSourceConfigRequest struct {
1513
TypeName string
16-
Config *tftypes.RawValue
14+
Config *DynamicValue
1715
}
1816

1917
type ValidateDataSourceConfigResponse struct {
@@ -22,11 +20,11 @@ type ValidateDataSourceConfigResponse struct {
2220

2321
type ReadDataSourceRequest struct {
2422
TypeName string
25-
Config *tftypes.RawValue
26-
ProviderMeta *tftypes.RawValue
23+
Config *DynamicValue
24+
ProviderMeta *DynamicValue
2725
}
2826

2927
type ReadDataSourceResponse struct {
30-
State *tftypes.RawValue
28+
State *DynamicValue
3129
Diagnostics []*Diagnostic
3230
}

tfprotov5/dynamic_value.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package tfprotov5
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
7+
"github.com/hashicorp/terraform-plugin-go/tfprotov5/tftypes"
8+
"github.com/vmihailenco/msgpack"
9+
)
10+
11+
var ErrUnknownDynamicValueType = errors.New("DynamicValue had no JSON or msgpack data set")
12+
13+
// DynamicValue represents a nested encoding value that came from the protocol.
14+
// The only way providers should ever interact with it is by calling its
15+
// `Unmarshal` method to retrive a `tftypes.Value`. Although the type system
16+
// allows for other interactions, they are explicitly not supported, and will
17+
// not be considered when evaluation for breaking changes. Treat this type as
18+
// an opaque value, and *only* call its `Unmarshal` method.
19+
type DynamicValue struct {
20+
MsgPack []byte
21+
JSON []byte
22+
}
23+
24+
// Unmarshal returns a tftypes.Value that represents the information contained
25+
// in the DynamicValue in an easy-to-interact-with way. It is the main purpose
26+
// of the DynamicValue type, and is how provider developers should obtain
27+
// config, state, and other values from the protocol.
28+
//
29+
// Pass in the type you want the value to be interpreted as. Terraform's type
30+
// system encodes in a lossy manner, meaning the type information is not
31+
// preserved losslessly when going over the wire. Sets, lists, and tuples all
32+
// look the same, as do user-specified values when the provider has a
33+
// DynamicPseudoType in its schema. Objects and maps all look the same, as
34+
// well, as do PseudoDynamicType values sometimes. Fortunately, the provider
35+
// should already know the type; it should be the type of the schema, or
36+
// PseudoDynamicType if that's what's in the schema. `Unmarshal` will then
37+
// parse the value as though it belongs to that type, if possible, and return a
38+
// tftypes.Value with the appropriate information. If the data can't be
39+
// interpreted as that type, an error will be returned saying so. In these
40+
// cases, double check to make sure the schema is declaring the same type being
41+
// passed into Unmarshal.
42+
//
43+
// In the event an ErrUnknownDynamicValueType is returned, one of three things
44+
// has happened:
45+
//
46+
// 1. terraform-plugin-go is out of date and out of sync with the protocol, and
47+
// an issue should be opened on its repo to get it updated.
48+
//
49+
// 2. terraform-plugin-go has a bug somewhere, and an issue should be opened on
50+
// its repo to get it fixed.
51+
//
52+
// 3. The provider or a dependency has modified the DynamicValue in an
53+
// unsupported way, and should treat it as opaque and not modify it, only call
54+
// Unmarshal.
55+
func (d DynamicValue) Unmarshal(typ tftypes.Type) (tftypes.Value, error) {
56+
if d.JSON != nil {
57+
return jsonUnmarshal(d.JSON, typ, nil)
58+
}
59+
if d.MsgPack != nil {
60+
r := bytes.NewReader(d.MsgPack)
61+
dec := msgpack.NewDecoder(r)
62+
return msgpackUnmarshal(dec, typ, nil)
63+
}
64+
return tftypes.Value{}, ErrUnknownDynamicValueType
65+
}

0 commit comments

Comments
 (0)