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-site.git
commit 4f85c20d2ec3ac159af25ade82ff99619b3176d4 Author: chaokunyang <[email protected]> AuthorDate: Sun Feb 8 02:38:04 2026 +0000 🔄 synced local 'docs/compiler/' with remote 'docs/compiler/' --- docs/compiler/generated-code.md | 729 ++++++++++++++++++++++------------------ docs/compiler/schema-idl.md | 77 ++--- 2 files changed, 425 insertions(+), 381 deletions(-) diff --git a/docs/compiler/generated-code.md b/docs/compiler/generated-code.md index 225576d85a..b433374af1 100644 --- a/docs/compiler/generated-code.md +++ b/docs/compiler/generated-code.md @@ -23,41 +23,47 @@ This document explains generated code for each target language. Fory IDL generated types are idiomatic in host languages and can be used directly as domain objects. Generated types also include `to/from bytes` helpers and registration helpers. -All snippets are representative excerpts from real generated output. +## Reference Schemas -## Example Fory IDL Schema +The examples below use two real schemas: -The sections below use this schema: +1. `addressbook.fdl` (explicit type IDs) +2. `auto_id.fdl` (no explicit type IDs) + +### `addressbook.fdl` (excerpt) ```protobuf -package demo; +package addressbook; -enum DeviceTier [id=100] { - DEVICE_TIER_UNKNOWN = 0; - DEVICE_TIER_TIER1 = 1; - DEVICE_TIER_TIER2 = 2; -} +option go_package = "github.com/myorg/myrepo/gen/addressbook;addressbook"; -message User [id=101] { - string id = 1; - string name = 2; - optional string email = 3; -} +message Person [id=100] { + string name = 1; + int32 id = 2; -message SearchResponse [id=102] { - message Result [id=103] { - string url = 1; - string title = 2; + enum PhoneType [id=101] { + PHONE_TYPE_MOBILE = 0; + PHONE_TYPE_HOME = 1; + PHONE_TYPE_WORK = 2; } - list<Result> results = 1; + + message PhoneNumber [id=102] { + string number = 1; + PhoneType phone_type = 2; + } + + list<PhoneNumber> phones = 7; + Animal pet = 8; } message Dog [id=104] { string name = 1; + int32 bark_volume = 2; } message Cat [id=105] { string name = 1; + int32 lives = 2; } union Animal [id=106] { @@ -65,86 +71,108 @@ union Animal [id=106] { Cat cat = 2; } -message Order [id=107] { - string id = 1; - ref User customer = 2; - list<string> items = 3; - map<string, int32> quantities = 4; - DeviceTier tier = 5; - Animal pet = 6; +message AddressBook [id=103] { + list<Person> people = 1; + map<string, Person> people_by_name = 2; } ``` -## Java +### `auto_id.fdl` (excerpt) -### Output Layout +```protobuf +package auto_id; -For package `demo`, Java code is generated under `demo/`: +enum Status { + UNKNOWN = 0; + OK = 1; +} -- `DeviceTier.java`, `User.java`, `SearchResponse.java`, `Dog.java`, `Cat.java`, `Animal.java`, `Order.java` -- `DemoForyRegistration.java` +message Envelope { + string id = 1; -### Type Generation + message Payload { + int32 value = 1; + } -Enum prefix stripping keeps scoped enum values clean: + union Detail { + Payload payload = 1; + string note = 2; + } -```java -public enum DeviceTier { - UNKNOWN, - TIER1, - TIER2; + Payload payload = 2; + Detail detail = 3; + Status status = 4; +} + +union Wrapper { + Envelope envelope = 1; + string raw = 2; } ``` -Messages are regular Java classes with `@ForyField` metadata and Java-style getters/setters: +## Java + +### Output Layout + +For `package addressbook`, Java output is generated under: + +- `<java_out>/addressbook/` +- Type files: `AddressBook.java`, `Person.java`, `Dog.java`, `Cat.java`, `Animal.java` +- Registration helper: `AddressbookForyRegistration.java` + +### Type Generation + +Messages generate Java classes with `@ForyField`, default constructors, getters/setters, and byte helpers: ```java -public class Order { - @ForyField(id = 1) - private String id; +public class Person { + public static enum PhoneType { + MOBILE, + HOME, + WORK; + } - @ForyField(id = 2, nullable = true, ref = true) - private User customer; + public static class PhoneNumber { + @ForyField(id = 1) + private String number; - @ForyField(id = 3) - private List<String> items; + @ForyField(id = 2) + private PhoneType phoneType; - @ForyField(id = 4) - private Map<String, Integer> quantities; + public byte[] toBytes() { ... } + public static PhoneNumber fromBytes(byte[] bytes) { ... } + } - @ForyField(id = 5) - private DeviceTier tier; + @ForyField(id = 1) + private String name; - @ForyField(id = 6) + @ForyField(id = 8) private Animal pet; - public String getId() { ... } - public void setId(String id) { ... } - public User getCustomer() { ... } - public void setCustomer(User customer) { ... } - public byte[] toBytes() { ... } - public static Order fromBytes(byte[] bytes) { ... } + public static Person fromBytes(byte[] bytes) { ... } } ``` -Nested messages become static inner classes: +Unions generate classes extending `org.apache.fory.type.union.Union`: ```java -public class SearchResponse { - public static class Result { ... } -} -``` +public final class Animal extends Union { + public enum AnimalCase { + DOG(1), + CAT(2); + public final int id; + AnimalCase(int id) { this.id = id; } + } -Unions generate explicit case APIs: + public static Animal ofDog(Dog v) { ... } + public AnimalCase getAnimalCase() { ... } + public int getAnimalCaseId() { ... } -```java -Animal pet = Animal.ofDog(new Dog()); -if (pet.hasDog()) { - Dog dog = pet.getDog(); + public boolean hasDog() { ... } + public Dog getDog() { ... } + public void setDog(Dog v) { ... } } -Animal.AnimalCase c = pet.getAnimalCase(); -int caseId = pet.getAnimalCaseId(); ``` ### Registration @@ -152,25 +180,29 @@ int caseId = pet.getAnimalCaseId(); Generated registration helper: ```java -public class DemoForyRegistration { - public static void register(Fory fory) { - org.apache.fory.resolver.TypeResolver resolver = fory.getTypeResolver(); - resolver.register(DeviceTier.class, 100L); - resolver.registerUnion( - Animal.class, - 106L, - new org.apache.fory.serializer.UnionSerializer(fory, Animal.class)); - resolver.register(User.class, 101L); - resolver.register(SearchResponse.class, 102L); - resolver.register(SearchResponse.Result.class, 103L); - resolver.register(Dog.class, 104L); - resolver.register(Cat.class, 105L); - resolver.register(Order.class, 107L); - } +public static void register(Fory fory) { + org.apache.fory.resolver.TypeResolver resolver = fory.getTypeResolver(); + resolver.registerUnion(Animal.class, 106L, new org.apache.fory.serializer.UnionSerializer(fory, Animal.class)); + resolver.register(Person.class, 100L); + resolver.register(Person.PhoneType.class, 101L); + resolver.register(Person.PhoneNumber.class, 102L); + resolver.register(Dog.class, 104L); + resolver.register(Cat.class, 105L); + resolver.register(AddressBook.class, 103L); } ``` -If you disable auto IDs (`option enable_auto_type_id = false;`), registration switches to namespace + type name: +For schemas without explicit `[id=...]`, generated registration uses computed numeric IDs (for example from `auto_id.fdl`): + +```java +resolver.register(Status.class, 1124725126L); +resolver.registerUnion(Wrapper.class, 1471345060L, new org.apache.fory.serializer.UnionSerializer(fory, Wrapper.class)); +resolver.register(Envelope.class, 3022445236L); +resolver.registerUnion(Envelope.Detail.class, 1609214087L, new org.apache.fory.serializer.UnionSerializer(fory, Envelope.Detail.class)); +resolver.register(Envelope.Payload.class, 2862577837L); +``` + +If `option enable_auto_type_id = false;` is set, registration uses namespace and type name: ```java resolver.register(Config.class, "myapp.models", "Config"); @@ -184,68 +216,65 @@ resolver.registerUnion( ### Usage ```java -Order order = new Order(); -order.setId("o456"); -order.setCustomer(new User()); -order.setTier(DeviceTier.TIER1); -order.setPet(Animal.ofDog(new Dog())); +Person person = new Person(); +person.setName("Alice"); +person.setPet(Animal.ofDog(new Dog())); -byte[] bytes = order.toBytes(); -Order restored = Order.fromBytes(bytes); +byte[] data = person.toBytes(); +Person restored = Person.fromBytes(data); ``` ## Python ### Output Layout -One module is generated per package, for example `demo.py`. +Python output is one module per schema file, for example: + +- `<python_out>/addressbook.py` ### Type Generation -Enums are `IntEnum` values with prefix stripping: +Unions generate a case enum plus a `Union` subclass with typed helpers: ```python -class DeviceTier(IntEnum): - UNKNOWN = 0 - TIER1 = 1 - TIER2 = 2 -``` +class AnimalCase(Enum): + DOG = 1 + CAT = 2 -Messages are `@pyfory.dataclass` classes: +class Animal(Union): + @classmethod + def dog(cls, v: Dog) -> "Animal": ... -```python [email protected](repr=False) -class Order: - id: str = pyfory.field(id=1, default="") - customer: Optional[User] = pyfory.field(id=2, nullable=True, ref=True, default=None) - items: List[str] = pyfory.field(id=3, default_factory=list) - quantities: Dict[str, pyfory.int32] = pyfory.field(id=4, default_factory=dict) - tier: DeviceTier = pyfory.field(id=5, default=None) - pet: Animal = pyfory.field(id=6, default=None) + def case(self) -> AnimalCase: ... + def case_id(self) -> int: ... - def to_bytes(self) -> bytes: ... - @classmethod - def from_bytes(cls, data: bytes) -> "Order": ... + def is_dog(self) -> bool: ... + def dog_value(self) -> Dog: ... + def set_dog(self, v: Dog) -> None: ... ``` -Nested messages stay nested: +Messages generate `@pyfory.dataclass` types, and nested types stay nested: ```python @pyfory.dataclass -class SearchResponse: +class Person: + class PhoneType(IntEnum): + MOBILE = 0 + HOME = 1 + WORK = 2 + @pyfory.dataclass - class Result: - url: str = pyfory.field(id=1, default="") - title: str = pyfory.field(id=2, default="") -``` + class PhoneNumber: + number: str = pyfory.field(id=1, default="") + phone_type: Person.PhoneType = pyfory.field(id=2, default=None) -Unions generate case enum + typed accessors: + name: str = pyfory.field(id=1, default="") + phones: List[Person.PhoneNumber] = pyfory.field(id=7, default_factory=list) + pet: Animal = pyfory.field(id=8, default=None) -```python -pet = Animal.dog(Dog(name="Rex")) -if pet.is_dog(): - dog = pet.dog_value() -case_id = pet.case_id() + def to_bytes(self) -> bytes: ... + @classmethod + def from_bytes(cls, data: bytes) -> "Person": ... ``` ### Registration @@ -253,18 +282,27 @@ case_id = pet.case_id() Generated registration function: ```python -def register_demo_types(fory: pyfory.Fory): - fory.register_type(DeviceTier, type_id=100) +def register_addressbook_types(fory: pyfory.Fory): fory.register_union(Animal, type_id=106, serializer=AnimalSerializer(fory)) - fory.register_type(User, type_id=101) - fory.register_type(SearchResponse, type_id=102) - fory.register_type(SearchResponse.Result, type_id=103) + fory.register_type(Person, type_id=100) + fory.register_type(Person.PhoneType, type_id=101) + fory.register_type(Person.PhoneNumber, type_id=102) fory.register_type(Dog, type_id=104) fory.register_type(Cat, type_id=105) - fory.register_type(Order, type_id=107) + fory.register_type(AddressBook, type_id=103) ``` -If auto IDs are disabled: +For schemas without explicit `[id=...]`, generated registration uses computed numeric IDs: + +```python +fory.register_type(Status, type_id=1124725126) +fory.register_union(Wrapper, type_id=1471345060, serializer=WrapperSerializer(fory)) +fory.register_type(Envelope, type_id=3022445236) +fory.register_union(Envelope.Detail, type_id=1609214087, serializer=Envelope.DetailSerializer(fory)) +fory.register_type(Envelope.Payload, type_id=2862577837) +``` + +If `option enable_auto_type_id = false;` is set: ```python fory.register_type(Config, namespace="myapp.models", typename="Config") @@ -279,78 +317,68 @@ fory.register_union( ### Usage ```python -order = Order( - id="o456", - customer=User(id="u1", name="Alice"), - items=["a", "b"], - quantities={"a": 1, "b": 2}, - tier=DeviceTier.TIER1, - pet=Animal.dog(Dog(name="Rex")), -) +person = Person(name="Alice", pet=Animal.dog(Dog(name="Rex", bark_volume=10))) -data = order.to_bytes() -restored = Order.from_bytes(data) +data = person.to_bytes() +restored = Person.from_bytes(data) ``` ## Rust ### Output Layout -One Rust module file per package, for example `demo.rs`. +Rust output is one module file per schema, for example: -### Type Generation +- `<rust_out>/addressbook.rs` -Enums are strongly typed and use stripped, idiomatic variant names: - -```rust -#[derive(ForyObject, Debug, Clone, PartialEq, Default)] -#[repr(i32)] -pub enum DeviceTier { - #[default] - Unknown = 0, - Tier1 = 1, - Tier2 = 2, -} -``` +### Type Generation -Messages derive `ForyObject`: +Unions map to Rust enums with `#[fory(id = ...)]` case attributes: ```rust -#[derive(ForyObject, Clone, PartialEq, Default)] -pub struct Order { +#[derive(ForyObject, Debug, Clone, PartialEq)] +pub enum Animal { #[fory(id = 1)] - pub id: String, - #[fory(id = 2, nullable = true, ref = true)] - pub customer: Option<Arc<User>>, - #[fory(id = 3)] - pub items: Vec<String>, - #[fory(id = 4)] - pub quantities: HashMap<String, i32>, - #[fory(id = 5)] - pub tier: DeviceTier, - #[fory(id = 6, type_id = "union")] - pub pet: Animal, + Dog(Dog), + #[fory(id = 2)] + Cat(Cat), } ``` -Nested types are generated in nested modules: +Nested types generate nested modules: ```rust -pub mod search_response { +pub mod person { #[derive(ForyObject, Debug, Clone, PartialEq, Default)] - pub struct Result { ... } + #[repr(i32)] + pub enum PhoneType { + #[default] + Mobile = 0, + Home = 1, + Work = 2, + } + + #[derive(ForyObject, Debug, Clone, PartialEq, Default)] + pub struct PhoneNumber { + #[fory(id = 1)] + pub number: String, + #[fory(id = 2)] + pub phone_type: PhoneType, + } } ``` -Unions map to Rust enums with per-case IDs: +Messages derive `ForyObject` and include `to_bytes`/`from_bytes` helpers: ```rust -#[derive(ForyObject, Debug, Clone, PartialEq)] -pub enum Animal { +#[derive(ForyObject, Debug, Clone, PartialEq, Default)] +pub struct Person { #[fory(id = 1)] - Dog(Dog), - #[fory(id = 2)] - Cat(Cat), + pub name: String, + #[fory(id = 7)] + pub phones: Vec<person::PhoneNumber>, + #[fory(id = 8, type_id = "union")] + pub pet: Animal, } ``` @@ -360,19 +388,28 @@ Generated registration function: ```rust pub fn register_types(fory: &mut Fory) -> Result<(), fory::Error> { - fory.register::<DeviceTier>(100)?; fory.register_union::<Animal>(106)?; - fory.register::<User>(101)?; - fory.register::<search_response::Result>(103)?; - fory.register::<SearchResponse>(102)?; + fory.register::<person::PhoneType>(101)?; + fory.register::<person::PhoneNumber>(102)?; + fory.register::<Person>(100)?; fory.register::<Dog>(104)?; fory.register::<Cat>(105)?; - fory.register::<Order>(107)?; + fory.register::<AddressBook>(103)?; Ok(()) } ``` -If auto IDs are disabled: +For schemas without explicit `[id=...]`, generated registration uses computed numeric IDs: + +```rust +fory.register::<Status>(1124725126)?; +fory.register_union::<Wrapper>(1471345060)?; +fory.register::<Envelope>(3022445236)?; +fory.register_union::<envelope::Detail>(1609214087)?; +fory.register::<envelope::Payload>(2862577837)?; +``` + +If `option enable_auto_type_id = false;` is set: ```rust fory.register_by_namespace::<Config>("myapp.models", "Config")?; @@ -382,108 +419,131 @@ fory.register_union_by_namespace::<Holder>("myapp.models", "Holder")?; ### Usage ```rust -let order = Order { - id: "o456".into(), - customer: Some(Arc::new(User::default())), - items: vec!["a".into(), "b".into()], - quantities: HashMap::new(), - tier: DeviceTier::Tier1, - pet: Animal::Dog(Dog { name: "Rex".into() }), +let person = Person { + name: "Alice".into(), + pet: Animal::Dog(Dog::default()), + ..Default::default() }; -let bytes = order.to_bytes()?; -let restored = Order::from_bytes(&bytes)?; +let bytes = person.to_bytes()?; +let restored = Person::from_bytes(&bytes)?; ``` ## C++ ### Output Layout -One header per package, for example `demo.h`. +C++ output is one header per schema file, for example: + +- `<cpp_out>/addressbook.h` ### Type Generation -Enums are generated as `enum class` with stripped names: +Messages generate `final` classes with typed accessors and byte helpers: ```cpp -enum class DeviceTier : int32_t { - UNKNOWN = 0, - TIER1 = 1, - TIER2 = 2, +class Person final { + public: + class PhoneNumber final { + public: + const std::string& number() const; + std::string* mutable_number(); + template <class Arg, class... Args> + void set_number(Arg&& arg, Args&&... args); + + fory::Result<std::vector<uint8_t>, fory::Error> to_bytes() const; + static fory::Result<PhoneNumber, fory::Error> from_bytes(const std::vector<uint8_t>& data); + }; + + const std::string& name() const; + std::string* mutable_name(); + template <class Arg, class... Args> + void set_name(Arg&& arg, Args&&... args); + + const Animal& pet() const; + Animal* mutable_pet(); }; -FORY_ENUM(demo::DeviceTier, UNKNOWN, TIER1, TIER2); ``` -Messages are generated as classes with typed accessors and private fields, including `has_xxx`, `mutable_xxx`, and `set_xxx` where applicable: +Optional message fields generate `has_xxx`, `mutable_xxx`, and `clear_xxx` APIs: ```cpp -class Order final { +class Envelope final { public: - const std::string& id() const; - std::string* mutable_id(); - template <class Arg, class... Args> - void set_id(Arg&& arg, Args&&... args); - - bool has_customer() const; - const std::shared_ptr<User>& customer() const; - std::shared_ptr<User>* mutable_customer(); - void set_customer(std::shared_ptr<User> value); - void clear_customer(); - - fory::Result<std::vector<uint8_t>, fory::Error> to_bytes() const; - static fory::Result<Order, fory::Error> from_bytes( - const std::vector<uint8_t>& data); + bool has_payload() const { return payload_ != nullptr; } + const Envelope::Payload& payload() const { return *payload_; } + Envelope::Payload* mutable_payload() { + if (!payload_) { + payload_ = std::make_unique<Envelope::Payload>(); + } + return payload_.get(); + } + void clear_payload() { payload_.reset(); } private: - std::string id_; - std::shared_ptr<User> customer_; - std::vector<std::string> items_; - std::map<std::string, int32_t> quantities_; - DeviceTier tier_; - Animal pet_; - - public: - FORY_STRUCT(Order, id_, customer_, items_, quantities_, tier_, pet_); + std::unique_ptr<Envelope::Payload> payload_; }; ``` -Nested messages are nested classes: +Unions generate `std::variant` wrappers: ```cpp -class SearchResponse final { +class Animal final { public: - class Result final { ... }; -}; -``` + enum class AnimalCase : uint32_t { + DOG = 1, + CAT = 2, + }; -Unions are generated as tagged `std::variant` wrappers: + static Animal dog(Dog v); + static Animal cat(Cat v); -```cpp -Animal pet = Animal::dog(Dog{}); -if (pet.is_dog()) { - const Dog& dog = pet.dog(); -} -uint32_t case_id = pet.animal_case_id(); + AnimalCase animal_case() const noexcept; + uint32_t animal_case_id() const noexcept; + + bool is_dog() const noexcept; + const Dog* as_dog() const noexcept; + Dog* as_dog() noexcept; + const Dog& dog() const; + Dog& dog(); + + template <class Visitor> + decltype(auto) visit(Visitor&& vis) const; + + private: + std::variant<Dog, Cat> value_; +}; ``` +Generated headers also include `FORY_UNION`, `FORY_FIELD_CONFIG`, `FORY_ENUM`, and `FORY_STRUCT` macros for serialization metadata. + ### Registration Generated registration function: ```cpp inline void register_types(fory::serialization::BaseFory& fory) { - fory.register_enum<DeviceTier>(100); - fory.register_union<Animal>(106); - fory.register_struct<User>(101); - fory.register_struct<SearchResponse::Result>(103); - fory.register_struct<SearchResponse>(102); - fory.register_struct<Dog>(104); - fory.register_struct<Cat>(105); - fory.register_struct<Order>(107); + fory.register_union<Animal>(106); + fory.register_enum<Person::PhoneType>(101); + fory.register_struct<Person::PhoneNumber>(102); + fory.register_struct<Person>(100); + fory.register_struct<Dog>(104); + fory.register_struct<Cat>(105); + fory.register_struct<AddressBook>(103); } ``` -If auto IDs are disabled: +For schemas without explicit `[id=...]`, generated registration uses computed numeric IDs: + +```cpp +fory.register_enum<Status>(1124725126); +fory.register_union<Wrapper>(1471345060); +fory.register_struct<Envelope>(3022445236); +fory.register_union<Envelope::Detail>(1609214087); +fory.register_struct<Envelope::Payload>(2862577837); +``` + +If `option enable_auto_type_id = false;` is set: ```cpp fory.register_struct<Config>("myapp.models", "Config"); @@ -493,96 +553,73 @@ fory.register_union<Holder>("myapp.models", "Holder"); ### Usage ```cpp -demo::Order order; -order.set_id("o456"); -order.set_customer(std::make_shared<demo::User>()); +addressbook::Person person; +person.set_name("Alice"); +*person.mutable_pet() = addressbook::Animal::dog(addressbook::Dog{}); -auto bytes_result = order.to_bytes(); -if (!bytes_result.ok()) { - return 1; -} -auto order_result = demo::Order::from_bytes(bytes_result.value()); -if (!order_result.ok()) { - return 1; -} -demo::Order restored = std::move(order_result.value()); +auto bytes = person.to_bytes(); +auto restored = addressbook::Person::from_bytes(bytes.value()); ``` ## Go ### Output Layout -Go output path depends on whether `go_package` is configured. - -When `go_package` is set in schema options (as in `integration_tests/idl_tests/idl/addressbook.fdl`), output follows that package path, for example: - -- `integration_tests/idl_tests/go/addressbook/generated/addressbook.go` - -Without `go_package`, compiler derives output from the Fory IDL package name. +Go output path depends on schema options and `--go_out`. -For package `demo`, output is: +For `addressbook.fdl`, `go_package` is configured and generated output follows the configured import path/package (for example under your `--go_out` root). -- `<go_out>/demo/demo.go` - -For package `myapp.models`, output is: - -- `<go_out>/models/myapp_models.go` +Without `go_package`, output uses the requested `--go_out` directory and package-derived file naming. ### Type Generation -Enums keep Go-style unscoped constant names: +Nested types use underscore naming by default (`Person_PhoneType`, `Person_PhoneNumber`): ```go -type DeviceTier int32 +type Person_PhoneType int32 const ( - DeviceTierUnknown DeviceTier = 0 - DeviceTierTier1 DeviceTier = 1 - DeviceTierTier2 DeviceTier = 2 + Person_PhoneTypeMobile Person_PhoneType = 0 + Person_PhoneTypeHome Person_PhoneType = 1 + Person_PhoneTypeWork Person_PhoneType = 2 ) + +type Person_PhoneNumber struct { + Number string `fory:"id=1"` + PhoneType Person_PhoneType `fory:"id=2"` +} ``` -Messages are regular structs with fory tags: +Messages generate structs with `fory` tags and byte helpers: ```go -type User struct { - Id string `fory:"id=1"` - Name string `fory:"id=2"` - Email optional.Optional[string] `fory:"id=3,nullable"` +type Person struct { + Name string `fory:"id=1"` + Id int32 `fory:"id=2,compress=true"` + Phones []Person_PhoneNumber `fory:"id=7"` + Pet Animal `fory:"id=8"` } -type Order struct { - Id string `fory:"id=1"` - Customer *User `fory:"id=2,nullable,ref"` - Items []string `fory:"id=3"` - Quantities map[string]int32 `fory:"id=4"` - Tier DeviceTier `fory:"id=5"` - Pet Animal `fory:"id=6"` -} +func (m *Person) ToBytes() ([]byte, error) { ... } +func (m *Person) FromBytes(data []byte) error { ... } ``` -Nested type naming defaults to underscore: +Unions generate typed case structs with constructors/accessors/visitor APIs: ```go -type SearchResponse_Result struct { ... } -``` - -You can switch to concatenated names with: +type AnimalCase uint32 -```protobuf -option go_nested_type_style = "camelcase"; -``` +type Animal struct { + case_ AnimalCase + value any +} -Unions generate typed case helpers: +func DogAnimal(v *Dog) Animal { ... } +func CatAnimal(v *Cat) Animal { ... } -```go -pet := DogAnimal(&Dog{Name: "Rex"}) -if dog, ok := pet.AsDog(); ok { - _ = dog -} -_ = pet.Visit(AnimalVisitor{ - Dog: func(d *Dog) error { return nil }, -}) +func (u Animal) Case() AnimalCase { ... } +func (u Animal) AsDog() (*Dog, bool) { ... } +func (u Animal) Visit(visitor AnimalVisitor) error { ... } ``` ### Registration @@ -591,44 +628,58 @@ Generated registration function: ```go func RegisterTypes(f *fory.Fory) error { - if err := f.RegisterEnum(DeviceTier(0), 100); err != nil { + if err := f.RegisterUnion(Animal{}, 106, fory.NewUnionSerializer(...)); err != nil { return err } - if err := f.RegisterUnion(Animal{}, 106, ...); err != nil { + if err := f.RegisterEnum(Person_PhoneType(0), 101); err != nil { return err } - if err := f.RegisterStruct(User{}, 101); err != nil { + if err := f.RegisterStruct(Person_PhoneNumber{}, 102); err != nil { + return err + } + if err := f.RegisterStruct(Person{}, 100); err != nil { return err } - // ... SearchResponse_Result, SearchResponse, Dog, Cat, Order return nil } ``` -If auto IDs are disabled: +For schemas without explicit `[id=...]`, generated registration uses computed numeric IDs: + +```go +if err := f.RegisterEnum(Status(0), 1124725126); err != nil { ... } +if err := f.RegisterUnion(Wrapper{}, 1471345060, fory.NewUnionSerializer(...)); err != nil { ... } +if err := f.RegisterStruct(Envelope{}, 3022445236); err != nil { ... } +if err := f.RegisterUnion(Envelope_Detail{}, 1609214087, fory.NewUnionSerializer(...)); err != nil { ... } +if err := f.RegisterStruct(Envelope_Payload{}, 2862577837); err != nil { ... } +``` + +If `option enable_auto_type_id = false;` is set: ```go if err := f.RegisterNamedStruct(Config{}, "myapp.models.Config"); err != nil { ... } -if err := f.RegisterNamedUnion(Holder{}, "myapp.models.Holder", ...); err != nil { ... } +if err := f.RegisterNamedUnion(Holder{}, "myapp.models.Holder", fory.NewUnionSerializer(...)); err != nil { ... } +``` + +`go_nested_type_style` controls nested type naming: + +```protobuf +option go_nested_type_style = "camelcase"; ``` ### Usage ```go -email := optional.Some("[email protected]") -order := &Order{ - Id: "o456", - Customer: &User{Id: "u1", Name: "Alice", Email: email}, - Items: []string{"a", "b"}, - Tier: DeviceTierTier1, - Pet: DogAnimal(&Dog{Name: "Rex"}), +person := &Person{ + Name: "Alice", + Pet: DogAnimal(&Dog{Name: "Rex"}), } -data, err := order.ToBytes() +data, err := person.ToBytes() if err != nil { panic(err) } -var restored Order +var restored Person if err := restored.FromBytes(data); err != nil { panic(err) } @@ -638,16 +689,26 @@ if err := restored.FromBytes(data); err != nil { ### Type ID Behavior -- Explicit `[id=...]` is used directly. -- Without explicit IDs, compiler-generated IDs are used by default. -- With `option enable_auto_type_id = false;`, generated code registers by namespace + type name. - -### Nested Type Shapes - -| Language | Nested Type Form | -| -------- | ----------------------- | -| Java | `Outer.Inner` | -| Python | `Outer.Inner` | -| Rust | `outer::Inner` | -| C++ | `Outer::Inner` | -| Go | `Outer_Inner` (default) | +- Explicit `[id=...]` values are used directly in generated registration. +- When type IDs are omitted, generated code uses computed numeric IDs (see `auto_id.*` outputs). +- If `option enable_auto_type_id = false;` is set, generated registration uses namespace/type-name APIs instead of numeric IDs. + +### Nested Type Shape + +| Language | Nested type form | +| -------- | ------------------------------ | +| Java | `Person.PhoneNumber` | +| Python | `Person.PhoneNumber` | +| Rust | `person::PhoneNumber` | +| C++ | `Person::PhoneNumber` | +| Go | `Person_PhoneNumber` (default) | + +### Byte Helper Naming + +| Language | Helpers | +| -------- | ------------------------- | +| Java | `toBytes` / `fromBytes` | +| Python | `to_bytes` / `from_bytes` | +| Rust | `to_bytes` / `from_bytes` | +| C++ | `to_bytes` / `from_bytes` | +| Go | `ToBytes` / `FromBytes` | diff --git a/docs/compiler/schema-idl.md b/docs/compiler/schema-idl.md index 869187cd0e..4e297aa770 100644 --- a/docs/compiler/schema-idl.md +++ b/docs/compiler/schema-idl.md @@ -272,30 +272,15 @@ message Payment { } ``` -### Protobuf Compatibility Options +### Protobuf Extension Syntax -Fory IDL accepts protobuf-style extension syntax (for example, `(fory).id`) for -compatibility, but native Fory IDL style uses plain option keys such as `id`, -`evolving`, `ref`, and `nullable` without the `(fory)` prefix. +In `.fdl` files, use native Fory IDL syntax only (for example, `[id=100]`, `ref`, +`optional`, `nullable=true`). -Equivalent forms: +Protobuf extension syntax with `(fory).` is for `.proto` files and the protobuf +frontend only. -```protobuf -// Native Fory IDL style (preferred in .fdl files) -message Node [id=100] { - ref Node parent = 1; - optional string nickname = 2; -} - -// Protobuf-style compatibility syntax -message Node { - option (fory).id = 100; - Node parent = 1 [(fory).ref = true]; - string nickname = 2 [(fory).nullable = true]; -} -``` - -For the protobuf-specific extension option guide, see +For protobuf extension options, see [Protocol Buffers IDL Support](protobuf-idl.md#fory-extension-options-protobuf). ### Option Priority @@ -474,21 +459,22 @@ enum Status { } ``` -### Enum Options +### Enum Type Options -Options can be specified within enums: +Enum-level options are declared inline in `[]` after the enum name: ```protobuf -enum Status { - option deprecated = true; // Allowed +enum Status [deprecated=true] { PENDING = 0; ACTIVE = 1; } ``` -**Forbidden Options:** +FDL does not support `option ...;` statements inside enum bodies. + +**Unsupported:** -- `option allow_alias = true` is **not supported**. Each enum value must have a unique integer. +- `allow_alias` is **not supported**. Each enum value must have a unique integer. ### Language Mapping @@ -531,8 +517,7 @@ enum DeviceTier { enum_def := 'enum' IDENTIFIER [type_options] '{' enum_body '}' type_options := '[' type_option (',' type_option)* ']' type_option := IDENTIFIER '=' option_value -enum_body := (option_stmt | reserved_stmt | enum_value)* -option_stmt := 'option' IDENTIFIER '=' option_value ';' +enum_body := (reserved_stmt | enum_value)* reserved_stmt := 'reserved' reserved_items ';' enum_value := IDENTIFIER '=' INTEGER ';' ``` @@ -616,26 +601,27 @@ message User { } ``` -### Message Options +### Message Type Options -Options can be specified within messages: +Message-level options are declared inline in `[]` after the message name: ```protobuf -message User { - option deprecated = true; +message User [deprecated=true] { string id = 1; string name = 2; } ``` +FDL does not support `option ...;` statements inside message or enum bodies. + **Grammar:** ``` message_def := 'message' IDENTIFIER [type_options] '{' message_body '}' type_options := '[' type_option (',' type_option)* ']' type_option := IDENTIFIER '=' option_value -message_body := (option_stmt | reserved_stmt | nested_type | field_def)* -nested_type := enum_def | message_def +message_body := (reserved_stmt | nested_type | field_def)* +nested_type := enum_def | message_def | union_def ``` **Rules:** @@ -644,7 +630,7 @@ nested_type := enum_def | message_def ## Nested Types -Messages can contain nested message and enum definitions. This is useful for defining types that are closely related to their parent message. +Messages can contain nested message, enum, and union definitions. This is useful for defining types that are closely related to their parent message. ### Nested Messages @@ -722,7 +708,7 @@ message OtherMessage { | Rust | Nested modules (`search_response::Result`) | | C++ | Nested classes (`SearchResponse::Result`) | -**Note:** Go defaults to underscore-separated nested names; set `option (fory).go_nested_type_style = "camelcase";` to use concatenated names. Rust emits nested modules for nested types. +**Note:** Go defaults to underscore-separated nested names; set `option go_nested_type_style = "camelcase";` to use concatenated names. Rust emits nested modules for nested types. ### Nested Type Rules @@ -1361,33 +1347,30 @@ package_decl := 'package' package_name ['alias' package_name] ';' package_name := IDENTIFIER ('.' IDENTIFIER)* file_option := 'option' option_name '=' option_value ';' -option_name := IDENTIFIER | extension_name -extension_name := '(' IDENTIFIER ')' '.' IDENTIFIER // e.g., (fory).polymorphism +option_name := IDENTIFIER import_decl := 'import' STRING ';' type_def := enum_def | message_def | union_def enum_def := 'enum' IDENTIFIER [type_options] '{' enum_body '}' -enum_body := (option_stmt | reserved_stmt | enum_value)* +enum_body := (reserved_stmt | enum_value)* enum_value := IDENTIFIER '=' INTEGER ';' message_def := 'message' IDENTIFIER [type_options] '{' message_body '}' -message_body := (option_stmt | reserved_stmt | nested_type | field_def)* -nested_type := enum_def | message_def +message_body := (reserved_stmt | nested_type | field_def)* +nested_type := enum_def | message_def | union_def field_def := [modifiers] field_type IDENTIFIER '=' INTEGER [field_options] ';' union_def := 'union' IDENTIFIER [type_options] '{' union_field* '}' -union_field := field_type IDENTIFIER '=' INTEGER ';' - -option_stmt := 'option' option_name '=' option_value ';' +union_field := ['repeated'] field_type IDENTIFIER '=' INTEGER [field_options] ';' option_value := 'true' | 'false' | IDENTIFIER | INTEGER | STRING reserved_stmt := 'reserved' reserved_items ';' reserved_items := reserved_item (',' reserved_item)* reserved_item := INTEGER | INTEGER 'to' INTEGER | INTEGER 'to' 'max' | STRING -modifiers := { 'optional' | 'ref' } ['list' { 'optional' | 'ref' }] +modifiers := { 'optional' | 'ref' | 'repeated' } field_type := primitive_type | named_type | list_type | map_type primitive_type := 'bool' @@ -1407,7 +1390,7 @@ map_type := 'map' '<' field_type ',' field_type '>' type_options := '[' type_option (',' type_option)* ']' type_option := IDENTIFIER '=' option_value // e.g., id=100, deprecated=true field_options := '[' field_option (',' field_option)* ']' -field_option := option_name '=' option_value // e.g., deprecated=true, (fory).ref=true +field_option := IDENTIFIER '=' option_value // e.g., deprecated=true, ref=true STRING := '"' [^"\n]* '"' | "'" [^'\n]* "'" IDENTIFIER := [a-zA-Z_][a-zA-Z0-9_]* --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
