Skip to content

Commit a1d5f44

Browse files
committed
feat: add C# and Python generators and extend TS client generation
1 parent 917521f commit a1d5f44

File tree

20 files changed

+1137
-15
lines changed

20 files changed

+1137
-15
lines changed

cmd/protoc-gen-csharp-http/main.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package main
2+
3+
import (
4+
"google.golang.org/protobuf/compiler/protogen"
5+
"google.golang.org/protobuf/types/pluginpb"
6+
7+
"github.com/SebastienMelki/sebuf/internal/csharpgen"
8+
)
9+
10+
func main() {
11+
options, cfg := csharpgen.NewOptions()
12+
options.Run(func(plugin *protogen.Plugin) error {
13+
plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
14+
gen := csharpgen.New(plugin, *cfg)
15+
return gen.Generate()
16+
})
17+
}

cmd/protoc-gen-py-client/main.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package main
2+
3+
import (
4+
"google.golang.org/protobuf/compiler/protogen"
5+
"google.golang.org/protobuf/types/pluginpb"
6+
7+
"github.com/SebastienMelki/sebuf/internal/pyclientgen"
8+
)
9+
10+
func main() {
11+
options := pyclientgen.NewOptions()
12+
options.Run(func(plugin *protogen.Plugin) error {
13+
plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
14+
gen := pyclientgen.New(plugin)
15+
return gen.Generate()
16+
})
17+
}

cmd/protoc-gen-ts-client/main.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
package main
22

33
import (
4+
"flag"
5+
46
"google.golang.org/protobuf/compiler/protogen"
57
"google.golang.org/protobuf/types/pluginpb"
68

79
"github.com/SebastienMelki/sebuf/internal/tsclientgen"
810
)
911

1012
func main() {
11-
options := protogen.Options{}
13+
var flags flag.FlagSet
14+
var fieldNames string
15+
flags.StringVar(&fieldNames, "field_names", "json", "TypeScript field naming: json or proto")
16+
17+
options := protogen.Options{ParamFunc: flags.Set}
1218

1319
options.Run(func(plugin *protogen.Plugin) error {
1420
plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
15-
gen := tsclientgen.New(plugin)
21+
gen := tsclientgen.New(plugin, tsclientgen.Options{FieldNames: fieldNames})
1622
return gen.Generate()
1723
})
1824
}

internal/contractmodel/model.go

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
package contractmodel
2+
3+
import (
4+
"sort"
5+
"strings"
6+
7+
"google.golang.org/protobuf/compiler/protogen"
8+
"google.golang.org/protobuf/reflect/protoreflect"
9+
)
10+
11+
const (
12+
StructFullName protoreflect.FullName = "google.protobuf.Struct"
13+
TimestampFullName protoreflect.FullName = "google.protobuf.Timestamp"
14+
)
15+
16+
type Kind int
17+
18+
const (
19+
KindScalar Kind = iota
20+
KindEnum
21+
KindMessage
22+
KindStruct
23+
KindTimestamp
24+
KindMap
25+
)
26+
27+
type TypeRef struct {
28+
Kind Kind
29+
Name string
30+
MapKey *TypeRef
31+
MapValue *TypeRef
32+
}
33+
34+
type Field struct {
35+
Name string
36+
Type *TypeRef
37+
Repeated bool
38+
}
39+
40+
type Enum struct {
41+
Name string
42+
Values []string
43+
}
44+
45+
type Message struct {
46+
Name string
47+
Fields []*Field
48+
}
49+
50+
type Method struct {
51+
Name string
52+
InputType string
53+
ResponseType string
54+
}
55+
56+
type Service struct {
57+
Name string
58+
Methods []*Method
59+
}
60+
61+
type Package struct {
62+
Name string
63+
Files []*protogen.File
64+
Enums []*Enum
65+
Messages []*Message
66+
Services []*Service
67+
}
68+
69+
type symbols struct {
70+
messages map[protoreflect.FullName]string
71+
enums map[protoreflect.FullName]string
72+
}
73+
74+
func Packages(files []*protogen.File) []*Package {
75+
byPackage := make(map[string][]*protogen.File)
76+
for _, file := range files {
77+
if !file.Generate {
78+
continue
79+
}
80+
pkg := string(file.Desc.Package())
81+
if pkg == "" {
82+
pkg = "default"
83+
}
84+
byPackage[pkg] = append(byPackage[pkg], file)
85+
}
86+
87+
names := make([]string, 0, len(byPackage))
88+
for name := range byPackage {
89+
names = append(names, name)
90+
}
91+
sort.Strings(names)
92+
93+
packages := make([]*Package, 0, len(names))
94+
for _, name := range names {
95+
filesForPackage := byPackage[name]
96+
sort.Slice(filesForPackage, func(i, j int) bool {
97+
return filesForPackage[i].Desc.Path() < filesForPackage[j].Desc.Path()
98+
})
99+
100+
table := buildSymbols(filesForPackage)
101+
pkg := &Package{Name: name, Files: filesForPackage}
102+
pkg.Enums = collectEnums(filesForPackage, table)
103+
pkg.Messages = collectMessages(filesForPackage, table)
104+
pkg.Services = collectServices(filesForPackage, table)
105+
packages = append(packages, pkg)
106+
}
107+
108+
return packages
109+
}
110+
111+
func buildSymbols(files []*protogen.File) *symbols {
112+
table := &symbols{
113+
messages: make(map[protoreflect.FullName]string),
114+
enums: make(map[protoreflect.FullName]string),
115+
}
116+
for _, file := range files {
117+
for _, enum := range file.Enums {
118+
table.enums[enum.Desc.FullName()] = enum.GoIdent.GoName
119+
}
120+
for _, msg := range file.Messages {
121+
walkMessageSymbols(msg, "", table)
122+
}
123+
}
124+
return table
125+
}
126+
127+
func walkMessageSymbols(msg *protogen.Message, prefix string, table *symbols) {
128+
name := prefix + msg.GoIdent.GoName
129+
if !msg.Desc.IsMapEntry() {
130+
table.messages[msg.Desc.FullName()] = name
131+
}
132+
for _, enum := range msg.Enums {
133+
table.enums[enum.Desc.FullName()] = name + enum.GoIdent.GoName
134+
}
135+
for _, nested := range msg.Messages {
136+
walkMessageSymbols(nested, name+"__", table)
137+
}
138+
}
139+
140+
func collectEnums(files []*protogen.File, table *symbols) []*Enum {
141+
var result []*Enum
142+
for _, file := range files {
143+
for _, enum := range file.Enums {
144+
result = append(result, &Enum{Name: table.enums[enum.Desc.FullName()], Values: enumValues(enum)})
145+
}
146+
for _, msg := range file.Messages {
147+
collectNestedEnums(msg, table, &result)
148+
}
149+
}
150+
return result
151+
}
152+
153+
func collectNestedEnums(msg *protogen.Message, table *symbols, out *[]*Enum) {
154+
for _, enum := range msg.Enums {
155+
*out = append(*out, &Enum{Name: table.enums[enum.Desc.FullName()], Values: enumValues(enum)})
156+
}
157+
for _, nested := range msg.Messages {
158+
collectNestedEnums(nested, table, out)
159+
}
160+
}
161+
162+
func enumValues(enum *protogen.Enum) []string {
163+
values := make([]string, 0, len(enum.Values))
164+
for _, value := range enum.Values {
165+
values = append(values, string(value.Desc.Name()))
166+
}
167+
return values
168+
}
169+
170+
func collectMessages(files []*protogen.File, table *symbols) []*Message {
171+
var result []*Message
172+
for _, file := range files {
173+
for _, msg := range file.Messages {
174+
collectMessage(msg, table, &result)
175+
}
176+
}
177+
return result
178+
}
179+
180+
func collectMessage(msg *protogen.Message, table *symbols, out *[]*Message) {
181+
if !msg.Desc.IsMapEntry() {
182+
fields := make([]*Field, 0, len(msg.Fields))
183+
for _, field := range msg.Fields {
184+
fields = append(fields, &Field{
185+
Name: string(field.Desc.Name()),
186+
Type: resolveType(field, table),
187+
Repeated: field.Desc.IsList() && !field.Desc.IsMap(),
188+
})
189+
}
190+
*out = append(*out, &Message{Name: table.messages[msg.Desc.FullName()], Fields: fields})
191+
}
192+
for _, nested := range msg.Messages {
193+
collectMessage(nested, table, out)
194+
}
195+
}
196+
197+
func collectServices(files []*protogen.File, table *symbols) []*Service {
198+
var result []*Service
199+
for _, file := range files {
200+
for _, service := range file.Services {
201+
methods := make([]*Method, 0, len(service.Methods))
202+
for _, method := range service.Methods {
203+
methods = append(methods, &Method{
204+
Name: method.GoName,
205+
InputType: resolveMessageName(method.Input, table),
206+
ResponseType: resolveMessageName(method.Output, table),
207+
})
208+
}
209+
result = append(result, &Service{Name: service.GoName, Methods: methods})
210+
}
211+
}
212+
return result
213+
}
214+
215+
func resolveMessageName(message *protogen.Message, table *symbols) string {
216+
if message == nil {
217+
return ""
218+
}
219+
if name, ok := table.messages[message.Desc.FullName()]; ok {
220+
return name
221+
}
222+
return message.GoIdent.GoName
223+
}
224+
225+
func resolveType(field *protogen.Field, table *symbols) *TypeRef {
226+
if field.Desc.IsMap() {
227+
keyField := field.Message.Fields[0]
228+
valueField := field.Message.Fields[1]
229+
return &TypeRef{
230+
Kind: KindMap,
231+
MapKey: scalarTypeRef(keyField.Desc.Kind()),
232+
MapValue: resolveMapValueType(valueField, table),
233+
}
234+
}
235+
236+
switch field.Desc.Kind() {
237+
case protoreflect.EnumKind:
238+
if name, ok := table.enums[field.Enum.Desc.FullName()]; ok {
239+
return &TypeRef{Kind: KindEnum, Name: name}
240+
}
241+
return &TypeRef{Kind: KindEnum, Name: field.Enum.GoIdent.GoName}
242+
case protoreflect.MessageKind, protoreflect.GroupKind:
243+
fullName := field.Message.Desc.FullName()
244+
switch fullName {
245+
case StructFullName:
246+
return &TypeRef{Kind: KindStruct}
247+
case TimestampFullName:
248+
return &TypeRef{Kind: KindTimestamp}
249+
default:
250+
return &TypeRef{Kind: KindMessage, Name: resolveMessageName(field.Message, table)}
251+
}
252+
default:
253+
return scalarTypeRef(field.Desc.Kind())
254+
}
255+
}
256+
257+
func resolveMapValueType(field *protogen.Field, table *symbols) *TypeRef {
258+
switch field.Desc.Kind() {
259+
case protoreflect.EnumKind:
260+
if name, ok := table.enums[field.Enum.Desc.FullName()]; ok {
261+
return &TypeRef{Kind: KindEnum, Name: name}
262+
}
263+
return &TypeRef{Kind: KindEnum, Name: field.Enum.GoIdent.GoName}
264+
case protoreflect.MessageKind, protoreflect.GroupKind:
265+
fullName := field.Message.Desc.FullName()
266+
switch fullName {
267+
case StructFullName:
268+
return &TypeRef{Kind: KindStruct}
269+
case TimestampFullName:
270+
return &TypeRef{Kind: KindTimestamp}
271+
default:
272+
return &TypeRef{Kind: KindMessage, Name: resolveMessageName(field.Message, table)}
273+
}
274+
default:
275+
return scalarTypeRef(field.Desc.Kind())
276+
}
277+
}
278+
279+
func scalarTypeRef(kind protoreflect.Kind) *TypeRef {
280+
return &TypeRef{Kind: KindScalar, Name: strings.ToLower(kind.String())}
281+
}

0 commit comments

Comments
 (0)