Skip to content

Commit

Permalink
[protobuf-schema] Use snake_case for protobuf fields, UPPER_SNAKE_CAS…
Browse files Browse the repository at this point in the history
…E 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]>
  • Loading branch information
lucy66hw and luckyxilu66 authored Mar 4, 2025
1 parent 90de8dc commit c96d308
Show file tree
Hide file tree
Showing 27 changed files with 637 additions and 31 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/samples-protobuf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Samples Protobuf
on:
push:
paths:
- .github/workflows/samples-protobuf.yaml
- samples/config/petstore/protobuf-schema/**
- samples/config/petstore/protobuf-schema-config/**
pull_request:
paths:
- .github/workflows/samples-protobuf.yaml
- samples/config/petstore/protobuf-schema/**
- samples/config/petstore/protobuf-schema-config/**
jobs:
build:
name: Build Protobuf Client
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sample:
- 'samples/config/petstore/protobuf-schema/'
- 'samples/config/petstore/protobuf-schema-config/'
steps:
- uses: actions/checkout@v4
- name: Install Protocol Buffers Compiler
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler
- name: Generate Protobuf Schema
working-directory: ${{ matrix.sample }}
run: |
mkdir out
protoc --proto_path=. --cpp_out=out models/*.proto services/*.proto
- name: Verify Generated Files
working-directory: ${{ matrix.sample }}
run: |
ls -l out/models
ls -l out/services
9 changes: 9 additions & 0 deletions bin/configs/protobuf-schema-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
generatorName: protobuf-schema
outputDir: samples/config/petstore/protobuf-schema-config
inputSpec: modules/openapi-generator/src/test/resources/3_0/protobuf/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/protobuf-schema
additionalProperties:
packageName: petstore
addJsonNameAnnotation: true
numberedFieldNumberList: true
startEnumsWithUnspecified: true
1 change: 1 addition & 0 deletions docs/generators/protobuf-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import com.google.common.base.CaseFormat;

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

public static final String START_ENUMS_WITH_UNSPECIFIED = "startEnumsWithUnspecified";

public static final String ADD_JSON_NAME_ANNOTATION = "addJsonNameAnnotation";

private final Logger LOGGER = LoggerFactory.getLogger(ProtobufSchemaCodegen.class);

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

private boolean startEnumsWithUnspecified = false;

private boolean addJsonNameAnnotation = false;

@Override
public CodegenType getTag() {
return CodegenType.SCHEMA;
}

@Override
public String toEnumName(CodegenProperty property) {
return StringUtils.capitalize(property.name);
}

@Override
public String getName() {
return "protobuf-schema";
Expand Down Expand Up @@ -163,6 +173,7 @@ public ProtobufSchemaCodegen() {

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

@Override
Expand Down Expand Up @@ -197,6 +208,10 @@ public void processOpts() {
this.startEnumsWithUnspecified = convertPropertyToBooleanAndWriteBack(START_ENUMS_WITH_UNSPECIFIED);
}

if (additionalProperties.containsKey(this.ADD_JSON_NAME_ANNOTATION)) {
this.addJsonNameAnnotation = convertPropertyToBooleanAndWriteBack(ADD_JSON_NAME_ANNOTATION);
}

supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
}

Expand Down Expand Up @@ -226,7 +241,7 @@ public String toOperationId(String operationId) {
public void addEnumValuesPrefix(Map<String, Object> allowableValues, String prefix) {
if (allowableValues.containsKey("enumVars")) {
List<Map<String, Object>> enumVars = (List<Map<String, Object>>) allowableValues.get("enumVars");

prefix = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, prefix);
for (Map<String, Object> value : enumVars) {
String name = (String) value.get("name");
value.put("name", prefix + "_" + name);
Expand Down Expand Up @@ -338,6 +353,10 @@ public ModelsMap postProcessModels(ModelsMap objs) {
var.vendorExtensions.putIfAbsent("x-protobuf-index", "Generated field number is in reserved range (19000, 19999)");
}
}

if (addJsonNameAnnotation && !var.baseName.equals(var.name)) {
var.vendorExtensions.put("x-protobuf-json-name", var.baseName);
}
}
}
return objs;
Expand Down Expand Up @@ -493,10 +512,38 @@ public String toModelFilename(String name) {
}

@Override
public String toVarName(final String name) {
public String toVarName(String name) {
if (nameMapping.containsKey(name)) {
return nameMapping.get(name);
}
// sanitize name
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.

// if it's all upper case, convert to lower case
if (name.matches("^[A-Z_]*$")) {
name = name.toLowerCase(Locale.ROOT);
}

// underscore the variable name
// petId => pet_id
name = underscore(name);

// remove leading underscore
name = name.replaceAll("^_*", "");

// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}

return name;
}

@Override
public String toParamName(String name) {
return toVarName(name);
}

@Override
public String toModelName(String name) {
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
Expand Down Expand Up @@ -571,6 +618,10 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
}
}

if (addJsonNameAnnotation && !p.baseName.equals(p.paramName) && !p.isBodyParam) {
p.vendorExtensions.put("x-protobuf-json-name", p.baseName);
}

p.vendorExtensions.putIfAbsent("x-protobuf-index", index);
index++;
}
Expand Down Expand Up @@ -646,4 +697,5 @@ private boolean parentVarsContainsVar(List<CodegenProperty> parentVars, CodegenP
public GeneratorLanguage generatorLanguage() {
return GeneratorLanguage.PROTOBUF;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ message {{operationId}}Request {
{{#description}}
// {{{.}}}
{{/description}}
{{#vendorExtensions.x-protobuf-type}}{{.}} {{/vendorExtensions.x-protobuf-type}}{{vendorExtensions.x-protobuf-data-type}} {{paramName}} = {{vendorExtensions.x-protobuf-index}};
{{#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}};
{{/allParams}}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import public "{{{modelPackage}}}/{{{import}}}.proto";
// {{{.}}}
{{/description}}
{{^isEnum}}
{{#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-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}};
{{/isEnum}}
{{#isEnum}}
enum {{enumName}} {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,6 @@ public void modelTest() {
final CodegenModel simpleName = codegen.fromModel("$DollarModel$", openAPI.getComponents().getSchemas().get("$DollarModel$"));
Assert.assertEquals(simpleName.name, "$DollarModel$");
Assert.assertEquals(simpleName.classname, "DollarModel");
Assert.assertEquals(simpleName.classVarName, "$DollarModel$");
Assert.assertEquals(simpleName.classVarName, "dollar_model");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ package openapitools;

message Pet {

string petType = 140636936;
string pet_type = 482112090;

string name = 3373707;

string bark = 3016376;

bool lovesRocks = 499337491;
bool loves_rocks = 465093427;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
README.md
models/api_response.proto
models/category.proto
models/order.proto
models/other_test.proto
models/pet.proto
models/tag.proto
models/user.proto
services/pet_service.proto
services/store_service.proto
services/user_service.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.13.0-SNAPSHOT
32 changes: 32 additions & 0 deletions samples/config/petstore/protobuf-schema-config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# gPRC for petstore

This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.

## Overview
These files were generated by the [OpenAPI Generator](https://openapi-generator.tech) project.

- API version: 1.0.0
- Package version:
- Generator version: 7.13.0-SNAPSHOT
- Build package: org.openapitools.codegen.languages.ProtobufSchemaCodegen

## Usage

Below are some usage examples for Go and Ruby. For other languages, please refer to https://grpc.io/docs/quickstart/.

### Go
```
# assuming `protoc-gen-go` has been installed with `go get -u github.com/golang/protobuf/protoc-gen-go`
mkdir /var/tmp/go/petstore
protoc --go_out=/var/tmp/go/petstore services/*
protoc --go_out=/var/tmp/go/petstore models/*
```

### Ruby
```
# assuming `grpc_tools_ruby_protoc` has been installed via `gem install grpc-tools`
RUBY_OUTPUT_DIR="/var/tmp/ruby/petstore"
mkdir $RUBY_OUTPUT_DIR
grpc_tools_ruby_protoc --ruby_out=$RUBY_OUTPUT_DIR --grpc_out=$RUBY_OUTPUT_DIR/lib services/*
grpc_tools_ruby_protoc --ruby_out=$RUBY_OUTPUT_DIR --grpc_out=$RUBY_OUTPUT_DIR/lib models/*
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
OpenAPI Petstore
This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
The version of the OpenAPI document: 1.0.0
Generated by OpenAPI Generator: https://openapi-generator.tech
*/

syntax = "proto3";

package petstore;


message ApiResponse {

int32 code = 1;

string type = 2;

string message = 3;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
OpenAPI Petstore
This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
The version of the OpenAPI document: 1.0.0
Generated by OpenAPI Generator: https://openapi-generator.tech
*/

syntax = "proto3";

package petstore;


message Category {

int64 id = 1;

string name = 2;

}
38 changes: 38 additions & 0 deletions samples/config/petstore/protobuf-schema-config/models/order.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
OpenAPI Petstore
This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
The version of the OpenAPI document: 1.0.0
Generated by OpenAPI Generator: https://openapi-generator.tech
*/

syntax = "proto3";

package petstore;


message Order {

int64 id = 1;

int64 pet_id = 2 [json_name="petId"];

int32 quantity = 3;

string ship_date = 4 [json_name="shipDate"];

// Order Status
enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_PLACED = 1;
STATUS_APPROVED = 2;
STATUS_DELIVERED = 3;
}

Status status = 5;

bool complete = 6;

}
Loading

0 comments on commit c96d308

Please sign in to comment.