Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions compiler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,21 +185,21 @@ message Config { ... } // Registered as "package.Config"

### Primitive Types

| FDL Type | Java | Python | Go | Rust | C++ | C# | JavaScript |
| ----------- | ----------- | ------------------- | ----------- | ----------------------- | ---------------------- | ---------------- | ------------------ |
| `bool` | `boolean` | `bool` | `bool` | `bool` | `bool` | `bool` | `boolean` |
| `int8` | `byte` | `pyfory.Int8` | `int8` | `i8` | `int8_t` | `sbyte` | `number` |
| `int16` | `short` | `pyfory.Int16` | `int16` | `i16` | `int16_t` | `short` | `number` |
| `int32` | `int` | `pyfory.Int32` | `int32` | `i32` | `int32_t` | `int` | `number` |
| `int64` | `long` | `pyfory.Int64` | `int64` | `i64` | `int64_t` | `long` | `bigint \| number` |
| `float16` | `Float16` | `pyfory.Float16` | `float16` | `Float16` | `fory::float16_t` | `Half` | `number` |
| `bfloat16` | `BFloat16` | `pyfory.BFloat16` | `bfloat16` | `BFloat16` | `fory::bfloat16_t` | `BFloat16` | `number` |
| `float32` | `float` | `pyfory.Float32` | `float32` | `f32` | `float` | `float` | `number` |
| `float64` | `double` | `pyfory.Float64` | `float64` | `f64` | `double` | `double` | `number` |
| `string` | `String` | `str` | `string` | `String` | `std::string` | `string` | `string` |
| `bytes` | `byte[]` | `bytes` | `[]byte` | `Vec<u8>` | `std::vector<uint8_t>` | `byte[]` | `Uint8Array` |
| `date` | `LocalDate` | `datetime.date` | `time.Time` | `chrono::NaiveDate` | `fory::Date` | `DateOnly` | `Date` |
| `timestamp` | `Instant` | `datetime.datetime` | `time.Time` | `chrono::NaiveDateTime` | `fory::Timestamp` | `DateTimeOffset` | `Date` |
| FDL Type | Java | Python | Go | Rust | C++ | C# | JavaScript |
| ----------- | ----------- | ------------------- | ----------- | ----------------- | ---------------------- | ---------------- | ------------------ |
| `bool` | `boolean` | `bool` | `bool` | `bool` | `bool` | `bool` | `boolean` |
| `int8` | `byte` | `pyfory.Int8` | `int8` | `i8` | `int8_t` | `sbyte` | `number` |
| `int16` | `short` | `pyfory.Int16` | `int16` | `i16` | `int16_t` | `short` | `number` |
| `int32` | `int` | `pyfory.Int32` | `int32` | `i32` | `int32_t` | `int` | `number` |
| `int64` | `long` | `pyfory.Int64` | `int64` | `i64` | `int64_t` | `long` | `bigint \| number` |
| `float16` | `Float16` | `pyfory.Float16` | `float16` | `Float16` | `fory::float16_t` | `Half` | `number` |
| `bfloat16` | `BFloat16` | `pyfory.BFloat16` | `bfloat16` | `BFloat16` | `fory::bfloat16_t` | `BFloat16` | `number` |
| `float32` | `float` | `pyfory.Float32` | `float32` | `f32` | `float` | `float` | `number` |
| `float64` | `double` | `pyfory.Float64` | `float64` | `f64` | `double` | `double` | `number` |
| `string` | `String` | `str` | `string` | `String` | `std::string` | `string` | `string` |
| `bytes` | `byte[]` | `bytes` | `[]byte` | `Vec<u8>` | `std::vector<uint8_t>` | `byte[]` | `Uint8Array` |
| `date` | `LocalDate` | `datetime.date` | `time.Time` | `fory::Date` | `fory::Date` | `DateOnly` | `Date` |
| `timestamp` | `Instant` | `datetime.datetime` | `time.Time` | `fory::Timestamp` | `fory::Timestamp` | `DateTimeOffset` | `Date` |

### Collection Types

Expand Down
1 change: 1 addition & 0 deletions compiler/fory_compiler/frontend/fdl/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"enable_auto_type_id",
"go_nested_type_style",
"swift_namespace_style",
"rust_use_chrono_temporal_types",
"evolving",
}

Expand Down
28 changes: 25 additions & 3 deletions compiler/fory_compiler/generators/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,35 @@ class RustGenerator(BaseGenerator):
PrimitiveKind.FLOAT64: "f64",
PrimitiveKind.STRING: "::std::string::String",
PrimitiveKind.BYTES: "::std::vec::Vec<u8>",
PrimitiveKind.DECIMAL: "::fory::Decimal",
PrimitiveKind.ANY: "::std::boxed::Box<dyn ::std::any::Any>",
}

FORY_TEMPORAL_MAP = {
PrimitiveKind.DATE: "::fory::Date",
PrimitiveKind.TIMESTAMP: "::fory::Timestamp",
PrimitiveKind.DURATION: "::fory::Duration",
}

CHRONO_TEMPORAL_MAP = {
PrimitiveKind.DATE: "::chrono::NaiveDate",
PrimitiveKind.TIMESTAMP: "::chrono::NaiveDateTime",
PrimitiveKind.DURATION: "::chrono::Duration",
PrimitiveKind.DECIMAL: "::fory::Decimal",
PrimitiveKind.ANY: "::std::boxed::Box<dyn ::std::any::Any>",
}

def use_chrono_temporal_types(self) -> bool:
return self.schema.get_option("rust_use_chrono_temporal_types") is True

def primitive_type_name(self, kind: PrimitiveKind) -> str:
if kind in self.FORY_TEMPORAL_MAP:
temporal_map = (
self.CHRONO_TEMPORAL_MAP
if self.use_chrono_temporal_types()
else self.FORY_TEMPORAL_MAP
)
return temporal_map[kind]
return self.PRIMITIVE_MAP[kind]

def generate(self) -> List[GeneratedFile]:
"""Generate Rust files for the schema."""
files = []
Expand Down Expand Up @@ -723,7 +745,7 @@ def generate_type(
if isinstance(field_type, PrimitiveType):
if field_type.kind == PrimitiveKind.ANY:
return "::std::boxed::Box<dyn ::std::any::Any>"
base_type = self.PRIMITIVE_MAP[field_type.kind]
base_type = self.primitive_type_name(field_type.kind)
if nullable:
return f"::std::option::Option<{base_type}>"
return base_type
Expand Down
47 changes: 47 additions & 0 deletions compiler/fory_compiler/tests/test_generated_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,53 @@ def test_generated_code_scalar_types_equivalent():
assert_all_languages_equal(schemas)


def test_rust_generated_code_uses_fory_temporal_carriers():
schema = parse_fdl(
dedent(
"""
package gen;

message TemporalTypes {
date day = 1;
timestamp instant = 2;
duration elapsed = 3;
}
"""
)
)

rust_output = render_files(generate_files(schema, RustGenerator))
assert "pub day: ::fory::Date," in rust_output
assert "pub instant: ::fory::Timestamp," in rust_output
assert "pub elapsed: ::fory::Duration," in rust_output
assert "chrono::" not in rust_output


def test_rust_generated_code_can_use_chrono_temporal_types():
schema = parse_fdl(
dedent(
"""
package gen;
option rust_use_chrono_temporal_types = true;

message TemporalTypes {
date day = 1;
timestamp instant = 2;
duration elapsed = 3;
}
"""
)
)

rust_output = render_files(generate_files(schema, RustGenerator))
assert "pub day: ::chrono::NaiveDate," in rust_output
assert "pub instant: ::chrono::NaiveDateTime," in rust_output
assert "pub elapsed: ::chrono::Duration," in rust_output
assert "::fory::Date" not in rust_output
assert "::fory::Timestamp" not in rust_output
assert "::fory::Duration" not in rust_output


def test_generated_code_integer_encoding_variants_equivalent():
fdl = dedent(
"""
Expand Down
9 changes: 8 additions & 1 deletion docs/compiler/compiler-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,14 @@ Compile options:
| `--emit-fdl` | Emit translated FDL (for non-FDL inputs) | `false` |
| `--emit-fdl-path` | Write translated FDL to this path (file or directory) | (stdout) |

For both `go_nested_type_style` and `swift_namespace_style`, schema-level file options are supported (`option ... = ...;`) and the CLI flag overrides the schema option when both are present.
Schema-level file options are supported for language-specific generation choices.
For `go_nested_type_style` and `swift_namespace_style`, the CLI flag overrides
the schema option when both are present. Rust temporal codegen has no CLI flag:
set `option rust_use_chrono_temporal_types = true;` in the schema to generate
`chrono::NaiveDate`, `chrono::NaiveDateTime`, and `chrono::Duration` instead of
the default `fory::Date`, `fory::Timestamp`, and `fory::Duration`. Crates that
compile generated chrono-based Rust code must depend on `chrono` and enable
Fory's `chrono` feature.

Scan options (with `--scan-generated`):

Expand Down
70 changes: 52 additions & 18 deletions docs/compiler/schema-idl.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,29 @@ message Payment {

The CLI flag `--swift_namespace_style` overrides this schema option when both are set.

### Rust Chrono Temporal Types Option

Rust generated code uses Fory's lightweight temporal carrier types by default:
`fory::Date`, `fory::Timestamp`, and `fory::Duration`. Set
`rust_use_chrono_temporal_types` when the generated Rust API should expose
chrono temporal types instead:

```protobuf
package payment;
option rust_use_chrono_temporal_types = true;

message Event {
date business_day = 1;
timestamp created_at = 2;
duration timeout = 3;
}
```

With this option, Rust code maps `date` to `chrono::NaiveDate`, `timestamp` to
`chrono::NaiveDateTime`, and `duration` to `chrono::Duration`. The Rust crate
that compiles the generated code must depend on `chrono` and enable Fory's
`chrono` feature.

### Java Outer Classname Option

Generate all types as inner classes of a single outer wrapper class:
Expand Down Expand Up @@ -1150,27 +1173,38 @@ Underscore spellings for integer encoding are not FDL type names.

##### Date

| Language | Type | Notes |
| ---------- | --------------------------- | ----------------------- |
| Java | `java.time.LocalDate` | |
| Python | `datetime.date` | |
| Go | `time.Time` | Time portion ignored |
| Rust | `chrono::NaiveDate` | Requires `chrono` crate |
| C++ | `fory::serialization::Date` | |
| JavaScript | `Date` | |
| Dart | `LocalDate` | Fory package type |
| Language | Type | Notes |
| ---------- | --------------------------- | --------------------------------------------------------------------------- |
| Java | `java.time.LocalDate` | |
| Python | `datetime.date` | |
| Go | `time.Time` | Time portion ignored |
| Rust | `fory::Date` | Set `rust_use_chrono_temporal_types = true` to generate `chrono::NaiveDate` |
| C++ | `fory::serialization::Date` | |
| JavaScript | `Date` | |
| Dart | `LocalDate` | Fory package type |

##### Timestamp

| Language | Type | Notes |
| ---------- | -------------------------------- | ----------------------- |
| Java | `java.time.Instant` | UTC-based |
| Python | `datetime.datetime` | |
| Go | `time.Time` | |
| Rust | `chrono::NaiveDateTime` | Requires `chrono` crate |
| C++ | `fory::serialization::Timestamp` | |
| JavaScript | `Date` | |
| Dart | `Timestamp` | Fory package type |
| Language | Type | Notes |
| ---------- | -------------------------------- | ------------------------------------------------------------------------------- |
| Java | `java.time.Instant` | UTC-based |
| Python | `datetime.datetime` | |
| Go | `time.Time` | |
| Rust | `fory::Timestamp` | Set `rust_use_chrono_temporal_types = true` to generate `chrono::NaiveDateTime` |
| C++ | `fory::serialization::Timestamp` | |
| JavaScript | `Date` | |
| Dart | `Timestamp` | Fory package type |

##### Duration

| Language | Type | Notes |
| -------- | ------------------------------- | -------------------------------------------------------------------------- |
| Java | `java.time.Duration` | |
| Python | `datetime.timedelta` | |
| Go | `time.Duration` | |
| Rust | `fory::Duration` | Set `rust_use_chrono_temporal_types = true` to generate `chrono::Duration` |
| C++ | `fory::serialization::Duration` | |
| Dart | `Duration` | |

#### Any

Expand Down
34 changes: 30 additions & 4 deletions docs/guide/rust/basic-serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,36 @@ assert_eq!(person, decoded);

### Date and Time

| Rust Type | Description |
| ----------------------- | -------------------------- |
| `chrono::NaiveDate` | Date without timezone |
| `chrono::NaiveDateTime` | Timestamp without timezone |
| Rust Type | Description |
| ----------- | ------------------------------------------------------- |
| `Date` | Date without timezone, stored as epoch days |
| `Timestamp` | Point in time, stored as epoch seconds and nanos |
| `Duration` | Signed duration, stored as seconds and normalized nanos |

The built-in carriers expose dependency-free constructors, accessors, conversions, and checked
arithmetic:

```rust
use fory::{Date, Duration, Timestamp};

let date = Date::from_epoch_days(19_782);
assert_eq!(date.checked_add_days(1)?.epoch_days(), 19_783);

let timestamp = Timestamp::from_epoch_millis(-1);
assert_eq!(timestamp.to_epoch_millis()?, -1);

let duration = Duration::from_parts(1, 1_500_000_000)?;
assert_eq!(duration.to_millis()?, 2_500);
let later = timestamp.checked_add_duration(duration)?;
```

`chrono::NaiveDate`, `chrono::NaiveDateTime`, and `chrono::Duration` are supported when the Rust
`chrono` feature is enabled:

```toml
[dependencies]
fory = { version = "0.13", features = ["chrono"] }
```

### Custom Types

Expand Down
4 changes: 2 additions & 2 deletions docs/specification/xlang_type_mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ FDL spells them as an encoding modifier plus a semantic integer type.
| union | 33 | Union | typing.Union | / | `std::variant<Ts...>` | / | tagged union enum |
| none | 36 | null | None | null | `std::monostate` | nil | `()` |
| duration | 37 | Duration | timedelta | Number | duration | Duration | Duration |
| timestamp | 38 | Instant | datetime | Number | std::chrono::nanoseconds | Time | DateTime |
| date | 39 | LocalDate | datetime.date | Date | fory::serialization::Date | fory.Date | chrono::NaiveDate |
| timestamp | 38 | Instant | datetime | Number | std::chrono::nanoseconds | Time | Timestamp |
| date | 39 | LocalDate | datetime.date | Date | fory::serialization::Date | fory.Date | Date |
| decimal | 40 | BigDecimal | Decimal | Decimal | / | fory.Decimal | fory::Decimal |
| binary | 41 | byte[] | bytes | / | `uint8_t[n]/vector<T>` | `[n]uint8/[]T` | `Vec<u8>` |
| `array<bool>` (bool_array) | 43 | bool[] | BoolArray / ndarray(np.bool\_) | BoolArray / Type.boolArray() | `bool[n]` | `[n]bool/[]T` | `Vec<bool>` |
Expand Down
1 change: 1 addition & 0 deletions integration_tests/idl_tests/idl/example.fdl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package example;

option go_package = "github.com/apache/fory/integration_tests/idl_tests/go/example/generated;example";
option evolving = false;
option rust_use_chrono_temporal_types = true;

enum ExampleState [id=1504] {
UNKNOWN = 0;
Expand Down
Loading
Loading