mirror of https://github.com/hashicorp/terraform
This adds an ImpliedType to the msgpack package, which serves the same purpose as ImpliedType in the json package.pull/19086/head
parent
c28ce02f2a
commit
6dcaafa6ba
@ -0,0 +1,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
|
||||
}
|
||||
Loading…
Reference in new issue