diff --git a/protoc-cli/src/main/java/io/ballerina/protoc/builder/stub/Field.java b/protoc-cli/src/main/java/io/ballerina/protoc/builder/stub/Field.java index ce6475c..4c2eb37 100644 --- a/protoc-cli/src/main/java/io/ballerina/protoc/builder/stub/Field.java +++ b/protoc-cli/src/main/java/io/ballerina/protoc/builder/stub/Field.java @@ -137,7 +137,7 @@ public Field build() { private Builder(DescriptorProtos.FieldDescriptorProto fieldDescriptor, String fieldType) { this.fieldDescriptor = fieldDescriptor; - this.type = fieldType; + this.type = fieldType != null ? fieldType : "empty:Empty"; } } diff --git a/protoc-cli/src/main/java/module-info.java b/protoc-cli/src/main/java/module-info.java index fc43e79..cb001e9 100644 --- a/protoc-cli/src/main/java/module-info.java +++ b/protoc-cli/src/main/java/module-info.java @@ -28,4 +28,5 @@ requires info.picocli; requires org.slf4j; requires org.apache.commons.lang3; + requires protobuf.java; } diff --git a/tooling-tests/src/test/java/io/ballerina/protoc/tools/ToolingUnaryTest.java b/tooling-tests/src/test/java/io/ballerina/protoc/tools/ToolingUnaryTest.java index fec6453..9d46e98 100644 --- a/tooling-tests/src/test/java/io/ballerina/protoc/tools/ToolingUnaryTest.java +++ b/tooling-tests/src/test/java/io/ballerina/protoc/tools/ToolingUnaryTest.java @@ -96,4 +96,11 @@ public void testUnaryHelloWorldTimestamp() { "helloWorldTimestamp_pb.bal", "helloworld_service.bal", "helloworld_client.bal", "tool_test_unary_10"); } + + @Test + public void testUnaryWithEmptyFieldMessage() { + assertGeneratedSources("unary", "emptyFieldMessage.proto", + "emptyFieldMessage_pb.bal", "testservice_service.bal", + "testservice_client.bal", "tool_test_unary_11"); + } } diff --git a/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/emptyFieldMessage_pb.bal b/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/emptyFieldMessage_pb.bal new file mode 100644 index 0000000..c2a25ab --- /dev/null +++ b/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/emptyFieldMessage_pb.bal @@ -0,0 +1,119 @@ +import ballerina/grpc; +import ballerina/protobuf; +import ballerina/protobuf.types.empty; + +public const string EMPTYFIELDMESSAGE_DESC = "0A17656D7074794669656C644D6573736167652E70726F746F1204746573741A1B676F6F676C652F70726F746F6275662F656D7074792E70726F746F22410A0B436F6D706C65785479706512100A03666F6F18012001280D5203666F6F12200A0362617218022001280E320E2E746573742E54657374456E756D520362617222790A134F7074696F6E616C436F6D706C65785479706512360A0D636F6D706C65785F76616C756518012001280B32112E746573742E436F6D706C657854797065520C636F6D706C657856616C7565122A0A046E6F6E6518022001280B32162E676F6F676C652E70726F746F6275662E456D70747952046E6F6E6522500A0E4F7074696F6E616C537472696E6712120A046E616D6518012001280952046E616D65122A0A046E6F6E6518022001280B32162E676F6F676C652E70726F746F6275662E456D70747952046E6F6E65223D0A0E526571756573744D657373616765122B0A0372657118012003280B32192E746573742E4F7074696F6E616C436F6D706C6578547970655203726571223B0A0F526573706F6E73654D65737361676512280A047265737018012003280B32142E746573742E4F7074696F6E616C537472696E675204726573702A280A0854657374456E756D120D0A09454E5452595F4F4E451000120D0A09454E5452595F54574F100132450A0B546573745365727669636512360A075465737452504312142E746573742E526571756573744D6573736167651A152E746573742E526573706F6E73654D657373616765620670726F746F33"; + +public isolated client class TestServiceClient { + *grpc:AbstractClientEndpoint; + + private final grpc:Client grpcClient; + + public isolated function init(string url, *grpc:ClientConfiguration config) returns grpc:Error? { + self.grpcClient = check new (url, config); + check self.grpcClient.initStub(self, EMPTYFIELDMESSAGE_DESC); + } + + isolated remote function TestRPC(RequestMessage|ContextRequestMessage req) returns ResponseMessage|grpc:Error { + map headers = {}; + RequestMessage message; + if req is ContextRequestMessage { + message = req.content; + headers = req.headers; + } else { + message = req; + } + var payload = check self.grpcClient->executeSimpleRPC("test.TestService/TestRPC", message, headers); + [anydata, map] [result, _] = payload; + return result; + } + + isolated remote function TestRPCContext(RequestMessage|ContextRequestMessage req) returns ContextResponseMessage|grpc:Error { + map headers = {}; + RequestMessage message; + if req is ContextRequestMessage { + message = req.content; + headers = req.headers; + } else { + message = req; + } + var payload = check self.grpcClient->executeSimpleRPC("test.TestService/TestRPC", message, headers); + [anydata, map] [result, respHeaders] = payload; + return {content: result, headers: respHeaders}; + } +} + +public isolated client class TestServiceResponseMessageCaller { + private final grpc:Caller caller; + + public isolated function init(grpc:Caller caller) { + self.caller = caller; + } + + public isolated function getId() returns int { + return self.caller.getId(); + } + + isolated remote function sendResponseMessage(ResponseMessage response) returns grpc:Error? { + return self.caller->send(response); + } + + isolated remote function sendContextResponseMessage(ContextResponseMessage response) returns grpc:Error? { + return self.caller->send(response); + } + + isolated remote function sendError(grpc:Error response) returns grpc:Error? { + return self.caller->sendError(response); + } + + isolated remote function complete() returns grpc:Error? { + return self.caller->complete(); + } + + public isolated function isCancelled() returns boolean { + return self.caller.isCancelled(); + } +} + +public type ContextRequestMessage record {| + RequestMessage content; + map headers; +|}; + +public type ContextResponseMessage record {| + ResponseMessage content; + map headers; +|}; + +@protobuf:Descriptor {value: EMPTYFIELDMESSAGE_DESC} +public type RequestMessage record {| + OptionalComplexType[] req = []; +|}; + +@protobuf:Descriptor {value: EMPTYFIELDMESSAGE_DESC} +public type OptionalComplexType record {| + ComplexType complex_value = {}; + empty:Empty none = {}; +|}; + +@protobuf:Descriptor {value: EMPTYFIELDMESSAGE_DESC} +public type ResponseMessage record {| + OptionalString[] resp = []; +|}; + +@protobuf:Descriptor {value: EMPTYFIELDMESSAGE_DESC} +public type OptionalString record {| + string name = ""; + empty:Empty none = {}; +|}; + +@protobuf:Descriptor {value: EMPTYFIELDMESSAGE_DESC} +public type ComplexType record {| + int:Unsigned32 foo = 0; + TestEnum bar = ENTRY_ONE; +|}; + +public enum TestEnum { + ENTRY_ONE, ENTRY_TWO +} + diff --git a/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/emptyFieldMessage_pb_client.bal b/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/emptyFieldMessage_pb_client.bal new file mode 100644 index 0000000..795c20f --- /dev/null +++ b/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/emptyFieldMessage_pb_client.bal @@ -0,0 +1,87 @@ +import ballerina/grpc; +import ballerina/protobuf; +import ballerina/protobuf.types.empty; + +public const string EMPTYFIELDMESSAGE_DESC = "0A17656D7074794669656C644D6573736167652E70726F746F1204746573741A1B676F6F676C652F70726F746F6275662F656D7074792E70726F746F22410A0B436F6D706C65785479706512100A03666F6F18012001280D5203666F6F12200A0362617218022001280E320E2E746573742E54657374456E756D520362617222790A134F7074696F6E616C436F6D706C65785479706512360A0D636F6D706C65785F76616C756518012001280B32112E746573742E436F6D706C657854797065520C636F6D706C657856616C7565122A0A046E6F6E6518022001280B32162E676F6F676C652E70726F746F6275662E456D70747952046E6F6E6522500A0E4F7074696F6E616C537472696E6712120A046E616D6518012001280952046E616D65122A0A046E6F6E6518022001280B32162E676F6F676C652E70726F746F6275662E456D70747952046E6F6E65223D0A0E526571756573744D657373616765122B0A0372657118012003280B32192E746573742E4F7074696F6E616C436F6D706C6578547970655203726571223B0A0F526573706F6E73654D65737361676512280A047265737018012003280B32142E746573742E4F7074696F6E616C537472696E675204726573702A280A0854657374456E756D120D0A09454E5452595F4F4E451000120D0A09454E5452595F54574F100132450A0B546573745365727669636512360A075465737452504312142E746573742E526571756573744D6573736167651A152E746573742E526573706F6E73654D657373616765620670726F746F33"; + +public isolated client class TestServiceClient { + *grpc:AbstractClientEndpoint; + + private final grpc:Client grpcClient; + + public isolated function init(string url, *grpc:ClientConfiguration config) returns grpc:Error? { + self.grpcClient = check new (url, config); + check self.grpcClient.initStub(self, EMPTYFIELDMESSAGE_DESC); + } + + isolated remote function TestRPC(RequestMessage|ContextRequestMessage req) returns ResponseMessage|grpc:Error { + map headers = {}; + RequestMessage message; + if req is ContextRequestMessage { + message = req.content; + headers = req.headers; + } else { + message = req; + } + var payload = check self.grpcClient->executeSimpleRPC("test.TestService/TestRPC", message, headers); + [anydata, map] [result, _] = payload; + return result; + } + + isolated remote function TestRPCContext(RequestMessage|ContextRequestMessage req) returns ContextResponseMessage|grpc:Error { + map headers = {}; + RequestMessage message; + if req is ContextRequestMessage { + message = req.content; + headers = req.headers; + } else { + message = req; + } + var payload = check self.grpcClient->executeSimpleRPC("test.TestService/TestRPC", message, headers); + [anydata, map] [result, respHeaders] = payload; + return {content: result, headers: respHeaders}; + } +} + +public type ContextRequestMessage record {| + RequestMessage content; + map headers; +|}; + +public type ContextResponseMessage record {| + ResponseMessage content; + map headers; +|}; + +@protobuf:Descriptor {value: EMPTYFIELDMESSAGE_DESC} +public type RequestMessage record {| + OptionalComplexType[] req = []; +|}; + +@protobuf:Descriptor {value: EMPTYFIELDMESSAGE_DESC} +public type OptionalComplexType record {| + ComplexType complex_value = {}; + empty:Empty none = {}; +|}; + +@protobuf:Descriptor {value: EMPTYFIELDMESSAGE_DESC} +public type ResponseMessage record {| + OptionalString[] resp = []; +|}; + +@protobuf:Descriptor {value: EMPTYFIELDMESSAGE_DESC} +public type OptionalString record {| + string name = ""; + empty:Empty none = {}; +|}; + +@protobuf:Descriptor {value: EMPTYFIELDMESSAGE_DESC} +public type ComplexType record {| + int:Unsigned32 foo = 0; + TestEnum bar = ENTRY_ONE; +|}; + +public enum TestEnum { + ENTRY_ONE, ENTRY_TWO +} + diff --git a/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/testservice_client.bal b/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/testservice_client.bal new file mode 100644 index 0000000..be75ff8 --- /dev/null +++ b/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/testservice_client.bal @@ -0,0 +1,10 @@ +import ballerina/io; + +TestServiceClient ep = check new ("http://localhost:9090"); + +public function main() returns error? { + RequestMessage testRPCRequest = {req: [{complex_value: {foo: 1, bar: "ENTRY_ONE"}, none: {}}]}; + ResponseMessage testRPCResponse = check ep->TestRPC(testRPCRequest); + io:println(testRPCResponse); +} + diff --git a/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/testservice_service.bal b/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/testservice_service.bal new file mode 100644 index 0000000..5b047f7 --- /dev/null +++ b/tooling-tests/src/test/resources/test-src/generated-sources/tool_test_unary_11/testservice_service.bal @@ -0,0 +1,11 @@ +import ballerina/grpc; + +listener grpc:Listener ep = new (9090); + +@grpc:Descriptor {value: EMPTYFIELDMESSAGE_DESC} +service "TestService" on ep { + + remote function TestRPC(RequestMessage value) returns ResponseMessage|error { + } +} + diff --git a/tooling-tests/src/test/resources/test-src/proto-files/unary/emptyFieldMessage.proto b/tooling-tests/src/test/resources/test-src/proto-files/unary/emptyFieldMessage.proto new file mode 100644 index 0000000..518cc6a --- /dev/null +++ b/tooling-tests/src/test/resources/test-src/proto-files/unary/emptyFieldMessage.proto @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you 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"; + +package test; + +import "google/protobuf/empty.proto"; + +service TestService { + rpc TestRPC(RequestMessage) returns (ResponseMessage); +} + +enum TestEnum { + ENTRY_ONE = 0; + ENTRY_TWO = 1; +} + +message ComplexType { + uint32 foo = 1; + TestEnum bar = 2; +} + +message OptionalComplexType { + ComplexType complex_value = 1; + google.protobuf.Empty none = 2; +} + +message OptionalString { + string name = 1; + google.protobuf.Empty none = 2; +} + +message RequestMessage { + repeated OptionalComplexType req = 1; +} + +message ResponseMessage { + repeated OptionalString resp = 1; +}