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


##########
compiler/fory_compiler/generators/services/dart.py:
##########
@@ -0,0 +1,283 @@
+# 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.
+
+"""Dart gRPC service generator helpers."""
+
+from pathlib import Path
+from typing import List
+
+from fory_compiler.generators.base import GeneratedFile
+from fory_compiler.ir.ast import RpcMethod, Service
+
+
+class DartServiceGeneratorMixin:
+    """Generates Dart gRPC service companions for all four RPC modes."""
+
+    def generate_services(self) -> List[GeneratedFile]:
+        local_services = [
+            service
+            for service in self.schema.services
+            if not self.is_imported_type(service)
+        ]
+        if not local_services:
+            return []
+        self.check_dart_grpc_service_collisions(local_services)
+        self.check_dart_grpc_method_collisions(local_services)
+        return [self.generate_grpc_module(local_services)]
+
+    def check_dart_grpc_service_collisions(self, services: List[Service]) -> 
None:
+        generated_names = set(self._top_level_names())
+        service_names = set()
+        for service in services:
+            for emitted in (f"{service.name}Client", 
f"{service.name}ServiceBase"):
+                if emitted in generated_names or emitted in service_names:
+                    raise ValueError(
+                        f"Dart gRPC class {emitted} conflicts with a generated 
"
+                        "type or another service; rename the service or type"
+                    )
+                service_names.add(emitted)
+
+    def check_dart_grpc_method_collisions(self, services: List[Service]) -> 
None:
+        for service in services:
+            seen = {}
+            for method in service.methods:
+                emitted = self.dart_grpc_method_name(method)
+                if emitted in seen:
+                    raise ValueError(
+                        f"Dart gRPC method name collision in service 
{service.name}: "
+                        f"{seen[emitted]} and {method.name} both generate 
{emitted}"
+                    )
+                seen[emitted] = method.name
+
+    def generate_grpc_module(self, services: List[Service]) -> GeneratedFile:
+        """Emit a grpc-dart companion module for schema services."""
+        models_output = Path(
+            self.output_file_path()
+        )  # e.g. "demo/greeter/demo_greeter.dart"
+        models_stem = models_output.stem  # e.g. "demo_greeter"
+        grpc_path = str(models_output.with_name(f"{models_stem}_grpc.dart"))
+
+        lines: List[str] = []
+        lines.append(self.get_license_header("//"))
+        lines.append("")
+        lines.append(
+            "// ignore_for_file: camel_case_types, constant_identifier_names, "
+            "non_constant_identifier_names"
+        )
+        lines.append("")
+        lines.append("import 'dart:async';")
+        lines.append("import 'dart:typed_data';")
+        lines.append("")
+        lines.append("import 'package:grpc/grpc.dart';")
+        lines.append("")
+        lines.append(f"import '{models_stem}.dart' as _models;")
+        lines.append("")
+        lines.append(
+            "// grpc-dart Service self-registers via $methods; "
+            "no separate registration helper needed."
+        )
+        lines.append("")
+        fory = f"_models.{self.module_type_name()}.getFory()"

Review Comment:
   This makes the generated gRPC path depend on callers having already run 
`<Module>ForyModule.install(Fory())`; otherwise `getFory()` throws before the 
first RPC. Generated gRPC services should not require manual generated-type 
registration, and the other language companions hide this behind a module-owned 
ready runtime. Please make the Dart companion construct or otherwise obtain a 
ready Fory instance for generated gRPC, keeping custom runtime injection 
optional rather than required.



##########
compiler/fory_compiler/generators/services/dart.py:
##########
@@ -0,0 +1,283 @@
+# 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.
+
+"""Dart gRPC service generator helpers."""
+
+from pathlib import Path
+from typing import List
+
+from fory_compiler.generators.base import GeneratedFile
+from fory_compiler.ir.ast import RpcMethod, Service
+
+
+class DartServiceGeneratorMixin:
+    """Generates Dart gRPC service companions for all four RPC modes."""
+
+    def generate_services(self) -> List[GeneratedFile]:
+        local_services = [
+            service
+            for service in self.schema.services
+            if not self.is_imported_type(service)
+        ]
+        if not local_services:
+            return []
+        self.check_dart_grpc_service_collisions(local_services)
+        self.check_dart_grpc_method_collisions(local_services)
+        return [self.generate_grpc_module(local_services)]
+
+    def check_dart_grpc_service_collisions(self, services: List[Service]) -> 
None:
+        generated_names = set(self._top_level_names())
+        service_names = set()
+        for service in services:
+            for emitted in (f"{service.name}Client", 
f"{service.name}ServiceBase"):
+                if emitted in generated_names or emitted in service_names:
+                    raise ValueError(
+                        f"Dart gRPC class {emitted} conflicts with a generated 
"
+                        "type or another service; rename the service or type"
+                    )
+                service_names.add(emitted)
+
+    def check_dart_grpc_method_collisions(self, services: List[Service]) -> 
None:
+        for service in services:
+            seen = {}
+            for method in service.methods:
+                emitted = self.dart_grpc_method_name(method)
+                if emitted in seen:
+                    raise ValueError(
+                        f"Dart gRPC method name collision in service 
{service.name}: "
+                        f"{seen[emitted]} and {method.name} both generate 
{emitted}"
+                    )
+                seen[emitted] = method.name
+
+    def generate_grpc_module(self, services: List[Service]) -> GeneratedFile:
+        """Emit a grpc-dart companion module for schema services."""
+        models_output = Path(
+            self.output_file_path()
+        )  # e.g. "demo/greeter/demo_greeter.dart"
+        models_stem = models_output.stem  # e.g. "demo_greeter"
+        grpc_path = str(models_output.with_name(f"{models_stem}_grpc.dart"))
+
+        lines: List[str] = []
+        lines.append(self.get_license_header("//"))
+        lines.append("")
+        lines.append(
+            "// ignore_for_file: camel_case_types, constant_identifier_names, "
+            "non_constant_identifier_names"
+        )
+        lines.append("")
+        lines.append("import 'dart:async';")
+        lines.append("import 'dart:typed_data';")
+        lines.append("")
+        lines.append("import 'package:grpc/grpc.dart';")
+        lines.append("")
+        lines.append(f"import '{models_stem}.dart' as _models;")
+        lines.append("")
+        lines.append(
+            "// grpc-dart Service self-registers via $methods; "
+            "no separate registration helper needed."
+        )
+        lines.append("")
+        fory = f"_models.{self.module_type_name()}.getFory()"
+        lines.append("List<int> _serialize<T>(T value) =>")
+        lines.append(f"    {fory}.serialize(value, trackRef: true);")
+        lines.append("")
+        lines.append("T _deserialize<T>(List<int> bytes) {")
+        lines.append(
+            "  final u8 = bytes is Uint8List ? bytes : 
Uint8List.fromList(bytes);"
+        )
+        lines.append(f"  return {fory}.deserialize<T>(u8);")
+        lines.append("}")
+        lines.append("")
+
+        for service in services:
+            lines.extend(self.generate_dart_grpc_client(service))
+            lines.append("")
+            lines.extend(self.generate_dart_grpc_service_base(service))
+            lines.append("")
+
+        return GeneratedFile(path=grpc_path, content="\n".join(lines))
+
+    def generate_dart_grpc_client(self, service: Service) -> List[str]:
+        lines: List[str] = []
+        lines.append(f"class {service.name}Client extends Client {{")
+        for method in service.methods:
+            method_const = f"_${self.dart_grpc_method_name(method)}"
+            req_t = f"_models.{method.request_type.name}"

Review Comment:
   This raw AST name only works for top-level local message types. For an 
imported payload such as `demo.common.SharedReq`, the companion emits 
`_models.demo.common.SharedReq`; for a nested payload such as 
`Envelope.Request`, it emits `_models.Envelope.Request`, while Dart model code 
emits/imports flattened or alias-qualified symbols like `Envelope_Request`. 
Please resolve RPC payloads through the Dart generator's emitted type-name path 
(`resolve_type`/`ref_name`) and add analyzer coverage for imported and nested 
RPC payloads.



##########
docs/guide/dart/grpc-support.md:
##########
@@ -0,0 +1,310 @@
+---
+title: gRPC Support
+sidebar_position: 12
+id: grpc_support
+license: |
+  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.
+---
+
+Fory can generate Dart gRPC service companions for schemas that define 
services.
+The generated code uses normal `package:grpc` clients, service bases, method
+descriptors, call options, deadlines, cancellations, and status codes, while
+request and response objects are serialized with Fory instead of protobuf.
+
+Use this mode when both RPC peers are generated from the same Fory IDL, 
protobuf
+IDL, or FlatBuffers IDL and both sides expect Fory-encoded message bodies. Use
+normal protobuf gRPC generation for APIs that must be consumed by generic
+protobuf clients, reflection tools, or components that expect protobuf message
+bytes.
+
+## Add Dependencies
+
+The `fory` package does not add gRPC dependencies. Add `grpc` (and the
+`build_runner` dev dependency that generates the Fory serializer code) in the
+application that compiles or runs generated service companions:
+
+```yaml
+dependencies:
+  fory: ^1.1.0
+  grpc: ^4.0.0
+
+dev_dependencies:
+  build_runner: ^2.4.0
+```
+
+The same dependencies cover both client and server applications.
+
+## Define a Service
+
+Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
+`rpc_service` definitions. A Fory IDL service looks like this:
+
+```protobuf
+package demo.greeter;
+
+message HelloRequest {
+  string name = 1;
+}
+
+message HelloReply {
+  string reply = 1;
+}
+
+service Greeter {
+  rpc SayHello (HelloRequest) returns (HelloReply);
+}
+```
+
+Generate Dart model and gRPC companion code with `--grpc`:
+
+```bash
+foryc service.fdl --dart_out=./lib/generated --grpc
+```
+
+Then run `build_runner` once to emit the Fory serializer part file for the
+generated models (this step is required before the code can run):
+
+```bash
+dart run build_runner build --delete-conflicting-outputs
+```
+
+For this schema, the Dart generator emits:
+
+| File                                             | Purpose                   
                           |
+| ------------------------------------------------ | 
---------------------------------------------------- |
+| `demo/greeter/demo_greeter.dart`                 | Fory model types and the 
schema module               |
+| `demo/greeter/demo_greeter.fory.dart`            | Serializers and 
registration (built by build_runner) |
+| `demo/greeter/demo_greeter_grpc.dart`            | gRPC client, service 
base, and method descriptors    |
+| `DemoGreeterForyModule` in `demo_greeter.dart`   | Fory registration module 
for generated types         |
+| `GreeterServiceBase` in `demo_greeter_grpc.dart` | Base class for server 
implementations                |
+| `GreeterClient` in `demo_greeter_grpc.dart`      | Client stub for gRPC 
calls                           |
+
+## Register Types

Review Comment:
   This section documents a setup step that generated gRPC users should not 
need. The gRPC companion should own or obtain a ready Fory instance and 
register generated service payload types automatically, consistent with the 
other language gRPC generators. Once the generator handles that, please remove 
this manual registration section and the `install(Fory())` calls from the 
server/client examples below.



##########
docs/compiler/generated-code.md:
##########
@@ -1370,6 +1370,44 @@ void main() {
 }
 ```
 
+### gRPC Service Companions
+
+When a schema contains services and the compiler is run with `--grpc`, Dart
+generation emits one `<module>_grpc.dart` file per schema next to the model
+types. It targets `package:grpc` and serializes each request and response with
+the schema module's `getFory()` instance (for example
+`GreeterForyModule.getFory()`).
+
+All four RPC modes are generated: unary, server-streaming, client-streaming, 
and
+bidirectional. The client class extends `Client`; the service base class 
extends
+`Service` and self-registers each method with `$addMethod`.
+
+```dart
+class GreeterClient extends Client {
+  // Single response: ResponseFuture. Streaming response: ResponseStream.
+  ResponseFuture<HelloReply> sayHello(HelloRequest request, {CallOptions? 
options}) { ... }
+  ResponseStream<HelloReply> sayHellos(HelloRequest request, {CallOptions? 
options}) { ... }
+  ResponseFuture<HelloReply> collectHellos(Stream<HelloRequest> request, 
{CallOptions? options}) { ... }
+  ResponseStream<HelloReply> chatHellos(Stream<HelloRequest> request, 
{CallOptions? options}) { ... }
+}
+
+abstract class GreeterServiceBase extends Service {
+  Future<HelloReply> sayHello(ServiceCall call, HelloRequest request);
+  Stream<HelloReply> sayHellos(ServiceCall call, HelloRequest request);
+  Future<HelloReply> collectHellos(ServiceCall call, Stream<HelloRequest> 
request);
+  Stream<HelloReply> chatHellos(ServiceCall call, Stream<HelloRequest> 
request);
+}
+```
+
+A single-response client method returns `ResponseFuture<R>` (client-streaming
+adapts the streaming call with `.single`); a streaming-response method returns
+`ResponseStream<R>`. On the server, single requests arrive as `Future<Q>` and

Review Comment:
   This describes the internal `_Pre` shim, not the public service override. 
The generated abstract methods receive a concrete `Q` for single-request RPCs 
and `Stream<Q>` for client-streaming RPCs. Please adjust this sentence so users 
do not implement server methods with `Future<Q>` request parameters.



-- 
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