blob: aef645e40b92df75da6af9fd61f7de952ba36748 [file] [log] [blame]
khenaidoo59ce9dd2019-11-11 13:05:32 -05001package runtime
2
3import (
4 "encoding/json"
5 "io"
6 "strings"
7
khenaidoo26721882021-08-11 17:42:52 -04008 descriptor2 "github.com/golang/protobuf/descriptor"
9 "github.com/golang/protobuf/protoc-gen-go/descriptor"
khenaidoo59ce9dd2019-11-11 13:05:32 -050010 "google.golang.org/genproto/protobuf/field_mask"
11)
12
khenaidoo26721882021-08-11 17:42:52 -040013func translateName(name string, md *descriptor.DescriptorProto) (string, *descriptor.DescriptorProto) {
14 // TODO - should really gate this with a test that the marshaller has used json names
15 if md != nil {
16 for _, f := range md.Field {
17 if f.JsonName != nil && f.Name != nil && *f.JsonName == name {
18 var subType *descriptor.DescriptorProto
19
20 // If the field has a TypeName then we retrieve the nested type for translating the embedded message names.
21 if f.TypeName != nil {
22 typeSplit := strings.Split(*f.TypeName, ".")
23 typeName := typeSplit[len(typeSplit)-1]
24 for _, t := range md.NestedType {
25 if typeName == *t.Name {
26 subType = t
27 }
28 }
29 }
30 return *f.Name, subType
31 }
32 }
33 }
34 return name, nil
35}
36
khenaidoo59ce9dd2019-11-11 13:05:32 -050037// FieldMaskFromRequestBody creates a FieldMask printing all complete paths from the JSON body.
khenaidoo26721882021-08-11 17:42:52 -040038func FieldMaskFromRequestBody(r io.Reader, md *descriptor.DescriptorProto) (*field_mask.FieldMask, error) {
khenaidoo59ce9dd2019-11-11 13:05:32 -050039 fm := &field_mask.FieldMask{}
40 var root interface{}
41 if err := json.NewDecoder(r).Decode(&root); err != nil {
42 if err == io.EOF {
43 return fm, nil
44 }
45 return nil, err
46 }
47
khenaidoo26721882021-08-11 17:42:52 -040048 queue := []fieldMaskPathItem{{node: root, md: md}}
khenaidoo59ce9dd2019-11-11 13:05:32 -050049 for len(queue) > 0 {
50 // dequeue an item
51 item := queue[0]
52 queue = queue[1:]
53
54 if m, ok := item.node.(map[string]interface{}); ok {
55 // if the item is an object, then enqueue all of its children
56 for k, v := range m {
khenaidoo26721882021-08-11 17:42:52 -040057 protoName, subMd := translateName(k, item.md)
58 if subMsg, ok := v.(descriptor2.Message); ok {
59 _, subMd = descriptor2.ForMessage(subMsg)
60 }
61
62 var path string
63 if item.path == "" {
64 path = protoName
65 } else {
66 path = item.path + "." + protoName
67 }
68 queue = append(queue, fieldMaskPathItem{path: path, node: v, md: subMd})
khenaidoo59ce9dd2019-11-11 13:05:32 -050069 }
70 } else if len(item.path) > 0 {
71 // otherwise, it's a leaf node so print its path
khenaidoo26721882021-08-11 17:42:52 -040072 fm.Paths = append(fm.Paths, item.path)
khenaidoo59ce9dd2019-11-11 13:05:32 -050073 }
74 }
75
76 return fm, nil
77}
78
79// fieldMaskPathItem stores a in-progress deconstruction of a path for a fieldmask
80type fieldMaskPathItem struct {
khenaidoo26721882021-08-11 17:42:52 -040081 // the list of prior fields leading up to node connected by dots
82 path string
khenaidoo59ce9dd2019-11-11 13:05:32 -050083
84 // a generic decoded json object the current item to inspect for further path extraction
85 node interface{}
khenaidoo59ce9dd2019-11-11 13:05:32 -050086
khenaidoo26721882021-08-11 17:42:52 -040087 // descriptor for parent message
88 md *descriptor.DescriptorProto
khenaidoo59ce9dd2019-11-11 13:05:32 -050089}