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