2019-01-15 06:32:05 -08:00
|
|
|
package runtime
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"io"
|
|
|
|
"strings"
|
|
|
|
|
2020-02-17 13:13:33 -08:00
|
|
|
descriptor2 "github.com/golang/protobuf/descriptor"
|
|
|
|
"github.com/golang/protobuf/protoc-gen-go/descriptor"
|
2019-01-15 06:32:05 -08:00
|
|
|
"google.golang.org/genproto/protobuf/field_mask"
|
|
|
|
)
|
|
|
|
|
2020-02-17 13:13:33 -08:00
|
|
|
func translateName(name string, md *descriptor.DescriptorProto) (string, *descriptor.DescriptorProto) {
|
|
|
|
// TODO - should really gate this with a test that the marshaller has used json names
|
|
|
|
if md != nil {
|
|
|
|
for _, f := range md.Field {
|
|
|
|
if f.JsonName != nil && f.Name != nil && *f.JsonName == name {
|
|
|
|
var subType *descriptor.DescriptorProto
|
|
|
|
|
|
|
|
// If the field has a TypeName then we retrieve the nested type for translating the embedded message names.
|
|
|
|
if f.TypeName != nil {
|
|
|
|
typeSplit := strings.Split(*f.TypeName, ".")
|
|
|
|
typeName := typeSplit[len(typeSplit)-1]
|
|
|
|
for _, t := range md.NestedType {
|
|
|
|
if typeName == *t.Name {
|
|
|
|
subType = t
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return *f.Name, subType
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return name, nil
|
|
|
|
}
|
|
|
|
|
2019-01-15 06:32:05 -08:00
|
|
|
// FieldMaskFromRequestBody creates a FieldMask printing all complete paths from the JSON body.
|
2020-02-17 13:13:33 -08:00
|
|
|
func FieldMaskFromRequestBody(r io.Reader, md *descriptor.DescriptorProto) (*field_mask.FieldMask, error) {
|
2019-01-15 06:32:05 -08:00
|
|
|
fm := &field_mask.FieldMask{}
|
|
|
|
var root interface{}
|
|
|
|
if err := json.NewDecoder(r).Decode(&root); err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
return fm, nil
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-02-17 13:13:33 -08:00
|
|
|
queue := []fieldMaskPathItem{{node: root, md: md}}
|
2019-01-15 06:32:05 -08:00
|
|
|
for len(queue) > 0 {
|
|
|
|
// dequeue an item
|
|
|
|
item := queue[0]
|
|
|
|
queue = queue[1:]
|
|
|
|
|
|
|
|
if m, ok := item.node.(map[string]interface{}); ok {
|
|
|
|
// if the item is an object, then enqueue all of its children
|
|
|
|
for k, v := range m {
|
2020-02-17 13:13:33 -08:00
|
|
|
protoName, subMd := translateName(k, item.md)
|
|
|
|
if subMsg, ok := v.(descriptor2.Message); ok {
|
|
|
|
_, subMd = descriptor2.ForMessage(subMsg)
|
|
|
|
}
|
|
|
|
queue = append(queue, fieldMaskPathItem{path: append(item.path, protoName), node: v, md: subMd})
|
2019-01-15 06:32:05 -08:00
|
|
|
}
|
|
|
|
} else if len(item.path) > 0 {
|
|
|
|
// otherwise, it's a leaf node so print its path
|
|
|
|
fm.Paths = append(fm.Paths, strings.Join(item.path, "."))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fm, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fieldMaskPathItem stores a in-progress deconstruction of a path for a fieldmask
|
|
|
|
type fieldMaskPathItem struct {
|
|
|
|
// the list of prior fields leading up to node
|
|
|
|
path []string
|
|
|
|
|
|
|
|
// a generic decoded json object the current item to inspect for further path extraction
|
|
|
|
node interface{}
|
|
|
|
|
2020-02-17 13:13:33 -08:00
|
|
|
// descriptor for parent message
|
|
|
|
md *descriptor.DescriptorProto
|
2019-01-15 06:32:05 -08:00
|
|
|
}
|