跳到主要内容
版本:dev

Schema IDL 语法

本文档给出 Fory IDL 的语法与语义参考,覆盖文件结构、类型系统、字段规则、选项与类型注册策略。

编译器使用方式与构建集成请参见 Compiler Guide。 protobuf/FlatBuffers 前端映射请参见 Protocol Buffers IDL SupportFlatBuffers IDL Support

文件结构

一个 Fory IDL 文件通常包含:

  1. 可选 package 声明
  2. 可选文件级 option
  3. 可选 import 语句
  4. 类型定义(enummessageunion
// Optional package declaration
package com.example.models;

// Optional file-level options
option java_package = "com.example.models";

// Import statements
import "common/types.fdl";

// Type definitions
enum Color [id=100] { ... }
message User [id=101] { ... }
message Order [id=102] { ... }
union Event [id=103] { ... }

注释

支持单行注释与块注释:

// This is a single-line comment

/*
* This is a block comment
* that spans multiple lines
*/

message Example {
string name = 1; // Inline comment
}

Package 声明

package 定义文件中所有类型的命名空间。

package com.example.models;

也可以配置 alias(用于自动类型 ID 计算):

package com.example.models alias models_v1;

规则:

  • 可选但推荐
  • 必须位于任何类型定义之前
  • 每个文件最多一个 package
  • 用于命名空间注册
  • alias 会参与 auto-ID 哈希

语言映射:

语言package 用法
JavaJava package
Python模块名(._
Go包名(通常取最后一段)
Rust模块名(._
C++命名空间(.::
C#命名空间
JavaScript模块名(取最后一段)
Dart库名(保留 package 各段)

文件级选项

文件级选项用于控制语言定制代码生成。

语法

option option_name = value;

Java Package 选项

通过 java_package 覆盖 Java 输出包名:

package payment;
option java_package = "com.mycorp.payment.v1";

message Payment {
string id = 1;
}

效果:

  • Java 文件输出到 com/mycorp/payment/v1/
  • Java package 声明使用该值
  • 跨语言类型注册仍以 Fory package(如 payment)为准

Go Package 选项

通过 go_package 指定 Go import path 与包名:

package payment;
option go_package = "github.com/mycorp/apis/gen/payment/v1;paymentv1";

message Payment {
string id = 1;
}

格式:"import/path;package_name" 或仅 "import/path"(包名取最后一段)。

Java Outer Classname 选项

将多个类型包装到一个外层类:

package payment;
option java_outer_classname = "DescriptorProtos";

enum Status {
UNKNOWN = 0;
ACTIVE = 1;
}

message Payment {
string id = 1;
Status status = 2;
}

默认会生成单文件,枚举与消息作为静态内部类型。

Java Multiple Files 选项

控制 Java 是否拆分多文件:

package payment;
option java_outer_classname = "PaymentProtos";
option java_multiple_files = true;

message Payment {
string id = 1;
}

message Receipt {
string id = 1;
}

行为:

java_outer_classnamejava_multiple_files结果
未设置任意每个类型一个文件
已设置false(默认)单文件 + 内部类
已设置true强制拆分为多文件

多个选项组合

package payment;
option java_package = "com.mycorp.payment.v1";
option go_package = "github.com/mycorp/apis/gen/payment/v1;paymentv1";
option deprecated = true;

Protobuf 扩展语法说明

.fdl 中请使用 Fory 原生语法(如 [id=100]refoptionalnullable=true)。 (fory).xxx 形式仅用于 .proto(protobuf 前端)。

选项优先级

语言包路径优先级:

  1. 命令行覆盖(最高)
  2. 语言选项(java_packagego_package
  3. Fory IDL package(兜底)

跨语言类型注册

默认情况下,注册名由 package + type name(或类型 ID)确定。建议长期保持 package 稳定,以避免跨版本注册不一致。

Import 语句

基本语法

import "common/types.fdl";

多个导入

import "common/types.fdl";
import "domain/user.fdl";
import "domain/order.fdl";

路径解析

导入解析顺序:

  1. 导入者文件所在目录
  2. 命令行 -I/--proto_path/--import_path 指定目录(按给定顺序)

完整示例

// src/main.fdl
package app;

import "common.fdl";
import "models/user.fdl";

message Main {
common.Meta meta = 1;
models.User user = 2;
}

不支持的 import 写法

  • URL 形式(如 https://...
  • 绝对路径依赖(不推荐,会破坏可移植性)

import 错误

典型错误:

  • 文件不存在
  • 搜索路径未包含依赖目录
  • 同名文件冲突导致解析到错误版本

Enum 定义

基本语法

enum Status {
UNKNOWN = 0;
ACTIVE = 1;
DISABLED = 2;
}

显式类型 ID

enum Status [id=101] {
UNKNOWN = 0;
ACTIVE = 1;
DISABLED = 2;
}

预留值

enum Status {
UNKNOWN = 0;
ACTIVE = 1;
reserved 2, 3;
reserved 10 to 20;
}

enum 类型选项

常见:idaliasdeprecated

语言映射

语言实现形式
Javaenum Status { UNKNOWN, ACTIVE, ... }
Pythonclass Status(IntEnum): UNKNOWN = 0
Gotype Status int32 配合常量
Rust#[repr(i32)] enum Status { Unknown }
C++enum class Status : int32_t { ... }
JavaScriptexport enum Status { UNKNOWN, ... }
Dartenum Status { unknown, active, ... }

枚举前缀处理

针对 protobuf 风格 TYPE_NAME_VALUE,生成器通常会按语言习惯去除冗余前缀,使 API 更自然。

语言输出示例风格
JavaUNKNOWN, TIER1, TIER2作用域枚举
RustUnknown, Tier1, Tier2作用域枚举
C++UNKNOWN, TIER1, TIER2作用域枚举
PythonUNKNOWN, TIER1, TIER2作用域 IntEnum
GoDeviceTierUnknown, DeviceTierTier1, ...非作用域常量
JavaScriptUNKNOWN, TIER1, TIER2作用域枚举
Dartunknown, tier1, tier2作用域枚举

Message 定义

基本语法

message User {
string name = 1;
int32 age = 2;
}

显式类型 ID

message User [id=100] {
string name = 1;
int32 age = 2;
}

无显式类型 ID

未声明 [id=...] 时,编译器可按配置自动生成类型 ID,或使用 namespace/name 注册。

语言映射

语言实现形式
Java带 getter/setter 的 POJO
Python@dataclass
Go导出字段的 struct
Rust#[derive(ForyObject)] 的 struct
C++FORY_STRUCT 宏的 struct
JavaScriptexport interface 声明
Dart@ForyStruct final class

预留字段

message User {
string name = 1;
reserved 2, 3;
reserved 10 to 20;
}

message 类型选项

常见选项:idaliasevolvingdeprecatednamespaceuse_record_for_java

嵌套类型

嵌套 message

message Person {
message PhoneNumber {
string number = 1;
}
PhoneNumber phone = 1;
}

嵌套 enum

message Person {
enum PhoneType {
MOBILE = 0;
HOME = 1;
}
PhoneType type = 1;
}

限定类型名

可使用完整限定名引用嵌套类型,例如 Person.PhoneNumber

深层嵌套类型

支持多层嵌套,但建议控制层级,避免可读性下降。

各语言生成形态

语言嵌套类型形态
JavaOuter.Inner
PythonOuter.Inner
Rustouter::Inner
C++Outer::Inner
GoOuter_Inner(默认,可配置为 camelcase)
JavaScript扁平名称(如 Result
Dart带下划线的扁平类名(如 SearchResponse_Result

嵌套规则

  • 嵌套类型名在其父作用域内必须唯一
  • 可被同文件后续类型引用
  • 可通过 import + 限定名跨文件引用

Union 定义

基本语法

union Animal {
Dog dog = 1;
Cat cat = 2;
}

在 message 中使用 union

message Person {
Animal pet = 1;
}

规则

  • case 字段号必须唯一
  • case 类型通常为消息类型或可序列化复合类型
  • 各语言会生成带 case 判别和访问器的 union 表达

字段定义

基本语法

string name = 1;

带修饰符语法

optional string nickname = 2;
ref Node parent = 3;
list<int32> scores = 4;

字段修饰符

optional

声明字段可为空(null/None):

message User {
optional string email = 1;
}

建议在跨语言场景显式使用,以避免默认值差异。

生成代码:

语言非 optionaloptional
JavaString nameString email 配合 @ForyField(nullable=true)
Pythonname: strname: Optional[str]
GoName stringName *string
Rustname: Stringname: Option<String>
C++std::string namestd::optional<std::string> name
JavaScriptname: stringname?: string | null
DartString nameString? email

ref

开启引用跟踪,用于共享对象与循环结构:

message Node {
string name = 1;
ref Node parent = 2;
list<ref Node> children = 3;
}

当运行时全局 ref tracking 开启时,字段级 ref 才会生效。

生成代码:

语言不使用 ref使用 ref
JavaNode parentNode parent 配合 @ForyField(ref=true)
Pythonparent: Nodeparent: Node = pyfory.field(ref=True)
GoParent NodeParent *Node 配合 fory:"ref"
Rustparent: Nodeparent: Arc<Node>
C++Node parentstd::shared_ptr<Node> parent
JavaScriptparent: Nodeparent: Node(无额外 ref 区分)
DartNode parentNode parent 配合 @ForyField(ref: true)

list

列表字段(repeated 为等价别名):

message Group {
list<string> tags = 1;
}

生成代码:

语言类型
JavaList<String>
PythonList[str]
Go[]string
RustVec<String>
C++std::vector<std::string>
JavaScriptstring[]
DartList<String>

组合修饰符

可组合使用:

message Graph {
optional ref Node root = 1;
list<ref Node> nodes = 2;
}

字段号

字段号规则:

  • 同一 message 内必须唯一
  • 必须为正整数
  • 不应复用已删除字段号(建议使用 reserved
  • 建议预留编号区间以便演进

Type System

Fory IDL 类型系统包括基础类型、命名类型和集合类型。

Primitive Types

类型族示例
布尔bool
整数int8/int16/int32/int64uint*
浮点float32float64
字符串string
字节数组bytes
时间datetimestampduration
动态类型any

Boolean

bool 表示布尔值。

语言类型说明
Javaboolean
Pythonbool
Gobool
Rustbool
C++bool
JavaScriptboolean
Dartbool

Integer Types

支持有符号/无符号与不同位宽。跨语言场景建议明确编码策略并保持字段语义稳定。

有符号整数映射:

Fory IDLJavaPythonGoRustC++JavaScriptDart
int8bytepyfory.int8int8i8int8_tnumberInt8
int16shortpyfory.int16int16i16int16_tnumberInt16
int32intpyfory.int32int32i32int32_tnumberInt32
int64longpyfory.int64int64i64int64_tbigint | numberint

无符号整数映射:

Fory IDLJavaPythonGoRustC++JavaScriptDart
uint8shortpyfory.uint8uint8u8uint8_tnumberUInt8
uint16intpyfory.uint16uint16u16uint16_tnumberUInt16
uint32longpyfory.uint32uint32u32uint32_tnumberUInt32
uint64longpyfory.uint64uint64u64uint64_tbigint | numberint

Integer Encoding Variants

常见编码:

  • varint:小值更省空间
  • fixed:固定长度,性能稳定
  • tagged:混合编码(特定类型可用)

Floating-Point Types

  • float32
  • float64
Fory IDLJavaPythonGoRustC++JavaScriptDart
float32floatpyfory.float32float32f32floatnumberFloat32
float64doublepyfory.float64float64f64doublenumberdouble

String Type

string 使用 UTF-8 文本语义。

语言类型说明
JavaString不可变
Pythonstr
Gostring不可变
RustString所有权字符串,堆分配
C++std::string
JavaScriptstring
DartString

Bytes Type

bytes 用于原始二进制载荷。

语言类型说明
Javabyte[]
Pythonbytes不可变
Go[]byte
RustVec<u8>
C++std::vector<uint8_t>
JavaScriptUint8Array
DartUint8List

Temporal Types

Date

date 表示日期(不含时区时间部分)。

语言类型说明
Javajava.time.LocalDate
Pythondatetime.date
Gotime.Time会忽略时间部分
Rustchrono::NaiveDate需依赖 chrono crate
C++fory::serialization::Date
JavaScriptDate
DartLocalDateFory package 提供的类型
Timestamp

timestamp 表示时间点(跨语言应统一时间语义与精度预期)。

语言类型说明
Javajava.time.Instant基于 UTC
Pythondatetime.datetime
Gotime.Time
Rustchrono::NaiveDateTime需依赖 chrono crate
C++fory::serialization::Timestamp
JavaScriptDate
DartTimestampFory package 提供的类型

Any

any 允许存储动态类型值。使用 any 时建议配合清晰的业务约束,避免滥用导致模型不稳定。

语言类型说明
JavaObject写入运行时类型
PythonAny写入运行时类型
Goany写入运行时类型
RustBox<dyn Any>写入运行时类型
C++std::any写入运行时类型
JavaScriptany写入运行时类型
DartObject?写入运行时类型

Named Types

命名类型包括:

  • enum
  • message
  • union

支持跨文件 import 和限定名引用。

Collection Types

List (list)

list<T>

等价别名:repeated T

Map

map<K, V>

约束:

  • K 一般应为可稳定比较的标量类型
  • V 可为任意支持类型
Fory IDLJavaPythonGoRustC++JavaScriptDart
map<string, int32>Map<String, Integer>Dict[str, int]map[string]int32HashMap<String, i32>std::map<std::string, int32_t>Map<string, number>Map<String, Int32>
map<string, User>Map<String, User>Dict[str, User]map[string]UserHashMap<String, User>std::map<std::string, User>Map<string, User>Map<String, User>

Type Compatibility Matrix

跨语言建议:

  • 使用各语言都稳定支持的公共子集
  • 尽量避免平台相关宽度/语义差异
  • 对整数编码与可空语义显式声明

Best Practices

  1. 优先使用显式字段号与稳定命名
  2. 需要可空就显式 optional
  3. 存在共享/循环关系时使用 ref
  4. 降低 any 使用范围,优先强类型建模
  5. 预留字段号与枚举值区间

Type IDs

类型 ID 用于跨语言快速注册与解码匹配。

显式类型 ID

message User [id=100] {
string name = 1;
}

无显式类型 ID

未显式声明时可:

  • 自动生成数值 ID(默认配置)
  • 禁用自动 ID,改用 namespace/type-name 注册

实践说明

  • 类型 ID 在协议层面应视为稳定标识
  • 一经发布,不建议更改
  • 建议按域规划 ID 段(如 100-199 用户域)

ID 分配策略

  • 核心高频模型优先分配固定 ID
  • 团队统一管理 ID 区间与分配规范
  • 在 CI 中增加 ID 冲突检查

完整示例

package demo.order;

option java_package = "com.example.demo.order";
option go_package = "github.com/example/demo/gen/order;order";

import "demo/common.fdl";

enum Status [id=200] {
UNKNOWN = 0;
CREATED = 1;
PAID = 2;
SHIPPED = 3;
}

message Item [id=201] {
string sku = 1;
int32 quantity = 2;
}

message User [id=202] {
string id = 1;
string name = 2;
optional string email = 3;
}

union Animal [id=203] {
Dog dog = 1;
Cat cat = 2;
}

message Dog [id=204] {
string name = 1;
}

message Cat [id=205] {
string name = 1;
}

message Order [id=206] {
string id = 1;
ref User buyer = 2;
list<Item> items = 3;
Status status = 4;
map<string, string> metadata = 5;
optional Animal pet = 6;
}

语法摘要

以下为简化文法(便于快速查阅,具体以编译器实现为准):

file            = [packageDecl] {optionDecl} {importDecl} {typeDecl} ;

packageDecl = "package" qualifiedName ["alias" identifier] ";" ;
optionDecl = "option" identifier "=" optionValue ";" ;
importDecl = "import" stringLiteral ";" ;

typeDecl = enumDecl | messageDecl | unionDecl ;

enumDecl = "enum" identifier [typeOptions] "{" {enumField | reservedDecl} "}" ;
enumField = identifier "=" intLiteral ";" ;

messageDecl = "message" identifier [typeOptions] "{" {fieldDecl | nestedTypeDecl | reservedDecl} "}" ;
unionDecl = "union" identifier [typeOptions] "{" {unionCaseDecl} "}" ;

fieldDecl = [fieldModifier] typeRef identifier "=" intLiteral [fieldOptions] ";" ;
unionCaseDecl = typeRef identifier "=" intLiteral [fieldOptions] ";" ;

fieldModifier = "optional" | "ref" | "list" ;

typeRef = primitiveType | qualifiedName | listType | mapType ;
listType = "list" "<" typeRef ">" ;
mapType = "map" "<" typeRef "," typeRef ">" ;

typeOptions = "[" optionPair {"," optionPair} "]" ;
fieldOptions = "[" optionPair {"," optionPair} "]" ;
optionPair = identifier "=" optionValue ;

reservedDecl = "reserved" reservedItem {"," reservedItem} ";" ;
reservedItem = intLiteral | intLiteral "to" intLiteral | stringLiteral ;

实现建议:如需严谨验证,请以编译器语法解析器和测试用例为准。