This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new 00e88c3c5 fix(compiler): validate rpc request/response types (#3493)
00e88c3c5 is described below

commit 00e88c3c55ef052a70b635cd9e78c0e0471a16bf
Author: Dimitris <[email protected]>
AuthorDate: Wed Mar 18 17:25:55 2026 +0200

    fix(compiler): validate rpc request/response types (#3493)
    
    ## Why?
    
    Service RPC definitions can reference request/response types that do not
    exist in the schema. These errors currently surface late (during
    generation or integration). This PR makes such schemas fail fast during
    validation.
    
    ## What does this PR do?
    
    - Validate that each service RPC request/response type resolves to a
    defined type.
    - Add FDL/Proto/FBS tests that ensure unknown RPC request/response types
    fail validation.
    - Add a small `compiler/examples/service.fdl` example and document that
    RPC request/response types are validated, with a `foryc` usage snippet.
    
    ## Related issues
    
    - Related to #3266
    
    ## AI Contribution Checklist
    
    - [ ] Substantial AI assistance was used in this PR: `no`
    
    ## Does this PR introduce any user-facing change?
    
    - [ ] Does this PR introduce any public API change? `no`
    - [ ] Does this PR introduce any binary protocol compatibility change?
    `no`
    
    ## Benchmark
    
    N/A
---
 compiler/examples/service.fdl                      | 31 +++++++++++
 compiler/fory_compiler/ir/validator.py             | 11 ++++
 compiler/fory_compiler/tests/test_fbs_service.py   | 41 ++++++++++++++
 compiler/fory_compiler/tests/test_fdl_service.py   | 64 ++++++++++++++++++++++
 compiler/fory_compiler/tests/test_proto_service.py | 37 +++++++++++++
 docs/compiler/compiler-guide.md                    | 10 ++++
 6 files changed, 194 insertions(+)

diff --git a/compiler/examples/service.fdl b/compiler/examples/service.fdl
new file mode 100644
index 000000000..3017433f3
--- /dev/null
+++ b/compiler/examples/service.fdl
@@ -0,0 +1,31 @@
+// 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 demo.service;
+
+message HelloRequest {
+    string name = 1;
+}
+
+message HelloReply {
+    string message = 1;
+}
+
+service Greeter {
+    rpc SayHello (HelloRequest) returns (HelloReply);
+}
+
diff --git a/compiler/fory_compiler/ir/validator.py 
b/compiler/fory_compiler/ir/validator.py
index 712760cc0..294657b2f 100644
--- a/compiler/fory_compiler/ir/validator.py
+++ b/compiler/fory_compiler/ir/validator.py
@@ -386,6 +386,17 @@ class SchemaValidator:
             for f in union.fields:
                 check_type_ref(f.field_type, f, None)
 
+        # Also validate service RPC request/response type references.
+        for service in self.schema.services:
+            for method in service.methods:
+                for named_type in (method.request_type, method.response_type):
+                    type_name = named_type.name
+                    if self.schema.get_type(type_name) is None:
+                        self._error(
+                            f"Unknown type '{type_name}'",
+                            named_type.location,
+                        )
+
     def _check_ref_rules(self) -> None:
         def is_any_type(field_type: FieldType) -> bool:
             return (
diff --git a/compiler/fory_compiler/tests/test_fbs_service.py 
b/compiler/fory_compiler/tests/test_fbs_service.py
index 27b86c784..4f052bd12 100644
--- a/compiler/fory_compiler/tests/test_fbs_service.py
+++ b/compiler/fory_compiler/tests/test_fbs_service.py
@@ -20,6 +20,7 @@
 from fory_compiler.frontend.fbs.lexer import Lexer
 from fory_compiler.frontend.fbs.parser import Parser
 from fory_compiler.frontend.fbs.translator import FbsTranslator
+from fory_compiler.ir.validator import SchemaValidator
 
 
 def parse_and_translate(source):
@@ -107,3 +108,43 @@ def test_streaming_attributes():
     assert m3.name == "BidiStream"
     assert m3.client_streaming is True
     assert m3.server_streaming is True
+
+
+def test_service_unknown_request_type_fails_validation():
+    source = """
+    namespace demo;
+
+    table Response {
+        result: string;
+    }
+
+    rpc_service Greeter {
+        SayHello(UnknownRequest):Response;
+    }
+    """
+    schema = parse_and_translate(source)
+    validator = SchemaValidator(schema)
+    assert not validator.validate()
+    assert any(
+        "Unknown type 'UnknownRequest'" in err.message for err in 
validator.errors
+    )
+
+
+def test_service_unknown_response_type_fails_validation():
+    source = """
+    namespace demo;
+
+    table Request {
+        id: int;
+    }
+
+    rpc_service Greeter {
+        SayHello(Request):UnknownResponse;
+    }
+    """
+    schema = parse_and_translate(source)
+    validator = SchemaValidator(schema)
+    assert not validator.validate()
+    assert any(
+        "Unknown type 'UnknownResponse'" in err.message for err in 
validator.errors
+    )
diff --git a/compiler/fory_compiler/tests/test_fdl_service.py 
b/compiler/fory_compiler/tests/test_fdl_service.py
index a884c9820..78bf889b0 100644
--- a/compiler/fory_compiler/tests/test_fdl_service.py
+++ b/compiler/fory_compiler/tests/test_fdl_service.py
@@ -17,6 +17,7 @@
 
 import pytest
 from fory_compiler.frontend.fdl.parser import Parser, ParseError
+from fory_compiler.ir.validator import SchemaValidator
 
 
 def parse(source: str):
@@ -25,6 +26,13 @@ def parse(source: str):
     return schema
 
 
+def validate(source: str) -> SchemaValidator:
+    schema = parse(source)
+    validator = SchemaValidator(schema)
+    validator.validate()
+    return validator
+
+
 def test_empty_service():
     source = """
     package test;
@@ -168,3 +176,59 @@ def test_invalid_syntax_missing_parens():
     """
     with pytest.raises(ParseError):
         parse(source)
+
+
+def test_service_unknown_request_type_fails_validation():
+    source = """
+    package test;
+
+    message HelloReply {}
+
+    service Greeter {
+        rpc SayHello (UnknownRequest) returns (HelloReply);
+    }
+    """
+    schema = parse(source)
+    validator = SchemaValidator(schema)
+    assert not validator.validate()
+    # Ensure we surface a clear unknown-type error on the RPC line.
+    matching_errors = [
+        err
+        for err in validator.errors
+        if "Unknown type 'UnknownRequest'" in err.message
+    ]
+    assert matching_errors
+    # Location should be attached so tooling/CLI can point at the RPC.
+    assert matching_errors[0].location is not None
+
+
+def test_service_unknown_response_type_fails_validation():
+    source = """
+    package test;
+
+    message HelloRequest {}
+
+    service Greeter {
+        rpc SayHello (HelloRequest) returns (UnknownReply);
+    }
+    """
+    schema = parse(source)
+    validator = SchemaValidator(schema)
+    assert not validator.validate()
+    assert any("Unknown type 'UnknownReply'" in err.message for err in 
validator.errors)
+
+
+def test_service_known_types_pass_validation():
+    source = """
+    package test;
+
+    message HelloRequest {}
+    message HelloReply {}
+
+    service Greeter {
+        rpc SayHello (HelloRequest) returns (HelloReply);
+    }
+    """
+    schema = parse(source)
+    validator = SchemaValidator(schema)
+    assert validator.validate()
diff --git a/compiler/fory_compiler/tests/test_proto_service.py 
b/compiler/fory_compiler/tests/test_proto_service.py
index c7878cbc3..80297beff 100644
--- a/compiler/fory_compiler/tests/test_proto_service.py
+++ b/compiler/fory_compiler/tests/test_proto_service.py
@@ -20,6 +20,7 @@
 from fory_compiler.frontend.proto.lexer import Lexer
 from fory_compiler.frontend.proto.parser import Parser
 from fory_compiler.frontend.proto.translator import ProtoTranslator
+from fory_compiler.ir.validator import SchemaValidator
 
 
 def parse_and_translate(source):
@@ -120,3 +121,39 @@ def test_service_options():
     schema = parse_and_translate(source)
     service = schema.services[0]
     assert service.options["deprecated"] is True
+
+
+def test_service_unknown_request_type_fails_validation():
+    source = """
+    syntax = "proto3";
+    package demo;
+
+    message Response {}
+
+    service Greeter {
+        rpc SayHello (UnknownRequest) returns (Response);
+    }
+    """
+    schema = parse_and_translate(source)
+    validator = SchemaValidator(schema)
+    assert not validator.validate()
+    assert any(
+        "Unknown type 'UnknownRequest'" in err.message for err in 
validator.errors
+    )
+
+
+def test_service_unknown_response_type_fails_validation():
+    source = """
+    syntax = "proto3";
+    package demo;
+
+    message Request {}
+
+    service Greeter {
+        rpc SayHello (Request) returns (UnknownReply);
+    }
+    """
+    schema = parse_and_translate(source)
+    validator = SchemaValidator(schema)
+    assert not validator.validate()
+    assert any("Unknown type 'UnknownReply'" in err.message for err in 
validator.errors)
diff --git a/docs/compiler/compiler-guide.md b/docs/compiler/compiler-guide.md
index f58eaee0c..65a481f76 100644
--- a/docs/compiler/compiler-guide.md
+++ b/docs/compiler/compiler-guide.md
@@ -136,6 +136,12 @@ foryc schema.fdl --package com.myapp.models
 foryc user.fdl order.fdl product.fdl --output ./generated
 ```
 
+**Compile a simple service schema (Java + Python):**
+
+```bash
+foryc compiler/examples/service.fdl --java_out=./generated/java 
--python_out=./generated/python
+```
+
 **Use import search paths:**
 
 ```bash
@@ -598,6 +604,10 @@ Error: Unknown type 'Address' in Customer.address
 
 Fix: Define the referenced type before using it, or check for typos.
 
+Service RPC request and response types are validated in the same way: an RPC 
such as
+`rpc SayHello (HelloRequest) returns (HelloReply);` must reference defined 
message
+types, otherwise the validator reports an `Unknown type '...'` error on the 
RPC line.
+
 ### Duplicate Field Numbers
 
 ```


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

Reply via email to