Skip to content

Commit c96d308

Browse files
[protobuf-schema] Use snake_case for protobuf fields, UPPER_SNAKE_CASE for enums. (#20696)
* protobuf enum prefix use upper underscore Add json name parameters and change parameter field name to snake case * rerun generate-samples.sh * Add CI test * rebase master --------- Co-authored-by: xil <[email protected]>
1 parent 90de8dc commit c96d308

File tree

27 files changed

+637
-31
lines changed

27 files changed

+637
-31
lines changed
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Samples Protobuf
2+
on:
3+
push:
4+
paths:
5+
- .github/workflows/samples-protobuf.yaml
6+
- samples/config/petstore/protobuf-schema/**
7+
- samples/config/petstore/protobuf-schema-config/**
8+
pull_request:
9+
paths:
10+
- .github/workflows/samples-protobuf.yaml
11+
- samples/config/petstore/protobuf-schema/**
12+
- samples/config/petstore/protobuf-schema-config/**
13+
jobs:
14+
build:
15+
name: Build Protobuf Client
16+
runs-on: ubuntu-latest
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
sample:
21+
- 'samples/config/petstore/protobuf-schema/'
22+
- 'samples/config/petstore/protobuf-schema-config/'
23+
steps:
24+
- uses: actions/checkout@v4
25+
- name: Install Protocol Buffers Compiler
26+
run: |
27+
sudo apt-get update
28+
sudo apt-get install -y protobuf-compiler
29+
- name: Generate Protobuf Schema
30+
working-directory: ${{ matrix.sample }}
31+
run: |
32+
mkdir out
33+
protoc --proto_path=. --cpp_out=out models/*.proto services/*.proto
34+
- name: Verify Generated Files
35+
working-directory: ${{ matrix.sample }}
36+
run: |
37+
ls -l out/models
38+
ls -l out/services
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
generatorName: protobuf-schema
2+
outputDir: samples/config/petstore/protobuf-schema-config
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/protobuf/petstore.yaml
4+
templateDir: modules/openapi-generator/src/main/resources/protobuf-schema
5+
additionalProperties:
6+
packageName: petstore
7+
addJsonNameAnnotation: true
8+
numberedFieldNumberList: true
9+
startEnumsWithUnspecified: true

docs/generators/protobuf-schema.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
1818

1919
| Option | Description | Values | Default |
2020
| ------ | ----------- | ------ | ------- |
21+
|addJsonNameAnnotation|Append &quot;json_name&quot; annotation to message field when the specification name differs from the protobuf field name| |false|
2122
|numberedFieldNumberList|Field numbers in order.| |false|
2223
|startEnumsWithUnspecified|Introduces &quot;UNSPECIFIED&quot; as the first element of enumerations.| |false|
2324

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java

+54-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.*;
3939
import java.util.Map.Entry;
4040
import java.util.regex.Pattern;
41+
import com.google.common.base.CaseFormat;
4142

4243
import static org.openapitools.codegen.utils.StringUtils.camelize;
4344
import static org.openapitools.codegen.utils.StringUtils.underscore;
@@ -52,6 +53,8 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
5253

5354
public static final String START_ENUMS_WITH_UNSPECIFIED = "startEnumsWithUnspecified";
5455

56+
public static final String ADD_JSON_NAME_ANNOTATION = "addJsonNameAnnotation";
57+
5558
private final Logger LOGGER = LoggerFactory.getLogger(ProtobufSchemaCodegen.class);
5659

5760
@Setter protected String packageName = "openapitools";
@@ -60,11 +63,18 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
6063

6164
private boolean startEnumsWithUnspecified = false;
6265

66+
private boolean addJsonNameAnnotation = false;
67+
6368
@Override
6469
public CodegenType getTag() {
6570
return CodegenType.SCHEMA;
6671
}
6772

73+
@Override
74+
public String toEnumName(CodegenProperty property) {
75+
return StringUtils.capitalize(property.name);
76+
}
77+
6878
@Override
6979
public String getName() {
7080
return "protobuf-schema";
@@ -163,6 +173,7 @@ public ProtobufSchemaCodegen() {
163173

164174
addSwitch(NUMBERED_FIELD_NUMBER_LIST, "Field numbers in order.", numberedFieldNumberList);
165175
addSwitch(START_ENUMS_WITH_UNSPECIFIED, "Introduces \"UNSPECIFIED\" as the first element of enumerations.", startEnumsWithUnspecified);
176+
addSwitch(ADD_JSON_NAME_ANNOTATION, "Append \"json_name\" annotation to message field when the specification name differs from the protobuf field name", addJsonNameAnnotation);
166177
}
167178

168179
@Override
@@ -197,6 +208,10 @@ public void processOpts() {
197208
this.startEnumsWithUnspecified = convertPropertyToBooleanAndWriteBack(START_ENUMS_WITH_UNSPECIFIED);
198209
}
199210

211+
if (additionalProperties.containsKey(this.ADD_JSON_NAME_ANNOTATION)) {
212+
this.addJsonNameAnnotation = convertPropertyToBooleanAndWriteBack(ADD_JSON_NAME_ANNOTATION);
213+
}
214+
200215
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
201216
}
202217

@@ -226,7 +241,7 @@ public String toOperationId(String operationId) {
226241
public void addEnumValuesPrefix(Map<String, Object> allowableValues, String prefix) {
227242
if (allowableValues.containsKey("enumVars")) {
228243
List<Map<String, Object>> enumVars = (List<Map<String, Object>>) allowableValues.get("enumVars");
229-
244+
prefix = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, prefix);
230245
for (Map<String, Object> value : enumVars) {
231246
String name = (String) value.get("name");
232247
value.put("name", prefix + "_" + name);
@@ -338,6 +353,10 @@ public ModelsMap postProcessModels(ModelsMap objs) {
338353
var.vendorExtensions.putIfAbsent("x-protobuf-index", "Generated field number is in reserved range (19000, 19999)");
339354
}
340355
}
356+
357+
if (addJsonNameAnnotation && !var.baseName.equals(var.name)) {
358+
var.vendorExtensions.put("x-protobuf-json-name", var.baseName);
359+
}
341360
}
342361
}
343362
return objs;
@@ -493,10 +512,38 @@ public String toModelFilename(String name) {
493512
}
494513

495514
@Override
496-
public String toVarName(final String name) {
515+
public String toVarName(String name) {
516+
if (nameMapping.containsKey(name)) {
517+
return nameMapping.get(name);
518+
}
519+
// sanitize name
520+
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
521+
522+
// if it's all upper case, convert to lower case
523+
if (name.matches("^[A-Z_]*$")) {
524+
name = name.toLowerCase(Locale.ROOT);
525+
}
526+
527+
// underscore the variable name
528+
// petId => pet_id
529+
name = underscore(name);
530+
531+
// remove leading underscore
532+
name = name.replaceAll("^_*", "");
533+
534+
// for reserved word or word starting with number, append _
535+
if (isReservedWord(name) || name.matches("^\\d.*")) {
536+
name = escapeReservedWord(name);
537+
}
538+
497539
return name;
498540
}
499541

542+
@Override
543+
public String toParamName(String name) {
544+
return toVarName(name);
545+
}
546+
500547
@Override
501548
public String toModelName(String name) {
502549
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
@@ -571,6 +618,10 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
571618
}
572619
}
573620

621+
if (addJsonNameAnnotation && !p.baseName.equals(p.paramName) && !p.isBodyParam) {
622+
p.vendorExtensions.put("x-protobuf-json-name", p.baseName);
623+
}
624+
574625
p.vendorExtensions.putIfAbsent("x-protobuf-index", index);
575626
index++;
576627
}
@@ -646,4 +697,5 @@ private boolean parentVarsContainsVar(List<CodegenProperty> parentVars, CodegenP
646697
public GeneratorLanguage generatorLanguage() {
647698
return GeneratorLanguage.PROTOBUF;
648699
}
700+
649701
}

modules/openapi-generator/src/main/resources/protobuf-schema/api.mustache

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ message {{operationId}}Request {
3030
{{#description}}
3131
// {{{.}}}
3232
{{/description}}
33-
{{#vendorExtensions.x-protobuf-type}}{{.}} {{/vendorExtensions.x-protobuf-type}}{{vendorExtensions.x-protobuf-data-type}} {{paramName}} = {{vendorExtensions.x-protobuf-index}};
33+
{{#vendorExtensions.x-protobuf-type}}{{.}} {{/vendorExtensions.x-protobuf-type}}{{vendorExtensions.x-protobuf-data-type}} {{paramName}} = {{vendorExtensions.x-protobuf-index}}{{#vendorExtensions.x-protobuf-json-name}} [json_name="{{vendorExtensions.x-protobuf-json-name}}"]{{/vendorExtensions.x-protobuf-json-name}};
3434
{{/allParams}}
3535

3636
}

modules/openapi-generator/src/main/resources/protobuf-schema/model.mustache

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import public "{{{modelPackage}}}/{{{import}}}.proto";
1818
// {{{.}}}
1919
{{/description}}
2020
{{^isEnum}}
21-
{{#vendorExtensions.x-protobuf-type}}{{{.}}} {{/vendorExtensions.x-protobuf-type}}{{{vendorExtensions.x-protobuf-data-type}}} {{{name}}} = {{vendorExtensions.x-protobuf-index}}{{#vendorExtensions.x-protobuf-packed}} [packed=true]{{/vendorExtensions.x-protobuf-packed}};
21+
{{#vendorExtensions.x-protobuf-type}}{{{.}}} {{/vendorExtensions.x-protobuf-type}}{{{vendorExtensions.x-protobuf-data-type}}} {{{name}}} = {{vendorExtensions.x-protobuf-index}}{{#vendorExtensions.x-protobuf-packed}} [packed=true]{{/vendorExtensions.x-protobuf-packed}}{{#vendorExtensions.x-protobuf-json-name}} [json_name="{{vendorExtensions.x-protobuf-json-name}}"]{{/vendorExtensions.x-protobuf-json-name}};
2222
{{/isEnum}}
2323
{{#isEnum}}
2424
enum {{enumName}} {

modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,6 @@ public void modelTest() {
9191
final CodegenModel simpleName = codegen.fromModel("$DollarModel$", openAPI.getComponents().getSchemas().get("$DollarModel$"));
9292
Assert.assertEquals(simpleName.name, "$DollarModel$");
9393
Assert.assertEquals(simpleName.classname, "DollarModel");
94-
Assert.assertEquals(simpleName.classVarName, "$DollarModel$");
94+
Assert.assertEquals(simpleName.classVarName, "dollar_model");
9595
}
9696
}

modules/openapi-generator/src/test/resources/3_0/protobuf-schema/pet.proto

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ package openapitools;
1414

1515
message Pet {
1616

17-
string petType = 140636936;
17+
string pet_type = 482112090;
1818

1919
string name = 3373707;
2020

2121
string bark = 3016376;
2222

23-
bool lovesRocks = 499337491;
23+
bool loves_rocks = 465093427;
2424

2525
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# OpenAPI Generator Ignore
2+
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
3+
4+
# Use this file to prevent files from being overwritten by the generator.
5+
# The patterns follow closely to .gitignore or .dockerignore.
6+
7+
# As an example, the C# client generator defines ApiClient.cs.
8+
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9+
#ApiClient.cs
10+
11+
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
12+
#foo/*/qux
13+
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14+
15+
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16+
#foo/**/qux
17+
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18+
19+
# You can also negate patterns with an exclamation (!).
20+
# For example, you can ignore all files in a docs folder with the file extension .md:
21+
#docs/*.md
22+
# Then explicitly reverse the ignore rule for a single file:
23+
#!docs/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
README.md
2+
models/api_response.proto
3+
models/category.proto
4+
models/order.proto
5+
models/other_test.proto
6+
models/pet.proto
7+
models/tag.proto
8+
models/user.proto
9+
services/pet_service.proto
10+
services/store_service.proto
11+
services/user_service.proto
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7.13.0-SNAPSHOT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# gPRC for petstore
2+
3+
This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
4+
5+
## Overview
6+
These files were generated by the [OpenAPI Generator](https://openapi-generator.tech) project.
7+
8+
- API version: 1.0.0
9+
- Package version:
10+
- Generator version: 7.13.0-SNAPSHOT
11+
- Build package: org.openapitools.codegen.languages.ProtobufSchemaCodegen
12+
13+
## Usage
14+
15+
Below are some usage examples for Go and Ruby. For other languages, please refer to https://grpc.io/docs/quickstart/.
16+
17+
### Go
18+
```
19+
# assuming `protoc-gen-go` has been installed with `go get -u github.com/golang/protobuf/protoc-gen-go`
20+
mkdir /var/tmp/go/petstore
21+
protoc --go_out=/var/tmp/go/petstore services/*
22+
protoc --go_out=/var/tmp/go/petstore models/*
23+
```
24+
25+
### Ruby
26+
```
27+
# assuming `grpc_tools_ruby_protoc` has been installed via `gem install grpc-tools`
28+
RUBY_OUTPUT_DIR="/var/tmp/ruby/petstore"
29+
mkdir $RUBY_OUTPUT_DIR
30+
grpc_tools_ruby_protoc --ruby_out=$RUBY_OUTPUT_DIR --grpc_out=$RUBY_OUTPUT_DIR/lib services/*
31+
grpc_tools_ruby_protoc --ruby_out=$RUBY_OUTPUT_DIR --grpc_out=$RUBY_OUTPUT_DIR/lib models/*
32+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
OpenAPI Petstore
3+
4+
This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
5+
6+
The version of the OpenAPI document: 1.0.0
7+
8+
Generated by OpenAPI Generator: https://openapi-generator.tech
9+
*/
10+
11+
syntax = "proto3";
12+
13+
package petstore;
14+
15+
16+
message ApiResponse {
17+
18+
int32 code = 1;
19+
20+
string type = 2;
21+
22+
string message = 3;
23+
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
OpenAPI Petstore
3+
4+
This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
5+
6+
The version of the OpenAPI document: 1.0.0
7+
8+
Generated by OpenAPI Generator: https://openapi-generator.tech
9+
*/
10+
11+
syntax = "proto3";
12+
13+
package petstore;
14+
15+
16+
message Category {
17+
18+
int64 id = 1;
19+
20+
string name = 2;
21+
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
OpenAPI Petstore
3+
4+
This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
5+
6+
The version of the OpenAPI document: 1.0.0
7+
8+
Generated by OpenAPI Generator: https://openapi-generator.tech
9+
*/
10+
11+
syntax = "proto3";
12+
13+
package petstore;
14+
15+
16+
message Order {
17+
18+
int64 id = 1;
19+
20+
int64 pet_id = 2 [json_name="petId"];
21+
22+
int32 quantity = 3;
23+
24+
string ship_date = 4 [json_name="shipDate"];
25+
26+
// Order Status
27+
enum Status {
28+
STATUS_UNSPECIFIED = 0;
29+
STATUS_PLACED = 1;
30+
STATUS_APPROVED = 2;
31+
STATUS_DELIVERED = 3;
32+
}
33+
34+
Status status = 5;
35+
36+
bool complete = 6;
37+
38+
}

0 commit comments

Comments
 (0)