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