Skip to content

Commit 3febabf

Browse files
author
Kazuyoshi Kato
committed
Add protoc-gen-go-fieldpath
This command will replace protobuf/plugin/fieldpath when we migrate off from gogo/protobuf. See containerd#6564 for the detail. Signed-off-by: Kazuyoshi Kato <katokazu@amazon.com>
1 parent d128c37 commit 3febabf

File tree

5 files changed

+2106
-0
lines changed

5 files changed

+2106
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"google.golang.org/protobuf/compiler/protogen"
21+
"google.golang.org/protobuf/reflect/protoreflect"
22+
)
23+
24+
type generator struct {
25+
out *protogen.GeneratedFile
26+
}
27+
28+
func newGenerator(out *protogen.GeneratedFile) *generator {
29+
gen := generator{out: out}
30+
return &gen
31+
}
32+
33+
func (gen *generator) genFieldMethod(m *protogen.Message) {
34+
p := gen.out
35+
36+
p.P("// Field returns the value for the given fieldpath as a string, if defined.")
37+
p.P("// If the value is not defined, the second value will be false.")
38+
p.P("func (m *", m.GoIdent, ") Field(fieldpath []string) (string, bool) {")
39+
40+
var (
41+
fields []*protogen.Field
42+
unhandled []*protogen.Field
43+
)
44+
45+
for _, f := range m.Fields {
46+
if f.Desc.Kind() == protoreflect.BoolKind ||
47+
f.Desc.Kind() == protoreflect.StringKind ||
48+
isLabelsField(f) || isAnyField(f) || isMessageField(f) {
49+
fields = append(fields, f)
50+
} else {
51+
unhandled = append(unhandled, f)
52+
}
53+
54+
}
55+
56+
if len(fields) > 0 {
57+
p.P("if len(fieldpath) == 0 {")
58+
p.P(`return "", false`)
59+
p.P("}")
60+
61+
p.P("switch fieldpath[0] {")
62+
63+
for _, f := range unhandled {
64+
p.P("// unhandled: ", f.Desc.Name())
65+
}
66+
67+
for _, f := range fields {
68+
p.P(`case "`, f.Desc.Name(), `":`)
69+
switch {
70+
case isLabelsField(f):
71+
stringsJoin := gen.out.QualifiedGoIdent(protogen.GoIdent{
72+
GoImportPath: "strings",
73+
GoName: "Join",
74+
})
75+
76+
p.P(`// Labels fields have been special-cased by name. If this breaks,`)
77+
p.P(`// add better special casing to fieldpath plugin.`)
78+
p.P("if len(m.", f.GoName, ") == 0 {")
79+
p.P(`return "", false`)
80+
p.P("}")
81+
p.P("value, ok := m.", f.GoName, "[", stringsJoin, `(fieldpath[1:], ".")]`)
82+
p.P("return value, ok")
83+
case isAnyField(f):
84+
typeurlUnmarshalAny := gen.out.QualifiedGoIdent(protogen.GoIdent{
85+
GoImportPath: "github.com/containerd/typeurl",
86+
GoName: "UnmarshalAny",
87+
})
88+
89+
p.P("decoded, err := ", typeurlUnmarshalAny, "(m.", f.GoName, ")")
90+
p.P("if err != nil {")
91+
p.P(`return "", false`)
92+
p.P("}")
93+
p.P("adaptor, ok := decoded.(interface{ Field([]string) (string, bool) })")
94+
p.P("if !ok {")
95+
p.P(`return "", false`)
96+
p.P("}")
97+
p.P("return adaptor.Field(fieldpath[1:])")
98+
case isMessageField(f):
99+
p.P(`// NOTE(stevvooe): This is probably not correct in many cases.`)
100+
p.P(`// We assume that the target message also implements the Field`)
101+
p.P(`// method, which isn't likely true in a lot of cases.`)
102+
p.P(`//`)
103+
p.P(`// If you have a broken build and have found this comment,`)
104+
p.P(`// you may be closer to a solution.`)
105+
p.P("if m.", f.GoName, " == nil {")
106+
p.P(`return "", false`)
107+
p.P("}")
108+
p.P("return m.", f.GoName, ".Field(fieldpath[1:])")
109+
case f.Desc.Kind() == protoreflect.StringKind:
110+
p.P("return string(m.", f.GoName, "), len(m.", f.GoName, ") > 0")
111+
case f.Desc.Kind() == protoreflect.BoolKind:
112+
fmtSprint := gen.out.QualifiedGoIdent(protogen.GoIdent{
113+
GoImportPath: "fmt",
114+
GoName: "Sprint",
115+
})
116+
117+
p.P("return ", fmtSprint, "(m.", f.GoName, "), true")
118+
}
119+
}
120+
121+
p.P("}")
122+
} else {
123+
for _, f := range unhandled {
124+
p.P("// unhandled: ", f.Desc.Name())
125+
}
126+
}
127+
128+
p.P(`return "", false`)
129+
p.P("}")
130+
}
131+
132+
func isMessageField(f *protogen.Field) bool {
133+
return f.Desc.Kind() == protoreflect.MessageKind && f.GoIdent.GoName != "Timestamp"
134+
}
135+
136+
func isLabelsField(f *protogen.Field) bool {
137+
return f.Desc.Kind() == protoreflect.MessageKind && f.Desc.Name() == "labels"
138+
}
139+
140+
func isAnyField(f *protogen.Field) bool {
141+
return f.Desc.Kind() == protoreflect.MessageKind && f.GoIdent.GoName == "Any"
142+
}
143+
144+
func generate(plugin *protogen.Plugin, input *protogen.File) error {
145+
file := plugin.NewGeneratedFile(input.GeneratedFilenamePrefix+"_fieldpath.pb.go", input.GoImportPath)
146+
file.P("// Code generated by protoc-gen-go-fieldpath. DO NOT EDIT.")
147+
file.P("// source: ", input.Desc.Path())
148+
file.P("package ", input.GoPackageName)
149+
150+
gen := newGenerator(file)
151+
for _, m := range input.Messages {
152+
gen.genFieldMethod(m)
153+
}
154+
return nil
155+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"google.golang.org/protobuf/compiler/protogen"
21+
)
22+
23+
func main() {
24+
protogen.Options{}.Run(func(gen *protogen.Plugin) error {
25+
for _, f := range gen.Files {
26+
if !f.Generate {
27+
continue
28+
}
29+
if err := generate(gen, f); err != nil {
30+
return err
31+
}
32+
}
33+
return nil
34+
})
35+
}

0 commit comments

Comments
 (0)