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 }