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