aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/zclconf/go-cty/cty/msgpack/type_implied.go
blob: 6f6022e4d5b91519406d42d3e7b03a502187eb63 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package msgpack

import (
	"bytes"
	"fmt"
	"io"

	"github.com/vmihailenco/msgpack"
	msgpackcodes "github.com/vmihailenco/msgpack/codes"
	"github.com/zclconf/go-cty/cty"
)

// ImpliedType returns the cty Type implied by the structure of the given
// msgpack-compliant buffer. This function implements the default type mapping
// behavior used when decoding arbitrary msgpack without explicit cty Type
// information.
//
// The rules are as follows:
//
// msgpack strings, numbers and bools map to their equivalent primitive type in
// cty.
//
// msgpack maps become cty object types, with the attributes defined by the
// map keys and the types of their values.
//
// msgpack arrays become cty tuple types, with the elements defined by the
// types of the array members.
//
// Any nulls are typed as DynamicPseudoType, so callers of this function
// must be prepared to deal with this. Callers that do not wish to deal with
// dynamic typing should not use this function and should instead describe
// their required types explicitly with a cty.Type instance when decoding.
//
// Any unknown values are similarly typed as DynamicPseudoType, because these
// do not carry type information on the wire.
//
// Any parse errors will be returned as an error, and the type will be the
// invalid value cty.NilType.
func ImpliedType(buf []byte) (cty.Type, error) {
	r := bytes.NewReader(buf)
	dec := msgpack.NewDecoder(r)

	ty, err := impliedType(dec)
	if err != nil {
		return cty.NilType, err
	}

	// We must now be at the end of the buffer
	err = dec.Skip()
	if err != io.EOF {
		return ty, fmt.Errorf("extra bytes after msgpack value")
	}

	return ty, nil
}

func impliedType(dec *msgpack.Decoder) (cty.Type, error) {
	// If this function returns with a nil error then it must have already
	// consumed the next value from the decoder, since when called recursively
	// the caller will be expecting to find a following value here.

	code, err := dec.PeekCode()
	if err != nil {
		return cty.NilType, err
	}

	switch {

	case code == msgpackcodes.Nil || msgpackcodes.IsExt(code):
		err := dec.Skip()
		return cty.DynamicPseudoType, err

	case code == msgpackcodes.True || code == msgpackcodes.False:
		_, err := dec.DecodeBool()
		return cty.Bool, err

	case msgpackcodes.IsFixedNum(code):
		_, err := dec.DecodeInt64()
		return cty.Number, err

	case code == msgpackcodes.Int8 || code == msgpackcodes.Int16 || code == msgpackcodes.Int32 || code == msgpackcodes.Int64:
		_, err := dec.DecodeInt64()
		return cty.Number, err

	case code == msgpackcodes.Uint8 || code == msgpackcodes.Uint16 || code == msgpackcodes.Uint32 || code == msgpackcodes.Uint64:
		_, err := dec.DecodeUint64()
		return cty.Number, err

	case code == msgpackcodes.Float || code == msgpackcodes.Double:
		_, err := dec.DecodeFloat64()
		return cty.Number, err

	case msgpackcodes.IsString(code):
		_, err := dec.DecodeString()
		return cty.String, err

	case msgpackcodes.IsFixedMap(code) || code == msgpackcodes.Map16 || code == msgpackcodes.Map32:
		return impliedObjectType(dec)

	case msgpackcodes.IsFixedArray(code) || code == msgpackcodes.Array16 || code == msgpackcodes.Array32:
		return impliedTupleType(dec)

	default:
		return cty.NilType, fmt.Errorf("unsupported msgpack code %#v", code)
	}
}

func impliedObjectType(dec *msgpack.Decoder) (cty.Type, error) {
	// If we get in here then we've already peeked the next code and know
	// it's some sort of map.
	l, err := dec.DecodeMapLen()
	if err != nil {
		return cty.DynamicPseudoType, nil
	}

	var atys map[string]cty.Type

	for i := 0; i < l; i++ {
		// Read the map key first. We require maps to be strings, but msgpack
		// doesn't so we're prepared to error here if not.
		k, err := dec.DecodeString()
		if err != nil {
			return cty.DynamicPseudoType, err
		}

		aty, err := impliedType(dec)
		if err != nil {
			return cty.DynamicPseudoType, err
		}

		if atys == nil {
			atys = make(map[string]cty.Type)
		}
		atys[k] = aty
	}

	if len(atys) == 0 {
		return cty.EmptyObject, nil
	}

	return cty.Object(atys), nil
}

func impliedTupleType(dec *msgpack.Decoder) (cty.Type, error) {
	// If we get in here then we've already peeked the next code and know
	// it's some sort of array.
	l, err := dec.DecodeArrayLen()
	if err != nil {
		return cty.DynamicPseudoType, nil
	}

	if l == 0 {
		return cty.EmptyTuple, nil
	}

	etys := make([]cty.Type, l)

	for i := 0; i < l; i++ {
		ety, err := impliedType(dec)
		if err != nil {
			return cty.DynamicPseudoType, err
		}
		etys[i] = ety
	}

	return cty.Tuple(etys), nil
}