Skip to content

Commit 725cd54

Browse files
committed
feat: add support for custom codecs in gRPC server configuration
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent c2099f4 commit 725cd54

File tree

3 files changed

+348
-27
lines changed

3 files changed

+348
-27
lines changed

cmd/protoc-gen-elixir-grpc/README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ This generates:
5858

5959
```elixir
6060
defmodule Greeter.Server do
61-
use GRPC.Server, service: Greeter.Service, http_transcode: true
61+
use GRPC.Server,
62+
service: Greeter.Service,
63+
http_transcode: true
6264
6365
# ... method delegates
6466
end
@@ -79,6 +81,33 @@ plugins:
7981
- handler_module_prefix=MyApp.Handlers
8082
```
8183

84+
#### Custom Codecs
85+
86+
Specify custom codec modules for your gRPC server:
87+
88+
```yaml
89+
version: v2
90+
plugins:
91+
- local: protoc-gen-elixir
92+
out: lib
93+
- local: protoc-gen-elixir-grpc
94+
out: lib
95+
opt:
96+
- codecs=GRPC.Codec.Proto,GRPC.Codec.WebText,GRPC.Codec.JSON
97+
```
98+
99+
This generates:
100+
101+
```elixir
102+
defmodule Greeter.Server do
103+
use GRPC.Server,
104+
service: Greeter.Service,
105+
codecs: [GRPC.Codec.Proto, GRPC.Codec.WebText, GRPC.Codec.JSON]
106+
107+
# ... method delegates
108+
end
109+
```
110+
82111
#### Combining Options
83112

84113
You can combine multiple options:
@@ -93,6 +122,7 @@ plugins:
93122
opt:
94123
- http_transcode=true
95124
- handler_module_prefix=MyApp.Handlers
125+
- codecs=GRPC.Codec.Proto,GRPC.Codec.WebText,GRPC.Codec.JSON
96126
```
97127

98128
### Basic Service Implementation

cmd/protoc-gen-elixir-grpc/main.go

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,33 +54,89 @@ const (
5454
packagePrefixFlag = "package_prefix"
5555
handlerModulePrefixFlag = "handler_module_prefix"
5656
httpTranscodeFlag = "http_transcode"
57+
codecsFlag = "codecs"
5758

58-
usage = "\n\nFlags:\n -h, --help\tPrint this help and exit.\n --version\tPrint the version and exit.\n --handler_module_prefix\tCustom Elixir module prefix for handler modules instead of protobuf package.\n --http_transcode\tEnable HTTP transcoding support (adds http_transcode: true to use GRPC.Server)."
59+
usage = "\n\nFlags:\n -h, --help\tPrint this help and exit.\n --version\tPrint the version and exit.\n --handler_module_prefix\tCustom Elixir module prefix for handler modules instead of protobuf package.\n --http_transcode\tEnable HTTP transcoding support (adds http_transcode: true to use GRPC.Server).\n --codecs\tComma-separated list of codec modules (e.g., 'GRPC.Codec.Proto,GRPC.Codec.WebText,GRPC.Codec.JSON')."
5960
)
6061

6162
func parsePluginParameters(paramStr string, flagSet *flag.FlagSet) error {
6263
if paramStr == "" {
6364
return nil
6465
}
6566

66-
params := strings.Split(paramStr, ",")
67-
for _, param := range params {
68-
param = strings.TrimSpace(param)
69-
if param == "" {
67+
// Parse key=value pairs, handling values that may contain commas
68+
params := parseKeyValuePairs(paramStr)
69+
for key, value := range params {
70+
if err := flagSet.Set(key, value); err != nil {
71+
return err
72+
}
73+
}
74+
75+
return nil
76+
}
77+
78+
// parseKeyValuePairs parses a comma-separated list of key=value pairs.
79+
// It handles the special case where a value may contain commas (e.g., codecs=A,B,C).
80+
// The parser works by splitting on commas, then checking if each segment is a valid key=value pair.
81+
// If not, it's assumed to be a continuation of the previous value.
82+
func parseKeyValuePairs(paramStr string) map[string]string {
83+
result := make(map[string]string)
84+
85+
segments := strings.Split(paramStr, ",")
86+
var currentKey string
87+
var currentValue strings.Builder
88+
89+
for i, segment := range segments {
90+
segment = strings.TrimSpace(segment)
91+
if segment == "" {
7092
continue
7193
}
7294

73-
parts := strings.SplitN(param, "=", 2)
74-
if len(parts) != 2 {
75-
return fmt.Errorf("invalid parameter format: %q, expected key=value", param)
95+
// Check if this segment contains an '=' sign
96+
if idx := strings.Index(segment, "="); idx > 0 {
97+
// This is a new key=value pair
98+
// Save the previous key-value if exists
99+
if currentKey != "" {
100+
result[currentKey] = currentValue.String()
101+
}
102+
103+
// Start new key-value
104+
currentKey = strings.TrimSpace(segment[:idx])
105+
valueStart := strings.TrimSpace(segment[idx+1:])
106+
currentValue.Reset()
107+
currentValue.WriteString(valueStart)
108+
} else {
109+
// This is a continuation of the current value
110+
if currentKey != "" {
111+
currentValue.WriteString(",")
112+
currentValue.WriteString(segment)
113+
}
76114
}
77115

78-
if err := flagSet.Set(parts[0], parts[1]); err != nil {
79-
return err
116+
// If this is the last segment, save the current key-value
117+
if i == len(segments)-1 && currentKey != "" {
118+
result[currentKey] = currentValue.String()
80119
}
81120
}
82121

83-
return nil
122+
return result
123+
}
124+
125+
func parseCodecs(codecsStr string) []string {
126+
if codecsStr == "" {
127+
return nil
128+
}
129+
130+
codecs := strings.Split(codecsStr, ",")
131+
var result []string
132+
for _, codec := range codecs {
133+
codec = strings.TrimSpace(codec)
134+
if codec != "" {
135+
result = append(result, codec)
136+
}
137+
}
138+
139+
return result
84140
}
85141

86142
func main() {
@@ -117,6 +173,11 @@ func main() {
117173
false,
118174
"Enable HTTP transcoding support (adds http_transcode: true to use GRPC.Server).",
119175
)
176+
codecs := flagSet.String(
177+
codecsFlag,
178+
"",
179+
"Comma-separated list of codec modules (e.g., 'GRPC.Codec.Proto,GRPC.Codec.WebText,GRPC.Codec.JSON').",
180+
)
120181

121182
input, err := io.ReadAll(os.Stdin)
122183
if err != nil {
@@ -152,6 +213,8 @@ func main() {
152213
SupportedFeatures: proto.Uint64(uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)),
153214
}
154215

216+
codecsList := parseCodecs(*codecs)
217+
155218
for _, fileName := range req.FileToGenerate {
156219
var protoFile *descriptorpb.FileDescriptorProto
157220
for _, file := range req.ProtoFile {
@@ -168,7 +231,7 @@ func main() {
168231
continue
169232
}
170233

171-
generateElixirFile(resp, protoFile, *packagePrefix, *handlerModulePrefix, *httpTranscode)
234+
generateElixirFile(resp, protoFile, *packagePrefix, *handlerModulePrefix, *httpTranscode, codecsList)
172235
}
173236

174237
output, err := proto.Marshal(resp)
@@ -183,7 +246,7 @@ func main() {
183246
}
184247
}
185248

186-
func generateElixirFile(resp *pluginpb.CodeGeneratorResponse, file *descriptorpb.FileDescriptorProto, packagePrefix, handlerModulePrefix string, httpTranscode bool) {
249+
func generateElixirFile(resp *pluginpb.CodeGeneratorResponse, file *descriptorpb.FileDescriptorProto, packagePrefix, handlerModulePrefix string, httpTranscode bool, codecs []string) {
187250
if len(file.Service) == 0 {
188251
return
189252
}
@@ -197,7 +260,7 @@ func generateElixirFile(resp *pluginpb.CodeGeneratorResponse, file *descriptorpb
197260
content.WriteString("\n")
198261

199262
for _, service := range file.Service {
200-
generateServiceModule(&content, file, service, handlerModulePrefix, httpTranscode)
263+
generateServiceModule(&content, file, service, handlerModulePrefix, httpTranscode, codecs)
201264
content.WriteString("\n")
202265
}
203266

@@ -207,14 +270,25 @@ func generateElixirFile(resp *pluginpb.CodeGeneratorResponse, file *descriptorpb
207270
})
208271
}
209272

210-
func generateServiceModule(content *strings.Builder, file *descriptorpb.FileDescriptorProto, service *descriptorpb.ServiceDescriptorProto, handlerModulePrefix string, httpTranscode bool) {
273+
func generateServiceModule(content *strings.Builder, file *descriptorpb.FileDescriptorProto, service *descriptorpb.ServiceDescriptorProto, handlerModulePrefix string, httpTranscode bool, codecs []string) {
211274
serverModuleName := generateServerModuleName(file, service)
212275
serviceModuleName := generateServiceModuleName(file, service)
213276

214277
content.WriteString("defmodule " + serverModuleName + " do\n")
215-
content.WriteString(" use GRPC.Server, service: " + serviceModuleName)
278+
content.WriteString(" use GRPC.Server,\n")
279+
content.WriteString(" service: " + serviceModuleName)
216280
if httpTranscode {
217-
content.WriteString(", http_transcode: true")
281+
content.WriteString(",\n http_transcode: true")
282+
}
283+
if len(codecs) > 0 {
284+
content.WriteString(",\n codecs: [")
285+
for i, codec := range codecs {
286+
if i > 0 {
287+
content.WriteString(", ")
288+
}
289+
content.WriteString(codec)
290+
}
291+
content.WriteString("]")
218292
}
219293
content.WriteString("\n\n")
220294

0 commit comments

Comments
 (0)