Skip to content

Commit

Permalink
Add an option to generate default service instances
Browse files Browse the repository at this point in the history
  • Loading branch information
mgodave committed Nov 15, 2024
1 parent 057cc56 commit 39fa5d2
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 4 deletions.
1 change: 1 addition & 0 deletions servicetalk-examples/grpc/protoc-options/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ protobuf {
outputSubDir = "java"
// Option to append a suffix to type names to avoid naming collisions with other code generation.
option 'typeNameSuffix=St'
option 'defaultServiceMethods=true'
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package io.servicetalk.examples.grpc.protocoptions;

import io.grpc.examples.helloworld.HelloRequest;
import io.servicetalk.grpc.api.GrpcServiceContext;
import io.servicetalk.grpc.netty.GrpcServers;

import io.grpc.examples.helloworld.GreeterSt.BlockingGreeterService;
Expand All @@ -23,8 +25,12 @@
public final class BlockingProtocOptionsServer {
public static void main(String[] args) throws Exception {
GrpcServers.forPort(8080)
.listenAndAwait((BlockingGreeterService) (ctx, request) ->
HelloReply.newBuilder().setMessage("Hello " + request.getName()).build())
.listenAndAwait(new BlockingGreeterService() {
@Override
public HelloReply sayHello(GrpcServiceContext ctx, HelloRequest request) {
return HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();
}
})
.awaitShutdown();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// Copyright © 2019 Apple Inc. and the ServiceTalk project authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.servicetalk.grpc.netty";
option java_outer_classname = "TesterProto";

package grpc.netty;

service Tester {
rpc test (TestRequest) returns (TestResponse) {}
rpc testBiDiStream (stream TestRequest) returns (stream TestResponse) {}
rpc testResponseStream (TestRequest) returns (stream TestResponse) {}
rpc testRequestStream (stream TestRequest) returns (TestResponse) {}
}

message TestRequest {
string name = 1;
}

message TestResponse {
string message = 1;
}
31 changes: 31 additions & 0 deletions servicetalk-grpc-protoc/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,34 @@ And with Maven:
</args>
</protocPlugin>
----

==== `defaultServiceMethods=<true|false> (default: false)`
Generates default service interface methods to ensure implementations will continue to compile as the API is evolved.

If you are using the
link:https://github.com/google/protobuf-gradle-plugin#configure-what-to-generate[protobuf-gradle-plugin] this is how you
can specify an option:

[source,gradle]
----
task.plugins {
servicetalk_grpc {
option 'defaultServiceMethods=true'
}
}
----

And with Maven:

And with Maven:

[source, xml]
----
<protocPlugin>
<id>servicetalk-grpc-protoc</id>
<!-- more params -->
<args>
<arg>defaultServiceMethods=true</arg>
</args>
</protocPlugin>
----
Original file line number Diff line number Diff line change
Expand Up @@ -248,13 +248,15 @@ private State(ServiceDescriptorProto serviceProto, GenerationContext context, St
private final Map<String, ClassName> messageTypesMap;
private final ServiceCommentsMap serviceCommentsMap;
private final boolean printJavaDocs;
private final boolean defaultServiceMethods;

Generator(final GenerationContext context, final Map<String, ClassName> messageTypesMap,
final boolean printJavaDocs, SourceCodeInfo sourceCodeInfo) {
final boolean printJavaDocs, final boolean defaultServiceMethods, SourceCodeInfo sourceCodeInfo) {
this.context = context;
this.messageTypesMap = messageTypesMap;
this.serviceCommentsMap = printJavaDocs ? new DefaultServiceCommentsMap(sourceCodeInfo) : NOOP_MAP;
this.printJavaDocs = printJavaDocs;
this.defaultServiceMethods = defaultServiceMethods;
}

/**
Expand Down Expand Up @@ -1465,6 +1467,33 @@ private TypeSpec newServiceInterfaceSpec(final State state, final boolean blocki
.addStatement("return $L", blocking ? BLOCKING_METHOD_DESCRIPTORS : ASYNC_METHOD_DESCRIPTORS)
.build());

// generate default service methods
if (defaultServiceMethods) {
for (int i = 0; i < state.serviceProto.getMethodList().size(); i++) {
final MethodDescriptorProto methodProto = state.serviceProto.getMethodList().get(i);
final ClassName inClass = messageTypesMap.get(methodProto.getInputType());
final ClassName outClass = messageTypesMap.get(methodProto.getOutputType());
final String methodName = sanitizeIdentifier(methodProto.getName(), true);
final int methodIndex = i;
interfaceSpecBuilder.addMethod(newRpcMethodSpec(inClass, outClass, methodName,
methodProto.getClientStreaming(),
methodProto.getServerStreaming(),
!blocking ? EnumSet.of(INTERFACE) : EnumSet.of(INTERFACE, BLOCKING),
printJavaDocs, (__, c) -> {
c.addModifiers(DEFAULT)
.addParameter(GrpcServiceContext, ctx)
.addStatement("throw new UnsupportedOperationException(\"Method " + methodName +
" is unimplemented\")");
if (printJavaDocs) {
extractJavaDocComments(state, methodIndex, c);
c.addJavadoc(JAVADOC_PARAM + ctx +
" context associated with this service and request." + lineSeparator());
}
return c;
}));
}
}

return interfaceSpecBuilder.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,28 @@ public final class Main {
* }</pre>
*/
private static final String PRINT_JAVA_DOCS_OPTION = "javaDocs";
/**
* Supports an option to generate default service interface methods.
* <p>
* Gradle:
* <pre>
* task.plugins {
* servicetalk_grpc {
* option 'defaultServiceMethods=true'
* }
* }
* </pre>
* <p>
* Maven:
* <pre>{@code
* <protocPlugin>
* <args>
* <arg>defaultServiceMethods=true</arg>
* </args>
* </protocPlugin>
* }</pre>
*/
private static final String DEFAULT_SERVICE_METHODS = "defaultServiceMethods";
private Main() {
// no instances
}
Expand Down Expand Up @@ -149,6 +171,7 @@ private static CodeGeneratorResponse generate(final CodeGeneratorRequest request
}
final String typeSuffixValue = optionsMap.get(TYPE_NAME_SUFFIX_OPTION);
final boolean printJavaDocs = parseBoolean(optionsMap.getOrDefault(PRINT_JAVA_DOCS_OPTION, "true"));
final boolean defaultServiceMethods = parseBoolean(optionsMap.getOrDefault(DEFAULT_SERVICE_METHODS, "false"));

final List<FileDescriptor> fileDescriptors = request.getProtoFileList().stream()
.map(protoFile -> new FileDescriptor(protoFile, typeSuffixValue)).collect(toList());
Expand All @@ -161,7 +184,8 @@ private static CodeGeneratorResponse generate(final CodeGeneratorRequest request

for (FileDescriptor f : fileDescriptors) {
if (filesToGenerate.contains(f.protoFileName())) {
final Generator generator = new Generator(f, messageTypesMap, printJavaDocs, f.sourceCodeInfo());
final Generator generator = new Generator(f, messageTypesMap, printJavaDocs, defaultServiceMethods,
f.sourceCodeInfo());
List<ServiceDescriptorProto> serviceDescriptorProtoList = f.protoServices();
for (int i = 0; i < serviceDescriptorProtoList.size(); ++i) {
ServiceDescriptorProto serviceDescriptor = serviceDescriptorProtoList.get(i);
Expand Down

0 comments on commit 39fa5d2

Please sign in to comment.