chaokunyang commented on code in PR #3738:
URL: https://github.com/apache/fory/pull/3738#discussion_r3361225280


##########
compiler/fory_compiler/generators/services/rust.py:
##########
@@ -0,0 +1,690 @@
+# 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.
+
+"""Rust gRPC service generator helpers."""
+
+from typing import List, Set
+
+from fory_compiler.generators.base import GeneratedFile
+from fory_compiler.ir.ast import NamedType, RpcMethod, Service
+
+
+class RustServiceGeneratorMixin:
+    """Generates Rust gRPC service companions."""
+
+    def generate_services(self) -> List[GeneratedFile]:
+        """Generate Rust grpc service companion module."""
+        local_services = [
+            service
+            for service in self.schema.services
+            if not self.is_imported_type(service)
+        ]
+        if not local_services:
+            return []
+        return [
+            self.generate_service_api_module(local_services),
+            self.generate_service_grpc_module(local_services),
+        ]
+
+    def generate_service_api_module(self, services: List[Service]) -> 
GeneratedFile:
+        """Generate Rust service API module (service.rs)."""
+        lines: List[str] = []
+        lines.append(self.get_license_header("//"))
+        lines.append("")
+        for i, service in enumerate(services):
+            if i > 0:
+                lines.append("")
+            lines.extend(self.generate_service_trait(service))
+            lines.append("")
+            lines.extend(self.generate_service_constants(service))
+        return GeneratedFile(
+            path="service.rs", content="\n".join(lines).rstrip() + "\n"
+        )
+
+    def generate_service_trait(self, service: Service) -> List[str]:
+        """Generate tonic service trait."""
+        trait_name = self.to_pascal_case(service.name)
+        lines: List[str] = []
+        lines.append("#[::tonic::async_trait]")
+        lines.append(
+            f"pub trait {trait_name}: ::std::marker::Send + 
::std::marker::Sync + 'static {{"
+        )
+        for i, method in enumerate(service.methods):
+            if i > 0:
+                lines.append("")
+            lines.extend(
+                self.indent_lines(self.generate_grpc_method_signature(method), 
1)
+            )
+        lines.append("}")
+        return lines
+
+    def generate_grpc_method_signature(self, method: RpcMethod) -> List[str]:
+        """Generate tonic service trait method signature."""
+        method_name = self.to_snake_case(method.name)

Review Comment:
   These generated Rust identifiers need keyword escaping and collision 
validation. The validator accepts schemas such as `rpc Type` and `rpc Foo` plus 
`rpc foo`, which currently emit invalid or duplicate Rust items like `async fn 
type`, duplicate `async fn foo`, and duplicate `API_FOO_PATH` constants. 
Java/Python service generation already guards these cases; Rust should do the 
same for service/method/module/constant names.



##########
compiler/fory_compiler/generators/services/rust.py:
##########
@@ -0,0 +1,690 @@
+# 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.
+
+"""Rust gRPC service generator helpers."""
+
+from typing import List, Set
+
+from fory_compiler.generators.base import GeneratedFile
+from fory_compiler.ir.ast import NamedType, RpcMethod, Service
+
+
+class RustServiceGeneratorMixin:
+    """Generates Rust gRPC service companions."""
+
+    def generate_services(self) -> List[GeneratedFile]:
+        """Generate Rust grpc service companion module."""
+        local_services = [
+            service
+            for service in self.schema.services
+            if not self.is_imported_type(service)
+        ]
+        if not local_services:
+            return []
+        return [
+            self.generate_service_api_module(local_services),
+            self.generate_service_grpc_module(local_services),
+        ]
+
+    def generate_service_api_module(self, services: List[Service]) -> 
GeneratedFile:
+        """Generate Rust service API module (service.rs)."""
+        lines: List[str] = []
+        lines.append(self.get_license_header("//"))
+        lines.append("")
+        for i, service in enumerate(services):
+            if i > 0:
+                lines.append("")
+            lines.extend(self.generate_service_trait(service))
+            lines.append("")
+            lines.extend(self.generate_service_constants(service))
+        return GeneratedFile(
+            path="service.rs", content="\n".join(lines).rstrip() + "\n"
+        )
+
+    def generate_service_trait(self, service: Service) -> List[str]:
+        """Generate tonic service trait."""
+        trait_name = self.to_pascal_case(service.name)
+        lines: List[str] = []
+        lines.append("#[::tonic::async_trait]")
+        lines.append(
+            f"pub trait {trait_name}: ::std::marker::Send + 
::std::marker::Sync + 'static {{"
+        )
+        for i, method in enumerate(service.methods):
+            if i > 0:
+                lines.append("")
+            lines.extend(
+                self.indent_lines(self.generate_grpc_method_signature(method), 
1)
+            )
+        lines.append("}")
+        return lines
+
+    def generate_grpc_method_signature(self, method: RpcMethod) -> List[str]:
+        """Generate tonic service trait method signature."""
+        method_name = self.to_snake_case(method.name)
+        request_type = self.service_type_path(method.request_type)
+        response_type = self.service_type_path(method.response_type)
+        lines: List[str] = []
+        if method.client_streaming:
+            request_arg_type = 
f"::tonic::Request<::tonic::Streaming<{request_type}>>"
+        else:
+            request_arg_type = f"::tonic::Request<{request_type}>"
+        if method.server_streaming:
+            stream_type = f"{self.to_pascal_case(method.name)}Stream"
+            response_return_type = f"::tonic::Response<Self::{stream_type}>"
+            lines.extend(
+                [
+                    f"type {stream_type}: 
::tonic::codegen::tokio_stream::Stream<",
+                    f"    Item = ::std::result::Result<{response_type}, 
::tonic::Status>,",
+                    "> + ::std::marker::Send + 'static;",
+                    "",
+                ]
+            )
+        else:
+            response_return_type = f"::tonic::Response<{response_type}>"
+        lines.extend(
+            [
+                f"async fn {method_name}(",
+                "    &self,",
+                f"    request: {request_arg_type},",
+                ") -> ::std::result::Result<",
+                f"    {response_return_type},",
+                "    ::tonic::Status,",
+                ">;",
+            ]
+        )
+        return lines
+
+    def service_type_path(self, named_type: NamedType) -> str:
+        """Get Rust path for a gRPC request or response type."""
+        # e.g. HelloRequest -> crate::demo_greeter::HelloRequest
+        resolved = self.schema.get_type(named_type.name)
+        if resolved is not None and self.is_imported_type(resolved):
+            module = self._module_name_for_type(resolved)
+            if module:
+                return self._format_imported_type_name(named_type.name, module)
+        local = self.resolve_nested_type_name(named_type.name, 
parent_stack=None)

Review Comment:
   Qualified RPC type names are formatted directly after `crate::{module}`, 
which breaks valid proto/imported payloads. For example, `rpc Get 
(.demo.Request) returns (.demo.Response)` generates 
`crate::demo::::demo::Request`, and an imported `common.Shared` generates 
`crate::common::common::Shared`. Both are invalid Rust paths. Please resolve 
the RPC type through the schema before formatting local/imported module paths, 
and add Rust service-codegen coverage alongside the existing Java 
absolute/imported type tests.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to