From cb24719108fe7558dada3608460002b9210df9e4 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sat, 23 May 2026 00:03:44 +0530 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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 +