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