blob: 92708c9bdc07b55cdd26ecab033a1ba4895ea89c [file] [log] [blame] [view]
---
title: Generated Code
sidebar_position: 5
id: generated_code
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.
---
This document explains the code generated by the FDL compiler for each target language.
## Example Schema
The examples in this document use this FDL schema:
```protobuf
package demo;
enum Status [id=100] {
PENDING = 0;
ACTIVE = 1;
COMPLETED = 2;
}
message User [id=101] {
string id = 1;
string name = 2;
optional string email = 3;
int32 age = 4;
}
message Order [id=102] {
string id = 1;
ref User customer = 2;
repeated string items = 3;
map<string, int32> quantities = 4;
Status status = 5;
}
```
## Enum Prefix Stripping
When enum values use a protobuf-style prefix (enum name in UPPER_SNAKE_CASE), the compiler automatically strips the prefix for languages with scoped enums. This produces cleaner, more idiomatic code.
**Input FDL:**
```protobuf
enum DeviceTier {
DEVICE_TIER_UNKNOWN = 0;
DEVICE_TIER_TIER1 = 1;
DEVICE_TIER_TIER2 = 2;
}
```
**Generated output by language:**
| Language | Generated Values | Notes |
| -------- | ----------------------------------------- | ------------------------- |
| Java | `UNKNOWN, TIER1, TIER2` | Scoped enum |
| Rust | `Unknown, Tier1, Tier2` | PascalCase variants |
| C++ | `UNKNOWN, TIER1, TIER2` | Scoped `enum class` |
| Python | `UNKNOWN, TIER1, TIER2` | Scoped `IntEnum` |
| Go | `DeviceTierUnknown, DeviceTierTier1, ...` | Unscoped, prefix re-added |
**Note:** Go uses unscoped constants, so the enum name prefix is added back to avoid naming collisions.
## Nested Types
When using nested message and enum definitions, the generated code varies by language.
**Input FDL:**
```protobuf
message SearchResponse {
message Result {
string url = 1;
string title = 2;
}
repeated Result results = 1;
}
```
### Java - Inner Classes
```java
public class SearchResponse {
public static class Result {
private String url;
private String title;
// getters, setters...
}
private List<Result> results;
// getters, setters...
}
```
### Python - Nested Classes
```python
@dataclass
class SearchResponse:
@dataclass
class Result:
url: str = ""
title: str = ""
results: List[Result] = field(default_factory=list)
```
### Go - Underscore
```go
type SearchResponse_Result struct {
Url string
Title string
}
type SearchResponse struct {
Results []SearchResponse_Result
}
```
**Note:** Set `option (fory).go_nested_type_style = "camelcase";` to generate `SearchResponseResult` instead.
### Rust - Nested Module
```rust
pub mod search_response {
use super::*;
#[derive(ForyObject)]
pub struct Result {
pub url: String,
pub title: String,
}
}
#[derive(ForyObject)]
pub struct SearchResponse {
pub results: Vec<search_response::Result>,
}
```
### C++ - Nested Classes
```cpp
class SearchResponse final {
public:
class Result final {
public:
std::string url;
std::string title;
};
std::vector<Result> results;
};
FORY_STRUCT(SearchResponse::Result, url, title);
FORY_STRUCT(SearchResponse, results);
```
**Summary:**
| Language | Approach | Syntax Example |
| -------- | ------------------------- | ------------------------- |
| Java | Static inner classes | `SearchResponse.Result` |
| Python | Nested dataclasses | `SearchResponse.Result` |
| Go | Underscore (configurable) | `SearchResponse_Result` |
| Rust | Nested module | `search_response::Result` |
| C++ | Nested classes | `SearchResponse::Result` |
## Union Generation
FDL unions generate type-safe APIs with an explicit active case. This example is
based on `integration_tests/idl_tests/idl/addressbook.fdl`:
```protobuf
package addressbook;
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] {
Dog dog = 1;
Cat cat = 2;
}
message Person [id=100] {
Animal pet = 8;
}
```
### Java
```java
Animal pet = Animal.ofDog(new Dog());
if (pet.hasDog()) {
Dog dog = pet.getDog();
}
Animal.AnimalCase caseId = pet.getAnimalCase();
```
### Python
```python
pet = Animal.dog(Dog(name="Rex", bark_volume=5))
if pet.is_dog():
dog = pet.dog_value()
case_id = pet.case_id()
```
### Go
```go
pet := DogAnimal(&Dog{Name: "Rex", BarkVolume: 5})
if dog, ok := pet.AsDog(); ok {
_ = dog
}
_ = pet.Visit(AnimalVisitor{
Dog: func(d *Dog) error { return nil },
})
```
### Rust
```rust
let pet = Animal::Dog(Dog {
name: "Rex".into(),
bark_volume: 5,
});
```
### C++
```cpp
addressbook::Animal pet = addressbook::Animal::dog(
addressbook::Dog{"Rex", 5});
if (pet.is_dog()) {
const addressbook::Dog& dog = pet.dog();
}
```
Generated registration helpers also register union types, for example:
- Java: `fory.registerUnion(Animal.class, 106, new UnionSerializer(...))`
- Python: `fory.register_union(Animal, type_id=106, serializer=AnimalSerializer(fory))`
- Go: `f.RegisterUnion(...)`
- Rust: `fory.register_union::<Animal>(106)?`
- C++: `FORY_UNION(addressbook::Animal, ...)`
## Java
### Enum Generation
```java
package demo;
public enum Status {
PENDING,
ACTIVE,
COMPLETED;
}
```
### Message Generation
```java
package demo;
import java.util.List;
import java.util.Map;
import org.apache.fory.annotation.ForyField;
public class User {
private String id;
private String name;
@ForyField(nullable = true)
private String email;
private int age;
public User() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
```
```java
package demo;
import java.util.List;
import java.util.Map;
import org.apache.fory.annotation.ForyField;
public class Order {
private String id;
@ForyField(ref = true)
private User customer;
private List<String> items;
private Map<String, Integer> quantities;
private Status status;
public Order() {
}
// Getters and setters...
}
```
### Registration Helper
```java
package demo;
import org.apache.fory.Fory;
import org.apache.fory.ThreadSafeFory;
import org.apache.fory.pool.SimpleForyPool;
public class DemoForyRegistration {
private static ThreadSafeFory createFory() {
ThreadSafeFory fory = new SimpleForyPool(c -> Fory.builder()
.withXlang(true)
.withRefTracking(true)
.build());
fory.registerCallback(f -> registerAllTypes(f));
return fory;
}
private static void registerAllTypes(Fory fory) {
register(fory);
}
public static void register(Fory fory) {
fory.register(Status.class, 100);
fory.register(User.class, 101);
fory.register(Order.class, 102);
}
}
```
`register` only contains types defined in the current file. The generated
`registerAllTypes` registers imported types first and then calls `register`.
### Usage
```java
import demo.*;
public class Example {
public static void main(String[] args) {
User user = new User();
user.setId("u123");
user.setName("Alice");
user.setAge(30);
Order order = new Order();
order.setId("o456");
order.setCustomer(user);
order.setStatus(Status.ACTIVE);
byte[] bytes = order.toBytes();
Order restored = Order.fromBytes(bytes);
}
}
```
## Python
### Module Generation
```python
# Licensed to the Apache Software Foundation (ASF)...
from dataclasses import dataclass
from enum import IntEnum
from typing import Dict, List, Optional
import pyfory
class Status(IntEnum):
PENDING = 0
ACTIVE = 1
COMPLETED = 2
@dataclass
class User:
id: str = ""
name: str = ""
email: Optional[str] = None
age: pyfory.int32 = 0
@dataclass
class Order:
id: str = ""
customer: Optional[User] = None
items: List[str] = None
quantities: Dict[str, pyfory.int32] = None
status: Status = None
def register_demo_types(fory: pyfory.Fory):
fory.register_type(Status, type_id=100)
fory.register_type(User, type_id=101)
fory.register_type(Order, type_id=102)
def _register_all_types(fory: pyfory.Fory):
register_demo_types(fory)
```
`register_demo_types` only contains types defined in the current file. The
generated `_register_all_types` registers imported types first and then calls
`register_demo_types`.
### Usage
```python
from demo import User, Order, Status
user = User(id="u123", name="Alice", age=30)
order = Order(
id="o456",
customer=user,
items=["item1", "item2"],
quantities={"item1": 2, "item2": 1},
status=Status.ACTIVE
)
data = order.to_bytes()
restored = Order.from_bytes(data)
```
## Go
### File Generation
```go
// Licensed to the Apache Software Foundation (ASF)...
package demo
import (
fory "github.com/apache/fory/go/fory"
)
type Status int32
const (
StatusPending Status = 0
StatusActive Status = 1
StatusCompleted Status = 2
)
type User struct {
Id string
Name string
Email *string `fory:"nullable"`
Age int32
}
type Order struct {
Id string
Customer *User `fory:"ref"`
Items []string
Quantities map[string]int32
Status Status
}
func RegisterTypes(f *fory.Fory) error {
if err := f.RegisterEnum(Status(0), 100); err != nil {
return err
}
if err := f.Register(User{}, 101); err != nil {
return err
}
if err := f.Register(Order{}, 102); err != nil {
return err
}
return nil
}
func registerAllTypes(f *fory.Fory) error {
if err := RegisterTypes(f); err != nil {
return err
}
return nil
}
```
`RegisterTypes` only contains types defined in the current file. The generated
`registerAllTypes` registers imported types first and then calls `RegisterTypes`.
### Usage
```go
package main
import (
"demo"
)
func main() {
email := "alice@example.com"
user := &demo.User{
Id: "u123",
Name: "Alice",
Email: &email,
Age: 30,
}
order := &demo.Order{
Id: "o456",
Customer: user,
Items: []string{"item1", "item2"},
Quantities: map[string]int32{
"item1": 2,
"item2": 1,
},
Status: demo.StatusActive,
}
bytes, err := order.ToBytes()
if err != nil {
panic(err)
}
var restored demo.Order
if err := restored.FromBytes(bytes); err != nil {
panic(err)
}
}
```
## Rust
### Module Generation
```rust
// Licensed to the Apache Software Foundation (ASF)...
use fory::{Fory, ForyObject};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
#[repr(i32)]
pub enum Status {
#[default]
Pending = 0,
Active = 1,
Completed = 2,
}
#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
pub struct User {
pub id: String,
pub name: String,
#[fory(nullable = true)]
pub email: Option<String>,
pub age: i32,
}
#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
pub struct Order {
pub id: String,
pub customer: Arc<User>,
pub items: Vec<String>,
pub quantities: HashMap<String, i32>,
pub status: Status,
}
pub fn register_types(fory: &mut Fory) -> Result<(), fory::Error> {
fory.register::<Status>(100)?;
fory.register::<User>(101)?;
fory.register::<Order>(102)?;
Ok(())
}
fn register_all_types(fory: &mut Fory) -> Result<(), fory::Error> {
register_types(fory)?;
Ok(())
}
```
`register_types` only contains types defined in the current file. The generated
`register_all_types` registers imported types first and then calls
`register_types`.
**Note:** Rust uses `Arc` by default for `ref` fields. In FDL, use
`ref(thread_safe = false)` to generate `Rc`, and `ref(weak = true)` to generate
`ArcWeak`/`RcWeak`. For protobuf/IDL extensions, use
`[(fory).thread_safe_pointer = false]` and `[(fory).weak_ref = true]`.
### Usage
```rust
use demo::{User, Order, Status};
use std::sync::Arc;
use std::collections::HashMap;
fn main() -> Result<(), fory::Error> {
let user = Arc::new(User {
id: "u123".to_string(),
name: "Alice".to_string(),
email: Some("alice@example.com".to_string()),
age: 30,
});
let mut quantities = HashMap::new();
quantities.insert("item1".to_string(), 2);
quantities.insert("item2".to_string(), 1);
let order = Order {
id: "o456".to_string(),
customer: user,
items: vec!["item1".to_string(), "item2".to_string()],
quantities,
status: Status::Active,
};
let bytes = order.to_bytes()?;
let restored = Order::from_bytes(&bytes)?;
Ok(())
}
```
## C++
### Header Generation
```cpp
/*
* Licensed to the Apache Software Foundation (ASF)...
*/
#ifndef DEMO_H_
#define DEMO_H_
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "fory/serialization/fory.h"
namespace demo {
struct User;
struct Order;
enum class Status : int32_t {
PENDING = 0,
ACTIVE = 1,
COMPLETED = 2,
};
FORY_ENUM(Status, PENDING, ACTIVE, COMPLETED);
struct User {
std::string id;
std::string name;
std::optional<std::string> email;
int32_t age;
bool operator==(const User& other) const {
return id == other.id && name == other.name &&
email == other.email && age == other.age;
}
};
FORY_STRUCT(User, id, name, email, age);
struct Order {
std::string id;
std::shared_ptr<User> customer;
std::vector<std::string> items;
std::map<std::string, int32_t> quantities;
Status status;
bool operator==(const Order& other) const {
return id == other.id && customer == other.customer &&
items == other.items && quantities == other.quantities &&
status == other.status;
}
};
FORY_STRUCT(Order, id, customer, items, quantities, status);
inline void register_types(fory::serialization::BaseFory& fory) {
fory.register_enum<Status>(100);
fory.register_struct<User>(101);
fory.register_struct<Order>(102);
}
inline void register_all_types(fory::serialization::BaseFory& fory) {
register_types(fory);
}
} // namespace demo
#endif // DEMO_H_
```
`register_types` only contains types defined in the current file. The generated
`register_all_types` registers imported types first and then calls
`register_types`.
### Usage
```cpp
#include "demo.h"
#include <iostream>
int main() {
auto user = std::make_shared<demo::User>();
user->id = "u123";
user->name = "Alice";
user->email = "alice@example.com";
user->age = 30;
demo::Order order;
order.id = "o456";
order.customer = user;
order.items = {"item1", "item2"};
order.quantities = {{"item1", 2}, {"item2", 1}};
order.status = demo::Status::ACTIVE;
auto bytes = order.to_bytes();
auto restored = demo::Order::from_bytes(bytes.value());
return 0;
}
```
**Note:** C++ uses `std::shared_ptr<T>` for `ref` fields. Set
`ref(weak = true)` in FDL (or `[(fory).weak_ref = true]` in protobuf) to generate
`fory::serialization::SharedWeak<T>` for weak references.
## Generated Annotations Summary
### Java Annotations
| Annotation | Purpose |
| ----------------------------- | -------------------------- |
| `@ForyField(nullable = true)` | Marks field as nullable |
| `@ForyField(ref = true)` | Enables reference tracking |
### Python Type Hints
| Hint | Purpose |
| -------------- | ------------------- |
| `Optional[T]` | Nullable field |
| `List[T]` | Repeated field |
| `Dict[K, V]` | Map field |
| `pyfory.int32` | Fixed-width integer |
### Go Struct Tags
| Tag | Purpose |
| ----------------- | -------------------------- |
| `fory:"nullable"` | Marks field as nullable |
| `fory:"ref"` | Enables reference tracking |
### Rust Attributes
| Attribute | Purpose |
| -------------------------- | -------------------------- |
| `#[derive(ForyObject)]` | Enables Fory serialization |
| `#[fory(nullable = true)]` | Marks field as nullable |
| `#[repr(i32)]` | Enum representation |
### C++ Macros
| Macro | Purpose |
| ---------------------------- | ----------------------- |
| `FORY_STRUCT(T[, fields..])` | Registers struct fields |
| `FORY_ENUM(T, values..)` | Registers enum values |
## Name-Based Registration
When types don't have explicit type IDs, they use namespace-based registration:
### FDL
```protobuf
package myapp.models;
message Config { // No @id
string key = 1;
string value = 2;
}
```
### Generated Registration
**Java:**
```java
fory.register(Config.class, "myapp.models", "Config");
```
**Python:**
```python
fory.register_type(Config, namespace="myapp.models", typename="Config")
```
**Go:**
```go
f.RegisterTagType("myapp.models.Config", Config{})
```
**Rust:**
```rust
fory.register_by_namespace::<Config>("myapp.models", "Config")?;
```
**C++:**
```cpp
fory.register_struct<Config>("myapp.models", "Config");
```
## Customization
### Extending Generated Code
Generated code can be extended through language-specific mechanisms:
**Java:** Use inheritance or composition:
```java
public class ExtendedUser extends User {
public String getDisplayName() {
return getName() + " <" + getEmail() + ">";
}
}
```
**Python:** Add methods after import:
```python
from demo import User
def get_display_name(self):
return f"{self.name} <{self.email}>"
User.get_display_name = get_display_name
```
**Go:** Use separate file in same package:
```go
package demo
func (u *User) DisplayName() string {
return u.Name + " <" + *u.Email + ">"
}
```
**Rust:** Use trait extensions:
```rust
trait UserExt {
fn display_name(&self) -> String;
}
impl UserExt for User {
fn display_name(&self) -> String {
format!("{} <{}>", self.name, self.email.as_deref().unwrap_or(""))
}
}
```
**C++:** Use inheritance or free functions:
```cpp
std::string display_name(const demo::User& user) {
return user.name + " <" + user.email.value_or("") + ">";
}
```