diff --git a/.asf.yaml b/.asf.yaml index 38b3e48d28..dc7a48f32c 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -19,7 +19,7 @@ github: description: >- - A blazingly fast multi-language serialization framework powered by JIT and zero-copy. + A blazingly fast multi-language serialization framework for idiomatic domain objects, schema IDL, and cross-language data exchange. homepage: https://fory.apache.org labels: - javascript diff --git a/README.md b/README.md index 5e8b708c6a..6964aa0f48 100644 --- a/README.md +++ b/README.md @@ -12,155 +12,128 @@ [![NuGet](https://img.shields.io/nuget/v/Apache.Fory?logo=nuget&style=for-the-badge)](https://www.nuget.org/packages/Apache.Fory) [![pub.dev](https://img.shields.io/pub/v/fory?logo=dart&style=for-the-badge)](https://pub.dev/packages/fory) -**Apache Fory™** is a blazingly-fast multi-language serialization framework powered by **JIT compilation**, **zero-copy** techniques, and **advanced code generation**, achieving up to **170x performance improvement** while maintaining simplicity and ease of use. +**Apache Fory™** is a blazingly fast multi-language serialization framework for +idiomatic domain objects, schema IDL, and cross-language data exchange. > [!IMPORTANT] -> **Apache Fory™ was previously named as Apache Fury. For versions before 0.11, please use "fury" instead of "fory" in package names, imports, and dependencies, see [Fury Docs](https://fory.apache.org/docs/0.10/docs/introduction/) for how to use Fury in older versions**. - -## Key Features - -### High-Performance Serialization - -Apache Fory™ delivers excellent performance through advanced optimization techniques: - -- **JIT Compilation**: Runtime code generation for Java eliminates virtual method calls and inlines hot paths -- **Static Code Generation**: Compile-time code generation for Rust, C++, and Go delivers peak performance without runtime overhead -- **Meta Packing & Sharing**: Class metadata packing and sharing reduces redundant type information across objects on one stream - -### Cross-Language Serialization - -The **[xlang serialization format](docs/specification/xlang_serialization_spec.md)** enables seamless data exchange across programming languages: - -- **Reference Preservation**: Shared and circular references work correctly across languages -- **Polymorphism**: Objects serialize/deserialize with their actual runtime types -- **Schema Evolution**: Optional forward/backward compatibility for evolving schemas -- **Automatic Serialization**: Serialize domain objects automatically, no IDL or schema definitions required - -### Row Format - -A cache-friendly **[row format](docs/specification/row_format_spec.md)** optimized for analytics workloads: - -- **Zero-Copy Random Access**: Read individual fields without deserializing entire objects -- **Partial Operations**: Selective field serialization and deserialization for efficiency -- **Apache Arrow Integration**: Seamless conversion to columnar format for analytics pipelines -- **Multi-Language**: Available in Java, Python, Rust and C++ - -### Security & Production-Readiness - -Built for production environments with secure defaults and explicit control: - -- **Class Registration**: Whitelist-based deserialization control is enabled by default to block untrusted classes. -- **Depth Limiting**: Configurable object graph depth limits mitigate recursive and stack exhaustion attacks. -- **Configurable Policies**: Custom class checkers and deserialization policies let teams enforce internal security rules. -- **Platform Support**: Runs on Java 8 through 25, supports GraalVM native image, and works across major operating systems. - -## Protocols - -Apache Fory™ provides three protocol families optimized for different scenarios: - -| Protocol Family | Use Case | Key Features | -| ------------------------------------------------------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **[Xlang Serialization](docs/specification/xlang_serialization_spec.md)** | Cross-language object exchange | Automatic serialization, reference preservation, polymorphism | -| **[Row Format](docs/specification/row_format_spec.md)** | Analytics and data processing | Zero-copy random access, partial operations, Apache Arrow compatibility | -| **Native Serialization** | Language-specific optimization | Native protocol implementations per language, including **[Java Serialization](docs/specification/java_serialization_spec.md)** and Python Native. Python Native extends Xlang with more type support and better performance. | - -All protocol families share the same optimized codebase, allowing improvements in one family to benefit others. - -## Benchmarks - -### Java Serialization Performance - -Charts labeled **"compatible"** show schema evolution mode with forward/backward compatibility enabled, while others show schema consistent mode where class schemas must match. - -**Serialization Throughput**: +> Apache Fory™ was previously named Apache Fury. For versions before 0.11, use +> `fury` instead of `fory` in package names, imports, and dependencies. See the +> [Fury docs](https://fory.apache.org/docs/0.10/docs/introduction/) for older +> releases. + +## Why Fory + +Fory is built for fast, compact serialization across languages and runtimes. It +works with idiomatic objects in each language, supports shared schemas when you +need a contract, and preserves object features such as shared and circular +references. + +- **Efficient Cross-Language Encoding**: Exchange payloads across supported + languages with compact binary encoding, metadata packing, schema evolution, + shared/circular references, and polymorphic runtime types. +- **Domain Objects First**: Serialize Java classes, Python dataclasses, Go + structs, Rust/C++ structs, and generated or annotated model types directly. + Preserve shared and circular references when object identity matters. +- **Reference-Aware Schema IDL**: Support shared and circular references + directly in the schema, alongside numbers, strings, lists, maps, arrays, + enums, structs, and unions. Define schemas once, then generate native domain + objects for each language without forcing wrapper types into user code. +- **Row-Format Random Access**: Read fields, arrays, and nested values without + rebuilding full objects, with zero-copy access, partial reads, and Arrow + integration. +- **Optimized Runtimes**: Java JIT serializers and generated/static serializers + in other runtimes keep hot paths fast and payloads compact. +- **Language And Platform Support**: Java, Python, C++, Go, Rust, + JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin, including GraalVM + native image, Dart VM/Flutter/web, and Node.js/browser JavaScript. + +## Performance + +Benchmarks show Fory delivering higher throughput and smaller serialized +payloads than common serialization frameworks on representative workloads. Java +has the broadest comparison set; the other charts show runtime-specific results +across supported languages. + +**Java** [Benchmarks](docs/benchmarks/java) + +In Java serialization benchmarks, Fory reaches up to **170x** the throughput of +JDK serialization on selected workloads.

-Java Serialization Throughput +Java serialization throughput

-**Deserialization Throughput**: -

-Java Deserialization Throughput +Java deserialization throughput

-**Xlang Throughput**: -

- +Java xlang throughput

-See [Java Benchmarks](docs/benchmarks/java) for more details. - -### Rust Serialization Performance +**Python** [Benchmarks](benchmarks/python)

- +Python serialization throughput

-For more detailed benchmarks and methodology, see [Rust Benchmarks](benchmarks/rust). - -### C++ Serialization Performance +**Rust** [Benchmarks](benchmarks/rust)

- +Rust serialization throughput

-For more detailed benchmarks and methodology, see [C++ Benchmarks](benchmarks/cpp). +
+Benchmarks for C++, Go, JavaScript/TypeScript, C#, Swift, and Dart -### Go Serialization Performance +**C++** [Benchmarks](benchmarks/cpp)

- +C++ serialization throughput

-For more detailed benchmarks and methodology, see [Go Benchmark](benchmarks/go). - -### Python Serialization Performance +**Go** [Benchmarks](benchmarks/go)

- +Go serialization throughput

-For more detailed benchmarks and methodology, see [Python Benchmarks](benchmarks/python). - -### JavaScript/NodeJS Serialization Performance +**JavaScript/TypeScript** [Benchmarks](docs/benchmarks/javascript)

- +JavaScript serialization throughput

-For more detailed benchmarks and methodology, see [JavaScript Benchmarks](docs/benchmarks/javascript). - -### C# Serialization Performance +**C#** [Benchmarks](docs/benchmarks/csharp)

- +C# serialization throughput

-For more detailed benchmarks and methodology, see [C# Benchmarks](docs/benchmarks/csharp). - -### Swift Serialization Performance +**Swift** [Benchmarks](docs/benchmarks/swift)

- +Swift serialization throughput

-For more detailed benchmarks and methodology, see [Swift Benchmarks](docs/benchmarks/swift). - -### Dart Serialization Performance +**Dart** [Benchmarks](docs/benchmarks/dart)

- +Dart serialization throughput

-For more detailed benchmarks and methodology, see [Dart Benchmarks](docs/benchmarks/dart). +
## Installation -**Java**: +Pick the runtime you use and run the package-manager command, or paste the +dependency block into your build file. + +**Java** + +Maven: ```xml @@ -170,19 +143,29 @@ For more detailed benchmarks and methodology, see [Dart Benchmarks](docs/benchma ``` -Snapshots are available from `https://repository.apache.org/snapshots/` (version `0.17.0-SNAPSHOT`). +Gradle: + +```gradle +implementation "org.apache.fory:fory-core:0.17.0" +``` + +**Scala** + +sbt: + +```scala +libraryDependencies += "org.apache.fory" %% "fory-scala" % "0.17.0" +``` -**Scala**: +**Kotlin** -```sbt -// Scala 2.13 -libraryDependencies += "org.apache.fory" % "fory-scala_2.13" % "0.17.0" +Gradle: -// Scala 3 -libraryDependencies += "org.apache.fory" % "fory-scala_3" % "0.17.0" +```kotlin +implementation("org.apache.fory:fory-kotlin:0.17.0") ``` -**Kotlin**: +Maven: ```xml @@ -192,113 +175,177 @@ libraryDependencies += "org.apache.fory" % "fory-scala_3" % "0.17.0" ``` -**Python**: +**Python** ```bash pip install pyfory +``` + +For row-format support: -# With row format support -pip install pyfory[format] +```bash +pip install "pyfory[format]" ``` -**Rust**: +**Rust** + +`Cargo.toml`: ```toml [dependencies] -fory = "0.16" +fory = "0.17" +``` + +**C++** + +CMake: + +```cmake +include(FetchContent) +FetchContent_Declare( + fory + GIT_REPOSITORY https://github.com/apache/fory.git + GIT_TAG v0.17.0 + SOURCE_SUBDIR cpp +) +FetchContent_MakeAvailable(fory) +target_link_libraries(my_app PRIVATE fory::serialization) ``` -**C++**: +Bazel: -Fory C++ supports both CMake and Bazel build systems. See [C++ Installation Guide](https://fory.apache.org/docs/guide/cpp/#installation) for detailed instructions. +```bazel +# MODULE.bazel +bazel_dep(name = "fory", version = "0.17.0") +git_override(module_name = "fory", remote = "https://github.com/apache/fory.git", commit = "v0.17.0") -**Golang**: +# BUILD +deps = ["@fory//cpp/fory/serialization:fory_serialization"] +``` + +See the [C++ installation guide](https://fory.apache.org/docs/guide/cpp/#installation) +for complete CMake, Bazel, and source-build details. + +**Go** ```bash go get github.com/apache/fory/go/fory ``` -**NodeJS/JavaScript**: +**JavaScript/TypeScript** ```bash npm install @apache-fory/core ``` -Optional Node.js string fast-path support: +For the Node.js string fast path: ```bash npm install @apache-fory/core @apache-fory/hps ``` -**C#**: +**C#** -```xml - - - +```bash +dotnet add package Apache.Fory --version 0.17.0 ``` -**Dart**: +**Dart** -```yaml -dependencies: - fory: ^0.17.0 +```bash +dart pub add fory:^0.17.0 +dart pub add dev:build_runner +``` + +**Swift** + +Add Fory to `Package.swift`: -dev_dependencies: - build_runner: ^2.4.0 +```swift +dependencies: [ + .package(url: "https://github.com/apache/fory.git", exact: "0.17.0") +], +targets: [ + .target( + name: "YourTarget", + dependencies: [.product(name: "Fory", package: "fory")] + ) +] ``` -## Quick Start +See the [Swift guide](https://fory.apache.org/docs/guide/swift/) for generated +serializer setup. + +**Development From Source** + +See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md). + +Snapshots for Java, Scala, and Kotlin are available from +`https://repository.apache.org/snapshots/` with the matching `-SNAPSHOT` version. -This section provides quick examples for getting started with Apache Fory™. For comprehensive guides, see the [Documentation](#documentation). +## Choose Serialization Mode -### Native Serialization +| Mode | Use it when | Start here | +| -------------------- | ------------------------------------------------------------- | -------------------------------------------------------- | +| Xlang serialization | Data crosses language boundaries | [Cross-language guide](docs/guide/xlang) | +| Native serialization | Producer and consumer are in the same language | Language guide for your runtime | +| Row format | You need random field access or analytics-style partial reads | [Row format spec](docs/specification/row_format_spec.md) | -**Always use native mode when working with a single language.** Native mode delivers optimal performance by avoiding the type metadata overhead required for cross-language compatibility. Xlang mode introduces additional metadata encoding costs and restricts serialization to types that are common across all supported languages. Language-specific types will be rejected during serialization in xlang-mode. +Use native mode for same-language traffic. It avoids xlang's cross-language +type mapping and metadata constraints, so it can serialize broader +language-specific object graphs and is the fastest path for same-language +payloads. -#### Java Serialization +Compatible mode is Fory's schema-evolution mode. It writes the metadata readers +and writers need to tolerate schema differences. Xlang mode enables compatible +mode by default to better handle differences between language type systems. +Native mode keeps it off by default for smaller payloads and higher throughput. -When you don't need cross-language support, use Java mode for optimal performance. +Use compatible mode when services deploy independently or when fields may be +added or deleted over time. Use schema-consistent mode when writer and reader +schemas deploy together and you want the smallest payloads. + +For xlang, all peers must agree on type identity. Name-based registration is +easier to read in examples. Numeric IDs are smaller and faster, but they require +coordination across every reader and writer. + +## Cross-Language Serialization + +Xlang mode writes the cross-language Fory wire format. Bytes produced by one +runtime can be read by another when the runtimes use the same type identity, +compatible mode setting, and field schema. + +**Java** ```java -import org.apache.fory.*; -import org.apache.fory.config.*; +import org.apache.fory.Fory; public class Example { public static class Person { - String name; - int age; + public String name; + public int age; } public static void main(String[] args) { - // Create Fory instance - should be reused across serializations - BaseFory fory = Fory.builder() - .withXlang(false) - .requireClassRegistration(true) - // replace `build` with `buildThreadSafeFory` for Thread-Safe Usage - .build(); - // Register your classes (required when class registration is enabled) - // Registration order must be consistent if id is not specified - fory.register(Person.class); - // Serialize + Fory fory = Fory.builder().withXlang(true).withCompatible(true).build(); + fory.register(Person.class, "example.Person"); + Person person = new Person(); - person.name = "chaokunyang"; - person.age = 28; + person.name = "Alice"; + person.age = 30; + byte[] bytes = fory.serialize(person); - Person result = (Person) fory.deserialize(bytes); - System.out.println(result.name + " " + result.age); // Output: chaokunyang 28 + Person decoded = (Person) fory.deserialize(bytes); + System.out.println(decoded.name); } } ``` -For detailed Java usage including compatibility modes, compression, and advanced features, see [Java Serialization Guide](docs/guide/java) and [java/README.md](java/README.md). - -#### Python Serialization - -Python native mode provides a high-performance drop-in replacement for pickle/cloudpickle with better speed and compatibility. +**Python** ```python from dataclasses import dataclass + import pyfory @dataclass @@ -306,28 +353,49 @@ class Person: name: str age: pyfory.Int32 -# Create Fory instance - should be reused across serializations -fory = pyfory.Fory() -# Register your classes (required when class registration is enabled) -fory.register_type(Person) -person = Person(name="chaokunyang", age=28) -data = fory.serialize(person) -result = fory.deserialize(data) -print(result.name, result.age) # Output: chaokunyang 28 +fory = pyfory.Fory(xlang=True, compatible=True) +fory.register_type(Person, typename="example.Person") + +data = fory.serialize(Person("Alice", 30)) +person = fory.deserialize(data) +print(person.name) ``` -Python schema aliases also apply inside declared containers, such as -`Dict[pyfory.FixedInt32, List[pyfory.TaggedInt64]]`, so nested keys and values use the requested -wire encoding in both pure Python and Cython modes. +**Go** + +```go +package main + +import ( + "fmt" + + "github.com/apache/fory/go/fory" +) -For detailed Python usage including type hints, compatibility modes, and advanced features, see [Python Guide](docs/guide/python). +type Person struct { + Name string + Age int32 +} -#### Rust Serialization +func main() { + f := fory.New(fory.WithXlang(true), fory.WithCompatible(true)) + if err := f.RegisterStructByName(Person{}, "example.Person"); err != nil { + panic(err) + } + + data, _ := f.Serialize(&Person{Name: "Alice", Age: 30}) + var person Person + if err := f.Deserialize(data, &person); err != nil { + panic(err) + } + fmt.Println(person.Name) +} +``` -Rust native mode provides compile-time code generation via derive macros for high-performance serialization without runtime overhead. +**Rust** ```rust -use fory::{Fory, ForyStruct}; +use fory::{Error, Fory, ForyStruct}; #[derive(ForyStruct, Debug, PartialEq)] struct Person { @@ -335,111 +403,93 @@ struct Person { age: i32, } -fn main() -> Result<(), fory::Error> { - // Create Fory instance - should be reused across serializations - let mut fory = Fory::default(); - // Register your structs (required when class registration is enabled) - fory.register::(1); - let person = Person { - name: "chaokunyang".to_string(), - age: 28, - }; - let bytes = fory.serialize(&person); - let result: Person = fory.deserialize(&bytes)?; - println!("{} {}", result.name, result.age); // Output: chaokunyang 28 +fn main() -> Result<(), Error> { + let mut fory = Fory::builder().xlang(true).compatible(true).build(); + fory.register_by_name::("example", "Person")?; + + let bytes = fory.serialize(&Person { + name: "Alice".to_string(), + age: 30, + })?; + let person: Person = fory.deserialize(&bytes)?; + println!("{}", person.name); Ok(()) } ``` -For detailed Rust usage including collections, references, and custom serializers, see [Rust Guide](docs/guide/rust). - -#### C++ Serialization - -C++ native mode provides compile-time reflection via the `FORY_STRUCT` macro for efficient serialization with zero runtime overhead. +**C++** ```cpp #include "fory/serialization/fory.h" +#include +#include +#include using namespace fory::serialization; struct Person { - std::string name; - int32_t age; + std::string name; + int32_t age; }; FORY_STRUCT(Person, name, age); int main() { - // Create Fory instance - should be reused across serializations - auto fory = Fory::builder().build(); - // Register your structs (required when class registration is enabled) - fory.register_struct(1); - Person person{"chaokunyang", 28}; - auto bytes = fory.serialize(person).value(); - auto result = fory.deserialize(bytes).value(); - std::cout << result.name << " " << result.age << std::endl; // Output: chaokunyang 28 + auto fory = Fory::builder().xlang(true).compatible(true).build(); + fory.register_struct("example.Person"); + + auto bytes = fory.serialize(Person{"Alice", 30}).value(); + Person person = fory.deserialize(bytes).value(); + std::cout << person.name << std::endl; } ``` -For detailed C++ usage including collections, smart pointers, and error handling, see [C++ Guide](docs/guide/cpp). - -#### NodeJS/JavaScript Serialization - -JavaScript native mode uses registered schemas to generate fast serializers for repeated use in browser or Node.js applications. +**JavaScript/TypeScript** ```ts import Fory, { Type } from "@apache-fory/core"; -const personType = Type.struct("example.person", { - name: Type.string(), - age: Type.int32(), -}); +const personType = Type.struct( + { typeName: "example.Person" }, + { + name: Type.string(), + age: Type.int32(), + }, +); -const fory = new Fory(); +const fory = new Fory({ compatible: true }); const { serialize, deserialize } = fory.register(personType); -const bytes = serialize({ - name: "chaokunyang", - age: 28, -}); +const bytes = serialize({ name: "Alice", age: 30 }); const person = deserialize(bytes); -console.log(person.name, person.age); // Output: chaokunyang 28 +console.log(person.name); ``` -For detailed JavaScript usage including schema registration, references, and cross-language support, see [JavaScript Guide](docs/guide/javascript). - -#### C# Serialization - -C# native mode provides source-generator-backed serialization for registered .NET types. +**C#** ```csharp using Apache.Fory; -[ForyStruct] +[ForyObject] public sealed class Person { - public long Id { get; set; } public string Name { get; set; } = string.Empty; + public int Age { get; set; } } -Fory fory = Fory.Builder().Build(); -fory.Register(1); - -Person person = new() -{ - Id = 1, - Name = "chaokunyang", -}; +Fory fory = Fory.Builder() + .Compatible(true) + .Build(); +fory.Register("example", "Person"); -byte[] bytes = fory.Serialize(person); -Person result = fory.Deserialize(bytes); -Console.WriteLine($"{result.Name} {result.Id}"); // Output: chaokunyang 1 +byte[] bytes = fory.Serialize(new Person { Name = "Alice", Age = 30 }); +Person person = fory.Deserialize(bytes); +Console.WriteLine(person.Name); ``` -For detailed C# usage including configuration, custom serializers, and thread-safe runtime options, see [C# Guide](docs/guide/csharp). - -#### Dart Serialization +C# always writes the xlang frame header, so there is no separate xlang builder +flag. -Dart native mode uses generated serializers for fast serialization without runtime reflection. +**Dart** ```dart import 'package:fory/fory.dart'; @@ -457,7 +507,7 @@ class Person { } void main() { - final fory = Fory(); + final fory = Fory(compatible: true); PersonFory.register( fory, Person, @@ -465,27 +515,41 @@ void main() { typeName: 'Person', ); - final person = Person() - ..name = 'chaokunyang' - ..age = 28; - - final bytes = fory.serialize(person); - final result = fory.deserialize(bytes); - print('${result.name} ${result.age}'); + final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = 30); + final person = fory.deserialize(bytes); + print(person.name); } ``` -Generate the companion file before running the program: +Dart uses the xlang wire format directly. Generate the companion file before +running: ```bash dart run build_runner build --delete-conflicting-outputs ``` -For detailed Dart usage including code generation, field configuration, and cross-language guidance, see [Dart Guide](docs/guide/dart). +**Swift** + +```swift +import Fory + +@ForyStruct +struct Person { + var name: String = "" + var age: Int32 = 0 +} -#### Scala Serialization +let fory = Fory(xlang: true, compatible: true) +try fory.register(Person.self, namespace: "example", name: "Person") -Scala native mode provides optimized serialization for Scala-specific types including case classes, collections, and Option types. +let bytes = try fory.serialize(Person(name: "Alice", age: 30)) +let person: Person = try fory.deserialize(bytes) +print(person.name) +``` + +**Scala** ```scala import org.apache.fory.Fory @@ -493,29 +557,19 @@ import org.apache.fory.serializer.scala.ScalaSerializers case class Person(name: String, age: Int) -object Example { - def main(args: Array[String]): Unit = { - // Create Fory instance - should be reused across serializations - val fory = Fory.builder() - .withXlang(false) - .requireClassRegistration(true) - .build() - // Register Scala serializers for Scala-specific types - ScalaSerializers.registerSerializers(fory) - // Register your case classes - fory.register(classOf[Person]) - val bytes = fory.serialize(Person("chaokunyang", 28)) - val result = fory.deserialize(bytes).asInstanceOf[Person] - println(s"${result.name} ${result.age}") // Output: chaokunyang 28 - } -} -``` - -For detailed Scala usage including collection serialization and integration patterns, see [Scala Guide](docs/guide/scala). +val fory = Fory.builder() + .withXlang(true) + .withCompatible(true) + .build() +ScalaSerializers.registerSerializers(fory) +fory.register(classOf[Person], "example.Person") -#### Kotlin Serialization +val bytes = fory.serialize(Person("Alice", 30)) +val person = fory.deserialize(bytes).asInstanceOf[Person] +println(person.name) +``` -Kotlin native mode provides optimized serialization for Kotlin-specific types including data classes, nullable types, and Kotlin collections. +**Kotlin** ```kotlin import org.apache.fory.Fory @@ -524,285 +578,197 @@ import org.apache.fory.serializer.kotlin.KotlinSerializers data class Person(val name: String, val age: Int) fun main() { - // Create Fory instance - should be reused across serializations val fory = Fory.builder() - .withXlang(false) - .requireClassRegistration(true) + .withXlang(true) + .withCompatible(true) .build() - // Register Kotlin serializers for Kotlin-specific types KotlinSerializers.registerSerializers(fory) - // Register your data classes - fory.register(Person::class.java) - val bytes = fory.serialize(Person("chaokunyang", 28)) - val result = fory.deserialize(bytes) as Person - println("${result.name} ${result.age}") // Output: chaokunyang 28 + fory.register(Person::class.java, "example.Person") + + val bytes = fory.serialize(Person("Alice", 30)) + val person = fory.deserialize(bytes) as Person + println(person.name) } ``` -For detailed Kotlin usage including null safety and default value support, see [kotlin/README.md](kotlin/README.md). +For shared/circular references, polymorphism, numeric IDs versus names, and +type-mapping rules, see the [cross-language guide](docs/guide/xlang) and +[type mapping specification](docs/specification/xlang_type_mapping.md). -### Cross-Language Serialization +## Native Serialization -**Only use xlang mode when you need cross-language data exchange.** Xlang mode adds type metadata overhead for cross-language compatibility and only supports types that can be mapped across all languages. For single-language use cases, always prefer native mode for better performance. +Use native mode when the writer and reader are in the same language. Java and +Python can serialize broader language-specific object graphs this way. The +languages below expose an explicit `xlang=false` or native-mode setting; runtimes +without that switch stay on their documented default path. -The following examples demonstrate serializing a `Person` object across Java and Rust. For other languages (Python, Go, JavaScript, etc.), simply set the xlang mode to `true` and follow the same pattern. +Keep class/type registration enabled for untrusted input. See the language guides +for runtime-specific security and compatibility settings. **Java** ```java -import org.apache.fory.*; -import org.apache.fory.config.*; - -public class XlangExample { - public record Person(String name, int age) {} - - public static void main(String[] args) { - // Create Fory instance with XLANG mode. Compatible mode is the xlang default. - Fory fory = Fory.builder().withXlang(true).withCompatible(true).build(); - // Register with cross-language type id/name - fory.register(Person.class, 1); - // fory.register(Person.class, "example.Person"); - Person person = new Person("chaokunyang", 28); - byte[] bytes = fory.serialize(person); - // bytes can be deserialized by Rust, Python, Go, or other languages - Person result = (Person) fory.deserialize(bytes); - System.out.println(result.name + " " + result.age); // Output: chaokunyang 28 - } -} +Fory fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(true) + .build(); +// Register, serialize, and deserialize as in the xlang example above. ``` -**Rust** - -```rust -use fory::{Fory, ForyStruct}; - -#[derive(ForyStruct, Debug)] -struct Person { - name: String, - age: i32, -} +**Python** -fn main() -> Result<(), Error> { - let mut fory = Fory::builder().xlang(true).compatible(true).build(); - fory.register::(1)?; - // fory.register_by_name::("example.Person")?; - let person = Person { - name: "chaokunyang".to_string(), - age: 28, - }; - let bytes = fory.serialize(&person); - // bytes can be deserialized by Java, Python, Go, or other languages - let result: Person = fory.deserialize(&bytes)?; - println!("{} {}", result.name, result.age); // Output: chaokunyang 28 -} +```python +fory = pyfory.Fory(xlang=False, ref=True) +# Register, serialize, and deserialize as in the xlang example above. ``` -**Key Points for Cross-Language Serialization**: - -- Enable xlang mode in all languages, for example `withXlang(true)` in Java -- Compatible mode defaults to `true` when xlang mode is enabled and is recommended for - cross-language services because schemas can diverge more easily across languages. -- Register types with **consistent IDs or names** across all languages: - - **By ID** (`fory.register(Person.class, 1)`): Faster serialization, more compact encoding, but requires coordination to avoid ID conflicts - - **By name** (`fory.register(Person.class, "example.Person")`): More flexible, less prone to conflicts, easier to manage across teams, but slightly larger encoding -- Type IDs/names must match across all languages for successful deserialization -- Only use types that have cross-language mappings (see [Type Mapping](docs/specification/xlang_type_mapping.md)) +**Go** -For examples with **circular references**, **shared references**, and **polymorphism** across languages, see: - -- [Cross-Language Serialization Guide](docs/guide/xlang) -- [Java Serialization Guide - Cross Language](docs/guide/java) -- [Python Guide - Cross Language](docs/guide/python) - -### Row Format Encoding +```go +f := fory.New(fory.WithXlang(false)) +// Register, serialize, and deserialize as in the xlang example above. +``` -Row format provides zero-copy random access to serialized data, making it ideal for analytics workloads and data processing pipelines. +**Rust** -#### Java +```rust +let mut fory = Fory::builder().xlang(false).build(); +// Register, serialize, and deserialize as in the xlang example above. +``` -```java -import org.apache.fory.format.*; -import java.util.*; -import java.util.stream.*; +**C++** -public class Bar { - String f1; - List f2; -} +```cpp +auto fory = Fory::builder().xlang(false).build(); +// Register, serialize, and deserialize as in the xlang example above. +``` -public class Foo { - int f1; - List f2; - Map f3; - List f4; -} +**Scala** -RowEncoder encoder = Encoders.bean(Foo.class); -Foo foo = new Foo(); -foo.f1 = 10; -foo.f2 = IntStream.range(0, 1000000).boxed().collect(Collectors.toList()); -foo.f3 = IntStream.range(0, 1000000).boxed().collect(Collectors.toMap(i -> "k"+i, i -> i)); - -List bars = new ArrayList<>(1000000); -for (int i = 0; i < 1000000; i++) { - Bar bar = new Bar(); - bar.f1 = "s" + i; - bar.f2 = LongStream.range(0, 10).boxed().collect(Collectors.toList()); - bars.add(bar); -} -foo.f4 = bars; +```scala +val fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(true) + .build() +ScalaSerializers.registerSerializers(fory) +// Register, serialize, and deserialize as in the xlang example above. +``` -// Serialize to row format (can be zero-copy read by Python) -BinaryRow binaryRow = encoder.toRow(foo); +**Kotlin** -// Deserialize entire object -Foo newFoo = encoder.fromRow(binaryRow); +```kotlin +val fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(true) + .build() +KotlinSerializers.registerSerializers(fory) +// Register, serialize, and deserialize as in the xlang example above. +``` -// Zero-copy access to nested fields without full deserialization -BinaryArray binaryArray2 = binaryRow.getArray(1); // Access f2 field -BinaryArray binaryArray4 = binaryRow.getArray(3); // Access f4 field -BinaryRow barStruct = binaryArray4.getStruct(10); // Access 11th Bar element -long value = barStruct.getArray(1).getInt64(5); // Access nested value +## Row Format -// Partial deserialization -RowEncoder barEncoder = Encoders.bean(Bar.class); -Bar newBar = barEncoder.fromRow(barStruct); -Bar newBar2 = barEncoder.fromRow(binaryArray4.getStruct(20)); -``` +Row format is for random access and partial reads. These examples encode an +object with an integer array field, then read one array element from the binary +row without rebuilding the object. -#### Python +**Python** ```python from dataclasses import dataclass -from typing import List, Dict -import pyarrow as pa -import pyfory +from typing import List -@dataclass -class Bar: - f1: str - f2: List[pa.int64] +import pyfory @dataclass -class Foo: - f1: pa.int32 - f2: List[pa.int32] - f3: Dict[str, pa.int32] - f4: List[Bar] - -encoder = pyfory.encoder(Foo) -foo = Foo( - f1=10, - f2=list(range(1000_000)), - f3={f"k{i}": i for i in range(1000_000)}, - f4=[Bar(f1=f"s{i}", f2=list(range(10))) for i in range(1000_000)] -) +class User: + id: pyfory.Int32 + name: str + scores: List[pyfory.Int32] -# Serialize to row format -binary: bytes = encoder.to_row(foo).to_bytes() +encoder = pyfory.encoder(User) +binary = encoder.to_row(User(1, "Alice", [98, 100, 95])).to_bytes() -# Zero-copy random access without full deserialization -foo_row = pyfory.RowData(encoder.schema, binary) -print(foo_row.f2[100000]) # Access element directly -print(foo_row.f4[100000].f1) # Access nested field -print(foo_row.f4[200000].f2[5]) # Access deeply nested field +row = pyfory.RowData(encoder.schema, binary) +print(row.name) +print(row.scores[1]) ``` -For more details on row format, see [Row Format Specification](docs/specification/row_format_spec.md). - -## Documentation - -### User Guides - -| Guide | Description | Source | Website | -| -------------------------------- | ------------------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------ | -| **Java Serialization** | Comprehensive guide for Java serialization | [java](docs/guide/java) | [📖 View](https://fory.apache.org/docs/guide/java/) | -| **Python** | Python-specific features and usage | [python](docs/guide/python) | [📖 View](https://fory.apache.org/docs/guide/python/) | -| **Rust** | Rust implementation and patterns | [rust](docs/guide/rust) | [📖 View](https://fory.apache.org/docs/guide/rust/) | -| **C++** | C++ implementation and patterns | [cpp](docs/guide/cpp) | [📖 View](https://fory.apache.org/docs/guide/cpp/) | -| **Go** | Go serialization and runtime usage | [go](docs/guide/go) | [📖 View](https://fory.apache.org/docs/guide/go/) | -| **JavaScript/NodeJS** | JavaScript and Node.js serialization guide | [javascript](docs/guide/javascript) | [📖 View](https://fory.apache.org/docs/guide/javascript/) | -| **C#** | C# serialization and .NET usage | [csharp](docs/guide/csharp) | [📖 View](https://fory.apache.org/docs/guide/csharp/) | -| **Swift** | Swift implementation and patterns | [swift](docs/guide/swift) | [📖 View](https://fory.apache.org/docs/guide/swift/) | -| **Dart** | Dart serialization and codegen usage | [dart](docs/guide/dart) | [📖 View](https://fory.apache.org/docs/guide/dart/) | -| **Scala** | Scala integration and best practices | [scala](docs/guide/scala) | [📖 View](https://fory.apache.org/docs/guide/scala/) | -| **Kotlin** | Kotlin integration and type support | [kotlin](docs/guide/kotlin) | [📖 View](https://fory.apache.org/docs/guide/kotlin/) | -| **Cross-Language Serialization** | Multi-language object exchange | [xlang](docs/guide/xlang) | [📖 View](https://fory.apache.org/docs/guide/xlang/) | -| **GraalVM** | Native image support and AOT compilation | [graalvm-support.md](docs/guide/java/graalvm-support.md) | [📖 View](https://fory.apache.org/docs/guide/java/graalvm_support) | -| **Development** | Building and contributing to Fory | [DEVELOPMENT.md](docs/DEVELOPMENT.md) | [📖 View](docs/DEVELOPMENT.md) | - -### Protocol Specifications - -| Specification | Description | Source | Website | -| ----------------------- | ------------------------------ | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| **Xlang Serialization** | Cross-language binary protocol | [xlang_serialization_spec.md](docs/specification/xlang_serialization_spec.md) | [📖 View](https://fory.apache.org/docs/specification/fory_xlang_serialization_spec) | -| **Java Serialization** | Java-optimized protocol | [java_serialization_spec.md](docs/specification/java_serialization_spec.md) | [📖 View](https://fory.apache.org/docs/specification/fory_java_serialization_spec) | -| **Row Format** | Row-based binary format | [row_format_spec.md](docs/specification/row_format_spec.md) | [📖 View](https://fory.apache.org/docs/specification/fory_row_format_spec) | -| **Type Mapping** | Cross-language type conversion | [xlang_type_mapping.md](docs/specification/xlang_type_mapping.md) | [📖 View](https://fory.apache.org/docs/specification/fory_xlang_serialization_spec) | - -## Compatibility - -### Schema Compatibility - -Apache Fory™ supports class schema forward/backward compatibility across languages, enabling seamless schema evolution in production systems without requiring coordinated upgrades across all services. Fory provides two schema compatibility modes: - -1. **Compatible Mode (xlang default)**: Supports independent schema evolution with forward and backward compatibility. This mode enables field addition/deletion, limited type evolution, and graceful handling of schema mismatches. It is recommended for `xlang=true` because different languages can diverge on schemas more easily. Enable explicitly using `withCompatible(true)` in Java, `compatible=True` in Python, `compatible(true)` in Rust, `WithCompatible(true)` in Go, or the equivalent runtime option. - -2. **Schema Consistent Mode**: Assumes identical class schemas between serialization and deserialization peers. This mode offers minimal serialization overhead, smallest data size, and fastest performance. Use it only when schemas do not change, or when all services deploy schema changes at the same time. Disable compatible mode explicitly, for example `withCompatible(false)` in Java. - -### Binary Compatibility - -**Current Status**: Binary compatibility is **not guaranteed** between Fory major releases as the protocol continues to evolve. Compatibility **is guaranteed** between minor versions (for example, 0.13.x). - -**Recommendations**: - -- Version your serialized data by Fory major version -- Plan migration strategies when upgrading major versions -- See [upgrade guide](docs/guide/java) for details - -Major-version compatibility is the boundary for stable serialized data. - -## Security - -### Overview - -Serialization security varies by protocol: - -- **Row Format**: Secure with predefined schemas -- **Object Graph Serialization** (Java/Python native): More flexible but requires careful security configuration - -Dynamic serialization can deserialize arbitrary types, which may introduce risks. For example, the deserialization may invoke `init` constructor or `equals/hashCode` method; If the method body contains malicious code, the system will be at risk. - -Fory enables class registration **by default** for dynamic protocols, allowing only trusted registered types. -**Do not disable class registration unless you can ensure your environment is secure**. - -If this option is disabled, you are responsible for serialization security. You should implement and configure a customized `TypeChecker` or `DeserializationPolicy` for fine-grained security control. +**Java** -To report security vulnerabilities in Apache Fory™, please follow the [ASF vulnerability reporting process](https://apache.org/security/#reporting-a-vulnerability). +```java +public class User { + public int id; + public String name; + public int[] scores; +} -## Community and Support +RowEncoder encoder = Encoders.bean(User.class); -### Getting Help +User user = new User(); +user.id = 1; +user.name = "Alice"; +user.scores = new int[] {98, 100, 95}; -- **Slack**: Join our [Slack workspace](https://join.slack.com/t/fory-project/shared_invite/zt-36g0qouzm-kcQSvV_dtfbtBKHRwT5gsw) for community discussions -- **Twitter/X**: Follow [@ApacheFory](https://x.com/ApacheFory) for updates and announcements -- **GitHub Issues**: Report bugs and request features at [apache/fory](https://github.com/apache/fory/issues) -- **Mailing Lists**: Subscribe to Apache Fory mailing lists for development discussions +BinaryRow row = encoder.toRow(user); -### Contributing +Schema schema = encoder.schema(); +Schema.StringField nameField = schema.stringField("name"); +Schema.ArrayField scoresField = schema.arrayField("scores"); -We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md) to get started. +String name = nameField.get(row); +ArrayData scores = scoresField.get(row); +int secondScore = scores.getInt32(1); +``` -**Ways to Contribute**: +For Java imports, nested structs, arrays/maps, Arrow integration, and partial +deserialization, see the +[Java row-format guide](https://fory.apache.org/docs/guide/java/row_format), the +[Python row-format guide](docs/guide/python/row-format.md), and the +[row-format specification](docs/specification/row_format_spec.md). -- 🐛 Report bugs and issues -- 💡 Propose new features -- 📝 Improve documentation -- 🔧 Submit pull requests -- 🧪 Add test cases -- 📊 Share benchmarks +## Documentation -See [Development Guide](docs/DEVELOPMENT.md) for build instructions and development workflow. +**User Guides** + +| Guide | Source | Website | +| --------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------- | +| Java | [docs/guide/java](docs/guide/java) | [View](https://fory.apache.org/docs/guide/java/) | +| Python | [docs/guide/python](docs/guide/python) | [View](https://fory.apache.org/docs/guide/python/) | +| Rust | [docs/guide/rust](docs/guide/rust) | [View](https://fory.apache.org/docs/guide/rust/) | +| C++ | [docs/guide/cpp](docs/guide/cpp) | [View](https://fory.apache.org/docs/guide/cpp/) | +| Go | [docs/guide/go](docs/guide/go) | [View](https://fory.apache.org/docs/guide/go/) | +| JavaScript/TypeScript | [docs/guide/javascript](docs/guide/javascript) | [View](https://fory.apache.org/docs/guide/javascript/) | +| C# | [docs/guide/csharp](docs/guide/csharp) | [View](https://fory.apache.org/docs/guide/csharp/) | +| Swift | [docs/guide/swift](docs/guide/swift) | [View](https://fory.apache.org/docs/guide/swift/) | +| Dart | [docs/guide/dart](docs/guide/dart) | [View](https://fory.apache.org/docs/guide/dart/) | +| Scala | [docs/guide/scala](docs/guide/scala) | [View](https://fory.apache.org/docs/guide/scala/) | +| Kotlin | [docs/guide/kotlin](docs/guide/kotlin) | [View](https://fory.apache.org/docs/guide/kotlin/) | +| Cross-language xlang | [docs/guide/xlang](docs/guide/xlang) | [View](https://fory.apache.org/docs/guide/xlang/) | +| Schema IDL/compiler | [docs/compiler](docs/compiler) | [View](https://fory.apache.org/docs/compiler/) | +| GraalVM native image | [docs/guide/java/graalvm-support.md](docs/guide/java/graalvm-support.md) | [View](https://fory.apache.org/docs/guide/java/graalvm_support) | +| Development | [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) | [View](docs/DEVELOPMENT.md) | + +**Specifications** + +| Specification | Source | Website | +| ---------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| Xlang serialization | [xlang_serialization_spec.md](docs/specification/xlang_serialization_spec.md) | [View](https://fory.apache.org/docs/specification/fory_xlang_serialization_spec) | +| Java serialization | [java_serialization_spec.md](docs/specification/java_serialization_spec.md) | [View](https://fory.apache.org/docs/specification/fory_java_serialization_spec) | +| Row format | [row_format_spec.md](docs/specification/row_format_spec.md) | [View](https://fory.apache.org/docs/specification/fory_row_format_spec) | +| Cross-language mapping | [xlang_type_mapping.md](docs/specification/xlang_type_mapping.md) | [View](https://fory.apache.org/docs/specification/fory_xlang_serialization_spec) | + +## Community + +- [Slack workspace](https://join.slack.com/t/fory-project/shared_invite/zt-36g0qouzm-kcQSvV_dtfbtBKHRwT5gsw) +- [@ApacheFory on X](https://x.com/ApacheFory) +- [GitHub issues](https://github.com/apache/fory/issues) +- Apache Fory mailing lists for development discussion + +## Contributing + +Read [CONTRIBUTING.md](CONTRIBUTING.md) and +[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) before sending pull requests. Bug +reports, docs fixes, tests, benchmarks, and runtime improvements are welcome. ## License diff --git a/compiler/README.md b/compiler/README.md index 0205707eea..1f4a6b8d2f 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -177,7 +177,7 @@ message User [id=101] { ... } // Registered with type ID 101 message User [id=101, deprecated=true] { ... } // Multiple options ``` -Types without `[id=...]` use namespace-based registration: +Types without `[id=...]` use name-based registration: ```fdl message Config { ... } // Registered as "package.Config" @@ -238,7 +238,7 @@ option polymorphism = true; option enable_auto_type_id = true; ``` -`enable_auto_type_id` defaults to `true`. Set it to `false` to keep namespace-based registration +`enable_auto_type_id` defaults to `true`. Set it to `false` to keep name-based registration for types that omit explicit IDs. **Message/Enum options:** @@ -372,7 +372,7 @@ Generates structs with: - `#[derive(ForyStruct)]`, `#[derive(ForyEnum)]`, and `#[derive(ForyUnion)]` macros - `#[fory(...)]` field attributes -- a registration helper for namespace-based registration +- a registration helper for name-based registration ```rust #[derive(ForyStruct, Debug, Clone, PartialEq, Default)] diff --git a/compiler/extension/fory_options.proto b/compiler/extension/fory_options.proto index 9dc89abcc3..1fbf72e26d 100644 --- a/compiler/extension/fory_options.proto +++ b/compiler/extension/fory_options.proto @@ -108,7 +108,7 @@ message ForyMessageOptions { // Unique type ID for cross-language registration. // Used for efficient type lookup during deserialization. // Must be a positive integer unique within the schema. - // If not specified, namespace-based registration is used. + // If not specified, name-based registration is used. optional int32 id = 1; // Enable schema evolution for this message. diff --git a/compiler/fory_compiler/generators/go.py b/compiler/fory_compiler/generators/go.py index 3673b99d3b..b25e63c799 100644 --- a/compiler/fory_compiler/generators/go.py +++ b/compiler/fory_compiler/generators/go.py @@ -1570,7 +1570,7 @@ def generate_enum_registration( # Use FDL package for namespace (consistent across languages) ns = self.schema.package or "default" lines.append( - f'\tif err := f.RegisterNamedEnum({code_name}(0), "{ns}.{type_name}"); err != nil {{' + f'\tif err := f.RegisterEnumByName({code_name}(0), "{ns}.{type_name}"); err != nil {{' ) lines.append("\t\treturn err") lines.append("\t}") @@ -1613,7 +1613,7 @@ def generate_message_registration( # Use FDL package for namespace (consistent across languages) ns = self.schema.package or "default" lines.append( - f'\tif err := f.RegisterNamedStruct({code_name}{{}}, "{ns}.{type_name}"); err != nil {{' + f'\tif err := f.RegisterStructByName({code_name}{{}}, "{ns}.{type_name}"); err != nil {{' ) lines.append("\t\treturn err") lines.append("\t}") @@ -1647,7 +1647,7 @@ def generate_union_registration( else: ns = self.schema.package or "default" lines.append( - f'\tif err := f.RegisterNamedUnion({code_name}{{}}, "{ns}.{type_name}", {serializer_expr}); err != nil {{' + f'\tif err := f.RegisterUnionByName({code_name}{{}}, "{ns}.{type_name}", {serializer_expr}); err != nil {{' ) lines.append("\t\treturn err") lines.append("\t}") diff --git a/compiler/fory_compiler/generators/rust.py b/compiler/fory_compiler/generators/rust.py index 376d3c02b2..167c722efe 100644 --- a/compiler/fory_compiler/generators/rust.py +++ b/compiler/fory_compiler/generators/rust.py @@ -1009,7 +1009,7 @@ def generate_enum_registration( else: ns = self.package or "default" lines.append( - f' fory.register_by_namespace::<{type_name}>("{ns}", "{reg_name}")?;' + f' fory.register_by_name::<{type_name}>("{ns}", "{reg_name}")?;' ) def generate_message_registration( @@ -1045,7 +1045,7 @@ def generate_message_registration( else: ns = self.package or "default" lines.append( - f' fory.register_by_namespace::<{type_name}>("{ns}", "{reg_name}")?;' + f' fory.register_by_name::<{type_name}>("{ns}", "{reg_name}")?;' ) def generate_union_registration( @@ -1063,5 +1063,5 @@ def generate_union_registration( else: ns = self.package or "default" lines.append( - f' fory.register_union_by_namespace::<{type_name}>("{ns}", "{reg_name}")?;' + f' fory.register_union_by_name::<{type_name}>("{ns}", "{reg_name}")?;' ) diff --git a/compiler/fory_compiler/tests/test_nested_types.py b/compiler/fory_compiler/tests/test_nested_types.py index 4dafcb5b72..55d06515be 100644 --- a/compiler/fory_compiler/tests/test_nested_types.py +++ b/compiler/fory_compiler/tests/test_nested_types.py @@ -415,7 +415,7 @@ def test_nested_message_field_type_qualified(self): assert "pub bar: foo::Bar," in content def test_nested_message_registration_uses_pascal_struct(self): - """register_by_namespace must reference the PascalCase struct name.""" + """register_by_name must reference the PascalCase struct name.""" source = dedent(""" message foo { message Bar { @@ -425,9 +425,9 @@ def test_nested_message_registration_uses_pascal_struct(self): } """) content = self._generate_rust(source) - assert "register_by_namespace::" in content - assert 'register_by_namespace::("default", "foo")' in content - assert "register_by_namespace::" in content + assert "register_by_name::" in content + assert 'register_by_name::("default", "foo")' in content + assert "register_by_name::" in content def test_pascal_case_names_unchanged(self): """Messages already in PascalCase must not be renamed.""" diff --git a/docs/compiler/generated-code.md b/docs/compiler/generated-code.md index 6fcdc5f373..73746b877f 100644 --- a/docs/compiler/generated-code.md +++ b/docs/compiler/generated-code.md @@ -412,8 +412,8 @@ fory.register::(2862577837)?; If `option enable_auto_type_id = false;` is set: ```rust -fory.register_by_namespace::("myapp.models", "Config")?; -fory.register_union_by_namespace::("myapp.models", "Holder")?; +fory.register_by_name::("myapp.models", "Config")?; +fory.register_union_by_name::("myapp.models", "Holder")?; ``` ### Usage @@ -659,8 +659,8 @@ 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", fory.NewUnionSerializer(...)); err != nil { ... } +if err := f.RegisterStructByName(Config{}, "myapp.models.Config"); err != nil { ... } +if err := f.RegisterUnionByName(Holder{}, "myapp.models.Holder", fory.NewUnionSerializer(...)); err != nil { ... } ``` `go_nested_type_style` controls nested type naming: @@ -1046,7 +1046,7 @@ void main() { - 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. +- If `option enable_auto_type_id = false;` is set, generated registration uses name-based APIs instead of numeric IDs. ### Nested Type Shape diff --git a/docs/compiler/index.md b/docs/compiler/index.md index 707a679fcb..286de0b5cc 100644 --- a/docs/compiler/index.md +++ b/docs/compiler/index.md @@ -88,7 +88,7 @@ Unlike generic IDLs, Fory IDL is designed specifically for Fory serialization: - **Reference Tracking**: First-class support for shared and circular references via `ref` - **Nullable Fields**: Explicit `optional` modifier for nullable types -- **Type Registration**: Built-in support for both numeric IDs and namespace-based registration +- **Type Registration**: Built-in support for both numeric IDs and name-based registration - **Native Code Generation**: Generates idiomatic code with Fory annotations/macros ### Low Integration Overhead diff --git a/docs/compiler/schema-idl.md b/docs/compiler/schema-idl.md index 85249596d0..a008f7740a 100644 --- a/docs/compiler/schema-idl.md +++ b/docs/compiler/schema-idl.md @@ -88,7 +88,7 @@ package com.example.models alias models_v1; - Optional but recommended - Must appear before any type definitions - Only one package declaration per file -- Used for namespace-based type registration +- Used for name-based type registration - Package alias is used for auto-ID hashing **Language Mapping:** diff --git a/docs/guide/go/basic-serialization.md b/docs/guide/go/basic-serialization.md index c0617783b2..8fc7779337 100644 --- a/docs/guide/go/basic-serialization.md +++ b/docs/guide/go/basic-serialization.md @@ -35,7 +35,7 @@ f.RegisterStruct(User{}, 1) f.RegisterStruct(Order{}, 2) // Or register with a name (more flexible, less prone to ID conflicts, but higher serialization cost) -f.RegisterNamedStruct(User{}, "example.User") +f.RegisterStructByName(User{}, "example.User") // Register enum types f.RegisterEnum(Color(0), 3) diff --git a/docs/guide/go/custom-serializers.md b/docs/guide/go/custom-serializers.md index b3db628e27..96849d55d7 100644 --- a/docs/guide/go/custom-serializers.md +++ b/docs/guide/go/custom-serializers.md @@ -242,7 +242,7 @@ f.RegisterExtension(MyType{}, 100, &MySerializer{}) More flexible but more serialization cost, type name included in serialized data: ```go -f.RegisterNamedExtension(MyType{}, "myapp.MyType", &MySerializer{}) +f.RegisterExtensionByName(MyType{}, "myapp.MyType", &MySerializer{}) ``` ## Best Practices diff --git a/docs/guide/go/troubleshooting.md b/docs/guide/go/troubleshooting.md index 529bba8df4..f87ce5fc25 100644 --- a/docs/guide/go/troubleshooting.md +++ b/docs/guide/go/troubleshooting.md @@ -280,7 +280,7 @@ Ensure the same field IDs are used across all languages for corresponding fields ```go // Go -f.RegisterNamedStruct(User{}, "example.User") +f.RegisterStructByName(User{}, "example.User") // Java - must match exactly fory.register(User.class, "example.User"); diff --git a/docs/guide/go/type-registration.md b/docs/guide/go/type-registration.md index 51425ec30d..edf5e5e114 100644 --- a/docs/guide/go/type-registration.md +++ b/docs/guide/go/type-registration.md @@ -58,7 +58,7 @@ Register a struct with a type name string. This is more flexible but has higher ```go f := fory.New() -err := f.RegisterNamedStruct(User{}, "example.User") +err := f.RegisterStructByName(User{}, "example.User") if err != nil { panic(err) } @@ -92,7 +92,7 @@ err := f.RegisterEnum(Status(0), 1) ### Register by Name ```go -err := f.RegisterNamedEnum(Status(0), "example.Status") +err := f.RegisterEnumByName(Status(0), "example.Status") ``` ## Extension Types @@ -106,7 +106,7 @@ f := fory.New() err := f.RegisterExtension(CustomType{}, 1, &CustomSerializer{}) // Or register by name -err = f.RegisterNamedExtension(CustomType{}, "example.Custom", &CustomSerializer{}) +err = f.RegisterExtensionByName(CustomType{}, "example.Custom", &CustomSerializer{}) ``` See [Custom Serializers](custom-serializers.md) for details on implementing the `ExtensionSerializer` interface. @@ -196,7 +196,7 @@ All languages use the same type name: **Go**: ```go -f.RegisterNamedStruct(User{}, "example.User") +f.RegisterStructByName(User{}, "example.User") ``` **Java**: @@ -223,7 +223,7 @@ struct User { } let mut fory = Fory::default(); -fory.register_by_name::("example.User")?; +fory.register_by_name::("example", "User")?; ``` ## Best Practices diff --git a/docs/guide/python/cross-language.md b/docs/guide/python/cross-language.md index 0022958245..ab8f5cab61 100644 --- a/docs/guide/python/cross-language.md +++ b/docs/guide/python/cross-language.md @@ -89,7 +89,7 @@ let mut fory = Fory::builder() .compatible(true) .xlang(true).build(); -fory.register_by_namespace::("example", "Person"); +fory.register_by_name::("example", "Person"); let person: Person = fory.deserialize(&binary_data)?; ``` diff --git a/docs/guide/rust/cross-language.md b/docs/guide/rust/cross-language.md index b95e1741bf..4223276a28 100644 --- a/docs/guide/rust/cross-language.md +++ b/docs/guide/rust/cross-language.md @@ -34,8 +34,8 @@ let mut fory = Fory::builder() // Register types with consistent IDs across languages fory.register::(100); -// Or use namespace-based registration -fory.register_by_namespace::("com.example", "MyStruct"); +// Or use name-based registration +fory.register_by_name::("com.example", "MyStruct"); ``` ## Type Registration for Cross-Language @@ -52,12 +52,12 @@ let mut fory = Fory::builder() fory.register::(100); // Same ID in Java, Python, etc. ``` -### Register by Namespace +### Register by Name For more flexible type naming: ```rust -fory.register_by_namespace::("com.example", "User"); +fory.register_by_name::("com.example", "User"); ``` ## Cross-Language Example diff --git a/docs/guide/rust/type-registration.md b/docs/guide/rust/type-registration.md index d2849ac881..0eedec6476 100644 --- a/docs/guide/rust/type-registration.md +++ b/docs/guide/rust/type-registration.md @@ -47,7 +47,7 @@ let bytes = fory.serialize(&user)?; let decoded: User = fory.deserialize(&bytes)?; ``` -## Register by Namespace +## Register by Name For cross-language compatibility, register with namespace and type name: @@ -56,8 +56,8 @@ let mut fory = Fory::builder() .compatible(true) .xlang(true).build(); -// Register with namespace-based naming -fory.register_by_namespace::("com.example", "MyStruct")?; +// Register with symbolic type identity +fory.register_by_name::("com.example", "MyStruct")?; ``` ## Register Custom Serializer diff --git a/docs/guide/xlang/field-nullability.md b/docs/guide/xlang/field-nullability.md index fc8d3736c9..5b9d645768 100644 --- a/docs/guide/xlang/field-nullability.md +++ b/docs/guide/xlang/field-nullability.md @@ -167,7 +167,7 @@ type Person struct { } fory := forygo.NewFory(forygo.WithXlang(true), forygo.WithCompatible(true)) -fory.RegisterNamedStruct(Person{}, "example.Person") +fory.RegisterStructByName(Person{}, "example.Person") ``` ### C++ diff --git a/docs/guide/xlang/getting-started.md b/docs/guide/xlang/getting-started.md index 7d091e3912..62f279b340 100644 --- a/docs/guide/xlang/getting-started.md +++ b/docs/guide/xlang/getting-started.md @@ -161,7 +161,7 @@ fory.register_type(Person, typename="example.Person") **Go:** ```go -fory.RegisterNamedStruct(Person{}, "example.Person") +fory.RegisterStructByName(Person{}, "example.Person") ``` **Rust:** @@ -177,7 +177,7 @@ struct Person { let mut fory = Fory::builder().xlang(true).compatible(true).build(); fory - .register_by_namespace::("example", "Person") + .register_by_name::("example", "Person") .expect("register Person"); ``` diff --git a/docs/guide/xlang/serialization.md b/docs/guide/xlang/serialization.md index aca6d3c250..68be9c5ef6 100644 --- a/docs/guide/xlang/serialization.md +++ b/docs/guide/xlang/serialization.md @@ -299,10 +299,10 @@ func main() { F12 []int16 } serializer := forygo.NewFory(forygo.WithXlang(true), forygo.WithCompatible(true)) - if err := serializer.RegisterNamedStruct(SomeClass1{}, "example.SomeClass1"); err != nil { + if err := serializer.RegisterStructByName(SomeClass1{}, "example.SomeClass1"); err != nil { panic(err) } - if err := serializer.RegisterNamedStruct(SomeClass2{}, "example.SomeClass2"); err != nil { + if err := serializer.RegisterStructByName(SomeClass2{}, "example.SomeClass2"); err != nil { panic(err) } obj1 := &SomeClass1{F1: true, F2: map[int8]int32{-1: 2}} @@ -410,10 +410,10 @@ fn complex_struct() { let mut fory = Fory::builder().xlang(true).compatible(true).build(); fory - .register_by_namespace::("example", "foo2") + .register_by_name::("example", "foo2") .expect("register Animal"); fory - .register_by_namespace::("example", "foo") + .register_by_name::("example", "foo") .expect("register Person"); let bin = fory.serialize(&person).expect("serialize success"); let obj: Person = fory.deserialize(&bin).expect("deserialize success"); diff --git a/go/fory/README.md b/go/fory/README.md index 69aa9dc98f..8a7ebafe55 100644 --- a/go/fory/README.md +++ b/go/fory/README.md @@ -103,7 +103,7 @@ Fory Go enables seamless data exchange with Java, Python, C++, Rust, and JavaScr ```go // Go f := fory.New() -f.RegisterNamedStruct(User{}, "example.User") +f.RegisterStructByName(User{}, "example.User") data, _ := f.Serialize(&User{ID: 1, Name: "Alice"}) // 'data' can be deserialized by Java, Python, etc. ``` diff --git a/go/fory/doc.go b/go/fory/doc.go index 84bd9dff32..055483ab1a 100644 --- a/go/fory/doc.go +++ b/go/fory/doc.go @@ -105,7 +105,7 @@ Types must be registered before serialization. Registration can be done by ID f.RegisterStruct(Order{}, 2) // Register by name (more flexible) - f.RegisterNamedStruct(User{}, "example.User") + f.RegisterStructByName(User{}, "example.User") // Register enum types f.RegisterEnum(Status(0), 3) diff --git a/go/fory/enum_test.go b/go/fory/enum_test.go index 3eebfbcd7c..90fa57ddf0 100644 --- a/go/fory/enum_test.go +++ b/go/fory/enum_test.go @@ -76,7 +76,7 @@ func TestEnumReadRejectsMismatchedRegisteredTypeInfo(t *testing.T) { func TestNamedEnumReadConsumesNamedTypeInfo(t *testing.T) { f := NewFory(WithXlang(true), WithCompatible(false)) - require.NoError(t, f.RegisterNamedEnum(namedAuditEnum(0), "example.NamedAuditEnum")) + require.NoError(t, f.RegisterEnumByName(namedAuditEnum(0), "example.NamedAuditEnum")) enumType := reflect.TypeOf(namedAuditEnum(0)) serializer, err := f.typeResolver.getSerializerByType(enumType, false) require.NoError(t, err) diff --git a/go/fory/fory.go b/go/fory/fory.go index c82f1e2625..6b9c48eacc 100644 --- a/go/fory/fory.go +++ b/go/fory/fory.go @@ -287,14 +287,14 @@ func (f *Fory) RegisterUnion(type_ any, typeID uint32, serializer Serializer) er return f.typeResolver.RegisterUnion(t, typeID, serializer) } -// RegisterNamedUnion registers a union type with a namespace + type name for cross-language serialization. +// RegisterUnionByName registers a union type by namespace + type name for cross-language serialization. // type_ can be either a reflect.Type or an instance of the union type. // serializer must implement union payload encoding/decoding. // //go:noinline -func (f *Fory) RegisterNamedUnion(type_ any, typeName string, serializer Serializer) error { +func (f *Fory) RegisterUnionByName(type_ any, typeName string, serializer Serializer) error { if serializer == nil { - return fmt.Errorf("RegisterNamedUnion requires a non-nil serializer") + return fmt.Errorf("RegisterUnionByName requires a non-nil serializer") } var t reflect.Type if rt, ok := type_.(reflect.Type); ok { @@ -306,7 +306,7 @@ func (f *Fory) RegisterNamedUnion(type_ any, typeName string, serializer Seriali } } if t.Kind() != reflect.Struct { - return fmt.Errorf("RegisterNamedUnion only supports struct types; got: %v", t.Kind()) + return fmt.Errorf("RegisterUnionByName only supports struct types; got: %v", t.Kind()) } namespace := "" name := typeName @@ -314,16 +314,16 @@ func (f *Fory) RegisterNamedUnion(type_ any, typeName string, serializer Seriali namespace = typeName[:lastDot] name = typeName[lastDot+1:] } - return f.typeResolver.RegisterNamedUnion(t, namespace, name, serializer) + return f.typeResolver.RegisterUnionByName(t, namespace, name, serializer) } -// RegisterNamedStruct registers a named struct type for cross-language serialization -// type_ can be either a reflect.Type or an instance of the type -// typeName can include a namespace prefix separated by "." (e.g., "example.Foo") -// Note: For enum types, use RegisterNamedEnum instead. +// RegisterStructByName registers a struct type by namespace + type name for cross-language serialization. +// type_ can be either a reflect.Type or an instance of the type. +// typeName can include a namespace prefix separated by "." (e.g., "example.Foo"). +// Note: For enum types, use RegisterEnumByName instead. // //go:noinline -func (f *Fory) RegisterNamedStruct(type_ any, typeName string) error { +func (f *Fory) RegisterStructByName(type_ any, typeName string) error { var t reflect.Type if rt, ok := type_.(reflect.Type); ok { t = rt @@ -334,7 +334,7 @@ func (f *Fory) RegisterNamedStruct(type_ any, typeName string) error { } } if t.Kind() != reflect.Struct { - return fmt.Errorf("RegisterNamedStruct only supports struct types; for enum types use RegisterNamedEnum. Got: %v", t.Kind()) + return fmt.Errorf("RegisterStructByName only supports struct types; for enum types use RegisterEnumByName. Got: %v", t.Kind()) } // Split typeName by last "." to extract namespace and type name namespace := "" @@ -343,7 +343,7 @@ func (f *Fory) RegisterNamedStruct(type_ any, typeName string) error { namespace = typeName[:lastDot] name = typeName[lastDot+1:] } - return f.typeResolver.RegisterNamedStruct(t, 0, namespace, name) + return f.typeResolver.RegisterStructByName(t, namespace, name) } // RegisterEnum registers an enum type with a numeric ID for cross-language serialization. @@ -379,13 +379,13 @@ func (f *Fory) RegisterEnum(type_ any, typeID uint32) error { return f.typeResolver.RegisterEnum(t, typeID) } -// RegisterNamedEnum registers an enum type with a name for cross-language serialization. +// RegisterEnumByName registers an enum type by namespace + type name for cross-language serialization. // In Go, enums are typically defined as int-based types (e.g., type Color int32). -// type_ can be either a reflect.Type or an instance of the enum type -// typeName can include a namespace prefix separated by "." (e.g., "example.Color") +// type_ can be either a reflect.Type or an instance of the enum type. +// typeName can include a namespace prefix separated by "." (e.g., "example.Color"). // //go:noinline -func (f *Fory) RegisterNamedEnum(type_ any, typeName string) error { +func (f *Fory) RegisterEnumByName(type_ any, typeName string) error { var t reflect.Type if rt, ok := type_.(reflect.Type); ok { t = rt @@ -402,7 +402,7 @@ func (f *Fory) RegisterNamedEnum(type_ any, typeName string) error { reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: // OK default: - return fmt.Errorf("RegisterNamedEnum only supports numeric types (Go enums); got: %v", t.Kind()) + return fmt.Errorf("RegisterEnumByName only supports numeric types (Go enums); got: %v", t.Kind()) } // Split typeName by last "." to extract namespace and type name @@ -412,7 +412,7 @@ func (f *Fory) RegisterNamedEnum(type_ any, typeName string) error { namespace = typeName[:lastDot] name = typeName[lastDot+1:] } - return f.typeResolver.RegisterNamedEnum(t, namespace, name) + return f.typeResolver.RegisterEnumByName(t, namespace, name) } // RegisterExtension registers a type as an extension type with a numeric ID. @@ -436,7 +436,7 @@ func (f *Fory) RegisterExtension(type_ any, typeID uint32, serializer ExtensionS return f.typeResolver.RegisterExtension(t, typeID, serializer) } -// RegisterNamedExtension registers a type as an extension type (NAMED_EXT) for cross-language serialization. +// RegisterExtensionByName registers an extension type by namespace + type name for cross-language serialization. // Extension types use a custom serializer provided by the user. // This is used for types with custom serializers in cross-language serialization. // @@ -456,10 +456,10 @@ func (f *Fory) RegisterExtension(type_ any, typeID uint32, serializer ExtensionS // } // // // Register with custom serializer -// f.RegisterNamedExtension(MyExt{}, "my_ext", &MyExtSerializer{}) +// f.RegisterExtensionByName(MyExt{}, "my_ext", &MyExtSerializer{}) // //go:noinline -func (f *Fory) RegisterNamedExtension(type_ any, typeName string, serializer ExtensionSerializer) error { +func (f *Fory) RegisterExtensionByName(type_ any, typeName string, serializer ExtensionSerializer) error { var t reflect.Type if rt, ok := type_.(reflect.Type); ok { t = rt @@ -469,7 +469,7 @@ func (f *Fory) RegisterNamedExtension(type_ any, typeName string, serializer Ext t = t.Elem() } } - return f.typeResolver.RegisterNamedExtension(t, "", typeName, serializer) + return f.typeResolver.RegisterExtensionByName(t, "", typeName, serializer) } // Reset clears internal state for reuse diff --git a/go/fory/fory_compatible_test.go b/go/fory/fory_compatible_test.go index 16ccca9cd2..4a78cd5c32 100644 --- a/go/fory/fory_compatible_test.go +++ b/go/fory/fory_compatible_test.go @@ -226,10 +226,10 @@ func TestCompatibleSerializationScenarios(t *testing.T) { } }(), writerSetup: func(f *Fory) error { - return f.RegisterNamedStruct(ComplexObject2{}, "test.ComplexObject2") + return f.RegisterStructByName(ComplexObject2{}, "test.ComplexObject2") }, readerSetup: func(f *Fory) error { - return f.RegisterNamedStruct(ComplexObject2{}, "test.ComplexObject2") + return f.RegisterStructByName(ComplexObject2{}, "test.ComplexObject2") }, assertFunc: func(t *testing.T, input any, output any) { in := input.(ComplexObject1) @@ -403,10 +403,10 @@ func TestCompatibleSerializationScenarios(t *testing.T) { } }(), writerSetup: func(f *Fory) error { - return f.RegisterNamedStruct(SimpleDataClass{}, "SimpleDataClass") + return f.RegisterStructByName(SimpleDataClass{}, "SimpleDataClass") }, readerSetup: func(f *Fory) error { - return f.RegisterNamedStruct(SimpleDataClass{}, "SimpleDataClass") + return f.RegisterStructByName(SimpleDataClass{}, "SimpleDataClass") }, assertFunc: func(t *testing.T, input any, output any) { in := input.(PointerDataClass) @@ -431,10 +431,10 @@ func TestCompatibleSerializationScenarios(t *testing.T) { } }(), writerSetup: func(f *Fory) error { - return f.RegisterNamedStruct(SimpleDataClass{}, "SimpleDataClass") + return f.RegisterStructByName(SimpleDataClass{}, "SimpleDataClass") }, readerSetup: func(f *Fory) error { - return f.RegisterNamedStruct(InconsistentDataClass{}, "SimpleDataClass") + return f.RegisterStructByName(InconsistentDataClass{}, "SimpleDataClass") }, assertFunc: func(t *testing.T, input any, output any) { in := input.(PointerDataClass) @@ -480,13 +480,13 @@ func TestCompatibleSerializationScenarios(t *testing.T) { Inner: SimpleDataClass{Name: "inner", Age: 18, Active: true}, }, writerSetup: func(f *Fory) error { - if err := f.RegisterNamedStruct(SimpleDataClass{}, "SimpleDataClass"); err != nil { + if err := f.RegisterStructByName(SimpleDataClass{}, "SimpleDataClass"); err != nil { return err } return nil }, readerSetup: func(f *Fory) error { - if err := f.RegisterNamedStruct(SimpleDataClass{}, "SimpleDataClass"); err != nil { + if err := f.RegisterStructByName(SimpleDataClass{}, "SimpleDataClass"); err != nil { return err } return nil @@ -508,13 +508,13 @@ func TestCompatibleSerializationScenarios(t *testing.T) { Inner: SimpleDataClass{Name: "inner", Age: 18, Active: true}, }, writerSetup: func(f *Fory) error { - if err := f.RegisterNamedStruct(SimpleDataClass{}, "SimpleDataClass"); err != nil { + if err := f.RegisterStructByName(SimpleDataClass{}, "SimpleDataClass"); err != nil { return err } return nil }, readerSetup: func(f *Fory) error { - if err := f.RegisterNamedStruct(InconsistentDataClass{}, "SimpleDataClass"); err != nil { + if err := f.RegisterStructByName(InconsistentDataClass{}, "SimpleDataClass"); err != nil { return err } return nil @@ -667,7 +667,7 @@ func runCompatibilityCase(t *testing.T, tc compatibilityCase) { err := tc.writerSetup(writer) assert.NoError(t, err) } - err := writer.RegisterNamedStruct(tc.writeType, tc.tag) + err := writer.RegisterStructByName(tc.writeType, tc.tag) assert.NoError(t, err) marshalInput := tc.input @@ -687,7 +687,7 @@ func runCompatibilityCase(t *testing.T, tc compatibilityCase) { err = tc.readerSetup(reader) assert.NoError(t, err) } - err = reader.RegisterNamedStruct(tc.readType, tc.tag) + err = reader.RegisterStructByName(tc.readType, tc.tag) assert.NoError(t, err) target := reflect.New(reflect.TypeOf(tc.readType)) diff --git a/go/fory/fory_test.go b/go/fory/fory_test.go index f50bde7914..e1e5f938dc 100644 --- a/go/fory/fory_test.go +++ b/go/fory/fory_test.go @@ -273,7 +273,7 @@ func TestSerializeStructSimple(t *testing.T) { type A struct { F1 []string } - require.Nil(t, fory.RegisterNamedStruct(A{}, "example.A")) + require.Nil(t, fory.RegisterStructByName(A{}, "example.A")) serde(t, fory, A{}) serde(t, fory, &A{}) serde(t, fory, A{F1: []string{"str1", "", "str2"}}) @@ -283,7 +283,7 @@ func TestSerializeStructSimple(t *testing.T) { F1 []string F2 map[string]int32 } - require.Nil(t, fory.RegisterNamedStruct(SimpleB{}, "example.SimpleB")) + require.Nil(t, fory.RegisterStructByName(SimpleB{}, "example.SimpleB")) serde(t, fory, SimpleB{}) serde(t, fory, SimpleB{ F1: []string{"str1", "", "str2"}, @@ -300,7 +300,7 @@ func TestRegisterById(t *testing.T) { type simple struct { Field string } - require.NoError(t, fory.RegisterNamedStruct(simple{}, "simple")) + require.NoError(t, fory.RegisterStructByName(simple{}, "simple")) serde(t, fory, simple{Field: "value"}) } @@ -350,7 +350,7 @@ func newFoo() Foo { func TestSerializeStruct(t *testing.T) { for _, referenceTracking := range []bool{false, true} { fory := NewFory(WithXlang(true), WithCompatible(false), WithRefTracking(referenceTracking)) - require.Nil(t, fory.RegisterNamedStruct(Bar{}, "example.Bar")) + require.Nil(t, fory.RegisterStructByName(Bar{}, "example.Bar")) serde(t, fory, &Bar{}) bar := Bar{F1: 1, F2: "str"} serde(t, fory, bar) @@ -360,14 +360,14 @@ func TestSerializeStruct(t *testing.T) { F1 Bar F2 any } - require.Nil(t, fory.RegisterNamedStruct(A{}, "example.A")) + require.Nil(t, fory.RegisterStructByName(A{}, "example.A")) serde(t, fory, A{}) serde(t, fory, &A{}) // Use int64 for any fields since xlang deserializes integers to int64 serde(t, fory, A{F1: Bar{F1: 1, F2: "str"}, F2: int64(-1)}) serde(t, fory, &A{F1: Bar{F1: 1, F2: "str"}, F2: int64(-1)}) - require.Nil(t, fory.RegisterNamedStruct(Foo{}, "example.Foo")) + require.Nil(t, fory.RegisterStructByName(Foo{}, "example.Foo")) foo := newFoo() serde(t, fory, foo) serde(t, fory, &foo) @@ -380,7 +380,7 @@ func TestSerializeCircularReference(t *testing.T) { type A struct { A1 *A } - require.Nil(t, fory.RegisterNamedStruct(A{}, "example.A")) + require.Nil(t, fory.RegisterStructByName(A{}, "example.A")) // If use `A{}` instead of `&A{}` and pass `a` instead of `&a`, there will be serialization data duplication // and can't be deserialized by other languages too. // TODO(chaokunyang) If pass by value(have a copy) and there are some inner value reference, return a readable @@ -400,7 +400,7 @@ func TestSerializeCircularReference(t *testing.T) { F2 *CircularRefB F3 *CircularRefB } - require.Nil(t, fory.RegisterNamedStruct(CircularRefB{}, "example.CircularRefB")) + require.Nil(t, fory.RegisterStructByName(CircularRefB{}, "example.CircularRefB")) b := &CircularRefB{F1: "str"} b.F2 = b b.F3 = b @@ -428,8 +428,8 @@ func TestSerializeComplexReference(t *testing.T) { F3 *A F4 *B } - require.Nil(t, fory.RegisterNamedStruct(A{}, "example.ComplexRefA")) - require.Nil(t, fory.RegisterNamedStruct(B{}, "example.ComplexRefB")) + require.Nil(t, fory.RegisterStructByName(A{}, "example.ComplexRefA")) + require.Nil(t, fory.RegisterStructByName(B{}, "example.ComplexRefB")) a := &A{F1: "str"} a.F2 = a @@ -563,8 +563,8 @@ func serde(t *testing.T, fory *Fory, value any) { func BenchmarkMarshal(b *testing.B) { fory := NewFory(WithXlang(true), WithCompatible(false), WithRefTracking(true)) - require.Nil(b, fory.RegisterNamedStruct(Foo{}, "example.Foo")) - require.Nil(b, fory.RegisterNamedStruct(Bar{}, "example.Bar")) + require.Nil(b, fory.RegisterStructByName(Foo{}, "example.Foo")) + require.Nil(b, fory.RegisterStructByName(Bar{}, "example.Bar")) value := benchData() for i := 0; i < b.N; i++ { _, err := fory.Marshal(value) @@ -576,8 +576,8 @@ func BenchmarkMarshal(b *testing.B) { func BenchmarkUnmarshal(b *testing.B) { fory := NewFory(WithXlang(true), WithCompatible(false), WithRefTracking(true)) - require.Nil(b, fory.RegisterNamedStruct(Foo{}, "example.Foo")) - require.Nil(b, fory.RegisterNamedStruct(Bar{}, "example.Bar")) + require.Nil(b, fory.RegisterStructByName(Foo{}, "example.Foo")) + require.Nil(b, fory.RegisterStructByName(Bar{}, "example.Bar")) value := benchData() data, err := fory.Marshal(value) if err != nil { @@ -695,10 +695,10 @@ func TestStructWithNestedSlice(t *testing.T) { } fory := NewFory(WithXlang(true), WithCompatible(false), WithRefTracking(true)) - if err := fory.RegisterNamedStruct(Example{}, "Example"); err != nil { + if err := fory.RegisterStructByName(Example{}, "Example"); err != nil { panic(err) } - if err := fory.RegisterNamedStruct(Item{}, "Item"); err != nil { + if err := fory.RegisterStructByName(Item{}, "Item"); err != nil { panic(err) } diff --git a/go/fory/fory_typed_test.go b/go/fory/fory_typed_test.go index 127c05c891..e1263772cd 100644 --- a/go/fory/fory_typed_test.go +++ b/go/fory/fory_typed_test.go @@ -180,7 +180,7 @@ func TestSerializeGenericComplex(t *testing.T) { Name string Value int32 } - err := f.RegisterNamedStruct(TestStruct{}, "example.TestStruct") + err := f.RegisterStructByName(TestStruct{}, "example.TestStruct") require.NoError(t, err) original := TestStruct{Name: "test", Value: 100} @@ -249,7 +249,7 @@ func TestSerializeDeserializeRoundTrip(t *testing.T) { ID int64 Name string } - f.RegisterNamedStruct(CustomStruct{}, "test.CustomStruct") + f.RegisterStructByName(CustomStruct{}, "test.CustomStruct") original := CustomStruct{ID: 123, Name: "test"} data, err := Serialize(f, &original) diff --git a/go/fory/ref_resolver_test.go b/go/fory/ref_resolver_test.go index 173043d182..2e5157c2cd 100644 --- a/go/fory/ref_resolver_test.go +++ b/go/fory/ref_resolver_test.go @@ -150,9 +150,9 @@ func TestRefTrackingLargeCount(t *testing.T) { t.Run(tt.name, func(t *testing.T) { f := New(WithXlang(true), WithCompatible(false), WithRefTracking(true)) - err := f.RegisterNamedStruct(&Inner{}, fmt.Sprintf("RefTest_Inner_%d", tt.count)) + err := f.RegisterStructByName(&Inner{}, fmt.Sprintf("RefTest_Inner_%d", tt.count)) require.NoError(t, err) - err = f.RegisterNamedStruct(&Outer{}, fmt.Sprintf("RefTest_Outer_%d", tt.count)) + err = f.RegisterStructByName(&Outer{}, fmt.Sprintf("RefTest_Outer_%d", tt.count)) require.NoError(t, err) original := make([]Outer, tt.count) diff --git a/go/fory/serializer.go b/go/fory/serializer.go index b79088874f..b4af6a4b76 100644 --- a/go/fory/serializer.go +++ b/go/fory/serializer.go @@ -109,7 +109,7 @@ type Serializer interface { // ExtensionSerializer is a simplified interface for user-implemented extension serializers. // Users implement this interface to provide custom serialization logic for types -// registered via RegisterNamedExtension. +// registered via RegisterExtensionByName. // // Unlike the full Serializer interface, ExtensionSerializer only requires implementing // the core data serialization logic - reference tracking, type info, and protocol @@ -130,7 +130,7 @@ type Serializer interface { // } // // // Register with custom serializer -// f.RegisterNamedExtension(MyExt{}, "my_ext", &MyExtSerializer{}) +// f.RegisterExtensionByName(MyExt{}, "my_ext", &MyExtSerializer{}) type ExtensionSerializer interface { // WriteData serializes the value's data to the buffer. // Only write the data fields - don't write ref flags or type info. diff --git a/go/fory/struct_test.go b/go/fory/struct_test.go index fe97316aaf..c98c2f0b40 100644 --- a/go/fory/struct_test.go +++ b/go/fory/struct_test.go @@ -246,12 +246,12 @@ func TestNumericPointerOptionalInterop(t *testing.T) { t.Run("PointerToOptionalNull", func(t *testing.T) { writer := New(WithXlang(true), WithCompatible(true)) - require.NoError(t, writer.RegisterNamedStruct(NumericPtrStruct{}, "NumericInterop")) + require.NoError(t, writer.RegisterStructByName(NumericPtrStruct{}, "NumericInterop")) data, err := writer.Marshal(&NumericPtrStruct{}) require.NoError(t, err) reader := New(WithXlang(true), WithCompatible(true)) - require.NoError(t, reader.RegisterNamedStruct(NumericOptStruct{}, "NumericInterop")) + require.NoError(t, reader.RegisterStructByName(NumericOptStruct{}, "NumericInterop")) var out NumericOptStruct require.NoError(t, reader.Unmarshal(data, &out)) @@ -271,12 +271,12 @@ func TestNumericPointerOptionalInterop(t *testing.T) { t.Run("PointerToOptionalValue", func(t *testing.T) { writer := New(WithXlang(true), WithCompatible(true)) - require.NoError(t, writer.RegisterNamedStruct(NumericPtrStruct{}, "NumericInterop")) + require.NoError(t, writer.RegisterStructByName(NumericPtrStruct{}, "NumericInterop")) data, err := writer.Marshal(&ptrValues) require.NoError(t, err) reader := New(WithXlang(true), WithCompatible(true)) - require.NoError(t, reader.RegisterNamedStruct(NumericOptStruct{}, "NumericInterop")) + require.NoError(t, reader.RegisterStructByName(NumericOptStruct{}, "NumericInterop")) var out NumericOptStruct require.NoError(t, reader.Unmarshal(data, &out)) @@ -285,12 +285,12 @@ func TestNumericPointerOptionalInterop(t *testing.T) { t.Run("OptionalToPointerNull", func(t *testing.T) { writer := New(WithXlang(true), WithCompatible(true)) - require.NoError(t, writer.RegisterNamedStruct(NumericOptStruct{}, "NumericInterop")) + require.NoError(t, writer.RegisterStructByName(NumericOptStruct{}, "NumericInterop")) data, err := writer.Marshal(&NumericOptStruct{}) require.NoError(t, err) reader := New(WithXlang(true), WithCompatible(true)) - require.NoError(t, reader.RegisterNamedStruct(NumericPtrStruct{}, "NumericInterop")) + require.NoError(t, reader.RegisterStructByName(NumericPtrStruct{}, "NumericInterop")) var out NumericPtrStruct require.NoError(t, reader.Unmarshal(data, &out)) @@ -310,12 +310,12 @@ func TestNumericPointerOptionalInterop(t *testing.T) { t.Run("OptionalToPointerValue", func(t *testing.T) { writer := New(WithXlang(true), WithCompatible(true)) - require.NoError(t, writer.RegisterNamedStruct(NumericOptStruct{}, "NumericInterop")) + require.NoError(t, writer.RegisterStructByName(NumericOptStruct{}, "NumericInterop")) data, err := writer.Marshal(&optValues) require.NoError(t, err) reader := New(WithXlang(true), WithCompatible(true)) - require.NoError(t, reader.RegisterNamedStruct(NumericPtrStruct{}, "NumericInterop")) + require.NoError(t, reader.RegisterStructByName(NumericPtrStruct{}, "NumericInterop")) var out NumericPtrStruct require.NoError(t, reader.Unmarshal(data, &out)) diff --git a/go/fory/tests/generator_xlang_test.go b/go/fory/tests/generator_xlang_test.go index 3584f04647..473d4d77eb 100644 --- a/go/fory/tests/generator_xlang_test.go +++ b/go/fory/tests/generator_xlang_test.go @@ -66,7 +66,7 @@ func TestValidationDemoXlang(t *testing.T) { // Reflect mode (register with full name) foryForReflect := forygo.NewFory(forygo.WithRefTracking(true)) - err := foryForReflect.RegisterNamedStruct(ReflectStruct{}, expectedTypeTag) + err := foryForReflect.RegisterStructByName(ReflectStruct{}, expectedTypeTag) require.NoError(t, err, "Should be able to register ReflectStruct with full name") // Serialization test @@ -131,7 +131,7 @@ func TestSliceDemoXlang(t *testing.T) { // Reflect mode - enable reference tracking foryForReflect := forygo.NewFory(forygo.WithRefTracking(true)) - err := foryForReflect.RegisterNamedStruct(ReflectSliceStruct{}, expectedTypeTag) + err := foryForReflect.RegisterStructByName(ReflectSliceStruct{}, expectedTypeTag) require.NoError(t, err, "Should be able to register ReflectSliceStruct with full name") // Serialization test @@ -205,7 +205,7 @@ func TestDynamicSliceDemoXlang(t *testing.T) { // Reflect mode - enable reference tracking foryForReflect := forygo.NewFory(forygo.WithRefTracking(true)) - err := foryForReflect.RegisterNamedStruct(ReflectDynamicStruct{}, expectedTypeTag) + err := foryForReflect.RegisterStructByName(ReflectDynamicStruct{}, expectedTypeTag) require.NoError(t, err, "Should be able to register ReflectDynamicStruct with full name") // Serialization test diff --git a/go/fory/tests/xlang/xlang_test_main.go b/go/fory/tests/xlang/xlang_test_main.go index c6342a9e6b..5dfba1f1a3 100644 --- a/go/fory/tests/xlang/xlang_test_main.go +++ b/go/fory/tests/xlang/xlang_test_main.go @@ -770,9 +770,9 @@ func testNamedSimpleStruct() { f := fory.New(fory.WithXlang(true), fory.WithCompatible(true)) // Use namespace "demo" to match Java's fory.register(Color.class, "demo", "color"), etc. - f.RegisterNamedEnum(Color(0), "demo.color") - f.RegisterNamedStruct(Item{}, "demo.item") - f.RegisterNamedStruct(SimpleStruct{}, "demo.simple_struct") + f.RegisterEnumByName(Color(0), "demo.color") + f.RegisterStructByName(Item{}, "demo.item") + f.RegisterStructByName(SimpleStruct{}, "demo.simple_struct") var obj SimpleStruct if err := f.Deserialize(data, &obj); err != nil { @@ -791,8 +791,8 @@ func testStructEvolvingOverride() { data := readFile(dataFile) f := fory.New(fory.WithXlang(true), fory.WithCompatible(true)) - f.RegisterNamedStruct(EvolvingOverrideStruct{}, "test.evolving_yes") - f.RegisterNamedStruct(FixedOverrideStruct{}, "test.evolving_off") + f.RegisterStructByName(EvolvingOverrideStruct{}, "test.evolving_yes") + f.RegisterStructByName(FixedOverrideStruct{}, "test.evolving_off") buffer := fory.NewByteBuffer(data) var evolving EvolvingOverrideStruct @@ -1174,8 +1174,8 @@ func testSkipNameCustom() { data := readFile(dataFile) f := fory.New(fory.WithXlang(true), fory.WithCompatible(true)) - f.RegisterNamedExtension(MyExt{}, "my_ext", &MyExtSerializer{}) - f.RegisterNamedStruct(EmptyWrapper{}, "my_wrapper") + f.RegisterExtensionByName(MyExt{}, "my_ext", &MyExtSerializer{}) + f.RegisterStructByName(EmptyWrapper{}, "my_wrapper") var obj EmptyWrapper if err := f.Deserialize(data, &obj); err != nil { @@ -1197,10 +1197,10 @@ func testConsistentNamed() { // Java uses SCHEMA_CONSISTENT mode which doesn't enable metaShare // So Go should NOT expect meta offset field f := fory.New(fory.WithXlang(true), fory.WithCompatible(false)) - f.RegisterNamedEnum(Color(0), "color") - f.RegisterNamedStruct(MyStruct{}, "my_struct") + f.RegisterEnumByName(Color(0), "color") + f.RegisterStructByName(MyStruct{}, "my_struct") // MyExt uses an extension serializer in Java (MyExtSerializer), so register as extension type - f.RegisterNamedExtension(MyExt{}, "my_ext", &MyExtSerializer{}) + f.RegisterExtensionByName(MyExt{}, "my_ext", &MyExtSerializer{}) buf := fory.NewByteBuffer(data) values := make([]any, 9) diff --git a/go/fory/threadsafe/fory.go b/go/fory/threadsafe/fory.go index aea6ca9460..02bf70284b 100644 --- a/go/fory/threadsafe/fory.go +++ b/go/fory/threadsafe/fory.go @@ -90,11 +90,11 @@ func (f *Fory) Deserialize(data []byte, v any) error { return inner.Deserialize(data, v) } -// RegisterNamedStruct registers a named struct type for cross-language serialization -func (f *Fory) RegisterNamedStruct(type_ any, typeName string) error { +// RegisterStructByName registers a struct type by namespace + type name for cross-language serialization. +func (f *Fory) RegisterStructByName(type_ any, typeName string) error { inner := f.acquire() defer f.release(inner) - return inner.RegisterNamedStruct(type_, typeName) + return inner.RegisterStructByName(type_, typeName) } // ============================================================================ diff --git a/go/fory/type_def_test.go b/go/fory/type_def_test.go index ef1627d7bd..09db8d4bee 100644 --- a/go/fory/type_def_test.go +++ b/go/fory/type_def_test.go @@ -111,7 +111,7 @@ func TestTypeDefEncodingDecoding(t *testing.T) { t.Run(tt.name, func(t *testing.T) { fory := NewFory(WithRefTracking(false)) - if err := fory.RegisterNamedStruct(tt.testStruct, tt.tagName); err != nil { + if err := fory.RegisterStructByName(tt.testStruct, tt.tagName); err != nil { t.Fatalf("Failed to register tag type: %v", err) } @@ -132,7 +132,7 @@ func TestTypeDefEncodingDecoding(t *testing.T) { // basic checks assert.True(t, decodedTypeDef.typeId == originalTypeDef.typeId || decodedTypeDef.typeId == -originalTypeDef.typeId, "TypeId mismatch") - assert.Equal(t, originalTypeDef.registerByName, decodedTypeDef.registerByName, "RegisterNamedStruct mismatch") + assert.Equal(t, originalTypeDef.registerByName, decodedTypeDef.registerByName, "RegisterStructByName mismatch") assert.Equal(t, originalTypeDef.compressed, decodedTypeDef.compressed, "Compressed flag mismatch") assert.Equal(t, len(originalTypeDef.fieldDefs), len(decodedTypeDef.fieldDefs), "Field count mismatch") @@ -224,7 +224,7 @@ func TestTypeDefNullableFields(t *testing.T) { fory := NewFory(WithRefTracking(false)) // Register the type - if err := fory.RegisterNamedStruct(Item1{}, "test.Item1"); err != nil { + if err := fory.RegisterStructByName(Item1{}, "test.Item1"); err != nil { t.Fatalf("Failed to register type: %v", err) } @@ -460,7 +460,7 @@ func TestTypeDefRejectsCompressedMetadata(t *testing.T) { func TestReadSharedTypeMetaCapsParsedTypeDefCache(t *testing.T) { fory := NewFory(WithCompatible(true)) - require.NoError(t, fory.RegisterNamedStruct(SimpleStruct{}, "example.SimpleStruct")) + require.NoError(t, fory.RegisterStructByName(SimpleStruct{}, "example.SimpleStruct")) typeDef, err := buildTypeDef(fory, reflect.ValueOf(SimpleStruct{})) require.NoError(t, err) require.NotEmpty(t, typeDef.encoded) @@ -486,7 +486,7 @@ func TestReadSharedTypeMetaCapsParsedTypeDefCache(t *testing.T) { func TestDecodeTypeDefFallbackNamedTypeCacheRespectsCap(t *testing.T) { fory := NewFory(WithCompatible(true)) - require.NoError(t, fory.RegisterNamedStruct(SimpleStruct{}, "example.SimpleStruct")) + require.NoError(t, fory.RegisterStructByName(SimpleStruct{}, "example.SimpleStruct")) typeDef, err := buildTypeDef(fory, reflect.ValueOf(SimpleStruct{})) require.NoError(t, err) require.NotNil(t, typeDef.nsName) diff --git a/go/fory/type_resolver.go b/go/fory/type_resolver.go index 1090871700..cd651aa6e1 100644 --- a/go/fory/type_resolver.go +++ b/go/fory/type_resolver.go @@ -680,8 +680,8 @@ func (r *TypeResolver) RegisterEnum(type_ reflect.Type, userTypeID uint32) error return nil } -// RegisterNamedEnum registers an enum type (numeric type in Go) with a namespace and type name -func (r *TypeResolver) RegisterNamedEnum(type_ reflect.Type, namespace, typeName string) error { +// RegisterEnumByName registers an enum type by namespace and type name. +func (r *TypeResolver) RegisterEnumByName(type_ reflect.Type, namespace, typeName string) error { // Check if already registered if prev, ok := r.typeToSerializers[type_]; ok { return fmt.Errorf("type %s already has a serializer %s registered", type_, prev) @@ -693,7 +693,7 @@ func (r *TypeResolver) RegisterNamedEnum(type_ reflect.Type, namespace, typeName reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: // OK default: - return fmt.Errorf("RegisterNamedEnum only supports numeric types; got: %v", type_.Kind()) + return fmt.Errorf("RegisterEnumByName only supports numeric types; got: %v", type_.Kind()) } // Parse namespace from typeName if not provided @@ -730,19 +730,11 @@ func (r *TypeResolver) RegisterNamedEnum(type_ reflect.Type, namespace, typeName return nil } -func (r *TypeResolver) RegisterNamedStruct( - type_ reflect.Type, - typeId uint32, - namespace string, - typeName string, -) error { +// RegisterStructByName registers a struct type by namespace and type name. +func (r *TypeResolver) RegisterStructByName(type_ reflect.Type, namespace, typeName string) error { if prev, ok := r.typeToSerializers[type_]; ok { return fmt.Errorf("type %s already has a serializer %s registered", type_, prev) } - registerById := (typeId != 0) - if registerById && typeName != "" { - return fmt.Errorf("typename %s and typeId %d cannot be both register", typeName, typeId) - } if namespace == "" { if idx := strings.LastIndex(typeName, "."); idx != -1 { namespace = typeName[:idx] @@ -752,9 +744,6 @@ func (r *TypeResolver) RegisterNamedStruct( if typeName == "" && namespace != "" { return fmt.Errorf("typeName cannot be empty if namespace is provided") } - if typeId > 0 && typeId > maxUserTypeID { - return fmt.Errorf("typeId must be in range [0, 0xfffffffe], got %d", typeId) - } var tag string if namespace == "" { tag = typeName @@ -776,47 +765,36 @@ func (r *TypeResolver) RegisterNamedStruct( r.typeTagToSerializers[tag] = ptrSerializer r.typeToTypeInfo[ptrType] = "*@" + tag r.typeInfoToType["*@"+tag] = ptrType - var internalTypeID TypeId + internalTypeID := r.structTypeID(type_, true) userTypeID := invalidUserTypeID - if typeId == 0 { - internalTypeID = r.structTypeID(type_, true) - } else { - internalTypeID = r.structTypeID(type_, false) - userTypeID = typeId - } - if registerById { - if info, ok := r.userTypeIdToTypeInfo[userTypeID]; ok { - return fmt.Errorf("type %s with id %d has been registered", info.Type, typeId) - } - } - // For named structs, directly register both their value and pointer types with same typeId + // For structs registered by name, directly register both their value and pointer types. _, err := r.registerType(type_, uint32(internalTypeID), userTypeID, namespace, typeName, nil, false) if err != nil { - return fmt.Errorf("failed to register named structs: %w", err) + return fmt.Errorf("failed to register struct by name: %w", err) } _, err = r.registerType(ptrType, uint32(internalTypeID), userTypeID, namespace, typeName, nil, false) if err != nil { - return fmt.Errorf("failed to register named structs: %w", err) + return fmt.Errorf("failed to register struct by name: %w", err) } return nil } -// RegisterNamedUnion registers a union type with a namespace and type name. +// RegisterUnionByName registers a union type by namespace and type name. // Union types always use NAMED_UNION and follow the same meta-share rules as other named types. -func (r *TypeResolver) RegisterNamedUnion( +func (r *TypeResolver) RegisterUnionByName( type_ reflect.Type, namespace string, typeName string, serializer Serializer, ) error { if serializer == nil { - return fmt.Errorf("RegisterNamedUnion requires a non-nil serializer") + return fmt.Errorf("RegisterUnionByName requires a non-nil serializer") } if prev, ok := r.typeToSerializers[type_]; ok { return fmt.Errorf("type %s already has a serializer %s registered", type_, prev) } if type_.Kind() != reflect.Struct { - return fmt.Errorf("RegisterNamedUnion only supports struct types; got: %v", type_.Kind()) + return fmt.Errorf("RegisterUnionByName only supports struct types; got: %v", type_.Kind()) } if namespace == "" { if idx := strings.LastIndex(typeName, "."); idx != -1 { @@ -861,10 +839,10 @@ func (r *TypeResolver) RegisterExt(extId int16, type_ reflect.Type) error { panic("not supported") } -// RegisterNamedExtension registers a type as an extension type (NAMED_EXT). +// RegisterExtensionByName registers an extension type by namespace and type name. // Extension types use a user-provided serializer for custom serialization logic. // This is used for types with custom serializers in cross-language serialization. -func (r *TypeResolver) RegisterNamedExtension( +func (r *TypeResolver) RegisterExtensionByName( type_ reflect.Type, namespace string, typeName string, @@ -1115,7 +1093,7 @@ func (r *TypeResolver) getTypeInfo(value reflect.Value, create bool) (*TypeInfo, // First register the value type elemPkgPath := elemType.PkgPath() elemTypeName := elemType.Name() - if err := r.RegisterNamedStruct(elemType, 0, elemPkgPath, elemTypeName); err != nil { + if err := r.RegisterStructByName(elemType, elemPkgPath, elemTypeName); err != nil { // Might already be registered, that's okay _ = err } @@ -1882,7 +1860,7 @@ func (r *TypeResolver) createSerializer(type_ reflect.Type, mapInStruct bool) (s return nil, fmt.Errorf("cannot auto-register anonymous struct type %s", type_.String()) } // For auto-registered types, use package path as namespace and type name - if err := r.RegisterNamedStruct(type_, 0, pkgPath, typeName); err != nil { + if err := r.RegisterStructByName(type_, pkgPath, typeName); err != nil { return nil, fmt.Errorf("failed to auto-register struct %s: %w", type_.String(), err) } serializer = r.typeToSerializers[type_] diff --git a/go/fory/type_test.go b/go/fory/type_test.go index c7713d03a0..fe47b0fe38 100644 --- a/go/fory/type_test.go +++ b/go/fory/type_test.go @@ -32,8 +32,8 @@ func TestTypeResolver(t *testing.T) { type A struct { F1 string } - require.Nil(t, typeResolver.RegisterNamedStruct(reflect.TypeOf(A{}), 0, "", "example.A")) - require.Error(t, typeResolver.RegisterNamedStruct(reflect.TypeOf(A{}), 0, "", "example.A")) + require.Nil(t, typeResolver.RegisterStructByName(reflect.TypeOf(A{}), "", "example.A")) + require.Error(t, typeResolver.RegisterStructByName(reflect.TypeOf(A{}), "", "example.A")) var tests = []struct { type_ reflect.Type diff --git a/rust/README.md b/rust/README.md index 6fd35dc004..7714e22a24 100644 --- a/rust/README.md +++ b/rust/README.md @@ -609,8 +609,8 @@ let mut fory = Fory::builder() // Register types with consistent IDs across languages fory.register::(100); -// Or use namespace-based registration -fory.register_by_namespace::("com.example", "MyStruct"); +// Or use name-based registration +fory.register_by_name::("com.example", "MyStruct"); ``` See [xlang_type_mapping.md](https://fory.apache.org/docs/specification/xlang_type_mapping) for type mapping across languages. diff --git a/rust/fory-core/src/fory.rs b/rust/fory-core/src/fory.rs index 89c9d1c083..6ff698d59a 100644 --- a/rust/fory-core/src/fory.rs +++ b/rust/fory-core/src/fory.rs @@ -813,7 +813,7 @@ impl Fory { id: u32, ) -> Result<(), Error> { self.check_registration_allowed()?; - self.type_resolver.register_by_id::(id) + self.type_resolver.register::(id) } /// Register a union type with a numeric type ID. @@ -824,7 +824,7 @@ impl Fory { id: u32, ) -> Result<(), Error> { self.check_registration_allowed()?; - self.type_resolver.register_union_by_id::(id) + self.type_resolver.register_union::(id) } /// Registers a struct type with a namespace and type name for cross-language serialization. @@ -855,72 +855,29 @@ impl Fory { /// struct User { name: String, age: u32 } /// /// let mut fory = Fory::default(); - /// fory.register_by_namespace::("com.example", "User"); + /// fory.register_by_name::("com.example", "User"); /// ``` - pub fn register_by_namespace( + pub fn register_by_name( &mut self, namespace: &str, type_name: &str, ) -> Result<(), Error> { self.check_registration_allowed()?; self.type_resolver - .register_by_namespace::(namespace, type_name) + .register_by_name::(namespace, type_name) } /// Register a union type with namespace and type name. /// /// This is intended for union-compatible enums generated by the compiler. - pub fn register_union_by_namespace( + pub fn register_union_by_name( &mut self, namespace: &str, type_name: &str, ) -> Result<(), Error> { self.check_registration_allowed()?; self.type_resolver - .register_union_by_namespace::(namespace, type_name) - } - - /// Registers a struct type with a type name (using the default namespace). - /// - /// # Type Parameters - /// - /// * `T` - The struct type to register. Must implement `StructSerializer`, `Serializer`, and `ForyDefault`. - /// - /// # Arguments - /// - /// * `type_name` - The name of the type (e.g., "User"). - /// - /// # Notes - /// - /// This is a convenience method that calls `register_by_namespace` with an empty namespace string. - /// - /// # Examples - /// - /// ```rust, ignore - /// use fory::Fory; - /// use fory::{ForyEnum, ForyStruct, ForyUnion}; - /// - /// #[derive(ForyStruct)] - /// struct User { name: String, age: u32 } - /// - /// let mut fory = Fory::default(); - /// fory.register_by_name::("User"); - /// ``` - pub fn register_by_name( - &mut self, - type_name: &str, - ) -> Result<(), Error> { - self.check_registration_allowed()?; - self.register_by_namespace::("", type_name) - } - - /// Register a union type with type name only (no namespace). - pub fn register_union_by_name( - &mut self, - type_name: &str, - ) -> Result<(), Error> { - self.check_registration_allowed()?; - self.register_union_by_namespace::("", type_name) + .register_union_by_name::(namespace, type_name) } /// Registers a custom serializer type with a numeric type ID. @@ -955,7 +912,7 @@ impl Fory { id: u32, ) -> Result<(), Error> { self.check_registration_allowed()?; - self.type_resolver.register_serializer_by_id::(id) + self.type_resolver.register_serializer::(id) } /// Registers a custom serializer type with a namespace and type name. @@ -971,38 +928,17 @@ impl Fory { /// /// # Notes /// - /// This is the namespace-based equivalent of `register_serializer()`, preferred for + /// This is the named equivalent of `register_serializer()`, preferred for /// cross-language serialization scenarios. /// - pub fn register_serializer_by_namespace( + pub fn register_serializer_by_name( &mut self, namespace: &str, type_name: &str, ) -> Result<(), Error> { self.check_registration_allowed()?; self.type_resolver - .register_serializer_by_namespace::(namespace, type_name) - } - - /// Registers a custom serializer type with a type name (using the default namespace). - /// - /// # Type Parameters - /// - /// * `T` - The type to register. Must implement `Serializer` and `ForyDefault`. - /// - /// # Arguments - /// - /// * `type_name` - The name of the type. - /// - /// # Notes - /// - /// This is a convenience method that calls `register_serializer_by_namespace` with an empty namespace. - pub fn register_serializer_by_name( - &mut self, - type_name: &str, - ) -> Result<(), Error> { - self.check_registration_allowed()?; - self.register_serializer_by_namespace::("", type_name) + .register_serializer_by_name::(namespace, type_name) } /// Registers a generic trait object type for serialization. diff --git a/rust/fory-core/src/resolver/type_resolver.rs b/rust/fory-core/src/resolver/type_resolver.rs index 1a0e3fbaa7..3fcacda798 100644 --- a/rust/fory-core/src/resolver/type_resolver.rs +++ b/rust/fory-core/src/resolver/type_resolver.rs @@ -69,6 +69,16 @@ const INTERNAL_TYPE_ID_LIMIT: usize = 256; const MAX_USER_TYPE_ID: u32 = 0xfffffffe; pub(crate) const NO_USER_TYPE_ID: u32 = u32::MAX; +fn validate_named_registration(type_name: &str, api: &str) -> Result<(), Error> { + if type_name.is_empty() { + return Err(Error::not_allowed(format!( + "type_name must be non-empty for {}", + api + ))); + } + Ok(()) +} + #[derive(Clone, Debug)] pub struct Harness { write_fn: WriteFn, @@ -799,47 +809,49 @@ impl TypeResolver { Ok(()) } - pub fn register_by_id( + pub fn register( &mut self, id: u32, ) -> Result<(), Error> { - self.register::(id, &EMPTY_STRING, &EMPTY_STRING, true) + self.register_struct_type::(id, &EMPTY_STRING, &EMPTY_STRING, true) } - pub fn register_union_by_id( + pub fn register_union( &mut self, id: u32, ) -> Result<(), Error> { if T::fory_static_type_id() != TypeId::UNION { return Err(Error::not_allowed( - "register_union_by_id requires a union-compatible enum type", + "register_union requires a union-compatible enum type", )); } - self.register::(id, &EMPTY_STRING, &EMPTY_STRING, true) + self.register_struct_type::(id, &EMPTY_STRING, &EMPTY_STRING, true) } - pub fn register_by_namespace( + pub fn register_by_name( &mut self, namespace: &str, type_name: &str, ) -> Result<(), Error> { - self.register::(0, namespace, type_name, true) + validate_named_registration(type_name, "register_by_name")?; + self.register_struct_type::(0, namespace, type_name, true) } - pub fn register_union_by_namespace( + pub fn register_union_by_name( &mut self, namespace: &str, type_name: &str, ) -> Result<(), Error> { + validate_named_registration(type_name, "register_union_by_name")?; if T::fory_static_type_id() != TypeId::UNION { return Err(Error::not_allowed( - "register_union_by_namespace requires a union-compatible enum type", + "register_union_by_name requires a union-compatible enum type", )); } - self.register::(0, namespace, type_name, true) + self.register_struct_type::(0, namespace, type_name, true) } - fn register( + fn register_struct_type( &mut self, id: u32, namespace: &str, @@ -847,6 +859,18 @@ impl TypeResolver { _lazy: bool, ) -> Result<(), Error> { let register_by_name = !type_name.is_empty(); + if register_by_name + && self.partial_type_infos.values().any(|info| { + info.register_by_name + && info.namespace.original == namespace + && info.type_name.original == type_name + }) + { + return Err(Error::type_error(format!( + "Type name {}::{} conflicts with already registered type", + namespace, type_name + ))); + } if !register_by_name && id > MAX_USER_TYPE_ID { return Err(Error::not_allowed(format!( "type id must be in range [0, 0xfffffffe], got {}", @@ -1011,7 +1035,7 @@ impl TypeResolver { Ok(()) } - pub fn register_serializer_by_id( + pub fn register_serializer( &mut self, id: u32, ) -> Result<(), Error> { @@ -1022,14 +1046,15 @@ impl TypeResolver { "register_serializer can only be used for ext and named_ext types", )); } - self.register_serializer::(id, actual_type_id, &EMPTY_STRING, &EMPTY_STRING) + self.register_serializer_type::(id, actual_type_id, &EMPTY_STRING, &EMPTY_STRING) } - pub fn register_serializer_by_namespace( + pub fn register_serializer_by_name( &mut self, namespace: &str, type_name: &str, ) -> Result<(), Error> { + validate_named_registration(type_name, "register_serializer_by_name")?; let actual_type_id = get_ext_actual_type_id(0, true); let static_type_id = T::fory_static_type_id(); if static_type_id != TypeId::EXT && static_type_id != TypeId::NAMED_EXT { @@ -1037,7 +1062,7 @@ impl TypeResolver { "register_serializer can only be used for ext and named_ext types", )); } - self.register_serializer::(0, actual_type_id, namespace, type_name) + self.register_serializer_type::(0, actual_type_id, namespace, type_name) } fn register_internal_serializer( @@ -1051,10 +1076,10 @@ impl TypeResolver { raw_id ))); } - self.register_serializer::(raw_id, raw_id, &EMPTY_STRING, &EMPTY_STRING) + self.register_serializer_type::(raw_id, raw_id, &EMPTY_STRING, &EMPTY_STRING) } - fn register_serializer( + fn register_serializer_type( &mut self, id: u32, actual_type_id: u32, @@ -1062,6 +1087,18 @@ impl TypeResolver { type_name: &str, ) -> Result<(), Error> { let register_by_name = !type_name.is_empty(); + if register_by_name + && self.partial_type_infos.values().any(|info| { + info.register_by_name + && info.namespace.original == namespace + && info.type_name.original == type_name + }) + { + return Err(Error::type_error(format!( + "Type name {}::{} conflicts with already registered type", + namespace, type_name + ))); + } if !register_by_name && id > MAX_USER_TYPE_ID { return Err(Error::not_allowed(format!( "type id must be in range [0, 0xfffffffe], got {}", @@ -1298,6 +1335,12 @@ impl TypeResolver { let namespace = &type_info.namespace; let type_name = &type_info.type_name; let ms_key = (namespace.clone(), type_name.clone()); + if type_info_map_by_meta_string_name.contains_key(&ms_key) { + return Err(Error::type_error(format!( + "Type name {}::{} conflicts with already registered type", + namespace.original, type_name.original + ))); + } type_info_map_by_meta_string_name.insert(ms_key, Rc::new(type_info.clone())); let string_key = (namespace.original.clone(), type_name.original.clone()); type_info_map_by_name.insert(string_key, Rc::new(type_info.clone())); diff --git a/rust/fory/src/lib.rs b/rust/fory/src/lib.rs index c96e46baf9..af9e019fd0 100644 --- a/rust/fory/src/lib.rs +++ b/rust/fory/src/lib.rs @@ -1061,13 +1061,13 @@ //! field2: String, //! } //! -//! fory.register_by_namespace::("com.example", "MyStruct"); +//! fory.register_by_name::("com.example", "MyStruct"); //! ``` //! //! **Type registration strategies:** //! //! - **ID-based registration**: `fory.register::(id)` - Fastest, requires coordination -//! - **Namespace-based registration**: `fory.register_by_namespace::(namespace, name)` - Automatic cross-language mapping +//! - **Name-based registration**: `fory.register_by_name::(namespace, name)` - Automatic cross-language mapping //! //! ## Performance Characteristics //! diff --git a/rust/tests/tests/compatible/test_container.rs b/rust/tests/tests/compatible/test_container.rs index 5abebea86c..d9ff1139c3 100644 --- a/rust/tests/tests/compatible/test_container.rs +++ b/rust/tests/tests/compatible/test_container.rs @@ -288,7 +288,7 @@ fn collection_inner() { let mut fory1 = Fory::builder().compatible(true).build(); fory1.register::(101).unwrap(); let mut fory2 = Fory::builder().compatible(true).build(); - fory2.register_by_name::("item").unwrap(); + fory2.register_by_name::("", "item").unwrap(); for fory in [fory1, fory2] { // serialize let mut bins = vec![ @@ -351,7 +351,7 @@ fn collection_inner_auto_conv() { let mut fory1 = Fory::builder().compatible(true).build(); fory1.register::(101).unwrap(); let mut fory2 = Fory::builder().compatible(true).build(); - fory2.register_by_name::("item").unwrap(); + fory2.register_by_name::("", "item").unwrap(); for fory in [fory1, fory2] { // serialize_non_null let mut bins = vec![ @@ -419,7 +419,7 @@ fn map_inner() { let mut fory1 = Fory::builder().compatible(true).build(); fory1.register::(101).unwrap(); let mut fory2 = Fory::builder().compatible(true).build(); - fory2.register_by_name::("item").unwrap(); + fory2.register_by_name::("", "item").unwrap(); for fory in [fory1, fory2] { // serialize let bytes = fory.serialize(&basic_map()).unwrap(); @@ -456,7 +456,7 @@ fn map_inner_auto_conv() { let mut fory1 = Fory::builder().compatible(true).build(); fory1.register::(101).unwrap(); let mut fory2 = Fory::builder().compatible(true).build(); - fory2.register_by_name::("item").unwrap(); + fory2.register_by_name::("", "item").unwrap(); for fory in [fory1, fory2] { // serialize_non_null let bytes = fory.serialize(&basic_map()).unwrap(); @@ -496,7 +496,7 @@ fn complex() { let mut fory1 = Fory::builder().compatible(true).build(); fory1.register::(101).unwrap(); let mut fory2 = Fory::builder().compatible(true).build(); - fory2.register_by_name::("item").unwrap(); + fory2.register_by_name::("", "item").unwrap(); for fory in [fory1, fory2] { let mut bins = vec![ fory.serialize(&nested_collection()).unwrap(), diff --git a/rust/tests/tests/compatible/test_struct.rs b/rust/tests/tests/compatible/test_struct.rs index 1b5e94dec4..38a7f38560 100644 --- a/rust/tests/tests/compatible/test_struct.rs +++ b/rust/tests/tests/compatible/test_struct.rs @@ -589,10 +589,10 @@ fn named_enum() { last: i8, } let mut fory1 = Fory::builder().compatible(true).xlang(true).build(); - fory1.register_by_name::("a").unwrap(); + fory1.register_by_name::("", "a").unwrap(); fory1.register::(101).unwrap(); let mut fory2 = Fory::builder().compatible(true).xlang(true).build(); - fory2.register_by_name::("a").unwrap(); + fory2.register_by_name::("", "a").unwrap(); fory2.register::(101).unwrap(); let item1 = Item1 { f1: Color::Red, diff --git a/rust/tests/tests/compatible/test_struct_enum.rs b/rust/tests/tests/compatible/test_struct_enum.rs index 0ff6a6b884..36b75d7fef 100644 --- a/rust/tests/tests/compatible/test_struct_enum.rs +++ b/rust/tests/tests/compatible/test_struct_enum.rs @@ -540,10 +540,10 @@ fn named_enum() { last: i8, } let mut fory1 = Fory::builder().compatible(true).xlang(true).build(); - fory1.register_by_name::("a").unwrap(); + fory1.register_by_name::("", "a").unwrap(); fory1.register::(101).unwrap(); let mut fory2 = Fory::builder().compatible(true).xlang(true).build(); - fory2.register_by_name::("a").unwrap(); + fory2.register_by_name::("", "a").unwrap(); fory2.register::(101).unwrap(); let item1 = Item1 { f1: Color::Red, diff --git a/rust/tests/tests/test_any.rs b/rust/tests/tests/test_any.rs index 9c13e00b18..ecca768fcb 100644 --- a/rust/tests/tests/test_any.rs +++ b/rust/tests/tests/test_any.rs @@ -147,8 +147,7 @@ fn test_any_registered_by_name() { } let mut fory = Fory::default(); - fory.register_by_namespace::("test", "Person") - .unwrap(); + fory.register_by_name::("test", "Person").unwrap(); let person = Person { name: "Alice".to_string(), @@ -175,7 +174,7 @@ fn test_mixed_any_types() { } let mut fory = Fory::default(); - fory.register_by_name::("Item").unwrap(); + fory.register_by_name::("", "Item").unwrap(); let item = Item { id: 123, @@ -251,7 +250,7 @@ fn test_hashmap_fixed_key_rc_any_field_compatible() { #[test] fn test_arc_by_name() { let mut fory = Fory::default(); - fory.register_by_name::("Container").unwrap(); + fory.register_by_name::("", "Container").unwrap(); let container = Container { id: 999, @@ -282,7 +281,7 @@ fn test_arc_by_name() { #[test] fn test_rc_by_name() { let mut fory = Fory::default(); - fory.register_by_name::("Container").unwrap(); + fory.register_by_name::("", "Container").unwrap(); let container = Container { id: 555, @@ -333,9 +332,9 @@ struct StructB { fn test_vec_of_different_struct_types_in_box_any_returns_error() { let mut fory = Fory::default(); // Register both struct types - fory.register_by_name::("StructA").unwrap(); + fory.register_by_name::("", "StructA").unwrap(); fory.register_generic_trait::>().unwrap(); - fory.register_by_name::("StructB").unwrap(); + fory.register_by_name::("", "StructB").unwrap(); fory.register_generic_trait::>().unwrap(); // Create Box wrappers for Vec and Vec diff --git a/rust/tests/tests/test_array.rs b/rust/tests/tests/test_array.rs index b21e8d72e4..fb2e55e2bb 100644 --- a/rust/tests/tests/test_array.rs +++ b/rust/tests/tests/test_array.rs @@ -129,7 +129,7 @@ struct Point { #[test] fn test_array_struct() { let mut fory = Fory::default(); - fory.register_by_name::("Point").unwrap(); + fory.register_by_name::("", "Point").unwrap(); let arr = [ Point { x: 1, y: 2 }, @@ -151,7 +151,8 @@ struct ArrayStruct { #[test] fn test_struct_with_arrays() { let mut fory = Fory::default(); - fory.register_by_name::("ArrayStruct").unwrap(); + fory.register_by_name::("", "ArrayStruct") + .unwrap(); let data = ArrayStruct { int_array: [1, 2, 3, 4, 5], @@ -315,7 +316,8 @@ fn test_struct_with_vec_of_arrays() { } let mut fory = Fory::default(); - fory.register_by_name::("PointCloud").unwrap(); + fory.register_by_name::("", "PointCloud") + .unwrap(); let data = PointCloud { index: 42, diff --git a/rust/tests/tests/test_complex_struct.rs b/rust/tests/tests/test_complex_struct.rs index 933a32ff35..55e4a4f68e 100644 --- a/rust/tests/tests/test_complex_struct.rs +++ b/rust/tests/tests/test_complex_struct.rs @@ -115,8 +115,8 @@ fn complex_struct() { let obj: Person = fory.deserialize(&bin).expect("should success"); assert_eq!(person, obj); let mut fory = Fory::default(); - fory.register_by_name::("animal").unwrap(); - fory.register_by_name::("person").unwrap(); + fory.register_by_name::("", "animal").unwrap(); + fory.register_by_name::("", "person").unwrap(); let bin: Vec = fory.serialize(&person).unwrap(); let obj: Person = fory.deserialize(&bin).expect("should success"); assert_eq!(person, obj); diff --git a/rust/tests/tests/test_cross_language.rs b/rust/tests/tests/test_cross_language.rs index 8660ad493e..fcf5f0d261 100644 --- a/rust/tests/tests/test_cross_language.rs +++ b/rust/tests/tests/test_cross_language.rs @@ -444,10 +444,9 @@ fn test_named_simple_struct() { let data_file_path = get_data_file(); let bytes = fs::read(&data_file_path).unwrap(); let mut fory = Fory::builder().compatible(true).xlang(true).build(); - fory.register_by_namespace::("demo", "color") - .unwrap(); - fory.register_by_namespace::("demo", "item").unwrap(); - fory.register_by_namespace::("demo", "simple_struct") + fory.register_by_name::("demo", "color").unwrap(); + fory.register_by_name::("demo", "item").unwrap(); + fory.register_by_name::("demo", "simple_struct") .unwrap(); let local_obj = SimpleStruct { @@ -477,9 +476,9 @@ fn test_struct_evolving_override() { let data_file_path = get_data_file(); let bytes = fs::read(&data_file_path).unwrap(); let mut fory = Fory::builder().compatible(true).xlang(true).build(); - fory.register_by_namespace::("test", "evolving_yes") + fory.register_by_name::("test", "evolving_yes") .unwrap(); - fory.register_by_namespace::("test", "evolving_off") + fory.register_by_name::("test", "evolving_off") .unwrap(); let mut reader = Reader::new(bytes.as_slice()); @@ -789,16 +788,18 @@ fn test_skip_id_custom() { fn test_skip_name_custom() { let mut fory1 = Fory::builder().compatible(true).xlang(true).build(); fory1 - .register_serializer_by_name::("my_ext") + .register_serializer_by_name::("", "my_ext") .unwrap(); - fory1.register_by_name::("my_wrapper").unwrap(); + fory1.register_by_name::("", "my_wrapper").unwrap(); let mut fory2 = Fory::builder().compatible(true).xlang(true).build(); - fory2.register_by_name::("color").unwrap(); - fory2.register_by_name::("my_struct").unwrap(); + fory2.register_by_name::("", "color").unwrap(); + fory2.register_by_name::("", "my_struct").unwrap(); + fory2 + .register_serializer_by_name::("", "my_ext") + .unwrap(); fory2 - .register_serializer_by_name::("my_ext") + .register_by_name::("", "my_wrapper") .unwrap(); - fory2.register_by_name::("my_wrapper").unwrap(); _test_skip_custom(&fory1, &fory2); } @@ -806,9 +807,10 @@ fn test_skip_name_custom() { #[ignore] fn test_consistent_named() { let mut fory = Fory::builder().compatible(false).xlang(true).build(); - fory.register_by_name::("color").unwrap(); - fory.register_by_name::("my_struct").unwrap(); - fory.register_serializer_by_name::("my_ext").unwrap(); + fory.register_by_name::("", "color").unwrap(); + fory.register_by_name::("", "my_struct").unwrap(); + fory.register_serializer_by_name::("", "my_ext") + .unwrap(); let color = Color::White; let my_struct = MyStruct { id: 42 }; diff --git a/rust/tests/tests/test_field_meta.rs b/rust/tests/tests/test_field_meta.rs index f99576b74b..d09d586ac1 100644 --- a/rust/tests/tests/test_field_meta.rs +++ b/rust/tests/tests/test_field_meta.rs @@ -394,9 +394,9 @@ fn annotated_primitive_vec_field_uses_list_element_type_meta() { #[test] fn unannotated_non_primitive_vec_field_keeps_declared_element_type_meta() { let mut type_resolver = TypeResolver::default(); - type_resolver.register_by_id::(401).unwrap(); + type_resolver.register::(401).unwrap(); type_resolver - .register_by_id::(402) + .register::(402) .unwrap(); let field_type = only_field_type::(&type_resolver); diff --git a/rust/tests/tests/test_lifecycle_guard.rs b/rust/tests/tests/test_lifecycle_guard.rs index 193c37c4e1..a12dd63e87 100644 --- a/rust/tests/tests/test_lifecycle_guard.rs +++ b/rust/tests/tests/test_lifecycle_guard.rs @@ -63,6 +63,26 @@ fn test_multiple_registrations_before_serialize_succeed() { assert_eq!(point, result); } +#[test] +fn test_register_by_name_requires_type_name() { + let mut fory = Fory::default(); + let err = fory + .register_by_name::("com.example", "") + .unwrap_err(); + assert!(matches!(err, Error::NotAllowed(_))); +} + +#[test] +fn test_register_by_name_rejects_duplicate_identity() { + let mut fory = Fory::default(); + fory.register_by_name::("com.example", "Point") + .unwrap(); + let err = fory + .register_by_name::("com.example", "Point") + .unwrap_err(); + assert!(matches!(err, Error::TypeError(_))); +} + // Negative tests /// ensures `register()` is forbidden after `serialize()` triggers snapshot init. @@ -118,21 +138,21 @@ fn test_register_by_name_after_serialize_fails() { let _bytes = fory.serialize(&Point { x: 0, y: 0 }).unwrap(); let err = fory - .register_by_name::("Color") + .register_by_name::("", "Color") .expect_err("register_by_name after serialize should fail"); assert!(matches!(err, Error::NotAllowed(_))); } -/// Ensures `register_by_namespace()` is forbidden after snapshot init. +/// Ensures `register_by_name()` with a non-empty namespace is forbidden after snapshot init. #[test] -fn test_register_by_namespace_after_serialize_fails() { +fn test_register_by_name_with_namespace_after_serialize_fails() { let mut fory = Fory::default(); fory.register::(100).unwrap(); let _bytes = fory.serialize(&Point { x: 0, y: 0 }).unwrap(); let err = fory - .register_by_namespace::("com.example", "Color") - .expect_err("register_by_namespace after serialize should fail"); + .register_by_name::("com.example", "Color") + .expect_err("register_by_name after serialize should fail"); assert!(matches!(err, Error::NotAllowed(_))); } @@ -157,21 +177,21 @@ fn test_register_serializer_by_name_after_serialize_fails() { let _bytes = fory.serialize(&Point { x: 0, y: 0 }).unwrap(); let err = fory - .register_serializer_by_name::("Color") + .register_serializer_by_name::("", "Color") .expect_err("register_serializer_by_name after serialize should fail"); assert!(matches!(err, Error::NotAllowed(_))); } -/// Ensures `register_serializer_by_namespace()` is forbidden after snapshot init. +/// Ensures `register_serializer_by_name()` with a non-empty namespace is forbidden after snapshot init. #[test] -fn test_register_serializer_by_namespace_after_serialize_fails() { +fn test_register_serializer_by_name_with_namespace_after_serialize_fails() { let mut fory = Fory::default(); fory.register::(100).unwrap(); let _bytes = fory.serialize(&Point { x: 0, y: 0 }).unwrap(); let err = fory - .register_serializer_by_namespace::("com.example", "Color") - .expect_err("register_serializer_by_namespace after serialize should fail"); + .register_serializer_by_name::("com.example", "Color") + .expect_err("register_serializer_by_name after serialize should fail"); assert!(matches!(err, Error::NotAllowed(_))); } @@ -209,21 +229,21 @@ fn test_register_union_by_name_after_serialize_fails() { let _bytes = fory.serialize(&Point { x: 0, y: 0 }).unwrap(); let err = fory - .register_union_by_name::("Color") + .register_union_by_name::("", "Color") .expect_err("register_union_by_name after serialize should fail"); assert!(matches!(err, Error::NotAllowed(_))); } -/// Ensures `register_union_by_namespace()` is forbidden after snapshot init. +/// Ensures `register_union_by_name()` with a non-empty namespace is forbidden after snapshot init. #[test] -fn test_register_union_by_namespace_after_serialize_fails() { +fn test_register_union_by_name_with_namespace_after_serialize_fails() { let mut fory = Fory::default(); fory.register::(100).unwrap(); let _bytes = fory.serialize(&Point { x: 0, y: 0 }).unwrap(); let err = fory - .register_union_by_namespace::("com.example", "Color") - .expect_err("register_union_by_namespace after serialize should fail"); + .register_union_by_name::("com.example", "Color") + .expect_err("register_union_by_name after serialize should fail"); assert!(matches!(err, Error::NotAllowed(_))); } diff --git a/rust/tests/tests/test_list.rs b/rust/tests/tests/test_list.rs index cb9ad473fe..744204860e 100644 --- a/rust/tests/tests/test_list.rs +++ b/rust/tests/tests/test_list.rs @@ -117,7 +117,7 @@ struct CollectionStruct { #[test] fn test_struct_with_collections() { let mut fory = Fory::default(); - fory.register_by_name::("CollectionStruct") + fory.register_by_name::("", "CollectionStruct") .unwrap(); let mut deque = VecDeque::new(); diff --git a/rust/tests/tests/test_map.rs b/rust/tests/tests/test_map.rs index 9396baeb69..62436c6015 100644 --- a/rust/tests/tests/test_map.rs +++ b/rust/tests/tests/test_map.rs @@ -50,7 +50,7 @@ struct MapContainer { #[test] fn test_struct_with_maps() { let mut fory = Fory::default(); - fory.register_by_name::("MapContainer") + fory.register_by_name::("", "MapContainer") .unwrap(); let mut hash_map = HashMap::new(); hash_map.insert("foo".to_string(), "bar".to_string());