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(>) 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