]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
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(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 | // Get the number of elements in the list | |
102 | countRaw, err := r.readPrimitive( | |
103 | append(address, "#"), &Schema{Type: TypeInt}) | |
104 | if err != nil { | |
105 | return FieldReadResult{}, err | |
106 | } | |
107 | if !countRaw.Exists { | |
108 | // No count, means we have no list | |
109 | countRaw.Value = 0 | |
110 | } | |
111 | ||
112 | // Create the set that will be our result | |
113 | set := schema.ZeroValue().(*Set) | |
114 | ||
115 | // If we have an empty list, then return an empty list | |
116 | if countRaw.Computed || countRaw.Value.(int) == 0 { | |
117 | return FieldReadResult{ | |
118 | Value: set, | |
119 | Exists: countRaw.Exists, | |
120 | Computed: countRaw.Computed, | |
121 | }, nil | |
122 | } | |
123 | ||
124 | // Go through the map and find all the set items | |
125 | prefix := strings.Join(address, ".") + "." | |
126 | countExpected := countRaw.Value.(int) | |
127 | countActual := make(map[string]struct{}) | |
128 | completed := r.Map.Range(func(k, _ string) bool { | |
129 | if !strings.HasPrefix(k, prefix) { | |
130 | return true | |
131 | } | |
132 | if strings.HasPrefix(k, prefix+"#") { | |
133 | // Ignore the count field | |
134 | return true | |
135 | } | |
136 | ||
137 | // Split the key, since it might be a sub-object like "idx.field" | |
138 | parts := strings.Split(k[len(prefix):], ".") | |
139 | idx := parts[0] | |
140 | ||
141 | var raw FieldReadResult | |
142 | raw, err = r.ReadField(append(address, idx)) | |
143 | if err != nil { | |
144 | return false | |
145 | } | |
146 | if !raw.Exists { | |
147 | // This shouldn't happen because we just verified it does exist | |
148 | panic("missing field in set: " + k + "." + idx) | |
149 | } | |
150 | ||
151 | set.Add(raw.Value) | |
152 | ||
153 | // Due to the way multimap readers work, if we've seen the number | |
154 | // of fields we expect, then exit so that we don't read later values. | |
155 | // For example: the "set" map might have "ports.#", "ports.0", and | |
156 | // "ports.1", but the "state" map might have those plus "ports.2". | |
157 | // We don't want "ports.2" | |
158 | countActual[idx] = struct{}{} | |
159 | if len(countActual) >= countExpected { | |
160 | return false | |
161 | } | |
162 | ||
163 | return true | |
164 | }) | |
165 | if !completed && err != nil { | |
166 | return FieldReadResult{}, err | |
167 | } | |
168 | ||
169 | return FieldReadResult{ | |
170 | Value: set, | |
171 | Exists: true, | |
172 | }, nil | |
173 | } | |
174 | ||
175 | // MapReader is an interface that is given to MapFieldReader for accessing | |
176 | // a "map". This can be used to have alternate implementations. For a basic | |
177 | // map[string]string, use BasicMapReader. | |
178 | type MapReader interface { | |
179 | Access(string) (string, bool) | |
180 | Range(func(string, string) bool) bool | |
181 | } | |
182 | ||
183 | // BasicMapReader implements MapReader for a single map. | |
184 | type BasicMapReader map[string]string | |
185 | ||
186 | func (r BasicMapReader) Access(k string) (string, bool) { | |
187 | v, ok := r[k] | |
188 | return v, ok | |
189 | } | |
190 | ||
191 | func (r BasicMapReader) Range(f func(string, string) bool) bool { | |
192 | for k, v := range r { | |
193 | if cont := f(k, v); !cont { | |
194 | return false | |
195 | } | |
196 | } | |
197 | ||
198 | return true | |
199 | } | |
200 | ||
201 | // MultiMapReader reads over multiple maps, preferring keys that are | |
202 | // founder earlier (lower number index) vs. later (higher number index) | |
203 | type MultiMapReader []map[string]string | |
204 | ||
205 | func (r MultiMapReader) Access(k string) (string, bool) { | |
206 | for _, m := range r { | |
207 | if v, ok := m[k]; ok { | |
208 | return v, ok | |
209 | } | |
210 | } | |
211 | ||
212 | return "", false | |
213 | } | |
214 | ||
215 | func (r MultiMapReader) Range(f func(string, string) bool) bool { | |
216 | done := make(map[string]struct{}) | |
217 | for _, m := range r { | |
218 | for k, v := range m { | |
219 | if _, ok := done[k]; ok { | |
220 | continue | |
221 | } | |
222 | ||
223 | if cont := f(k, v); !cont { | |
224 | return false | |
225 | } | |
226 | ||
227 | done[k] = struct{}{} | |
228 | } | |
229 | } | |
230 | ||
231 | return true | |
232 | } |