Skip to content

Commit 59c3d20

Browse files
author
jennybuckley
committed
Merge map and struct in walkers
1 parent d9b9b90 commit 59c3d20

File tree

5 files changed

+57
-180
lines changed

5 files changed

+57
-180
lines changed

typed/helpers.go

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ func (ef errorFormatter) prefixError(prefix string, err error) ValidationErrors
9090

9191
type atomHandler interface {
9292
doScalar(schema.Scalar) ValidationErrors
93-
doStruct(schema.Struct) ValidationErrors
9493
doList(schema.List) ValidationErrors
9594
doMap(schema.Map) ValidationErrors
9695

@@ -115,7 +114,7 @@ func deduceAtom(a schema.Atom, v *value.Value) schema.Atom {
115114
case v.ListValue != nil:
116115
return schema.Atom{List: a.List}
117116
case v.MapValue != nil:
118-
return schema.Atom{Struct: a.Struct, Map: a.Map}
117+
return schema.Atom{Map: a.Map}
119118
}
120119
return a
121120
}
@@ -124,8 +123,6 @@ func handleAtom(a schema.Atom, tr schema.TypeRef, ah atomHandler) ValidationErro
124123
switch {
125124
case a.Map != nil:
126125
return ah.doMap(*a.Map)
127-
case a.Struct != nil:
128-
return ah.doStruct(*a.Struct)
129126
case a.Scalar != nil:
130127
return ah.doScalar(*a.Scalar)
131128
case a.List != nil:
@@ -179,29 +176,17 @@ func listValue(val value.Value) (*value.List, error) {
179176
}
180177

181178
// Returns the map, or an error. Reminder: nil is a valid map and might be returned.
182-
func mapOrStructValue(val value.Value, typeName string) (*value.Map, error) {
179+
func mapValue(val value.Value) (*value.Map, error) {
183180
switch {
184181
case val.Null:
185182
return nil, nil
186183
case val.MapValue != nil:
187184
return val.MapValue, nil
188185
default:
189-
return nil, fmt.Errorf("expected %v, got %v", typeName, val)
186+
return nil, fmt.Errorf("expected map, got %v", val)
190187
}
191188
}
192189

193-
func (ef errorFormatter) rejectExtraStructFields(m *value.Map, allowedNames map[string]struct{}, prefix string) (errs ValidationErrors) {
194-
if m == nil {
195-
return nil
196-
}
197-
for _, f := range m.Items {
198-
if _, allowed := allowedNames[f.Name]; !allowed {
199-
errs = append(errs, ef.errorf("%vfield %v is not mentioned in the schema", prefix, f.Name)...)
200-
}
201-
}
202-
return errs
203-
}
204-
205190
func keyedAssociativeListItemToPathElement(list schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
206191
pe := fieldpath.PathElement{}
207192
if child.Null {

typed/merge.go

Lines changed: 22 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -126,99 +126,20 @@ func (w *mergingWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeR
126126
return &w2
127127
}
128128

129-
func (w *mergingWalker) visitStructFields(t schema.Struct, lhs, rhs *value.Map) (errs ValidationErrors) {
130-
out := &value.Map{}
131-
132-
valOrNil := func(m *value.Map, name string) *value.Value {
133-
if m == nil {
134-
return nil
135-
}
136-
val, ok := m.Get(name)
137-
if ok {
138-
return &val.Value
139-
}
140-
return nil
141-
}
142-
143-
allowedNames := map[string]struct{}{}
144-
for i := range t.Fields {
145-
// I don't want to use the loop variable since a reference
146-
// might outlive the loop iteration (in an error message).
147-
f := t.Fields[i]
148-
allowedNames[f.Name] = struct{}{}
149-
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &f.Name}, f.Type)
150-
w2.lhs = valOrNil(lhs, f.Name)
151-
w2.rhs = valOrNil(rhs, f.Name)
152-
if w2.lhs == nil && w2.rhs == nil {
153-
// All fields are optional
154-
continue
155-
}
156-
if newErrs := w2.merge(); len(newErrs) > 0 {
157-
errs = append(errs, newErrs...)
158-
} else if w2.out != nil {
159-
out.Set(f.Name, *w2.out)
160-
}
161-
}
162-
163-
// All fields may be optional, but unknown fields are not allowed.
164-
errs = append(errs, w.rejectExtraStructFields(lhs, allowedNames, "lhs: ")...)
165-
errs = append(errs, w.rejectExtraStructFields(rhs, allowedNames, "rhs: ")...)
166-
if len(errs) > 0 {
167-
return errs
168-
}
169-
170-
if len(out.Items) > 0 {
171-
w.out = &value.Value{MapValue: out}
172-
}
173-
174-
return errs
175-
}
176-
177-
func (w *mergingWalker) derefMapOrStruct(prefix, typeName string, v *value.Value, dest **value.Map) (errs ValidationErrors) {
129+
func (w *mergingWalker) derefMap(prefix string, v *value.Value, dest **value.Map) (errs ValidationErrors) {
178130
// taking dest as input so that it can be called as a one-liner with
179131
// append.
180132
if v == nil {
181133
return nil
182134
}
183-
m, err := mapOrStructValue(*v, typeName)
135+
m, err := mapValue(*v)
184136
if err != nil {
185137
return w.prefixError(prefix, err)
186138
}
187139
*dest = m
188140
return nil
189141
}
190142

191-
func (w *mergingWalker) doStruct(t schema.Struct) (errs ValidationErrors) {
192-
var lhs, rhs *value.Map
193-
errs = append(errs, w.derefMapOrStruct("lhs: ", "struct", w.lhs, &lhs)...)
194-
errs = append(errs, w.derefMapOrStruct("rhs: ", "struct", w.rhs, &rhs)...)
195-
if len(errs) > 0 {
196-
return errs
197-
}
198-
199-
// If both lhs and rhs are empty/null, treat it as a
200-
// leaf: this helps preserve the empty/null
201-
// distinction.
202-
emptyPromoteToLeaf := (lhs == nil || len(lhs.Items) == 0) &&
203-
(rhs == nil || len(rhs.Items) == 0)
204-
205-
if t.ElementRelationship == schema.Atomic || emptyPromoteToLeaf {
206-
w.doLeaf()
207-
return nil
208-
}
209-
210-
if lhs == nil && rhs == nil {
211-
// nil is a valid map!
212-
return nil
213-
}
214-
215-
errs = w.visitStructFields(t, lhs, rhs)
216-
217-
// TODO: Check unions.
218-
219-
return errs
220-
}
221-
222143
func (w *mergingWalker) visitListItems(t schema.List, lhs, rhs *value.List) (errs ValidationErrors) {
223144
out := &value.List{}
224145

@@ -342,10 +263,22 @@ func (w *mergingWalker) doList(t schema.List) (errs ValidationErrors) {
342263
func (w *mergingWalker) visitMapItems(t schema.Map, lhs, rhs *value.Map) (errs ValidationErrors) {
343264
out := &value.Map{}
344265

266+
fieldTypes := map[string]schema.TypeRef{}
267+
for i := range t.Fields {
268+
// I don't want to use the loop variable since a reference
269+
// might outlive the loop iteration (in an error message).
270+
f := t.Fields[i]
271+
fieldTypes[f.Name] = f.Type
272+
}
273+
345274
if lhs != nil {
346275
for _, litem := range lhs.Items {
347276
name := litem.Name
348-
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, t.ElementType)
277+
fieldType := t.ElementType
278+
if ft, ok := fieldTypes[name]; ok {
279+
fieldType = ft
280+
}
281+
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, fieldType)
349282
w2.lhs = &litem.Value
350283
if rhs != nil {
351284
if ritem, ok := rhs.Get(litem.Name); ok {
@@ -369,7 +302,11 @@ func (w *mergingWalker) visitMapItems(t schema.Map, lhs, rhs *value.Map) (errs V
369302
}
370303

371304
name := ritem.Name
372-
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, t.ElementType)
305+
fieldType := t.ElementType
306+
if ft, ok := fieldTypes[name]; ok {
307+
fieldType = ft
308+
}
309+
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, fieldType)
373310
w2.rhs = &ritem.Value
374311
if newErrs := w2.merge(); len(newErrs) > 0 {
375312
errs = append(errs, newErrs...)
@@ -387,8 +324,8 @@ func (w *mergingWalker) visitMapItems(t schema.Map, lhs, rhs *value.Map) (errs V
387324

388325
func (w *mergingWalker) doMap(t schema.Map) (errs ValidationErrors) {
389326
var lhs, rhs *value.Map
390-
w.derefMapOrStruct("lhs: ", "map", w.lhs, &lhs)
391-
w.derefMapOrStruct("rhs: ", "map", w.rhs, &rhs)
327+
w.derefMap("lhs: ", w.lhs, &lhs)
328+
w.derefMap("rhs: ", w.rhs, &rhs)
392329

393330
// If both lhs and rhs are empty/null, treat it as a
394331
// leaf: this helps preserve the empty/null

typed/remove.go

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -40,29 +40,6 @@ func (w *removingWalker) doLeaf() ValidationErrors { return nil }
4040

4141
func (w *removingWalker) doScalar(t schema.Scalar) ValidationErrors { return nil }
4242

43-
func (w *removingWalker) doStruct(t schema.Struct) ValidationErrors {
44-
s := w.value.MapValue
45-
46-
// If struct is null, empty, or atomic just return
47-
if s == nil || len(s.Items) == 0 || t.ElementRelationship == schema.Atomic {
48-
return nil
49-
}
50-
51-
fieldTypes := map[string]schema.TypeRef{}
52-
for _, structField := range t.Fields {
53-
fieldTypes[structField.Name] = structField.Type
54-
}
55-
56-
for i := range s.Items {
57-
item := s.Items[i]
58-
pe := fieldpath.PathElement{FieldName: &item.Name}
59-
if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
60-
removeItemsWithSchema(&s.Items[i].Value, subset, w.schema, fieldTypes[item.Name])
61-
}
62-
}
63-
return nil
64-
}
65-
6643
func (w *removingWalker) doList(t schema.List) (errs ValidationErrors) {
6744
l := w.value.ListValue
6845

@@ -101,16 +78,26 @@ func (w *removingWalker) doMap(t schema.Map) ValidationErrors {
10178
return nil
10279
}
10380

81+
fieldTypes := map[string]schema.TypeRef{}
82+
for _, structField := range t.Fields {
83+
fieldTypes[structField.Name] = structField.Type
84+
}
85+
10486
newMap := &value.Map{}
10587
for i := range m.Items {
10688
item := m.Items[i]
10789
pe := fieldpath.PathElement{FieldName: &item.Name}
10890
path, _ := fieldpath.MakePath(pe)
109-
if w.toRemove.Has(path) {
110-
continue
91+
fieldType := t.ElementType
92+
if ft, ok := fieldTypes[item.Name]; ok {
93+
fieldType = ft
94+
} else {
95+
if w.toRemove.Has(path) {
96+
continue
97+
}
11198
}
11299
if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
113-
removeItemsWithSchema(&m.Items[i].Value, subset, w.schema, t.ElementType)
100+
removeItemsWithSchema(&m.Items[i].Value, subset, w.schema, fieldType)
114101
}
115102
newMap.Set(item.Name, m.Items[i].Value)
116103
}

typed/union.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ func normalizeUnions(w *mergingWalker) error {
3030
panic(fmt.Sprintf("Unable to resolve schema in normalize union: %v/%v", w.schema, w.typeRef))
3131
}
3232
// Unions can only be in structures, and the struct must not have been removed
33-
if atom.Struct == nil || w.out == nil {
33+
if atom.Map == nil || w.out == nil {
3434
return nil
3535
}
3636

3737
old := &value.Map{}
3838
if w.lhs != nil {
3939
old = w.lhs.MapValue
4040
}
41-
for _, union := range atom.Struct.Unions {
41+
for _, union := range atom.Map.Unions {
4242
if err := newUnion(&union).Normalize(old, w.rhs.MapValue, w.out.MapValue); err != nil {
4343
return err
4444
}

typed/validate.go

Lines changed: 17 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -98,51 +98,6 @@ func (v validatingObjectWalker) doScalar(t schema.Scalar) ValidationErrors {
9898
return nil
9999
}
100100

101-
func (v validatingObjectWalker) visitStructFields(t schema.Struct, m *value.Map) (errs ValidationErrors) {
102-
allowedNames := map[string]struct{}{}
103-
for i := range t.Fields {
104-
// I don't want to use the loop variable since a reference
105-
// might outlive the loop iteration (in an error message).
106-
f := t.Fields[i]
107-
allowedNames[f.Name] = struct{}{}
108-
child, ok := m.Get(f.Name)
109-
if !ok {
110-
// All fields are optional
111-
continue
112-
}
113-
v2 := v
114-
v2.errorFormatter.descend(fieldpath.PathElement{FieldName: &f.Name})
115-
v2.value = child.Value
116-
v2.typeRef = f.Type
117-
errs = append(errs, v2.validate()...)
118-
}
119-
120-
// All fields may be optional, but unknown fields are not allowed.
121-
return append(errs, v.rejectExtraStructFields(m, allowedNames, "")...)
122-
}
123-
124-
func (v validatingObjectWalker) doStruct(t schema.Struct) (errs ValidationErrors) {
125-
m, err := mapOrStructValue(v.value, "struct")
126-
if err != nil {
127-
return v.error(err)
128-
}
129-
130-
if t.ElementRelationship == schema.Atomic {
131-
v.doLeaf()
132-
}
133-
134-
if m == nil {
135-
// nil is a valid map!
136-
return nil
137-
}
138-
139-
errs = v.visitStructFields(t, m)
140-
141-
// TODO: Check unions.
142-
143-
return errs
144-
}
145-
146101
func (v validatingObjectWalker) visitListItems(t schema.List, list *value.List) (errs ValidationErrors) {
147102
observedKeys := map[string]struct{}{}
148103
for i, child := range list.Items {
@@ -190,21 +145,34 @@ func (v validatingObjectWalker) doList(t schema.List) (errs ValidationErrors) {
190145
}
191146

192147
func (v validatingObjectWalker) visitMapItems(t schema.Map, m *value.Map) (errs ValidationErrors) {
148+
fieldTypes := map[string]schema.TypeRef{}
149+
for i := range t.Fields {
150+
// I don't want to use the loop variable since a reference
151+
// might outlive the loop iteration (in an error message).
152+
f := t.Fields[i]
153+
fieldTypes[f.Name] = f.Type
154+
}
155+
193156
for _, item := range m.Items {
194157
v2 := v
195158
name := item.Name
196159
v2.errorFormatter.descend(fieldpath.PathElement{FieldName: &name})
197160
v2.value = item.Value
198-
v2.typeRef = t.ElementType
199-
errs = append(errs, v2.validate()...)
200161

201-
v2.doNode()
162+
var ok bool
163+
if v2.typeRef, ok = fieldTypes[name]; ok {
164+
errs = append(errs, v2.validate()...)
165+
} else {
166+
v2.typeRef = t.ElementType
167+
errs = append(errs, v2.validate()...)
168+
v2.doNode()
169+
}
202170
}
203171
return errs
204172
}
205173

206174
func (v validatingObjectWalker) doMap(t schema.Map) (errs ValidationErrors) {
207-
m, err := mapOrStructValue(v.value, "map")
175+
m, err := mapValue(v.value)
208176
if err != nil {
209177
return v.error(err)
210178
}

0 commit comments

Comments
 (0)