From cb24719108fe7558dada3608460002b9210df9e4 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sat, 23 May 2026 00:03:44 +0530 Subject: [PATCH 01/23] feat: added shared base utilities --- .../fory_compiler/generators/services/base.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 compiler/fory_compiler/generators/services/base.py diff --git a/compiler/fory_compiler/generators/services/base.py b/compiler/fory_compiler/generators/services/base.py new file mode 100644 index 0000000000..37bae1a5e0 --- /dev/null +++ b/compiler/fory_compiler/generators/services/base.py @@ -0,0 +1,51 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF 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. + + +from enum import Enum +from typing import List, Dict +from fory_compiler.ir.ast import RpcMethod + +class StreamingMode(Enum): + UNARY = 1 + CLIENT_STREAMING = 2 + SERVER_STREAMING = 3 + BIDIRECTIONAL = 4 + + +def streaming_mode(method: RpcMethod) -> StreamingMode: + """Defines the type of rpc streaming patterns.""" + if not method.client_streaming and not method.server_streaming: + return StreamingMode.UNARY + elif method.client_streaming and not method.server_streaming: + return StreamingMode.CLIENT_STREAMING + elif not method.client_streaming and method.server_streaming: + return StreamingMode.SERVER_STREAMING + else: + return StreamingMode.BIDIRECTIONAL + + +class ImportTracker: + """Accumulates cross-package Go imports for generated service stubs.""" + def __init__(self): + self._imports: Dict[str, str] = {} + + def add(self, alias: str, import_path: str) -> None: + self._imports[alias] = import_path + + def go_imports(self) -> List[str]: + return sorted(self._imports.values()) From 18ff0a81272ce1a616f5f0c41d8ef1a37c47ea23 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sun, 24 May 2026 23:49:47 +0530 Subject: [PATCH 02/23] feat: build package imports for generated_services() --- .../fory_compiler/generators/services/base.py | 2 +- .../fory_compiler/generators/services/go.py | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 compiler/fory_compiler/generators/services/go.py diff --git a/compiler/fory_compiler/generators/services/base.py b/compiler/fory_compiler/generators/services/base.py index 37bae1a5e0..57aa619c0c 100644 --- a/compiler/fory_compiler/generators/services/base.py +++ b/compiler/fory_compiler/generators/services/base.py @@ -34,7 +34,7 @@ def streaming_mode(method: RpcMethod) -> StreamingMode: elif method.client_streaming and not method.server_streaming: return StreamingMode.CLIENT_STREAMING elif not method.client_streaming and method.server_streaming: - return StreamingMode.SERVER_STREAMING + return StreamingMode.SERVER_STREAMING else: return StreamingMode.BIDIRECTIONAL diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py new file mode 100644 index 0000000000..15a051a81a --- /dev/null +++ b/compiler/fory_compiler/generators/services/go.py @@ -0,0 +1,84 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF 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. + +"""Go gRPC service code generator.""" + +from typing import List +from fory_compiler.generators.services.base import ImportTracker, StreamingMode, streaming_mode +from fory_compiler.generators.base import GeneratedFile +from fory_compiler.ir.ast import RpcMethod, Service + + +class GoServiceGeneratorMixin: + """Generates Go gRPC service stubs.""" + + def generate_services(self) -> List[GeneratedFile]: + local_services = [s for s in self.schema.services if not self.is_imported_type(s)] + if not local_services: + return [] + return [self._generate_grpc_file(s) for s in local_services] + + def _generate_grpc_file(self, service: Service) -> GeneratedFile: + lines: List[str] = [] + tracker = ImportTracker() + + # License header + lines.append(self.get_license_header("//")) + lines.append("") + + # Package declaration + lines.append(f"package {self.get_package_name()}") + lines.append("") + + # Imports + # save the placeholder index for now + import_placeholder_index = len(lines) + + + + + + # after all the service code gets generated, insert the import block at the placeholder index + import_lines = self._build_import_block(tracker) + for i, line in enumerate(import_lines): + lines.insert(import_placeholder_index + i, line) + + return GeneratedFile( + path=f"{self.get_file_name()}_grpc.go", + content="\n".join(lines) + ) + + def _build_import_block(self, tracker: ImportTracker) -> List[str]: + imports = [ + '"context"', + '"google.golang.org/grpc"', + '"google.golang.org/grpc/codes"', + '"google.golang.org/grpc/status"', + ] + + for path in tracker.go_imports(): + imports.append(f'"{path}"') + + sorted_imports = sorted(set(imports)) # deduplicate and sort the imports + + lines = ["import ("] + for imp in sorted_imports: + lines.append(f"\t{imp}") + lines.append(")") + lines.append("") + + return lines From b6389fb4e4d70c36580248f3af2873f116991928 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Wed, 27 May 2026 11:49:24 +0530 Subject: [PATCH 03/23] feat: generates client api interface for service --- .../fory_compiler/generators/services/go.py | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 15a051a81a..88dcdcf707 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -20,7 +20,7 @@ from typing import List from fory_compiler.generators.services.base import ImportTracker, StreamingMode, streaming_mode from fory_compiler.generators.base import GeneratedFile -from fory_compiler.ir.ast import RpcMethod, Service +from fory_compiler.ir.ast import RpcMethod, Service, NamedType class GoServiceGeneratorMixin: @@ -48,7 +48,8 @@ def _generate_grpc_file(self, service: Service) -> GeneratedFile: # save the placeholder index for now import_placeholder_index = len(lines) - + # Client interface + @@ -73,7 +74,7 @@ def _build_import_block(self, tracker: ImportTracker) -> List[str]: for path in tracker.go_imports(): imports.append(f'"{path}"') - sorted_imports = sorted(set(imports)) # deduplicate and sort the imports + sorted_imports = sorted(set(imports)) lines = ["import ("] for imp in sorted_imports: @@ -82,3 +83,36 @@ def _build_import_block(self, tracker: ImportTracker) -> List[str]: lines.append("") return lines + + def _resolve_go_type(self, named_type: NamedType, tracker: ImportTracker) -> str: + type_ref = self.schema.resolve_type_name(named_type.name) + type_def = self.schema.get_type(type_ref) + if type_def is not None and self.is_imported_type(type_def): + info = self._import_info_for_type(type_def) + if info: + alias, import_path, _ = info + tracker.add(alias, import_path) + return f"*{alias}.{type_ref}" + return f"*{type_ref}" + + def _generate_client_interface(self, service: Service, tracker: ImportTracker) -> List[str]: + lines: List[str] = [] + lines.append(f"// {service.name}Client is the client API for {service.name} service") + lines.append(f"type {service.name}Client interface {{") + for method in service.methods: + req_type = self._resolve_go_type(method.request_type, tracker) + res_type = self._resolve_go_type(method.response_type, tracker) + mode = streaming_mode(method) + if mode is StreamingMode.UNARY: + signature = f"(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({res_type}, error)" + lines.append(f"\t{self.to_pascal_case(method.name)}{signature}") + elif mode is StreamingMode.SERVER_STREAMING: + signature = f"(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error)" + lines.append(f"\t{self.to_pascal_case(method.name)}{signature}") + else: + signature = f"(ctx context.Context, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error)" + lines.append(f"\t{self.to_pascal_case(method.name)}{signature}") + + lines.append("}") + lines.append("") + return lines From ea12a23206ebdf28d3299dd77bd24f8bb1a6cf1e Mon Sep 17 00:00:00 2001 From: ayush00git Date: Wed, 27 May 2026 12:33:51 +0530 Subject: [PATCH 04/23] feat: add client struct and init constructor --- .../fory_compiler/generators/services/go.py | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 88dcdcf707..d405ffeeda 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -97,7 +97,7 @@ def _resolve_go_type(self, named_type: NamedType, tracker: ImportTracker) -> str def _generate_client_interface(self, service: Service, tracker: ImportTracker) -> List[str]: lines: List[str] = [] - lines.append(f"// {service.name}Client is the client API for {service.name} service") + lines.append(f"// {service.name}Client is the client API for {service.name} service.") lines.append(f"type {service.name}Client interface {{") for method in service.methods: req_type = self._resolve_go_type(method.request_type, tracker) @@ -116,3 +116,36 @@ def _generate_client_interface(self, service: Service, tracker: ImportTracker) - lines.append("}") lines.append("") return lines + + def _generate_client_struct(self, service: Service) -> List[str]: + lines: List[str] = [] + lines.append(f"type {self.to_camel_case(service.name)}Client struct {{") + lines.append("\tcc grpc.ClientConnInterface") + lines.append("}") + lines.append("") + return lines + + def _generate_new_client(self, service: Service) -> List[str]: + lines: List[str] = [] + lines.append(f"func New{self.to_pascal_case(service.name)}Client(cc grpc.ClientConnInterface) {self.to_pascal_case(service.name)}Client {{") + lines.append(f"\treturn &{self.to_camel_case(service.name)}Client{{cc}}") + lines.append("}") + lines.append("") + return lines + + def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> List[str]: + lines: List[str] = [] + for method in service.methods: + req_type = self._resolve_go_type(method.request_type, tracker) + res_type = self._resolve_go_type(method.response_type, tracker) + if method is StreamingMode.UNARY: + lines.append(f"func (c *{self.to_camel_case(service.name)}) {self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({res_type}, error) {{") + lines.append(f"\tout := new({method.name})") + lines.append(f"\terr := c.cc.Invoke(ctx, {self.get_grpc_method_path(service, method)}, in, out, grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)") + lines.append(f"\tif err != nil {{") + lines.append(f"\t\treturn nil, err") + lines.append("\t}") + lines.append("\treturn out, nil") + lines.append("}") + lines.append("") + \ No newline at end of file From a8ca3253c9515157332c0859d4bf4d1798755100 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Fri, 29 May 2026 13:05:52 +0530 Subject: [PATCH 05/23] feat: added server streaming client method generator --- .../fory_compiler/generators/services/go.py | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index d405ffeeda..41903411e1 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -135,17 +135,39 @@ def _generate_new_client(self, service: Service) -> List[str]: def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> List[str]: lines: List[str] = [] + tracker.add("forygrpc", "github.com/apache/fory/go/fory/grpc") + stream_index = 0 for method in service.methods: req_type = self._resolve_go_type(method.request_type, tracker) res_type = self._resolve_go_type(method.response_type, tracker) - if method is StreamingMode.UNARY: - lines.append(f"func (c *{self.to_camel_case(service.name)}) {self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({res_type}, error) {{") - lines.append(f"\tout := new({method.name})") - lines.append(f"\terr := c.cc.Invoke(ctx, {self.get_grpc_method_path(service, method)}, in, out, grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)") - lines.append(f"\tif err != nil {{") - lines.append(f"\t\treturn nil, err") + mode = streaming_mode(method) + if mode is StreamingMode.UNARY: + lines.append(f"func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({res_type}, error) {{") + lines.append(f"\tout := new({res_type[1:]})") + lines.append(f'\terr := c.cc.Invoke(ctx, "{self.get_grpc_method_path(service, method)}", in, out, grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)') + lines.append("\tif err != nil {") + lines.append("\t\treturn nil, err") lines.append("\t}") lines.append("\treturn out, nil") lines.append("}") lines.append("") - \ No newline at end of file + elif mode is StreamingMode.SERVER_STREAMING: + lines.append(f'func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error) {{') + lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{self.to_pascal_case(service.name)}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)') + lines.append("\tif err != nil {") + lines.append("\t\treturn nil, err") + lines.append("\t}") + lines.append(f"\tx := &{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client{{stream}}") + lines.append("\tif err := x.SendMsg(in); err != nil {") + lines.append("\t\treturn nil, err") + lines.append("\t}") + lines.append("\tif err := x.CloseSend(); err != nil {") + lines.append("\t\treturn nil, err") + lines.append("\t}") + lines.append("\treturn x, nil") + lines.append("}") + lines.append("") + stream_index += 1 + + + From 296b94cdc456bac361cb96fba9c00b48ede9cfb1 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Fri, 29 May 2026 13:38:21 +0530 Subject: [PATCH 06/23] feat: added client/bidi client method patterns --- compiler/fory_compiler/generators/services/go.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 41903411e1..6855cd2c7e 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -168,6 +168,13 @@ def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> lines.append("}") lines.append("") stream_index += 1 - - - + else: + lines.append(f"func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error) {{") + lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{self.to_pascal_case(service.name)}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)') + lines.append("\tif err != nil {") + lines.append("\t\treturn nil, err") + lines.append("\t}") + lines.append(f"\treturn &{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client{{stream}}, nil") + lines.append("}") + lines.append("") + stream_index += 1 From d597d66e39242ef99290ac819503e48e35548c24 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Fri, 29 May 2026 14:52:41 +0530 Subject: [PATCH 07/23] feat: added stream type interface, struct and methods --- .../fory_compiler/generators/services/go.py | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 6855cd2c7e..fcf9a37941 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -178,3 +178,94 @@ def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> lines.append("}") lines.append("") stream_index += 1 + + def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> List[str]: + lines: List[str] = [] + for method in service.methods: + req_type = self._resolve_go_type(method.request_type, tracker) + res_type = self._resolve_go_type(method.response_type, tracker) + mode = streaming_mode(method) + if mode is StreamingMode.UNARY: + continue + if mode is StreamingMode.CLIENT_STREAMING: + # type interface + lines.append(f"type {self.to_pascal_case(service.name)}_{self.to_pascal_case(method.name)}Client interface {{") + lines.append(f"\tSend({req_type}) error") + lines.append(f"\tCloseAndRecv() ({res_type}, error)") + lines.append("\tgrpc.ClientStream") + lines.append("}") + lines.append("") + + # struct + lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client struct {{") + lines.append("\tgrpc.ClientStream") + lines.append("}") + lines.append("") + + # methods + lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Send(m {req_type}) error {{") + lines.append(f"\treturn x.ClientStream.SendMsg(m)") + lines.append("}") + lines.append("") + lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) CloseAndRecv() ({res_type}, error) {{") + lines.append("\tif err := x.ClientStream.CloseSend(); err != nil {") + lines.append("\t\treturn nil, err") + lines.append("\t}") + lines.append(f"\tm := new({res_type[1:]})") + lines.append("\tif err := x.ClientStream.RecvMsg(m); err != nil {") + lines.append("\t\treturn nil, err") + lines.append("\t}") + lines.append("\treturn m, nil") + lines.append("}") + lines.append("") + elif mode is StreamingMode.SERVER_STREAMING: + # type interface + lines.append(f"type {self.to_pascal_case(service.name)}_{self.to_pascal_case(method.name)}Client interface {{") + lines.append(f"\tRecv() ({res_type}, error)") + lines.append("\tgrpc.ClientStream") + lines.append("}") + lines.append("") + + # struct + lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client struct {{") + lines.append("\tgrpc.ClientStream") + lines.append("}") + lines.append("") + + # methods + lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Recv() ({res_type}, error) {{") + lines.append(f"\tm := new({res_type[1:]})") + lines.append("\tif err := x.ClientStream.RecvMsg(m); err != nil {") + lines.append("\t\treturn nil, err") + lines.append("\t}") + lines.append("\treturn m, nil") + lines.append("}") + lines.append("") + else: + # interface + lines.append(f"type {self.to_pascal_case(service.name)}_{self.to_pascal_case(method.name)}Client interface {{") + lines.append(f"\tSend({req_type}) error") + lines.append(f"\tRecv() ({res_type}, error)") + lines.append("\tgrpc.ClientStream") + lines.append("}") + lines.append("") + + # struct + lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client struct {{") + lines.append("\tgrpc.ClientStream") + lines.append("}") + lines.append("") + + # methods + lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Send(m {req_type}) error {{") + lines.append("\treturn x.ClientStream.SendMsg(m)") + lines.append("}") + lines.append("") + lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Recv() ({res_type}, error) {{") + lines.append(f"\tm := new({res_type[1:]})") + lines.append("\tif err := x.ClientStream.RecvMsg(m); err != nil {") + lines.append("\t\treturn nil, err") + lines.append("\t}") + lines.append("\treturn m, nil") + lines.append("}") + lines.append("") From 86953863200a88e00bfa30457e49ad5a59ef948b Mon Sep 17 00:00:00 2001 From: ayush00git Date: Fri, 29 May 2026 15:34:23 +0530 Subject: [PATCH 08/23] feat: add generate server api interface --- .../fory_compiler/generators/services/go.py | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index fcf9a37941..65097b95ad 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -127,7 +127,7 @@ def _generate_client_struct(self, service: Service) -> List[str]: def _generate_new_client(self, service: Service) -> List[str]: lines: List[str] = [] - lines.append(f"func New{self.to_pascal_case(service.name)}Client(cc grpc.ClientConnInterface) {self.to_pascal_case(service.name)}Client {{") + lines.append(f"func New{service.name}Client(cc grpc.ClientConnInterface) {service.name}Client {{") lines.append(f"\treturn &{self.to_camel_case(service.name)}Client{{cc}}") lines.append("}") lines.append("") @@ -153,7 +153,7 @@ def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> lines.append("") elif mode is StreamingMode.SERVER_STREAMING: lines.append(f'func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error) {{') - lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{self.to_pascal_case(service.name)}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)') + lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{service.name}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)') lines.append("\tif err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") @@ -170,7 +170,7 @@ def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> stream_index += 1 else: lines.append(f"func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error) {{") - lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{self.to_pascal_case(service.name)}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)') + lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{service.name}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)') lines.append("\tif err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") @@ -178,6 +178,7 @@ def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> lines.append("}") lines.append("") stream_index += 1 + return lines def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> List[str]: lines: List[str] = [] @@ -189,7 +190,7 @@ def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> Li continue if mode is StreamingMode.CLIENT_STREAMING: # type interface - lines.append(f"type {self.to_pascal_case(service.name)}_{self.to_pascal_case(method.name)}Client interface {{") + lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Client interface {{") lines.append(f"\tSend({req_type}) error") lines.append(f"\tCloseAndRecv() ({res_type}, error)") lines.append("\tgrpc.ClientStream") @@ -204,7 +205,7 @@ def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> Li # methods lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Send(m {req_type}) error {{") - lines.append(f"\treturn x.ClientStream.SendMsg(m)") + lines.append("\treturn x.ClientStream.SendMsg(m)") lines.append("}") lines.append("") lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) CloseAndRecv() ({res_type}, error) {{") @@ -220,7 +221,7 @@ def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> Li lines.append("") elif mode is StreamingMode.SERVER_STREAMING: # type interface - lines.append(f"type {self.to_pascal_case(service.name)}_{self.to_pascal_case(method.name)}Client interface {{") + lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Client interface {{") lines.append(f"\tRecv() ({res_type}, error)") lines.append("\tgrpc.ClientStream") lines.append("}") @@ -243,7 +244,7 @@ def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> Li lines.append("") else: # interface - lines.append(f"type {self.to_pascal_case(service.name)}_{self.to_pascal_case(method.name)}Client interface {{") + lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Client interface {{") lines.append(f"\tSend({req_type}) error") lines.append(f"\tRecv() ({res_type}, error)") lines.append("\tgrpc.ClientStream") @@ -269,3 +270,25 @@ def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> Li lines.append("\treturn m, nil") lines.append("}") lines.append("") + return lines + + def _generate_server_interface(self, service: Service, tracker: ImportTracker) -> List[str]: + lines: List[str] = [] + lines.append(f"// {service.name}Server is the server API for {service.name} service.") + lines.append(f"// All implementations must embed Unimplemented{service.name}Server") + lines.append("// for forward compatibility.") + lines.append(f"type {service.name}Server interface {{") + for method in service.methods: + req_type = self._resolve_go_type(method.request_type, tracker) + res_type = self._resolve_go_type(method.response_type, tracker) + mode = streaming_mode(method) + if mode is StreamingMode.UNARY: + lines.append(f"\t{self.to_pascal_case(method.name)}(context.Context, {req_type}) ({res_type}, error)") + elif mode is StreamingMode.SERVER_STREAMING: + lines.append(f"\t{self.to_pascal_case(method.name)}({req_type}, {service.name}_{self.to_pascal_case(method.name)}Server) error") + else: + lines.append(f"\t{self.to_pascal_case(method.name)}({service.name}_{self.to_pascal_case(method.name)}Server) error") + lines.append(f"\tmustEmbedUnimplemented{service.name}Server()") + lines.append("}") + lines.append("") + return lines From c7e0ddef017bc16890ec538efd58431ea26b27fc Mon Sep 17 00:00:00 2001 From: ayush00git Date: Fri, 29 May 2026 15:59:32 +0530 Subject: [PATCH 09/23] feat: add unimplemented server for forward compatibility --- .../fory_compiler/generators/services/go.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 65097b95ad..6b23cdc92b 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -292,3 +292,32 @@ def _generate_server_interface(self, service: Service, tracker: ImportTracker) - lines.append("}") lines.append("") return lines + + def _generate_unimplemented_server(self, service: Service, tracker: ImportTracker) -> List[str]: + lines: List[str] = [] + lines.append(f"// Unimplemented{service.name}Server must be embedded to have forward compatible implementation.") + lines.append(f"type Unimplemented{service.name}Server struct {{}}") + lines.append("") + lines.append(f"func (Unimplemented{service.name}Server) mustEmbedUnimplemented{service.name}Server() {{}}") + lines.append("") + for method in service.methods: + req_type = self._resolve_go_type(method.request_type, tracker) + res_type = self._resolve_go_type(method.response_type, tracker) + mode = streaming_mode(method) + if mode is StreamingMode.UNARY: + lines.append(f"func (Unimplemented{service.name}Server) {self.to_pascal_case(method.name)}(context.Context, {req_type}) ({res_type}, error) {{") + lines.append(f'\treturn nil, status.Errorf(codes.Unimplemented, "method {self.to_pascal_case(method.name)} not implemented")') + lines.append("}") + lines.append("") + elif mode is StreamingMode.SERVER_STREAMING: + lines.append(f"func (Unimplemented{service.name}Server) {self.to_pascal_case(method.name)}({req_type}, {service.name}_{self.to_pascal_case(method.name)}Server) error {{") + lines.append(f'\treturn status.Errorf(codes.Unimplemented, "method {self.to_pascal_case(method.name)} not implemented")') + lines.append("}") + lines.append("") + else: + lines.append(f"func (Unimplemented{service.name}Server) {self.to_pascal_case(method.name)}({service.name}_{self.to_pascal_case(method.name)}Server) error {{") + lines.append(f'\treturn status.Errorf(codes.Unimplemented, "method {self.to_pascal_case(method.name)} not implemented")') + lines.append("}") + lines.append("") + return lines + From bb8af196c11413ec9e54469671962e251ca39e72 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sat, 30 May 2026 14:26:29 +0530 Subject: [PATCH 10/23] feat: added server registrar and stream types --- .../fory_compiler/generators/services/go.py | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 6b23cdc92b..5f688df23f 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -321,3 +321,95 @@ def _generate_unimplemented_server(self, service: Service, tracker: ImportTracke lines.append("") return lines + def _generate_register_server(self, service: Service) -> List[str]: + lines: List[str] = [] + lines.append(f"func Register{service.name}Server(s grpc.ServiceRegistrar, srv {service.name}Server) {{") + lines.append(f"\ts.RegisterService(&_{service.name}_serviceDesc, srv)") + lines.append("}") + lines.append("") + return lines + + def _generate_server_stream_types(self, service: Service, tracker: ImportTracker) -> List[str]: + lines: List[str] = [] + for method in service.methods: + req_type = self._resolve_go_type(method.request_type, tracker) + res_type = self._resolve_go_type(method.response_type, tracker) + mode = streaming_mode(method) + if mode is StreamingMode.UNARY: + continue + elif mode is StreamingMode.CLIENT_STREAMING: + # type interface + lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Server interface {{") + lines.append(f"\tRecv() ({req_type}, error)") + lines.append(f"\tSendAndClose({res_type}) error") + lines.append("\tgrpc.ServerStream") + lines.append("}") + lines.append("") + + # struct + lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server struct {{") + lines.append("\tgrpc.ServerStream") + lines.append("}") + lines.append("") + + # methods + lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Recv() ({req_type}, error) {{") + lines.append(f"\tm := new({req_type[1:]})") + lines.append(f"\tif err := x.ServerStream.RecvMsg(m); err != nil {{") + lines.append("\t\treturn nil, err") + lines.append("\t}") + lines.append("\treturn m, nil") + lines.append("}") + lines.append("") + lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) SendAndClose(m {res_type}) error {{") + lines.append("\treturn x.ServerStream.SendMsg(m)") + lines.append("}") + lines.append("") + elif mode is StreamingMode.SERVER_STREAMING: + # type interface + lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Server interface {{") + lines.append(f"\tSend({res_type}) error") + lines.append("\tgrpc.ServerStream") + lines.append("}") + lines.append("") + + # struct + lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server struct {{") + lines.append("\tgrpc.ServerStream") + lines.append("}") + lines.append("") + + # methods + lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Send(m {res_type}) error {{") + lines.append("\treturn x.ServerStream.SendMsg(m)") + lines.append("}") + lines.append("") + else: + # type interface + lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Server interface {{") + lines.append(f"\tSend({res_type}) error") + lines.append(f"\tRecv() ({req_type}, error)") + lines.append("\tgrpc.ServerStream") + lines.append("}") + lines.append("") + + # struct + lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server struct {{") + lines.append("\tgrpc.ServerStream") + lines.append("}") + lines.append("") + + # methods + lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Send(m {res_type}) error {{") + lines.append(f"\treturn x.ServerStream.SendMsg(m)") + lines.append("}") + lines.append("") + lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Recv() ({req_type}, error) {{") + lines.append(f"\tm := new({req_type[1:]})") + lines.append("\tif err := x.ServerStream.RecvMsg(m); err != nil {") + lines.append("\t\treturn nil, err") + lines.append("\t}") + lines.append("\treturn m, nil") + lines.append("}") + lines.append("") + return lines From e498a52299b98705404069db125d3211539fccc0 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sun, 31 May 2026 17:03:17 +0530 Subject: [PATCH 11/23] feat: add service desc generations --- .../fory_compiler/generators/services/base.py | 1 + .../fory_compiler/generators/services/go.py | 85 +++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/compiler/fory_compiler/generators/services/base.py b/compiler/fory_compiler/generators/services/base.py index 57aa619c0c..802cacc01d 100644 --- a/compiler/fory_compiler/generators/services/base.py +++ b/compiler/fory_compiler/generators/services/base.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. +"Shared utilities for gRPC service stub generators." from enum import Enum from typing import List, Dict diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 5f688df23f..c0553a9f6b 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -413,3 +413,88 @@ def _generate_server_stream_types(self, service: Service, tracker: ImportTracker lines.append("}") lines.append("") return lines + + def _generate_service_desc(self, service: Service, tracker: ImportTracker) -> List[str]: + lines: List[str] = [] + for method in service.methods: + req_type = self._resolve_go_type(method.request_type, tracker) + res_type = self._resolve_go_type(method.response_type, tracker) + mode = streaming_mode(method) + # handlers + if mode is StreamingMode.UNARY: + lines.append(f"func _{service.name}_{self.to_pascal_case(method.name)}_Handler(srv interface{{}}, ctx context.Context, dec func(interface{{}}) error, interceptor grpc.UnaryServerInterceptor) (interface{{}}, error) {{") + lines.append(f"\tin := new({req_type[1:]})") + lines.append("\tif err := dec(in); err != nil {") + lines.append("\t\treturn nil, err") + lines.append("\t}") + lines.append("\tif interceptor == nil {") + lines.append(f"\t\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(ctx, in)") + lines.append("\t}") + lines.append("\tinfo := &grpc.UnaryServerInfo{") + lines.append("\t\tServer:\tsrv,") + lines.append(f'\t\tFullMethod:\t"{self.get_grpc_method_path(service, method)}",') + lines.append("\t}") + lines.append("\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {") + lines.append(f"\t\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(ctx, req.({req_type}))") + lines.append("\t}") + lines.append("\treturn interceptor(ctx, in, info, handler)") + lines.append("}") + lines.append("") + elif mode is StreamingMode.SERVER_STREAMING: + lines.append(f"func _{service.name}_{self.to_pascal_case(method.name)}_Handler(srv interface{{}}, stream grpc.ServerStream) error {{") + lines.append(f"\tm := new({req_type[1:]})") + lines.append("\tif err := stream.RecvMsg(m); err != nil {") + lines.append(f"\t\treturn err") + lines.append("\t}") + lines.append(f"\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(m, &{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server{{stream}})") + lines.append("}") + lines.append("") + else: + lines.append(f"func _{service.name}_{self.to_pascal_case(method.name)}_Handler(srv interface{{}}, stream grpc.ServerStream) error {{") + lines.append(f"\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(&{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server{{stream}}))") + lines.append("}") + lines.append("") + # vars + lines.append(f"var _{service.name}_serviceDesc = grpc.ServiceDesc{{") + lines.append(f'\tServiceName: "{self.get_grpc_service_name(service)}",') + lines.append(f"\tHandlerType: (*{service.name}Server)(nil),") + # unary type service descriptors + lines.append("\tMethods: []grpc.MethodDesc{") + lines.extend(self._generate_unary_type_desc(service)) + lines.append("\t},") + # stream type service descriptors + lines.append("\tStreams: []grpc.StreamDesc{") + lines.extend(self._generate_stream_type_desc(service)) + lines.append("\t},") + lines.append(f'\tMetadata: "{self.get_file_name()}.fory",') + lines.append("}") + lines.append("") + return lines + + def _generate_unary_type_desc(self, service: Service) -> List[str]: + lines: List[str] = [] + for method in service.methods: + mode = streaming_mode(method) + if mode is StreamingMode.UNARY: + lines.append("\t\t{") + lines.append(f'\t\t\tMethodName:\t"{self.to_pascal_case(method.name)}",') + lines.append(f"\t\t\tHandler:\t_{service.name}_{self.to_pascal_case(method.name)}_Handler,") + lines.append("\t\t},") + return lines + + def _generate_stream_type_desc(self, service: Service) -> List[str]: + lines: List[str] = [] + for method in service.methods: + mode = streaming_mode(method) + if mode is StreamingMode.UNARY: + continue + else: + lines.append("\t\t{") + lines.append(f'\t\t\tStreamName:\t"{self.to_pascal_case(method.name)}",') + lines.append(f"\t\t\tHandler:\t_{service.name}_{self.to_pascal_case(method.name)}_Handler,") + if mode is StreamingMode.CLIENT_STREAMING or mode is StreamingMode.BIDIRECTIONAL: + lines.append("\t\t\tClientStreams:\ttrue,") + if mode is StreamingMode.SERVER_STREAMING or mode is StreamingMode.BIDIRECTIONAL: + lines.append("\t\t\tServerStreams:\ttrue,") + lines.append("\t\t},") + return lines From 51f4f859e0af245ada22efe64f9af6a5f75e7b3d Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sun, 31 May 2026 18:06:07 +0530 Subject: [PATCH 12/23] feat: wire up server/client generations to generate file --- .../fory_compiler/generators/services/go.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index c0553a9f6b..ddeca1626a 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -48,12 +48,17 @@ def _generate_grpc_file(self, service: Service) -> GeneratedFile: # save the placeholder index for now import_placeholder_index = len(lines) - # Client interface - - - - - # after all the service code gets generated, insert the import block at the placeholder index + lines.extend(self._generate_client_interface(service, tracker)) + lines.extend(self._generate_client_struct(service)) + lines.extend(self._generate_client_methods(service, tracker)) + lines.extend(self._generate_stream_types(service, tracker)) + lines.extend(self._generate_server_interface(service, tracker)) + lines.extend(self._generate_unimplemented_server(service, tracker)) + lines.extend(self._generate_server_stream_types(service, tracker)) + lines.extend(self._generate_service_desc(service, tracker)) + lines.extend(self._generate_register_server(service)) + + # insert the import block at the saved placeholder index import_lines = self._build_import_block(tracker) for i, line in enumerate(import_lines): lines.insert(import_placeholder_index + i, line) @@ -454,7 +459,7 @@ def _generate_service_desc(self, service: Service, tracker: ImportTracker) -> Li lines.append(f"\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(&{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server{{stream}}))") lines.append("}") lines.append("") - # vars + # var lines.append(f"var _{service.name}_serviceDesc = grpc.ServiceDesc{{") lines.append(f'\tServiceName: "{self.get_grpc_service_name(service)}",') lines.append(f"\tHandlerType: (*{service.name}Server)(nil),") From 11be2ce874afe5ba0bba406618241ffe0496f403 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sun, 31 May 2026 18:06:42 +0530 Subject: [PATCH 13/23] feat: GoGenerator inherits from the GoServiceGeneratorMixin --- compiler/fory_compiler/generators/go.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/fory_compiler/generators/go.py b/compiler/fory_compiler/generators/go.py index b25e63c799..383dd8a6e3 100644 --- a/compiler/fory_compiler/generators/go.py +++ b/compiler/fory_compiler/generators/go.py @@ -21,6 +21,7 @@ from typing import Dict, List, Optional, Set, Tuple, Union as TypingUnion from fory_compiler.generators.base import BaseGenerator, GeneratedFile +from fory_compiler.generators.services.go import GoServiceGeneratorMixin from fory_compiler.frontend.utils import parse_idl_file from fory_compiler.ir.ast import ( Message, @@ -38,7 +39,7 @@ from fory_compiler.ir.types import PrimitiveKind -class GoGenerator(BaseGenerator): +class GoGenerator(GoServiceGeneratorMixin, BaseGenerator): """Generates Go structs with fory tags.""" language_name = "go" From 3bca344e219ecc0e3d17d27a44c2211e44b0e8f3 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sun, 31 May 2026 18:11:10 +0530 Subject: [PATCH 14/23] fix: .fory -> .fdl --- compiler/fory_compiler/generators/services/go.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index ddeca1626a..35db769df9 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -471,7 +471,7 @@ def _generate_service_desc(self, service: Service, tracker: ImportTracker) -> Li lines.append("\tStreams: []grpc.StreamDesc{") lines.extend(self._generate_stream_type_desc(service)) lines.append("\t},") - lines.append(f'\tMetadata: "{self.get_file_name()}.fory",') + lines.append(f'\tMetadata: "{self.get_file_name()}.fdl",') lines.append("}") lines.append("") return lines From 839e9df39d1cfb7ae151ed187971cc2de4a08224 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sun, 31 May 2026 18:35:06 +0530 Subject: [PATCH 15/23] add service generate file def to GoGenerator class --- compiler/fory_compiler/generators/go.py | 3 +++ compiler/fory_compiler/generators/services/go.py | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/fory_compiler/generators/go.py b/compiler/fory_compiler/generators/go.py index 383dd8a6e3..28be319422 100644 --- a/compiler/fory_compiler/generators/go.py +++ b/compiler/fory_compiler/generators/go.py @@ -207,6 +207,9 @@ def generate(self) -> List[GeneratedFile]: # Generate a single Go file with all types files.append(self.generate_file()) + # Generate gRPC service stubs + files.extend(self.generate_services()) + return files def get_package_name(self) -> str: diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 35db769df9..741fb55d17 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -50,6 +50,7 @@ def _generate_grpc_file(self, service: Service) -> GeneratedFile: lines.extend(self._generate_client_interface(service, tracker)) lines.extend(self._generate_client_struct(service)) + lines.extend(self._generate_new_client(service)) lines.extend(self._generate_client_methods(service, tracker)) lines.extend(self._generate_stream_types(service, tracker)) lines.extend(self._generate_server_interface(service, tracker)) @@ -76,8 +77,8 @@ def _build_import_block(self, tracker: ImportTracker) -> List[str]: '"google.golang.org/grpc/status"', ] - for path in tracker.go_imports(): - imports.append(f'"{path}"') + for alias, path in tracker._imports.items(): + imports.append(f'{alias} "{path}"') sorted_imports = sorted(set(imports)) From 5a5312974e9221be275c807e4158ac63c5f0a89d Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sun, 31 May 2026 18:58:00 +0530 Subject: [PATCH 16/23] passed fory instance to enforce codecv2 --- compiler/fory_compiler/generators/services/go.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 741fb55d17..de554ee6ac 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -75,6 +75,7 @@ def _build_import_block(self, tracker: ImportTracker) -> List[str]: '"google.golang.org/grpc"', '"google.golang.org/grpc/codes"', '"google.golang.org/grpc/status"', + '"github.com/apache/fory/go/fory"', ] for alias, path in tracker._imports.items(): @@ -127,14 +128,15 @@ def _generate_client_struct(self, service: Service) -> List[str]: lines: List[str] = [] lines.append(f"type {self.to_camel_case(service.name)}Client struct {{") lines.append("\tcc grpc.ClientConnInterface") + lines.append("\tfory *fory.Fory") lines.append("}") lines.append("") return lines def _generate_new_client(self, service: Service) -> List[str]: lines: List[str] = [] - lines.append(f"func New{service.name}Client(cc grpc.ClientConnInterface) {service.name}Client {{") - lines.append(f"\treturn &{self.to_camel_case(service.name)}Client{{cc}}") + lines.append(f"func New{service.name}Client(cc grpc.ClientConnInterface, f *fory.Fory) {service.name}Client {{") + lines.append(f"\treturn &{self.to_camel_case(service.name)}Client{{cc: cc, fory: f}}") lines.append("}") lines.append("") return lines @@ -150,7 +152,7 @@ def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> if mode is StreamingMode.UNARY: lines.append(f"func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({res_type}, error) {{") lines.append(f"\tout := new({res_type[1:]})") - lines.append(f'\terr := c.cc.Invoke(ctx, "{self.get_grpc_method_path(service, method)}", in, out, grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)') + lines.append(f'\terr := c.cc.Invoke(ctx, "{self.get_grpc_method_path(service, method)}", in, out, grpc.ForceCodecV2(forygrpc.CodecV2{{Fory: c.fory}}), opts...)') lines.append("\tif err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") @@ -159,7 +161,7 @@ def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> lines.append("") elif mode is StreamingMode.SERVER_STREAMING: lines.append(f'func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error) {{') - lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{service.name}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)') + lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{service.name}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{Fory: c.fory}}), opts...)') lines.append("\tif err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") @@ -176,7 +178,7 @@ def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> stream_index += 1 else: lines.append(f"func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error) {{") - lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{service.name}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{}}), opts...)') + lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{service.name}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{Fory: c.fory}}), opts...)') lines.append("\tif err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") From 34c7537ed56a6cce83576601bc7980832ce8bbf2 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 1 Jun 2026 14:51:31 +0530 Subject: [PATCH 17/23] pkg: added new forygrpc codec --- go/fory/grpc/codec.go | 54 +++++++++++++++++++++++++++++++++++++++++++ go/fory/grpc/go.mod | 29 +++++++++++++++++++++++ go/fory/grpc/go.sum | 22 ++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 go/fory/grpc/codec.go create mode 100644 go/fory/grpc/go.mod create mode 100644 go/fory/grpc/go.sum diff --git a/go/fory/grpc/codec.go b/go/fory/grpc/codec.go new file mode 100644 index 0000000000..99b590a69a --- /dev/null +++ b/go/fory/grpc/codec.go @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF 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. + +package forygrpc + +import ( + "github.com/apache/fory/go/fory" + "google.golang.org/grpc/mem" +) + +// CodecV2 implements grpc/encoding.CodecV2, replacing the default protobuf +// codec with Fory binary serialization on both client and server sides. +// Pass a configured *fory.Fory instance with all message types registered. +type CodecV2 struct { + Fory *fory.Fory +} + +// Marshal serializes the message using Fory and wraps the resulting bytes +// in a single-buffer BufferSlice for gRPC transport. +func (c CodecV2) Marshal(v any) (mem.BufferSlice, error) { + b, err := c.Fory.Marshal(v) + if err != nil { + return nil, err + } + return mem.BufferSlice{mem.NewBuffer(&b, nil)}, nil +} + +// Unmarshal materializes the incoming buffer slice into a contiguous byte +// slice and deserializes it into v using Fory. +func (c CodecV2) Unmarshal(data mem.BufferSlice, v any) error { + buf := data.MaterializeToBuffer(mem.DefaultBufferPool()) + defer buf.Free() + return c.Fory.Unmarshal(buf.ReadOnlyData(), v) +} + +// Name returns the codec identifier registered with gRPC. Using "fory" +// ensures this codec does not conflict with the default "proto" codec. +func (CodecV2) Name() string { + return "fory" +} diff --git a/go/fory/grpc/go.mod b/go/fory/grpc/go.mod new file mode 100644 index 0000000000..47696f94b1 --- /dev/null +++ b/go/fory/grpc/go.mod @@ -0,0 +1,29 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF 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. + +module github.com/apache/fory/go/fory/grpc + +go 1.24.0 + +require ( + github.com/apache/fory/go/fory v0.0.0 + google.golang.org/grpc v1.68.0 +) + +require golang.org/x/sys v0.25.0 // indirect + +replace github.com/apache/fory/go/fory => ../ diff --git a/go/fory/grpc/go.sum b/go/fory/grpc/go.sum new file mode 100644 index 0000000000..d0471f5332 --- /dev/null +++ b/go/fory/grpc/go.sum @@ -0,0 +1,22 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 8338ce63fb60665b3f2dc773d370cc161bf9358e Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 1 Jun 2026 15:09:41 +0530 Subject: [PATCH 18/23] test: add tests for codec --- go/fory/grpc/codec_test.go | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 go/fory/grpc/codec_test.go diff --git a/go/fory/grpc/codec_test.go b/go/fory/grpc/codec_test.go new file mode 100644 index 0000000000..65aa8519ae --- /dev/null +++ b/go/fory/grpc/codec_test.go @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF 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. + +package forygrpc + +import ( + "testing" + + "github.com/apache/fory/go/fory" + "google.golang.org/grpc/mem" +) + +// testMessage is a simple struct registered with Fory for use across all codec tests. +type testMessage struct { + Name string + Value int32 +} + +// newTestFory creates a Fory instance with testMessage registered under type ID 200. +func newTestFory(t *testing.T) *fory.Fory { + t.Helper() + f := fory.New(fory.WithXlang(false)) + if err := f.RegisterStruct(testMessage{}, 200); err != nil { + t.Fatalf("RegisterStruct: %v", err) + } + return f +} + +// TestRoundTrip verifies that a message marshaled by CodecV2 can be fully +// recovered by Unmarshal with all fields intact. +func TestRoundTrip(t *testing.T) { + codec := CodecV2{Fory: newTestFory(t)} + original := &testMessage{Name: "hello", Value: 42} + + buf, err := codec.Marshal(original) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + got := &testMessage{} + if err := codec.Unmarshal(buf, got); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + + if got.Name != original.Name || got.Value != original.Value { + t.Errorf("round-trip mismatch: got %+v, want %+v", got, original) + } +} + +// TestUnmarshalError verifies that Unmarshal returns an error on corrupt input +// rather than silently producing a zero-value result. +func TestUnmarshalError(t *testing.T) { + codec := CodecV2{Fory: newTestFory(t)} + garbage := []byte{0xFF, 0xFE, 0x00, 0x01} + data := mem.BufferSlice{mem.NewBuffer(&garbage, nil)} + if err := codec.Unmarshal(data, &testMessage{}); err == nil { + t.Error("expected error unmarshaling corrupt data, got nil") + } +} + +// TestName verifies the codec identifier matches the value used in grpc.ForceCodecV2 calls. +func TestName(t *testing.T) { + if name := (CodecV2{}).Name(); name != "fory" { + t.Errorf("Name() = %q, want %q", name, "fory") + } +} From f1e0ab8c5ea485639111e17a5bfc24fd7251100a Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 1 Jun 2026 15:17:58 +0530 Subject: [PATCH 19/23] fix: register go to codengen tests --- compiler/fory_compiler/generators/go.py | 5 ++-- .../tests/test_service_codegen.py | 23 ++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/compiler/fory_compiler/generators/go.py b/compiler/fory_compiler/generators/go.py index 28be319422..c4cc57e73c 100644 --- a/compiler/fory_compiler/generators/go.py +++ b/compiler/fory_compiler/generators/go.py @@ -207,8 +207,9 @@ def generate(self) -> List[GeneratedFile]: # Generate a single Go file with all types files.append(self.generate_file()) - # Generate gRPC service stubs - files.extend(self.generate_services()) + # Generate gRPC service stubs if requested + if self.options.grpc: + files.extend(self.generate_services()) return files diff --git a/compiler/fory_compiler/tests/test_service_codegen.py b/compiler/fory_compiler/tests/test_service_codegen.py index 413fbf0131..a1fb686817 100644 --- a/compiler/fory_compiler/tests/test_service_codegen.py +++ b/compiler/fory_compiler/tests/test_service_codegen.py @@ -129,7 +129,7 @@ def test_service_definition_does_not_affect_message_codegen(): def test_generate_services_returns_empty_list_for_unsupported_generators(): schema = parse_fdl(_GREETER_WITH_SERVICE) for generator_cls in GENERATOR_CLASSES: - if generator_cls in (JavaGenerator, PythonGenerator): + if generator_cls in (JavaGenerator, PythonGenerator, GoGenerator): continue options = GeneratorOptions(output_dir=Path("/tmp")) generator = generator_cls(schema, options) @@ -218,6 +218,27 @@ def test_grpc_streaming_method_shapes(): assert "self.client = channel.stream_unary(" in python assert "self.bidi = channel.stream_stream(" in python + go = next(iter(generate_service_files(schema, GoGenerator).values())) + assert "ClientStreams:\ttrue" in go + assert "ServerStreams:\ttrue" in go + assert "grpc.ClientStream" in go + assert "grpc.ServerStream" in go + + +def test_go_grpc_service_codegen(): + schema = parse_fdl(_GREETER_WITH_SERVICE) + files = generate_service_files(schema, GoGenerator) + assert len(files) == 1 + content = next(iter(files.values())) + assert "func NewGreeterClient(" in content + assert "func RegisterGreeterServer(" in content + assert "type GreeterClient interface" in content + assert "type GreeterServer interface" in content + assert "type UnimplementedGreeterServer struct" in content + assert "forygrpc.CodecV2{Fory: c.fory}" in content + assert '"/demo.greeter.Greeter/SayHello"' in content + assert "mustEmbedUnimplementedGreeterServer()" in content + def test_java_outer_classname_service_references_nested_model_types(): schema = parse_fdl( From 09efb85fdc7159154fcef4bfa205fa6b623559d2 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 1 Jun 2026 15:32:04 +0530 Subject: [PATCH 20/23] fix: syntax fix --- compiler/fory_compiler/generators/services/go.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index de554ee6ac..0e38ffe0c0 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -459,7 +459,7 @@ def _generate_service_desc(self, service: Service, tracker: ImportTracker) -> Li lines.append("") else: lines.append(f"func _{service.name}_{self.to_pascal_case(method.name)}_Handler(srv interface{{}}, stream grpc.ServerStream) error {{") - lines.append(f"\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(&{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server{{stream}}))") + lines.append(f"\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(&{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server{{stream}})") lines.append("}") lines.append("") # var From c05871585e6501aed96a5e104d0d59ea8b26c7e1 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 1 Jun 2026 15:38:53 +0530 Subject: [PATCH 21/23] refactor comments --- compiler/fory_compiler/generators/services/go.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 0e38ffe0c0..33ad171e8c 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -45,7 +45,7 @@ def _generate_grpc_file(self, service: Service) -> GeneratedFile: lines.append("") # Imports - # save the placeholder index for now + # save the placeholder index import_placeholder_index = len(lines) lines.extend(self._generate_client_interface(service, tracker)) From ad19ea88e87098cd65651ae3553ef0c55f37c3a1 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 1 Jun 2026 15:41:03 +0530 Subject: [PATCH 22/23] fix: removed unused variable --- compiler/fory_compiler/generators/services/go.py | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 33ad171e8c..41407fb972 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -426,7 +426,6 @@ def _generate_service_desc(self, service: Service, tracker: ImportTracker) -> Li lines: List[str] = [] for method in service.methods: req_type = self._resolve_go_type(method.request_type, tracker) - res_type = self._resolve_go_type(method.response_type, tracker) mode = streaming_mode(method) # handlers if mode is StreamingMode.UNARY: From 3d9ec1075419a14c689d56f8249dacdb8abe0f3a Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 1 Jun 2026 15:51:42 +0530 Subject: [PATCH 23/23] fix: fit linting --- .../fory_compiler/generators/services/base.py | 4 +- .../fory_compiler/generators/services/go.py | 307 +++++++++++++----- 2 files changed, 229 insertions(+), 82 deletions(-) diff --git a/compiler/fory_compiler/generators/services/base.py b/compiler/fory_compiler/generators/services/base.py index 802cacc01d..2bc2bcfb2d 100644 --- a/compiler/fory_compiler/generators/services/base.py +++ b/compiler/fory_compiler/generators/services/base.py @@ -21,6 +21,7 @@ from typing import List, Dict from fory_compiler.ir.ast import RpcMethod + class StreamingMode(Enum): UNARY = 1 CLIENT_STREAMING = 2 @@ -42,9 +43,10 @@ def streaming_mode(method: RpcMethod) -> StreamingMode: class ImportTracker: """Accumulates cross-package Go imports for generated service stubs.""" + def __init__(self): self._imports: Dict[str, str] = {} - + def add(self, alias: str, import_path: str) -> None: self._imports[alias] = import_path diff --git a/compiler/fory_compiler/generators/services/go.py b/compiler/fory_compiler/generators/services/go.py index 41407fb972..281f6afab3 100644 --- a/compiler/fory_compiler/generators/services/go.py +++ b/compiler/fory_compiler/generators/services/go.py @@ -18,16 +18,22 @@ """Go gRPC service code generator.""" from typing import List -from fory_compiler.generators.services.base import ImportTracker, StreamingMode, streaming_mode +from fory_compiler.generators.services.base import ( + ImportTracker, + StreamingMode, + streaming_mode, +) from fory_compiler.generators.base import GeneratedFile -from fory_compiler.ir.ast import RpcMethod, Service, NamedType +from fory_compiler.ir.ast import Service, NamedType class GoServiceGeneratorMixin: """Generates Go gRPC service stubs.""" def generate_services(self) -> List[GeneratedFile]: - local_services = [s for s in self.schema.services if not self.is_imported_type(s)] + local_services = [ + s for s in self.schema.services if not self.is_imported_type(s) + ] if not local_services: return [] return [self._generate_grpc_file(s) for s in local_services] @@ -65,8 +71,7 @@ def _generate_grpc_file(self, service: Service) -> GeneratedFile: lines.insert(import_placeholder_index + i, line) return GeneratedFile( - path=f"{self.get_file_name()}_grpc.go", - content="\n".join(lines) + path=f"{self.get_file_name()}_grpc.go", content="\n".join(lines) ) def _build_import_block(self, tracker: ImportTracker) -> List[str]: @@ -77,10 +82,10 @@ def _build_import_block(self, tracker: ImportTracker) -> List[str]: '"google.golang.org/grpc/status"', '"github.com/apache/fory/go/fory"', ] - + for alias, path in tracker._imports.items(): imports.append(f'{alias} "{path}"') - + sorted_imports = sorted(set(imports)) lines = ["import ("] @@ -102,9 +107,13 @@ def _resolve_go_type(self, named_type: NamedType, tracker: ImportTracker) -> str return f"*{alias}.{type_ref}" return f"*{type_ref}" - def _generate_client_interface(self, service: Service, tracker: ImportTracker) -> List[str]: + def _generate_client_interface( + self, service: Service, tracker: ImportTracker + ) -> List[str]: lines: List[str] = [] - lines.append(f"// {service.name}Client is the client API for {service.name} service.") + lines.append( + f"// {service.name}Client is the client API for {service.name} service." + ) lines.append(f"type {service.name}Client interface {{") for method in service.methods: req_type = self._resolve_go_type(method.request_type, tracker) @@ -135,13 +144,19 @@ def _generate_client_struct(self, service: Service) -> List[str]: def _generate_new_client(self, service: Service) -> List[str]: lines: List[str] = [] - lines.append(f"func New{service.name}Client(cc grpc.ClientConnInterface, f *fory.Fory) {service.name}Client {{") - lines.append(f"\treturn &{self.to_camel_case(service.name)}Client{{cc: cc, fory: f}}") + lines.append( + f"func New{service.name}Client(cc grpc.ClientConnInterface, f *fory.Fory) {service.name}Client {{" + ) + lines.append( + f"\treturn &{self.to_camel_case(service.name)}Client{{cc: cc, fory: f}}" + ) lines.append("}") lines.append("") return lines - def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> List[str]: + def _generate_client_methods( + self, service: Service, tracker: ImportTracker + ) -> List[str]: lines: List[str] = [] tracker.add("forygrpc", "github.com/apache/fory/go/fory/grpc") stream_index = 0 @@ -150,9 +165,13 @@ def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> res_type = self._resolve_go_type(method.response_type, tracker) mode = streaming_mode(method) if mode is StreamingMode.UNARY: - lines.append(f"func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({res_type}, error) {{") + lines.append( + f"func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({res_type}, error) {{" + ) lines.append(f"\tout := new({res_type[1:]})") - lines.append(f'\terr := c.cc.Invoke(ctx, "{self.get_grpc_method_path(service, method)}", in, out, grpc.ForceCodecV2(forygrpc.CodecV2{{Fory: c.fory}}), opts...)') + lines.append( + f'\terr := c.cc.Invoke(ctx, "{self.get_grpc_method_path(service, method)}", in, out, grpc.ForceCodecV2(forygrpc.CodecV2{{Fory: c.fory}}), opts...)' + ) lines.append("\tif err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") @@ -160,12 +179,18 @@ def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> lines.append("}") lines.append("") elif mode is StreamingMode.SERVER_STREAMING: - lines.append(f'func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error) {{') - lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{service.name}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{Fory: c.fory}}), opts...)') + lines.append( + f"func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, in {req_type}, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error) {{" + ) + lines.append( + f'\tstream, err := c.cc.NewStream(ctx, &_{service.name}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{Fory: c.fory}}), opts...)' + ) lines.append("\tif err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") - lines.append(f"\tx := &{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client{{stream}}") + lines.append( + f"\tx := &{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client{{stream}}" + ) lines.append("\tif err := x.SendMsg(in); err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") @@ -177,18 +202,26 @@ def _generate_client_methods(self, service: Service, tracker: ImportTracker) -> lines.append("") stream_index += 1 else: - lines.append(f"func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error) {{") - lines.append(f'\tstream, err := c.cc.NewStream(ctx, &_{service.name}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{Fory: c.fory}}), opts...)') + lines.append( + f"func (c *{self.to_camel_case(service.name)}Client) {self.to_pascal_case(method.name)}(ctx context.Context, opts ...grpc.CallOption) ({service.name}_{self.to_pascal_case(method.name)}Client, error) {{" + ) + lines.append( + f'\tstream, err := c.cc.NewStream(ctx, &_{service.name}_serviceDesc.Streams[{stream_index}], "{self.get_grpc_method_path(service, method)}", grpc.ForceCodecV2(forygrpc.CodecV2{{Fory: c.fory}}), opts...)' + ) lines.append("\tif err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") - lines.append(f"\treturn &{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client{{stream}}, nil") + lines.append( + f"\treturn &{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client{{stream}}, nil" + ) lines.append("}") lines.append("") stream_index += 1 return lines - - def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> List[str]: + + def _generate_stream_types( + self, service: Service, tracker: ImportTracker + ) -> List[str]: lines: List[str] = [] for method in service.methods: req_type = self._resolve_go_type(method.request_type, tracker) @@ -198,7 +231,9 @@ def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> Li continue if mode is StreamingMode.CLIENT_STREAMING: # type interface - lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Client interface {{") + lines.append( + f"type {service.name}_{self.to_pascal_case(method.name)}Client interface {{" + ) lines.append(f"\tSend({req_type}) error") lines.append(f"\tCloseAndRecv() ({res_type}, error)") lines.append("\tgrpc.ClientStream") @@ -206,17 +241,23 @@ def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> Li lines.append("") # struct - lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client struct {{") + lines.append( + f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client struct {{" + ) lines.append("\tgrpc.ClientStream") lines.append("}") lines.append("") # methods - lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Send(m {req_type}) error {{") + lines.append( + f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Send(m {req_type}) error {{" + ) lines.append("\treturn x.ClientStream.SendMsg(m)") lines.append("}") lines.append("") - lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) CloseAndRecv() ({res_type}, error) {{") + lines.append( + f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) CloseAndRecv() ({res_type}, error) {{" + ) lines.append("\tif err := x.ClientStream.CloseSend(); err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") @@ -229,20 +270,26 @@ def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> Li lines.append("") elif mode is StreamingMode.SERVER_STREAMING: # type interface - lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Client interface {{") + lines.append( + f"type {service.name}_{self.to_pascal_case(method.name)}Client interface {{" + ) lines.append(f"\tRecv() ({res_type}, error)") lines.append("\tgrpc.ClientStream") lines.append("}") lines.append("") - + # struct - lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client struct {{") + lines.append( + f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client struct {{" + ) lines.append("\tgrpc.ClientStream") lines.append("}") lines.append("") # methods - lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Recv() ({res_type}, error) {{") + lines.append( + f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Recv() ({res_type}, error) {{" + ) lines.append(f"\tm := new({res_type[1:]})") lines.append("\tif err := x.ClientStream.RecvMsg(m); err != nil {") lines.append("\t\treturn nil, err") @@ -252,7 +299,9 @@ def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> Li lines.append("") else: # interface - lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Client interface {{") + lines.append( + f"type {service.name}_{self.to_pascal_case(method.name)}Client interface {{" + ) lines.append(f"\tSend({req_type}) error") lines.append(f"\tRecv() ({res_type}, error)") lines.append("\tgrpc.ClientStream") @@ -260,17 +309,23 @@ def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> Li lines.append("") # struct - lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client struct {{") + lines.append( + f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client struct {{" + ) lines.append("\tgrpc.ClientStream") lines.append("}") lines.append("") # methods - lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Send(m {req_type}) error {{") + lines.append( + f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Send(m {req_type}) error {{" + ) lines.append("\treturn x.ClientStream.SendMsg(m)") lines.append("}") lines.append("") - lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Recv() ({res_type}, error) {{") + lines.append( + f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Client) Recv() ({res_type}, error) {{" + ) lines.append(f"\tm := new({res_type[1:]})") lines.append("\tif err := x.ClientStream.RecvMsg(m); err != nil {") lines.append("\t\treturn nil, err") @@ -280,10 +335,16 @@ def _generate_stream_types(self, service: Service, tracker: ImportTracker) -> Li lines.append("") return lines - def _generate_server_interface(self, service: Service, tracker: ImportTracker) -> List[str]: + def _generate_server_interface( + self, service: Service, tracker: ImportTracker + ) -> List[str]: lines: List[str] = [] - lines.append(f"// {service.name}Server is the server API for {service.name} service.") - lines.append(f"// All implementations must embed Unimplemented{service.name}Server") + lines.append( + f"// {service.name}Server is the server API for {service.name} service." + ) + lines.append( + f"// All implementations must embed Unimplemented{service.name}Server" + ) lines.append("// for forward compatibility.") lines.append(f"type {service.name}Server interface {{") for method in service.methods: @@ -291,53 +352,81 @@ def _generate_server_interface(self, service: Service, tracker: ImportTracker) - res_type = self._resolve_go_type(method.response_type, tracker) mode = streaming_mode(method) if mode is StreamingMode.UNARY: - lines.append(f"\t{self.to_pascal_case(method.name)}(context.Context, {req_type}) ({res_type}, error)") + lines.append( + f"\t{self.to_pascal_case(method.name)}(context.Context, {req_type}) ({res_type}, error)" + ) elif mode is StreamingMode.SERVER_STREAMING: - lines.append(f"\t{self.to_pascal_case(method.name)}({req_type}, {service.name}_{self.to_pascal_case(method.name)}Server) error") + lines.append( + f"\t{self.to_pascal_case(method.name)}({req_type}, {service.name}_{self.to_pascal_case(method.name)}Server) error" + ) else: - lines.append(f"\t{self.to_pascal_case(method.name)}({service.name}_{self.to_pascal_case(method.name)}Server) error") + lines.append( + f"\t{self.to_pascal_case(method.name)}({service.name}_{self.to_pascal_case(method.name)}Server) error" + ) lines.append(f"\tmustEmbedUnimplemented{service.name}Server()") lines.append("}") lines.append("") return lines - def _generate_unimplemented_server(self, service: Service, tracker: ImportTracker) -> List[str]: + def _generate_unimplemented_server( + self, service: Service, tracker: ImportTracker + ) -> List[str]: lines: List[str] = [] - lines.append(f"// Unimplemented{service.name}Server must be embedded to have forward compatible implementation.") + lines.append( + f"// Unimplemented{service.name}Server must be embedded to have forward compatible implementation." + ) lines.append(f"type Unimplemented{service.name}Server struct {{}}") lines.append("") - lines.append(f"func (Unimplemented{service.name}Server) mustEmbedUnimplemented{service.name}Server() {{}}") + lines.append( + f"func (Unimplemented{service.name}Server) mustEmbedUnimplemented{service.name}Server() {{}}" + ) lines.append("") for method in service.methods: req_type = self._resolve_go_type(method.request_type, tracker) res_type = self._resolve_go_type(method.response_type, tracker) mode = streaming_mode(method) if mode is StreamingMode.UNARY: - lines.append(f"func (Unimplemented{service.name}Server) {self.to_pascal_case(method.name)}(context.Context, {req_type}) ({res_type}, error) {{") - lines.append(f'\treturn nil, status.Errorf(codes.Unimplemented, "method {self.to_pascal_case(method.name)} not implemented")') + lines.append( + f"func (Unimplemented{service.name}Server) {self.to_pascal_case(method.name)}(context.Context, {req_type}) ({res_type}, error) {{" + ) + lines.append( + f'\treturn nil, status.Errorf(codes.Unimplemented, "method {self.to_pascal_case(method.name)} not implemented")' + ) lines.append("}") lines.append("") elif mode is StreamingMode.SERVER_STREAMING: - lines.append(f"func (Unimplemented{service.name}Server) {self.to_pascal_case(method.name)}({req_type}, {service.name}_{self.to_pascal_case(method.name)}Server) error {{") - lines.append(f'\treturn status.Errorf(codes.Unimplemented, "method {self.to_pascal_case(method.name)} not implemented")') + lines.append( + f"func (Unimplemented{service.name}Server) {self.to_pascal_case(method.name)}({req_type}, {service.name}_{self.to_pascal_case(method.name)}Server) error {{" + ) + lines.append( + f'\treturn status.Errorf(codes.Unimplemented, "method {self.to_pascal_case(method.name)} not implemented")' + ) lines.append("}") lines.append("") else: - lines.append(f"func (Unimplemented{service.name}Server) {self.to_pascal_case(method.name)}({service.name}_{self.to_pascal_case(method.name)}Server) error {{") - lines.append(f'\treturn status.Errorf(codes.Unimplemented, "method {self.to_pascal_case(method.name)} not implemented")') + lines.append( + f"func (Unimplemented{service.name}Server) {self.to_pascal_case(method.name)}({service.name}_{self.to_pascal_case(method.name)}Server) error {{" + ) + lines.append( + f'\treturn status.Errorf(codes.Unimplemented, "method {self.to_pascal_case(method.name)} not implemented")' + ) lines.append("}") lines.append("") return lines def _generate_register_server(self, service: Service) -> List[str]: lines: List[str] = [] - lines.append(f"func Register{service.name}Server(s grpc.ServiceRegistrar, srv {service.name}Server) {{") + lines.append( + f"func Register{service.name}Server(s grpc.ServiceRegistrar, srv {service.name}Server) {{" + ) lines.append(f"\ts.RegisterService(&_{service.name}_serviceDesc, srv)") lines.append("}") lines.append("") return lines - def _generate_server_stream_types(self, service: Service, tracker: ImportTracker) -> List[str]: + def _generate_server_stream_types( + self, service: Service, tracker: ImportTracker + ) -> List[str]: lines: List[str] = [] for method in service.methods: req_type = self._resolve_go_type(method.request_type, tracker) @@ -347,7 +436,9 @@ def _generate_server_stream_types(self, service: Service, tracker: ImportTracker continue elif mode is StreamingMode.CLIENT_STREAMING: # type interface - lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Server interface {{") + lines.append( + f"type {service.name}_{self.to_pascal_case(method.name)}Server interface {{" + ) lines.append(f"\tRecv() ({req_type}, error)") lines.append(f"\tSendAndClose({res_type}) error") lines.append("\tgrpc.ServerStream") @@ -355,46 +446,60 @@ def _generate_server_stream_types(self, service: Service, tracker: ImportTracker lines.append("") # struct - lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server struct {{") + lines.append( + f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server struct {{" + ) lines.append("\tgrpc.ServerStream") lines.append("}") lines.append("") # methods - lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Recv() ({req_type}, error) {{") + lines.append( + f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Recv() ({req_type}, error) {{" + ) lines.append(f"\tm := new({req_type[1:]})") - lines.append(f"\tif err := x.ServerStream.RecvMsg(m); err != nil {{") + lines.append("\tif err := x.ServerStream.RecvMsg(m); err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") lines.append("\treturn m, nil") lines.append("}") lines.append("") - lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) SendAndClose(m {res_type}) error {{") + lines.append( + f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) SendAndClose(m {res_type}) error {{" + ) lines.append("\treturn x.ServerStream.SendMsg(m)") lines.append("}") lines.append("") elif mode is StreamingMode.SERVER_STREAMING: # type interface - lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Server interface {{") + lines.append( + f"type {service.name}_{self.to_pascal_case(method.name)}Server interface {{" + ) lines.append(f"\tSend({res_type}) error") lines.append("\tgrpc.ServerStream") lines.append("}") lines.append("") # struct - lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server struct {{") + lines.append( + f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server struct {{" + ) lines.append("\tgrpc.ServerStream") lines.append("}") lines.append("") # methods - lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Send(m {res_type}) error {{") + lines.append( + f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Send(m {res_type}) error {{" + ) lines.append("\treturn x.ServerStream.SendMsg(m)") lines.append("}") lines.append("") else: # type interface - lines.append(f"type {service.name}_{self.to_pascal_case(method.name)}Server interface {{") + lines.append( + f"type {service.name}_{self.to_pascal_case(method.name)}Server interface {{" + ) lines.append(f"\tSend({res_type}) error") lines.append(f"\tRecv() ({req_type}, error)") lines.append("\tgrpc.ServerStream") @@ -402,17 +507,23 @@ def _generate_server_stream_types(self, service: Service, tracker: ImportTracker lines.append("") # struct - lines.append(f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server struct {{") + lines.append( + f"type {self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server struct {{" + ) lines.append("\tgrpc.ServerStream") lines.append("}") lines.append("") # methods - lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Send(m {res_type}) error {{") - lines.append(f"\treturn x.ServerStream.SendMsg(m)") + lines.append( + f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Send(m {res_type}) error {{" + ) + lines.append("\treturn x.ServerStream.SendMsg(m)") lines.append("}") lines.append("") - lines.append(f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Recv() ({req_type}, error) {{") + lines.append( + f"func (x *{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server) Recv() ({req_type}, error) {{" + ) lines.append(f"\tm := new({req_type[1:]})") lines.append("\tif err := x.ServerStream.RecvMsg(m); err != nil {") lines.append("\t\treturn nil, err") @@ -422,43 +533,63 @@ def _generate_server_stream_types(self, service: Service, tracker: ImportTracker lines.append("") return lines - def _generate_service_desc(self, service: Service, tracker: ImportTracker) -> List[str]: + def _generate_service_desc( + self, service: Service, tracker: ImportTracker + ) -> List[str]: lines: List[str] = [] for method in service.methods: req_type = self._resolve_go_type(method.request_type, tracker) mode = streaming_mode(method) # handlers if mode is StreamingMode.UNARY: - lines.append(f"func _{service.name}_{self.to_pascal_case(method.name)}_Handler(srv interface{{}}, ctx context.Context, dec func(interface{{}}) error, interceptor grpc.UnaryServerInterceptor) (interface{{}}, error) {{") + lines.append( + f"func _{service.name}_{self.to_pascal_case(method.name)}_Handler(srv interface{{}}, ctx context.Context, dec func(interface{{}}) error, interceptor grpc.UnaryServerInterceptor) (interface{{}}, error) {{" + ) lines.append(f"\tin := new({req_type[1:]})") lines.append("\tif err := dec(in); err != nil {") lines.append("\t\treturn nil, err") lines.append("\t}") lines.append("\tif interceptor == nil {") - lines.append(f"\t\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(ctx, in)") + lines.append( + f"\t\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(ctx, in)" + ) lines.append("\t}") lines.append("\tinfo := &grpc.UnaryServerInfo{") lines.append("\t\tServer:\tsrv,") - lines.append(f'\t\tFullMethod:\t"{self.get_grpc_method_path(service, method)}",') + lines.append( + f'\t\tFullMethod:\t"{self.get_grpc_method_path(service, method)}",' + ) lines.append("\t}") - lines.append("\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {") - lines.append(f"\t\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(ctx, req.({req_type}))") + lines.append( + "\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {" + ) + lines.append( + f"\t\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(ctx, req.({req_type}))" + ) lines.append("\t}") lines.append("\treturn interceptor(ctx, in, info, handler)") lines.append("}") lines.append("") elif mode is StreamingMode.SERVER_STREAMING: - lines.append(f"func _{service.name}_{self.to_pascal_case(method.name)}_Handler(srv interface{{}}, stream grpc.ServerStream) error {{") + lines.append( + f"func _{service.name}_{self.to_pascal_case(method.name)}_Handler(srv interface{{}}, stream grpc.ServerStream) error {{" + ) lines.append(f"\tm := new({req_type[1:]})") lines.append("\tif err := stream.RecvMsg(m); err != nil {") - lines.append(f"\t\treturn err") + lines.append("\t\treturn err") lines.append("\t}") - lines.append(f"\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(m, &{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server{{stream}})") + lines.append( + f"\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(m, &{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server{{stream}})" + ) lines.append("}") lines.append("") else: - lines.append(f"func _{service.name}_{self.to_pascal_case(method.name)}_Handler(srv interface{{}}, stream grpc.ServerStream) error {{") - lines.append(f"\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(&{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server{{stream}})") + lines.append( + f"func _{service.name}_{self.to_pascal_case(method.name)}_Handler(srv interface{{}}, stream grpc.ServerStream) error {{" + ) + lines.append( + f"\treturn srv.({service.name}Server).{self.to_pascal_case(method.name)}(&{self.to_camel_case(service.name)}{self.to_pascal_case(method.name)}Server{{stream}})" + ) lines.append("}") lines.append("") # var @@ -484,8 +615,12 @@ def _generate_unary_type_desc(self, service: Service) -> List[str]: mode = streaming_mode(method) if mode is StreamingMode.UNARY: lines.append("\t\t{") - lines.append(f'\t\t\tMethodName:\t"{self.to_pascal_case(method.name)}",') - lines.append(f"\t\t\tHandler:\t_{service.name}_{self.to_pascal_case(method.name)}_Handler,") + lines.append( + f'\t\t\tMethodName:\t"{self.to_pascal_case(method.name)}",' + ) + lines.append( + f"\t\t\tHandler:\t_{service.name}_{self.to_pascal_case(method.name)}_Handler," + ) lines.append("\t\t},") return lines @@ -497,11 +632,21 @@ def _generate_stream_type_desc(self, service: Service) -> List[str]: continue else: lines.append("\t\t{") - lines.append(f'\t\t\tStreamName:\t"{self.to_pascal_case(method.name)}",') - lines.append(f"\t\t\tHandler:\t_{service.name}_{self.to_pascal_case(method.name)}_Handler,") - if mode is StreamingMode.CLIENT_STREAMING or mode is StreamingMode.BIDIRECTIONAL: + lines.append( + f'\t\t\tStreamName:\t"{self.to_pascal_case(method.name)}",' + ) + lines.append( + f"\t\t\tHandler:\t_{service.name}_{self.to_pascal_case(method.name)}_Handler," + ) + if ( + mode is StreamingMode.CLIENT_STREAMING + or mode is StreamingMode.BIDIRECTIONAL + ): lines.append("\t\t\tClientStreams:\ttrue,") - if mode is StreamingMode.SERVER_STREAMING or mode is StreamingMode.BIDIRECTIONAL: + if ( + mode is StreamingMode.SERVER_STREAMING + or mode is StreamingMode.BIDIRECTIONAL + ): lines.append("\t\t\tServerStreams:\ttrue,") lines.append("\t\t},") return lines