1package schema
2
3import (
4	"bytes"
5	"fmt"
6	"sort"
7	"strconv"
8)
9
10func 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.
87func 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
114func 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}
126