1package msgpack
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7
8	"github.com/vmihailenco/msgpack/v4"
9	msgpackcodes "github.com/vmihailenco/msgpack/v4/codes"
10	"github.com/zclconf/go-cty/cty"
11)
12
13// ImpliedType returns the cty Type implied by the structure of the given
14// msgpack-compliant buffer. This function implements the default type mapping
15// behavior used when decoding arbitrary msgpack without explicit cty Type
16// information.
17//
18// The rules are as follows:
19//
20// msgpack strings, numbers and bools map to their equivalent primitive type in
21// cty.
22//
23// msgpack maps become cty object types, with the attributes defined by the
24// map keys and the types of their values.
25//
26// msgpack arrays become cty tuple types, with the elements defined by the
27// types of the array members.
28//
29// Any nulls are typed as DynamicPseudoType, so callers of this function
30// must be prepared to deal with this. Callers that do not wish to deal with
31// dynamic typing should not use this function and should instead describe
32// their required types explicitly with a cty.Type instance when decoding.
33//
34// Any unknown values are similarly typed as DynamicPseudoType, because these
35// do not carry type information on the wire.
36//
37// Any parse errors will be returned as an error, and the type will be the
38// invalid value cty.NilType.
39func ImpliedType(buf []byte) (cty.Type, error) {
40	r := bytes.NewReader(buf)
41	dec := msgpack.NewDecoder(r)
42
43	ty, err := impliedType(dec)
44	if err != nil {
45		return cty.NilType, err
46	}
47
48	// We must now be at the end of the buffer
49	err = dec.Skip()
50	if err != io.EOF {
51		return ty, fmt.Errorf("extra bytes after msgpack value")
52	}
53
54	return ty, nil
55}
56
57func impliedType(dec *msgpack.Decoder) (cty.Type, error) {
58	// If this function returns with a nil error then it must have already
59	// consumed the next value from the decoder, since when called recursively
60	// the caller will be expecting to find a following value here.
61
62	code, err := dec.PeekCode()
63	if err != nil {
64		return cty.NilType, err
65	}
66
67	switch {
68
69	case code == msgpackcodes.Nil || msgpackcodes.IsExt(code):
70		err := dec.Skip()
71		return cty.DynamicPseudoType, err
72
73	case code == msgpackcodes.True || code == msgpackcodes.False:
74		_, err := dec.DecodeBool()
75		return cty.Bool, err
76
77	case msgpackcodes.IsFixedNum(code):
78		_, err := dec.DecodeInt64()
79		return cty.Number, err
80
81	case code == msgpackcodes.Int8 || code == msgpackcodes.Int16 || code == msgpackcodes.Int32 || code == msgpackcodes.Int64:
82		_, err := dec.DecodeInt64()
83		return cty.Number, err
84
85	case code == msgpackcodes.Uint8 || code == msgpackcodes.Uint16 || code == msgpackcodes.Uint32 || code == msgpackcodes.Uint64:
86		_, err := dec.DecodeUint64()
87		return cty.Number, err
88
89	case code == msgpackcodes.Float || code == msgpackcodes.Double:
90		_, err := dec.DecodeFloat64()
91		return cty.Number, err
92
93	case msgpackcodes.IsString(code):
94		_, err := dec.DecodeString()
95		return cty.String, err
96
97	case msgpackcodes.IsFixedMap(code) || code == msgpackcodes.Map16 || code == msgpackcodes.Map32:
98		return impliedObjectType(dec)
99
100	case msgpackcodes.IsFixedArray(code) || code == msgpackcodes.Array16 || code == msgpackcodes.Array32:
101		return impliedTupleType(dec)
102
103	default:
104		return cty.NilType, fmt.Errorf("unsupported msgpack code %#v", code)
105	}
106}
107
108func impliedObjectType(dec *msgpack.Decoder) (cty.Type, error) {
109	// If we get in here then we've already peeked the next code and know
110	// it's some sort of map.
111	l, err := dec.DecodeMapLen()
112	if err != nil {
113		return cty.DynamicPseudoType, nil
114	}
115
116	var atys map[string]cty.Type
117
118	for i := 0; i < l; i++ {
119		// Read the map key first. We require maps to be strings, but msgpack
120		// doesn't so we're prepared to error here if not.
121		k, err := dec.DecodeString()
122		if err != nil {
123			return cty.DynamicPseudoType, err
124		}
125
126		aty, err := impliedType(dec)
127		if err != nil {
128			return cty.DynamicPseudoType, err
129		}
130
131		if atys == nil {
132			atys = make(map[string]cty.Type)
133		}
134		atys[k] = aty
135	}
136
137	if len(atys) == 0 {
138		return cty.EmptyObject, nil
139	}
140
141	return cty.Object(atys), nil
142}
143
144func impliedTupleType(dec *msgpack.Decoder) (cty.Type, error) {
145	// If we get in here then we've already peeked the next code and know
146	// it's some sort of array.
147	l, err := dec.DecodeArrayLen()
148	if err != nil {
149		return cty.DynamicPseudoType, nil
150	}
151
152	if l == 0 {
153		return cty.EmptyTuple, nil
154	}
155
156	etys := make([]cty.Type, l)
157
158	for i := 0; i < l; i++ {
159		ety, err := impliedType(dec)
160		if err != nil {
161			return cty.DynamicPseudoType, err
162		}
163		etys[i] = ety
164	}
165
166	return cty.Tuple(etys), nil
167}
168