package msgpack import ( "bytes" "github.com/vmihailenco/msgpack/v4" msgpackCodes "github.com/vmihailenco/msgpack/v4/codes" "github.com/zclconf/go-cty/cty" ) // Unmarshal interprets the given bytes as a msgpack-encoded cty Value of // the given type, returning the result. // // If an error is returned, the error is written with a hypothetical // end-user that wrote the msgpack file as its audience, using cty type // system concepts rather than Go type system concepts. func Unmarshal(b []byte, ty cty.Type) (cty.Value, error) { r := bytes.NewReader(b) dec := msgpack.NewDecoder(r) var path cty.Path return unmarshal(dec, ty, path) } func unmarshal(dec *msgpack.Decoder, ty cty.Type, path cty.Path) (cty.Value, error) { peek, err := dec.PeekCode() if err != nil { return cty.DynamicVal, path.NewError(err) } if msgpackCodes.IsExt(peek) { // We just assume _all_ extensions are unknown values, // since we don't have any other extensions. dec.Skip() // skip what we've peeked return cty.UnknownVal(ty), nil } if ty == cty.DynamicPseudoType { return unmarshalDynamic(dec, path) } if peek == msgpackCodes.Nil { dec.Skip() // skip what we've peeked return cty.NullVal(ty), nil } switch { case ty.IsPrimitiveType(): val, err := unmarshalPrimitive(dec, ty, path) if err != nil { return cty.NilVal, err } return val, nil case ty.IsListType(): return unmarshalList(dec, ty.ElementType(), path) case ty.IsSetType(): return unmarshalSet(dec, ty.ElementType(), path) case ty.IsMapType(): return unmarshalMap(dec, ty.ElementType(), path) case ty.IsTupleType(): return unmarshalTuple(dec, ty.TupleElementTypes(), path) case ty.IsObjectType(): return unmarshalObject(dec, ty.AttributeTypes(), path) default: return cty.NilVal, path.NewErrorf("unsupported type %s", ty.FriendlyName()) } } func unmarshalPrimitive(dec *msgpack.Decoder, ty cty.Type, path cty.Path) (cty.Value, error) { switch ty { case cty.Bool: rv, err := dec.DecodeBool() if err != nil { return cty.DynamicVal, path.NewErrorf("bool is required") } return cty.BoolVal(rv), nil case cty.Number: // Marshal will try int and float first, if the value can be // losslessly represented in these encodings, and then fall // back on a string if the number is too large or too precise. peek, err := dec.PeekCode() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } if msgpackCodes.IsFixedNum(peek) { rv, err := dec.DecodeInt64() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } return cty.NumberIntVal(rv), nil } switch peek { case msgpackCodes.Int8, msgpackCodes.Int16, msgpackCodes.Int32, msgpackCodes.Int64: rv, err := dec.DecodeInt64() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } return cty.NumberIntVal(rv), nil case msgpackCodes.Uint8, msgpackCodes.Uint16, msgpackCodes.Uint32, msgpackCodes.Uint64: rv, err := dec.DecodeUint64() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } return cty.NumberUIntVal(rv), nil case msgpackCodes.Float, msgpackCodes.Double: rv, err := dec.DecodeFloat64() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } return cty.NumberFloatVal(rv), nil default: rv, err := dec.DecodeString() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } v, err := cty.ParseNumberVal(rv) if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } return v, nil } case cty.String: rv, err := dec.DecodeString() if err != nil { return cty.DynamicVal, path.NewErrorf("string is required") } return cty.StringVal(rv), nil default: // should never happen panic("unsupported primitive type") } } func unmarshalList(dec *msgpack.Decoder, ety cty.Type, path cty.Path) (cty.Value, error) { length, err := dec.DecodeArrayLen() if err != nil { return cty.DynamicVal, path.NewErrorf("a list is required") } switch { case length < 0: return cty.NullVal(cty.List(ety)), nil case length == 0: return cty.ListValEmpty(ety), nil } vals := make([]cty.Value, 0, length) path = append(path, nil) for i := 0; i < length; i++ { path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } val, err := unmarshal(dec, ety, path) if err != nil { return cty.DynamicVal, err } vals = append(vals, val) } return cty.ListVal(vals), nil } func unmarshalSet(dec *msgpack.Decoder, ety cty.Type, path cty.Path) (cty.Value, error) { length, err := dec.DecodeArrayLen() if err != nil { return cty.DynamicVal, path.NewErrorf("a set is required") } switch { case length < 0: return cty.NullVal(cty.Set(ety)), nil case length == 0: return cty.SetValEmpty(ety), nil } vals := make([]cty.Value, 0, length) path = append(path, nil) for i := 0; i < length; i++ { path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } val, err := unmarshal(dec, ety, path) if err != nil { return cty.DynamicVal, err } vals = append(vals, val) } return cty.SetVal(vals), nil } func unmarshalMap(dec *msgpack.Decoder, ety cty.Type, path cty.Path) (cty.Value, error) { length, err := dec.DecodeMapLen() if err != nil { return cty.DynamicVal, path.NewErrorf("a map is required") } switch { case length < 0: return cty.NullVal(cty.Map(ety)), nil case length == 0: return cty.MapValEmpty(ety), nil } vals := make(map[string]cty.Value, length) path = append(path, nil) for i := 0; i < length; i++ { key, err := dec.DecodeString() if err != nil { path[:len(path)-1].NewErrorf("non-string key in map") } path[len(path)-1] = cty.IndexStep{ Key: cty.StringVal(key), } val, err := unmarshal(dec, ety, path) if err != nil { return cty.DynamicVal, err } vals[key] = val } return cty.MapVal(vals), nil } func unmarshalTuple(dec *msgpack.Decoder, etys []cty.Type, path cty.Path) (cty.Value, error) { length, err := dec.DecodeArrayLen() if err != nil { return cty.DynamicVal, path.NewErrorf("a tuple is required") } switch { case length < 0: return cty.NullVal(cty.Tuple(etys)), nil case length == 0: return cty.TupleVal(nil), nil case length != len(etys): return cty.DynamicVal, path.NewErrorf("a tuple of length %d is required", len(etys)) } vals := make([]cty.Value, 0, length) path = append(path, nil) for i := 0; i < length; i++ { path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } ety := etys[i] val, err := unmarshal(dec, ety, path) if err != nil { return cty.DynamicVal, err } vals = append(vals, val) } return cty.TupleVal(vals), nil } func unmarshalObject(dec *msgpack.Decoder, atys map[string]cty.Type, path cty.Path) (cty.Value, error) { length, err := dec.DecodeMapLen() if err != nil { return cty.DynamicVal, path.NewErrorf("an object is required") } switch { case length < 0: return cty.NullVal(cty.Object(atys)), nil case length == 0: return cty.ObjectVal(nil), nil case length != len(atys): return cty.DynamicVal, path.NewErrorf("an object with %d attributes is required (%d given)", len(atys), length) } vals := make(map[string]cty.Value, length) path = append(path, nil) for i := 0; i < length; i++ { key, err := dec.DecodeString() if err != nil { return cty.DynamicVal, path[:len(path)-1].NewErrorf("all keys must be strings") } path[len(path)-1] = cty.IndexStep{ Key: cty.StringVal(key), } aty, exists := atys[key] if !exists { return cty.DynamicVal, path.NewErrorf("unsupported attribute") } val, err := unmarshal(dec, aty, path) if err != nil { return cty.DynamicVal, err } vals[key] = val } return cty.ObjectVal(vals), nil } func unmarshalDynamic(dec *msgpack.Decoder, path cty.Path) (cty.Value, error) { length, err := dec.DecodeArrayLen() if err != nil { return cty.DynamicVal, path.NewError(err) } switch { case length == -1: return cty.NullVal(cty.DynamicPseudoType), nil case length != 2: return cty.DynamicVal, path.NewErrorf( "dynamic value array must have exactly two elements", ) } typeJSON, err := dec.DecodeBytes() if err != nil { return cty.DynamicVal, path.NewError(err) } var ty cty.Type err = (&ty).UnmarshalJSON(typeJSON) if err != nil { return cty.DynamicVal, path.NewError(err) } return unmarshal(dec, ty, path) }