| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package schema |
| |
| import ( |
| "fmt" |
| "strings" |
| ) |
| |
| // MapFieldReader reads fields out of an untyped map[string]string to |
| // the best of its ability. |
| type MapFieldReader struct { |
| Map MapReader |
| Schema map[string]*Schema |
| } |
| |
| func (r *MapFieldReader) ReadField(address []string) (FieldReadResult, error) { |
| k := strings.Join(address, ".") |
| schemaList := addrToSchema(address, r.Schema) |
| if len(schemaList) == 0 { |
| return FieldReadResult{}, nil |
| } |
| |
| schema := schemaList[len(schemaList)-1] |
| switch schema.Type { |
| case TypeBool, TypeInt, TypeFloat, TypeString: |
| return r.readPrimitive(address, schema) |
| case TypeList: |
| return readListField(r, address, schema) |
| case TypeMap: |
| return r.readMap(k, schema) |
| case TypeSet: |
| return r.readSet(address, schema) |
| case typeObject: |
| return readObjectField(r, address, schema.Elem.(map[string]*Schema)) |
| default: |
| panic(fmt.Sprintf("Unknown type: %s", schema.Type)) |
| } |
| } |
| |
| func (r *MapFieldReader) readMap(k string, schema *Schema) (FieldReadResult, error) { |
| result := make(map[string]interface{}) |
| resultSet := false |
| |
| // If the name of the map field is directly in the map with an |
| // empty string, it means that the map is being deleted, so mark |
| // that is is set. |
| if v, ok := r.Map.Access(k); ok && v == "" { |
| resultSet = true |
| } |
| |
| prefix := k + "." |
| r.Map.Range(func(k, v string) bool { |
| if strings.HasPrefix(k, prefix) { |
| resultSet = true |
| |
| key := k[len(prefix):] |
| if key != "%" && key != "#" { |
| result[key] = v |
| } |
| } |
| |
| return true |
| }) |
| |
| err := mapValuesToPrimitive(k, result, schema) |
| if err != nil { |
| return FieldReadResult{}, nil |
| } |
| |
| var resultVal interface{} |
| if resultSet { |
| resultVal = result |
| } |
| |
| return FieldReadResult{ |
| Value: resultVal, |
| Exists: resultSet, |
| }, nil |
| } |
| |
| func (r *MapFieldReader) readPrimitive( |
| address []string, schema *Schema) (FieldReadResult, error) { |
| k := strings.Join(address, ".") |
| result, ok := r.Map.Access(k) |
| if !ok { |
| return FieldReadResult{}, nil |
| } |
| |
| returnVal, err := stringToPrimitive(result, false, schema) |
| if err != nil { |
| return FieldReadResult{}, err |
| } |
| |
| return FieldReadResult{ |
| Value: returnVal, |
| Exists: true, |
| }, nil |
| } |
| |
| func (r *MapFieldReader) readSet( |
| address []string, schema *Schema) (FieldReadResult, error) { |
| // copy address to ensure we don't modify the argument |
| address = append([]string(nil), address...) |
| |
| // Get the number of elements in the list |
| countRaw, err := r.readPrimitive( |
| append(address, "#"), &Schema{Type: TypeInt}) |
| if err != nil { |
| return FieldReadResult{}, err |
| } |
| if !countRaw.Exists { |
| // No count, means we have no list |
| countRaw.Value = 0 |
| } |
| |
| // Create the set that will be our result |
| set := schema.ZeroValue().(*Set) |
| |
| // If we have an empty list, then return an empty list |
| if countRaw.Computed || countRaw.Value.(int) == 0 { |
| return FieldReadResult{ |
| Value: set, |
| Exists: countRaw.Exists, |
| Computed: countRaw.Computed, |
| }, nil |
| } |
| |
| // Go through the map and find all the set items |
| prefix := strings.Join(address, ".") + "." |
| countExpected := countRaw.Value.(int) |
| countActual := make(map[string]struct{}) |
| completed := r.Map.Range(func(k, _ string) bool { |
| if !strings.HasPrefix(k, prefix) { |
| return true |
| } |
| if strings.HasPrefix(k, prefix+"#") { |
| // Ignore the count field |
| return true |
| } |
| |
| // Split the key, since it might be a sub-object like "idx.field" |
| parts := strings.Split(k[len(prefix):], ".") |
| idx := parts[0] |
| |
| var raw FieldReadResult |
| raw, err = r.ReadField(append(address, idx)) |
| if err != nil { |
| return false |
| } |
| if !raw.Exists { |
| // This shouldn't happen because we just verified it does exist |
| panic("missing field in set: " + k + "." + idx) |
| } |
| |
| set.Add(raw.Value) |
| |
| // Due to the way multimap readers work, if we've seen the number |
| // of fields we expect, then exit so that we don't read later values. |
| // For example: the "set" map might have "ports.#", "ports.0", and |
| // "ports.1", but the "state" map might have those plus "ports.2". |
| // We don't want "ports.2" |
| countActual[idx] = struct{}{} |
| if len(countActual) >= countExpected { |
| return false |
| } |
| |
| return true |
| }) |
| if !completed && err != nil { |
| return FieldReadResult{}, err |
| } |
| |
| return FieldReadResult{ |
| Value: set, |
| Exists: true, |
| }, nil |
| } |
| |
| // MapReader is an interface that is given to MapFieldReader for accessing |
| // a "map". This can be used to have alternate implementations. For a basic |
| // map[string]string, use BasicMapReader. |
| type MapReader interface { |
| Access(string) (string, bool) |
| Range(func(string, string) bool) bool |
| } |
| |
| // BasicMapReader implements MapReader for a single map. |
| type BasicMapReader map[string]string |
| |
| func (r BasicMapReader) Access(k string) (string, bool) { |
| v, ok := r[k] |
| return v, ok |
| } |
| |
| func (r BasicMapReader) Range(f func(string, string) bool) bool { |
| for k, v := range r { |
| if cont := f(k, v); !cont { |
| return false |
| } |
| } |
| |
| return true |
| } |
| |
| // MultiMapReader reads over multiple maps, preferring keys that are |
| // founder earlier (lower number index) vs. later (higher number index) |
| type MultiMapReader []map[string]string |
| |
| func (r MultiMapReader) Access(k string) (string, bool) { |
| for _, m := range r { |
| if v, ok := m[k]; ok { |
| return v, ok |
| } |
| } |
| |
| return "", false |
| } |
| |
| func (r MultiMapReader) Range(f func(string, string) bool) bool { |
| done := make(map[string]struct{}) |
| for _, m := range r { |
| for k, v := range m { |
| if _, ok := done[k]; ok { |
| continue |
| } |
| |
| if cont := f(k, v); !cont { |
| return false |
| } |
| |
| done[k] = struct{}{} |
| } |
| } |
| |
| return true |
| } |