blob: fe6d7504c74820babce819826bb5f05f4915689a [file] [log] [blame]
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))
}
}