blob: 87b096ca4a2787c3a59765d93339d6dafd5c4235 [file] [log] [blame]
package msgpack
import (
"bytes"
"math/big"
"sort"
"github.com/vmihailenco/msgpack"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
)
// Marshal produces a msgpack serialization of the given value that
// can be decoded into the given type later using Unmarshal.
//
// The given value must conform to the given type, or an error will
// be returned.
func Marshal(val cty.Value, ty cty.Type) ([]byte, error) {
errs := val.Type().TestConformance(ty)
if errs != nil {
// Attempt a conversion
var err error
val, err = convert.Convert(val, ty)
if err != nil {
return nil, err
}
}
// From this point onward, val can be assumed to be conforming to t.
var path cty.Path
var buf bytes.Buffer
enc := msgpack.NewEncoder(&buf)
err := marshal(val, ty, path, enc)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func marshal(val cty.Value, ty cty.Type, path cty.Path, enc *msgpack.Encoder) error {
// If we're going to decode as DynamicPseudoType then we need to save
// dynamic type information to recover the real type.
if ty == cty.DynamicPseudoType && val.Type() != cty.DynamicPseudoType {
return marshalDynamic(val, path, enc)
}
if !val.IsKnown() {
err := enc.Encode(unknownVal)
if err != nil {
return path.NewError(err)
}
return nil
}
if val.IsNull() {
err := enc.EncodeNil()
if err != nil {
return path.NewError(err)
}
return nil
}
// The caller should've guaranteed that the given val is conformant with
// the given type ty, so we'll proceed under that assumption here.
switch {
case ty.IsPrimitiveType():
switch ty {
case cty.String:
err := enc.EncodeString(val.AsString())
if err != nil {
return path.NewError(err)
}
return nil
case cty.Number:
var err error
switch {
case val.RawEquals(cty.PositiveInfinity):
err = enc.EncodeFloat64(positiveInfinity)
case val.RawEquals(cty.NegativeInfinity):
err = enc.EncodeFloat64(negativeInfinity)
default:
bf := val.AsBigFloat()
if iv, acc := bf.Int64(); acc == big.Exact {
err = enc.EncodeInt(iv)
} else if fv, acc := bf.Float64(); acc == big.Exact {
err = enc.EncodeFloat64(fv)
} else {
err = enc.EncodeString(bf.Text('f', -1))
}
}
if err != nil {
return path.NewError(err)
}
return nil
case cty.Bool:
err := enc.EncodeBool(val.True())
if err != nil {
return path.NewError(err)
}
return nil
default:
panic("unsupported primitive type")
}
case ty.IsListType(), ty.IsSetType():
enc.EncodeArrayLen(val.LengthInt())
ety := ty.ElementType()
it := val.ElementIterator()
path := append(path, nil) // local override of 'path' with extra element
for it.Next() {
ek, ev := it.Element()
path[len(path)-1] = cty.IndexStep{
Key: ek,
}
err := marshal(ev, ety, path, enc)
if err != nil {
return err
}
}
return nil
case ty.IsMapType():
enc.EncodeMapLen(val.LengthInt())
ety := ty.ElementType()
it := val.ElementIterator()
path := append(path, nil) // local override of 'path' with extra element
for it.Next() {
ek, ev := it.Element()
path[len(path)-1] = cty.IndexStep{
Key: ek,
}
var err error
err = marshal(ek, ek.Type(), path, enc)
if err != nil {
return err
}
err = marshal(ev, ety, path, enc)
if err != nil {
return err
}
}
return nil
case ty.IsTupleType():
etys := ty.TupleElementTypes()
it := val.ElementIterator()
path := append(path, nil) // local override of 'path' with extra element
i := 0
enc.EncodeArrayLen(len(etys))
for it.Next() {
ety := etys[i]
ek, ev := it.Element()
path[len(path)-1] = cty.IndexStep{
Key: ek,
}
err := marshal(ev, ety, path, enc)
if err != nil {
return err
}
i++
}
return nil
case ty.IsObjectType():
atys := ty.AttributeTypes()
path := append(path, nil) // local override of 'path' with extra element
names := make([]string, 0, len(atys))
for k := range atys {
names = append(names, k)
}
sort.Strings(names)
enc.EncodeMapLen(len(names))
for _, k := range names {
aty := atys[k]
av := val.GetAttr(k)
path[len(path)-1] = cty.GetAttrStep{
Name: k,
}
var err error
err = marshal(cty.StringVal(k), cty.String, path, enc)
if err != nil {
return err
}
err = marshal(av, aty, path, enc)
if err != nil {
return err
}
}
return nil
case ty.IsCapsuleType():
return path.NewErrorf("capsule types not supported for msgpack encoding")
default:
// should never happen
return path.NewErrorf("cannot msgpack-serialize %s", ty.FriendlyName())
}
}
// marshalDynamic adds an extra wrapping object containing dynamic type
// information for the given value.
func marshalDynamic(val cty.Value, path cty.Path, enc *msgpack.Encoder) error {
dv := dynamicVal{
Value: val,
Path: path,
}
return enc.Encode(&dv)
}