/
serialize.go
130 lines (120 loc) · 3.05 KB
/
serialize.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package schema
import (
"bytes"
"fmt"
"sort"
"strconv"
)
func SerializeValueForHash(buf *bytes.Buffer, val interface{}, schema *Schema) {
if val == nil {
buf.WriteRune(';')
return
}
switch schema.Type {
case TypeBool:
if val.(bool) {
buf.WriteRune('1')
} else {
buf.WriteRune('0')
}
case TypeInt:
buf.WriteString(strconv.Itoa(val.(int)))
case TypeFloat:
buf.WriteString(strconv.FormatFloat(val.(float64), 'g', -1, 64))
case TypeString:
buf.WriteString(val.(string))
case TypeList:
buf.WriteRune('(')
l := val.([]interface{})
for _, innerVal := range l {
serializeCollectionMemberForHash(buf, innerVal, schema.Elem)
}
buf.WriteRune(')')
case TypeMap:
m := val.(map[string]interface{})
var keys []string
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
buf.WriteRune('[')
for _, k := range keys {
innerVal := m[k]
if innerVal == nil {
continue
}
buf.WriteString(k)
buf.WriteRune(':')
switch innerVal := innerVal.(type) {
case int:
buf.WriteString(strconv.Itoa(innerVal))
case float64:
buf.WriteString(strconv.FormatFloat(innerVal, 'g', -1, 64))
case string:
buf.WriteString(innerVal)
default:
panic(fmt.Sprintf("unknown value type in TypeMap %T", innerVal))
}
buf.WriteRune(';')
}
buf.WriteRune(']')
case TypeSet:
buf.WriteRune('{')
s := val.(*Set)
for _, innerVal := range s.List() {
serializeCollectionMemberForHash(buf, innerVal, schema.Elem)
}
buf.WriteRune('}')
default:
panic("unknown schema type to serialize")
}
buf.WriteRune(';')
}
// SerializeValueForHash appends a serialization of the given resource config
// to the given buffer, guaranteeing deterministic results given the same value
// and schema.
//
// Its primary purpose is as input into a hashing function in order
// to hash complex substructures when used in sets, and so the serialization
// is not reversible.
func SerializeResourceForHash(buf *bytes.Buffer, val interface{}, resource *Resource) {
if val == nil {
return
}
sm := resource.Schema
m := val.(map[string]interface{})
var keys []string
for k := range sm {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
innerSchema := sm[k]
// Skip attributes that are not user-provided. Computed attributes
// do not contribute to the hash since their ultimate value cannot
// be known at plan/diff time.
if !(innerSchema.Required || innerSchema.Optional) {
continue
}
buf.WriteString(k)
buf.WriteRune(':')
innerVal := m[k]
SerializeValueForHash(buf, innerVal, innerSchema)
}
}
func serializeCollectionMemberForHash(buf *bytes.Buffer, val interface{}, elem interface{}) {
switch tElem := elem.(type) {
case *Schema:
SerializeValueForHash(buf, val, tElem)
case *Resource:
buf.WriteRune('<')
SerializeResourceForHash(buf, val, tElem)
buf.WriteString(">;")
default:
panic(fmt.Sprintf("invalid element type: %T", tElem))
}
}