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