1package cty
2
3import (
4	"bytes"
5	"encoding/gob"
6	"errors"
7	"fmt"
8	"math/big"
9
10	"github.com/zclconf/go-cty/cty/set"
11)
12
13// GobEncode is an implementation of the gob.GobEncoder interface, which
14// allows Values to be included in structures encoded with encoding/gob.
15//
16// Currently it is not possible to represent values of capsule types in gob,
17// because the types themselves cannot be represented.
18func (val Value) GobEncode() ([]byte, error) {
19	if val.IsMarked() {
20		return nil, errors.New("value is marked")
21	}
22
23	buf := &bytes.Buffer{}
24	enc := gob.NewEncoder(buf)
25
26	gv := gobValue{
27		Version: 0,
28		Ty:      val.ty,
29		V:       val.v,
30	}
31
32	err := enc.Encode(gv)
33	if err != nil {
34		return nil, fmt.Errorf("error encoding cty.Value: %s", err)
35	}
36
37	return buf.Bytes(), nil
38}
39
40// GobDecode is an implementation of the gob.GobDecoder interface, which
41// inverts the operation performed by GobEncode. See the documentation of
42// GobEncode for considerations when using cty.Value instances with gob.
43func (val *Value) GobDecode(buf []byte) error {
44	r := bytes.NewReader(buf)
45	dec := gob.NewDecoder(r)
46
47	var gv gobValue
48	err := dec.Decode(&gv)
49	if err != nil {
50		return fmt.Errorf("error decoding cty.Value: %s", err)
51	}
52	if gv.Version != 0 {
53		return fmt.Errorf("unsupported cty.Value encoding version %d; only 0 is supported", gv.Version)
54	}
55
56	// Because big.Float.GobEncode is implemented with a pointer reciever,
57	// gob encoding of an interface{} containing a *big.Float value does not
58	// round-trip correctly, emerging instead as a non-pointer big.Float.
59	// The rest of cty expects all number values to be represented by
60	// *big.Float, so we'll fix that up here.
61	gv.V = gobDecodeFixNumberPtr(gv.V, gv.Ty)
62
63	val.ty = gv.Ty
64	val.v = gv.V
65
66	return nil
67}
68
69// GobEncode is an implementation of the gob.GobEncoder interface, which
70// allows Types to be included in structures encoded with encoding/gob.
71//
72// Currently it is not possible to represent capsule types in gob.
73func (t Type) GobEncode() ([]byte, error) {
74	buf := &bytes.Buffer{}
75	enc := gob.NewEncoder(buf)
76
77	gt := gobType{
78		Version: 0,
79		Impl:    t.typeImpl,
80	}
81
82	err := enc.Encode(gt)
83	if err != nil {
84		return nil, fmt.Errorf("error encoding cty.Type: %s", err)
85	}
86
87	return buf.Bytes(), nil
88}
89
90// GobDecode is an implementatino of the gob.GobDecoder interface, which
91// reverses the encoding performed by GobEncode to allow types to be recovered
92// from gob buffers.
93func (t *Type) GobDecode(buf []byte) error {
94	r := bytes.NewReader(buf)
95	dec := gob.NewDecoder(r)
96
97	var gt gobType
98	err := dec.Decode(&gt)
99	if err != nil {
100		return fmt.Errorf("error decoding cty.Type: %s", err)
101	}
102	if gt.Version != 0 {
103		return fmt.Errorf("unsupported cty.Type encoding version %d; only 0 is supported", gt.Version)
104	}
105
106	t.typeImpl = gt.Impl
107
108	return nil
109}
110
111// Capsule types cannot currently be gob-encoded, because they rely on pointer
112// equality and we have no way to recover the original pointer on decode.
113func (t *capsuleType) GobEncode() ([]byte, error) {
114	return nil, fmt.Errorf("cannot gob-encode capsule type %q", t.FriendlyName(friendlyTypeName))
115}
116
117func (t *capsuleType) GobDecode() ([]byte, error) {
118	return nil, fmt.Errorf("cannot gob-decode capsule type %q", t.FriendlyName(friendlyTypeName))
119}
120
121type gobValue struct {
122	Version int
123	Ty      Type
124	V       interface{}
125}
126
127type gobType struct {
128	Version int
129	Impl    typeImpl
130}
131
132type gobCapsuleTypeImpl struct {
133}
134
135// goDecodeFixNumberPtr fixes an unfortunate quirk of round-tripping cty.Number
136// values through gob: the big.Float.GobEncode method is implemented on a
137// pointer receiver, and so it loses the "pointer-ness" of the value on
138// encode, causing the values to emerge the other end as big.Float rather than
139// *big.Float as we expect elsewhere in cty.
140//
141// The implementation of gobDecodeFixNumberPtr mutates the given raw value
142// during its work, and may either return the same value mutated or a new
143// value. Callers must no longer use whatever value they pass as "raw" after
144// this function is called.
145func gobDecodeFixNumberPtr(raw interface{}, ty Type) interface{} {
146	// Unfortunately we need to work recursively here because number values
147	// might be embedded in structural or collection type values.
148
149	switch {
150	case ty.Equals(Number):
151		if bf, ok := raw.(big.Float); ok {
152			return &bf // wrap in pointer
153		}
154	case ty.IsMapType() && ty.ElementType().Equals(Number):
155		if m, ok := raw.(map[string]interface{}); ok {
156			for k, v := range m {
157				m[k] = gobDecodeFixNumberPtr(v, ty.ElementType())
158			}
159		}
160	case ty.IsListType() && ty.ElementType().Equals(Number):
161		if s, ok := raw.([]interface{}); ok {
162			for i, v := range s {
163				s[i] = gobDecodeFixNumberPtr(v, ty.ElementType())
164			}
165		}
166	case ty.IsSetType() && ty.ElementType().Equals(Number):
167		if s, ok := raw.(set.Set); ok {
168			newS := set.NewSet(s.Rules())
169			for it := s.Iterator(); it.Next(); {
170				newV := gobDecodeFixNumberPtr(it.Value(), ty.ElementType())
171				newS.Add(newV)
172			}
173			return newS
174		}
175	case ty.IsObjectType():
176		if m, ok := raw.(map[string]interface{}); ok {
177			for k, v := range m {
178				aty := ty.AttributeType(k)
179				m[k] = gobDecodeFixNumberPtr(v, aty)
180			}
181		}
182	case ty.IsTupleType():
183		if s, ok := raw.([]interface{}); ok {
184			for i, v := range s {
185				ety := ty.TupleElementType(i)
186				s[i] = gobDecodeFixNumberPtr(v, ety)
187			}
188		}
189	}
190
191	return raw
192}
193
194// gobDecodeFixNumberPtrVal is a helper wrapper around gobDecodeFixNumberPtr
195// that works with already-constructed values. This is primarily for testing,
196// to fix up intentionally-invalid number values for the parts of the test
197// code that need them to be valid, such as calling GoString on them.
198func gobDecodeFixNumberPtrVal(v Value) Value {
199	raw := gobDecodeFixNumberPtr(v.v, v.ty)
200	return Value{
201		v:  raw,
202		ty: v.ty,
203	}
204}
205