Skip to content

Commit 124d17e

Browse files
authored
Merge pull request #114 from shkw/fine-grained-decoding-error
Fine grained decoding error
2 parents b972fc2 + e92c144 commit 124d17e

File tree

3 files changed

+316
-45
lines changed

3 files changed

+316
-45
lines changed

cache.go

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func (c *cache) get(t reflect.Type) *structInfo {
117117
info := c.m[t]
118118
c.l.RUnlock()
119119
if info == nil {
120-
info = c.create(t, nil)
120+
info = c.create(t, "")
121121
c.l.Lock()
122122
c.m[t] = info
123123
c.l.Unlock()
@@ -126,37 +126,40 @@ func (c *cache) get(t reflect.Type) *structInfo {
126126
}
127127

128128
// create creates a structInfo with meta-data about a struct.
129-
func (c *cache) create(t reflect.Type, info *structInfo) *structInfo {
130-
if info == nil {
131-
info = &structInfo{fields: []*fieldInfo{}}
132-
}
129+
func (c *cache) create(t reflect.Type, parentAlias string) *structInfo {
130+
info := &structInfo{}
131+
var anonymousInfos []*structInfo
133132
for i := 0; i < t.NumField(); i++ {
134-
field := t.Field(i)
135-
if field.Anonymous {
136-
ft := field.Type
137-
if ft.Kind() == reflect.Ptr {
138-
ft = ft.Elem()
133+
if f := c.createField(t.Field(i), parentAlias); f != nil {
134+
info.fields = append(info.fields, f)
135+
if ft := indirectType(f.typ); ft.Kind() == reflect.Struct && f.isAnonymous {
136+
anonymousInfos = append(anonymousInfos, c.create(ft, f.canonicalAlias))
139137
}
140-
if ft.Kind() == reflect.Struct {
141-
bef := len(info.fields)
142-
c.create(ft, info)
143-
for _, fi := range info.fields[bef:len(info.fields)] {
144-
// exclude required check because duplicated to embedded field
145-
fi.isRequired = false
146-
}
138+
}
139+
}
140+
for i, a := range anonymousInfos {
141+
others := []*structInfo{info}
142+
others = append(others, anonymousInfos[:i]...)
143+
others = append(others, anonymousInfos[i+1:]...)
144+
for _, f := range a.fields {
145+
if !containsAlias(others, f.alias) {
146+
info.fields = append(info.fields, f)
147147
}
148148
}
149-
c.createField(field, info)
150149
}
151150
return info
152151
}
153152

154153
// createField creates a fieldInfo for the given field.
155-
func (c *cache) createField(field reflect.StructField, info *structInfo) {
154+
func (c *cache) createField(field reflect.StructField, parentAlias string) *fieldInfo {
156155
alias, options := fieldAlias(field, c.tag)
157156
if alias == "-" {
158157
// Ignore this field.
159-
return
158+
return nil
159+
}
160+
canonicalAlias := alias
161+
if parentAlias != "" {
162+
canonicalAlias = parentAlias + "." + alias
160163
}
161164
// Check if the type is supported and don't cache it if not.
162165
// First let's get the basic type.
@@ -181,19 +184,20 @@ func (c *cache) createField(field reflect.StructField, info *structInfo) {
181184
if isStruct = ft.Kind() == reflect.Struct; !isStruct {
182185
if c.converter(ft) == nil && builtinConverters[ft.Kind()] == nil {
183186
// Type is not supported.
184-
return
187+
return nil
185188
}
186189
}
187190

188-
info.fields = append(info.fields, &fieldInfo{
191+
return &fieldInfo{
189192
typ: field.Type,
190193
name: field.Name,
191194
alias: alias,
195+
canonicalAlias: canonicalAlias,
192196
unmarshalerInfo: m,
193197
isSliceOfStructs: isSlice && isStruct,
194198
isAnonymous: field.Anonymous,
195199
isRequired: options.Contains("required"),
196-
})
200+
}
197201
}
198202

199203
// converter returns the converter for a type.
@@ -216,11 +220,26 @@ func (i *structInfo) get(alias string) *fieldInfo {
216220
return nil
217221
}
218222

223+
func containsAlias(infos []*structInfo, alias string) bool {
224+
for _, info := range infos {
225+
if info.get(alias) != nil {
226+
return true
227+
}
228+
}
229+
return false
230+
}
231+
219232
type fieldInfo struct {
220233
typ reflect.Type
221234
// name is the field name in the struct.
222235
name string
223236
alias string
237+
// canonicalAlias is almost the same as the alias, but is prefixed with
238+
// an embedded struct field alias in dotted notation if this field is
239+
// promoted from the struct.
240+
// For instance, if the alias is "N" and this field is an embedded field
241+
// in a struct "X", canonicalAlias will be "X.N".
242+
canonicalAlias string
224243
// unmarshalerInfo contains information regarding the
225244
// encoding.TextUnmarshaler implementation of the field type.
226245
unmarshalerInfo unmarshaler
@@ -231,6 +250,13 @@ type fieldInfo struct {
231250
isRequired bool
232251
}
233252

253+
func (f *fieldInfo) paths(prefix string) []string {
254+
if f.alias == f.canonicalAlias {
255+
return []string{prefix + f.alias}
256+
}
257+
return []string{prefix + f.alias, prefix + f.canonicalAlias}
258+
}
259+
234260
type pathPart struct {
235261
field *fieldInfo
236262
path []string // path to the field: walks structs using field names.
@@ -239,6 +265,13 @@ type pathPart struct {
239265

240266
// ----------------------------------------------------------------------------
241267

268+
func indirectType(typ reflect.Type) reflect.Type {
269+
if typ.Kind() == reflect.Ptr {
270+
return typ.Elem()
271+
}
272+
return typ
273+
}
274+
242275
// fieldAlias parses a field tag to get a field alias.
243276
func fieldAlias(field reflect.StructField, tagName string) (alias string, options tagOptions) {
244277
if tag := field.Tag.Get(tagName); tag != "" {

decoder.go

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -81,52 +81,83 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
8181
errors[path] = err
8282
}
8383
} else if !d.ignoreUnknownKeys {
84-
errors[path] = fmt.Errorf("schema: invalid path %q", path)
84+
errors[path] = UnknownKeyError{Key: path}
8585
}
8686
}
87+
errors.merge(d.checkRequired(t, src))
8788
if len(errors) > 0 {
8889
return errors
8990
}
90-
return d.checkRequired(t, src, "")
91+
return nil
9192
}
9293

9394
// checkRequired checks whether required fields are empty
9495
//
95-
// check type t recursively if t has struct fields, and prefix is same as parsePath: in dotted notation
96+
// check type t recursively if t has struct fields.
9697
//
9798
// src is the source map for decoding, we use it here to see if those required fields are included in src
98-
func (d *Decoder) checkRequired(t reflect.Type, src map[string][]string, prefix string) error {
99+
func (d *Decoder) checkRequired(t reflect.Type, src map[string][]string) MultiError {
100+
m, errs := d.findRequiredFields(t, "", "")
101+
for key, fields := range m {
102+
if isEmptyFields(fields, src) {
103+
errs[key] = EmptyFieldError{Key: key}
104+
}
105+
}
106+
return errs
107+
}
108+
109+
// findRequiredFields recursively searches the struct type t for required fields.
110+
//
111+
// canonicalPrefix and searchPrefix are used to resolve full paths in dotted notation
112+
// for nested struct fields. canonicalPrefix is a complete path which never omits
113+
// any embedded struct fields. searchPrefix is a user-friendly path which may omit
114+
// some embedded struct fields to point promoted fields.
115+
func (d *Decoder) findRequiredFields(t reflect.Type, canonicalPrefix, searchPrefix string) (map[string][]fieldWithPrefix, MultiError) {
99116
struc := d.cache.get(t)
100117
if struc == nil {
101118
// unexpect, cache.get never return nil
102-
return errors.New("cache fail")
119+
return nil, MultiError{canonicalPrefix + "*": errors.New("cache fail")}
103120
}
104121

122+
m := map[string][]fieldWithPrefix{}
123+
errs := MultiError{}
105124
for _, f := range struc.fields {
106125
if f.typ.Kind() == reflect.Struct {
107-
err := d.checkRequired(f.typ, src, prefix+f.alias+".")
108-
if err != nil {
109-
if !f.isAnonymous {
110-
return err
111-
}
112-
// check embedded parent field.
113-
err2 := d.checkRequired(f.typ, src, prefix)
114-
if err2 != nil {
115-
return err2
126+
fcprefix := canonicalPrefix + f.canonicalAlias + "."
127+
for _, fspath := range f.paths(searchPrefix) {
128+
fm, ferrs := d.findRequiredFields(f.typ, fcprefix, fspath+".")
129+
for key, fields := range fm {
130+
m[key] = append(m[key], fields...)
116131
}
132+
errs.merge(ferrs)
117133
}
118134
}
119135
if f.isRequired {
120-
key := f.alias
121-
if prefix != "" {
122-
key = prefix + key
123-
}
124-
if isEmpty(f.typ, src[key]) {
125-
return fmt.Errorf("%v is empty", key)
136+
key := canonicalPrefix + f.canonicalAlias
137+
m[key] = append(m[key], fieldWithPrefix{
138+
fieldInfo: f,
139+
prefix: searchPrefix,
140+
})
141+
}
142+
}
143+
return m, errs
144+
}
145+
146+
type fieldWithPrefix struct {
147+
*fieldInfo
148+
prefix string
149+
}
150+
151+
// isEmptyFields returns true if all of specified fields are empty.
152+
func isEmptyFields(fields []fieldWithPrefix, src map[string][]string) bool {
153+
for _, f := range fields {
154+
for _, path := range f.paths(f.prefix) {
155+
if !isEmpty(f.typ, src[path]) {
156+
return false
126157
}
127158
}
128159
}
129-
return nil
160+
return true
130161
}
131162

132163
// isEmpty returns true if value is empty for specific type
@@ -424,6 +455,24 @@ func (e ConversionError) Error() string {
424455
return output
425456
}
426457

458+
// UnknownKeyError stores information about an unknown key in the source map.
459+
type UnknownKeyError struct {
460+
Key string // key from the source map.
461+
}
462+
463+
func (e UnknownKeyError) Error() string {
464+
return fmt.Sprintf("schema: invalid path %q", e.Key)
465+
}
466+
467+
// EmptyFieldError stores information about an empty required field.
468+
type EmptyFieldError struct {
469+
Key string // required key in the source map.
470+
}
471+
472+
func (e EmptyFieldError) Error() string {
473+
return fmt.Sprintf("%v is empty", e.Key)
474+
}
475+
427476
// MultiError stores multiple decoding errors.
428477
//
429478
// Borrowed from the App Engine SDK.
@@ -445,3 +494,11 @@ func (e MultiError) Error() string {
445494
}
446495
return fmt.Sprintf("%s (and %d other errors)", s, len(e)-1)
447496
}
497+
498+
func (e MultiError) merge(errors MultiError) {
499+
for key, err := range errors {
500+
if e[key] == nil {
501+
e[key] = err
502+
}
503+
}
504+
}

0 commit comments

Comments
 (0)