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