| 39 | var unknownFieldRe = regexp.MustCompile(`unknown field "([^"]+)"`) |
| 40 | |
| 41 | func (d *suggestingDecoder) Decode(v any) error { |
| 42 | err := d.inner.Decode(v) |
| 43 | if err == nil { |
| 44 | return nil |
| 45 | } |
| 46 | |
| 47 | match := unknownFieldRe.FindStringSubmatch(err.Error()) |
| 48 | if match == nil { |
| 49 | return err |
| 50 | } |
| 51 | unknownField := match[1] |
| 52 | |
| 53 | msg, ok := v.(proto.Message) |
| 54 | if !ok { |
| 55 | return err |
| 56 | } |
| 57 | |
| 58 | fields := msg.ProtoReflect().Descriptor().Fields() |
| 59 | var candidates []string |
| 60 | for i := 0; i < fields.Len(); i++ { |
| 61 | // protojson uses camelCase JSON names. |
| 62 | candidates = append(candidates, fields.Get(i).JSONName()) |
| 63 | } |
| 64 | |
| 65 | suggestions := findSimilar(unknownField, candidates, 3) |
| 66 | if len(suggestions) == 0 { |
| 67 | return errors.Wrapf(err, "valid fields: %s", strings.Join(candidates, ", ")) |
| 68 | } |
| 69 | return errors.Wrapf(err, "did you mean: %s?", strings.Join(suggestions, ", ")) |
| 70 | } |
| 71 | |
| 72 | // findSimilar returns up to maxResults field names sorted by edit distance. |
| 73 | // Only fields within a reasonable distance threshold are included. |