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]