github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/field_reader_map.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 "fmt" 10 "strings" 11 ) 12 13 // MapFieldReader reads fields out of an untyped map[string]string to 14 // the best of its ability. 15 type MapFieldReader struct { 16 Map MapReader 17 Schema map[string]*Schema 18 } 19 20 func (r *MapFieldReader) ReadField(address []string) (FieldReadResult, error) { 21 k := strings.Join(address, ".") 22 schemaList := addrToSchema(address, r.Schema) 23 if len(schemaList) == 0 { 24 return FieldReadResult{}, nil 25 } 26 27 schema := schemaList[len(schemaList)-1] 28 switch schema.Type { 29 case TypeBool, TypeInt, TypeFloat, TypeString: 30 return r.readPrimitive(address, schema) 31 case TypeList: 32 return readListField(r, address, schema) 33 case TypeMap: 34 return r.readMap(k, schema) 35 case TypeSet: 36 return r.readSet(address, schema) 37 case typeObject: 38 return readObjectField(r, address, schema.Elem.(map[string]*Schema)) 39 default: 40 panic(fmt.Sprintf("Unknown type: %s", schema.Type)) 41 } 42 } 43 44 func (r *MapFieldReader) readMap(k string, schema *Schema) (FieldReadResult, error) { 45 result := make(map[string]interface{}) 46 resultSet := false 47 48 // If the name of the map field is directly in the map with an 49 // empty string, it means that the map is being deleted, so mark 50 // that is is set. 51 if v, ok := r.Map.Access(k); ok && v == "" { 52 resultSet = true 53 } 54 55 prefix := k + "." 56 r.Map.Range(func(k, v string) bool { 57 if strings.HasPrefix(k, prefix) { 58 resultSet = true 59 60 key := k[len(prefix):] 61 if key != "%" && key != "#" { 62 result[key] = v 63 } 64 } 65 66 return true 67 }) 68 69 err := mapValuesToPrimitive(k, result, schema) 70 if err != nil { 71 return FieldReadResult{}, nil 72 } 73 74 var resultVal interface{} 75 if resultSet { 76 resultVal = result 77 } 78 79 return FieldReadResult{ 80 Value: resultVal, 81 Exists: resultSet, 82 }, nil 83 } 84 85 func (r *MapFieldReader) readPrimitive( 86 address []string, schema *Schema) (FieldReadResult, error) { 87 k := strings.Join(address, ".") 88 result, ok := r.Map.Access(k) 89 if !ok { 90 return FieldReadResult{}, nil 91 } 92 93 returnVal, err := stringToPrimitive(result, false, schema) 94 if err != nil { 95 return FieldReadResult{}, err 96 } 97 98 return FieldReadResult{ 99 Value: returnVal, 100 Exists: true, 101 }, nil 102 } 103 104 func (r *MapFieldReader) readSet( 105 address []string, schema *Schema) (FieldReadResult, error) { 106 // copy address to ensure we don't modify the argument 107 address = append([]string(nil), address...) 108 109 // Get the number of elements in the list 110 countRaw, err := r.readPrimitive( 111 append(address, "#"), &Schema{Type: TypeInt}) 112 if err != nil { 113 return FieldReadResult{}, err 114 } 115 if !countRaw.Exists { 116 // No count, means we have no list 117 countRaw.Value = 0 118 } 119 120 // Create the set that will be our result 121 set := schema.ZeroValue().(*Set) 122 123 // If we have an empty list, then return an empty list 124 if countRaw.Computed || countRaw.Value.(int) == 0 { 125 return FieldReadResult{ 126 Value: set, 127 Exists: countRaw.Exists, 128 Computed: countRaw.Computed, 129 }, nil 130 } 131 132 // Go through the map and find all the set items 133 prefix := strings.Join(address, ".") + "." 134 countExpected := countRaw.Value.(int) 135 countActual := make(map[string]struct{}) 136 completed := r.Map.Range(func(k, _ string) bool { 137 if !strings.HasPrefix(k, prefix) { 138 return true 139 } 140 if strings.HasPrefix(k, prefix+"#") { 141 // Ignore the count field 142 return true 143 } 144 145 // Split the key, since it might be a sub-object like "idx.field" 146 parts := strings.Split(k[len(prefix):], ".") 147 idx := parts[0] 148 149 var raw FieldReadResult 150 raw, err = r.ReadField(append(address, idx)) 151 if err != nil { 152 return false 153 } 154 if !raw.Exists { 155 // This shouldn't happen because we just verified it does exist 156 panic("missing field in set: " + k + "." + idx) 157 } 158 159 set.Add(raw.Value) 160 161 // Due to the way multimap readers work, if we've seen the number 162 // of fields we expect, then exit so that we don't read later values. 163 // For example: the "set" map might have "ports.#", "ports.0", and 164 // "ports.1", but the "state" map might have those plus "ports.2". 165 // We don't want "ports.2" 166 countActual[idx] = struct{}{} 167 if len(countActual) >= countExpected { 168 return false 169 } 170 171 return true 172 }) 173 if !completed && err != nil { 174 return FieldReadResult{}, err 175 } 176 177 return FieldReadResult{ 178 Value: set, 179 Exists: true, 180 }, nil 181 } 182 183 // MapReader is an interface that is given to MapFieldReader for accessing 184 // a "map". This can be used to have alternate implementations. For a basic 185 // map[string]string, use BasicMapReader. 186 type MapReader interface { 187 Access(string) (string, bool) 188 Range(func(string, string) bool) bool 189 } 190 191 // BasicMapReader implements MapReader for a single map. 192 type BasicMapReader map[string]string 193 194 func (r BasicMapReader) Access(k string) (string, bool) { 195 v, ok := r[k] 196 return v, ok 197 } 198 199 func (r BasicMapReader) Range(f func(string, string) bool) bool { 200 for k, v := range r { 201 if cont := f(k, v); !cont { 202 return false 203 } 204 } 205 206 return true 207 } 208 209 // MultiMapReader reads over multiple maps, preferring keys that are 210 // founder earlier (lower number index) vs. later (higher number index) 211 type MultiMapReader []map[string]string 212 213 func (r MultiMapReader) Access(k string) (string, bool) { 214 for _, m := range r { 215 if v, ok := m[k]; ok { 216 return v, ok 217 } 218 } 219 220 return "", false 221 } 222 223 func (r MultiMapReader) Range(f func(string, string) bool) bool { 224 done := make(map[string]struct{}) 225 for _, m := range r { 226 for k, v := range m { 227 if _, ok := done[k]; ok { 228 continue 229 } 230 231 if cont := f(k, v); !cont { 232 return false 233 } 234 235 done[k] = struct{}{} 236 } 237 } 238 239 return true 240 }