1package runtime
2
3import (
4	"encoding/json"
5	"io"
6	"strings"
7
8	"github.com/golang/protobuf/protoc-gen-go/generator"
9	"google.golang.org/genproto/protobuf/field_mask"
10)
11
12// FieldMaskFromRequestBody creates a FieldMask printing all complete paths from the JSON body.
13func FieldMaskFromRequestBody(r io.Reader) (*field_mask.FieldMask, error) {
14	fm := &field_mask.FieldMask{}
15	var root interface{}
16	if err := json.NewDecoder(r).Decode(&root); err != nil {
17		if err == io.EOF {
18			return fm, nil
19		}
20		return nil, err
21	}
22
23	queue := []fieldMaskPathItem{{node: root}}
24	for len(queue) > 0 {
25		// dequeue an item
26		item := queue[0]
27		queue = queue[1:]
28
29		if m, ok := item.node.(map[string]interface{}); ok {
30			// if the item is an object, then enqueue all of its children
31			for k, v := range m {
32				queue = append(queue, fieldMaskPathItem{path: append(item.path, generator.CamelCase(k)), node: v})
33			}
34		} else if len(item.path) > 0 {
35			// otherwise, it's a leaf node so print its path
36			fm.Paths = append(fm.Paths, strings.Join(item.path, "."))
37		}
38	}
39
40	return fm, nil
41}
42
43// fieldMaskPathItem stores a in-progress deconstruction of a path for a fieldmask
44type fieldMaskPathItem struct {
45	// the list of prior fields leading up to node
46	path []string
47
48	// a generic decoded json object the current item to inspect for further path extraction
49	node interface{}
50}
51
52// CamelCaseFieldMask updates the given FieldMask by converting all of its paths to CamelCase, using the same heuristic
53// that's used for naming protobuf fields in Go.
54func CamelCaseFieldMask(mask *field_mask.FieldMask) {
55	if mask == nil || mask.Paths == nil {
56		return
57	}
58
59	var newPaths []string
60	for _, path := range mask.Paths {
61		lowerCasedParts := strings.Split(path, ".")
62		var camelCasedParts []string
63		for _, part := range lowerCasedParts {
64			camelCasedParts = append(camelCasedParts, generator.CamelCase(part))
65		}
66		newPaths = append(newPaths, strings.Join(camelCasedParts, "."))
67	}
68
69	mask.Paths = newPaths
70}
71