| 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 |
| } |