Skip to content

Commit

Permalink
Make service methods returning response type directly in cases when a…
Browse files Browse the repository at this point in the history
… method is not output-streaming
  • Loading branch information
Duzhinsky committed Sep 25, 2023
1 parent bc784ec commit 4654961
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.sudu.protogen.generator.type.RepeatedType;
import org.sudu.protogen.generator.type.TypeModel;
import org.sudu.protogen.generator.type.UnfoldedType;
import org.sudu.protogen.generator.type.VoidType;

import javax.annotation.processing.Generated;
import javax.lang.model.element.Modifier;
Expand Down Expand Up @@ -82,7 +83,7 @@ private Stream<MethodSpec> generateRpcMethod(Method method) {

protected TypeModel getMethodReturnType(Method method) {
if (method.getOutputType().getFields().isEmpty()) {
return new TypeModel(TypeName.VOID);
return new VoidType();
}
var responseType = context.processType(method.getOutputType());
TypeModel type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.jetbrains.annotations.NotNull;
import org.sudu.protogen.descriptors.Field;
import org.sudu.protogen.generator.type.TypeModel;
import org.sudu.protogen.generator.type.VoidType;

public record FieldProcessingResult(
@NotNull Field original,
Expand All @@ -17,7 +18,7 @@ public static FieldProcessingResult empty(Field original) {
return new FieldProcessingResult(
original,
FieldSpec.builder(TypeName.VOID, "empty").build(),
new TypeModel(TypeName.VOID),
new VoidType(),
false
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.sudu.protogen.generator.server;

import com.squareup.javapoet.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nullable;
import org.sudu.protogen.descriptors.Method;
import org.sudu.protogen.generator.GenerationContext;
Expand All @@ -11,8 +10,7 @@
import org.sudu.protogen.utils.Poem;

import javax.lang.model.element.Modifier;
import java.util.Objects;
import java.util.stream.Stream;
import java.util.List;

public class AbstractServiceMethodGenerator {

Expand All @@ -32,36 +30,26 @@ public AbstractServiceMethodGenerator(GenerationContext context, Method method)
}

public MethodSpec generate() {
return MethodSpec.methodBuilder(method.generatedName())
.returns(TypeName.VOID)
MethodSpec.Builder builder = MethodSpec.methodBuilder(method.generatedName())
.addModifiers(Modifier.PROTECTED, Modifier.ABSTRACT)
.addParameters(generateMethodParameters())
.build();
.addParameters(generateRequestParameters());
specifyResponseWay(builder);
return builder.build();
}

/**
* Generates a parameter for response observer or returns null if it is not required
* Either adds a return type or a {@code StreamObserver<Response> } parameter
*/
@Nullable
private ParameterSpec generateObserverParameter() {
if (responseType != null && responseType.getTypeName() == TypeName.VOID) {
return null;
private void specifyResponseWay(MethodSpec.Builder methodBuilder) {
TypeName returnType = responseType();
if (method.isOutputStreaming()) {
methodBuilder.returns(TypeName.VOID);
methodBuilder.addParameter(generateObserverParameter(returnType));
} else {
methodBuilder.returns(returnType);
}
if (method.getOutputType().getFields().isEmpty()) {
return null;
}
TypeName type = ParameterizedTypeName.get(
ClassName.get("io.grpc.stub", "StreamObserver"),
responseType().box() // as a processed type could be a primitive, always box it
);
return ParameterSpec.builder(type, "responseObserver").build();
}

/**
* If the return type is processed by a type processor, returns it
* If the response message is one-field, unfolds it
* Otherwise generates protoc-generated class
*/
private TypeName responseType() {
if (responseType != null) {
return responseType.getTypeName();
Expand All @@ -73,19 +61,24 @@ private TypeName responseType() {
return method.getOutputType().getProtobufTypeName();
}

private Iterable<ParameterSpec> generateMethodParameters() {
private Iterable<ParameterSpec> generateRequestParameters() {
if (requestType == null || method.doUnfoldRequest()) {
Stream<ParameterSpec> unfoldedFields = FieldGenerator.generateSeveral(method.getInputType().getFields(), context)
return FieldGenerator.generateSeveral(method.getInputType().getFields(), context)
.map(FieldProcessingResult::field)
.map(Poem::fieldToParameter);
return StreamEx.of(unfoldedFields).append(generateObserverParameter()).nonNull().toList();
.map(Poem::fieldToParameter)
.toList();
}
if (requestType.getTypeName() == TypeName.VOID) {
return Stream.of(generateObserverParameter()).filter(Objects::nonNull).toList();
return List.of();
}
return Stream.of(
ParameterSpec.builder(requestType.getTypeName(), "request").build(),
generateObserverParameter()
).filter(Objects::nonNull).toList();
return List.of(ParameterSpec.builder(requestType.getTypeName(), "request").build());
}

private ParameterSpec generateObserverParameter(TypeName responseType) {
TypeName observerType = ParameterizedTypeName.get(
ClassName.get("io.grpc.stub", "StreamObserver"),
responseType.box()
);
return ParameterSpec.builder(observerType, "responseObserver").build();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.sudu.protogen.generator.server;

import com.squareup.javapoet.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.sudu.protogen.descriptors.Method;
import org.sudu.protogen.generator.GenerationContext;
Expand Down Expand Up @@ -67,57 +67,76 @@ public MethodSpec generate() {
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameters(generateMethodParameters())
.addCode(CodeBlock.of("$N($L);", abstractMethodSpec, generateAbstractMethodCallParams()))
.addCode(generateBody())
.build();
}

private TypeModel responseTypeModel() {
if (responseType != null) {
return responseType;
}
if (method.doUnfoldResponse(responseType)) {
return new UnfoldedType(context.processType(method.unfoldedResponseField()), method.getOutputType());
}
return null;
}

@NotNull
private CodeBlock generateBody() {
CodeBlock abstractMethodCall = CodeBlock.of("$N($L)", abstractMethodSpec, generateAbstractMethodCallParams());
if (method.isOutputStreaming()) {
return CodeBlock.builder().addStatement(abstractMethodCall).build();
} else {
CodeBlock grpcType = abstractMethodCall;
TypeModel responsedTypeModel = responseTypeModel();
if (responsedTypeModel != null) {
grpcType = responsedTypeModel.toGrpcTransformer(grpcType);
}
return CodeBlock.of("""
responseObserver.onNext($L);
responseObserver.onCompleted();
""", grpcType);
}
}

/**
* Either unfolds request params into list of fields, or returns a domain object
*/
private CodeBlock generateAbstractMethodCallParams() {
CodeBlock callParams = requestCallParams();
if (method.isOutputStreaming()) {
if (!callParams.equals(CodeBlock.of(""))) callParams = callParams.toBuilder().add(",$W").build();
callParams = callParams.toBuilder().add("$L", responseObserverCallParam()).build();
}
return callParams;
}

private CodeBlock requestCallParams() {
if (requestType == null || method.doUnfoldRequest()) {
List<CodeBlock> unfoldedRequestFields = FieldGenerator.generateSeveral(method.getInputType().getFields(), context)
.map(f -> new FieldTransformerGenerator(f.type(), f.original().getName(), f.isNullable())
.fromGrpc("request")
).toList();
return CodeBlock.of("$L", Poem.separatedSequence(
StreamEx.of(unfoldedRequestFields).append(generateResponseObserver()).toList(),
",\n"
));
.fromGrpc("request")).toList();
return CodeBlock.of("$L", Poem.separatedSequence(unfoldedRequestFields, ",$W"));
}
if (requestType.getTypeName() == TypeName.VOID) {
return Poem.separatedSequence(generateResponseObserver(), ",\n");
return CodeBlock.of("");
}
return CodeBlock.of("$L",
Poem.separatedSequence(
StreamEx.of(requestType.fromGrpcTransformer(CodeBlock.of("request")))
.append(generateResponseObserver())
.toList(),
",\n"
)
);
return requestType.fromGrpcTransformer(CodeBlock.of("request"));
}

/**
* Creates anonymous ResponseObserver if necessary
*/
private List<CodeBlock> generateResponseObserver() {
if (method.getOutputType().getFields().isEmpty()) {
return List.of();
}
private CodeBlock responseObserverCallParam() {
if (responseType != null) {
if (responseType.getTypeName() == TypeName.VOID) return List.of();
return List.of(CodeBlock.of("$L", new AnonymousStreamObserverGenerator(responseType).generate()));
return CodeBlock.of("$L", new AnonymousStreamObserverGenerator(responseType).generate());
}
if (method.doUnfoldResponse(responseType)) {
var field = method.unfoldedResponseField();
TypeModel type = new UnfoldedType(
context.processType(field),
method.getOutputType()
);
return List.of(CodeBlock.of("$L", new AnonymousStreamObserverGenerator(type).generate()));
return CodeBlock.of("$L", new AnonymousStreamObserverGenerator(type).generate());
}
return List.of(CodeBlock.of("responseObserver"));
return CodeBlock.of("responseObserver");
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.sudu.protogen.generator.type;

import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.TypeName;

public class VoidType extends TypeModel {

public VoidType() {
super(TypeName.VOID);
}

@Override
public CodeBlock toGrpcTransformer(CodeBlock expr) {
return CodeBlock.of("null");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class DomainTypeProcessor extends TypeProcessor.Chain {
// throw new IllegalArgumentException((
// "It's not possible to process type of %s because it doesn't have a domain object. " +
// "Specify an existing domain object using custom_class option or generate it."
// ).formatted(type.getFullName()));
// ).formatted(type.getFullName())); todo move 2 type processor
return next(type, configuration);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.sudu.protogen.generator.type.processors;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.sudu.protogen.config.Configuration;
import org.sudu.protogen.descriptors.EnumOrMessage;
import org.sudu.protogen.descriptors.Message;
import org.sudu.protogen.generator.type.TypeModel;
import org.sudu.protogen.generator.type.VoidType;

/**
* Treats all empty messages as void
*/
public class EmptyMessageProcessor extends TypeProcessor.Chain {

@Override
public @Nullable TypeModel processType(@NotNull EnumOrMessage descriptor, @NotNull Configuration configuration) {
if (descriptor instanceof Message msg) {
if (msg.getFields().isEmpty()) return new VoidType();
}
return next(descriptor, configuration);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public static TypeProcessor getProcessingChain() {

var chain = List.of( // Ordering is important!
new RegisteredTypeProcessor(),
new DomainTypeProcessor()
new DomainTypeProcessor(),
new EmptyMessageProcessor()
);
for (int i = 0; i < chain.size() - 1; ++i) {
var current = chain.get(i);
Expand All @@ -31,9 +32,7 @@ public static TypeProcessor getProcessingChain() {
}

@Override
public @Nullable TypeModel processType(@NotNull EnumOrMessage descriptor, @NotNull Configuration configuration) {
return next(descriptor, configuration);
}
public abstract @Nullable TypeModel processType(@NotNull EnumOrMessage descriptor, @NotNull Configuration configuration);

private void setNext(@NotNull Chain typeProcessor) {
this.next = typeProcessor;
Expand Down

0 comments on commit 4654961

Please sign in to comment.