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