From 8dd76c30dfdaf2bb387e7ee1ad5893bf64a0abda Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 09:31:37 +0800 Subject: [PATCH 01/15] docs: complete getting started guides --- ci/release.py | 3 ++ ci/test_release.py | 62 ++++++++++++++++++++++ docs/start/cpp.md | 82 +++++++++++++++++++++++++---- docs/start/csharp.md | 59 ++++++++++++++++++--- docs/start/dart.md | 68 +++++++++++++++++++++--- docs/start/go.md | 70 +++++++++++++++++++++---- docs/start/index.md | 49 ++++++++++-------- docs/start/java.md | 108 +++++++++++++++++++++++++++++++++++---- docs/start/javascript.md | 58 ++++++++++++++++++--- docs/start/kotlin.md | 57 +++++++++++++++++---- docs/start/python.md | 52 +++++++++++++++---- docs/start/rust.md | 54 ++++++++++++++++---- docs/start/scala.md | 54 +++++++++++++++++--- docs/start/swift.md | 63 ++++++++++++++++++++--- 14 files changed, 725 insertions(+), 114 deletions(-) create mode 100644 ci/test_release.py diff --git a/ci/release.py b/ci/release.py index 1bc7ef12c9..e48c49da1f 100644 --- a/ci/release.py +++ b/ci/release.py @@ -1255,6 +1255,9 @@ def _is_release_doc_line(line): or "org.apache.fory" in line or "Apache.Fory" in line or "dart pub add fory" in line + or re.search(r"\bpyfory(?:\[[^\]]+\])?==" + VERSION_PATTERN, line) + or re.search(r"\bgithub\.com/apache/fory/go/fory@v" + VERSION_PATTERN, line) + or re.search(r"@apache-fory/(?:core|hps)@" + VERSION_PATTERN, line) or re.search(r"^\s*fory\s*[:=]", line) or "https://github.com/apache/fory.git" in line or 'bazel_dep(name = "fory"' in line diff --git a/ci/test_release.py b/ci/test_release.py new file mode 100644 index 0000000000..cd4444404d --- /dev/null +++ b/ci/test_release.py @@ -0,0 +1,62 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import unittest + +if __package__: + from . import release +else: + import release + + +class ReleaseDocVersionTest(unittest.TestCase): + def test_updates_release_doc_dependencies(self): + lines = [ + "python -m pip install pyfory==1.5.0\n", + 'python -m pip install "pyfory[format]==1.5.0"\n', + "go get github.com/apache/fory/go/fory@v1.5.0\n", + "npm install @apache-fory/core@1.5.0 @apache-fory/hps@1.5.0\n", + 'fory = "1.5.0"\n', + " fory: 1.5.0\n", + ' .package(url: "https://github.com/apache/fory.git", exact: "1.5.0")\n', + 'implementation("org.apache.fory:fory-core:1.5.0")\n', + 'libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.5.0"\n', + 'implementation("io.grpc:grpc-api:1.73.0")\n', + "The wire format was introduced in version 1.5.0.\n", + ] + expected = [ + "python -m pip install pyfory==1.6.0\n", + 'python -m pip install "pyfory[format]==1.6.0"\n', + "go get github.com/apache/fory/go/fory@v1.6.0\n", + "npm install @apache-fory/core@1.6.0 @apache-fory/hps@1.6.0\n", + 'fory = "1.6.0"\n', + " fory: 1.6.0\n", + ' .package(url: "https://github.com/apache/fory.git", exact: "1.6.0")\n', + 'implementation("org.apache.fory:fory-core:1.6.0")\n', + 'libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.6.0"\n', + 'implementation("io.grpc:grpc-api:1.73.0")\n', + "The wire format was introduced in version 1.5.0.\n", + ] + + updated = release._update_release_doc_lines(lines, "1.6.0") + + self.assertEqual(expected, updated) + self.assertEqual(expected, release._update_release_doc_lines(updated, "1.6.0")) + + +if __name__ == "__main__": + unittest.main() diff --git a/docs/start/cpp.md b/docs/start/cpp.md index 837cb81bd9..7e48ce01ab 100644 --- a/docs/start/cpp.md +++ b/docs/start/cpp.md @@ -19,7 +19,9 @@ license: | limitations under the License. --- -Fory C++ is consumed from the Apache Fory source tree. It requires a C++17 compiler and supports CMake 3.16 or later and Bazel 8 or later. Pin one Fory release or commit across every peer in an application. +Fory C++ provides binary Object Serialization, Row Format, generated models, +and Fory gRPC. It requires a C++17 compiler and supports CMake 3.16 or later +and Bazel 8 or later. ## Verify the Toolchain @@ -29,13 +31,75 @@ cmake --version # or: bazel --version ``` -## Choose a Capability +## Object Serialization -| Capability | Build target | Continue with | -| --------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Object Serialization | `fory::serialization` or `//cpp/fory/serialization` | [C++ object serialization](../object-serialization/cpp/index.md), then choose [xlang](../object-serialization/cpp/xlang.md) or [native](../object-serialization/cpp/native.md) | -| Row Format | C++ Row and encoder targets | [C++ Row Format](../row-format/cpp.md) | -| Schema and generated models | `fory-compiler` | [Fory IDL and Compiler](../compiler/index.md) | -| Fory gRPC | generated companions plus gRPC C++ | [C++ gRPC](../grpc/cpp.md) | +Use xlang mode for data shared with other Fory runtimes or native mode for +C++-only data. Fetch a released source tree and link the serialization target: -The selected capability guide owns its exact CMake or Bazel setup and first runnable example. +```cmake title="CMakeLists.txt" +cmake_minimum_required(VERSION 3.16) +project(fory_example LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +include(FetchContent) +FetchContent_Declare( + fory + GIT_REPOSITORY https://github.com/apache/fory.git + GIT_TAG v1.5.0 + SOURCE_SUBDIR cpp +) +FetchContent_MakeAvailable(fory) + +add_executable(fory_example main.cc) +target_link_libraries(fory_example PRIVATE fory::serialization) +``` + +```cpp title="main.cc" +#include +#include +#include +#include +#include + +#include "fory/serialization/fory.h" + +struct User { + int64_t id; + std::string name; + + bool operator==(const User &other) const { + return id == other.id && name == other.name; + } +}; +FORY_STRUCT(User, id, name); + +int main() { + auto fory = fory::serialization::Fory::builder().xlang(true).build(); + fory.register_struct(1); + + auto bytes = fory.serialize(User{1, "Alice"}); + assert(bytes.ok()); + std::vector data = std::move(bytes).value(); + auto decoded = fory.deserialize(data); + assert(decoded.ok()); + User user = std::move(decoded).value(); + assert(user.id == 1 && user.name == "Alice"); +} +``` + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build --parallel +./build/fory_example +``` + +See [C++ Object Serialization](../object-serialization/cpp/index.md) for Bazel, +Windows, error handling, and thread-safe instances; then continue to +[xlang](../object-serialization/cpp/xlang.md) or +[native mode](../object-serialization/cpp/native.md). + +## Other Capabilities + +- **Row Format** provides random and partial field access for trusted analytical data. See [C++ Row Format](../row-format/cpp.md). +- **Fory IDL and Compiler** generates C++ models and registration helpers. See [Compiler Getting Started](../compiler/getting-started.md) and the [C++ generated-code guide](../compiler/generated-code/cpp.md). +- **Fory gRPC** uses gRPC C++ transports with Fory-encoded messages. See [C++ gRPC](../grpc/cpp.md). diff --git a/docs/start/csharp.md b/docs/start/csharp.md index 545192663f..91ebefcb2f 100644 --- a/docs/start/csharp.md +++ b/docs/start/csharp.md @@ -19,7 +19,9 @@ license: | limitations under the License. --- -Fory C# is published on NuGet as `Apache.Fory` and requires the .NET 8 SDK or later. The package includes the runtime and source generator for `ForyStruct` types. Use one compatible Fory release across every peer in an application. +Fory C# provides xlang Object Serialization, generated models, and Fory gRPC. +The `Apache.Fory` NuGet package requires .NET 8 or later and includes both the +runtime and source generator. ## Verify the Toolchain @@ -27,12 +29,53 @@ Fory C# is published on NuGet as `Apache.Fory` and requires the .NET 8 SDK or la dotnet --version ``` -## Choose a Capability +## Object Serialization -| Capability | Package or tool | Continue with | -| --------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | -| Object Serialization | `Apache.Fory` | [C# object serialization](../object-serialization/csharp/index.md) and [xlang](../object-serialization/csharp/xlang.md) | -| Schema and generated models | `fory-compiler` | [Fory IDL and Compiler](../compiler/index.md) | -| Fory gRPC | generated companions plus gRPC .NET dependencies | [C# gRPC](../grpc/csharp.md) | +Create a console project and add the released package: -Each capability guide owns its exact package declaration and first runnable example. +```bash +dotnet new console -n ForyExample +cd ForyExample +dotnet add package Apache.Fory --version 1.5.0 +``` + +Replace `Program.cs` with: + +```csharp +using Apache.Fory; + +[ForyStruct] +public sealed class User +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; +} + +public static class Program +{ + public static void Main() + { + Fory fory = Fory.Builder().Build(); + fory.Register(1); + + byte[] bytes = fory.Serialize(new User { Id = 1, Name = "Alice" }); + User decoded = fory.Deserialize(bytes); + Console.WriteLine(decoded.Name); + } +} +``` + +```bash +dotnet run +``` + +C# uses xlang mode. Continue with +[C# Object Serialization](../object-serialization/csharp/index.md), +[xlang types](../object-serialization/csharp/xlang.md), +[configuration](../object-serialization/csharp/configuration.md), and +[schema evolution](../object-serialization/csharp/schema-evolution.md). + +## Other Capabilities + +- **Fory IDL and Compiler** generates C# models and registration helpers. See [Compiler Getting Started](../compiler/getting-started.md) and the [C# generated-code guide](../compiler/generated-code/csharp.md). +- **Fory gRPC** uses normal .NET gRPC transports with Fory-encoded messages. See [C# gRPC](../grpc/csharp.md). diff --git a/docs/start/dart.md b/docs/start/dart.md index b8f95f41b8..e68c918bbf 100644 --- a/docs/start/dart.md +++ b/docs/start/dart.md @@ -19,7 +19,9 @@ license: | limitations under the License. --- -Fory Dart is published on pub.dev and requires Dart SDK 3.7 or later. Generated serializers use `build_runner`. Keep Fory packages and generated code in one application on compatible versions. +Fory Dart provides xlang Object Serialization, generated models, and Fory gRPC. +It is published on pub.dev, requires Dart 3.7 or later, and uses `build_runner` +to generate serializers. ## Verify the Toolchain @@ -28,12 +30,62 @@ dart --version dart pub --help ``` -## Choose a Capability +## Object Serialization -| Capability | Package or tool | Continue with | -| --------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| Object Serialization | `fory` plus `build_runner` | [Dart object serialization](../object-serialization/dart/index.md) and [xlang](../object-serialization/dart/xlang.md) | -| Schema and generated models | `fory-compiler` | [Fory IDL and Compiler](../compiler/index.md) | -| Fory gRPC | generated companions plus `package:grpc` | [Dart gRPC](../grpc/dart.md) | +Add Fory and the generator to `pubspec.yaml`: -Each capability guide owns its exact `pubspec.yaml`, generation command, and first runnable example. +```yaml +dependencies: + fory: 1.5.0 + +dev_dependencies: + build_runner: ^2.4.0 +``` + +Create `lib/person.dart`: + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +@ForyStruct() +class Person { + Person(); + + @ForyField(type: Int64Type()) + int id = 0; + String name = ''; +} + +void main() { + final fory = Fory(); + PersonForyModule.register(fory, Person, name: 'example.Person'); + + final input = Person() + ..id = 1 + ..name = 'Alice'; + final bytes = fory.serialize(input); + final decoded = fory.deserialize(bytes); + print(decoded.name); +} +``` + +Generate the serializer and run the example: + +```bash +dart pub get +dart run build_runner build +dart run lib/person.dart +``` + +Dart uses xlang mode. Continue with +[Dart Object Serialization](../object-serialization/dart/index.md), +[code generation](../object-serialization/dart/code-generation.md), +[web support](../object-serialization/dart/web-platform-support.md), and +[schema evolution](../object-serialization/dart/schema-evolution.md). + +## Other Capabilities + +- **Fory IDL and Compiler** generates Dart models and registration helpers. See [Compiler Getting Started](../compiler/getting-started.md) and the [Dart generated-code guide](../compiler/generated-code/dart.md). +- **Fory gRPC** uses package:grpc transports with Fory-encoded messages. See [Dart gRPC](../grpc/dart.md). diff --git a/docs/start/go.md b/docs/start/go.md index fff98fe6ef..dc545e8f86 100644 --- a/docs/start/go.md +++ b/docs/start/go.md @@ -19,7 +19,9 @@ license: | limitations under the License. --- -Fory Go is published as the Go module `github.com/apache/fory/go/fory` and requires Go 1.24 or later. Use one compatible Fory release across every peer in an application. +Fory Go provides xlang and native Object Serialization, generated models, and +Fory gRPC. It is published as `github.com/apache/fory/go/fory` and requires Go +1.25 or later. ## Verify the Toolchain @@ -28,14 +30,64 @@ go version go env GOPROXY ``` -If a Go proxy has not picked up a new submodule tag yet, retry later or use `GOPROXY=direct` temporarily. +## Object Serialization -## Choose a Capability +Create a module and install the released Fory module: -| Capability | Module or tool | Continue with | -| --------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Object Serialization | `github.com/apache/fory/go/fory` | [Go object serialization](../object-serialization/go/index.md), then choose [xlang](../object-serialization/go/xlang.md) or [native](../object-serialization/go/native.md) | -| Schema and generated models | `fory-compiler` | [Fory IDL and Compiler](../compiler/index.md) | -| Fory gRPC | generated companions plus grpc-go | [Go gRPC](../grpc/go.md) | +```bash +mkdir fory-example +cd fory-example +go mod init example.com/fory-example +go get github.com/apache/fory/go/fory@v1.5.0 +``` + +If a Go proxy has not picked up a new submodule tag yet, retry later or use +`GOPROXY=direct` temporarily. + +```go title="main.go" +package main + +import ( + "fmt" + + "github.com/apache/fory/go/fory" +) + +type User struct { + ID int64 + Name string +} + +func main() { + f := fory.New(fory.WithXlang(true)) + if err := f.RegisterStruct(User{}, 1); err != nil { + panic(err) + } + + bytes, err := f.Serialize(&User{ID: 1, Name: "Alice"}) + if err != nil { + panic(err) + } + + var decoded User + if err := f.Deserialize(bytes, &decoded); err != nil { + panic(err) + } + fmt.Println(decoded.Name) +} +``` + +```bash +go run . +``` + +Use [xlang mode](../object-serialization/go/xlang.md) for cross-language data +and [native mode](../object-serialization/go/native.md) for Go-only data. +Continue with [Go Object Serialization](../object-serialization/go/index.md), +[configuration](../object-serialization/go/configuration.md), and +[schema evolution](../object-serialization/go/schema-evolution.md). + +## Other Capabilities -Each capability guide owns its exact module command and first runnable example. +- **Fory IDL and Compiler** generates Go models and registration helpers. See [Compiler Getting Started](../compiler/getting-started.md) and the [Go generated-code guide](../compiler/generated-code/go.md). +- **Fory gRPC** uses grpc-go transports with Fory-encoded messages. See [Go gRPC](../grpc/go.md). diff --git a/docs/start/index.md b/docs/start/index.md index da24c38d57..3575ff456a 100644 --- a/docs/start/index.md +++ b/docs/start/index.md @@ -19,14 +19,27 @@ license: | limitations under the License. --- -Apache Fory™ releases are available both as source artifacts and language-specific packages. +Start with a runtime page below. Each page includes a release-pinned installation, +a runnable object-serialization round trip, the modes supported by that runtime, +and a short path into every additional Fory capability available there. -For source downloads, see the Apache Fory™ [download](https://fory.apache.org/download) page. +Apache Fory™ releases are available as source artifacts and language-specific +packages. For source downloads, see the Apache Fory™ +[download](https://fory.apache.org/download) page. + +## First Five Minutes + +1. Choose the runtime used by your application. +2. Install the package shown on that runtime page. +3. Run its minimal serialize/deserialize example. +4. Select xlang mode for cross-language data or native mode for same-runtime data + when both are available. +5. Continue to the capability guide for production configuration and advanced APIs. ## Choose a Runtime -Each runtime setup page checks the toolchain and routes to the capabilities that -runtime supports: +Each runtime page provides a release-pinned installation snippet, a minimal +round trip for an application project, and the next capability-specific steps: | Runtime | Setup | | --------------------- | -------------------------------------- | @@ -42,21 +55,17 @@ runtime supports: | Scala | [Scala](scala.md) | | Kotlin | [Kotlin](kotlin.md) | -## Choose a Capability - -| Need | Continue with | -| ----------------------------------- | -------------------------------------------------------- | -| Reconstruct object graphs | [Object Serialization](../object-serialization/index.md) | -| Random or partial analytical access | [Row Format](../row-format/index.md) | -| Exchange standard JSON from Java | [Fory JSON](../json/index.md) | -| Generate models from a schema | [Fory IDL and Compiler](../compiler/index.md) | -| Use generated models over gRPC | [Fory gRPC](../grpc/index.md) | +## What You Can Build -Object Serialization then asks you to choose xlang mode for portable -cross-language payloads or native mode for same-runtime payloads. Use -[Choose a Format](../introduction/choose-a-format.md) when that product decision -is not yet clear. +| Capability | Use it for | Available runtimes | Detailed guide | +| -------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------- | -------------------------------------------------------- | +| Object Serialization | Reconstruct object graphs, including shared references and schema changes | All runtimes | [Object Serialization](../object-serialization/index.md) | +| Row Format | Trusted analytical data with zero-copy, random, or partial field access | Java, Python, C++, Rust | [Row Format](../row-format/index.md) | +| Fory JSON | High-performance standard JSON mapping | Java | [Fory JSON](../json/index.md) | +| Fory IDL | Generate native models and serializers from Fory, protobuf, or FlatBuffers IDL | All runtimes | [Fory IDL and Compiler](../compiler/index.md) | +| Fory gRPC | Use generated models over normal gRPC transports with Fory-encoded messages | Java, Python, C++, Go, Rust, JavaScript, C#, Dart, Scala, Kotlin | [Fory gRPC](../grpc/index.md) | -The selected capability guide owns its exact dependency and first successful -task. Schema compatibility settings and production configuration are -runtime-specific. +Object Serialization uses xlang mode for portable cross-language data. Java, +Python, C++, Go, Rust, Scala, and Kotlin also offer native mode for +same-runtime data. Use [Choose a Format](../introduction/choose-a-format.md) +when the product choice is not yet clear. diff --git a/docs/start/java.md b/docs/start/java.md index 314b4f09b6..54ee81df08 100644 --- a/docs/start/java.md +++ b/docs/start/java.md @@ -19,7 +19,11 @@ license: | limitations under the License. --- -Fory Java artifacts are published to Maven Central. Fory core supports Java 8 and later; Java Records require Java 17 or later, and Row Format requires Java 11 or later. Keep every Fory artifact in one application on the same version. +Fory Java provides binary Object Serialization, Fory JSON, Row Format, generated +models, and Fory gRPC. Artifacts are published to Maven Central. Fory core and +Fory JSON support Java 8 and later, Java Records require Java 17 or later, and +Row Format requires Java 11 or later. Keep every Fory artifact in one application +on the same version. ## Verify the Toolchain @@ -29,14 +33,98 @@ mvn -version # or: ./gradlew --version ``` -## Choose a Capability +## Object Serialization -| Capability | Artifact or tool | Continue with | -| --------------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Object Serialization | `fory-core` | [Java object serialization](../object-serialization/java/index.md), then choose [xlang](../object-serialization/java/xlang.md) or [native](../object-serialization/java/native.md) | -| Fory JSON | `fory-json` | [Fory JSON Getting Started](../json/getting-started.md) | -| Row Format | `fory-format` | [Java Row Format](../row-format/java.md) | -| Schema and generated models | `fory-compiler` | [Fory IDL and Compiler](../compiler/index.md) | -| Fory gRPC | generated companions plus gRPC Java dependencies | [Java gRPC](../grpc/java.md) | +Use Object Serialization for object graphs. Xlang mode produces data that other +Fory runtimes can read; native mode supports a broader JVM object surface. -Each capability guide owns its exact dependency declaration and first runnable example. +Maven: + +```xml + + org.apache.fory + fory-core + 1.5.0 + +``` + +Gradle: + +```kotlin +implementation("org.apache.fory:fory-core:1.5.0") +``` + +Run this complete xlang round trip: + +```java +import org.apache.fory.Fory; + +public final class ForyExample { + public static final class User { + public long id; + public String name; + + public User() {} + + public User(long id, String name) { + this.id = id; + this.name = name; + } + } + + public static void main(String[] args) { + Fory fory = Fory.builder().withXlang(true).build(); + fory.register(User.class, 1); + + byte[] bytes = fory.serialize(new User(1, "Alice")); + User decoded = (User) fory.deserialize(bytes); + System.out.println(decoded.name); + } +} +``` + +Reuse a `Fory` instance instead of rebuilding it for every value. Continue with +[Java Object Serialization](../object-serialization/java/index.md), +[xlang mode](../object-serialization/java/xlang.md), +[native mode](../object-serialization/java/native.md), or +[configuration](../object-serialization/java/configuration.md). + +## Fory JSON + +Fory JSON maps Java objects to standard JSON text and UTF-8 bytes. Add +`fory-json` instead of `fory-core` when the application only needs JSON: + +```kotlin +implementation("org.apache.fory:fory-json:1.5.0") +``` + +Add the import to `ForyExample.java`: + +```java +import org.apache.fory.json.ForyJson; +``` + +Then place the JSON round trip inside `ForyExample.main`: + +```java +ForyJson json = ForyJson.builder().build(); +String text = json.toJson(new User(1, "Alice")); +User jsonDecoded = json.fromJson(text, User.class); +System.out.println(jsonDecoded.name); +``` + +See [Fory JSON Getting Started](../json/getting-started.md) for Maven setup, +object mapping, annotations, Android, GraalVM, and security. + +## Other Capabilities + +- **Row Format** provides random and partial field access for trusted analytical data. See [Java Row Format](../row-format/java.md). +- **Fory IDL and Compiler** generates Java models and registration helpers from Fory IDL, protobuf IDL, or FlatBuffers IDL. See [Compiler Getting Started](../compiler/getting-started.md) and the [Java generated-code guide](../compiler/generated-code/java.md). +- **Fory gRPC** uses normal grpc-java transports with Fory-encoded request and response objects. See [Java gRPC](../grpc/java.md). + +## Runtime Notes + +- On JDK 25 and later, follow the setup in + [Java Object Serialization](../object-serialization/java/index.md#jdk-25-and-later). +- For Android, see [Java Android support](../object-serialization/java/android.md). +- For native images, see [Java GraalVM support](../object-serialization/java/graalvm.md). diff --git a/docs/start/javascript.md b/docs/start/javascript.md index 144b41652e..3e3cc755db 100644 --- a/docs/start/javascript.md +++ b/docs/start/javascript.md @@ -19,7 +19,10 @@ license: | limitations under the License. --- -Fory JavaScript/TypeScript packages are published on npm. The core package works without native acceleration; the optional `@apache-fory/hps` Node.js fast path requires Node.js 20 or later. Keep Fory packages in one application on compatible versions. +Fory JavaScript/TypeScript provides xlang Object Serialization, generated +models, Node.js gRPC, and browser gRPC-Web clients. Packages are published on +npm. The core package works without native acceleration; the optional +`@apache-fory/hps` Node.js fast path requires Node.js 20 or later. ## Verify the Toolchain @@ -28,12 +31,51 @@ node --version npm --version ``` -## Choose a Capability +## Object Serialization -| Capability | Package or tool | Continue with | -| --------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| Object Serialization | `@apache-fory/core`; optional `@apache-fory/hps` | [JavaScript/TypeScript object serialization](../object-serialization/javascript/index.md) and [xlang](../object-serialization/javascript/xlang.md) | -| Schema and generated models | `fory-compiler` | [Fory IDL and Compiler](../compiler/index.md) | -| Fory gRPC | generated Node.js or gRPC-Web companions | [JavaScript gRPC](../grpc/javascript.md) | +Install the core package: -Each capability guide owns its exact install command, environment-specific setup, and first runnable example. +```bash +npm install @apache-fory/core@1.5.0 +``` + +Define a schema and run an xlang round trip: + +```js title="example.mjs" +import Fory, { Type } from "@apache-fory/core"; + +const userType = Type.struct( + { typeName: "example.User" }, + { + id: Type.int64(), + name: Type.string(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); + +const bytes = serialize({ id: 1n, name: "Alice" }); +console.log(deserialize(bytes)); +``` + +```bash +node example.mjs +``` + +JavaScript uses xlang mode. Continue with +[JavaScript/TypeScript Object Serialization](../object-serialization/javascript/index.md), +[xlang types](../object-serialization/javascript/xlang.md), +[configuration](../object-serialization/javascript/configuration.md), and +[schema evolution](../object-serialization/javascript/schema-evolution.md). + +For the optional Node.js string fast path, install the matching package version: + +```bash +npm install @apache-fory/core@1.5.0 @apache-fory/hps@1.5.0 +``` + +## Other Capabilities + +- **Fory IDL and Compiler** generates TypeScript interfaces, schemas, and registration helpers. See [Compiler Getting Started](../compiler/getting-started.md) and the [JavaScript generated-code guide](../compiler/generated-code/javascript.md). +- **Fory gRPC** supports Node.js gRPC and browser gRPC-Web transports with Fory-encoded messages. See [JavaScript gRPC](../grpc/javascript.md). diff --git a/docs/start/kotlin.md b/docs/start/kotlin.md index 22139e7169..ad9768d65d 100644 --- a/docs/start/kotlin.md +++ b/docs/start/kotlin.md @@ -19,7 +19,8 @@ license: | limitations under the License. --- -Fory Kotlin artifacts are published to Maven Central and run on Fory Java. Fory core supports Java 8 and later. Keep Kotlin, Java core, and generated-code artifacts in one application on the same Fory release. +Fory Kotlin provides binary Object Serialization, generated models, Fory gRPC, +and Android support. It runs on Fory Java and supports Java 8 and later. ## Verify the Toolchain @@ -29,13 +30,51 @@ java -version # or: mvn -version ``` -## Choose a Capability +## Object Serialization -| Capability | Artifact or tool | Continue with | -| ---------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Object Serialization | `fory-kotlin` | [Kotlin object serialization](../object-serialization/kotlin/index.md), then choose [xlang](../object-serialization/kotlin/xlang.md) or [native](../object-serialization/kotlin/native.md) | -| Schema and generated models | `fory-compiler` and Kotlin KSP support | [Fory IDL and Compiler](../compiler/index.md) | -| Fory gRPC | generated coroutine companions plus grpc-kotlin | [Kotlin gRPC](../grpc/kotlin.md) | -| Android object serialization | `fory-kotlin` plus generated serializers | [Android](../object-serialization/java/android.md) | +Add the runtime to the application module: -Each capability guide owns its exact Maven or Gradle declaration and first runnable example. +```kotlin title="build.gradle.kts" +dependencies { + implementation("org.apache.fory:fory-kotlin:1.5.0") +} +``` + +Create `src/main/kotlin/KotlinExample.kt`: + +```kotlin +import org.apache.fory.ThreadSafeFory +import org.apache.fory.kotlin.ForyKotlin + +data class User(val id: Long, val name: String) + +fun main() { + val fory: ThreadSafeFory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .buildThreadSafeFory() + fory.register(User::class.java, 1) + + val bytes = fory.serialize(User(1, "Alice")) + val decoded = fory.deserialize(bytes) as User + println(decoded.name) +} +``` + +If the project applies Gradle's `application` plugin, run its application task: + +```bash +./gradlew run +``` + +Use xlang mode for data shared with other Fory runtimes or native mode for +Kotlin/JVM-only data. Continue with +[Kotlin Object Serialization](../object-serialization/kotlin/index.md), +[xlang](../object-serialization/kotlin/xlang.md), or +[native mode](../object-serialization/kotlin/native.md). + +## Other Capabilities + +- **Fory IDL and Compiler** generates Kotlin models and registration helpers through KSP. See [Compiler Getting Started](../compiler/getting-started.md) and the [Kotlin generated-code guide](../compiler/generated-code/kotlin.md). +- **Fory gRPC** uses grpc-kotlin and grpc-java transports with Fory-encoded messages. See [Kotlin gRPC](../grpc/kotlin.md). +- **Android** uses generated serializers with the same Fory Kotlin runtime. See [Android Object Serialization](../object-serialization/java/android.md). diff --git a/docs/start/python.md b/docs/start/python.md index cd9c233121..4f3d03a9d2 100644 --- a/docs/start/python.md +++ b/docs/start/python.md @@ -19,7 +19,9 @@ license: | limitations under the License. --- -Python packages are published on PyPI. `pyfory` supports Python 3.8 and later on Linux, macOS, and Windows. Use one compatible Fory release across every peer in an application. +`pyfory` provides binary Object Serialization and Row Format. Fory IDL can also +generate Python models and gRPC companions. The package supports Python 3.8 and +later on Linux, macOS, and Windows. ## Verify the Toolchain @@ -28,13 +30,45 @@ python --version python -m pip --version ``` -## Choose a Capability +## Object Serialization -| Capability | Package or extra | Continue with | -| --------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Object Serialization | `pyfory` | [Python object serialization](../object-serialization/python/index.md), then choose [xlang](../object-serialization/python/xlang.md) or [native](../object-serialization/python/native.md) | -| Row Format | `pyfory[format]` | [Python Row Format](../row-format/python.md) | -| Schema and generated models | `fory-compiler` | [Fory IDL and Compiler](../compiler/index.md) | -| Fory gRPC | `pyfory` plus `grpcio` | [Python gRPC](../grpc/python.md) | +Install the released package: -Each capability guide owns its exact install command and first runnable example. +```bash +python -m pip install pyfory==1.5.0 +``` + +Run an xlang round trip: + +```python +from dataclasses import dataclass + +import pyfory + + +@dataclass +class User: + name: str + age: pyfory.Int32 + + +fory = pyfory.Fory(xlang=True, ref=True) +fory.register(User, type_id=1) + +data = fory.serialize(User("Alice", 30)) +decoded = fory.deserialize(data) +print(decoded) +``` + +Use [xlang mode](../object-serialization/python/xlang.md) for cross-language +data. Use [native mode](../object-serialization/python/native.md) for Python-only +objects, including Python callables and serialization hooks. Continue with the +[Python guide](../object-serialization/python/index.md), +[configuration](../object-serialization/python/configuration.md), and +[type registration](../object-serialization/python/type-registration.md). + +## Other Capabilities + +- **Row Format** provides zero-copy field access for trusted analytical data. See [Python Row Format](../row-format/python.md). +- **Fory IDL and Compiler** generates Python models and registration helpers. See [Compiler Getting Started](../compiler/getting-started.md) and the [Python generated-code guide](../compiler/generated-code/python.md). +- **Fory gRPC** uses grpcio transports with Fory-encoded messages. See [Python gRPC](../grpc/python.md). diff --git a/docs/start/rust.md b/docs/start/rust.md index 9ae691e426..30f4303c74 100644 --- a/docs/start/rust.md +++ b/docs/start/rust.md @@ -19,7 +19,9 @@ license: | limitations under the License. --- -Fory Rust is published on crates.io. The workspace minimum supported Rust version is 1.70 and uses the Rust 2021 edition. Use one compatible Fory release across every peer in an application. +Fory Rust provides xlang and native Object Serialization, standard Row Format, +generated models, and Fory gRPC. The public `fory` crate is published on +crates.io, supports Rust 1.70 or later, and uses the Rust 2021 edition. ## Verify the Toolchain @@ -28,13 +30,47 @@ rustc --version cargo --version ``` -## Choose a Capability +## Object Serialization -| Capability | Crate or tool | Continue with | -| --------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Object Serialization | `fory` | [Rust object serialization](../object-serialization/rust/index.md), then choose [xlang](../object-serialization/rust/xlang.md) or [native](../object-serialization/rust/native.md) | -| Standard Row Format | `fory` | [Rust Row Format](../row-format/rust.md) | -| Schema and generated models | `fory-compiler` | [Fory IDL and Compiler](../compiler/index.md) | -| Fory gRPC | generated companions plus `tonic` and `bytes` | [Rust gRPC](../grpc/rust.md) | +Add the public crate: -Each capability guide owns its exact dependency declaration and first runnable example. +```toml title="Cargo.toml" +[dependencies] +fory = "1.5.0" +``` + +```rust +use fory::{Error, Fory, ForyStruct}; + +#[derive(ForyStruct, Debug, PartialEq)] +struct User { + id: i64, + name: String, +} + +fn main() -> Result<(), Error> { + let mut fory = Fory::builder().xlang(true).build(); + fory.register::(1)?; + + let user = User { + id: 1, + name: "Alice".to_string(), + }; + let bytes = fory.serialize(&user)?; + let decoded: User = fory.deserialize(&bytes)?; + assert_eq!(user, decoded); + Ok(()) +} +``` + +Use [xlang mode](../object-serialization/rust/xlang.md) for cross-language data +and [native mode](../object-serialization/rust/native.md) for Rust-only data. +Continue with [Rust Object Serialization](../object-serialization/rust/index.md), +[configuration](../object-serialization/rust/configuration.md), and +[type registration](../object-serialization/rust/type-registration.md). + +## Other Capabilities + +- **Row Format** provides zero-copy views over trusted analytical data using the standard Fory Row layout. See [Rust Row Format](../row-format/rust.md). +- **Fory IDL and Compiler** generates Rust models and registration helpers. See [Compiler Getting Started](../compiler/getting-started.md) and the [Rust generated-code guide](../compiler/generated-code/rust.md). +- **Fory gRPC** uses tonic transports with Fory-encoded messages. See [Rust gRPC](../grpc/rust.md). diff --git a/docs/start/scala.md b/docs/start/scala.md index 6101958a17..974038f1a5 100644 --- a/docs/start/scala.md +++ b/docs/start/scala.md @@ -19,7 +19,9 @@ license: | limitations under the License. --- -Fory Scala artifacts are published to Maven Central for Scala 2.13 and Scala 3. Schema-generated Scala sources and macro-derived xlang serializers require Scala 3. The runtime uses Fory Java, so keep all Fory artifacts on the same release. +Fory Scala provides binary Object Serialization, generated models, and Fory +gRPC. The runtime artifact supports Scala 2.13 and Scala 3; generated Scala +models require Scala 3. ## Verify the Toolchain @@ -29,12 +31,48 @@ scala -version sbt --version ``` -## Choose a Capability +## Object Serialization -| Capability | Artifact or tool | Continue with | -| --------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Object Serialization | `fory-scala_2.13` or `fory-scala_3` | [Scala object serialization](../object-serialization/scala/index.md), then choose [xlang](../object-serialization/scala/xlang.md) or [native](../object-serialization/scala/native.md) | -| Schema and generated models | `fory-compiler`; Scala 3 output | [Fory IDL and Compiler](../compiler/index.md) | -| Fory gRPC | Scala 3 generated companions plus grpc-java | [Scala gRPC](../grpc/scala.md) | +Add the runtime to `build.sbt`: -Each capability guide owns its exact Maven or sbt declaration and first runnable example. +```sbt +ThisBuild / scalaVersion := "3.3.1" +libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.5.0" +``` + +Create `src/main/scala/ScalaExample.scala`: + +```scala +import org.apache.fory.Fory +import org.apache.fory.scala.ForyScala + +case class User(id: Long, name: String) + +object ScalaExample { + def main(args: Array[String]): Unit = { + val fory: Fory = ForyScala.builder() + .withXlang(true) + .build() + fory.register(classOf[User], 1) + + val bytes = fory.serialize(User(1, "Alice")) + val decoded = fory.deserialize(bytes).asInstanceOf[User] + println(decoded.name) + } +} +``` + +```bash +sbt run +``` + +Use xlang mode for data shared with other Fory runtimes or native mode for +Scala/JVM-only data. Continue with +[Scala Object Serialization](../object-serialization/scala/index.md), +[xlang](../object-serialization/scala/xlang.md), or +[native mode](../object-serialization/scala/native.md). + +## Other Capabilities + +- **Fory IDL and Compiler** generates Scala 3 models and registration helpers. See [Compiler Getting Started](../compiler/getting-started.md) and the [Scala generated-code guide](../compiler/generated-code/scala.md). +- **Fory gRPC** uses grpc-java transports with Fory-encoded messages. See [Scala gRPC](../grpc/scala.md). diff --git a/docs/start/swift.md b/docs/start/swift.md index af494457c8..7118e7d9c7 100644 --- a/docs/start/swift.md +++ b/docs/start/swift.md @@ -19,7 +19,9 @@ license: | limitations under the License. --- -Fory Swift is distributed through Swift Package Manager from the Apache Fory repository. The current package uses Swift tools 6.0 and targets macOS 13 or later and iOS 16 or later. Pin one Fory release across every peer in an application. +Fory Swift provides xlang Object Serialization and compiler-generated models. +It is distributed through Swift Package Manager, uses Swift tools 6.0, and +targets macOS 13 or later and iOS 16 or later. ## Verify the Toolchain @@ -27,11 +29,58 @@ Fory Swift is distributed through Swift Package Manager from the Apache Fory rep swift --version ``` -## Choose a Capability +## Object Serialization -| Capability | Package product or tool | Continue with | -| --------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| Object Serialization | Swift package product `Fory` | [Swift object serialization](../object-serialization/swift/index.md) and [xlang](../object-serialization/swift/xlang.md) | -| Schema and generated models | `fory-compiler` | [Fory IDL and Compiler](../compiler/index.md) | +Create an executable package: -The selected capability guide owns its exact package declaration and first runnable example. +```bash +swift package init --type executable --name ForyExample +``` + +Add the released package and `Fory` product to the generated `Package.swift`: + +```swift title="Package.swift" +dependencies: [ + .package(url: "https://github.com/apache/fory.git", exact: "1.5.0") +], +targets: [ + .executableTarget( + name: "ForyExample", + dependencies: [.product(name: "Fory", package: "fory")] + ) +] +``` + +Replace `Sources/main.swift` with: + +```swift title="Sources/main.swift" +import Fory + +@ForyStruct +struct User: Equatable { + var id: Int64 = 0 + var name: String = "" +} + +let fory = Fory() +try fory.register(User.self, id: 1) + +let input = User(id: 1, name: "Alice") +let bytes = try fory.serialize(input) +let decoded: User = try fory.deserialize(bytes) +assert(input == decoded) +``` + +```bash +swift run +``` + +Swift uses xlang mode. Continue with +[Swift Object Serialization](../object-serialization/swift/index.md), +[xlang types](../object-serialization/swift/xlang.md), +[configuration](../object-serialization/swift/configuration.md), and +[schema evolution](../object-serialization/swift/schema-evolution.md). + +## Other Capabilities + +- **Fory IDL and Compiler** generates Swift models and registration helpers. See [Compiler Getting Started](../compiler/getting-started.md) and the [Swift generated-code guide](../compiler/generated-code/swift.md). From 9ee2933e7c32fc01d4186d5c7d482db16324be9a Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 09:45:29 +0800 Subject: [PATCH 02/15] docs: fix Java start guide link --- docs/start/java.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/start/java.md b/docs/start/java.md index 54ee81df08..9bb6ce224b 100644 --- a/docs/start/java.md +++ b/docs/start/java.md @@ -125,6 +125,6 @@ object mapping, annotations, Android, GraalVM, and security. ## Runtime Notes - On JDK 25 and later, follow the setup in - [Java Object Serialization](../object-serialization/java/index.md#jdk-25-and-later). + [Java Object Serialization](../object-serialization/java/index.md). - For Android, see [Java Android support](../object-serialization/java/android.md). - For native images, see [Java GraalVM support](../object-serialization/java/graalvm.md). From 790267798caa8d894d4d95a78cd3974736ed9f23 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 10:01:30 +0800 Subject: [PATCH 03/15] fix: address getting started review findings --- .github/workflows/ci.yml | 2 ++ ci/release.py | 16 +++++++++++++--- ci/test_release.py | 6 ++++++ docs/start/java.md | 4 +++- docs/start/javascript.md | 6 +++--- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db809611bb..5585589a19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2011,6 +2011,8 @@ jobs: uses: actions/setup-python@v5 with: python-version: 3.8 + - name: Test release documentation updater + run: python -m unittest ci.test_release - name: Set up .NET 8 uses: actions/setup-dotnet@v4 with: diff --git a/ci/release.py b/ci/release.py index e48c49da1f..258836eeb4 100644 --- a/ci/release.py +++ b/ci/release.py @@ -1237,6 +1237,19 @@ def _update_release_doc_lines(lines, release_version): def _update_release_doc_line(line, release_version): + scoped_patterns = ( + r"(\bpyfory(?:\[[^\]]+\])?==)" + VERSION_PATTERN, + r"(\bgithub\.com/apache/fory/go/fory@v)" + VERSION_PATTERN, + r"(@apache-fory/(?:core|hps)@)" + VERSION_PATTERN, + ) + scoped_update = line + has_scoped_dependency = False + for pattern in scoped_patterns: + if re.search(pattern, scoped_update): + has_scoped_dependency = True + scoped_update = re.sub(pattern, r"\g<1>" + release_version, scoped_update) + if has_scoped_dependency: + return scoped_update if not _is_release_doc_line(line): return line if "crates.io-v" in line: @@ -1255,9 +1268,6 @@ def _is_release_doc_line(line): or "org.apache.fory" in line or "Apache.Fory" in line or "dart pub add fory" in line - or re.search(r"\bpyfory(?:\[[^\]]+\])?==" + VERSION_PATTERN, line) - or re.search(r"\bgithub\.com/apache/fory/go/fory@v" + VERSION_PATTERN, line) - or re.search(r"@apache-fory/(?:core|hps)@" + VERSION_PATTERN, line) or re.search(r"^\s*fory\s*[:=]", line) or "https://github.com/apache/fory.git" in line or 'bazel_dep(name = "fory"' in line diff --git a/ci/test_release.py b/ci/test_release.py index cd4444404d..f06acf3bbb 100644 --- a/ci/test_release.py +++ b/ci/test_release.py @@ -30,6 +30,9 @@ def test_updates_release_doc_dependencies(self): 'python -m pip install "pyfory[format]==1.5.0"\n', "go get github.com/apache/fory/go/fory@v1.5.0\n", "npm install @apache-fory/core@1.5.0 @apache-fory/hps@1.5.0\n", + "python -m pip install pyfory==1.5.0 grpcio==1.73.0 # Fory 1.4.0\n", + "go get github.com/apache/fory/go/fory@v1.5.0 # Fory 1.4.0\n", + "npm install @apache-fory/core@1.5.0 grpc@1.73.0 @apache-fory/hps@1.5.0\n", 'fory = "1.5.0"\n', " fory: 1.5.0\n", ' .package(url: "https://github.com/apache/fory.git", exact: "1.5.0")\n', @@ -43,6 +46,9 @@ def test_updates_release_doc_dependencies(self): 'python -m pip install "pyfory[format]==1.6.0"\n', "go get github.com/apache/fory/go/fory@v1.6.0\n", "npm install @apache-fory/core@1.6.0 @apache-fory/hps@1.6.0\n", + "python -m pip install pyfory==1.6.0 grpcio==1.73.0 # Fory 1.4.0\n", + "go get github.com/apache/fory/go/fory@v1.6.0 # Fory 1.4.0\n", + "npm install @apache-fory/core@1.6.0 grpc@1.73.0 @apache-fory/hps@1.6.0\n", 'fory = "1.6.0"\n', " fory: 1.6.0\n", ' .package(url: "https://github.com/apache/fory.git", exact: "1.6.0")\n', diff --git a/docs/start/java.md b/docs/start/java.md index 9bb6ce224b..1bf4ecd4c4 100644 --- a/docs/start/java.md +++ b/docs/start/java.md @@ -83,7 +83,9 @@ public final class ForyExample { } ``` -Reuse a `Fory` instance instead of rebuilding it for every value. Continue with +Reuse a `Fory` instance within one thread instead of rebuilding it for every +value. `Fory` is not thread-safe; use `ThreadSafeFory` for shared concurrent +access. Continue with [Java Object Serialization](../object-serialization/java/index.md), [xlang mode](../object-serialization/java/xlang.md), [native mode](../object-serialization/java/native.md), or diff --git a/docs/start/javascript.md b/docs/start/javascript.md index 3e3cc755db..693b78632c 100644 --- a/docs/start/javascript.md +++ b/docs/start/javascript.md @@ -41,8 +41,8 @@ npm install @apache-fory/core@1.5.0 Define a schema and run an xlang round trip: -```js title="example.mjs" -import Fory, { Type } from "@apache-fory/core"; +```js title="example.cjs" +const { default: Fory, Type } = require("@apache-fory/core"); const userType = Type.struct( { typeName: "example.User" }, @@ -60,7 +60,7 @@ console.log(deserialize(bytes)); ``` ```bash -node example.mjs +node example.cjs ``` JavaScript uses xlang mode. Continue with From 3e4bd65e66b110ddae592e1a93f44c22dffb80c8 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 10:44:46 +0800 Subject: [PATCH 04/15] docs: consolidate format and grpc guidance --- .../xlang/swift/README.md | 4 +- docs/grpc/architecture.md | 41 ---------------- docs/grpc/index.md | 47 +++++++++++++++++-- docs/grpc/interoperability.md | 41 ---------------- docs/introduction/choose-a-format.md | 25 +++++++--- docs/introduction/product-model.md | 47 ------------------- docs/introduction/support-matrix.md | 6 +-- docs/object-serialization/java/index.md | 2 +- docs/object-serialization/runtimes.md | 2 +- docs/start/index.md | 2 +- docs/start/swift.md | 2 +- 11 files changed, 72 insertions(+), 147 deletions(-) delete mode 100644 docs/grpc/architecture.md delete mode 100644 docs/grpc/interoperability.md delete mode 100644 docs/introduction/product-model.md diff --git a/docs/benchmarks/object-serialization/xlang/swift/README.md b/docs/benchmarks/object-serialization/xlang/swift/README.md index f3322f902b..91d4568221 100644 --- a/docs/benchmarks/object-serialization/xlang/swift/README.md +++ b/docs/benchmarks/object-serialization/xlang/swift/README.md @@ -2,11 +2,11 @@ This benchmark compares serialization and deserialization throughput for Apache Fory, Protocol Buffers, and JSON in Swift. -## Benchmark Products +## Benchmark Executables The ordinary/xlang cases are built by `swift-benchmark`. External-type and carrier comparisons are built by the separate `swift-external-benchmark` -product, so building the ordinary product does not compile those models or +executable, so building the ordinary executable does not compile those models or serializer specializations. ## Throughput Plot diff --git a/docs/grpc/architecture.md b/docs/grpc/architecture.md deleted file mode 100644 index ffe4c30162..0000000000 --- a/docs/grpc/architecture.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: Architecture -sidebar_position: 2 -id: architecture -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Generated service companions use normal gRPC servers, channels, method descriptors, deadlines, -status codes, interceptors, and streaming APIs. Fory-generated marshallers encode and decode the -generated request and response models. - -## Ownership boundary - -Fory can generate service companions for application-provided gRPC runtimes. -Those companions provide Fory serialization for request and response objects; -the application and gRPC stack still own listeners, channels, credentials, -authentication, authorization, deadlines, retries, and transport lifecycle. - -Fory packages do not add a gRPC implementation as a hard dependency. The application selects and -configures the runtime's gRPC libraries. - -## Generated service surface - -The compiler emits runtime-idiomatic service bases, clients or stubs, method metadata, and Fory -marshallers. Model generation is documented under -[Generated Code](../compiler/generated-code/index.md); the runtime pages document server and client -integration. diff --git a/docs/grpc/index.md b/docs/grpc/index.md index 5e019a79ff..6fef383aee 100644 --- a/docs/grpc/index.md +++ b/docs/grpc/index.md @@ -54,8 +54,52 @@ generation with `--grpc-python-mode=sync`. JavaScript uses `@grpc/grpc-js` for Node.js; browser clients are generated separately with `--grpc-web` and use `grpc-web`. +## Architecture + +Generated service companions use normal gRPC servers, channels, method descriptors, deadlines, +status codes, interceptors, and streaming APIs. Fory-generated marshallers encode and decode the +generated request and response models. + +### Ownership Boundary + +Fory can generate service companions for application-provided gRPC runtimes. Those companions +provide Fory serialization for request and response objects; the application and gRPC stack still +own listeners, channels, credentials, authentication, authorization, deadlines, retries, and +transport lifecycle. + +Fory packages do not add a gRPC implementation as a hard dependency. The application selects and +configures the runtime's gRPC libraries. + +### Generated Service Surface + +The compiler emits runtime-idiomatic service bases, clients or stubs, method metadata, and Fory +marshallers. Model generation is documented under +[Generated Code](../compiler/generated-code/index.md); the runtime pages document server and client +integration. + +## Interoperability + +Fory gRPC peers interoperate only when they use the same generated service contract, matching Fory +type identities, and compatible generated model schemas. + +### Protocol Boundary + +The transport is gRPC, but the message bytes are Fory payloads. Generic protobuf clients and server +reflection tools cannot decode those payloads as protobuf messages. Generate every peer through a +supported Fory compiler frontend. + +### Verification + +Test at least one unary call and every streaming shape used by the service. A protobuf +`UNIMPLEMENTED` or decode failure usually means the peer used an ordinary protobuf stub or a +different generated service contract. + ## Runtime Guides +Java, Python, C++, Go, Rust, JavaScript/TypeScript, C#, Dart, Scala, and Kotlin have documented gRPC +companions. Use the [support matrix](../introduction/support-matrix.md) and the selected runtime page +for current dependencies and streaming support. + | Runtime | Guide | | --------------------- | ------------------------------------- | | Java | [Java](java.md) | @@ -68,6 +112,3 @@ Node.js; browser clients are generated separately with `--grpc-web` and use | Dart | [Dart](dart.md) | | Scala | [Scala](scala.md) | | Kotlin | [Kotlin](kotlin.md) | - -See [Architecture](architecture.md) for payload and transport ownership and -[Interoperability](interoperability.md) for peer compatibility. diff --git a/docs/grpc/interoperability.md b/docs/grpc/interoperability.md deleted file mode 100644 index 1de36c4885..0000000000 --- a/docs/grpc/interoperability.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: Interoperability -sidebar_position: 3 -id: interoperability -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Fory gRPC peers interoperate only when they use the same generated service contract, matching Fory -type identities, and compatible generated model schemas. - -## Protocol boundary - -The transport is gRPC, but the message bytes are Fory payloads. Generic protobuf clients and server -reflection tools cannot decode those payloads as protobuf messages. Generate every peer through a -supported Fory compiler frontend. - -## Supported generated companions - -Java, Python, C++, Go, Rust, JavaScript/TypeScript, C#, Dart, Scala, and Kotlin have documented gRPC -companions. Use the [support matrix](../introduction/support-matrix.md) and the selected runtime page -for current dependencies and streaming support. - -## Verification - -Test at least one unary call and every streaming shape used by the service. A protobuf -`UNIMPLEMENTED` or decode failure usually means the peer used an ordinary protobuf stub or a -different generated service contract. diff --git a/docs/introduction/choose-a-format.md b/docs/introduction/choose-a-format.md index 0d7225c6a4..75cee3efce 100644 --- a/docs/introduction/choose-a-format.md +++ b/docs/introduction/choose-a-format.md @@ -19,12 +19,16 @@ license: | limitations under the License. --- -| Format | Use it when | Start here | -| ------------- | ------------------------------------------------------------- | -------------------------------------------------------------- | -| Xlang binary | Data crosses language boundaries | [Cross-language guide](../object-serialization/xlang/index.md) | -| Native binary | Producer and consumer are in the same language | Language guide | -| Row format | You need random field access or analytics-style partial reads | [Row format spec](../row-format/index.md) | -| Fory JSON | Java applications need high-performance standard JSON | [Fory JSON guide](../json/index.md) | +| Format or mode | Data model | Use it when | Interoperability | Start here | +| -------------- | ------------------------------------ | ------------------------------------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------- | +| Xlang binary | Portable object graphs | Data crosses language boundaries | Shared wire format across supported runtimes | [Cross-language guide](../object-serialization/xlang/index.md) | +| Native binary | Runtime-native object graphs | Producer and consumer use the same language | Same runtime family only | [Object Serialization](../object-serialization/index.md) | +| Row Format | Random-access binary rows | You need random field access or analytics-style partial reads | Standard Row is shared by Java, Python, C++, and Rust; Compact is Java-only | [Row Format guide](../row-format/index.md) | +| Fory JSON | Standard JSON mapped to Java objects | Java applications need high-performance standard JSON | Standard JSON text | [Fory JSON guide](../json/index.md) | + +Xlang and native are sibling modes of Object Serialization. Use them when the receiver needs to +reconstruct an object graph. Row Format and Fory JSON are separate formats, not additional Object +Serialization modes. For Java, Scala, Kotlin, Python, C++, Go, and Rust, use native mode for same-language traffic. It avoids xlang's cross-language type mapping and @@ -59,3 +63,12 @@ Choose xlang or native mode when you need to reconstruct object graphs. Choose R trusted analytical data that benefits from random field access. Choose Fory JSON for standard JSON in Java applications. Use Fory IDL and the compiler when multiple teams need one schema-first contract; it generates models that use the relevant Fory capability. + +## Related capabilities + +[Fory IDL and the compiler](../compiler/index.md) generate native models for supported runtimes. +Service definitions can also generate [Fory gRPC](../grpc/index.md) code. Neither capability defines +another serialization format. + +Protocol implementers should follow the normative format documentation under +[Specifications](../specification/xlang_serialization_spec.md). diff --git a/docs/introduction/product-model.md b/docs/introduction/product-model.md deleted file mode 100644 index 3379dd6b52..0000000000 --- a/docs/introduction/product-model.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Product Model -sidebar_position: 3 -id: product-model -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Apache Fory exposes three serialization products and one schema toolchain. Start from the data -contract you need, then choose a runtime API. - -## Serialization products - -| Product | Data model | Interoperability boundary | -| ---------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------- | -| Binary Object Serialization: xlang mode | Portable object graphs | Shared wire format across supported runtimes | -| Binary Object Serialization: native mode | Runtime-native object graphs | Same runtime family only | -| Row Format | Random-access binary rows | Standard Row is shared by Java, Python, C++, and Rust; Compact Row is Java-only | -| Fory JSON | Standard JSON mapped to Java objects | Java API with text interoperability | - -Xlang and native are sibling modes of Binary Object Serialization. Row Format and Fory JSON are -separate products; neither is a third object-serialization mode. - -## Schema and services - -[Fory IDL and the compiler](../compiler/index.md) generate native models for supported runtimes. -Service definitions can also generate [Fory gRPC](../grpc/index.md) companions. The compiler and -gRPC integration do not define additional serialization formats. - -## Normative formats - -Protocol implementers should use the unchanged [Specification](../specification/xlang_serialization_spec.md) -surface. User guides explain tasks and link to the exact specification instead of duplicating its -wire-level rules. diff --git a/docs/introduction/support-matrix.md b/docs/introduction/support-matrix.md index 5127bd48fb..7fee55a395 100644 --- a/docs/introduction/support-matrix.md +++ b/docs/introduction/support-matrix.md @@ -20,7 +20,7 @@ license: | --- Use this matrix to confirm the documented API surface before choosing a capability. A runtime page -does not imply support for every Fory product. +does not imply support for every Fory capability. | Capability | Documented runtimes | Interoperability | | --------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------- | @@ -33,5 +33,5 @@ does not imply support for every Fory product. | Fory gRPC | Java, Python, C++, Go, Rust, JavaScript/TypeScript, C#, Dart, Scala, Kotlin | Peers must use matching generated Fory service contracts | Platform constraints such as [Android](../object-serialization/java/android.md) and -[GraalVM Native Image](../object-serialization/java/graalvm.md) are documented by the owning Java -object-serialization and Fory JSON products. +[GraalVM Native Image](../object-serialization/java/graalvm.md) are documented in the Java Object +Serialization and Fory JSON guides. diff --git a/docs/object-serialization/java/index.md b/docs/object-serialization/java/index.md index 88a69b87ef..ea3a0f3d9a 100644 --- a/docs/object-serialization/java/index.md +++ b/docs/object-serialization/java/index.md @@ -22,7 +22,7 @@ license: | Apache Fory Java provides high-performance binary object serialization. Use xlang mode for payloads shared with other supported runtimes and native mode for Java/JVM-only object graphs. -This runtime guide is scoped to Binary Object Serialization. For other Java products, use +This runtime guide is scoped to Binary Object Serialization. For other Java capabilities, use [Row Format](../../row-format/java.md), [Fory JSON](../../json/index.md), [Fory IDL and compiler](../../compiler/index.md), or [Fory gRPC](../../grpc/java.md). diff --git a/docs/object-serialization/runtimes.md b/docs/object-serialization/runtimes.md index c820c929d9..491608e543 100644 --- a/docs/object-serialization/runtimes.md +++ b/docs/object-serialization/runtimes.md @@ -21,7 +21,7 @@ license: | Choose the runtime whose API your application uses. Runtime documentation is scoped to binary object serialization; Row Format, Fory JSON, compiler, and gRPC pages remain with -their product owners. +their respective capability guides. | Runtime | Modes | Documentation | | --------------------- | -------------------- | ------------------------------------------------------ | diff --git a/docs/start/index.md b/docs/start/index.md index 3575ff456a..898a0e86a7 100644 --- a/docs/start/index.md +++ b/docs/start/index.md @@ -68,4 +68,4 @@ round trip for an application project, and the next capability-specific steps: Object Serialization uses xlang mode for portable cross-language data. Java, Python, C++, Go, Rust, Scala, and Kotlin also offer native mode for same-runtime data. Use [Choose a Format](../introduction/choose-a-format.md) -when the product choice is not yet clear. +when you have not yet chosen a format. diff --git a/docs/start/swift.md b/docs/start/swift.md index 7118e7d9c7..4c086bdd89 100644 --- a/docs/start/swift.md +++ b/docs/start/swift.md @@ -37,7 +37,7 @@ Create an executable package: swift package init --type executable --name ForyExample ``` -Add the released package and `Fory` product to the generated `Package.swift`: +Add the released package and depend on its `Fory` library in the generated `Package.swift`: ```swift title="Package.swift" dependencies: [ From 56ed78bd15031128f4ab80931fcb1fc7a4a0cb8b Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 10:46:22 +0800 Subject: [PATCH 05/15] docs: consolidate development guide --- README.md | 6 +- docs/development/building.md | 135 ----------------------------------- docs/development/index.md | 120 +++++++++++++++++++++++++++++-- 3 files changed, 119 insertions(+), 142 deletions(-) delete mode 100644 docs/development/building.md diff --git a/README.md b/README.md index dc5d5cd4a4..6ab0c92a3c 100644 --- a/README.md +++ b/README.md @@ -305,7 +305,7 @@ serializer setup. **Development From Source** -See [docs/development/building.md](docs/development/building.md). +See [docs/development/index.md](docs/development/index.md). Snapshots for Java, Scala, and Kotlin are available from `https://repository.apache.org/snapshots/` with the matching `-SNAPSHOT` version. @@ -913,7 +913,7 @@ security, and platform guides. | Schema IDL/compiler | [docs/compiler](docs/compiler) | [View](https://fory.apache.org/docs/compiler/) | | GraalVM native image | [docs/object-serialization/java/graalvm.md](docs/object-serialization/java/graalvm.md) | [View](https://fory.apache.org/docs/object-serialization/java/graalvm) | | Android | [docs/object-serialization/java/android.md](docs/object-serialization/java/android.md) | [View](https://fory.apache.org/docs/object-serialization/java/android) | -| Development | [docs/development/building.md](docs/development/building.md) | [View](docs/development/building.md) | +| Development | [docs/development/index.md](docs/development/index.md) | [View](docs/development/index.md) | **Specifications** @@ -934,7 +934,7 @@ security, and platform guides. ## Contributing Read [CONTRIBUTING.md](CONTRIBUTING.md) and -[docs/development/building.md](docs/development/building.md) before sending pull requests. Bug +[docs/development/index.md](docs/development/index.md) before sending pull requests. Bug reports, docs fixes, tests, benchmarks, and implementation improvements are welcome. ## License diff --git a/docs/development/building.md b/docs/development/building.md deleted file mode 100644 index 4e6bb13846..0000000000 --- a/docs/development/building.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: Development -sidebar_position: 20 -id: building -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -## How to build Apache Fory™ - -Clone the source tree from https://github.com/apache/fory. - -### Build Apache Fory™ Java - -```bash -cd java -mvn -T16 package -``` - -#### Environment Requirements - -- JDK 17+ -- Maven 3.6.3+ - -### Build Apache Fory™ Python - -```bash -cd python -pip install -v -e . - -# Optional: build Cython extension (replace X.Y with your Python version) -bazel build //:cp_fory_so --@rules_python//python/config_settings:python_version=X.Y -``` - -#### Environment Requirements - -- CPython 3.8+ -- Bazel 8+ (required when building Cython extensions) - -### Build Apache Fory™ C++ - -```bash -cd cpp -bazel build //cpp/... -``` - -#### Environment Requirements - -- C++17 compiler -- Bazel 8+ - -### Build Apache Fory™ Go - -```bash -cd go/fory -go test -v ./... -``` - -Run Go xlang tests from Java test module: - -```bash -cd java -mvn -T16 install -DskipTests -cd fory-core -FORY_GO_JAVA_CI=1 ENABLE_FORY_DEBUG_OUTPUT=1 mvn test -Dtest=org.apache.fory.xlang.GoXlangTest -``` - -#### Environment Requirements - -- Go 1.24+ - -### Build Apache Fory™ Rust - -```bash -cd rust -cargo build -cargo test --features tests - -# Debug a specific test -RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 ENABLE_FORY_DEBUG_OUTPUT=1 \ - cargo test --test mod $dir$::$test_file::$test_method -- --nocapture -``` - -#### Environment Requirements - -- Rust toolchain via rustup -- `cargo-expand` (optional, for macro expansion debugging) - -### Build Apache Fory™ JavaScript - -```bash -cd javascript -npm install - -npm run build -node ./node_modules/.bin/jest --ci --reporters=default --reporters=jest-junit -``` - -#### Environment Requirements - -- Node.js (LTS) -- npm - -### Lint Markdown Docs - -```bash -cd docs -npx prettier --write "**/*.md" -``` - -#### Environment Requirements - -- Node.js (LTS) -- npm - -## Contributing - -For contribution details, see [How to contribute to Apache Fory™](https://github.com/apache/fory/blob/main/CONTRIBUTING.md). -For AI-assisted contributions, follow the -[AI Contribution Policy](https://github.com/apache/fory/blob/main/AI_POLICY.md), including the -required self-review, two-reviewer AI review loop, disclosure, and verification evidence for -substantial AI assistance. diff --git a/docs/development/index.md b/docs/development/index.md index 7044b921de..aea0a3e778 100644 --- a/docs/development/index.md +++ b/docs/development/index.md @@ -22,11 +22,123 @@ license: | Contributor documentation covers repository setup, builds, tests, debugging, and release-oriented workflows. It is separate from the user guides for released artifacts. -## Start here +## Build and Test -- [Build and test the repository](building.md) -- [Debug the C++ implementation](cpp-debugging.md) -- [Contributing guide](https://github.com/apache/fory/blob/main/CONTRIBUTING.md) +Clone the source tree from https://github.com/apache/fory. + +### Java + +```bash +cd java +mvn -T16 package +``` + +Requirements: + +- JDK 17+ +- Maven 3.6.3+ + +### Python + +```bash +cd python +pip install -v -e . + +# Optional: build Cython extension (replace X.Y with your Python version) +bazel build //:cp_fory_so --@rules_python//python/config_settings:python_version=X.Y +``` + +Requirements: + +- CPython 3.8+ +- Bazel 8+ (required when building Cython extensions) + +### C++ + +```bash +cd cpp +bazel build //cpp/... +``` + +Requirements: + +- C++17 compiler +- Bazel 8+ + +### Go + +```bash +cd go/fory +go test -v ./... +``` + +Run Go xlang tests from the Java test module: + +```bash +cd java +mvn -T16 install -DskipTests +cd fory-core +FORY_GO_JAVA_CI=1 ENABLE_FORY_DEBUG_OUTPUT=1 mvn test -Dtest=org.apache.fory.xlang.GoXlangTest +``` + +Requirement: Go 1.24+. + +### Rust + +```bash +cd rust +cargo build +cargo test --features tests + +# Debug a specific test +RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 ENABLE_FORY_DEBUG_OUTPUT=1 \ + cargo test --test mod $dir$::$test_file::$test_method -- --nocapture +``` + +Requirements: + +- Rust toolchain via rustup +- `cargo-expand` (optional, for macro expansion debugging) + +### JavaScript + +```bash +cd javascript +npm install + +npm run build +node ./node_modules/.bin/jest --ci --reporters=default --reporters=jest-junit +``` + +Requirements: + +- Node.js (LTS) +- npm + +### Markdown + +```bash +cd docs +npx prettier --write "**/*.md" +``` + +Requirements: + +- Node.js (LTS) +- npm + +## Debugging + +See [Debugging C++](cpp-debugging.md) for the repository's VS Code, Bazel, LLDB, and GDB setup. Runtime-specific contributor instructions remain in each runtime's source tree and contributor README when they are not part of the shared repository build. + +## Contributing + +For contribution details, see +[How to contribute to Apache Fory™](https://github.com/apache/fory/blob/main/CONTRIBUTING.md). +For AI-assisted contributions, follow the +[AI Contribution Policy](https://github.com/apache/fory/blob/main/AI_POLICY.md), including the +required self-review, two-reviewer AI review loop, disclosure, and verification evidence for +substantial AI assistance. From e5bc04df33ec15c9f0d99711aa868584e9e58815 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 10:49:00 +0800 Subject: [PATCH 06/15] docs: consolidate row format overview --- docs/row-format/compact.md | 50 --------------------- docs/row-format/index.md | 90 +++++++++++++++++++++++++++++-------- docs/row-format/rust.md | 2 +- docs/row-format/standard.md | 57 ----------------------- 4 files changed, 73 insertions(+), 126 deletions(-) delete mode 100644 docs/row-format/compact.md delete mode 100644 docs/row-format/standard.md diff --git a/docs/row-format/compact.md b/docs/row-format/compact.md deleted file mode 100644 index dbe2104691..0000000000 --- a/docs/row-format/compact.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Compact Row -sidebar_position: 2 -id: compact -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Compact Row is a Java-only row encoding that reduces fixed-slot and null-bitmap overhead. It is not -wire-compatible with Standard Row. - -## Create a compact encoder - -```java -RowEncoder encoder = - Encoders.buildBeanCodec(MyBean.class) - .compactEncoding() - .build() - .get(); - -BinaryRow row = encoder.toRow(value); -MyBean decoded = encoder.fromRow(row); -``` - -Reuse the encoder within one thread. Create separate encoders for concurrent threads. - -## Layout tradeoffs - -- Fixed-size fields use their natural widths instead of eight-byte Standard Row slots. -- Fields are sorted by alignment to reduce padding. -- The null bitmap is omitted when no field is nullable. -- Fixed-size nested structs can be stored inline. - -Choose Compact Row only when every reader is Java and the space reduction justifies the -Java-specific layout. Use [Standard Row](standard.md) for Java/Python/C++/Rust interchange. - -See the [Row Format specification](../specification/row_format_spec.md) for the exact binary layout. diff --git a/docs/row-format/index.md b/docs/row-format/index.md index 1ebcf6d913..9960f200b4 100644 --- a/docs/row-format/index.md +++ b/docs/row-format/index.md @@ -19,27 +19,81 @@ license: | limitations under the License. --- -Row Format stores typed values in a cache-friendly binary layout for random and partial access -without reconstructing a complete object graph. Use it for analytical and -in-memory data processing. +Row Format is a cache-friendly binary format for efficient random access and partial +deserialization. Unlike object graph serialization, it lets readers access individual fields +without reconstructing the complete object. -## Choose a row family +Row Format is intended only for trusted analytical data, including memory-mapped data, selective +field access, and data pipelines. Use [Object Serialization](../object-serialization/index.md) when +the application needs general object graphs, shared or circular references, or complete object +reconstruction as its primary access pattern. -| Family | Runtime support | Compatibility | -| --------------------------- | ----------------------- | -------------------------------- | -| [Standard Row](standard.md) | Java, Python, C++, Rust | Shared Standard Row layout | -| [Compact Row](compact.md) | Java | Java-only, space-oriented layout | +## Choose a Layout -Use Binary Object Serialization when the goal is complete object reconstruction, references, or -general application messaging. Use Row Format when a workload reads selected fields, nested arrays, -or maps directly from encoded data. +| Layout | Runtime support | Compatibility | +| ------------ | ----------------------- | -------------------------------- | +| Standard Row | Java, Python, C++, Rust | Shared Standard Row layout | +| Compact Row | Java | Java-only, space-oriented layout | -## Runtime guides +## Standard Row -- [Java](java.md) -- [Python](python.md) -- [C++](cpp.md) -- [Rust](rust.md) +Standard Row is the interoperable layout for Java, Python, C++, and Rust. -The normative [Row Format specification](../specification/row_format_spec.md) defines Standard and -Compact layouts. +### Features + +- **Zero-copy random access**: Read selected fields directly from encoded data. +- **Partial deserialization**: Reconstruct only the values an application needs. +- **Cross-language compatibility**: Share Standard Row bytes between Java, Python, C++, and Rust. +- **Apache Arrow integration**: Convert rows to Arrow data in Java and Python. + +### Layout + +Standard Row stores fixed-width values inline and variable-width values by offset and size. Rows, +arrays, and maps use a schema to resolve field positions and element types. The normative byte +layout, alignment rules, type table, and endianness are defined by the +[Row Format specification](../specification/row_format_spec.md). + +### Runtime Support + +| Runtime | Standard Row compatibility | Runtime guide | Additional integration | +| ------- | -------------------------- | ------------------- | ------------------------------------------------------ | +| Java | Compatible | [Java](java.md) | Arrow conversion; interface and extension-type mapping | +| Python | Compatible | [Python](python.md) | PyArrow schema and table conversion | +| C++ | Compatible | [C++](cpp.md) | Native row readers and writers | +| Rust | Compatible | [Rust](rust.md) | Borrowed struct, array, and map views | + +Use the runtime guides for installation, schema construction, encoding, random access, partial +reads, and language-specific integrations. + +## Compact Row + +Compact Row is a Java-only row encoding that reduces fixed-slot and null-bitmap overhead. It is not +wire-compatible with Standard Row. + +### Create a Compact Encoder + +```java +RowEncoder encoder = + Encoders.buildBeanCodec(MyBean.class) + .compactEncoding() + .build() + .get(); + +BinaryRow row = encoder.toRow(value); +MyBean decoded = encoder.fromRow(row); +``` + +Reuse the encoder within one thread. Create separate encoders for concurrent threads. + +### Layout Tradeoffs + +- Fixed-size fields use their natural widths instead of eight-byte Standard Row slots. +- Fields are sorted by alignment to reduce padding. +- The null bitmap is omitted when no field is nullable. +- Fixed-size nested structs can be stored inline. + +Choose Compact Row only when every reader is Java and the space reduction justifies the +Java-specific layout. Use Standard Row for Java, Python, C++, and Rust interchange. + +See the [Row Format specification](../specification/row_format_spec.md) for the exact Standard and +Compact binary layouts. diff --git a/docs/row-format/rust.md b/docs/row-format/rust.md index 33efc184ac..e0efd399ce 100644 --- a/docs/row-format/rust.md +++ b/docs/row-format/rust.md @@ -186,5 +186,5 @@ assert!(scores.get(scores.len()).is_err()); ## Related Topics - [Basic Serialization](../object-serialization/rust/core-api.md) - Object graph serialization -- [Standard Row Format](standard.md) - Shared layout for Java, Python, C++, and Rust +- [Standard Row Format](index.md#standard-row) - Shared layout for Java, Python, C++, and Rust - [Row Format Specification](../specification/row_format_spec.md) - Protocol details diff --git a/docs/row-format/standard.md b/docs/row-format/standard.md deleted file mode 100644 index f7d1b1aa62..0000000000 --- a/docs/row-format/standard.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Standard Row Format -sidebar_position: 1 -id: standard -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Fory Row Format is a cache-friendly binary format for efficient random access -and partial deserialization. Unlike object graph serialization, it lets readers -access individual fields without reconstructing the complete object. - -## Features - -- **Zero-copy random access**: Read selected fields directly from encoded data. -- **Partial deserialization**: Reconstruct only the values an application needs. -- **Cross-language compatibility**: Share Standard Row bytes between Java, - Python, C++, and Rust. -- **Apache Arrow integration**: Convert rows to Arrow data in Java and Python. - -## Format Boundary - -Standard Row stores fixed-width values inline and variable-width values by -offset and size. Rows, arrays, and maps use a schema to resolve field positions -and element types. The normative byte layout, alignment rules, type table, and -endianness are defined by the -[Row Format Specification](../specification/row_format_spec.md). - -Row Format is intended for analytics, memory-mapped data, selective field -access, and data pipelines. Use [Object Serialization](../object-serialization/index.md) -when the application needs general object graphs, shared or circular references, -or complete object reconstruction as its primary access pattern. - -## Implementations - -| Runtime | Standard Row compatibility | Runtime guide | Additional integration | -| ------- | -------------------------- | ------------------- | ------------------------------------------------------ | -| Java | Compatible | [Java](java.md) | Arrow conversion; interface and extension-type mapping | -| Python | Compatible | [Python](python.md) | PyArrow schema and table conversion | -| C++ | Compatible | [C++](cpp.md) | Native row readers and writers | -| Rust | Compatible | [Rust](rust.md) | Borrowed struct, array, and map views | - -Use the runtime guides for installation, schema construction, encoding, random -access, partial reads, and language-specific integrations. From 1415836a1a252a3340344f036e794308964a3f86 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 10:52:08 +0800 Subject: [PATCH 07/15] docs: consolidate object serialization security --- .agents/docs-and-formatting.md | 2 +- AGENTS.md | 6 +- .../deserialization-security-model.md | 712 ------------------ docs/object-serialization/index.md | 16 +- docs/object-serialization/runtimes.md | 38 - docs/object-serialization/security.md | 704 ++++++++++++++++- 6 files changed, 717 insertions(+), 761 deletions(-) delete mode 100644 docs/object-serialization/deserialization-security-model.md delete mode 100644 docs/object-serialization/runtimes.md diff --git a/.agents/docs-and-formatting.md b/.agents/docs-and-formatting.md index 1d1d6da40c..2cc34ce8f5 100644 --- a/.agents/docs-and-formatting.md +++ b/.agents/docs-and-formatting.md @@ -37,7 +37,7 @@ Load this file when changing documentation, public APIs, protocol specs, benchma - Keep user-facing configuration text action focused. Do not put cache ownership, cold/hot path, publish ordering, exact-local schema bypass, or TypeDef/TypeMeta routing invariants there; put implementation invariants in `docs/specification/xlang_implementation_guide.md`, - security classifications in `docs/object-serialization/deserialization-security-model.md`, and agent-only workflow rules in + security classifications in `docs/object-serialization/security.md`, and agent-only workflow rules in `.agents/**`. - Documentation examples should use normal explicit imports, avoid unused imports, and keep common Fory types unqualified where that is the idiom. - Generated Markdown under `docs/benchmarks/**` should satisfy markdownlint blank-line rules at generation time: no repeated blank lines and no extra blank line after final content. diff --git a/AGENTS.md b/AGENTS.md index 6f6dc1a67f..56ea9bf3b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th - `docs/object-serialization/security.md`: user-facing security guidance for binary object serialization. - `docs/json/security.md`: user-facing security guidance for Fory JSON. -- `docs/object-serialization/deserialization-security-model.md`: implementation boundaries for +- `docs/object-serialization/security.md`: implementation boundaries for untrusted deserialization classification. - `.agents/languages/java.md` - `.agents/languages/csharp.md` @@ -34,7 +34,7 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th - Respect ownership. Keep logic, state, and helpers in their natural owner, and do not move serializer-local, context-local, runtime-type-local, or protocol-local problems into global utilities. - Check the spec before implementation. For wire behavior and xlang mapping, use the specs as the source of truth and never copy one runtime's bug into another runtime just to make tests pass. - Do not make assumptions about runtime behavior, ownership, registration, metadata construction, protocol semantics, or test coverage. Read the current code, owning docs/specs, and relevant tests before making a design judgment or implementation decision. If the evidence is incomplete, inspect more or state the uncertainty explicitly instead of filling gaps from memory or analogy with another runtime. -- For untrusted deserialization, read `docs/object-serialization/deserialization-security-model.md` before changing allocation, stream filling, skip, reference, metadata, or policy validation behavior. Variable-length deserialization must not allocate or reserve backing/output capacity from attacker-declared lengths or counts before the byte owner has proven proportional readable bytes with `checkReadableBytes` or the runtime equivalent. Root graph memory reservation is accounting only and may happen before that byte check, but it must not replace the byte check. +- For untrusted deserialization, read `docs/object-serialization/security.md` before changing allocation, stream filling, skip, reference, metadata, or policy validation behavior. Variable-length deserialization must not allocate or reserve backing/output capacity from attacker-declared lengths or counts before the byte owner has proven proportional readable bytes with `checkReadableBytes` or the runtime equivalent. Root graph memory reservation is accounting only and may happen before that byte check, but it must not replace the byte check. - Malformed input must surface as a controlled root-operation error and still run root cleanup, but the exact exception type, error code, message, detection layer, and detection point are not contracts unless a public API or @@ -334,4 +334,4 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th User-facing security guidance lives only under Object Serialization and Fory JSON. Read `docs/object-serialization/security.md` or `docs/json/security.md` for the selected product. Before reporting or changing allocation, stream filling, skip, reference, metadata, or policy validation -behavior, read `docs/object-serialization/deserialization-security-model.md`. +behavior, read `docs/object-serialization/security.md`. diff --git a/docs/object-serialization/deserialization-security-model.md b/docs/object-serialization/deserialization-security-model.md deleted file mode 100644 index 11ba637b17..0000000000 --- a/docs/object-serialization/deserialization-security-model.md +++ /dev/null @@ -1,712 +0,0 @@ ---- -title: Deserialization Security Model -sidebar_position: 99 -id: deserialization-security-model -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -This document defines the security model for Apache Fory deserialization. It is -a public security reference for classifying deserialization behavior and -deciding where validation is required. It is not a vulnerability disclosure, -does not describe exploit techniques, and does not document implementation -history. - -The model is intentionally narrow. Fory should prevent resource and policy -failures caused by untrusted input, but it should not add hot-path validation -that only enforces byte-form strictness when doing so does not protect a Fory -security boundary. - -## Scope - -This model applies only to deserializing Fory binary object-serialization data -from untrusted or partially trusted sources. Fory JSON has a separate -[security guide](../json/security.md) because it uses different readers, -policies, codecs, and resource-accounting rules. - -It does not treat the semantic content of a successfully deserialized value as a -Fory security boundary. A sender can always construct protocol-valid data whose -value is chosen by that sender. Application authorization, object-level business -rules, and domain-specific validation remain application responsibilities. -The selected business invariant remains an application policy rather than a -Fory protocol security boundary. - -This model does not govern memory-format paths unless a runtime explicitly -exposes such a path through an untrusted deserialization API. - -## Trust Boundaries - -Fory deserialization should treat the encoded input as untrusted at API -boundaries that accept external bytes or streams. - -Fory security boundaries include: - -- Resource ownership, such as memory, CPU progress, stream buffering, file - handles, native allocations, callbacks, and retained read-side tables. -- Runtime safety, such as avoiding crashes, panics, undefined behavior, and - out-of-bounds reads or writes. -- Explicit Fory policy checks, such as type, function, method, class, or - registration policies that are intended to restrict what may be materialized. -- Cleanup boundaries, where state created during a failed read must be released - or reset before the next root operation. - -Fory security boundaries do not include: - -- The business meaning of a protocol-valid value. -- Which protocol-allowed byte form was used for a value. -- Whether a map, set, object, or metadata value uses one specific encoding - shape, unless rejecting other shapes is an explicit owner policy or protects - one of the boundaries above. - -## Type And Class Policy - -Type, class, function, method, registration, and deserialization policies are -security boundaries when they are intended to restrict what untrusted bytes may -materialize. - -For untrusted data, a bypass is security-relevant when encoded bytes can -materialize a type, function, method, class, or dynamic object that the active -Fory policy should reject. This includes bypasses of class or type -registration, allow-list checkers, strict-mode checks, or language-specific -deserialization policies. - -An application explicitly trusts a class when it registers that class or -registers a serializer for that class. Both operations are configuration-time -trust decisions under the class-registration policy. Explicitly selecting a -static root serializer or static root target at the deserialization call is -also an application authorization decision for that root path. Authorization -of that statically selected root does not depend on a separate registration -lookup; any registration needed to access registered identity or -registration-backed metadata remains access-driven. - -Explicitly declaring or selecting a static field codec is itself an application -authorization decision for that field; the codec does not need to be registered -separately for authorization. Registering an enclosing class or schema also -authorizes the statically declared field codecs and serializers that belong to -that registered owner. This applies equally to declared Array, Set, Map, Struct, -and other statically composed field paths. Those declared field paths do not -require independent registration merely because their bodies are decoded -without another type lookup. Likewise, an encoded declared-type marker does not -create a registration bypass when it can only invoke the codec already selected -by the authorized root or enclosing schema. - -These static authorization paths do not authorize an arbitrary alternative -chosen by encoded type metadata. A dynamic or polymorphic type selected by -input must still pass the active registration and deserialization-policy checks -for that type. A serializer that Fory merely discovers or generates, and that -is not reached through an explicitly selected static root or a registered -enclosing owner, is serialization mechanics only and does not by itself -authorize a dynamically selected class. - -Disabling registration or dynamic-type checks for trusted data is a caller -configuration choice. That choice only removes the arbitrary-type materialization -claim provided by that policy; it does not remove Fory's runtime-safety, -resource, cleanup, retained-state, or no-progress-loop requirements for -untrusted deserialization paths. - -Fory is not a sandbox for application-owned types. If a registered type or -serializer is allowed by the active policy, the application owns whether that -type's construction, hooks, setters, finalizers, or other logic is safe for the -application's trust boundary. - -When policy-approved construction or callable execution is allowed, resource -accounting should not claim to bound arbitrary code outside Fory's ownership. -Fory-owned accounting can cover only objects and storage that Fory itself -clearly creates or copies and that remain reachable from the materialized graph. -Temporary helper allocations and user-code internals remain outside that -accounting boundary. - -## Depth And Progress - -Deserialization paths that recurse through objects, metadata, containers, or -references should enforce the runtime's configured depth limit before crafted -nesting can exhaust the call stack or bypass cleanup. A malformed input that -exceeds the configured depth should fail the root operation instead of -continuing unbounded recursion. - -Loops that consume encoded data should guarantee byte progress, logical -progress, or a terminal error. Inputs that can keep a reader in a no-progress -loop are security-relevant even when they do not allocate memory. - -## Security Invariants - -Deserialization code must prevent the following outcomes for untrusted input: - -- Crash, panic, undefined behavior, or out-of-bounds memory access. -- OOM or disproportionate allocation compared with bytes that are already - supplied or proven readable. -- No-progress loops, including loops where neither logical progress nor byte - progress is guaranteed after malformed input. -- Stream-buffer growth to an attacker-declared size before the corresponding - bytes have been read or skipped exactly. -- Resource leaks, including native allocations, handles, callbacks, or - registered cleanup work that cannot run. -- Retained attacker-controlled state after failure when that state can affect a - later root operation or grow across operations. -- Successful bypass of an explicit Fory policy boundary. - -When a path cannot produce one of these outcomes, earlier rejection of malformed -bytes is normally a correctness or interoperability choice, not a security -requirement. - -## Robustness Scope Gate - -Before reporting or fixing a deserialization robustness finding, establish a -concrete consequence in the current implementation: - -- Crash, panic, undefined behavior, or out-of-bounds access. -- Disproportionate allocation, CPU work, or stream growth. -- A no-progress loop. -- Persistent state, reference-table, or cache pollution. -- Later-root corruption or a failed-root cleanup leak. -- A concrete type, registration, callable, or deserialization-policy violation. - -Protocol strictness alone is outside this gate. Do not change code merely -because a malformed or noncanonical flag, enum value, marker, length form, or -reserved value is accepted, rejected late, decoded differently, or produces a -less precise error. Such validation is actionable only when it prevents one of -the concrete consequences above or implements an explicit public contract. - -## Controlled Deserialization Errors - -When a decoder determines that input is invalid for the active owner path, the -root operation must return an error and run its normal failure cleanup. This is -an outcome requirement, not an error-taxonomy requirement. - -Unless a public API or specification explicitly promises otherwise, Fory does -not require a particular exception type, error code, message, detection layer, -input offset, or earliest possible detection point. An existing bounded -downstream buffer-underflow, type, reference, depth, or serializer error is a -valid rejection. A decoder does not need a new local check merely to replace -that controlled failure with a more specific or more uniform error. - -Tests for malformed input should prove that the root operation fails, cleanup -remains correct, and any relevant security invariant is preserved. They should -not pin an exact error type or message when doing so would require additional -successful-path validation that protects no security boundary. - -## Non-Security Semantics - -The following patterns are not vulnerabilities by default: - -- Protocol-allowed collection chunking, map chunking, and field ordering. -- Duplicate keys, set elements, or compatible fields that collapse according to - the target data structure or owning serializer semantics. -- Malformed ref, null, or type flags that eventually produce a read error. -- Malformed scalar bytes that are consumed linearly and eventually produce a - read error. -- Reading an encoded body before later shape validation when the operation - ultimately returns an error and does not create a security-invariant failure. -- Materializing an array whose component is an interface already allowed as a - class token. Allocating the reference array does not instantiate or execute - the interface, and every non-null element must still pass the active policy - for its concrete type. Treat this as security-relevant only if the array path - bypasses that concrete element check, invokes a policy-forbidden callback, or - violates a runtime-safety or resource invariant owned by Fory. - -Fory may still reject malformed forms for specification strictness or -interoperability. That validation should be added only when it is required by -the protocol owner, is effectively free on the relevant path, or protects a -security invariant listed above. Do not add protocol-layer validation solely to -reject scalar byte forms whose only effect is extra decode cost. - -### Value-bearing ref flags - -Some read paths intentionally share handling for multiple value-bearing flags. -For example, when both `NotNullValue` and `RefValue` mean that an encoded value -follows, a reader may merge their hot-path handling. This is not a malformed -flag bug by itself. Treat it as a bug only if the merged handling loses required -reference semantics, returns success across an explicit owner policy, or creates -a resource or runtime-safety failure. - -## Allocation And Byte Availability - -Fory should not make large allocations from attacker-declared lengths before -the required bytes are available or have been read exactly. - -For buffer-backed input: - -- Fixed-size binary values and primitive dense arrays should call the byte - owner's readability check for the required encoded byte size before allocating - the destination. For buffer-backed input this is normally a remaining-byte - comparison. -- Multi-byte element arrays should compute the required byte size with overflow - checks before allocation. -- Container readers that allocate backing storage or size-hint from a declared - logical element count should call the byte owner's readability check for that - count before that backing allocation or capacity reservation. This is not a - full container-body validation; it is the allocation proof that the sender has - supplied at least proportional input bytes before the reader preallocates from - the count. Estimated memory-budget accounting may reserve budget before this - byte check because it does not allocate backing storage. -- Readers should not add count-based readability checks merely because a loop - will read that many values when the destination grows incrementally and each - item read still uses the normal byte-owner checks. The security boundary is - direct preallocation from an untrusted count, not the existence of a counted - loop. - -For stream-backed input: - -- Reading or skipping a large byte region is the proof that the bytes exist. -- Byte-counted variable-length result allocation should use the byte owner's - readability check before allocation. Skip paths may use bounded skip without - materializing the skipped value. -- A stream-backed buffer may hold the full requested encoded body after that - body has been read from the stream. It must not reserve the attacker-declared - length before input bytes prove that length exists. -- Stream-backed fill buffers should grow geometrically from the current proven - buffer size, such as by doubling current capacity. Growth must not be capped - to the immediate fill target: for small fills the target is barely above the - current capacity, so cap-to-target degenerates into constant-size growth - steps that copy the whole buffer on every small read and make stream - deserialization O(n^2) overall. A byte owner may use an owner-local - availability signal as a one-shot growth hint when the stream implementation - itself is caller-owned trusted code, and may then reserve the full immediate - target at once while keeping at least the geometric growth step; if that hint - is absent or insufficient, the reader must fall back to bounded geometric - growth from already buffered bytes. Serializers should not add their own - availability branches. -- A truncated stream should fail before allocating the final deserialized value - and should allocate only for bytes actually read plus bounded spare capacity. - -The byte owner should stay byte-oriented. Buffer, reader, or read-context APIs -may expose byte read and byte skip operations, but string decoding, decimal -parsing, primitive-array encoding, compression modes, and collection capacity -policy belong to the owning serializers. - -## Collection And Map Capacity - -Large valid collection inputs are allowed. If the input contains many encoded -elements, proportional deserialization is expected. - -The security requirement is to avoid disproportionate preallocation from a -declared logical count before enough input bytes justify that capacity. When -the repeated element or entry body is proven to consume at least one byte, a -reader that allocates or reserves from the declared count should call -`checkReadableBytes(logicalCount)` or the runtime equivalent before that -allocation. When the body may consume no bytes, the readable-byte requirement -may exclude the root operation's remaining unbacked-container allowance. The -reader must still account for actual input progress while reading the -container. The byte check does not decode the whole container, validate element -semantics, or replace chunk validation. Readers that do not preallocate from -the logical count may still grow proportionally as elements are actually read. - -Map or collection chunk validation is security-relevant only when missing -validation can cause a no-progress loop, unbounded resource growth, retained -state, or success across a Fory policy boundary. Protocol-allowed chunk -segmentation is normal input and is not a security issue by itself. - -## Unbacked Container Work Budget - -Runtimes enforce a root-scoped limit on count-driven collection elements and -map entries whose repeated read bodies are not backed by input progress. The -public option is named `maxUnbackedContainerItems` or the language-equivalent -spelling. Its default is `8192`; values must be non-negative, and zero is a -strict limit rather than an unlimited sentinel. - -The allowance is shared by all nested collections, maps, and compatible field -skip operations in one root read. Collection readers account for completed -items every 1024 elements and at the final partial window. Map readers account -at existing protocol chunk boundaries. Bytes actually consumed by the repeated -item bodies offset the completed item count in the same window. The budget does -not add framing, reject values on write, change reference publication, or -replace graph-memory accounting. - -Readers whose exact repeated operation is known to consume at least one byte -retain their direct loop and proportional readable-byte check. Generated and -compiled serializers should remove budget access and periodic branches from -those proven-positive paths. - -## Graph Memory Budget - -Runtimes should enforce a per-operation approximate gate for estimated memory created by one -materialized graph. This is cumulative accounting for graph owners created by one top-level -deserialization operation; it is not exact heap measurement and it is not a raw element-slot limit. -Actual process memory can be higher than the configured gate. - -The public configuration is `maxGraphMemoryBytes`. The default is a fixed `128 MiB` for all input -forms; positive user configuration overrides the default. Explicit non-positive configuration is -invalid and should be rejected when the runtime is created. The budget is not derived from input -size, and stream budgeting should not depend on dynamic bytes-read accounting. - -Graph budget accounting should: - -- be initialized in top-level read state, with cleanup owned by the top-level deserialization - `finally`; -- account only for Fory-created objects or storage that are retained by the - returned value graph; temporary helper objects used only during construction - are outside the graph budget; -- not claim to budget arbitrary constructor, callable, descriptor, finalizer, - or state-restoration internals that run after an explicit policy allows that - code; -- keep read context/read state limited to raw byte reservation; counted arithmetic and collection, - map, array, struct, and object storage formulas belong in the concrete serializer or generated - serializer owner; -- reject arithmetic overflow before comparing budget or allocating; -- estimate lower-bound shallow owner storage: reference-backed or heap-materialized collections, - maps, sets, and reference arrays reserve nonzero shallow self cost plus - backing/reference/inline storage, and reference-backed or heap-materialized struct, record, - POJO, tuple/product, compatible, generated, and dynamic object owners reserve a nonzero shallow - self cost plus shallow field storage; -- use a 4-byte reference slot when the actual reference slot size is not cheap or reliable to query, - and use primitive/value field widths for inline storage; -- preserve existing byte-availability checks before backing allocation or capacity reservation; -- skip enum/union as separate owners and skip dedicated string, binary, primitive scalar, primitive - array, and primitive dense-array leaf owners unless a runtime-specific owner section explicitly - includes them. - -Skipped leaf owners must still be gated by remaining input bytes. If the unread input does not -contain enough bytes for a string, binary value, primitive scalar, primitive array, or primitive -dense array, the runtime must not read or create that leaf value. - -Each runtime must inspect the concrete owner path before choosing formulas. Reserve self storage -exactly once at the owner that stores, boxes, or allocates the value. Deserialization facades may -reset the budget for each operation, but must not pre-reserve the top-level result type, self bytes, -or value storage. -Reference-backed paths reserve parent owner self cost plus reference storage, while each referenced -heap owner reserves its own shallow self cost when materialized. Inline/value paths reserve inline -element, field, or boxed storage in the holder/allocation owner; top-level value serializers and -generated struct/product read paths must not charge their own self storage. -For inline/value collection or map runtimes, the top-level value container itself is not charged by -the deserialization facade or by the container serializer only because it is the returned value. -Nested value containers are charged as inline slots of the parent holder or as backing storage -elements of the outer collection that actually owns those slots. Pointer, box, smart-pointer, or -type-erased materialization paths reserve the shallow storage for the heap value they allocate. -Parents must not recursively include child object, collection, map, string, binary, or primitive -dense-array contents; the child owner reserves its own shallow memory when it is materialized. - -### Java Fory Core - -Java Fory core primitive-array serializers reserve the portable array header plus the logical -length multiplied by the primitive storage width. Primitive-list serializers reserve the returned -list's shallow owner, the backing-array header, and the same primitive storage. These known-length -paths reserve once after their existing proportional readable-byte check and before allocation; -they do not use incremental batches. Compressed inputs use the decompressed logical length, while -temporary compressed arrays remain construction scratch outside the retained graph budget. -Float16 and BFloat16 dense-array carriers also include their wrapper's shallow owner. When a boxed -list conversion first decodes a primitive array, the array's reservation remains as credit toward -the final list estimate, and the conversion reserves only a positive remaining difference. - -### Generated Structural Targets - -Wire members and physical storage are separate inputs. Properties, accessors, interfaces, and -logical schema aliases are not physical fields and must not be charged as storage. A field that is -both serialized and stored is counted once. A storage-only declaration contributes its field width -but must not enter wire metadata or generated reads and writes. - -For C# ordinary classes, each directly annotated class owns the physical instance fields declared -by that class. An inheritable class provider publishes the cumulative parent-provider value plus -those direct fields. A sealed concrete serializer uses the same cumulative expression privately. -A concrete descendant uses the immediate accessible provider value and its own direct fields; it -must not enumerate referenced private metadata or reconstruct parent storage. The concrete object -serializer reserves one shallow object owner plus this cumulative field storage. - -A C# external class declaration owns the exact third-party physical fields it lists. An exact -field mapping contributes storage. A visible property mapping does not, so its backing field must -be listed separately. An ignored mapping must identify one exact class field and is storage-only. -External struct declarations support visible member mappings only. -Discoverable unmapped public instance fields may be added once. A `BaseOnly` declaration can own -the complete target and target-ancestor prefix used by an ordinary child. It must list every -non-public physical field in that prefix; the generator does not scan the referenced assembly for -private layout. - -Exact external private identities are version-pinned package ABI assertions. Runtime wire access -uses exact accessors and must not fall back to reflection, layout probing, or a different member. -Storage-only private declarations have no runtime accessor, so the application must validate them -against the pinned package version. - -Dart generators may additionally include public instance fields visible on the target at -generation time. Swift macros cannot inspect another type's stored layout and therefore use only -the external declaration. In every runtime, these formulas are resolved during generation and -must not add reflection, layout probing, allocation, or field enumeration to deserialization hot -paths. The normal owner rules still apply: a reference target reserves its shallow owner and field -storage, while an inline value target is charged by the holder that owns its storage. - -### Runtime-Specific Owner Notes - -#### C++ - -C++ plain structs, products, and standard-library containers are value storage unless a pointer, -smart pointer, or type-erased owner allocates them on the heap. Top-level deserialization initializes -the remaining graph budget but does not reserve `sizeof(T)` for the returned value. Plain value -serializers must not reserve their own `sizeof(T)` only because they are reading a value. - -Generic collection and map serializers reserve the lower-bound element, key, and value storage -owned by the container path. Nested value container headers are charged when they are inline slots -of a parent object or elements in an outer container backing store. Smart-pointer and type-erased -materialization paths reserve the shallow storage for the heap value they allocate before publishing -or returning it. Generic C++ paths must not invent standard-library header, node, bucket, allocator, -or debug-layout overheads. - -#### Rust - -Rust structs, tuples, enums, and collection values are inline value storage unless a `Box`, `Rc`, -`Arc`, or type-erased owner allocates them. Top-level and derived value read paths initialize or -consume the budget but do not reserve `size_of::()` for the value being read. `Vec`, `HashMap`, -`BTreeMap`, and similar serializers reserve backing or entry value storage that they allocate from -counts; nested value container headers are charged as parent inline fields or outer backing elements. - -Boxed, reference-counted, and type-erased materialization paths reserve `size_of::()` for the heap -payload they create. Compile-time `size_of::()` formulas are acceptable in those allocation -owners, but value serializers should not add a parallel self-reserve for the same `T`. - -Before count-derived allocation, Rust owners whose exact repeated operation is proven to consume at -least one byte retain the full readable-byte gate. Uncertain owners require readable bytes only for -the portion of the count not covered by the remaining unbacked-item allowance. Apply the selected -gate exactly once at the allocation owner; do not repeat it after reading shared metadata. Writers -continue to encode legal compact or empty bodies and do not enforce this reader-side allowance. - -Fixed arrays do not allocate from their validated wire count and omit the allocation gate. `Vec`, -`VecDeque`, and `BinaryHeap` also omit it for zero-sized elements because they create no -count-derived backing allocation in that case. Node, bucket, and entry owners retain the gate where -the declared count drives allocation. Implementations must not substitute guessed allocation costs, -padding bytes, a global compact-body bypass, or a second collection or map codec. - -#### Swift - -Swift structs, enums, tuples, and collection values are value storage. Top-level value reads and -nested value serializers should not reserve their own self storage. The holder that owns the value, -such as a struct field, array backing store, dictionary entry storage, or boxed/dynamic -materialization path, owns the corresponding graph-budget reservation. - -Array, dictionary, and set serializers may reserve lower-bound backing storage using stable Swift -type-size information, such as `MemoryLayout.stride`, when they allocate or reserve that storage. -Class, existential, or boxed materialization paths reserve owner storage when Fory creates the -retained object or box. Runtime object-layout probing should not be added to hot read paths. - -#### Go - -Go structs and slice or map headers are value storage unless a pointer, interface materialization, or -other heap owner allocates them. Top-level deserialization and struct value serializers should not -reserve the returned struct or a nested inline struct by themselves. Pointer serializers reserve the -concrete struct storage when they allocate a retained `*T`. - -Slice, array, map, and set serializers reserve the backing or entry storage they allocate from -declared counts. Element and entry widths should come from stable type information captured by the -serializer or resolver when possible; read loops should not recompute reflective size information -when the owner already knows the concrete type. Interface or dynamic paths reserve only storage that -Fory clearly materializes and retains. - -#### C\# - -C# combines reference owners and inline value types. Classes, arrays, lists, dictionaries, hash sets, -and other heap containers reserve a nonzero shallow owner cost plus direct backing, reference-slot, -or inline element storage. A dictionary is a reference-type container even when its key or value type -is a struct, so the dictionary owner is still charged separately from its entry storage. - -Value structs do not reserve their own self storage when read inline; the holder that stores the -struct, such as an object field, array element, list backing store, dictionary entry, box, or dynamic -materialization path, owns that reservation. Boxing, `object`, and dynamic materialization paths -reserve a boxed owner when Fory creates the retained box. Owner constants should be real portable -lower bounds for the relevant C# object or container shape, not placeholder markers. - -Runtimes should not guess object headers, array headers, allocator headers, debug-mode fields, hash -buckets, tree links, hash-chain links, node headers, map-entry objects, spare blocks, or runtime -table layouts unless the owner path has a cheap, stable, explicit lower-bound storage signal and -documents the formula. Owner constants should be real lower bounds for the owner shape, not -placeholder markers. - -## Skip Semantics - -Skipping unknown or incompatible data is classified by concrete impact, not by -whether the runtime materializes a temporary value. - -Directly consuming encoded contents is useful when it is simple and owned by the -current runtime path. It is not a security requirement for complex fields such -as lists, sets, and maps. A runtime may materialize a value and discard it when -that preserves the existing serializer ownership model. - -For extension, dynamic, or user-owned types, the owning runtime may not always -have enough information to skip without invoking a registered serializer. In -that case, classify the behavior by concrete impact: - -- Resource leak, retained state, no-progress loop, or policy bypass is - security-relevant. -- Bounded materialization followed by an error or discard is allowed unless it - creates meaningful memory or CPU pressure. -- Pure strictness about whether a skipped value used one specific encoding shape - is not a security issue. - -## Metadata And Type Resolution - -Metadata parsing is security-sensitive when it affects retained read-side state, -type dispatch, or policy decisions. - -Metadata readers should: - -- Avoid unbounded recursion in nested metadata structures. -- Avoid unbounded table growth from attacker-controlled metadata streams. -- Validate metadata bodies before using them to bypass or replace existing - policy decisions. -- For Java metadata paths, keep name-level checks such as `TypeChecker` and the - disallowed-class list before `Class.forName` by routing remote class-name - loading through the existing `TypeResolver.loadClass` owner. Do not bypass - that owner with direct class loading from TypeDef or TypeMeta names. A rejected - input name must not cause class loading. Preserve registration, dynamic-loading, - and unknown-type semantics while moving this decision before loading. Checks - that require a materialized `Class` remain after loading; do not replace - them with string-only approximations. -- Pass a complete input array descriptor to `TypeChecker`. Input may derive up - to six array dimensions from an accepted component class. Higher-dimensional - arrays require an exact trusted full-array registration or checked name-cache - entry so input cannot make the JVM derive an unbounded family of array classes. -- Reset or release metadata state at the correct root-operation boundary. - -A class-resolution cache reachable from untrusted deserialization may publish -an entry only from explicit trusted configuration or after the active class -policy has accepted the resolved class. A cache hit therefore represents an -already trusted and validated `Class` and should use that cached class -without repeating class loading or name-level `TypeChecker` work. Only a cache -miss performs those name-level checks and publishes the accepted result. -Checks that require the materialized `Class` remain owned by their existing -caller. A cache entry that stores a data-only unknown-class placeholder may -return that same placeholder on an exact hit, but must not authorize loading the -original missed wire name. -Exact registered-name-table hits are trusted for both ID and name registrations, -and exact checked name-cache hits are trusted. After both exact lookups miss, a -reader must not infer another accepted name from inverse registration, -class-keyed state, or `Class.getName()`. A custom-name registration does not by -itself publish the Java class name as an additional alias; ID registration does -publish the Java class name. - -Remote metadata that can create persistent read state must be bounded before -that state is retained. The check is resource control only: it must not change -wire compatibility, type registration, dynamic class loading, unknown-type -handling, deserialization policy, or schema-evolution semantics. Failed or -incompatible metadata must not consume schema-version limits, and metadata -cache hits or generated field readers must not add validation, hashing, -allocation, or policy work for these limits. The concrete sequence for metadata -parsing, cache publishing, exact-local matching, and counting belongs to the -[xlang implementation guide](../specification/xlang_implementation_guide.md). - -The checked metadata cache is the only owner of whether a received TypeDef or -TypeMeta header has already been validated. A metadata cache hit means the -header was previously parsed, body/hash-validated, policy-checked, and -published by the owning cache, so the reader must skip the remaining metadata -body and use the cached metadata without repeating body validation, hash -validation, limit checks, exact-local checks, or policy work. A metadata cache -miss is the only path that parses the metadata body, validates its hash and -shape, enforces metadata limits, performs exact-local byte comparison, and -publishes to the cache. Do not add separate nullable flags, sentinel headers, -per-TypeInfo acceptance markers, or parallel state to represent this decision. - -Only metadata that is actually carried as a TypeDef or TypeMeta body is subject -to metadata body and schema-version limits. Compatible named enum, ext, and -union metadata normally has one version, but still counts against remote -metadata total limits when it is sent as shared metadata. Pure id-based enum, -ext, and typed-union values use type id plus user type id and must not be moved -onto this metadata body path. - -Remote metadata bodies and struct field lists must also be bounded on the cold -metadata parse path. `maxTypeMetaBytes` limits the encoded metadata body bytes -for one received TypeDef or TypeMeta body, excluding the 8-byte header and any -extended-size varint. `maxTypeFields` limits the number of fields declared by -one received struct metadata body. For Java native TypeDef class layers, the -field limit applies to the total field count across the class layers in that -one TypeDef. These limits are checked before copying, decompressing, reserving, -or allocating from attacker-declared metadata sizes or field counts. - -The default limits are `maxTypeFields = 512` and `maxTypeMetaBytes = 4096`. -Runtimes should report limit failures as possible malicious data and tell users -to increase the exact option only when the data is not malicious. These limits -must not introduce validation on metadata cache-hit, generated serializer, or -already-resolved type-id hot paths. - -Metadata byte-form strictness alone is not a security requirement. Rejecting a -metadata shape is useful only when the owner wants that strictness or when the -shape changes type identity, retained state, resource use, or policy behavior. - -## Reference Tracking - -Reference tracking is part of the wire protocol and is performance-sensitive. -Readers may use sentinel values and shared value-bearing branches to keep hot -paths compact. - -Reference tracking validation is security-relevant when malformed input can: - -- Access an out-of-range reference without reporting an error. -- Leave retained reference state after a failed root operation. -- Register unbounded callbacks or resolver state before the referenced value is - available. -- Cause a no-progress loop or crash. - -Reference tracking validation is not required merely because a malformed flag is -not rejected at the earliest possible byte. Lazy rejection is acceptable when -the root operation still returns an error and no security invariant is violated. -The downstream error does not need to be a dedicated reference-protocol error. - -## Error Propagation And Cleanup - -Fory runtimes may intentionally use lazy error propagation. After a read records -an error, later read steps may continue until the outer operation observes and -returns the error. - -This is acceptable when the continued work cannot: - -- Crash or panic. -- Allocate or retain attacker-controlled state. -- Leak resources. -- Bypass required cleanup. -- Return success across an explicit validation or policy boundary. - -Nested `try`/`finally` or equivalent cleanup should be added only when the -outer root-operation cleanup cannot cover the state or resource owned by the -nested path. - -## Performance Requirements - -Security validation must preserve Fory hot-path performance. Do not add -validation solely for strictness when it introduces: - -- Per-element object allocation. -- Dynamic dispatch or callbacks in hot loops. -- Wrapper objects or result carriers on success paths. -- Extra copying for buffer-backed string, binary, or primitive-array reads. -- Branches that do not protect a security invariant. -- Helper calls or generated-code expansion whose only purpose is to normalize - an eventual error's type, message, location, or timing. - -Prefer owner-local checks that can be inlined and that already use information -available in the current serializer. Do not move serializer-owned semantics into -generic read-context helpers. - -## Classification Guide - -Use the following questions when reviewing deserialization behavior: - -1. Can this input crash, panic, or access memory out of bounds? -2. Can a small or unproven input length cause disproportionate allocation? -3. Can a stream-backed reader grow a buffer before exact read or skip proves the - bytes exist? -4. Can a loop continue without byte progress or logical progress? -5. Can the path retain attacker-controlled state after the root operation fails? -6. Can the path leak resources or skip required cleanup? -7. Can the path return success across an explicit Fory policy boundary? -8. Is the proposed validation effectively free in the relevant hot path? - -If the answer to the first seven questions is no, the issue is normally not a -security finding. If the validation is not effectively free, avoid adding it -unless the protocol owner explicitly requires it. - -## Documentation Boundaries - -Security model documents must not include exploit samples, CVE narratives, -line-level vulnerability candidates, branch history, migration timelines, or -cleanup plans. Keep those details in private reports, issues, or pull requests -as appropriate. - -Public security documentation should describe durable boundaries and invariants, -not the history of how the implementation reached them. diff --git a/docs/object-serialization/index.md b/docs/object-serialization/index.md index 996a5080dd..9ed38d9f8f 100644 --- a/docs/object-serialization/index.md +++ b/docs/object-serialization/index.md @@ -38,11 +38,23 @@ representation, and Fory JSON is a Java JSON codec; use the Choose a runtime to find its installation route, lifecycle, exact APIs, configuration, type registration, schema behavior, extensions, platforms, and troubleshooting: -[Browse supported runtimes](runtimes.md) +| Runtime | Modes | Documentation | +| --------------------- | -------------------- | ------------------------------------------------------ | +| Java | xlang and native | [Java runtime](./java/index.md) | +| Python | xlang and native | [Python runtime](./python/index.md) | +| C++ | xlang and native | [C++ runtime](./cpp/index.md) | +| Go | xlang and native | [Go runtime](./go/index.md) | +| Rust | xlang and native | [Rust runtime](./rust/index.md) | +| JavaScript/TypeScript | xlang | [JavaScript/TypeScript runtime](./javascript/index.md) | +| C# | xlang | [C# runtime](./csharp/index.md) | +| Swift | xlang | [Swift runtime](./swift/index.md) | +| Dart | xlang | [Dart runtime](./dart/index.md) | +| Scala | xlang and JVM native | [Scala runtime](./scala/index.md) | +| Kotlin | xlang and JVM native | [Kotlin runtime](./kotlin/index.md) | ## Security -Before decoding externally supplied bytes, read [Object Serialization Security](security.md). It +Before decoding externally supplied bytes, read [Security](security.md). It covers accepted-type policy, registration, resource limits, transport responsibilities, and negative verification for both modes. diff --git a/docs/object-serialization/runtimes.md b/docs/object-serialization/runtimes.md deleted file mode 100644 index 491608e543..0000000000 --- a/docs/object-serialization/runtimes.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Object Serialization Runtimes -sidebar_position: 3 -id: runtimes -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Choose the runtime whose API your application uses. Runtime documentation is scoped to -binary object serialization; Row Format, Fory JSON, compiler, and gRPC pages remain with -their respective capability guides. - -| Runtime | Modes | Documentation | -| --------------------- | -------------------- | ------------------------------------------------------ | -| Java | xlang and native | [Java runtime](./java/index.md) | -| Python | xlang and native | [Python runtime](./python/index.md) | -| C++ | xlang and native | [C++ runtime](./cpp/index.md) | -| Go | xlang and native | [Go runtime](./go/index.md) | -| Rust | xlang and native | [Rust runtime](./rust/index.md) | -| JavaScript/TypeScript | xlang | [JavaScript/TypeScript runtime](./javascript/index.md) | -| C# | xlang | [C# runtime](./csharp/index.md) | -| Swift | xlang | [Swift runtime](./swift/index.md) | -| Dart | xlang | [Dart runtime](./dart/index.md) | -| Scala | xlang and JVM native | [Scala runtime](./scala/index.md) | -| Kotlin | xlang and JVM native | [Kotlin runtime](./kotlin/index.md) | diff --git a/docs/object-serialization/security.md b/docs/object-serialization/security.md index 28789dc466..09e88a25a8 100644 --- a/docs/object-serialization/security.md +++ b/docs/object-serialization/security.md @@ -1,6 +1,6 @@ --- -title: Object Serialization Security -sidebar_position: 4 +title: Security +sidebar_position: 99 id: security license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -19,7 +19,7 @@ license: | limitations under the License. --- -This guide defines the trust boundary and safe operating model for Fory binary object serialization in xlang and native mode. Contributor-facing classification rules live in the [deserialization security model](deserialization-security-model.md). +This guide defines the trust boundary and safe operating model for Fory binary object serialization in xlang and native mode. It also provides the contributor-facing classification rules used to review untrusted deserialization paths. Fory is an in-process serialization library. Applications link Fory into their own process, configure serializers and type policies, and call Fory APIs to @@ -61,7 +61,7 @@ equivalent allow-list policy is the relevant gate. Generated serializer code is derived from checked type descriptors rather than from attacker-controlled byte contents. -The [deserialization security model](deserialization-security-model.md) defines how to +The [security review model](#security-review-model) defines how to classify these boundaries for untrusted deserialization paths. ## Non-Goals @@ -100,7 +100,7 @@ Disabling registration or using dynamic deserialization on trusted data is a configuration choice. For untrusted data, bypassing an explicit Fory policy, crashing, leaking resources, retaining attacker-controlled state, or allocating disproportionately remains security-relevant as described in the -[deserialization security model](deserialization-security-model.md). +[security review model](#security-review-model). ## Resource Limits @@ -208,3 +208,697 @@ reader rejects: Also verify the application's external authentication, integrity, request-size, timeout, and domain-validation controls independently of Fory. + +## Security Review Model + +This document defines the security model for Apache Fory deserialization. It is +a public security reference for classifying deserialization behavior and +deciding where validation is required. It is not a vulnerability disclosure, +does not describe exploit techniques, and does not document implementation +history. + +The model is intentionally narrow. Fory should prevent resource and policy +failures caused by untrusted input, but it should not add hot-path validation +that only enforces byte-form strictness when doing so does not protect a Fory +security boundary. + +### Scope + +This model applies only to deserializing Fory binary object-serialization data +from untrusted or partially trusted sources. Fory JSON has a separate +[security guide](../json/security.md) because it uses different readers, +policies, codecs, and resource-accounting rules. + +It does not treat the semantic content of a successfully deserialized value as a +Fory security boundary. A sender can always construct protocol-valid data whose +value is chosen by that sender. Application authorization, object-level business +rules, and domain-specific validation remain application responsibilities. +The selected business invariant remains an application policy rather than a +Fory protocol security boundary. + +This model does not govern memory-format paths unless a runtime explicitly +exposes such a path through an untrusted deserialization API. + +### Trust Boundaries + +Fory deserialization should treat the encoded input as untrusted at API +boundaries that accept external bytes or streams. + +Fory security boundaries include: + +- Resource ownership, such as memory, CPU progress, stream buffering, file + handles, native allocations, callbacks, and retained read-side tables. +- Runtime safety, such as avoiding crashes, panics, undefined behavior, and + out-of-bounds reads or writes. +- Explicit Fory policy checks, such as type, function, method, class, or + registration policies that are intended to restrict what may be materialized. +- Cleanup boundaries, where state created during a failed read must be released + or reset before the next root operation. + +Fory security boundaries do not include: + +- The business meaning of a protocol-valid value. +- Which protocol-allowed byte form was used for a value. +- Whether a map, set, object, or metadata value uses one specific encoding + shape, unless rejecting other shapes is an explicit owner policy or protects + one of the boundaries above. + +### Type And Class Policy + +Type, class, function, method, registration, and deserialization policies are +security boundaries when they are intended to restrict what untrusted bytes may +materialize. + +For untrusted data, a bypass is security-relevant when encoded bytes can +materialize a type, function, method, class, or dynamic object that the active +Fory policy should reject. This includes bypasses of class or type +registration, allow-list checkers, strict-mode checks, or language-specific +deserialization policies. + +An application explicitly trusts a class when it registers that class or +registers a serializer for that class. Both operations are configuration-time +trust decisions under the class-registration policy. Explicitly selecting a +static root serializer or static root target at the deserialization call is +also an application authorization decision for that root path. Authorization +of that statically selected root does not depend on a separate registration +lookup; any registration needed to access registered identity or +registration-backed metadata remains access-driven. + +Explicitly declaring or selecting a static field codec is itself an application +authorization decision for that field; the codec does not need to be registered +separately for authorization. Registering an enclosing class or schema also +authorizes the statically declared field codecs and serializers that belong to +that registered owner. This applies equally to declared Array, Set, Map, Struct, +and other statically composed field paths. Those declared field paths do not +require independent registration merely because their bodies are decoded +without another type lookup. Likewise, an encoded declared-type marker does not +create a registration bypass when it can only invoke the codec already selected +by the authorized root or enclosing schema. + +These static authorization paths do not authorize an arbitrary alternative +chosen by encoded type metadata. A dynamic or polymorphic type selected by +input must still pass the active registration and deserialization-policy checks +for that type. A serializer that Fory merely discovers or generates, and that +is not reached through an explicitly selected static root or a registered +enclosing owner, is serialization mechanics only and does not by itself +authorize a dynamically selected class. + +Disabling registration or dynamic-type checks for trusted data is a caller +configuration choice. That choice only removes the arbitrary-type materialization +claim provided by that policy; it does not remove Fory's runtime-safety, +resource, cleanup, retained-state, or no-progress-loop requirements for +untrusted deserialization paths. + +Fory is not a sandbox for application-owned types. If a registered type or +serializer is allowed by the active policy, the application owns whether that +type's construction, hooks, setters, finalizers, or other logic is safe for the +application's trust boundary. + +When policy-approved construction or callable execution is allowed, resource +accounting should not claim to bound arbitrary code outside Fory's ownership. +Fory-owned accounting can cover only objects and storage that Fory itself +clearly creates or copies and that remain reachable from the materialized graph. +Temporary helper allocations and user-code internals remain outside that +accounting boundary. + +### Depth And Progress + +Deserialization paths that recurse through objects, metadata, containers, or +references should enforce the runtime's configured depth limit before crafted +nesting can exhaust the call stack or bypass cleanup. A malformed input that +exceeds the configured depth should fail the root operation instead of +continuing unbounded recursion. + +Loops that consume encoded data should guarantee byte progress, logical +progress, or a terminal error. Inputs that can keep a reader in a no-progress +loop are security-relevant even when they do not allocate memory. + +### Security Invariants + +Deserialization code must prevent the following outcomes for untrusted input: + +- Crash, panic, undefined behavior, or out-of-bounds memory access. +- OOM or disproportionate allocation compared with bytes that are already + supplied or proven readable. +- No-progress loops, including loops where neither logical progress nor byte + progress is guaranteed after malformed input. +- Stream-buffer growth to an attacker-declared size before the corresponding + bytes have been read or skipped exactly. +- Resource leaks, including native allocations, handles, callbacks, or + registered cleanup work that cannot run. +- Retained attacker-controlled state after failure when that state can affect a + later root operation or grow across operations. +- Successful bypass of an explicit Fory policy boundary. + +When a path cannot produce one of these outcomes, earlier rejection of malformed +bytes is normally a correctness or interoperability choice, not a security +requirement. + +### Robustness Scope Gate + +Before reporting or fixing a deserialization robustness finding, establish a +concrete consequence in the current implementation: + +- Crash, panic, undefined behavior, or out-of-bounds access. +- Disproportionate allocation, CPU work, or stream growth. +- A no-progress loop. +- Persistent state, reference-table, or cache pollution. +- Later-root corruption or a failed-root cleanup leak. +- A concrete type, registration, callable, or deserialization-policy violation. + +Protocol strictness alone is outside this gate. Do not change code merely +because a malformed or noncanonical flag, enum value, marker, length form, or +reserved value is accepted, rejected late, decoded differently, or produces a +less precise error. Such validation is actionable only when it prevents one of +the concrete consequences above or implements an explicit public contract. + +### Controlled Deserialization Errors + +When a decoder determines that input is invalid for the active owner path, the +root operation must return an error and run its normal failure cleanup. This is +an outcome requirement, not an error-taxonomy requirement. + +Unless a public API or specification explicitly promises otherwise, Fory does +not require a particular exception type, error code, message, detection layer, +input offset, or earliest possible detection point. An existing bounded +downstream buffer-underflow, type, reference, depth, or serializer error is a +valid rejection. A decoder does not need a new local check merely to replace +that controlled failure with a more specific or more uniform error. + +Tests for malformed input should prove that the root operation fails, cleanup +remains correct, and any relevant security invariant is preserved. They should +not pin an exact error type or message when doing so would require additional +successful-path validation that protects no security boundary. + +### Non-Security Semantics + +The following patterns are not vulnerabilities by default: + +- Protocol-allowed collection chunking, map chunking, and field ordering. +- Duplicate keys, set elements, or compatible fields that collapse according to + the target data structure or owning serializer semantics. +- Malformed ref, null, or type flags that eventually produce a read error. +- Malformed scalar bytes that are consumed linearly and eventually produce a + read error. +- Reading an encoded body before later shape validation when the operation + ultimately returns an error and does not create a security-invariant failure. +- Materializing an array whose component is an interface already allowed as a + class token. Allocating the reference array does not instantiate or execute + the interface, and every non-null element must still pass the active policy + for its concrete type. Treat this as security-relevant only if the array path + bypasses that concrete element check, invokes a policy-forbidden callback, or + violates a runtime-safety or resource invariant owned by Fory. + +Fory may still reject malformed forms for specification strictness or +interoperability. That validation should be added only when it is required by +the protocol owner, is effectively free on the relevant path, or protects a +security invariant listed above. Do not add protocol-layer validation solely to +reject scalar byte forms whose only effect is extra decode cost. + +#### Value-bearing ref flags + +Some read paths intentionally share handling for multiple value-bearing flags. +For example, when both `NotNullValue` and `RefValue` mean that an encoded value +follows, a reader may merge their hot-path handling. This is not a malformed +flag bug by itself. Treat it as a bug only if the merged handling loses required +reference semantics, returns success across an explicit owner policy, or creates +a resource or runtime-safety failure. + +### Allocation And Byte Availability + +Fory should not make large allocations from attacker-declared lengths before +the required bytes are available or have been read exactly. + +For buffer-backed input: + +- Fixed-size binary values and primitive dense arrays should call the byte + owner's readability check for the required encoded byte size before allocating + the destination. For buffer-backed input this is normally a remaining-byte + comparison. +- Multi-byte element arrays should compute the required byte size with overflow + checks before allocation. +- Container readers that allocate backing storage or size-hint from a declared + logical element count should call the byte owner's readability check for that + count before that backing allocation or capacity reservation. This is not a + full container-body validation; it is the allocation proof that the sender has + supplied at least proportional input bytes before the reader preallocates from + the count. Estimated memory-budget accounting may reserve budget before this + byte check because it does not allocate backing storage. +- Readers should not add count-based readability checks merely because a loop + will read that many values when the destination grows incrementally and each + item read still uses the normal byte-owner checks. The security boundary is + direct preallocation from an untrusted count, not the existence of a counted + loop. + +For stream-backed input: + +- Reading or skipping a large byte region is the proof that the bytes exist. +- Byte-counted variable-length result allocation should use the byte owner's + readability check before allocation. Skip paths may use bounded skip without + materializing the skipped value. +- A stream-backed buffer may hold the full requested encoded body after that + body has been read from the stream. It must not reserve the attacker-declared + length before input bytes prove that length exists. +- Stream-backed fill buffers should grow geometrically from the current proven + buffer size, such as by doubling current capacity. Growth must not be capped + to the immediate fill target: for small fills the target is barely above the + current capacity, so cap-to-target degenerates into constant-size growth + steps that copy the whole buffer on every small read and make stream + deserialization O(n^2) overall. A byte owner may use an owner-local + availability signal as a one-shot growth hint when the stream implementation + itself is caller-owned trusted code, and may then reserve the full immediate + target at once while keeping at least the geometric growth step; if that hint + is absent or insufficient, the reader must fall back to bounded geometric + growth from already buffered bytes. Serializers should not add their own + availability branches. +- A truncated stream should fail before allocating the final deserialized value + and should allocate only for bytes actually read plus bounded spare capacity. + +The byte owner should stay byte-oriented. Buffer, reader, or read-context APIs +may expose byte read and byte skip operations, but string decoding, decimal +parsing, primitive-array encoding, compression modes, and collection capacity +policy belong to the owning serializers. + +### Collection And Map Capacity + +Large valid collection inputs are allowed. If the input contains many encoded +elements, proportional deserialization is expected. + +The security requirement is to avoid disproportionate preallocation from a +declared logical count before enough input bytes justify that capacity. When +the repeated element or entry body is proven to consume at least one byte, a +reader that allocates or reserves from the declared count should call +`checkReadableBytes(logicalCount)` or the runtime equivalent before that +allocation. When the body may consume no bytes, the readable-byte requirement +may exclude the root operation's remaining unbacked-container allowance. The +reader must still account for actual input progress while reading the +container. The byte check does not decode the whole container, validate element +semantics, or replace chunk validation. Readers that do not preallocate from +the logical count may still grow proportionally as elements are actually read. + +Map or collection chunk validation is security-relevant only when missing +validation can cause a no-progress loop, unbounded resource growth, retained +state, or success across a Fory policy boundary. Protocol-allowed chunk +segmentation is normal input and is not a security issue by itself. + +### Unbacked Container Work Budget + +Runtimes enforce a root-scoped limit on count-driven collection elements and +map entries whose repeated read bodies are not backed by input progress. The +public option is named `maxUnbackedContainerItems` or the language-equivalent +spelling. Its default is `8192`; values must be non-negative, and zero is a +strict limit rather than an unlimited sentinel. + +The allowance is shared by all nested collections, maps, and compatible field +skip operations in one root read. Collection readers account for completed +items every 1024 elements and at the final partial window. Map readers account +at existing protocol chunk boundaries. Bytes actually consumed by the repeated +item bodies offset the completed item count in the same window. The budget does +not add framing, reject values on write, change reference publication, or +replace graph-memory accounting. + +Readers whose exact repeated operation is known to consume at least one byte +retain their direct loop and proportional readable-byte check. Generated and +compiled serializers should remove budget access and periodic branches from +those proven-positive paths. + +### Graph Memory Budget + +Runtimes should enforce a per-operation approximate gate for estimated memory created by one +materialized graph. This is cumulative accounting for graph owners created by one top-level +deserialization operation; it is not exact heap measurement and it is not a raw element-slot limit. +Actual process memory can be higher than the configured gate. + +The public configuration is `maxGraphMemoryBytes`. The default is a fixed `128 MiB` for all input +forms; positive user configuration overrides the default. Explicit non-positive configuration is +invalid and should be rejected when the runtime is created. The budget is not derived from input +size, and stream budgeting should not depend on dynamic bytes-read accounting. + +Graph budget accounting should: + +- be initialized in top-level read state, with cleanup owned by the top-level deserialization + `finally`; +- account only for Fory-created objects or storage that are retained by the + returned value graph; temporary helper objects used only during construction + are outside the graph budget; +- not claim to budget arbitrary constructor, callable, descriptor, finalizer, + or state-restoration internals that run after an explicit policy allows that + code; +- keep read context/read state limited to raw byte reservation; counted arithmetic and collection, + map, array, struct, and object storage formulas belong in the concrete serializer or generated + serializer owner; +- reject arithmetic overflow before comparing budget or allocating; +- estimate lower-bound shallow owner storage: reference-backed or heap-materialized collections, + maps, sets, and reference arrays reserve nonzero shallow self cost plus + backing/reference/inline storage, and reference-backed or heap-materialized struct, record, + POJO, tuple/product, compatible, generated, and dynamic object owners reserve a nonzero shallow + self cost plus shallow field storage; +- use a 4-byte reference slot when the actual reference slot size is not cheap or reliable to query, + and use primitive/value field widths for inline storage; +- preserve existing byte-availability checks before backing allocation or capacity reservation; +- skip enum/union as separate owners and skip dedicated string, binary, primitive scalar, primitive + array, and primitive dense-array leaf owners unless a runtime-specific owner section explicitly + includes them. + +Skipped leaf owners must still be gated by remaining input bytes. If the unread input does not +contain enough bytes for a string, binary value, primitive scalar, primitive array, or primitive +dense array, the runtime must not read or create that leaf value. + +Each runtime must inspect the concrete owner path before choosing formulas. Reserve self storage +exactly once at the owner that stores, boxes, or allocates the value. Deserialization facades may +reset the budget for each operation, but must not pre-reserve the top-level result type, self bytes, +or value storage. +Reference-backed paths reserve parent owner self cost plus reference storage, while each referenced +heap owner reserves its own shallow self cost when materialized. Inline/value paths reserve inline +element, field, or boxed storage in the holder/allocation owner; top-level value serializers and +generated struct/product read paths must not charge their own self storage. +For inline/value collection or map runtimes, the top-level value container itself is not charged by +the deserialization facade or by the container serializer only because it is the returned value. +Nested value containers are charged as inline slots of the parent holder or as backing storage +elements of the outer collection that actually owns those slots. Pointer, box, smart-pointer, or +type-erased materialization paths reserve the shallow storage for the heap value they allocate. +Parents must not recursively include child object, collection, map, string, binary, or primitive +dense-array contents; the child owner reserves its own shallow memory when it is materialized. + +#### Java Fory Core + +Java Fory core primitive-array serializers reserve the portable array header plus the logical +length multiplied by the primitive storage width. Primitive-list serializers reserve the returned +list's shallow owner, the backing-array header, and the same primitive storage. These known-length +paths reserve once after their existing proportional readable-byte check and before allocation; +they do not use incremental batches. Compressed inputs use the decompressed logical length, while +temporary compressed arrays remain construction scratch outside the retained graph budget. +Float16 and BFloat16 dense-array carriers also include their wrapper's shallow owner. When a boxed +list conversion first decodes a primitive array, the array's reservation remains as credit toward +the final list estimate, and the conversion reserves only a positive remaining difference. + +#### Generated Structural Targets + +Wire members and physical storage are separate inputs. Properties, accessors, interfaces, and +logical schema aliases are not physical fields and must not be charged as storage. A field that is +both serialized and stored is counted once. A storage-only declaration contributes its field width +but must not enter wire metadata or generated reads and writes. + +For C# ordinary classes, each directly annotated class owns the physical instance fields declared +by that class. An inheritable class provider publishes the cumulative parent-provider value plus +those direct fields. A sealed concrete serializer uses the same cumulative expression privately. +A concrete descendant uses the immediate accessible provider value and its own direct fields; it +must not enumerate referenced private metadata or reconstruct parent storage. The concrete object +serializer reserves one shallow object owner plus this cumulative field storage. + +A C# external class declaration owns the exact third-party physical fields it lists. An exact +field mapping contributes storage. A visible property mapping does not, so its backing field must +be listed separately. An ignored mapping must identify one exact class field and is storage-only. +External struct declarations support visible member mappings only. +Discoverable unmapped public instance fields may be added once. A `BaseOnly` declaration can own +the complete target and target-ancestor prefix used by an ordinary child. It must list every +non-public physical field in that prefix; the generator does not scan the referenced assembly for +private layout. + +Exact external private identities are version-pinned package ABI assertions. Runtime wire access +uses exact accessors and must not fall back to reflection, layout probing, or a different member. +Storage-only private declarations have no runtime accessor, so the application must validate them +against the pinned package version. + +Dart generators may additionally include public instance fields visible on the target at +generation time. Swift macros cannot inspect another type's stored layout and therefore use only +the external declaration. In every runtime, these formulas are resolved during generation and +must not add reflection, layout probing, allocation, or field enumeration to deserialization hot +paths. The normal owner rules still apply: a reference target reserves its shallow owner and field +storage, while an inline value target is charged by the holder that owns its storage. + +#### Runtime-Specific Owner Notes + +##### C++ + +C++ plain structs, products, and standard-library containers are value storage unless a pointer, +smart pointer, or type-erased owner allocates them on the heap. Top-level deserialization initializes +the remaining graph budget but does not reserve `sizeof(T)` for the returned value. Plain value +serializers must not reserve their own `sizeof(T)` only because they are reading a value. + +Generic collection and map serializers reserve the lower-bound element, key, and value storage +owned by the container path. Nested value container headers are charged when they are inline slots +of a parent object or elements in an outer container backing store. Smart-pointer and type-erased +materialization paths reserve the shallow storage for the heap value they allocate before publishing +or returning it. Generic C++ paths must not invent standard-library header, node, bucket, allocator, +or debug-layout overheads. + +##### Rust + +Rust structs, tuples, enums, and collection values are inline value storage unless a `Box`, `Rc`, +`Arc`, or type-erased owner allocates them. Top-level and derived value read paths initialize or +consume the budget but do not reserve `size_of::()` for the value being read. `Vec`, `HashMap`, +`BTreeMap`, and similar serializers reserve backing or entry value storage that they allocate from +counts; nested value container headers are charged as parent inline fields or outer backing elements. + +Boxed, reference-counted, and type-erased materialization paths reserve `size_of::()` for the heap +payload they create. Compile-time `size_of::()` formulas are acceptable in those allocation +owners, but value serializers should not add a parallel self-reserve for the same `T`. + +Before count-derived allocation, Rust owners whose exact repeated operation is proven to consume at +least one byte retain the full readable-byte gate. Uncertain owners require readable bytes only for +the portion of the count not covered by the remaining unbacked-item allowance. Apply the selected +gate exactly once at the allocation owner; do not repeat it after reading shared metadata. Writers +continue to encode legal compact or empty bodies and do not enforce this reader-side allowance. + +Fixed arrays do not allocate from their validated wire count and omit the allocation gate. `Vec`, +`VecDeque`, and `BinaryHeap` also omit it for zero-sized elements because they create no +count-derived backing allocation in that case. Node, bucket, and entry owners retain the gate where +the declared count drives allocation. Implementations must not substitute guessed allocation costs, +padding bytes, a global compact-body bypass, or a second collection or map codec. + +##### Swift + +Swift structs, enums, tuples, and collection values are value storage. Top-level value reads and +nested value serializers should not reserve their own self storage. The holder that owns the value, +such as a struct field, array backing store, dictionary entry storage, or boxed/dynamic +materialization path, owns the corresponding graph-budget reservation. + +Array, dictionary, and set serializers may reserve lower-bound backing storage using stable Swift +type-size information, such as `MemoryLayout.stride`, when they allocate or reserve that storage. +Class, existential, or boxed materialization paths reserve owner storage when Fory creates the +retained object or box. Runtime object-layout probing should not be added to hot read paths. + +##### Go + +Go structs and slice or map headers are value storage unless a pointer, interface materialization, or +other heap owner allocates them. Top-level deserialization and struct value serializers should not +reserve the returned struct or a nested inline struct by themselves. Pointer serializers reserve the +concrete struct storage when they allocate a retained `*T`. + +Slice, array, map, and set serializers reserve the backing or entry storage they allocate from +declared counts. Element and entry widths should come from stable type information captured by the +serializer or resolver when possible; read loops should not recompute reflective size information +when the owner already knows the concrete type. Interface or dynamic paths reserve only storage that +Fory clearly materializes and retains. + +##### C\# + +C# combines reference owners and inline value types. Classes, arrays, lists, dictionaries, hash sets, +and other heap containers reserve a nonzero shallow owner cost plus direct backing, reference-slot, +or inline element storage. A dictionary is a reference-type container even when its key or value type +is a struct, so the dictionary owner is still charged separately from its entry storage. + +Value structs do not reserve their own self storage when read inline; the holder that stores the +struct, such as an object field, array element, list backing store, dictionary entry, box, or dynamic +materialization path, owns that reservation. Boxing, `object`, and dynamic materialization paths +reserve a boxed owner when Fory creates the retained box. Owner constants should be real portable +lower bounds for the relevant C# object or container shape, not placeholder markers. + +Runtimes should not guess object headers, array headers, allocator headers, debug-mode fields, hash +buckets, tree links, hash-chain links, node headers, map-entry objects, spare blocks, or runtime +table layouts unless the owner path has a cheap, stable, explicit lower-bound storage signal and +documents the formula. Owner constants should be real lower bounds for the owner shape, not +placeholder markers. + +### Skip Semantics + +Skipping unknown or incompatible data is classified by concrete impact, not by +whether the runtime materializes a temporary value. + +Directly consuming encoded contents is useful when it is simple and owned by the +current runtime path. It is not a security requirement for complex fields such +as lists, sets, and maps. A runtime may materialize a value and discard it when +that preserves the existing serializer ownership model. + +For extension, dynamic, or user-owned types, the owning runtime may not always +have enough information to skip without invoking a registered serializer. In +that case, classify the behavior by concrete impact: + +- Resource leak, retained state, no-progress loop, or policy bypass is + security-relevant. +- Bounded materialization followed by an error or discard is allowed unless it + creates meaningful memory or CPU pressure. +- Pure strictness about whether a skipped value used one specific encoding shape + is not a security issue. + +### Metadata And Type Resolution + +Metadata parsing is security-sensitive when it affects retained read-side state, +type dispatch, or policy decisions. + +Metadata readers should: + +- Avoid unbounded recursion in nested metadata structures. +- Avoid unbounded table growth from attacker-controlled metadata streams. +- Validate metadata bodies before using them to bypass or replace existing + policy decisions. +- For Java metadata paths, keep name-level checks such as `TypeChecker` and the + disallowed-class list before `Class.forName` by routing remote class-name + loading through the existing `TypeResolver.loadClass` owner. Do not bypass + that owner with direct class loading from TypeDef or TypeMeta names. A rejected + input name must not cause class loading. Preserve registration, dynamic-loading, + and unknown-type semantics while moving this decision before loading. Checks + that require a materialized `Class` remain after loading; do not replace + them with string-only approximations. +- Pass a complete input array descriptor to `TypeChecker`. Input may derive up + to six array dimensions from an accepted component class. Higher-dimensional + arrays require an exact trusted full-array registration or checked name-cache + entry so input cannot make the JVM derive an unbounded family of array classes. +- Reset or release metadata state at the correct root-operation boundary. + +A class-resolution cache reachable from untrusted deserialization may publish +an entry only from explicit trusted configuration or after the active class +policy has accepted the resolved class. A cache hit therefore represents an +already trusted and validated `Class` and should use that cached class +without repeating class loading or name-level `TypeChecker` work. Only a cache +miss performs those name-level checks and publishes the accepted result. +Checks that require the materialized `Class` remain owned by their existing +caller. A cache entry that stores a data-only unknown-class placeholder may +return that same placeholder on an exact hit, but must not authorize loading the +original missed wire name. +Exact registered-name-table hits are trusted for both ID and name registrations, +and exact checked name-cache hits are trusted. After both exact lookups miss, a +reader must not infer another accepted name from inverse registration, +class-keyed state, or `Class.getName()`. A custom-name registration does not by +itself publish the Java class name as an additional alias; ID registration does +publish the Java class name. + +Remote metadata that can create persistent read state must be bounded before +that state is retained. The check is resource control only: it must not change +wire compatibility, type registration, dynamic class loading, unknown-type +handling, deserialization policy, or schema-evolution semantics. Failed or +incompatible metadata must not consume schema-version limits, and metadata +cache hits or generated field readers must not add validation, hashing, +allocation, or policy work for these limits. The concrete sequence for metadata +parsing, cache publishing, exact-local matching, and counting belongs to the +[xlang implementation guide](../specification/xlang_implementation_guide.md). + +The checked metadata cache is the only owner of whether a received TypeDef or +TypeMeta header has already been validated. A metadata cache hit means the +header was previously parsed, body/hash-validated, policy-checked, and +published by the owning cache, so the reader must skip the remaining metadata +body and use the cached metadata without repeating body validation, hash +validation, limit checks, exact-local checks, or policy work. A metadata cache +miss is the only path that parses the metadata body, validates its hash and +shape, enforces metadata limits, performs exact-local byte comparison, and +publishes to the cache. Do not add separate nullable flags, sentinel headers, +per-TypeInfo acceptance markers, or parallel state to represent this decision. + +Only metadata that is actually carried as a TypeDef or TypeMeta body is subject +to metadata body and schema-version limits. Compatible named enum, ext, and +union metadata normally has one version, but still counts against remote +metadata total limits when it is sent as shared metadata. Pure id-based enum, +ext, and typed-union values use type id plus user type id and must not be moved +onto this metadata body path. + +Remote metadata bodies and struct field lists must also be bounded on the cold +metadata parse path. `maxTypeMetaBytes` limits the encoded metadata body bytes +for one received TypeDef or TypeMeta body, excluding the 8-byte header and any +extended-size varint. `maxTypeFields` limits the number of fields declared by +one received struct metadata body. For Java native TypeDef class layers, the +field limit applies to the total field count across the class layers in that +one TypeDef. These limits are checked before copying, decompressing, reserving, +or allocating from attacker-declared metadata sizes or field counts. + +The default limits are `maxTypeFields = 512` and `maxTypeMetaBytes = 4096`. +Runtimes should report limit failures as possible malicious data and tell users +to increase the exact option only when the data is not malicious. These limits +must not introduce validation on metadata cache-hit, generated serializer, or +already-resolved type-id hot paths. + +Metadata byte-form strictness alone is not a security requirement. Rejecting a +metadata shape is useful only when the owner wants that strictness or when the +shape changes type identity, retained state, resource use, or policy behavior. + +### Reference Tracking + +Reference tracking is part of the wire protocol and is performance-sensitive. +Readers may use sentinel values and shared value-bearing branches to keep hot +paths compact. + +Reference tracking validation is security-relevant when malformed input can: + +- Access an out-of-range reference without reporting an error. +- Leave retained reference state after a failed root operation. +- Register unbounded callbacks or resolver state before the referenced value is + available. +- Cause a no-progress loop or crash. + +Reference tracking validation is not required merely because a malformed flag is +not rejected at the earliest possible byte. Lazy rejection is acceptable when +the root operation still returns an error and no security invariant is violated. +The downstream error does not need to be a dedicated reference-protocol error. + +### Error Propagation And Cleanup + +Fory runtimes may intentionally use lazy error propagation. After a read records +an error, later read steps may continue until the outer operation observes and +returns the error. + +This is acceptable when the continued work cannot: + +- Crash or panic. +- Allocate or retain attacker-controlled state. +- Leak resources. +- Bypass required cleanup. +- Return success across an explicit validation or policy boundary. + +Nested `try`/`finally` or equivalent cleanup should be added only when the +outer root-operation cleanup cannot cover the state or resource owned by the +nested path. + +### Performance Requirements + +Security validation must preserve Fory hot-path performance. Do not add +validation solely for strictness when it introduces: + +- Per-element object allocation. +- Dynamic dispatch or callbacks in hot loops. +- Wrapper objects or result carriers on success paths. +- Extra copying for buffer-backed string, binary, or primitive-array reads. +- Branches that do not protect a security invariant. +- Helper calls or generated-code expansion whose only purpose is to normalize + an eventual error's type, message, location, or timing. + +Prefer owner-local checks that can be inlined and that already use information +available in the current serializer. Do not move serializer-owned semantics into +generic read-context helpers. + +### Classification Guide + +Use the following questions when reviewing deserialization behavior: + +1. Can this input crash, panic, or access memory out of bounds? +2. Can a small or unproven input length cause disproportionate allocation? +3. Can a stream-backed reader grow a buffer before exact read or skip proves the + bytes exist? +4. Can a loop continue without byte progress or logical progress? +5. Can the path retain attacker-controlled state after the root operation fails? +6. Can the path leak resources or skip required cleanup? +7. Can the path return success across an explicit Fory policy boundary? +8. Is the proposed validation effectively free in the relevant hot path? + +If the answer to the first seven questions is no, the issue is normally not a +security finding. If the validation is not effectively free, avoid adding it +unless the protocol owner explicitly requires it. + +### Documentation Boundaries + +Security model documents must not include exploit samples, CVE narratives, +line-level vulnerability candidates, branch history, migration timelines, or +cleanup plans. Keep those details in private reports, issues, or pull requests +as appropriate. + +Public security documentation should describe durable boundaries and invariants, +not the history of how the implementation reached them. From 50d281b26348c0d3542aaf7d931be4a35fcfc36e Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 10:55:09 +0800 Subject: [PATCH 08/15] docs: distinguish landing page titles --- docs/benchmarks/index.md | 2 +- docs/compiler/generated-code/index.md | 2 +- docs/development/index.md | 18 ++++++++---------- docs/grpc/index.md | 2 +- docs/introduction/index.md | 2 +- docs/json/index.md | 2 +- docs/object-serialization/index.md | 2 +- docs/row-format/index.md | 2 +- docs/start/index.md | 2 +- 9 files changed, 16 insertions(+), 18 deletions(-) diff --git a/docs/benchmarks/index.md b/docs/benchmarks/index.md index 46af48783d..f9a50cc987 100644 --- a/docs/benchmarks/index.md +++ b/docs/benchmarks/index.md @@ -1,5 +1,5 @@ --- -title: Benchmarks +title: Results sidebar_position: 1 id: index license: | diff --git a/docs/compiler/generated-code/index.md b/docs/compiler/generated-code/index.md index bf76e92ae3..ac9908fa7b 100644 --- a/docs/compiler/generated-code/index.md +++ b/docs/compiler/generated-code/index.md @@ -1,5 +1,5 @@ --- -title: Generated Code +title: Overview sidebar_position: 1 id: index license: | diff --git a/docs/development/index.md b/docs/development/index.md index aea0a3e778..1584659bb0 100644 --- a/docs/development/index.md +++ b/docs/development/index.md @@ -1,5 +1,5 @@ --- -title: Development +title: Build and Test sidebar_position: 1 id: index license: | @@ -22,11 +22,9 @@ license: | Contributor documentation covers repository setup, builds, tests, debugging, and release-oriented workflows. It is separate from the user guides for released artifacts. -## Build and Test - Clone the source tree from https://github.com/apache/fory. -### Java +## Java ```bash cd java @@ -38,7 +36,7 @@ Requirements: - JDK 17+ - Maven 3.6.3+ -### Python +## Python ```bash cd python @@ -53,7 +51,7 @@ Requirements: - CPython 3.8+ - Bazel 8+ (required when building Cython extensions) -### C++ +## C++ ```bash cd cpp @@ -65,7 +63,7 @@ Requirements: - C++17 compiler - Bazel 8+ -### Go +## Go ```bash cd go/fory @@ -83,7 +81,7 @@ FORY_GO_JAVA_CI=1 ENABLE_FORY_DEBUG_OUTPUT=1 mvn test -Dtest=org.apache.fory.xla Requirement: Go 1.24+. -### Rust +## Rust ```bash cd rust @@ -100,7 +98,7 @@ Requirements: - Rust toolchain via rustup - `cargo-expand` (optional, for macro expansion debugging) -### JavaScript +## JavaScript ```bash cd javascript @@ -115,7 +113,7 @@ Requirements: - Node.js (LTS) - npm -### Markdown +## Markdown ```bash cd docs diff --git a/docs/grpc/index.md b/docs/grpc/index.md index 6fef383aee..a0c4712e0a 100644 --- a/docs/grpc/index.md +++ b/docs/grpc/index.md @@ -1,5 +1,5 @@ --- -title: Fory gRPC +title: Overview sidebar_position: 1 id: index license: | diff --git a/docs/introduction/index.md b/docs/introduction/index.md index 3f0bd9eed0..2245ef3700 100644 --- a/docs/introduction/index.md +++ b/docs/introduction/index.md @@ -1,5 +1,5 @@ --- -title: Introduction +title: Overview sidebar_position: 1 id: index license: | diff --git a/docs/json/index.md b/docs/json/index.md index 6ab963dd7a..61692814bf 100644 --- a/docs/json/index.md +++ b/docs/json/index.md @@ -1,5 +1,5 @@ --- -title: Fory JSON +title: Overview sidebar_position: 1 id: index license: | diff --git a/docs/object-serialization/index.md b/docs/object-serialization/index.md index 9ed38d9f8f..3cd7539d19 100644 --- a/docs/object-serialization/index.md +++ b/docs/object-serialization/index.md @@ -1,5 +1,5 @@ --- -title: Object Serialization +title: Introduction sidebar_position: 0 id: index license: | diff --git a/docs/row-format/index.md b/docs/row-format/index.md index 9960f200b4..8c901cd074 100644 --- a/docs/row-format/index.md +++ b/docs/row-format/index.md @@ -1,5 +1,5 @@ --- -title: Row Format +title: Introduction sidebar_position: 0 id: index license: | diff --git a/docs/start/index.md b/docs/start/index.md index 898a0e86a7..ea48248b39 100644 --- a/docs/start/index.md +++ b/docs/start/index.md @@ -1,5 +1,5 @@ --- -title: Getting Started +title: Overview sidebar_position: 0 id: index license: | From f4070e70368b92b593513958d2eab21a4dd47e25 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 11:12:11 +0800 Subject: [PATCH 09/15] docs: merge default xlang guides into basic serialization --- docs/json/index.md | 2 +- .../object-serialization/cpp/configuration.md | 2 +- docs/object-serialization/cpp/core-api.md | 283 ++++++++++- .../cpp/custom-serializers.md | 2 +- docs/object-serialization/cpp/index.md | 5 +- docs/object-serialization/cpp/native.md | 4 +- .../cpp/schema-evolution.md | 2 +- .../cpp/supported-types.md | 2 +- .../cpp/type-registration.md | 2 +- docs/object-serialization/cpp/xlang.md | 295 ------------ docs/object-serialization/csharp/core-api.md | 123 ++++- docs/object-serialization/csharp/index.md | 3 +- .../csharp/supported-types.md | 2 +- .../csharp/type-registration.md | 2 +- docs/object-serialization/csharp/xlang.md | 135 ------ .../dart/configuration.md | 2 +- docs/object-serialization/dart/core-api.md | 250 +++++++++- .../dart/custom-serializers.md | 2 +- docs/object-serialization/dart/index.md | 31 +- .../dart/schema-evolution.md | 2 +- .../dart/schema-metadata.md | 2 +- .../dart/supported-types.md | 2 +- .../dart/troubleshooting.md | 2 +- .../dart/type-registration.md | 4 +- docs/object-serialization/dart/xlang.md | 262 ----------- docs/object-serialization/go/core-api.md | 430 ++++++++++++++++- .../go/custom-serializers.md | 2 +- docs/object-serialization/go/index.md | 33 +- docs/object-serialization/go/native.md | 4 +- docs/object-serialization/go/references.md | 2 +- .../go/schema-evolution.md | 2 +- .../go/supported-types.md | 4 +- .../go/troubleshooting.md | 2 +- .../go/type-registration.md | 2 +- docs/object-serialization/go/xlang.md | 442 ------------------ .../java/advanced-features.md | 2 +- docs/object-serialization/java/core-api.md | 362 +++++++++++++- docs/object-serialization/java/index.md | 6 +- docs/object-serialization/java/native.md | 4 +- .../java/schema-evolution.md | 2 +- .../java/schema-metadata.md | 2 +- docs/object-serialization/java/xlang.md | 371 --------------- .../javascript/core-api.md | 194 +++++++- docs/object-serialization/javascript/index.md | 3 +- .../javascript/references.md | 2 +- .../javascript/schema-evolution.md | 4 +- .../javascript/supported-types.md | 2 +- .../javascript/troubleshooting.md | 2 +- .../javascript/type-registration.md | 4 +- docs/object-serialization/javascript/xlang.md | 206 -------- .../kotlin/{xlang.md => core-api.md} | 14 +- docs/object-serialization/kotlin/index.md | 2 +- docs/object-serialization/kotlin/native.md | 2 +- docs/object-serialization/python/core-api.md | 320 ++++++++++++- .../python/custom-serializers.md | 2 +- docs/object-serialization/python/index.md | 5 +- docs/object-serialization/python/native.md | 4 +- .../python/schema-evolution.md | 2 +- .../python/schema-metadata.md | 2 +- docs/object-serialization/python/xlang.md | 334 ------------- .../rust/configuration.md | 2 +- docs/object-serialization/rust/core-api.md | 302 +++++++++++- docs/object-serialization/rust/index.md | 5 +- docs/object-serialization/rust/native.md | 4 +- .../rust/schema-evolution.md | 2 +- .../rust/schema-metadata.md | 2 +- .../rust/type-registration.md | 2 +- docs/object-serialization/rust/xlang.md | 314 ------------- .../scala/{xlang.md => core-api.md} | 22 +- docs/object-serialization/scala/index.md | 2 +- docs/object-serialization/scala/native.md | 2 +- .../scala/schema-metadata.md | 2 +- docs/object-serialization/swift/core-api.md | 174 ++++++- docs/object-serialization/swift/index.md | 1 - docs/object-serialization/swift/xlang.md | 186 -------- docs/object-serialization/xlang/index.md | 22 +- .../xlang/type-identity.md | 12 +- docs/row-format/java.md | 2 +- docs/row-format/python.md | 2 +- docs/start/cpp.md | 2 +- docs/start/csharp.md | 2 +- docs/start/go.md | 2 +- docs/start/java.md | 2 +- docs/start/javascript.md | 2 +- docs/start/kotlin.md | 2 +- docs/start/python.md | 2 +- docs/start/rust.md | 2 +- docs/start/scala.md | 2 +- docs/start/swift.md | 2 +- go/fory/README.md | 2 +- javascript/README.md | 2 +- javascript/packages/core/README.md | 2 +- swift/README.md | 2 +- 93 files changed, 2573 insertions(+), 2714 deletions(-) delete mode 100644 docs/object-serialization/cpp/xlang.md delete mode 100644 docs/object-serialization/csharp/xlang.md delete mode 100644 docs/object-serialization/dart/xlang.md delete mode 100644 docs/object-serialization/go/xlang.md delete mode 100644 docs/object-serialization/java/xlang.md delete mode 100644 docs/object-serialization/javascript/xlang.md rename docs/object-serialization/kotlin/{xlang.md => core-api.md} (82%) delete mode 100644 docs/object-serialization/python/xlang.md delete mode 100644 docs/object-serialization/rust/xlang.md rename docs/object-serialization/scala/{xlang.md => core-api.md} (93%) delete mode 100644 docs/object-serialization/swift/xlang.md diff --git a/docs/json/index.md b/docs/json/index.md index 61692814bf..4b18647d3a 100644 --- a/docs/json/index.md +++ b/docs/json/index.md @@ -63,6 +63,6 @@ native or xlang protocol when reference identity or cycles are required. ## Related Java guides For binary serialization, start with [Java Object Serialization](../object-serialization/java/index.md) -and choose [xlang](../object-serialization/java/xlang.md) or +and choose [xlang](../object-serialization/java/core-api.md#cross-language-interoperability) or [native](../object-serialization/java/native.md). Binary builder options are documented separately in [Java Configuration](../object-serialization/java/configuration.md). diff --git a/docs/object-serialization/cpp/configuration.md b/docs/object-serialization/cpp/configuration.md index 389636732f..c33f3563b8 100644 --- a/docs/object-serialization/cpp/configuration.md +++ b/docs/object-serialization/cpp/configuration.md @@ -267,5 +267,5 @@ Security-related configuration: ## Related Topics - [Basic Serialization](core-api.md) - Using configured Fory -- [Xlang Serialization](xlang.md) - xlang mode details +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - xlang mode details - [Type Registration](type-registration.md) - Registering types diff --git a/docs/object-serialization/cpp/core-api.md b/docs/object-serialization/cpp/core-api.md index c9ca5cbc3d..4b2965dfd4 100644 --- a/docs/object-serialization/cpp/core-api.md +++ b/docs/object-serialization/cpp/core-api.md @@ -1,6 +1,6 @@ --- title: Basic Serialization -sidebar_position: 3 +sidebar_position: 1 id: core-api license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -19,7 +19,7 @@ license: | limitations under the License. --- -This page covers basic object graph serialization and the core serialization APIs. +This page covers basic object graph serialization and the core serialization APIs in the default xlang mode. ## Object Graph Serialization @@ -355,6 +355,285 @@ fory.register_struct(2); - **Disable Tracking**: Use `track_ref(false)` when references aren't needed - **Compact Encoding**: Variable-length encoding for space efficiency +## Cross-Language Interoperability + +The default xlang format is shared by all Fory runtimes. The following sections cover its cross-language type mapping, type identity, and interoperability requirements. + +This page explains how to use Fory xlang serialization between C++ and other languages. + +### Overview + +Apache Fory™ enables seamless data exchange between C++, Java, Python, Go, Rust, +JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. Xlang mode ensures +binary compatibility across all supported languages. + +### Xlang Configuration + +C++ defaults to xlang mode. Compatible schema evolution is also the xlang default. Set the mode explicitly in xlang examples: + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +auto fory = Fory::builder().xlang(true).build(); +``` + +### Xlang Example + +#### C++ Producer + +```cpp +#include "fory/serialization/fory.h" +#include + +using namespace fory::serialization; + +struct Message { + std::string topic; + int64_t timestamp; + std::map headers; + std::vector payload; + + bool operator==(const Message &other) const { + return topic == other.topic && timestamp == other.timestamp && + headers == other.headers && payload == other.payload; + } +}; +FORY_STRUCT(Message, topic, timestamp, headers, payload); + +int main() { + auto fory = Fory::builder().xlang(true).build(); + fory.register_struct(100); + + Message msg{ + "events.user", + 1699999999000, + {{"content-type", "application/json"}}, + {'h', 'e', 'l', 'l', 'o'} + }; + + auto result = fory.serialize(msg); + if (result.ok()) { + auto bytes = std::move(result).value(); + // write to file, send over network, etc. + std::ofstream file("message.bin", std::ios::binary); + file.write(reinterpret_cast(bytes.data()), bytes.size()); + } + return 0; +} +``` + +#### Java Consumer + +```java +import org.apache.fory.Fory; + +public class Message { + public String topic; + public long timestamp; + public Map headers; + public byte[] payload; +} + +public class Consumer { + public static void main(String[] args) throws Exception { + Fory fory = Fory.builder() + .withXlang(true) + .build(); + fory.register(Message.class, 100); // Same ID as C++ + + byte[] bytes = Files.readAllBytes(Path.of("message.bin")); + Message msg = (Message) fory.deserialize(bytes); + + System.out.println("Topic: " + msg.topic); + System.out.println("Timestamp: " + msg.timestamp); + } +} +``` + +#### Python Consumer + +```python +import pyfory + +class Message: + topic: str + timestamp: int + headers: dict[str, str] + payload: bytes + +fory = pyfory.Fory(xlang=True) +fory.register(Message, type_id=100) # Same ID as C++ + +with open("message.bin", "rb") as f: + data = f.read() + +msg = fory.deserialize(data) +print(f"Topic: {msg.topic}") +print(f"Timestamp: {msg.timestamp}") +``` + +### Type Mapping + +#### Primitive Types + +| C++ Type | Java Type | Python Type | Go Type | Rust Type | +| ------------------ | ---------- | ----------------- | ------------------- | ---------- | +| `bool` | `boolean` | `bool` | `bool` | `bool` | +| `int8_t` | `byte` | `int` | `int8` | `i8` | +| `int16_t` | `short` | `int` | `int16` | `i16` | +| `int32_t` | `int` | `int` | `int32` | `i32` | +| `int64_t` | `long` | `int` | `int64` | `i64` | +| `float` | `float` | `float` | `float32` | `f32` | +| `double` | `double` | `float` | `float64` | `f64` | +| `fory::float16_t` | `Float16` | `pyfory.Float16` | `float16.Float16` | `Float16` | +| `fory::bfloat16_t` | `BFloat16` | `pyfory.BFloat16` | `bfloat16.BFloat16` | `BFloat16` | + +#### String Types + +| C++ Type | Java Type | Python Type | Go Type | Rust Type | +| ------------- | --------- | ----------- | -------- | --------- | +| `std::string` | `String` | `str` | `string` | `String` | + +#### Collection Types + +| C++ Type | Java Type | Python Type | Go Type | Rust Type | +| ------------------------------------------- | -------------- | --------------- | --------------------- | --------------- | +| `std::vector` | `List` | `list` | `[]T` | `Vec` | +| `std::vector` | `Float16List` | `Float16Array` | `[]float16.Float16` | `Vec` | +| `std::vector` | `BFloat16List` | `BFloat16Array` | `[]bfloat16.BFloat16` | `Vec` | +| `std::set` | `Set` | `set` | `map[T]struct{}` | `HashSet` | +| `std::map` / `std::unordered_map` | `Map` | `dict` | `map[K]V` | `HashMap` | + +#### Lists and Dense Arrays + +`std::vector` maps to Fory `list` by default in handwritten C++ structs. +Use the field metadata DSL's array node when the schema is dense `array`. + +| Fory schema | C++ metadata sketch | +| ----------------- | ---------------------------------------- | +| `list` | `fory::F(id).list(fory::T::int32())` | +| `array` | `fory::F(id).array(fory::T::bool_())` | +| `array` | `fory::F(id).array(fory::T::int8())` | +| `array` | `fory::F(id).array(fory::T::int16())` | +| `array` | `fory::F(id).array(fory::T::int32())` | +| `array` | `fory::F(id).array(fory::T::int64())` | +| `array` | `fory::F(id).array(fory::T::uint8())` | +| `array` | `fory::F(id).array(fory::T::uint16())` | +| `array` | `fory::F(id).array(fory::T::uint32())` | +| `array` | `fory::F(id).array(fory::T::uint64())` | +| `array` | `fory::F(id).array(fory::T::float16())` | +| `array` | `fory::F(id).array(fory::T::bfloat16())` | +| `array` | `fory::F(id).array(fory::T::float32())` | +| `array` | `fory::F(id).array(fory::T::float64())` | + +#### Temporal Types + +| C++ Type | Java Type | Python Type | Go Type | +| ----------------- | ----------- | --------------- | --------------- | +| `fory::Timestamp` | `Instant` | `datetime` | `time.Time` | +| `fory::Duration` | `Duration` | `timedelta` | `time.Duration` | +| `fory::Date` | `LocalDate` | `datetime.date` | `time.Time` | + +### Field Order Requirements + +**Critical:** Fields are sorted by snake_case field name. The converted names must match across languages. + +#### C++ + +```cpp +struct Person { + std::string name; // Field 0 + int32_t age; // Field 1 + std::string email; // Field 2 +}; +FORY_STRUCT(Person, name, age, email); // Order matters! +``` + +#### Java + +```java +public class Person { + public String name; // Field 0 + public int age; // Field 1 + public String email; // Field 2 +} +``` + +#### Python + +```python +class Person: + name: str # Field 0 + age: int # Field 1 + email: str # Field 2 +``` + +### Type ID Consistency + +All languages must use the same type IDs: + +```cpp +// C++ +fory.register_struct(100); +fory.register_struct
(101); +fory.register_struct(102); +``` + +```java +// Java +fory.register(Person.class, 100); +fory.register(Address.class, 101); +fory.register(Order.class, 102); +``` + +```python +# Python +fory.register(Person, type_id=100) +fory.register(Address, type_id=101) +fory.register(Order, type_id=102) +``` + +### Compatible Mode + +Xlang mode already uses compatible schema evolution by default. Keep that default for schemas that +may evolve independently: + +```cpp +auto fory = Fory::builder().xlang(true).build(); +``` + +Compatible mode allows: + +- Adding new fields (with defaults) +- Removing unused fields +- Reordering fields + +### Interoperability Troubleshooting + +#### Type Mismatch Errors + +``` +Error: Type mismatch: expected 100, got 101 +``` + +**Solution:** Ensure type IDs match across all languages. + +#### Encoding Errors + +``` +Error: Invalid UTF-8 sequence +``` + +**Solution:** Ensure strings are valid UTF-8 in all languages. + +### Related Guides + +- [Configuration](configuration.md) - Builder options +- [Type Registration](type-registration.md) - Registering types +- [Supported Types](supported-types.md) - Type compatibility + ## Related Topics - [Configuration](configuration.md) - Builder options diff --git a/docs/object-serialization/cpp/custom-serializers.md b/docs/object-serialization/cpp/custom-serializers.md index bc7106e79e..2604191a5f 100644 --- a/docs/object-serialization/cpp/custom-serializers.md +++ b/docs/object-serialization/cpp/custom-serializers.md @@ -368,4 +368,4 @@ static MyType read_data(ReadContext &ctx) { - [Type Registration](type-registration.md) - Registering serializers - [Basic Serialization](core-api.md) - Using FORY_STRUCT macro - [Schema Evolution](schema-evolution.md) - Compatible mode -- [Xlang Serialization](xlang.md) - Cross-language serialization +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language serialization diff --git a/docs/object-serialization/cpp/index.md b/docs/object-serialization/cpp/index.md index c69bcd5215..e0ce640444 100644 --- a/docs/object-serialization/cpp/index.md +++ b/docs/object-serialization/cpp/index.md @@ -222,7 +222,7 @@ Use xlang mode for cross-language payloads and schemas shared with other Fory im Use native mode for C++-only traffic. Native mode is selected with `.xlang(false)` and keeps C++ object serialization in C++-native form. It is optimized for C++ types and avoids portable xlang type-mapping constraints when the payload never leaves C++. Compatible mode is enabled by default. Set `.compatible(false)` only when every reader and writer uses the same C++ schema and you want faster serialization and smaller size. -See [Xlang Serialization](xlang.md) for C++ xlang registration and interoperability rules, and [Native Serialization](native.md) for C++-only payloads. +See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for C++ xlang registration and interoperability rules, and [Native Serialization](native.md) for C++-only payloads. ## Thread Safety @@ -264,8 +264,7 @@ std::thread t2([&]() { ## Next Steps - [Configuration](configuration.md) - Builder options and modes -- [Basic Serialization](core-api.md) - Object graph serialization -- [Xlang Serialization](xlang.md) - xlang mode and interoperability +- [Basic Serialization](core-api.md) - Default xlang object graphs and interoperability - [Native Serialization](native.md) - C++-only serialization - [Schema Metadata](schema-metadata.md) - Field-level metadata (nullable, ref tracking) - [Schema Evolution](schema-evolution.md) - Compatible mode and schema changes diff --git a/docs/object-serialization/cpp/native.md b/docs/object-serialization/cpp/native.md index a93c9ed06d..c648a38e37 100644 --- a/docs/object-serialization/cpp/native.md +++ b/docs/object-serialization/cpp/native.md @@ -23,7 +23,7 @@ C++ native serialization is the C++-only wire mode selected with `.xlang(false)` writer and reader is C++ and the payload should follow C++ type behavior instead of the portable xlang type system. -Use [Xlang Serialization](xlang.md), the default C++ mode, when +Use [Cross-Language Interoperability](core-api.md#cross-language-interoperability), the default C++ mode, when bytes must be read by Java, Python, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another non-C++ Fory implementation. @@ -206,7 +206,7 @@ Enable `.track_ref(true)` and verify the graph uses supported pointer patterns. ## Related Topics -- [Xlang Serialization](xlang.md) - Cross-language C++ payloads +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language C++ payloads - [Configuration](configuration.md) - Builder options - [Basic Serialization](core-api.md) - Object graph serialization - [Supported Types](supported-types.md) - C++ type support diff --git a/docs/object-serialization/cpp/schema-evolution.md b/docs/object-serialization/cpp/schema-evolution.md index 8ca540479d..15cd31f372 100644 --- a/docs/object-serialization/cpp/schema-evolution.md +++ b/docs/object-serialization/cpp/schema-evolution.md @@ -421,4 +421,4 @@ Both instances can exchange data even with different schema versions. - [Configuration](configuration.md) - Enabling compatible mode - [Type Registration](type-registration.md) - Type ID management -- [Xlang Serialization](xlang.md) - Cross-language considerations +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language considerations diff --git a/docs/object-serialization/cpp/supported-types.md b/docs/object-serialization/cpp/supported-types.md index 11f21a9e33..c77b0ee6ba 100644 --- a/docs/object-serialization/cpp/supported-types.md +++ b/docs/object-serialization/cpp/supported-types.md @@ -297,4 +297,4 @@ Currently not supported: - [Basic Serialization](core-api.md) - Using these types - [Type Registration](type-registration.md) - Registering types -- [Xlang Serialization](xlang.md) - Cross-language compatibility +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language compatibility diff --git a/docs/object-serialization/cpp/type-registration.md b/docs/object-serialization/cpp/type-registration.md index 6e2a3e5189..dd45edb151 100644 --- a/docs/object-serialization/cpp/type-registration.md +++ b/docs/object-serialization/cpp/type-registration.md @@ -250,5 +250,5 @@ if (!result.ok()) { ## Related Topics - [Basic Serialization](core-api.md) - Using registered types -- [Xlang Serialization](xlang.md) - Cross-language considerations +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language considerations - [Supported Types](supported-types.md) - All supported types diff --git a/docs/object-serialization/cpp/xlang.md b/docs/object-serialization/cpp/xlang.md deleted file mode 100644 index 726f63bda5..0000000000 --- a/docs/object-serialization/cpp/xlang.md +++ /dev/null @@ -1,295 +0,0 @@ ---- -title: Xlang Serialization -sidebar_position: 1 -id: xlang -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -This page explains how to use Fory xlang serialization between C++ and other languages. - -## Overview - -Apache Fory™ enables seamless data exchange between C++, Java, Python, Go, Rust, -JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. Xlang mode ensures -binary compatibility across all supported languages. - -## Create an Xlang Fory Instance - -C++ defaults to xlang mode. Compatible schema evolution is also the xlang default. Set the mode explicitly in xlang examples: - -```cpp -#include "fory/serialization/fory.h" - -using namespace fory::serialization; - -auto fory = Fory::builder().xlang(true).build(); -``` - -## Xlang Example - -### C++ Producer - -```cpp -#include "fory/serialization/fory.h" -#include - -using namespace fory::serialization; - -struct Message { - std::string topic; - int64_t timestamp; - std::map headers; - std::vector payload; - - bool operator==(const Message &other) const { - return topic == other.topic && timestamp == other.timestamp && - headers == other.headers && payload == other.payload; - } -}; -FORY_STRUCT(Message, topic, timestamp, headers, payload); - -int main() { - auto fory = Fory::builder().xlang(true).build(); - fory.register_struct(100); - - Message msg{ - "events.user", - 1699999999000, - {{"content-type", "application/json"}}, - {'h', 'e', 'l', 'l', 'o'} - }; - - auto result = fory.serialize(msg); - if (result.ok()) { - auto bytes = std::move(result).value(); - // write to file, send over network, etc. - std::ofstream file("message.bin", std::ios::binary); - file.write(reinterpret_cast(bytes.data()), bytes.size()); - } - return 0; -} -``` - -### Java Consumer - -```java -import org.apache.fory.Fory; - -public class Message { - public String topic; - public long timestamp; - public Map headers; - public byte[] payload; -} - -public class Consumer { - public static void main(String[] args) throws Exception { - Fory fory = Fory.builder() - .withXlang(true) - .build(); - fory.register(Message.class, 100); // Same ID as C++ - - byte[] bytes = Files.readAllBytes(Path.of("message.bin")); - Message msg = (Message) fory.deserialize(bytes); - - System.out.println("Topic: " + msg.topic); - System.out.println("Timestamp: " + msg.timestamp); - } -} -``` - -### Python Consumer - -```python -import pyfory - -class Message: - topic: str - timestamp: int - headers: dict[str, str] - payload: bytes - -fory = pyfory.Fory(xlang=True) -fory.register(Message, type_id=100) # Same ID as C++ - -with open("message.bin", "rb") as f: - data = f.read() - -msg = fory.deserialize(data) -print(f"Topic: {msg.topic}") -print(f"Timestamp: {msg.timestamp}") -``` - -## Type Mapping - -### Primitive Types - -| C++ Type | Java Type | Python Type | Go Type | Rust Type | -| ------------------ | ---------- | ----------------- | ------------------- | ---------- | -| `bool` | `boolean` | `bool` | `bool` | `bool` | -| `int8_t` | `byte` | `int` | `int8` | `i8` | -| `int16_t` | `short` | `int` | `int16` | `i16` | -| `int32_t` | `int` | `int` | `int32` | `i32` | -| `int64_t` | `long` | `int` | `int64` | `i64` | -| `float` | `float` | `float` | `float32` | `f32` | -| `double` | `double` | `float` | `float64` | `f64` | -| `fory::float16_t` | `Float16` | `pyfory.Float16` | `float16.Float16` | `Float16` | -| `fory::bfloat16_t` | `BFloat16` | `pyfory.BFloat16` | `bfloat16.BFloat16` | `BFloat16` | - -### String Types - -| C++ Type | Java Type | Python Type | Go Type | Rust Type | -| ------------- | --------- | ----------- | -------- | --------- | -| `std::string` | `String` | `str` | `string` | `String` | - -### Collection Types - -| C++ Type | Java Type | Python Type | Go Type | Rust Type | -| ------------------------------------------- | -------------- | --------------- | --------------------- | --------------- | -| `std::vector` | `List` | `list` | `[]T` | `Vec` | -| `std::vector` | `Float16List` | `Float16Array` | `[]float16.Float16` | `Vec` | -| `std::vector` | `BFloat16List` | `BFloat16Array` | `[]bfloat16.BFloat16` | `Vec` | -| `std::set` | `Set` | `set` | `map[T]struct{}` | `HashSet` | -| `std::map` / `std::unordered_map` | `Map` | `dict` | `map[K]V` | `HashMap` | - -### Lists and Dense Arrays - -`std::vector` maps to Fory `list` by default in handwritten C++ structs. -Use the field metadata DSL's array node when the schema is dense `array`. - -| Fory schema | C++ metadata sketch | -| ----------------- | ---------------------------------------- | -| `list` | `fory::F(id).list(fory::T::int32())` | -| `array` | `fory::F(id).array(fory::T::bool_())` | -| `array` | `fory::F(id).array(fory::T::int8())` | -| `array` | `fory::F(id).array(fory::T::int16())` | -| `array` | `fory::F(id).array(fory::T::int32())` | -| `array` | `fory::F(id).array(fory::T::int64())` | -| `array` | `fory::F(id).array(fory::T::uint8())` | -| `array` | `fory::F(id).array(fory::T::uint16())` | -| `array` | `fory::F(id).array(fory::T::uint32())` | -| `array` | `fory::F(id).array(fory::T::uint64())` | -| `array` | `fory::F(id).array(fory::T::float16())` | -| `array` | `fory::F(id).array(fory::T::bfloat16())` | -| `array` | `fory::F(id).array(fory::T::float32())` | -| `array` | `fory::F(id).array(fory::T::float64())` | - -### Temporal Types - -| C++ Type | Java Type | Python Type | Go Type | -| ----------------- | ----------- | --------------- | --------------- | -| `fory::Timestamp` | `Instant` | `datetime` | `time.Time` | -| `fory::Duration` | `Duration` | `timedelta` | `time.Duration` | -| `fory::Date` | `LocalDate` | `datetime.date` | `time.Time` | - -## Field Order Requirements - -**Critical:** Fields are sorted by snake_case field name. The converted names must match across languages. - -### C++ - -```cpp -struct Person { - std::string name; // Field 0 - int32_t age; // Field 1 - std::string email; // Field 2 -}; -FORY_STRUCT(Person, name, age, email); // Order matters! -``` - -### Java - -```java -public class Person { - public String name; // Field 0 - public int age; // Field 1 - public String email; // Field 2 -} -``` - -### Python - -```python -class Person: - name: str # Field 0 - age: int # Field 1 - email: str # Field 2 -``` - -## Type ID Consistency - -All languages must use the same type IDs: - -```cpp -// C++ -fory.register_struct(100); -fory.register_struct
(101); -fory.register_struct(102); -``` - -```java -// Java -fory.register(Person.class, 100); -fory.register(Address.class, 101); -fory.register(Order.class, 102); -``` - -```python -# Python -fory.register(Person, type_id=100) -fory.register(Address, type_id=101) -fory.register(Order, type_id=102) -``` - -## Compatible Mode - -Xlang mode already uses compatible schema evolution by default. Keep that default for schemas that -may evolve independently: - -```cpp -auto fory = Fory::builder().xlang(true).build(); -``` - -Compatible mode allows: - -- Adding new fields (with defaults) -- Removing unused fields -- Reordering fields - -## Troubleshooting - -### Type Mismatch Errors - -``` -Error: Type mismatch: expected 100, got 101 -``` - -**Solution:** Ensure type IDs match across all languages. - -### Encoding Errors - -``` -Error: Invalid UTF-8 sequence -``` - -**Solution:** Ensure strings are valid UTF-8 in all languages. - -## Related Topics - -- [Configuration](configuration.md) - Builder options -- [Type Registration](type-registration.md) - Registering types -- [Supported Types](supported-types.md) - Type compatibility diff --git a/docs/object-serialization/csharp/core-api.md b/docs/object-serialization/csharp/core-api.md index 43020f8130..561ee3c42a 100644 --- a/docs/object-serialization/csharp/core-api.md +++ b/docs/object-serialization/csharp/core-api.md @@ -1,6 +1,6 @@ --- title: Basic Serialization -sidebar_position: 3 +sidebar_position: 1 id: core-api license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -19,7 +19,7 @@ license: | limitations under the License. --- -This page covers typed serialization APIs in Apache Fory™ C#. +This page covers typed serialization APIs in the default xlang mode for Apache Fory™ C#. ## Object Graph Serialization @@ -159,6 +159,125 @@ fory.Serialize(dynamicWriter, value); - Register user types handled by `[ForyStruct]`, `[ForyEnum]`, `[ForyUnion]`, external structural serializers, or custom serializers explicitly. +## Cross-Language Interoperability + +The default xlang format is shared by all Fory runtimes. The following sections cover its cross-language type mapping, type identity, and interoperability requirements. + +Apache Fory™ C# supports xlang serialization with other Fory implementations. + +### Xlang Configuration + +C# always writes and reads the xlang frame header. There is no mode switch, so interoperability code +only needs to configure the remaining settings such as compatibility mode and reference +tracking. + +```csharp +Fory fory = Fory.Builder() + .Build(); +``` + +### Register with Stable IDs + +```csharp +[ForyStruct] +public sealed class Person +{ + public string Name { get; set; } = string.Empty; + public int Age { get; set; } +} + +Fory fory = Fory.Builder() + .Build(); + +fory.Register(100); +``` + +Use the same ID mapping on all languages. + +Third-party classes, structs, and enums can use +[external-type serialization](external-types.md). Register the target type, +not its local serializer declaration, with the same ID or name used by the +other language peers. + +### Register by Name + +```csharp +fory.Register("com.example.Person"); +``` + +### Xlang Example + +#### C# (Serializer) + +```csharp +Person person = new() { Name = "Alice", Age = 30 }; +byte[] payload = fory.Serialize(person); +``` + +#### Java (Deserializer) + +```java +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + +fory.register(Person.class, 100); +Person value = (Person) fory.deserialize(payloadFromCSharp); +``` + +#### Python (Deserializer) + +```python +import pyfory + +fory = pyfory.Fory(xlang=True, ref=True) +fory.register_type(Person, type_id=100) +value = fory.deserialize(payload_from_csharp) +``` + +### Type Mapping Reference + +See [xlang guide](../xlang/index.md) for complete mapping. + +For reduced-precision numeric payloads, use `Half` / `Half[]` or `List` for xlang `float16`, and `BFloat16` / `BFloat16[]` or `List` for xlang `bfloat16`. + +### Lists and Dense Arrays + +C# `List` maps to Fory `list`. Use the schema marker +`Apache.Fory.Schema.Types.Array` when a field is dense `array`. + +| Fory schema | C# schema marker sketch | +| ----------------- | ----------------------- | +| `list` | `S.List` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | + +### Interoperability Best Practices + +1. Keep type IDs stable and documented. +2. Keep compatible mode enabled for rolling upgrades. +3. Register all user types on both read/write peers. +4. Validate integration with real payload round trips. + +### Related Guides + +- [Type Registration](type-registration.md) +- [External Types](external-types.md) +- [Schema Evolution](schema-evolution.md) +- [Supported Types](supported-types.md) + ## Related Topics - [Type Registration](type-registration.md) diff --git a/docs/object-serialization/csharp/index.md b/docs/object-serialization/csharp/index.md index 66e3d2b5ff..20459b8696 100644 --- a/docs/object-serialization/csharp/index.md +++ b/docs/object-serialization/csharp/index.md @@ -89,8 +89,7 @@ User decoded = fory.Deserialize(payload); | Topic | Description | | ------------------------------------------- | --------------------------------------------- | | [Configuration](configuration.md) | Builder options and mode settings | -| [Basic Serialization](core-api.md) | Typed and dynamic serialization APIs | -| [Xlang Serialization](xlang.md) | Interoperability guidance | +| [Basic Serialization](core-api.md) | Default xlang APIs and interoperability | | [Schema Metadata](schema-metadata.md) | `[ForyField]` ids and schema type descriptors | | [Type Registration](type-registration.md) | Registering user types and custom serializers | | [External Types](external-types.md) | Serializers for third-party types | diff --git a/docs/object-serialization/csharp/supported-types.md b/docs/object-serialization/csharp/supported-types.md index 7f4a060456..c0e3a870bb 100644 --- a/docs/object-serialization/csharp/supported-types.md +++ b/docs/object-serialization/csharp/supported-types.md @@ -114,4 +114,4 @@ Dynamic object payloads via `Serialize` / `Deserialize` suppor - [Basic Serialization](core-api.md) - [External Types](external-types.md) - [Type Registration](type-registration.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/csharp/type-registration.md b/docs/object-serialization/csharp/type-registration.md index 2d64ee2c49..9a48292154 100644 --- a/docs/object-serialization/csharp/type-registration.md +++ b/docs/object-serialization/csharp/type-registration.md @@ -97,4 +97,4 @@ fory.Register(101); - [Basic Serialization](core-api.md) - [External Types](external-types.md) - [Custom Serializers](custom-serializers.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/csharp/xlang.md b/docs/object-serialization/csharp/xlang.md deleted file mode 100644 index fe17a459c5..0000000000 --- a/docs/object-serialization/csharp/xlang.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: Xlang Serialization -sidebar_position: 1 -id: xlang -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Apache Fory™ C# supports xlang serialization with other Fory implementations. - -## Xlang Fory Instance - -C# always writes and reads the xlang frame header. There is no mode switch, so interoperability code -only needs to configure the remaining settings such as compatibility mode and reference -tracking. - -```csharp -Fory fory = Fory.Builder() - .Build(); -``` - -## Register with Stable IDs - -```csharp -[ForyStruct] -public sealed class Person -{ - public string Name { get; set; } = string.Empty; - public int Age { get; set; } -} - -Fory fory = Fory.Builder() - .Build(); - -fory.Register(100); -``` - -Use the same ID mapping on all languages. - -Third-party classes, structs, and enums can use -[external-type serialization](external-types.md). Register the target type, -not its local serializer declaration, with the same ID or name used by the -other language peers. - -## Register by Name - -```csharp -fory.Register("com.example.Person"); -``` - -## Xlang Example - -### C# (Serializer) - -```csharp -Person person = new() { Name = "Alice", Age = 30 }; -byte[] payload = fory.Serialize(person); -``` - -### Java (Deserializer) - -```java -Fory fory = Fory.builder() - .withXlang(true) - .withRefTracking(true) - .build(); - -fory.register(Person.class, 100); -Person value = (Person) fory.deserialize(payloadFromCSharp); -``` - -### Python (Deserializer) - -```python -import pyfory - -fory = pyfory.Fory(xlang=True, ref=True) -fory.register_type(Person, type_id=100) -value = fory.deserialize(payload_from_csharp) -``` - -## Type Mapping Reference - -See [xlang guide](../xlang/index.md) for complete mapping. - -For reduced-precision numeric payloads, use `Half` / `Half[]` or `List` for xlang `float16`, and `BFloat16` / `BFloat16[]` or `List` for xlang `bfloat16`. - -## Lists and Dense Arrays - -C# `List` maps to Fory `list`. Use the schema marker -`Apache.Fory.Schema.Types.Array` when a field is dense `array`. - -| Fory schema | C# schema marker sketch | -| ----------------- | ----------------------- | -| `list` | `S.List` | -| `array` | `S.Array` | -| `array` | `S.Array` | -| `array` | `S.Array` | -| `array` | `S.Array` | -| `array` | `S.Array` | -| `array` | `S.Array` | -| `array` | `S.Array` | -| `array` | `S.Array` | -| `array` | `S.Array` | -| `array` | `S.Array` | -| `array` | `S.Array` | -| `array` | `S.Array` | -| `array` | `S.Array` | - -## Best Practices - -1. Keep type IDs stable and documented. -2. Keep compatible mode enabled for rolling upgrades. -3. Register all user types on both read/write peers. -4. Validate integration with real payload round trips. - -## Related Topics - -- [Type Registration](type-registration.md) -- [External Types](external-types.md) -- [Schema Evolution](schema-evolution.md) -- [Supported Types](supported-types.md) diff --git a/docs/object-serialization/dart/configuration.md b/docs/object-serialization/dart/configuration.md index cbacbb5f65..7a58c1f2a4 100644 --- a/docs/object-serialization/dart/configuration.md +++ b/docs/object-serialization/dart/configuration.md @@ -177,4 +177,4 @@ Security-related configuration: - [Basic Serialization](core-api.md) - [Schema Evolution](schema-evolution.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/dart/core-api.md b/docs/object-serialization/dart/core-api.md index 08d586971a..6828df0213 100644 --- a/docs/object-serialization/dart/core-api.md +++ b/docs/object-serialization/dart/core-api.md @@ -1,6 +1,6 @@ --- title: Basic Serialization -sidebar_position: 2 +sidebar_position: 1 id: core-api license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -19,7 +19,7 @@ license: | limitations under the License. --- -This page shows how to serialize and deserialize values with Apache Fory™ Dart. +This page shows how to serialize and deserialize values in the default xlang mode for Apache Fory™ Dart. ## Create a `Fory` Instance @@ -138,6 +138,252 @@ PersonForyModule.register( If you skip registration, deserialization fails with `Type ... is not registered`. See [Type Registration](type-registration.md) and [Code Generation](code-generation.md). +## Cross-Language Interoperability + +The default xlang format is shared by all Fory runtimes. The following sections cover its cross-language type mapping, type identity, and interoperability requirements. + +Apache Fory™ Dart serializes to the same binary format as the Java, Go, C#, Python, Rust, and Swift Fory implementations. You can write a message in Dart and read it in Java — or any other direction — without any conversion layer. + +### Xlang Configuration + +Create a `Fory` instance as normal. There is no separate xlang option to enable in Dart: + +```dart +final fory = Fory(); // xlang payloads with compatible schema evolution +``` + +The key requirement is that both sides register the same type using the same identity. + +### Registration Identity + +The most important rule: **use the same type identity on every side**. You have two options: + +#### Numeric ID + +Simpler for small, tightly-coordinated teams: + +```dart +// Dart +ModelsForyModule.register(fory, Person, id: 100); +``` + +#### Namespace + Type Name + +Better when multiple teams define types independently: + +```dart +// Dart +ModelsForyModule.register( + fory, + Person, + name: 'example.Person', +); +``` + +Do not mix the two strategies for the same type across implementations. + +### External Types + +For a struct class owned by another Dart package, define an +[external structural serializer](external-types.md) and register the target +with the same ID or name used by every peer: + +```dart +@ForyStruct(target: third_party.User) +abstract final class UserSerializer { + @ForyField(id: 1) + late final String name; + + @ForyField(id: 2, type: Int32Type()) + late final int age; +} + +ExternalSerializersForyModule.register( + fory, + third_party.User, + id: 100, +); +``` + +The declaration's field IDs, names, nullability, and wire-width annotations +define the Dart-side xlang schema. An external declaration may explicitly list +an accessible inherited target property, but Fory does not automatically scan +the external target hierarchy. + +### Dart to Java Example + +#### Dart + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +@ForyStruct() +class Person { + Person(); + + String name = ''; + + @ForyField(type: Int32Type()) + int age = 0; +} + +final fory = Fory(); +PersonForyModule.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = 30); +``` + +#### Java + +```java +Fory fory = Fory.builder() + .withXlang(true) + .build(); + +fory.register(Person.class, 100); +Person value = (Person) fory.deserialize(bytesFromDart); +``` + +### Dart to C# Example + +#### Dart + +```dart +final fory = Fory(); +PersonForyModule.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = 30); +``` + +#### CSharp + +```csharp +[ForyStruct] +public sealed class Person +{ + public string Name { get; set; } = string.Empty; + public int Age { get; set; } +} + +Fory fory = Fory.Builder() + .Build(); + +fory.Register(100); +Person person = fory.Deserialize(payloadFromDart); +``` + +### Dart to Go Example + +#### Dart + +```dart +final fory = Fory(); +PersonForyModule.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = 30); +``` + +#### Go + +```go +type Person struct { + Name string + Age int32 +} + +f := fory.New(fory.WithXlang(true)) +_ = f.RegisterStruct(Person{}, 100) + +var person Person +_ = f.Deserialize(bytesFromDart, &person) +``` + +### Field Matching Rules + +Fory matches fields by name or by stable field ID. For robust cross-language interop: + +1. Use the same type identity on every side (same numeric ID or same `name`). +2. Assign stable `@ForyField(id: ...)` values to all fields before shipping the first payload. +3. Keep field names consistent or rely on IDs, since Dart typically uses `lowerCamelCase` while Go uses `PascalCase` for exported fields and C# often uses `PascalCase` properties. +4. Use explicit numeric field metadata: `@ForyField(type: Int32Type())` in Dart for Java `int`, Go `int32`, and C# `int`; `double` in Dart for 64-bit floats; `double` plus `Float16Type` or `Bfloat16Type` for 16-bit floats; `Float32` for 32-bit; `Int64` / `Uint64` for full-range 64-bit values. +5. Use `Timestamp`, `LocalDate`, and `Duration` for temporal fields rather than raw `DateTime`. +6. Validate real round trips across all languages before shipping. + +For an ordinary Dart class, Fory flattens concrete superclass and applied-mixin +storage into the annotated child's one struct schema. Parent and child fields +share one field-ID namespace and one canonical ordering, so the peer language +should define the equivalent included flat field set. Fields omitted by +`@ForyField(ignore: true)` or the concrete child's +`ignoreInheritedPrivateFields` option are absent from that peer schema. A +parent is not encoded as a nested object. + +Included inherited `@ForyField(ref: true)` and nested container reference +metadata use the same reference behavior as fields declared directly on the +child. Inheritance does not change xlang reference framing or add parent-level +reference state. + +### Type Mapping Notes for Dart + +Because Dart `int` is not itself a promise about the exact xlang wire width, prefer explicit field metadata when exact cross-language interpretation matters: + +- `@ForyField(type: Int32Type())` for xlang `int32` +- `@ForyField(type: Uint32Type())` for xlang `uint32` +- `@ForyField(type: Int8Type())` / `@ForyField(type: Int16Type())` / `@ForyField(type: Uint8Type())` / `@ForyField(type: Uint16Type())` for narrower integer widths +- `Int64` and `Uint64` for full-range 64-bit values on web +- `double` fields annotated with `Float16Type` or `Bfloat16Type` for 16-bit + floating-point scalars, and `Float32` for single-precision values +- `Float16List` and `Bfloat16List` for 16-bit floating-point array payloads +- `Timestamp`, `LocalDate`, and `Duration` for explicit temporal semantics + +#### Lists and Dense Arrays + +`List` always represents Fory `list` unless a field has explicit array +metadata. Use `array` only for dense one-dimensional bool or numeric data. + +| Fory schema | Dart field carrier and annotation | +| ----------------- | --------------------------------------------------- | +| `list` | `List` | +| `array` | `@ArrayField(element: BoolType()) BoolList` | +| `array` | `@ArrayField(element: Int8Type()) Int8List` | +| `array` | `@ArrayField(element: Int16Type()) Int16List` | +| `array` | `@ArrayField(element: Int32Type()) Int32List` | +| `array` | `@ArrayField(element: Int64Type()) Int64List` | +| `array` | `@ArrayField(element: Uint8Type()) Uint8List` | +| `array` | `@ArrayField(element: Uint16Type()) Uint16List` | +| `array` | `@ArrayField(element: Uint32Type()) Uint32List` | +| `array` | `@ArrayField(element: Uint64Type()) Uint64List` | +| `array` | `@ArrayField(element: Float16Type()) Float16List` | +| `array` | `@ArrayField(element: Bfloat16Type()) Bfloat16List` | +| `array` | `@ArrayField(element: Float32Type()) Float32List` | +| `array` | `@ArrayField(element: Float64Type()) Float64List` | + +See [Supported Types](supported-types.md) and [xlang type mapping](../../specification/xlang_type_mapping.md). + +### Validation + +Before relying on a cross-language contract in production, test a payload end-to-end through every implementation you support. + +Run the Dart side: + +```bash +dart run build_runner build +dart analyze +dart test +``` + +### Related Guides + +- [Struct Inheritance](inheritance.md) +- [Type Registration](type-registration.md) +- [External-Type Serialization](external-types.md) +- [Schema Evolution](schema-evolution.md) +- [Xlang guide](../xlang/index.md) + ## Related Topics - [Configuration](configuration.md) diff --git a/docs/object-serialization/dart/custom-serializers.md b/docs/object-serialization/dart/custom-serializers.md index 7284d14851..ea148777c9 100644 --- a/docs/object-serialization/dart/custom-serializers.md +++ b/docs/object-serialization/dart/custom-serializers.md @@ -140,5 +140,5 @@ Skipping this step causes back-references to that object to resolve to `null`. - [Type Registration](type-registration.md) - [External-Type Serialization](external-types.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - [Troubleshooting](troubleshooting.md) diff --git a/docs/object-serialization/dart/index.md b/docs/object-serialization/dart/index.md index abe3b3eae1..1880d604e6 100644 --- a/docs/object-serialization/dart/index.md +++ b/docs/object-serialization/dart/index.md @@ -137,22 +137,21 @@ constructors, mixins, and field inclusion options. ## Documentation -| Topic | Description | -| ------------------------------------------------ | --------------------------------------------------------------- | -| [Configuration](configuration.md) | Fory options, compatible mode, and safety limits | -| [Basic Serialization](core-api.md) | `serialize`, `deserialize`, generated registration, root graphs | -| [Code Generation](code-generation.md) | `@ForyStruct`, build runner, and generated modules | -| [Struct Inheritance](inheritance.md) | Superclasses, mixins, private fields, and constructors | -| [External-Type Serialization](external-types.md) | Generated serializers for classes owned by another package | -| [Xlang Serialization](xlang.md) | Interoperability rules and field alignment | -| [Schema Metadata](schema-metadata.md) | `@ForyField`, field IDs, nullability, references, polymorphism | -| [Type Registration](type-registration.md) | ID-based vs name-based registration and registration rules | -| [Custom Serializers](custom-serializers.md) | Custom `Serializer` implementations and unions | -| [Supported Types](supported-types.md) | Built-in xlang values, wrappers, collections, and structs | -| [Schema Evolution](schema-evolution.md) | Compatible structs and evolving schemas | -| [Web Platform Support](web-platform-support.md) | Dart VM/AOT, Flutter, and web support, limits, and validation | -| [gRPC Support](../../grpc/dart.md) | Generated Fory-backed gRPC service companions | -| [Troubleshooting](troubleshooting.md) | Common errors, diagnostics, and validation steps | +| Topic | Description | +| ------------------------------------------------ | -------------------------------------------------------------- | +| [Configuration](configuration.md) | Fory options, compatible mode, and safety limits | +| [Basic Serialization](core-api.md) | Default xlang APIs, registration, and interoperability | +| [Code Generation](code-generation.md) | `@ForyStruct`, build runner, and generated modules | +| [Struct Inheritance](inheritance.md) | Superclasses, mixins, private fields, and constructors | +| [External-Type Serialization](external-types.md) | Generated serializers for classes owned by another package | +| [Schema Metadata](schema-metadata.md) | `@ForyField`, field IDs, nullability, references, polymorphism | +| [Type Registration](type-registration.md) | ID-based vs name-based registration and registration rules | +| [Custom Serializers](custom-serializers.md) | Custom `Serializer` implementations and unions | +| [Supported Types](supported-types.md) | Built-in xlang values, wrappers, collections, and structs | +| [Schema Evolution](schema-evolution.md) | Compatible structs and evolving schemas | +| [Web Platform Support](web-platform-support.md) | Dart VM/AOT, Flutter, and web support, limits, and validation | +| [gRPC Support](../../grpc/dart.md) | Generated Fory-backed gRPC service companions | +| [Troubleshooting](troubleshooting.md) | Common errors, diagnostics, and validation steps | ## Related Resources diff --git a/docs/object-serialization/dart/schema-evolution.md b/docs/object-serialization/dart/schema-evolution.md index 45f5c328e1..01b889aeb1 100644 --- a/docs/object-serialization/dart/schema-evolution.md +++ b/docs/object-serialization/dart/schema-evolution.md @@ -132,4 +132,4 @@ runtime reference protocol or add a compatibility reader. - [Configuration](configuration.md) - [External-Type Serialization](external-types.md) - [Schema Metadata](schema-metadata.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/dart/schema-metadata.md b/docs/object-serialization/dart/schema-metadata.md index bd2edffe7d..31d76e4861 100644 --- a/docs/object-serialization/dart/schema-metadata.md +++ b/docs/object-serialization/dart/schema-metadata.md @@ -169,4 +169,4 @@ When the same model is defined in multiple languages: - [Code Generation](code-generation.md) - [External-Type Serialization](external-types.md) - [Schema Evolution](schema-evolution.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/dart/supported-types.md b/docs/object-serialization/dart/supported-types.md index 2136bf78de..9445d87a92 100644 --- a/docs/object-serialization/dart/supported-types.md +++ b/docs/object-serialization/dart/supported-types.md @@ -173,5 +173,5 @@ width is one of the most common cross-language bugs. - [Struct Inheritance](inheritance.md) - [Schema Metadata](schema-metadata.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - [Schema Evolution](schema-evolution.md) diff --git a/docs/object-serialization/dart/troubleshooting.md b/docs/object-serialization/dart/troubleshooting.md index 0a73e13874..f98bd033f2 100644 --- a/docs/object-serialization/dart/troubleshooting.md +++ b/docs/object-serialization/dart/troubleshooting.md @@ -217,7 +217,7 @@ separate protobuf service endpoint for generic protobuf clients. ## Related Topics - [Struct Inheritance](inheritance.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - [Code Generation](code-generation.md) - [Custom Serializers](custom-serializers.md) - [Web Platform Support](web-platform-support.md) diff --git a/docs/object-serialization/dart/type-registration.md b/docs/object-serialization/dart/type-registration.md index ff57fdbd1c..7a7e7e1da5 100644 --- a/docs/object-serialization/dart/type-registration.md +++ b/docs/object-serialization/dart/type-registration.md @@ -119,12 +119,12 @@ See [Custom Serializers](custom-serializers.md) for how to implement a serialize ## Xlang Requirements -The same numeric ID or name must be used in every peer that reads or writes the type. See [Xlang Serialization](xlang.md) for examples. +The same numeric ID or name must be used in every peer that reads or writes the type. See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for examples. ## Related Topics - [Struct Inheritance](inheritance.md) - [Code Generation](code-generation.md) - [External-Type Serialization](external-types.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - [Custom Serializers](custom-serializers.md) diff --git a/docs/object-serialization/dart/xlang.md b/docs/object-serialization/dart/xlang.md deleted file mode 100644 index e65c9210f9..0000000000 --- a/docs/object-serialization/dart/xlang.md +++ /dev/null @@ -1,262 +0,0 @@ ---- -title: Xlang Serialization -sidebar_position: 1 -id: xlang -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Apache Fory™ Dart serializes to the same binary format as the Java, Go, C#, Python, Rust, and Swift Fory implementations. You can write a message in Dart and read it in Java — or any other direction — without any conversion layer. - -## Setup - -Create a `Fory` instance as normal. There is no separate xlang option to enable in Dart: - -```dart -final fory = Fory(); // xlang payloads with compatible schema evolution -``` - -The key requirement is that both sides register the same type using the same identity. - -## Registration Identity - -The most important rule: **use the same type identity on every side**. You have two options: - -### Numeric ID - -Simpler for small, tightly-coordinated teams: - -```dart -// Dart -ModelsForyModule.register(fory, Person, id: 100); -``` - -### Namespace + Type Name - -Better when multiple teams define types independently: - -```dart -// Dart -ModelsForyModule.register( - fory, - Person, - name: 'example.Person', -); -``` - -Do not mix the two strategies for the same type across implementations. - -## External Types - -For a struct class owned by another Dart package, define an -[external structural serializer](external-types.md) and register the target -with the same ID or name used by every peer: - -```dart -@ForyStruct(target: third_party.User) -abstract final class UserSerializer { - @ForyField(id: 1) - late final String name; - - @ForyField(id: 2, type: Int32Type()) - late final int age; -} - -ExternalSerializersForyModule.register( - fory, - third_party.User, - id: 100, -); -``` - -The declaration's field IDs, names, nullability, and wire-width annotations -define the Dart-side xlang schema. An external declaration may explicitly list -an accessible inherited target property, but Fory does not automatically scan -the external target hierarchy. - -## Dart to Java Example - -### Dart - -```dart -import 'package:fory/fory.dart'; - -part 'person.fory.dart'; - -@ForyStruct() -class Person { - Person(); - - String name = ''; - - @ForyField(type: Int32Type()) - int age = 0; -} - -final fory = Fory(); -PersonForyModule.register(fory, Person, id: 100); -final bytes = fory.serialize(Person() - ..name = 'Alice' - ..age = 30); -``` - -### Java - -```java -Fory fory = Fory.builder() - .withXlang(true) - .build(); - -fory.register(Person.class, 100); -Person value = (Person) fory.deserialize(bytesFromDart); -``` - -## Dart to C# Example - -### Dart - -```dart -final fory = Fory(); -PersonForyModule.register(fory, Person, id: 100); -final bytes = fory.serialize(Person() - ..name = 'Alice' - ..age = 30); -``` - -### CSharp - -```csharp -[ForyStruct] -public sealed class Person -{ - public string Name { get; set; } = string.Empty; - public int Age { get; set; } -} - -Fory fory = Fory.Builder() - .Build(); - -fory.Register(100); -Person person = fory.Deserialize(payloadFromDart); -``` - -## Dart to Go Example - -### Dart - -```dart -final fory = Fory(); -PersonForyModule.register(fory, Person, id: 100); -final bytes = fory.serialize(Person() - ..name = 'Alice' - ..age = 30); -``` - -### Go - -```go -type Person struct { - Name string - Age int32 -} - -f := fory.New(fory.WithXlang(true)) -_ = f.RegisterStruct(Person{}, 100) - -var person Person -_ = f.Deserialize(bytesFromDart, &person) -``` - -## Field Matching Rules - -Fory matches fields by name or by stable field ID. For robust cross-language interop: - -1. Use the same type identity on every side (same numeric ID or same `name`). -2. Assign stable `@ForyField(id: ...)` values to all fields before shipping the first payload. -3. Keep field names consistent or rely on IDs, since Dart typically uses `lowerCamelCase` while Go uses `PascalCase` for exported fields and C# often uses `PascalCase` properties. -4. Use explicit numeric field metadata: `@ForyField(type: Int32Type())` in Dart for Java `int`, Go `int32`, and C# `int`; `double` in Dart for 64-bit floats; `double` plus `Float16Type` or `Bfloat16Type` for 16-bit floats; `Float32` for 32-bit; `Int64` / `Uint64` for full-range 64-bit values. -5. Use `Timestamp`, `LocalDate`, and `Duration` for temporal fields rather than raw `DateTime`. -6. Validate real round trips across all languages before shipping. - -For an ordinary Dart class, Fory flattens concrete superclass and applied-mixin -storage into the annotated child's one struct schema. Parent and child fields -share one field-ID namespace and one canonical ordering, so the peer language -should define the equivalent included flat field set. Fields omitted by -`@ForyField(ignore: true)` or the concrete child's -`ignoreInheritedPrivateFields` option are absent from that peer schema. A -parent is not encoded as a nested object. - -Included inherited `@ForyField(ref: true)` and nested container reference -metadata use the same reference behavior as fields declared directly on the -child. Inheritance does not change xlang reference framing or add parent-level -reference state. - -## Type Mapping Notes for Dart - -Because Dart `int` is not itself a promise about the exact xlang wire width, prefer explicit field metadata when exact cross-language interpretation matters: - -- `@ForyField(type: Int32Type())` for xlang `int32` -- `@ForyField(type: Uint32Type())` for xlang `uint32` -- `@ForyField(type: Int8Type())` / `@ForyField(type: Int16Type())` / `@ForyField(type: Uint8Type())` / `@ForyField(type: Uint16Type())` for narrower integer widths -- `Int64` and `Uint64` for full-range 64-bit values on web -- `double` fields annotated with `Float16Type` or `Bfloat16Type` for 16-bit - floating-point scalars, and `Float32` for single-precision values -- `Float16List` and `Bfloat16List` for 16-bit floating-point array payloads -- `Timestamp`, `LocalDate`, and `Duration` for explicit temporal semantics - -### Lists and Dense Arrays - -`List` always represents Fory `list` unless a field has explicit array -metadata. Use `array` only for dense one-dimensional bool or numeric data. - -| Fory schema | Dart field carrier and annotation | -| ----------------- | --------------------------------------------------- | -| `list` | `List` | -| `array` | `@ArrayField(element: BoolType()) BoolList` | -| `array` | `@ArrayField(element: Int8Type()) Int8List` | -| `array` | `@ArrayField(element: Int16Type()) Int16List` | -| `array` | `@ArrayField(element: Int32Type()) Int32List` | -| `array` | `@ArrayField(element: Int64Type()) Int64List` | -| `array` | `@ArrayField(element: Uint8Type()) Uint8List` | -| `array` | `@ArrayField(element: Uint16Type()) Uint16List` | -| `array` | `@ArrayField(element: Uint32Type()) Uint32List` | -| `array` | `@ArrayField(element: Uint64Type()) Uint64List` | -| `array` | `@ArrayField(element: Float16Type()) Float16List` | -| `array` | `@ArrayField(element: Bfloat16Type()) Bfloat16List` | -| `array` | `@ArrayField(element: Float32Type()) Float32List` | -| `array` | `@ArrayField(element: Float64Type()) Float64List` | - -See [Supported Types](supported-types.md) and [xlang type mapping](../../specification/xlang_type_mapping.md). - -## Validation - -Before relying on a cross-language contract in production, test a payload end-to-end through every implementation you support. - -Run the Dart side: - -```bash -dart run build_runner build -dart analyze -dart test -``` - -## Related Topics - -- [Struct Inheritance](inheritance.md) -- [Type Registration](type-registration.md) -- [External-Type Serialization](external-types.md) -- [Schema Evolution](schema-evolution.md) -- [Xlang guide](../xlang/index.md) diff --git a/docs/object-serialization/go/core-api.md b/docs/object-serialization/go/core-api.md index 0a92aa70cf..ba11553b0d 100644 --- a/docs/object-serialization/go/core-api.md +++ b/docs/object-serialization/go/core-api.md @@ -1,6 +1,6 @@ --- title: Basic Serialization -sidebar_position: 3 +sidebar_position: 1 id: core-api license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -19,7 +19,7 @@ license: | limitations under the License. --- -This guide covers the core serialization APIs in Fory Go. +This guide covers the core serialization APIs in the default xlang mode for Fory Go. ## Creating a Fory Instance @@ -401,6 +401,432 @@ func main() { } ``` +## Cross-Language Interoperability + +The default xlang format is shared by all Fory runtimes. The following sections cover its cross-language type mapping, type identity, and interoperability requirements. + +Fory Go enables seamless data exchange with Java, Python, C++, Rust, +JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. This guide covers +xlang compatibility and type mapping. + +### Xlang Configuration + +Go defaults to xlang mode with compatible schema evolution. Set the mode explicitly in xlang examples: + +```go +f := fory.New(fory.WithXlang(true)) +``` + +### Type Registration for Xlang + +Use consistent type IDs across all languages: + +#### Go + +```go +type User struct { + ID int64 + Name string +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(User{}, 1) +data, _ := f.Serialize(&User{ID: 1, Name: "Alice"}) +``` + +#### Java + +```java +public class User { + public long id; + public String name; +} +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(User.class, 1); +User user = fory.deserialize(data, User.class); +``` + +#### Python + +```python +from dataclasses import dataclass +import pyfory + +@dataclass +class User: + id: pyfory.Int64 + name: str + +fory = pyfory.Fory(xlang=True) +fory.register(User, type_id=1) +user = fory.deserialize(data) +``` + +### Type Mapping + +See [Type Mapping Specification](../../specification/xlang_type_mapping.md) for detailed type mappings across all languages. + +### Field Ordering + +Cross-language serialization requires consistent field ordering. Fory sorts fields by their snake_case names alphabetically. + +Go field names are converted to snake_case for sorting: + +```go +type Example struct { + UserID int64 // -> user_id + FirstName string // -> first_name + Age int32 // -> age +} + +// Sorted order: age, first_name, user_id +``` + +Ensure other languages use matching field names that produce the same snake_case ordering, or use field IDs for explicit control: + +```go +type Example struct { + UserID int64 `fory:"id=0"` + FirstName string `fory:"id=1"` + Age int32 `fory:"id=2"` +} +``` + +### Examples + +#### Go to Java + +**Go (Serializer)**: + +```go +type Order struct { + ID int64 + Customer string + Total float64 + Items []string +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Order{}, 1) + +order := &Order{ + ID: 12345, + Customer: "Alice", + Total: 99.99, + Items: []string{"Widget", "Gadget"}, +} +data, _ := f.Serialize(order) +// Send 'data' to Java service +``` + +**Java (Deserializer)**: + +```java +public class Order { + public long id; + public String customer; + public double total; + public List items; +} + +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(Order.class, 1); + +Order order = fory.deserialize(data, Order.class); +``` + +#### Python to Go + +**Python (Serializer)**: + +```python +from dataclasses import dataclass +import pyfory + +@dataclass +class Message: + id: pyfory.Int64 + content: str + timestamp: pyfory.Int64 + +fory = pyfory.Fory(xlang=True) +fory.register(Message, type_id=1) + +msg = Message(id=1, content="Hello from Python", timestamp=1234567890) +data = fory.serialize(msg) +``` + +**Go (Deserializer)**: + +```go +type Message struct { + ID int64 + Content string + Timestamp int64 +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Message{}, 1) + +var msg Message +f.Deserialize(data, &msg) +fmt.Println(msg.Content) // "Hello from Python" +``` + +#### Nested Structures + +Cross-language nested structures require all types to be registered: + +### Lists and Dense Arrays + +Go slices are ordinary `list` carriers unless a field tag explicitly requests +the dense `array` schema. Use `array` only for one-dimensional bool or +numeric data. + +| Fory schema | Go carrier and tag sketch | +| ----------------- | ------------------------------------------------------ | +| `list` | `[]int32` / `fory:"type=list(element=int32)"` | +| `array` | `[]bool` / `fory:"type=array(element=bool)"` | +| `array` | `[]int8` / `fory:"type=array(element=int8)"` | +| `array` | `[]int16` / `fory:"type=array(element=int16)"` | +| `array` | `[]int32` / `fory:"type=array(element=int32)"` | +| `array` | `[]int64` / `fory:"type=array(element=int64)"` | +| `array` | `[]uint8` / `fory:"type=array(element=uint8)"` | +| `array` | `[]uint16` / `fory:"type=array(element=uint16)"` | +| `array` | `[]uint32` / `fory:"type=array(element=uint32)"` | +| `array` | `[]uint64` / `fory:"type=array(element=uint64)"` | +| `array` | `[]float16.Float16` / `type=array(element=float16)` | +| `array` | `[]bfloat16.BFloat16` / `type=array(element=bfloat16)` | +| `array` | `[]float32` / `fory:"type=array(element=float32)"` | +| `array` | `[]float64` / `fory:"type=array(element=float64)"` | + +**Go**: + +```go +type Address struct { + Street string + City string + Country string +} + +type Company struct { + Name string + Address Address +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Address{}, 1) +f.RegisterStruct(Company{}, 2) +``` + +**Java**: + +```java +public class Address { + public String street; + public String city; + public String country; +} + +public class Company { + public String name; + public Address address; +} + +fory.register(Address.class, 1); +fory.register(Company.class, 2); +``` + +### Common Issues + +#### Field Name Mismatch + +Go uses PascalCase, other languages may use camelCase or snake_case. Fields are matched by their snake_case conversion: + +```go +// Go +type User struct { + FirstName string // -> first_name +} + +// Java - field name converted to snake_case must match +public class User { + public String firstName; // -> first_name (matches) +} +``` + +#### Type Interpretation + +Go unsigned types map to Java signed types with the same bit pattern: + +```go +var value uint64 = 18446744073709551615 // Max uint64 +``` + +Java's `long` holds the same bits but interprets as -1. Use `Long.toUnsignedString()` in Java if unsigned interpretation is needed. + +#### Nil vs Null + +Go nil slices/maps serialize differently based on configuration: + +```go +var slice []string = nil +// In xlang mode: serializes based on nullable configuration +``` + +Ensure other languages handle null appropriately. + +### Interoperability Best Practices + +1. **Use consistent type IDs**: Same numeric ID for the same type across all languages +2. **Register all types**: Including nested struct types +3. **Match field ordering**: Use same snake_case names or explicit field IDs +4. **Test cross-language**: Run integration tests early and often +5. **Handle type differences**: Be aware of signed/unsigned interpretation differences + +### Related Guides + +- [Type Registration](type-registration.md) +- [Supported Types](supported-types.md) +- [Schema Evolution](schema-evolution.md) +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) +- [Type Mapping Specification](../../specification/xlang_type_mapping.md) + +### Built-in values + +```go +package main + +import forygo "github.com/apache/fory/go/fory" +import "fmt" + +func main() { + list := []any{true, false, "str", -1.1, 1, make([]int32, 10), make([]float64, 20)} + fory := forygo.NewFory(forygo.WithXlang(true)) + bytes, err := fory.Marshal(list) + if err != nil { + panic(err) + } + var newValue any + // bytes can be deserialized by other languages + if err := fory.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) + dict := map[string]any{ + "k1": "v1", + "k2": list, + "k3": -1, + } + bytes, err = fory.Marshal(dict) + if err != nil { + panic(err) + } + // bytes can be deserialized by other languages + if err := fory.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) +} +``` + +### Custom values + +```go +package main + +import forygo "github.com/apache/fory/go/fory" +import "fmt" + +func main() { + type SomeClass1 struct { + F1 any + F2 map[int8]int32 + } + + type SomeClass2 struct { + F1 any + F2 string + F3 []any + F4 map[int8]int32 + F5 int8 + F6 int16 + F7 int32 + F8 int64 + F9 float32 + F10 float64 + F11 []int16 + F12 []int16 + } + serializer := forygo.NewFory(forygo.WithXlang(true)) + if err := serializer.RegisterStructByName(SomeClass1{}, "example.SomeClass1"); err != nil { + panic(err) + } + if err := serializer.RegisterStructByName(SomeClass2{}, "example.SomeClass2"); err != nil { + panic(err) + } + obj1 := &SomeClass1{F1: true, F2: map[int8]int32{-1: 2}} + obj := &SomeClass2{ + F1: obj1, + F2: "abc", + F3: []any{"abc", "abc"}, + F4: map[int8]int32{1: 2}, + F5: 127, + F6: 32767, + F7: 2147483647, + F8: 9223372036854775807, + F9: 1.0 / 2, + F10: 1.0 / 3.0, + F11: []int16{1, 2}, + F12: []int16{-1, 4}, + } + bytes, err := serializer.Marshal(obj) + if err != nil { + panic(err) + } + var newValue any + // bytes can be deserialized by other languages + if err := serializer.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) +} +``` + +### Shared and circular references + +```go +package main + +import forygo "github.com/apache/fory/go/fory" +import "fmt" + +func main() { + type SomeClass struct { + F1 *SomeClass + F2 map[string]string + F3 map[string]string + } + fory := forygo.NewFory(forygo.WithXlang(true), forygo.WithTrackRef(true)) + if err := fory.RegisterStruct(SomeClass{}, 65); err != nil { + panic(err) + } + value := &SomeClass{F2: map[string]string{"k1": "v1", "k2": "v2"}} + value.F3 = value.F2 + value.F1 = value + bytes, err := fory.Marshal(value) + if err != nil { + panic(err) + } + var newValue any + // bytes can be deserialized by other languages + if err := fory.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) +} +``` + ## Related Topics - [Configuration](configuration.md) diff --git a/docs/object-serialization/go/custom-serializers.md b/docs/object-serialization/go/custom-serializers.md index 5ab02224d2..919aa29ea3 100644 --- a/docs/object-serialization/go/custom-serializers.md +++ b/docs/object-serialization/go/custom-serializers.md @@ -282,4 +282,4 @@ func TestMySerializer(t *testing.T) { - [Type Registration](type-registration.md) - [Supported Types](supported-types.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/go/index.md b/docs/object-serialization/go/index.md index b053d1e0b7..70aabe14fd 100644 --- a/docs/object-serialization/go/index.md +++ b/docs/object-serialization/go/index.md @@ -91,7 +91,7 @@ Use xlang mode for cross-language payloads and schemas shared with other Fory im Use native mode for Go-only traffic. Native mode is selected with `fory.WithXlang(false)` and keeps Go object serialization in Go-native form. It is optimized for Go structs, pointers, interfaces, and Go-specific type behavior that does not need a portable xlang mapping. Compatible mode is enabled by default. Set `fory.WithCompatible(false)` only when every reader and writer uses the same Go struct schema and you want faster serialization and smaller size. -See [Xlang Serialization](xlang.md) for Go xlang registration and interoperability rules, and [Native Serialization](native.md) for Go-only payloads. +See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for Go xlang registration and interoperability rules, and [Native Serialization](native.md) for Go-only payloads. ## Configuration @@ -133,25 +133,24 @@ data, _ := f.Serialize(&User{ID: 1, Name: "Alice"}) // 'data' can be deserialized by Java, Python, etc. ``` -See [Xlang Serialization](xlang.md) for type mapping and compatibility details. +See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for type mapping and compatibility details. ## Documentation -| Topic | Description | -| ------------------------------------------- | -------------------------------------- | -| [Basic Serialization](core-api.md) | Core APIs and usage patterns | -| [Xlang Serialization](xlang.md) | Multi-language serialization | -| [Native Serialization](native.md) | Go-only serialization | -| [Configuration](configuration.md) | Options and settings | -| [Schema Metadata](schema-metadata.md) | Field-level configuration | -| [Type Registration](type-registration.md) | Registering types for serialization | -| [Supported Types](supported-types.md) | Complete type support reference | -| [References](references.md) | Circular references and shared objects | -| [Schema Evolution](schema-evolution.md) | Forward/backward compatibility | -| [Custom Serializers](custom-serializers.md) | Extend serialization behavior | -| [Thread Safety](thread-safety.md) | Concurrent usage patterns | -| [gRPC Support](../../grpc/go.md) | Fory payloads over grpc-go | -| [Troubleshooting](troubleshooting.md) | Common issues and solutions | +| Topic | Description | +| ------------------------------------------- | --------------------------------------- | +| [Basic Serialization](core-api.md) | Default xlang APIs and interoperability | +| [Native Serialization](native.md) | Go-only serialization | +| [Configuration](configuration.md) | Options and settings | +| [Schema Metadata](schema-metadata.md) | Field-level configuration | +| [Type Registration](type-registration.md) | Registering types for serialization | +| [Supported Types](supported-types.md) | Complete type support reference | +| [References](references.md) | Circular references and shared objects | +| [Schema Evolution](schema-evolution.md) | Forward/backward compatibility | +| [Custom Serializers](custom-serializers.md) | Extend serialization behavior | +| [Thread Safety](thread-safety.md) | Concurrent usage patterns | +| [gRPC Support](../../grpc/go.md) | Fory payloads over grpc-go | +| [Troubleshooting](troubleshooting.md) | Common issues and solutions | ## Related Resources diff --git a/docs/object-serialization/go/native.md b/docs/object-serialization/go/native.md index ac1b831f72..0c7bab97c7 100644 --- a/docs/object-serialization/go/native.md +++ b/docs/object-serialization/go/native.md @@ -23,7 +23,7 @@ Go native serialization is the Go-only wire mode selected with `fory.WithXlang(f when every writer and reader is a Go service and the payload should follow Go's type system instead of the portable xlang type system. -Use [Xlang Serialization](xlang.md), the default Go mode, when bytes must be read by +Use [Cross-Language Interoperability](core-api.md#cross-language-interoperability), the default Go mode, when bytes must be read by Java, Python, C++, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another non-Go Fory implementation. @@ -210,7 +210,7 @@ The default `Fory` instance reuses its buffer. Copy the byte slice or use `threa ## Related Topics -- [Xlang Serialization](xlang.md) - Cross-language Go payloads +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language Go payloads - [Configuration](configuration.md) - Go options - [Type Registration](type-registration.md) - Struct and enum registration - [References](references.md) - Shared and circular references diff --git a/docs/object-serialization/go/references.md b/docs/object-serialization/go/references.md index 92470b5193..184a2e94ef 100644 --- a/docs/object-serialization/go/references.md +++ b/docs/object-serialization/go/references.md @@ -353,4 +353,4 @@ func main() { - [Configuration](configuration.md) - [Struct Tags](schema-metadata.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/go/schema-evolution.md b/docs/object-serialization/go/schema-evolution.md index b46aecf209..463f7f0b4f 100644 --- a/docs/object-serialization/go/schema-evolution.md +++ b/docs/object-serialization/go/schema-evolution.md @@ -389,5 +389,5 @@ func main() { ## Related Topics - [Configuration](configuration.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - [Troubleshooting](troubleshooting.md) diff --git a/docs/object-serialization/go/supported-types.md b/docs/object-serialization/go/supported-types.md index e21e0c48de..cfbed67b36 100644 --- a/docs/object-serialization/go/supported-types.md +++ b/docs/object-serialization/go/supported-types.md @@ -354,7 +354,7 @@ data, _ := f.Serialize(status) | `time.Time` | Instant | datetime | - | - | | `time.Duration` | Duration | timedelta | - | - | -See [Xlang Serialization](xlang.md) for detailed mapping. +See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for detailed mapping. ## Unsupported Types @@ -370,5 +370,5 @@ Attempting to serialize these types will result in an error. ## Related Topics - [Type Registration](type-registration.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - [References](references.md) diff --git a/docs/object-serialization/go/troubleshooting.md b/docs/object-serialization/go/troubleshooting.md index e9a04edcd4..58e78b2f71 100644 --- a/docs/object-serialization/go/troubleshooting.md +++ b/docs/object-serialization/go/troubleshooting.md @@ -438,6 +438,6 @@ If you encounter issues not covered here: ## Related Topics - [Configuration](configuration.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - [Schema Evolution](schema-evolution.md) - [Thread Safety](thread-safety.md) diff --git a/docs/object-serialization/go/type-registration.md b/docs/object-serialization/go/type-registration.md index 18abc8ad63..9020ae7a28 100644 --- a/docs/object-serialization/go/type-registration.md +++ b/docs/object-serialization/go/type-registration.md @@ -259,6 +259,6 @@ Two types registered with the same ID will conflict. ## Related Topics - [Basic Serialization](core-api.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - [Supported Types](supported-types.md) - [Troubleshooting](troubleshooting.md) diff --git a/docs/object-serialization/go/xlang.md b/docs/object-serialization/go/xlang.md deleted file mode 100644 index d2f9472696..0000000000 --- a/docs/object-serialization/go/xlang.md +++ /dev/null @@ -1,442 +0,0 @@ ---- -title: Xlang Serialization -sidebar_position: 1 -id: xlang -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Fory Go enables seamless data exchange with Java, Python, C++, Rust, -JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. This guide covers -xlang compatibility and type mapping. - -## Create an Xlang Fory Instance - -Go defaults to xlang mode with compatible schema evolution. Set the mode explicitly in xlang examples: - -```go -f := fory.New(fory.WithXlang(true)) -``` - -## Type Registration for Xlang - -Use consistent type IDs across all languages: - -### Go - -```go -type User struct { - ID int64 - Name string -} - -f := fory.New(fory.WithXlang(true)) -f.RegisterStruct(User{}, 1) -data, _ := f.Serialize(&User{ID: 1, Name: "Alice"}) -``` - -### Java - -```java -public class User { - public long id; - public String name; -} -Fory fory = Fory.builder().withXlang(true).build(); -fory.register(User.class, 1); -User user = fory.deserialize(data, User.class); -``` - -### Python - -```python -from dataclasses import dataclass -import pyfory - -@dataclass -class User: - id: pyfory.Int64 - name: str - -fory = pyfory.Fory(xlang=True) -fory.register(User, type_id=1) -user = fory.deserialize(data) -``` - -## Type Mapping - -See [Type Mapping Specification](../../specification/xlang_type_mapping.md) for detailed type mappings across all languages. - -## Field Ordering - -Cross-language serialization requires consistent field ordering. Fory sorts fields by their snake_case names alphabetically. - -Go field names are converted to snake_case for sorting: - -```go -type Example struct { - UserID int64 // -> user_id - FirstName string // -> first_name - Age int32 // -> age -} - -// Sorted order: age, first_name, user_id -``` - -Ensure other languages use matching field names that produce the same snake_case ordering, or use field IDs for explicit control: - -```go -type Example struct { - UserID int64 `fory:"id=0"` - FirstName string `fory:"id=1"` - Age int32 `fory:"id=2"` -} -``` - -## Examples - -### Go to Java - -**Go (Serializer)**: - -```go -type Order struct { - ID int64 - Customer string - Total float64 - Items []string -} - -f := fory.New(fory.WithXlang(true)) -f.RegisterStruct(Order{}, 1) - -order := &Order{ - ID: 12345, - Customer: "Alice", - Total: 99.99, - Items: []string{"Widget", "Gadget"}, -} -data, _ := f.Serialize(order) -// Send 'data' to Java service -``` - -**Java (Deserializer)**: - -```java -public class Order { - public long id; - public String customer; - public double total; - public List items; -} - -Fory fory = Fory.builder().withXlang(true).build(); -fory.register(Order.class, 1); - -Order order = fory.deserialize(data, Order.class); -``` - -### Python to Go - -**Python (Serializer)**: - -```python -from dataclasses import dataclass -import pyfory - -@dataclass -class Message: - id: pyfory.Int64 - content: str - timestamp: pyfory.Int64 - -fory = pyfory.Fory(xlang=True) -fory.register(Message, type_id=1) - -msg = Message(id=1, content="Hello from Python", timestamp=1234567890) -data = fory.serialize(msg) -``` - -**Go (Deserializer)**: - -```go -type Message struct { - ID int64 - Content string - Timestamp int64 -} - -f := fory.New(fory.WithXlang(true)) -f.RegisterStruct(Message{}, 1) - -var msg Message -f.Deserialize(data, &msg) -fmt.Println(msg.Content) // "Hello from Python" -``` - -### Nested Structures - -Cross-language nested structures require all types to be registered: - -## Lists and Dense Arrays - -Go slices are ordinary `list` carriers unless a field tag explicitly requests -the dense `array` schema. Use `array` only for one-dimensional bool or -numeric data. - -| Fory schema | Go carrier and tag sketch | -| ----------------- | ------------------------------------------------------ | -| `list` | `[]int32` / `fory:"type=list(element=int32)"` | -| `array` | `[]bool` / `fory:"type=array(element=bool)"` | -| `array` | `[]int8` / `fory:"type=array(element=int8)"` | -| `array` | `[]int16` / `fory:"type=array(element=int16)"` | -| `array` | `[]int32` / `fory:"type=array(element=int32)"` | -| `array` | `[]int64` / `fory:"type=array(element=int64)"` | -| `array` | `[]uint8` / `fory:"type=array(element=uint8)"` | -| `array` | `[]uint16` / `fory:"type=array(element=uint16)"` | -| `array` | `[]uint32` / `fory:"type=array(element=uint32)"` | -| `array` | `[]uint64` / `fory:"type=array(element=uint64)"` | -| `array` | `[]float16.Float16` / `type=array(element=float16)` | -| `array` | `[]bfloat16.BFloat16` / `type=array(element=bfloat16)` | -| `array` | `[]float32` / `fory:"type=array(element=float32)"` | -| `array` | `[]float64` / `fory:"type=array(element=float64)"` | - -**Go**: - -```go -type Address struct { - Street string - City string - Country string -} - -type Company struct { - Name string - Address Address -} - -f := fory.New(fory.WithXlang(true)) -f.RegisterStruct(Address{}, 1) -f.RegisterStruct(Company{}, 2) -``` - -**Java**: - -```java -public class Address { - public String street; - public String city; - public String country; -} - -public class Company { - public String name; - public Address address; -} - -fory.register(Address.class, 1); -fory.register(Company.class, 2); -``` - -## Common Issues - -### Field Name Mismatch - -Go uses PascalCase, other languages may use camelCase or snake_case. Fields are matched by their snake_case conversion: - -```go -// Go -type User struct { - FirstName string // -> first_name -} - -// Java - field name converted to snake_case must match -public class User { - public String firstName; // -> first_name (matches) -} -``` - -### Type Interpretation - -Go unsigned types map to Java signed types with the same bit pattern: - -```go -var value uint64 = 18446744073709551615 // Max uint64 -``` - -Java's `long` holds the same bits but interprets as -1. Use `Long.toUnsignedString()` in Java if unsigned interpretation is needed. - -### Nil vs Null - -Go nil slices/maps serialize differently based on configuration: - -```go -var slice []string = nil -// In xlang mode: serializes based on nullable configuration -``` - -Ensure other languages handle null appropriately. - -## Best Practices - -1. **Use consistent type IDs**: Same numeric ID for the same type across all languages -2. **Register all types**: Including nested struct types -3. **Match field ordering**: Use same snake_case names or explicit field IDs -4. **Test cross-language**: Run integration tests early and often -5. **Handle type differences**: Be aware of signed/unsigned interpretation differences - -## Related Topics - -- [Type Registration](type-registration.md) -- [Supported Types](supported-types.md) -- [Schema Evolution](schema-evolution.md) -- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) -- [Type Mapping Specification](../../specification/xlang_type_mapping.md) - -## Built-in values - -```go -package main - -import forygo "github.com/apache/fory/go/fory" -import "fmt" - -func main() { - list := []any{true, false, "str", -1.1, 1, make([]int32, 10), make([]float64, 20)} - fory := forygo.NewFory(forygo.WithXlang(true)) - bytes, err := fory.Marshal(list) - if err != nil { - panic(err) - } - var newValue any - // bytes can be deserialized by other languages - if err := fory.Unmarshal(bytes, &newValue); err != nil { - panic(err) - } - fmt.Println(newValue) - dict := map[string]any{ - "k1": "v1", - "k2": list, - "k3": -1, - } - bytes, err = fory.Marshal(dict) - if err != nil { - panic(err) - } - // bytes can be deserialized by other languages - if err := fory.Unmarshal(bytes, &newValue); err != nil { - panic(err) - } - fmt.Println(newValue) -} -``` - -## Custom values - -```go -package main - -import forygo "github.com/apache/fory/go/fory" -import "fmt" - -func main() { - type SomeClass1 struct { - F1 any - F2 map[int8]int32 - } - - type SomeClass2 struct { - F1 any - F2 string - F3 []any - F4 map[int8]int32 - F5 int8 - F6 int16 - F7 int32 - F8 int64 - F9 float32 - F10 float64 - F11 []int16 - F12 []int16 - } - serializer := forygo.NewFory(forygo.WithXlang(true)) - if err := serializer.RegisterStructByName(SomeClass1{}, "example.SomeClass1"); err != nil { - panic(err) - } - if err := serializer.RegisterStructByName(SomeClass2{}, "example.SomeClass2"); err != nil { - panic(err) - } - obj1 := &SomeClass1{F1: true, F2: map[int8]int32{-1: 2}} - obj := &SomeClass2{ - F1: obj1, - F2: "abc", - F3: []any{"abc", "abc"}, - F4: map[int8]int32{1: 2}, - F5: 127, - F6: 32767, - F7: 2147483647, - F8: 9223372036854775807, - F9: 1.0 / 2, - F10: 1.0 / 3.0, - F11: []int16{1, 2}, - F12: []int16{-1, 4}, - } - bytes, err := serializer.Marshal(obj) - if err != nil { - panic(err) - } - var newValue any - // bytes can be deserialized by other languages - if err := serializer.Unmarshal(bytes, &newValue); err != nil { - panic(err) - } - fmt.Println(newValue) -} -``` - -## Shared and circular references - -```go -package main - -import forygo "github.com/apache/fory/go/fory" -import "fmt" - -func main() { - type SomeClass struct { - F1 *SomeClass - F2 map[string]string - F3 map[string]string - } - fory := forygo.NewFory(forygo.WithXlang(true), forygo.WithTrackRef(true)) - if err := fory.RegisterStruct(SomeClass{}, 65); err != nil { - panic(err) - } - value := &SomeClass{F2: map[string]string{"k1": "v1", "k2": "v2"}} - value.F3 = value.F2 - value.F1 = value - bytes, err := fory.Marshal(value) - if err != nil { - panic(err) - } - var newValue any - // bytes can be deserialized by other languages - if err := fory.Unmarshal(bytes, &newValue); err != nil { - panic(err) - } - fmt.Println(newValue) -} -``` diff --git a/docs/object-serialization/java/advanced-features.md b/docs/object-serialization/java/advanced-features.md index 1f58d604ff..5c1a8b4b25 100644 --- a/docs/object-serialization/java/advanced-features.md +++ b/docs/object-serialization/java/advanced-features.md @@ -157,4 +157,4 @@ static { - [Configuration](configuration.md) - All ForyBuilder options - [Native Serialization](native.md) - Java-only serialization, JDK hooks, and zero-copy buffers - [Object Copy](object-copy.md) - Deep copy functionality -- [Xlang Serialization](xlang.md) - Java xlang interoperability +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Java xlang interoperability diff --git a/docs/object-serialization/java/core-api.md b/docs/object-serialization/java/core-api.md index a8f10bb452..6d2fd6a03e 100644 --- a/docs/object-serialization/java/core-api.md +++ b/docs/object-serialization/java/core-api.md @@ -1,6 +1,6 @@ --- title: Basic Serialization -sidebar_position: 3 +sidebar_position: 1 id: core-api license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -91,8 +91,8 @@ User decoded = fory.deserialize(bytes, User.class); ``` When xlang bytes cross languages, every peer must register the same type identity and compatible -field metadata. The shared rules live in [Xlang](../xlang/index.md), while Java-specific API calls -are in [Xlang Serialization](xlang.md). +field metadata. The shared rules live in [Xlang](../xlang/index.md), and the Java-specific +interoperability requirements are covered below. ## Use Native Serialization For Java-Only Traffic @@ -123,10 +123,364 @@ object copy, and native-mode zero-copy buffers. See [Native Serialization](nativ 3. **Register classes**: Keep type identity stable across every xlang peer 4. **Configure reference tracking**: Enable it only when the object graph needs identity or cycles +## Cross-Language Interoperability + +The default xlang format is shared by all Fory runtimes. The following sections cover its cross-language type mapping, type identity, and interoperability requirements. + +Apache Fory™ xlang serialization is the Java wire mode for payloads that must be read by Python, +Rust, Go, JavaScript/TypeScript, C++, C#, Swift, Dart, Scala, Kotlin, or another non-Java Fory implementation. Java defaults to +xlang mode with compatible schema evolution, but examples set the mode explicitly so the payload +contract is visible in code. + +### Xlang Configuration + +Use one long-lived `Fory` or `ThreadSafeFory` instance per configuration. Creating a Fory instance is +expensive because Fory caches type metadata and generated serializers. + +```java +import org.apache.fory.Fory; + +Fory fory = Fory.builder() + .withXlang(true) + .requireClassRegistration(true) + .withRefTracking(true) + .build(); +``` + +`withRefTracking(true)` is required only when the cross-language data model includes shared object +identity or cycles. Disable it for value-shaped schemas. + +Use [Native Serialization](native.md) instead when every writer and reader is Java +and the payload should preserve Java-specific object behavior. + +### Register Types + +Types must be registered with consistent IDs or names across all languages. Fory supports two +registration methods. + +#### Register by ID (Recommended for Performance) + +```java +public record Person(String name, int age) {} + +// Numeric ID registration is compact and fast. +fory.register(Person.class, 1); + +Person person = new Person("Alice", 30); +byte[] bytes = fory.serialize(person); +// bytes can be deserialized by Python, Rust, Go, etc. +``` + +Benefits: faster serialization and smaller binary size. + +Trade-off: every service must coordinate IDs so the same logical type uses the same number. + +#### Register by Name (Recommended for Flexibility) + +```java +public record Person(String name, int age) {} + +// Namespace/type-name registration is easier to coordinate across teams. +fory.register(Person.class, "example", "Person"); + +Person person = new Person("Alice", 30); +byte[] bytes = fory.serialize(person); +// bytes can be deserialized by Python, Rust, Go, etc. +``` + +Benefits: less risk of numeric ID conflicts and easier management across independently owned +services. + +Trade-off: the payload includes string identity, so it is larger than ID-based registration. + +The Java API also supports a single string type name, such as +`fory.register(Person.class, "example.Person")`. Use the same logical identity on every peer. + +### Java To Python Example + +#### Java (Serializer) + +```java +import org.apache.fory.Fory; +import java.nio.file.Files; +import java.nio.file.Path; + +public record Person(String name, int age) {} + +public class Example { + public static void main(String[] args) throws Exception { + Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + + // Register with the same logical name used by Python. + fory.register(Person.class, "example.Person"); + + Person person = new Person("Bob", 25); + byte[] bytes = fory.serialize(person); + Files.write(Path.of("person.bin"), bytes); + } +} +``` + +#### Python (Deserializer) + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True, ref=True) + +# Register with the same name as Java. +fory.register_type(Person, name="example.Person") + +with open("person.bin", "rb") as input_file: + person = fory.deserialize(input_file.read()) +print(f"{person.name}, {person.age}") # Output: Bob, 25 +``` + +### Handling Circular and Shared References + +Xlang mode supports circular and shared references when reference tracking is enabled: + +```java +public class Node { + public String value; + public Node next; + public Node parent; +} + +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + +fory.register(Node.class, "example.Node"); + +Node node1 = new Node(); +node1.value = "A"; +Node node2 = new Node(); +node2.value = "B"; +node1.next = node2; +node2.parent = node1; + +byte[] bytes = fory.serialize(node1); +// Python/Rust/Go can correctly deserialize this with circular references preserved +``` + +### Type Mapping Considerations + +Not all Java types have equivalents in other languages. When using xlang mode: + +- Use primitive types (`int`, `long`, `double`, `String`) for maximum compatibility. +- Use standard collections (`List`, `Map`, `Set`) instead of language-specific collections. +- Use reduced-precision carriers (`Float16`, `BFloat16`, `Float16List`, `BFloat16List`) for + 16-bit float payloads. +- Treat `Float16[]`, `BFloat16[]`, `Float16List`, and `BFloat16List` as `list` carriers by + default; use `@ArrayType` when the schema must be `array` or `array`. +- Avoid Java-specific types like `Optional`, `BigDecimal`, and `EnumSet` unless every target language + has an agreed mapping. +- See [Type Mapping Guide](../../specification/xlang_type_mapping.md) for the complete + compatibility matrix. + +#### Lists and Dense Arrays + +Java primitive arrays are dense `array` carriers, except plain `byte[]`, +which defaults to `bytes`. General Java collections and Fory primitive-list +carriers such as `Int32List`, `Float16List`, and `BFloat16List` use +`list` unless the field has explicit `@ArrayType` metadata. + +| Fory schema | Java field shape | +| ----------------- | ------------------------------------------ | +| `list` | `List` or `Int32List` | +| `array` | `boolean[]` | +| `array` | `@Int8Type byte[]` type-use | +| `array` | `short[]` | +| `array` | `int[]` | +| `array` | `long[]` | +| `array` | `@UInt8Type byte[]` type-use | +| `array` | `@UInt16Type short[]` type-use | +| `array` | `@UInt32Type int[]` type-use | +| `array` | `@UInt64Type long[]` type-use | +| `array` | `Float16Array` or `@Float16Type short[]` | +| `array` | `BFloat16Array` or `@BFloat16Type short[]` | +| `array` | `float[]` | +| `array` | `double[]` | + +Prefer type-use syntax for primitive-array annotations: + +```java +private @UInt32Type int[] ids; +private @BFloat16Type short[] values; +``` + +#### Compatible Types + +```java +public record UserData( + String name, // compatible + int age, // compatible + List tags, // compatible + Map scores // compatible +) {} +``` + +#### Problematic Types + +```java +public record UserData( + Optional name, // not cross-language compatible + BigDecimal balance, // limited support + EnumSet statuses // Java-specific collection +) {} +``` + +### Performance Considerations + +Xlang mode has additional overhead compared to Java native mode: + +- **Type metadata encoding**: Adds extra bytes per type +- **Type resolution**: Requires name/ID lookup during deserialization + +**For best performance**: + +- Use **ID-based registration** when possible (smaller encoding) +- **Disable reference tracking** if you don't need circular references (`withRefTracking(false)`) +- **Use native mode** (`withXlang(false)`) when only Java serialization is needed + +### Interoperability Best Practices + +1. Use explicit type IDs or namespace/type names for every user type. +2. Keep compatible mode for independently deployed services. +3. Test payloads through every peer before relying on a schema in production. +4. Use native serialization for Java-only traffic that needs Java-specific object behavior. + +### Interoperability Troubleshooting + +#### "Type not registered" errors + +- Verify type is registered with same ID/name on both sides +- Check if type name has typos or case differences + +#### "Type mismatch" errors + +- Ensure field types are compatible across languages +- Review [Type Mapping Guide](../../specification/xlang_type_mapping.md) + +#### Data corruption or unexpected values + +- Verify both sides use xlang payloads +- Ensure both sides have compatible Fory versions + +### Specifications and References + +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) +- [Type Mapping Reference](../../specification/xlang_type_mapping.md) +- [Python Interoperability Guide](../python/core-api.md#cross-language-interoperability) +- [Rust Interoperability Guide](../rust/core-api.md#cross-language-interoperability) + +### Related Guides + +- [Schema Evolution](schema-evolution.md) - Compatible mode +- [Type Registration](type-registration.md) - Registration methods +- [Native Serialization](native.md) - Java-only serialization features +- [Row Format](../../row-format/java.md) - Cross-language row format + +### Built-in values + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +import java.util.*; + +public class Example1 { + public static void main(String[] args) { + Fory fory = Fory.builder().withXlang(true).build(); + List list = ofArrayList(true, false, "str", -1.1, 1, new int[100], new double[20]); + byte[] bytes = fory.serialize(list); + // bytes can be deserialized by other languages + fory.deserialize(bytes); + Map map = new HashMap<>(); + map.put("k1", "v1"); + map.put("k2", list); + map.put("k3", -1); + bytes = fory.serialize(map); + // bytes can be deserialized by other languages + fory.deserialize(bytes); + } +} +``` + +### Custom values + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; +import java.util.*; + +public class Example2 { + public static class SomeClass1 { + Object f1; + Map f2; + } + + public static class SomeClass2 { + Object f1; + String f2; + List f3; + Map f4; + Byte f5; + Short f6; + Integer f7; + Long f8; + Float f9; + Double f10; + short[] f11; + List f12; + } + + public static Object createObject() { + SomeClass1 obj1 = new SomeClass1(); + obj1.f1 = true; + obj1.f2 = ofHashMap((byte) -1, 2); + SomeClass2 obj = new SomeClass2(); + obj.f1 = obj1; + obj.f2 = "abc"; + obj.f3 = ofArrayList("abc", "abc"); + obj.f4 = ofHashMap((byte) 1, 2); + obj.f5 = Byte.MAX_VALUE; + obj.f6 = Short.MAX_VALUE; + obj.f7 = Integer.MAX_VALUE; + obj.f8 = Long.MAX_VALUE; + obj.f9 = 1.0f / 2; + obj.f10 = 1 / 3.0; + obj.f11 = new short[]{(short) 1, (short) 2}; + obj.f12 = ofArrayList((short) -1, (short) 4); + return obj; + } + + // mvn exec:java -Dexec.mainClass="org.apache.fory.examples.Example2" + public static void main(String[] args) { + Fory fory = Fory.builder().withXlang(true).build(); + fory.register(SomeClass1.class, "example.SomeClass1"); + fory.register(SomeClass2.class, "example.SomeClass2"); + byte[] bytes = fory.serialize(createObject()); + // bytes can be deserialized by other languages + System.out.println(fory.deserialize(bytes)); + } +} +``` + ## Related Topics - [Configuration](configuration.md) - All ForyBuilder options - [Native Serialization](native.md) - Java-only serialization features - [Schema Metadata](schema-metadata.md) - Field IDs, nullability, reference tracking, and enum IDs -- [Xlang Serialization](xlang.md) - Java xlang interoperability - [Troubleshooting](troubleshooting.md) - Common API usage issues diff --git a/docs/object-serialization/java/index.md b/docs/object-serialization/java/index.md index ea3a0f3d9a..55993d666a 100644 --- a/docs/object-serialization/java/index.md +++ b/docs/object-serialization/java/index.md @@ -169,7 +169,7 @@ Use xlang mode for cross-language payloads and schemas shared with non-Java impl Use native mode for Java-only traffic. Native mode is selected with `.withXlang(false)` and owns Java-specific object behavior such as JDK serialization hooks, `Externalizable`, dynamic object graphs, object copy, and Java native-mode zero-copy buffers. It is optimized for the JVM type system and supports a broader Java object surface than xlang mode. Compatible mode is enabled by default. Set `.withCompatible(false)` only when every reader and writer uses the same class schema and you want faster serialization and smaller size. If you are replacing JDK serialization, Kryo, FST, Hessian, or Java-only Protocol Buffers payloads, start with native mode. -See [Native Serialization](native.md) for Java-only serialization details and [Xlang Serialization](xlang.md) for Java xlang registration and interoperability rules. +See [Native Serialization](native.md) for Java-only serialization details and [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for Java xlang registration and interoperability rules. ### Thread Safety @@ -257,8 +257,8 @@ model-discovery workflows differ from Fory Core. | Group | Pages | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Serialization modes | [Xlang](xlang.md), [Native](native.md) | -| Common | [Core API](core-api.md), [Configuration](configuration.md), [Type Registration](type-registration.md), [Schema Evolution](schema-evolution.md), [Schema Metadata](schema-metadata.md), [Custom Serializers](custom-serializers.md) | +| Serialization modes | [Basic Serialization](core-api.md), [Native Serialization](native.md) | +| Common | [Configuration](configuration.md), [Type Registration](type-registration.md), [Schema Evolution](schema-evolution.md), [Schema Metadata](schema-metadata.md), [Custom Serializers](custom-serializers.md) | | Java-specific features | [Advanced Features](advanced-features.md), [Compression](compression.md), [Object Copy](object-copy.md), [JDK Custom Serialization](jdk-serialization.md), [Static Generated Serializers](static-generated-serializers.md), [Virtual Threads](virtual-threads.md) | | Platform and operate | [Android](android.md), [GraalVM Native Image](graalvm.md), [Troubleshooting](troubleshooting.md) | diff --git a/docs/object-serialization/java/native.md b/docs/object-serialization/java/native.md index 6832213e73..06cf0af7be 100644 --- a/docs/object-serialization/java/native.md +++ b/docs/object-serialization/java/native.md @@ -28,7 +28,7 @@ payloads. Native serialization in this page means Fory's `xlang=false` wire mode. It is separate from GraalVM native image support, which is covered in [GraalVM Native Image](graalvm.md). -Use [Xlang Serialization](xlang.md), the default Java mode, when bytes must be read by +Use [Cross-Language Interoperability](core-api.md#cross-language-interoperability), the default Java mode, when bytes must be read by non-Java Fory implementations. ## When To Use Native Serialization @@ -327,7 +327,7 @@ Use `JavaSerializer.serializedByJDK(...)` only at the mixed-format boundary, the ## Related Topics - [Basic Serialization](core-api.md) - Xlang-first Java quickstart -- [Xlang Serialization](xlang.md) - Cross-language Java payloads +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language Java payloads - [Configuration](configuration.md) - Java builder options - [Schema Evolution](schema-evolution.md) - compatible mode and same-schema optimization - [Type Registration](type-registration.md) - Registration and security diff --git a/docs/object-serialization/java/schema-evolution.md b/docs/object-serialization/java/schema-evolution.md index 1e71d7e97e..66620997db 100644 --- a/docs/object-serialization/java/schema-evolution.md +++ b/docs/object-serialization/java/schema-evolution.md @@ -266,5 +266,5 @@ public class SameSchemaMessage { ## Related Topics - [Configuration](configuration.md) - All ForyBuilder options -- [Xlang Serialization](xlang.md) - xlang mode +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - xlang mode - [Troubleshooting](troubleshooting.md) - Common schema issues diff --git a/docs/object-serialization/java/schema-metadata.md b/docs/object-serialization/java/schema-metadata.md index 0e631edf9b..011b040bea 100644 --- a/docs/object-serialization/java/schema-metadata.md +++ b/docs/object-serialization/java/schema-metadata.md @@ -713,4 +713,4 @@ public class User { - [Basic Serialization](core-api.md) - Getting started with Fory serialization - [Configuration](configuration.md) - `ForyBuilder` options - [Schema Evolution](schema-evolution.md) - Compatible mode and schema evolution -- [Xlang Serialization](xlang.md) - Interoperability with Python, Rust, C++, Go +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Interoperability with Python, Rust, C++, Go diff --git a/docs/object-serialization/java/xlang.md b/docs/object-serialization/java/xlang.md deleted file mode 100644 index c18b13d538..0000000000 --- a/docs/object-serialization/java/xlang.md +++ /dev/null @@ -1,371 +0,0 @@ ---- -title: Xlang Serialization -sidebar_position: 1 -id: xlang -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Apache Fory™ xlang serialization is the Java wire mode for payloads that must be read by Python, -Rust, Go, JavaScript/TypeScript, C++, C#, Swift, Dart, Scala, Kotlin, or another non-Java Fory implementation. Java defaults to -xlang mode with compatible schema evolution, but examples set the mode explicitly so the payload -contract is visible in code. - -## Create an Xlang Fory Instance - -Use one long-lived `Fory` or `ThreadSafeFory` instance per configuration. Creating a Fory instance is -expensive because Fory caches type metadata and generated serializers. - -```java -import org.apache.fory.Fory; - -Fory fory = Fory.builder() - .withXlang(true) - .requireClassRegistration(true) - .withRefTracking(true) - .build(); -``` - -`withRefTracking(true)` is required only when the cross-language data model includes shared object -identity or cycles. Disable it for value-shaped schemas. - -Use [Native Serialization](native.md) instead when every writer and reader is Java -and the payload should preserve Java-specific object behavior. - -## Register Types - -Types must be registered with consistent IDs or names across all languages. Fory supports two -registration methods. - -### Register by ID (Recommended for Performance) - -```java -public record Person(String name, int age) {} - -// Numeric ID registration is compact and fast. -fory.register(Person.class, 1); - -Person person = new Person("Alice", 30); -byte[] bytes = fory.serialize(person); -// bytes can be deserialized by Python, Rust, Go, etc. -``` - -Benefits: faster serialization and smaller binary size. - -Trade-off: every service must coordinate IDs so the same logical type uses the same number. - -### Register by Name (Recommended for Flexibility) - -```java -public record Person(String name, int age) {} - -// Namespace/type-name registration is easier to coordinate across teams. -fory.register(Person.class, "example", "Person"); - -Person person = new Person("Alice", 30); -byte[] bytes = fory.serialize(person); -// bytes can be deserialized by Python, Rust, Go, etc. -``` - -Benefits: less risk of numeric ID conflicts and easier management across independently owned -services. - -Trade-off: the payload includes string identity, so it is larger than ID-based registration. - -The Java API also supports a single string type name, such as -`fory.register(Person.class, "example.Person")`. Use the same logical identity on every peer. - -## Java To Python Example - -### Java (Serializer) - -```java -import org.apache.fory.Fory; -import java.nio.file.Files; -import java.nio.file.Path; - -public record Person(String name, int age) {} - -public class Example { - public static void main(String[] args) throws Exception { - Fory fory = Fory.builder() - .withXlang(true) - .withRefTracking(true) - .build(); - - // Register with the same logical name used by Python. - fory.register(Person.class, "example.Person"); - - Person person = new Person("Bob", 25); - byte[] bytes = fory.serialize(person); - Files.write(Path.of("person.bin"), bytes); - } -} -``` - -### Python (Deserializer) - -```python -import pyfory -from dataclasses import dataclass - -@dataclass -class Person: - name: str - age: pyfory.Int32 - -fory = pyfory.Fory(xlang=True, ref=True) - -# Register with the same name as Java. -fory.register_type(Person, name="example.Person") - -with open("person.bin", "rb") as input_file: - person = fory.deserialize(input_file.read()) -print(f"{person.name}, {person.age}") # Output: Bob, 25 -``` - -## Handling Circular and Shared References - -Xlang mode supports circular and shared references when reference tracking is enabled: - -```java -public class Node { - public String value; - public Node next; - public Node parent; -} - -Fory fory = Fory.builder() - .withXlang(true) - .withRefTracking(true) - .build(); - -fory.register(Node.class, "example.Node"); - -Node node1 = new Node(); -node1.value = "A"; -Node node2 = new Node(); -node2.value = "B"; -node1.next = node2; -node2.parent = node1; - -byte[] bytes = fory.serialize(node1); -// Python/Rust/Go can correctly deserialize this with circular references preserved -``` - -## Type Mapping Considerations - -Not all Java types have equivalents in other languages. When using xlang mode: - -- Use primitive types (`int`, `long`, `double`, `String`) for maximum compatibility. -- Use standard collections (`List`, `Map`, `Set`) instead of language-specific collections. -- Use reduced-precision carriers (`Float16`, `BFloat16`, `Float16List`, `BFloat16List`) for - 16-bit float payloads. -- Treat `Float16[]`, `BFloat16[]`, `Float16List`, and `BFloat16List` as `list` carriers by - default; use `@ArrayType` when the schema must be `array` or `array`. -- Avoid Java-specific types like `Optional`, `BigDecimal`, and `EnumSet` unless every target language - has an agreed mapping. -- See [Type Mapping Guide](../../specification/xlang_type_mapping.md) for the complete - compatibility matrix. - -### Lists and Dense Arrays - -Java primitive arrays are dense `array` carriers, except plain `byte[]`, -which defaults to `bytes`. General Java collections and Fory primitive-list -carriers such as `Int32List`, `Float16List`, and `BFloat16List` use -`list` unless the field has explicit `@ArrayType` metadata. - -| Fory schema | Java field shape | -| ----------------- | ------------------------------------------ | -| `list` | `List` or `Int32List` | -| `array` | `boolean[]` | -| `array` | `@Int8Type byte[]` type-use | -| `array` | `short[]` | -| `array` | `int[]` | -| `array` | `long[]` | -| `array` | `@UInt8Type byte[]` type-use | -| `array` | `@UInt16Type short[]` type-use | -| `array` | `@UInt32Type int[]` type-use | -| `array` | `@UInt64Type long[]` type-use | -| `array` | `Float16Array` or `@Float16Type short[]` | -| `array` | `BFloat16Array` or `@BFloat16Type short[]` | -| `array` | `float[]` | -| `array` | `double[]` | - -Prefer type-use syntax for primitive-array annotations: - -```java -private @UInt32Type int[] ids; -private @BFloat16Type short[] values; -``` - -### Compatible Types - -```java -public record UserData( - String name, // compatible - int age, // compatible - List tags, // compatible - Map scores // compatible -) {} -``` - -### Problematic Types - -```java -public record UserData( - Optional name, // not cross-language compatible - BigDecimal balance, // limited support - EnumSet statuses // Java-specific collection -) {} -``` - -## Performance Considerations - -Xlang mode has additional overhead compared to Java native mode: - -- **Type metadata encoding**: Adds extra bytes per type -- **Type resolution**: Requires name/ID lookup during deserialization - -**For best performance**: - -- Use **ID-based registration** when possible (smaller encoding) -- **Disable reference tracking** if you don't need circular references (`withRefTracking(false)`) -- **Use native mode** (`withXlang(false)`) when only Java serialization is needed - -## Best Practices - -1. Use explicit type IDs or namespace/type names for every user type. -2. Keep compatible mode for independently deployed services. -3. Test payloads through every peer before relying on a schema in production. -4. Use native serialization for Java-only traffic that needs Java-specific object behavior. - -## Troubleshooting - -### "Type not registered" errors - -- Verify type is registered with same ID/name on both sides -- Check if type name has typos or case differences - -### "Type mismatch" errors - -- Ensure field types are compatible across languages -- Review [Type Mapping Guide](../../specification/xlang_type_mapping.md) - -### Data corruption or unexpected values - -- Verify both sides use xlang payloads -- Ensure both sides have compatible Fory versions - -## See Also - -- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) -- [Type Mapping Reference](../../specification/xlang_type_mapping.md) -- [Python Xlang Serialization Guide](../python/xlang.md) -- [Rust Xlang Serialization Guide](../rust/xlang.md) - -## Related Topics - -- [Schema Evolution](schema-evolution.md) - Compatible mode -- [Type Registration](type-registration.md) - Registration methods -- [Native Serialization](native.md) - Java-only serialization features -- [Row Format](../../row-format/java.md) - Cross-language row format - -## Built-in values - -```java -import org.apache.fory.*; -import org.apache.fory.config.*; - -import java.util.*; - -public class Example1 { - public static void main(String[] args) { - Fory fory = Fory.builder().withXlang(true).build(); - List list = ofArrayList(true, false, "str", -1.1, 1, new int[100], new double[20]); - byte[] bytes = fory.serialize(list); - // bytes can be deserialized by other languages - fory.deserialize(bytes); - Map map = new HashMap<>(); - map.put("k1", "v1"); - map.put("k2", list); - map.put("k3", -1); - bytes = fory.serialize(map); - // bytes can be deserialized by other languages - fory.deserialize(bytes); - } -} -``` - -## Custom values - -```java -import org.apache.fory.*; -import org.apache.fory.config.*; -import java.util.*; - -public class Example2 { - public static class SomeClass1 { - Object f1; - Map f2; - } - - public static class SomeClass2 { - Object f1; - String f2; - List f3; - Map f4; - Byte f5; - Short f6; - Integer f7; - Long f8; - Float f9; - Double f10; - short[] f11; - List f12; - } - - public static Object createObject() { - SomeClass1 obj1 = new SomeClass1(); - obj1.f1 = true; - obj1.f2 = ofHashMap((byte) -1, 2); - SomeClass2 obj = new SomeClass2(); - obj.f1 = obj1; - obj.f2 = "abc"; - obj.f3 = ofArrayList("abc", "abc"); - obj.f4 = ofHashMap((byte) 1, 2); - obj.f5 = Byte.MAX_VALUE; - obj.f6 = Short.MAX_VALUE; - obj.f7 = Integer.MAX_VALUE; - obj.f8 = Long.MAX_VALUE; - obj.f9 = 1.0f / 2; - obj.f10 = 1 / 3.0; - obj.f11 = new short[]{(short) 1, (short) 2}; - obj.f12 = ofArrayList((short) -1, (short) 4); - return obj; - } - - // mvn exec:java -Dexec.mainClass="org.apache.fory.examples.Example2" - public static void main(String[] args) { - Fory fory = Fory.builder().withXlang(true).build(); - fory.register(SomeClass1.class, "example.SomeClass1"); - fory.register(SomeClass2.class, "example.SomeClass2"); - byte[] bytes = fory.serialize(createObject()); - // bytes can be deserialized by other languages - System.out.println(fory.deserialize(bytes)); - } -} -``` diff --git a/docs/object-serialization/javascript/core-api.md b/docs/object-serialization/javascript/core-api.md index 6b4650e8f2..e466b33f4a 100644 --- a/docs/object-serialization/javascript/core-api.md +++ b/docs/object-serialization/javascript/core-api.md @@ -1,6 +1,6 @@ --- title: Basic Serialization -sidebar_position: 3 +sidebar_position: 1 id: core-api license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -19,7 +19,7 @@ license: | limitations under the License. --- -This guide covers the core serialization APIs in Apache Fory JavaScript. +This guide covers the core serialization APIs in the default xlang mode for Apache Fory JavaScript. ## Create a `Fory` Instance @@ -237,6 +237,196 @@ const fory = new Fory({ This is useful when debugging schema behavior, field ordering, or generated fast paths. +## Cross-Language Interoperability + +The default xlang format is shared by all Fory runtimes. The following sections cover its cross-language type mapping, type identity, and interoperability requirements. + +Fory JavaScript serializes to the same binary format as the Java, Python, C++, +Go, Rust, C#, Swift, Dart, Scala, and Kotlin Fory implementations. You can write a +message in JavaScript and read it in Java, or any other direction, without a +conversion layer. + +Things to keep in mind: + +- Fory JavaScript reads and writes cross-language payloads only; it does not support any native-mode format. +- JavaScript does not support out-of-band mode. + +### Requirements for a Successful Round Trip + +For a message to survive a round trip between JavaScript and another language: + +1. **Same type identity** on both sides — same numeric ID, or same `typeName`. +2. **Compatible field types** — a `Type.int32()` field in JavaScript matches Java `int`, Go `int32`, C# `int`. +3. **Same nullability** — if one side marks a field nullable, the other should too. +4. Compatible schema evolution on both sides. JavaScript enables it by default. +5. **Same reference tracking config** if your data has shared or circular references. + +### Step-by-Step: JavaScript to Another Peer + +1. Define the JavaScript schema with the same type name or numeric ID used by the peer. +2. Register the schema in both peers. +3. Match field types, nullability, and schema-evolution settings. +4. Test a real payload end-to-end before shipping. + +JavaScript side: + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const messageType = Type.struct( + { typeName: "example.message" }, + { + id: Type.int64(), + content: Type.string(), + }, +); + +const fory = new Fory(); +const { serialize } = fory.register(messageType); + +const bytes = serialize({ + id: 1n, + content: "hello from JavaScript", +}); +``` + +On the other side, register the same `example.message` type (same name or same numeric ID) using the peer language's API: + +- [Java guide](../java/index.md) +- [Python guide](../python/index.md) +- [Go guide](../go/index.md) +- [Rust guide](../rust/index.md) + +### Field Naming + +Fory matches fields by name. When models are defined in multiple languages, keep field names consistent — or at minimum use a naming scheme that maps unambiguously across languages (e.g. `snake_case` everywhere). + +With the default compatible schema evolution, field order differences are tolerated, but the names +themselves must still match. + +### Numeric Types + +JavaScript `number` is a 64-bit float, which does not map cleanly to every integer type in other languages. Use explicit schema types: + +- `Type.int32()` for 32-bit integers (Java `int`, Go `int32`, C# `int`) +- `Type.int64()` with `bigint` values for 64-bit integers (Java `long`, Go `int64`) +- `Type.float32()` or `Type.float64()` for floating-point values + +### Lists and Dense Arrays + +Use `Type.list(T)` for ordinary JavaScript `Array` values and Fory +`list` schema. Dense bool/numeric vectors use the explicit array builders +listed below. + +| Fory schema | JavaScript/TypeScript schema builder | +| ----------------- | ------------------------------------ | +| `list` | `Type.list(Type.int32())` | +| `array` | `Type.boolArray()` | +| `array` | `Type.int8Array()` | +| `array` | `Type.int16Array()` | +| `array` | `Type.int32Array()` | +| `array` | `Type.int64Array()` | +| `array` | `Type.uint8Array()` | +| `array` | `Type.uint16Array()` | +| `array` | `Type.uint32Array()` | +| `array` | `Type.uint64Array()` | +| `array` | `Type.float16Array()` | +| `array` | `Type.bfloat16Array()` | +| `array` | `Type.float32Array()` | +| `array` | `Type.float64Array()` | + +### Date and Time + +- `Type.timestamp()` — a point in time; round-trips as a JavaScript `Date` +- `Type.date()` — a date without time; deserializes as `Date` +- `Type.duration()` — exposed as a numeric millisecond value in JavaScript + +### Polymorphic Fields + +`Type.any()` lets a field hold different concrete types, but it is harder to keep in sync across languages. Prefer explicit field schemas whenever possible. + +```ts +const wrapperType = Type.struct( + { typeId: 3001 }, + { + payload: Type.any(), + }, +); +``` + +### Enums + +Enum member **order** must match across languages. Fory encodes enums by ordinal position, not by value. + +```ts +const Color = { Red: 1, Green: 2, Blue: 3 }; +const fory = new Fory(); +fory.register(Type.enum({ typeId: 210 }, Color)); +``` + +Use the same type ID or type name in every peer. + +### Safety Limits + +The `maxDepth` option bounds nested payloads. It does not change the binary format; it only controls what the local `Fory` instance accepts. + +### Related Guides + +- [Supported Types](supported-types.md) +- [Schema Evolution](schema-evolution.md) +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) + +### Built-in values + +```javascript +import Fory from "@apache-fory/core"; + +const fory = new Fory(); +const input = fory.serialize("hello fory"); +const result = fory.deserialize(input); +console.log(result); +``` + +### Custom values + +```javascript +import Fory, { Type } from "@apache-fory/core"; + +// Describe data structures using JSON schema +const description = Type.struct( + { typeName: "example.foo" }, + { + foo: Type.string(), + }, +); +const fory = new Fory(); +const { serialize, deserialize } = fory.register(description); +const input = serialize({ foo: "hello fory" }); +const result = deserialize(input); +console.log(result); +``` + +### Shared and circular references + +```javascript +import Fory, { Type } from "@apache-fory/core"; + +const description = Type.struct("example.foo", { + foo: Type.string(), + bar: Type.struct("example.foo").setTrackingRef(true), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(description); +const data: any = { + foo: "hello fory", +}; +data.bar = data; +const input = serialize(data); +const result = deserialize(input); +console.log(result.bar.foo === result.foo); +``` + ## Related Topics - [Type Registration](type-registration.md) diff --git a/docs/object-serialization/javascript/index.md b/docs/object-serialization/javascript/index.md index 994a350ccb..87e128cd2f 100644 --- a/docs/object-serialization/javascript/index.md +++ b/docs/object-serialization/javascript/index.md @@ -158,14 +158,13 @@ options; see [Configuration](configuration.md). | Topic | Description | | -------------------------------------------- | ------------------------------------------------------- | -| [Basic Serialization](core-api.md) | Core APIs and everyday usage | +| [Basic Serialization](core-api.md) | Default xlang APIs, usage, and interoperability | | [Configuration](configuration.md) | Fory options, compatible mode, limits, and HPS | | [Type Registration](type-registration.md) | Numeric IDs, names, decorators, and schema registration | | [Schema Metadata](schema-metadata.md) | Type builders, field options, and decorators | | [Supported Types](supported-types.md) | Primitive, collection, time, enum, and struct mappings | | [References](references.md) | Shared references and circular object graphs | | [Schema Evolution](schema-evolution.md) | Compatible mode and evolving structs | -| [Xlang Serialization](xlang.md) | Interop guidance and mapping rules | | [Fory IDL Compiler](../../compiler/index.md) | Generate TypeScript models from `.fdl` schemas | | [gRPC Support](../../grpc/javascript.md) | Node.js gRPC and browser gRPC-Web generated clients | | [Troubleshooting](troubleshooting.md) | Common issues, limits, and debugging tips | diff --git a/docs/object-serialization/javascript/references.md b/docs/object-serialization/javascript/references.md index 38b496843f..a491d0de8c 100644 --- a/docs/object-serialization/javascript/references.md +++ b/docs/object-serialization/javascript/references.md @@ -108,4 +108,4 @@ Reference tracking is part of the Fory binary protocol and works across language - [Basic Serialization](core-api.md) - [Schema Evolution](schema-evolution.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/javascript/schema-evolution.md b/docs/object-serialization/javascript/schema-evolution.md index 22d77da5d4..20d102590f 100644 --- a/docs/object-serialization/javascript/schema-evolution.md +++ b/docs/object-serialization/javascript/schema-evolution.md @@ -108,9 +108,9 @@ reads expecting compatible metadata, deserialization will fail. ## Xlang Requirement -Compatible mode only protects you from schema differences in the _fields_ of a type. You still need the same type identity (same numeric ID or same `typeName`) on every side. See [Xlang Serialization](xlang.md). +Compatible mode only protects you from schema differences in the _fields_ of a type. You still need the same type identity (same numeric ID or same `typeName`) on every side. See [Cross-Language Interoperability](core-api.md#cross-language-interoperability). ## Related Topics - [Type Registration](type-registration.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/javascript/supported-types.md b/docs/object-serialization/javascript/supported-types.md index 37cfda8b4e..2d78898ad5 100644 --- a/docs/object-serialization/javascript/supported-types.md +++ b/docs/object-serialization/javascript/supported-types.md @@ -174,4 +174,4 @@ For types that need completely custom encoding, use `Type.ext(...)` and pass a c - [Basic Serialization](core-api.md) - [References](references.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/javascript/troubleshooting.md b/docs/object-serialization/javascript/troubleshooting.md index c1aa890a1b..ed95b7eef0 100644 --- a/docs/object-serialization/javascript/troubleshooting.md +++ b/docs/object-serialization/javascript/troubleshooting.md @@ -87,4 +87,4 @@ const fory = new Fory({ - [Basic Serialization](core-api.md) - [References](references.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/javascript/type-registration.md b/docs/object-serialization/javascript/type-registration.md index b109c2f822..afcd166ab7 100644 --- a/docs/object-serialization/javascript/type-registration.md +++ b/docs/object-serialization/javascript/type-registration.md @@ -154,11 +154,11 @@ Use **names** when: ## Xlang -For a message to round-trip between JavaScript and another language, both sides must use the same identity for a given type: same numeric ID, or same `typeName`. Use `.` inside `typeName` to add a namespace prefix. See [Xlang Serialization](xlang.md). +For a message to round-trip between JavaScript and another language, both sides must use the same identity for a given type: same numeric ID, or same `typeName`. Use `.` inside `typeName` to add a namespace prefix. See [Cross-Language Interoperability](core-api.md#cross-language-interoperability). ## Related Topics - [Basic Serialization](core-api.md) - [Schema Metadata](schema-metadata.md) - [Schema Evolution](schema-evolution.md) -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) diff --git a/docs/object-serialization/javascript/xlang.md b/docs/object-serialization/javascript/xlang.md deleted file mode 100644 index 24452ee559..0000000000 --- a/docs/object-serialization/javascript/xlang.md +++ /dev/null @@ -1,206 +0,0 @@ ---- -title: Xlang Serialization -sidebar_position: 1 -id: xlang -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Fory JavaScript serializes to the same binary format as the Java, Python, C++, -Go, Rust, C#, Swift, Dart, Scala, and Kotlin Fory implementations. You can write a -message in JavaScript and read it in Java, or any other direction, without a -conversion layer. - -Things to keep in mind: - -- Fory JavaScript reads and writes cross-language payloads only; it does not support any native-mode format. -- JavaScript does not support out-of-band mode. - -## Requirements for a Successful Round Trip - -For a message to survive a round trip between JavaScript and another language: - -1. **Same type identity** on both sides — same numeric ID, or same `typeName`. -2. **Compatible field types** — a `Type.int32()` field in JavaScript matches Java `int`, Go `int32`, C# `int`. -3. **Same nullability** — if one side marks a field nullable, the other should too. -4. Compatible schema evolution on both sides. JavaScript enables it by default. -5. **Same reference tracking config** if your data has shared or circular references. - -## Step-by-Step: JavaScript to Another Peer - -1. Define the JavaScript schema with the same type name or numeric ID used by the peer. -2. Register the schema in both peers. -3. Match field types, nullability, and schema-evolution settings. -4. Test a real payload end-to-end before shipping. - -JavaScript side: - -```ts -import Fory, { Type } from "@apache-fory/core"; - -const messageType = Type.struct( - { typeName: "example.message" }, - { - id: Type.int64(), - content: Type.string(), - }, -); - -const fory = new Fory(); -const { serialize } = fory.register(messageType); - -const bytes = serialize({ - id: 1n, - content: "hello from JavaScript", -}); -``` - -On the other side, register the same `example.message` type (same name or same numeric ID) using the peer language's API: - -- [Java guide](../java/index.md) -- [Python guide](../python/index.md) -- [Go guide](../go/index.md) -- [Rust guide](../rust/index.md) - -## Field Naming - -Fory matches fields by name. When models are defined in multiple languages, keep field names consistent — or at minimum use a naming scheme that maps unambiguously across languages (e.g. `snake_case` everywhere). - -With the default compatible schema evolution, field order differences are tolerated, but the names -themselves must still match. - -## Numeric Types - -JavaScript `number` is a 64-bit float, which does not map cleanly to every integer type in other languages. Use explicit schema types: - -- `Type.int32()` for 32-bit integers (Java `int`, Go `int32`, C# `int`) -- `Type.int64()` with `bigint` values for 64-bit integers (Java `long`, Go `int64`) -- `Type.float32()` or `Type.float64()` for floating-point values - -## Lists and Dense Arrays - -Use `Type.list(T)` for ordinary JavaScript `Array` values and Fory -`list` schema. Dense bool/numeric vectors use the explicit array builders -listed below. - -| Fory schema | JavaScript/TypeScript schema builder | -| ----------------- | ------------------------------------ | -| `list` | `Type.list(Type.int32())` | -| `array` | `Type.boolArray()` | -| `array` | `Type.int8Array()` | -| `array` | `Type.int16Array()` | -| `array` | `Type.int32Array()` | -| `array` | `Type.int64Array()` | -| `array` | `Type.uint8Array()` | -| `array` | `Type.uint16Array()` | -| `array` | `Type.uint32Array()` | -| `array` | `Type.uint64Array()` | -| `array` | `Type.float16Array()` | -| `array` | `Type.bfloat16Array()` | -| `array` | `Type.float32Array()` | -| `array` | `Type.float64Array()` | - -## Date and Time - -- `Type.timestamp()` — a point in time; round-trips as a JavaScript `Date` -- `Type.date()` — a date without time; deserializes as `Date` -- `Type.duration()` — exposed as a numeric millisecond value in JavaScript - -## Polymorphic Fields - -`Type.any()` lets a field hold different concrete types, but it is harder to keep in sync across languages. Prefer explicit field schemas whenever possible. - -```ts -const wrapperType = Type.struct( - { typeId: 3001 }, - { - payload: Type.any(), - }, -); -``` - -## Enums - -Enum member **order** must match across languages. Fory encodes enums by ordinal position, not by value. - -```ts -const Color = { Red: 1, Green: 2, Blue: 3 }; -const fory = new Fory(); -fory.register(Type.enum({ typeId: 210 }, Color)); -``` - -Use the same type ID or type name in every peer. - -## Safety Limits - -The `maxDepth` option bounds nested payloads. It does not change the binary format; it only controls what the local `Fory` instance accepts. - -## Related Topics - -- [Supported Types](supported-types.md) -- [Schema Evolution](schema-evolution.md) -- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) - -## Built-in values - -```javascript -import Fory from "@apache-fory/core"; - -const fory = new Fory(); -const input = fory.serialize("hello fory"); -const result = fory.deserialize(input); -console.log(result); -``` - -## Custom values - -```javascript -import Fory, { Type } from "@apache-fory/core"; - -// Describe data structures using JSON schema -const description = Type.struct( - { typeName: "example.foo" }, - { - foo: Type.string(), - }, -); -const fory = new Fory(); -const { serialize, deserialize } = fory.register(description); -const input = serialize({ foo: "hello fory" }); -const result = deserialize(input); -console.log(result); -``` - -## Shared and circular references - -```javascript -import Fory, { Type } from "@apache-fory/core"; - -const description = Type.struct("example.foo", { - foo: Type.string(), - bar: Type.struct("example.foo").setTrackingRef(true), -}); - -const fory = new Fory({ ref: true }); -const { serialize, deserialize } = fory.register(description); -const data: any = { - foo: "hello fory", -}; -data.bar = data; -const input = serialize(data); -const result = deserialize(input); -console.log(result.bar.foo === result.foo); -``` diff --git a/docs/object-serialization/kotlin/xlang.md b/docs/object-serialization/kotlin/core-api.md similarity index 82% rename from docs/object-serialization/kotlin/xlang.md rename to docs/object-serialization/kotlin/core-api.md index 1f5b86e3bf..c152187fd1 100644 --- a/docs/object-serialization/kotlin/xlang.md +++ b/docs/object-serialization/kotlin/core-api.md @@ -1,7 +1,7 @@ --- -title: Kotlin Xlang Serialization +title: Basic Serialization sidebar_position: 1 -id: xlang +id: core-api license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -19,6 +19,12 @@ license: | limitations under the License. --- +Xlang is the default serialization mode for Fory Kotlin. This page covers the basic serialization API and interoperability rules for that default mode. + +## Cross-Language Interoperability + +The following sections cover model generation, registration, and cross-language round trips in the default xlang mode. + Kotlin xlang serialization uses the JVM Fory implementation through `ForyKotlin`. Use it when Kotlin payloads must be read by another supported Fory runtime. Register portable model types with the same identity and field schema on every peer. @@ -27,7 +33,7 @@ Kotlin data classes, enums, and sealed-class models use the Kotlin integration a serializers where applicable. Exact portable carrier mappings remain defined by the [xlang type mapping](../../specification/xlang_type_mapping.md). -## Create an xlang instance +### Create a Fory Instance ```kotlin import org.apache.fory.kotlin.ForyKotlin @@ -37,7 +43,7 @@ val fory = ForyKotlin.builder() .build() ``` -## First round trip +### First round trip ```kotlin import org.apache.fory.ThreadSafeFory diff --git a/docs/object-serialization/kotlin/index.md b/docs/object-serialization/kotlin/index.md index 76abeddabc..abe97519b5 100644 --- a/docs/object-serialization/kotlin/index.md +++ b/docs/object-serialization/kotlin/index.md @@ -126,7 +126,7 @@ Fory Kotlin is built on top of Fory Java. Most configuration options, features, ## Kotlin-Specific Documentation -- [Xlang Serialization](xlang.md) - Kotlin APIs for direct cross-language object serialization +- [Basic Serialization](core-api.md) - Models, APIs, and interoperability in the default xlang mode - [Configuration](configuration.md) - Kotlin-specific Fory setup requirements - [Native Serialization](native.md) - Serializing Kotlin types in JVM-native mode - [Schema Metadata](schema-metadata.md) - Kotlin annotations, nullability, references, and integer metadata diff --git a/docs/object-serialization/kotlin/native.md b/docs/object-serialization/kotlin/native.md index 5b16278525..f62a0d3566 100644 --- a/docs/object-serialization/kotlin/native.md +++ b/docs/object-serialization/kotlin/native.md @@ -20,7 +20,7 @@ license: | --- This page covers serialization of Kotlin-specific JVM types in native mode. For -cross-language Kotlin models, use [Kotlin Xlang Serialization](xlang.md). +cross-language Kotlin models, use [Kotlin Cross-Language Interoperability](core-api.md#cross-language-interoperability). When compatible mode is enabled, Kotlin readers use the JVM compatible-read rules for selected scalar field type changes. A matched field can read between `Boolean`, `String`, numeric scalars, diff --git a/docs/object-serialization/python/core-api.md b/docs/object-serialization/python/core-api.md index a14ab689d5..6499ee9433 100644 --- a/docs/object-serialization/python/core-api.md +++ b/docs/object-serialization/python/core-api.md @@ -1,6 +1,6 @@ --- title: Basic Serialization -sidebar_position: 3 +sidebar_position: 1 id: core-api license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -104,6 +104,324 @@ for obj in objects: data = fory.dumps(obj) ``` +## Cross-Language Interoperability + +The default xlang format is shared by all Fory runtimes. The following sections cover its cross-language type mapping, type identity, and interoperability requirements. + +`pyfory` supports xlang object graph serialization, allowing you to serialize +data in Python and deserialize it in Java, C++, Go, Rust, +JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another supported +language. + +### Xlang Configuration + +Python defaults to xlang mode with compatible schema evolution. Set the mode explicitly in xlang examples: + +```python +import pyfory +fory = pyfory.Fory(xlang=True, ref=False, strict=True) +``` + +### Xlang Example + +#### Python (Serializer) + +```python +import pyfory +from dataclasses import dataclass + +f = pyfory.Fory(xlang=True, ref=True) + +# Register type for xlang compatibility +@dataclass +class Person: + name: str + age: pyfory.Int32 + +f.register(Person, name="example.Person") + +person = Person("Charlie", 35) +binary_data = f.serialize(person) +# binary_data can now be sent to Java, Go, etc. +``` + +#### Java (Deserializer) + +```java +import org.apache.fory.*; + +public class Person { + public String name; + public int age; +} + +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + +fory.register(Person.class, "example.Person"); +Person person = (Person) fory.deserialize(binaryData); +``` + +#### Rust (Deserializer) + +```rust +use fory::Fory; +use fory::ForyStruct; + +#[derive(ForyStruct)] +struct Person { + name: String, + age: i32, +} + +let mut fory = Fory::builder().xlang(true).build(); + +fory.register_by_name::("example.Person"); +let person: Person = fory.deserialize(&binary_data)?; +``` + +### Type Annotations for Xlang + +Use pyfory type annotations for explicit xlang type mapping: + +Use these markers directly in Python type annotations. Field values remain +ordinary Python `int` or `float` values, and Fory serializes them with the +requested xlang numeric width and encoding. + +```python +from dataclasses import dataclass +from typing import Dict, List +import pyfory + +@dataclass +class TypedData: + int_value: pyfory.Int32 # 32-bit integer + long_value: pyfory.Int64 # 64-bit integer + float_value: pyfory.Float32 # 32-bit float + double_value: pyfory.Float64 # 64-bit float + values: Dict[pyfory.Int32, List[pyfory.Int64]] +``` + +Nested collection annotations are part of the field schema. Compatible-mode +reads consume bytes with the remote schema metadata, then assign only when the +decoded value safely satisfies the local schema. + +### Reduced-Precision Types + +`pyfory.Float16` and `pyfory.BFloat16` are reserved annotation markers for xlang +reduced-precision fields. They are not value wrapper classes; scalar values deserialize as native +Python `float`. + +Dense reduced-precision arrays use public dense wrappers with list-like sequence behavior. Construct them from Python +numeric values with `pyfory.Float16Array.from_values([...])` or +`pyfory.BFloat16Array.from_values([...])`. Use `from_buffer(...)` and `to_buffer()` only when you +already need packed little-endian `uint16` storage and want the raw-buffer fast path. + +### Type Mapping + +| Python marker/carrier | Java | Rust | Go | +| ---------------------- | -------------- | --------------- | --------------------- | +| `str` | `String` | `String` | `string` | +| `int` | `long` | `i64` | `int64` | +| `pyfory.Int32` | `int` | `i32` | `int32` | +| `pyfory.Int64` | `long` | `i64` | `int64` | +| `float` | `double` | `f64` | `float64` | +| `pyfory.Float32` | `float` | `f32` | `float32` | +| `pyfory.Float16` | `Float16` | `Float16` | `float16.Float16` | +| `pyfory.BFloat16` | `BFloat16` | `BFloat16` | `bfloat16.BFloat16` | +| `pyfory.Float16Array` | `Float16List` | `Vec` | `[]float16.Float16` | +| `pyfory.BFloat16Array` | `BFloat16List` | `Vec` | `[]bfloat16.BFloat16` | +| `list` | `List` | `Vec` | `[]T` | +| `dict` | `Map` | `HashMap` | `map[K]V` | + +#### Lists and Dense Arrays + +Python `List[T]` maps to Fory `list`. Use `pyfory.Array[T]`, +`pyfory.NDArray[T]`, or `pyfory.PyArray[T]` only when the schema is the dense +one-dimensional `array` kind. + +| Fory schema | Python annotation and default carrier | +| ----------------- | -------------------------------------------------- | +| `list` | `List[pyfory.Int32]` | +| `array` | `pyfory.Array[bool]` -> `BoolArray` | +| `array` | `pyfory.Array[pyfory.Int8]` -> `Int8Array` | +| `array` | `pyfory.Array[pyfory.Int16]` -> `Int16Array` | +| `array` | `pyfory.Array[pyfory.Int32]` -> `Int32Array` | +| `array` | `pyfory.Array[pyfory.Int64]` -> `Int64Array` | +| `array` | `pyfory.Array[pyfory.UInt8]` -> `UInt8Array` | +| `array` | `pyfory.Array[pyfory.UInt16]` -> `UInt16Array` | +| `array` | `pyfory.Array[pyfory.UInt32]` -> `UInt32Array` | +| `array` | `pyfory.Array[pyfory.UInt64]` -> `UInt64Array` | +| `array` | `pyfory.Array[pyfory.Float16]` -> `Float16Array` | +| `array` | `pyfory.Array[pyfory.BFloat16]` -> `BFloat16Array` | +| `array` | `pyfory.Array[pyfory.Float32]` -> `Float32Array` | +| `array` | `pyfory.Array[pyfory.Float64]` -> `Float64Array` | + +The `pyfory.*Array` wrappers accept iterable constructors such as +`pyfory.Float32Array([1, 2, 3])` and expose list-like sequence behavior over +dense owned storage. + +`pyfory.Array[T]`, `pyfory.NDArray[T]`, and `pyfory.PyArray[T]` all describe +the same Fory `array` schema. They differ only in the Python carrier +contract: + +| Python field annotation | Value accepted for that field | Deserialized carrier | +| ----------------------- | ------------------------------------------------------- | -------------------- | +| `pyfory.Array[T]` | `pyfory.*Array`, `numpy.ndarray`, `array.array`, `list` | `pyfory.*Array` | +| `pyfory.NDArray[T]` | `numpy.ndarray` | `numpy.ndarray` | +| `pyfory.PyArray[T]` | Python `array.array` | Python `array.array` | + +In compatible mode, a writer and reader can use different Python carriers for +the same named field as long as both annotations lower to the same Fory +`array` schema. For example, a writer field declared as +`pyfory.Array[pyfory.Int32]` can be read by a Python class whose matching field +is declared as `pyfory.NDArray[pyfory.Int32]`, and the reader receives a NumPy +`int32` ndarray. The reverse pattern also works for `pyfory.PyArray[T]`; that +name always means Python `array.array`. + +PyArrow is a separate row/columnar format surface, not a `pyfory.PyArray` +carrier. Use `pyfory.format.from_arrow_schema(...)` and +`pyfory.format.to_arrow_schema(...)` to convert between PyArrow schemas and +Fory row-format schemas. + +### Differences from Python Native Mode + +The binary protocol and API are similar to `pyfory`'s Python native mode, but Python native mode can serialize any Python object—including global functions, local functions, lambdas, local classes, and types with custom serialization using `__getstate__/__reduce__/__reduce_ex__`, which are **not allowed** in xlang mode. + +### Specifications and References + +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) +- [Type Mapping Reference](../../specification/xlang_type_mapping.md) +- [Java Interoperability Guide](../java/core-api.md#cross-language-interoperability) +- [Rust Interoperability Guide](../rust/core-api.md#cross-language-interoperability) + +### Related Guides + +- [Configuration](configuration.md) - xlang mode settings +- [Schema Evolution](schema-evolution.md) - Compatible mode +- [Type Registration](type-registration.md) - Registration patterns + +### Read the Java file example + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True) +fory.register_type(Person, name="example.Person") + +with open("person.bin", "rb") as f: + data = f.read() + +person = fory.deserialize(data) +print(f"Name: {person.name}, Age: {person.age}") +# Output: Name: Alice, Age: 30 +``` + +### Built-in values + +```python +import pyfory +import numpy as np + +fory = pyfory.Fory(xlang=True) +object_list = [True, False, "str", -1.1, 1, + np.full(100, 0, dtype=np.int32), np.full(20, 0.0, dtype=np.double)] +data = fory.serialize(object_list) +# bytes can be deserialized by other languages +new_list = fory.deserialize(data) +object_map = {"k1": "v1", "k2": object_list, "k3": -1} +data = fory.serialize(object_map) +# bytes can be deserialized by other languages +new_map = fory.deserialize(data) +print(new_map) +``` + +### Custom values + +```python +from dataclasses import dataclass +from typing import List, Dict, Any +import pyfory, array + + +@dataclass +class SomeClass1: + f1: Any + f2: Dict[pyfory.Int8, pyfory.Int32] + + +@dataclass +class SomeClass2: + f1: Any = None + f2: str = None + f3: List[str] = None + f4: Dict[pyfory.Int8, pyfory.Int32] = None + f5: pyfory.Int8 = None + f6: pyfory.Int16 = None + f7: pyfory.Int32 = None + # int type will be taken as `pyfory.Int64`. + # use `pyfory.Int32` for type hint if peer uses more narrow type. + f8: int = None + f9: pyfory.Float32 = None + # float type will be taken as `pyfory.Float64` + f10: float = None + f11: pyfory.Array[pyfory.Int16] = None + f12: List[pyfory.Int16] = None + + +if __name__ == "__main__": + f = pyfory.Fory(xlang=True) + f.register_type(SomeClass1, name="example.SomeClass1") + f.register_type(SomeClass2, name="example.SomeClass2") + obj1 = SomeClass1(f1=True, f2={-1: 2}) + obj = SomeClass2( + f1=obj1, + f2="abc", + f3=["abc", "abc"], + f4={1: 2}, + f5=2 ** 7 - 1, + f6=2 ** 15 - 1, + f7=2 ** 31 - 1, + f8=2 ** 63 - 1, + f9=1.0 / 2, + f10=1 / 3.0, + f11=array.array("h", [1, 2]), + f12=[-1, 4], + ) + data = f.serialize(obj) + # bytes can be deserialized by other languages + print(f.deserialize(data)) +``` + +### Shared and circular references + +```python +from typing import Dict +import pyfory + +class SomeClass: + f1: "SomeClass" + f2: Dict[str, str] + f3: Dict[str, str] + +fory = pyfory.Fory(xlang=True, ref=True) +fory.register_type(SomeClass, name="example.SomeClass") +obj = SomeClass() +obj.f2 = {"k1": "v1", "k2": "v2"} +obj.f1, obj.f3 = obj, obj.f2 +data = fory.serialize(obj) +# bytes can be deserialized by other languages +print(fory.deserialize(data)) +``` + ## Related Topics - [Configuration](configuration.md) - Fory parameters diff --git a/docs/object-serialization/python/custom-serializers.md b/docs/object-serialization/python/custom-serializers.md index e934c3454f..10dc0af7aa 100644 --- a/docs/object-serialization/python/custom-serializers.md +++ b/docs/object-serialization/python/custom-serializers.md @@ -135,4 +135,4 @@ fory.register(MyClass, name="com.example.MyClass", serializer=MySerializer(fory. - [Type Registration](type-registration.md) - Registration patterns - [Configuration](configuration.md) - Fory parameters -- [Xlang Serialization](xlang.md) - type registration and schema rules for xlang +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - type registration and schema rules for xlang diff --git a/docs/object-serialization/python/index.md b/docs/object-serialization/python/index.md index b356e36b94..cd700229a2 100644 --- a/docs/object-serialization/python/index.md +++ b/docs/object-serialization/python/index.md @@ -147,12 +147,11 @@ Use xlang mode for cross-language payloads and dataclass schemas shared with oth Use native mode for Python-only traffic. Native mode is selected with `xlang=False` and owns pickle/cloudpickle-style behavior such as functions, lambdas, classes, methods, `__reduce__`, `__getstate__`, and out-of-band pickle protocol 5 buffers. It is optimized for Python's type system and supports a broader Python object surface than xlang mode, so use it when replacing pickle or cloudpickle. Compatible mode is enabled by default. Set `compatible=False` only when every reader and writer uses the same Python class schema and you want faster serialization and smaller size. -See [Native Serialization](native.md) for Python-only serialization details and [Xlang Serialization](xlang.md) for Python xlang registration and interoperability rules. +See [Native Serialization](native.md) for Python-only serialization details and [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for Python xlang registration and interoperability rules. ## Next Steps -- [Basic Serialization](core-api.md) - Basic usage patterns -- [Xlang Serialization](xlang.md) - xlang mode +- [Basic Serialization](core-api.md) - Default xlang APIs and interoperability - [Native Serialization](native.md) - Python-only serialization - [Configuration](configuration.md) - Fory parameters, modes, and security - [Type Registration](type-registration.md) - User-defined type registration diff --git a/docs/object-serialization/python/native.md b/docs/object-serialization/python/native.md index fbab2d008e..65f2eeb636 100644 --- a/docs/object-serialization/python/native.md +++ b/docs/object-serialization/python/native.md @@ -23,7 +23,7 @@ Python native serialization is the Python-only wire mode selected with `xlang=Fa every writer and reader is Python and the payload should follow Python's object model instead of the portable xlang type system. -Use [Xlang Serialization](xlang.md), the default Python mode, when bytes must be read +Use [Cross-Language Interoperability](core-api.md#cross-language-interoperability), the default Python mode, when bytes must be read by Java, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another non-Python Fory implementation. @@ -186,7 +186,7 @@ Keep the payload in native mode. Xlang mode does not execute Python `__reduce__` ## Related Topics -- [Xlang Serialization](xlang.md) - Cross-language Python payloads +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language Python payloads - [Configuration](configuration.md) - Python `Fory` options - [Out-of-Band Serialization](out-of-band.md) - Zero-copy buffer support - [Configuration](configuration.md#security) - Deserialization policies diff --git a/docs/object-serialization/python/schema-evolution.md b/docs/object-serialization/python/schema-evolution.md index 52ba957c78..40af9aebfe 100644 --- a/docs/object-serialization/python/schema-evolution.md +++ b/docs/object-serialization/python/schema-evolution.md @@ -137,5 +137,5 @@ class SameSchemaMessage: ## Related Topics - [Configuration](configuration.md) - Compatible mode settings -- [Xlang Serialization](xlang.md) - Schema evolution across languages +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Schema evolution across languages - [Type Registration](type-registration.md) - Registration patterns diff --git a/docs/object-serialization/python/schema-metadata.md b/docs/object-serialization/python/schema-metadata.md index ad8db45551..9a315f3dde 100644 --- a/docs/object-serialization/python/schema-metadata.md +++ b/docs/object-serialization/python/schema-metadata.md @@ -526,4 +526,4 @@ class User: - [Basic Serialization](core-api.md) - Getting started with Fory serialization - [Schema Evolution](schema-evolution.md) - Compatible mode and schema evolution -- [Xlang Serialization](xlang.md) - Interoperability with Java, Rust, C++, Go +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Interoperability with Java, Rust, C++, Go diff --git a/docs/object-serialization/python/xlang.md b/docs/object-serialization/python/xlang.md deleted file mode 100644 index 3f9b53d19c..0000000000 --- a/docs/object-serialization/python/xlang.md +++ /dev/null @@ -1,334 +0,0 @@ ---- -title: Xlang Serialization -sidebar_position: 1 -id: xlang -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -`pyfory` supports xlang object graph serialization, allowing you to serialize -data in Python and deserialize it in Java, C++, Go, Rust, -JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another supported -language. - -## Create an Xlang Fory Instance - -Python defaults to xlang mode with compatible schema evolution. Set the mode explicitly in xlang examples: - -```python -import pyfory -fory = pyfory.Fory(xlang=True, ref=False, strict=True) -``` - -## Xlang Example - -### Python (Serializer) - -```python -import pyfory -from dataclasses import dataclass - -f = pyfory.Fory(xlang=True, ref=True) - -# Register type for xlang compatibility -@dataclass -class Person: - name: str - age: pyfory.Int32 - -f.register(Person, name="example.Person") - -person = Person("Charlie", 35) -binary_data = f.serialize(person) -# binary_data can now be sent to Java, Go, etc. -``` - -### Java (Deserializer) - -```java -import org.apache.fory.*; - -public class Person { - public String name; - public int age; -} - -Fory fory = Fory.builder() - .withXlang(true) - .withRefTracking(true) - .build(); - -fory.register(Person.class, "example.Person"); -Person person = (Person) fory.deserialize(binaryData); -``` - -### Rust (Deserializer) - -```rust -use fory::Fory; -use fory::ForyStruct; - -#[derive(ForyStruct)] -struct Person { - name: String, - age: i32, -} - -let mut fory = Fory::builder().xlang(true).build(); - -fory.register_by_name::("example.Person"); -let person: Person = fory.deserialize(&binary_data)?; -``` - -## Type Annotations for Xlang - -Use pyfory type annotations for explicit xlang type mapping: - -Use these markers directly in Python type annotations. Field values remain -ordinary Python `int` or `float` values, and Fory serializes them with the -requested xlang numeric width and encoding. - -```python -from dataclasses import dataclass -from typing import Dict, List -import pyfory - -@dataclass -class TypedData: - int_value: pyfory.Int32 # 32-bit integer - long_value: pyfory.Int64 # 64-bit integer - float_value: pyfory.Float32 # 32-bit float - double_value: pyfory.Float64 # 64-bit float - values: Dict[pyfory.Int32, List[pyfory.Int64]] -``` - -Nested collection annotations are part of the field schema. Compatible-mode -reads consume bytes with the remote schema metadata, then assign only when the -decoded value safely satisfies the local schema. - -## Reduced-Precision Types - -`pyfory.Float16` and `pyfory.BFloat16` are reserved annotation markers for xlang -reduced-precision fields. They are not value wrapper classes; scalar values deserialize as native -Python `float`. - -Dense reduced-precision arrays use public dense wrappers with list-like sequence behavior. Construct them from Python -numeric values with `pyfory.Float16Array.from_values([...])` or -`pyfory.BFloat16Array.from_values([...])`. Use `from_buffer(...)` and `to_buffer()` only when you -already need packed little-endian `uint16` storage and want the raw-buffer fast path. - -## Type Mapping - -| Python marker/carrier | Java | Rust | Go | -| ---------------------- | -------------- | --------------- | --------------------- | -| `str` | `String` | `String` | `string` | -| `int` | `long` | `i64` | `int64` | -| `pyfory.Int32` | `int` | `i32` | `int32` | -| `pyfory.Int64` | `long` | `i64` | `int64` | -| `float` | `double` | `f64` | `float64` | -| `pyfory.Float32` | `float` | `f32` | `float32` | -| `pyfory.Float16` | `Float16` | `Float16` | `float16.Float16` | -| `pyfory.BFloat16` | `BFloat16` | `BFloat16` | `bfloat16.BFloat16` | -| `pyfory.Float16Array` | `Float16List` | `Vec` | `[]float16.Float16` | -| `pyfory.BFloat16Array` | `BFloat16List` | `Vec` | `[]bfloat16.BFloat16` | -| `list` | `List` | `Vec` | `[]T` | -| `dict` | `Map` | `HashMap` | `map[K]V` | - -### Lists and Dense Arrays - -Python `List[T]` maps to Fory `list`. Use `pyfory.Array[T]`, -`pyfory.NDArray[T]`, or `pyfory.PyArray[T]` only when the schema is the dense -one-dimensional `array` kind. - -| Fory schema | Python annotation and default carrier | -| ----------------- | -------------------------------------------------- | -| `list` | `List[pyfory.Int32]` | -| `array` | `pyfory.Array[bool]` -> `BoolArray` | -| `array` | `pyfory.Array[pyfory.Int8]` -> `Int8Array` | -| `array` | `pyfory.Array[pyfory.Int16]` -> `Int16Array` | -| `array` | `pyfory.Array[pyfory.Int32]` -> `Int32Array` | -| `array` | `pyfory.Array[pyfory.Int64]` -> `Int64Array` | -| `array` | `pyfory.Array[pyfory.UInt8]` -> `UInt8Array` | -| `array` | `pyfory.Array[pyfory.UInt16]` -> `UInt16Array` | -| `array` | `pyfory.Array[pyfory.UInt32]` -> `UInt32Array` | -| `array` | `pyfory.Array[pyfory.UInt64]` -> `UInt64Array` | -| `array` | `pyfory.Array[pyfory.Float16]` -> `Float16Array` | -| `array` | `pyfory.Array[pyfory.BFloat16]` -> `BFloat16Array` | -| `array` | `pyfory.Array[pyfory.Float32]` -> `Float32Array` | -| `array` | `pyfory.Array[pyfory.Float64]` -> `Float64Array` | - -The `pyfory.*Array` wrappers accept iterable constructors such as -`pyfory.Float32Array([1, 2, 3])` and expose list-like sequence behavior over -dense owned storage. - -`pyfory.Array[T]`, `pyfory.NDArray[T]`, and `pyfory.PyArray[T]` all describe -the same Fory `array` schema. They differ only in the Python carrier -contract: - -| Python field annotation | Value accepted for that field | Deserialized carrier | -| ----------------------- | ------------------------------------------------------- | -------------------- | -| `pyfory.Array[T]` | `pyfory.*Array`, `numpy.ndarray`, `array.array`, `list` | `pyfory.*Array` | -| `pyfory.NDArray[T]` | `numpy.ndarray` | `numpy.ndarray` | -| `pyfory.PyArray[T]` | Python `array.array` | Python `array.array` | - -In compatible mode, a writer and reader can use different Python carriers for -the same named field as long as both annotations lower to the same Fory -`array` schema. For example, a writer field declared as -`pyfory.Array[pyfory.Int32]` can be read by a Python class whose matching field -is declared as `pyfory.NDArray[pyfory.Int32]`, and the reader receives a NumPy -`int32` ndarray. The reverse pattern also works for `pyfory.PyArray[T]`; that -name always means Python `array.array`. - -PyArrow is a separate row/columnar format surface, not a `pyfory.PyArray` -carrier. Use `pyfory.format.from_arrow_schema(...)` and -`pyfory.format.to_arrow_schema(...)` to convert between PyArrow schemas and -Fory row-format schemas. - -## Differences from Python Native Mode - -The binary protocol and API are similar to `pyfory`'s Python native mode, but Python native mode can serialize any Python object—including global functions, local functions, lambdas, local classes, and types with custom serialization using `__getstate__/__reduce__/__reduce_ex__`, which are **not allowed** in xlang mode. - -## See Also - -- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) -- [Type Mapping Reference](../../specification/xlang_type_mapping.md) -- [Java Xlang Serialization Guide](../java/xlang.md) -- [Rust Xlang Serialization Guide](../rust/xlang.md) - -## Related Topics - -- [Configuration](configuration.md) - xlang mode settings -- [Schema Evolution](schema-evolution.md) - Compatible mode -- [Type Registration](type-registration.md) - Registration patterns - -## Read the Java file example - -```python -import pyfory -from dataclasses import dataclass - -@dataclass -class Person: - name: str - age: pyfory.Int32 - -fory = pyfory.Fory(xlang=True) -fory.register_type(Person, name="example.Person") - -with open("person.bin", "rb") as f: - data = f.read() - -person = fory.deserialize(data) -print(f"Name: {person.name}, Age: {person.age}") -# Output: Name: Alice, Age: 30 -``` - -## Built-in values - -```python -import pyfory -import numpy as np - -fory = pyfory.Fory(xlang=True) -object_list = [True, False, "str", -1.1, 1, - np.full(100, 0, dtype=np.int32), np.full(20, 0.0, dtype=np.double)] -data = fory.serialize(object_list) -# bytes can be deserialized by other languages -new_list = fory.deserialize(data) -object_map = {"k1": "v1", "k2": object_list, "k3": -1} -data = fory.serialize(object_map) -# bytes can be deserialized by other languages -new_map = fory.deserialize(data) -print(new_map) -``` - -## Custom values - -```python -from dataclasses import dataclass -from typing import List, Dict, Any -import pyfory, array - - -@dataclass -class SomeClass1: - f1: Any - f2: Dict[pyfory.Int8, pyfory.Int32] - - -@dataclass -class SomeClass2: - f1: Any = None - f2: str = None - f3: List[str] = None - f4: Dict[pyfory.Int8, pyfory.Int32] = None - f5: pyfory.Int8 = None - f6: pyfory.Int16 = None - f7: pyfory.Int32 = None - # int type will be taken as `pyfory.Int64`. - # use `pyfory.Int32` for type hint if peer uses more narrow type. - f8: int = None - f9: pyfory.Float32 = None - # float type will be taken as `pyfory.Float64` - f10: float = None - f11: pyfory.Array[pyfory.Int16] = None - f12: List[pyfory.Int16] = None - - -if __name__ == "__main__": - f = pyfory.Fory(xlang=True) - f.register_type(SomeClass1, name="example.SomeClass1") - f.register_type(SomeClass2, name="example.SomeClass2") - obj1 = SomeClass1(f1=True, f2={-1: 2}) - obj = SomeClass2( - f1=obj1, - f2="abc", - f3=["abc", "abc"], - f4={1: 2}, - f5=2 ** 7 - 1, - f6=2 ** 15 - 1, - f7=2 ** 31 - 1, - f8=2 ** 63 - 1, - f9=1.0 / 2, - f10=1 / 3.0, - f11=array.array("h", [1, 2]), - f12=[-1, 4], - ) - data = f.serialize(obj) - # bytes can be deserialized by other languages - print(f.deserialize(data)) -``` - -## Shared and circular references - -```python -from typing import Dict -import pyfory - -class SomeClass: - f1: "SomeClass" - f2: Dict[str, str] - f3: Dict[str, str] - -fory = pyfory.Fory(xlang=True, ref=True) -fory.register_type(SomeClass, name="example.SomeClass") -obj = SomeClass() -obj.f2 = {"k1": "v1", "k2": "v2"} -obj.f1, obj.f3 = obj, obj.f2 -data = fory.serialize(obj) -# bytes can be deserialized by other languages -print(fory.deserialize(data)) -``` diff --git a/docs/object-serialization/rust/configuration.md b/docs/object-serialization/rust/configuration.md index 8edeb023d0..a2ba3204ca 100644 --- a/docs/object-serialization/rust/configuration.md +++ b/docs/object-serialization/rust/configuration.md @@ -218,4 +218,4 @@ Security-related configuration: - [Basic Serialization](core-api.md) - Using configured Fory - [Schema Evolution](schema-evolution.md) - Compatible mode details -- [Xlang Serialization](xlang.md) - xlang mode +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - xlang mode diff --git a/docs/object-serialization/rust/core-api.md b/docs/object-serialization/rust/core-api.md index 5bce4c68e4..8587e89b1c 100644 --- a/docs/object-serialization/rust/core-api.md +++ b/docs/object-serialization/rust/core-api.md @@ -1,6 +1,6 @@ --- title: Basic Serialization -sidebar_position: 3 +sidebar_position: 1 id: core-api license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -19,7 +19,7 @@ license: | limitations under the License. --- -This page covers basic object graph serialization and supported types. +This page covers basic object graph serialization and supported types in the default xlang mode for Fory Rust. ## Object Graph Serialization @@ -209,6 +209,304 @@ all supported carriers, and registration. - **Little-Endian**: Optimized for modern CPU architectures - **Reference Deduplication**: Shared objects serialized only once +## Cross-Language Interoperability + +The default xlang format is shared by all Fory runtimes. The following sections cover its cross-language type mapping, type identity, and interoperability requirements. + +Apache Fory™ supports seamless data exchange across Java, Python, C++, Go, +Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. + +### Xlang Configuration + +Rust defaults to xlang mode with compatible schema evolution. Set the mode explicitly in xlang examples: + +```rust +use fory::Fory; + +// Use xlang mode +let mut fory = Fory::builder().xlang(true).build(); + +// Register types with consistent IDs across languages +fory.register::(100)?; + +// Or, on a different Fory instance, use name-based registration +// fory.register_by_name::("com.example.MyStruct")?; +``` + +### Type Registration for Xlang + +#### Register by ID + +For fast, compact serialization with consistent IDs across languages: + +```rust +let mut fory = Fory::builder().xlang(true).build(); + +fory.register::(100)?; // Same ID in Java, Python, etc. +``` + +#### Register by Name + +For more flexible type naming: + +```rust +fory.register_by_name::("com.example.User")?; +``` + +### Xlang Example + +#### Rust (Serializer) + +```rust +use fory::Fory; +use fory::ForyStruct; + +#[derive(ForyStruct)] +struct Person { + name: String, + age: i32, +} + +let mut fory = Fory::builder().xlang(true).build(); + +fory.register::(100)?; + +let person = Person { + name: "Alice".to_string(), + age: 30, +}; + +let bytes = fory.serialize(&person)?; +// bytes can be deserialized by Java, Python, etc. +``` + +#### Third-Party Rust Types + +An external structural serializer gives a third-party Rust type the same xlang +schema as an equivalent local derive: + +```rust +#[derive(ForyStruct)] +#[fory(target = third_party::User)] +struct UserSerializer { + name: String, + age: u32, +} + +let mut fory = Fory::builder().xlang(true).build(); +fory.register::(100)?; + +let bytes = fory.serialize_with::(&user)?; +``` + +Container roots compose with carrier serializers and keep the ordinary xlang +LIST, MAP, tuple, or array representation: + +```rust +use fory::VecSerializer; + +let bytes = + fory.serialize_with::>(&users)?; +``` + +Only xlang-representable schemas are accepted. A native Rust enum variant with +multiple tuple or named fields is supported with `xlang(false)`, but its +serializer registration is rejected in xlang mode. See +[External-Type Serialization](external-types.md). + +#### Dynamic Rust Carriers + +`Box`, `Rc`, `Arc`, and application +`dyn Trait` carriers can be used in xlang mode when every selected concrete +target has an xlang-compatible structural or EXT identity. Fory writes the +concrete registered target identity; the Rust trait or erased-carrier identity +does not appear on the wire. + +#### Java (Deserializer) + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +public class Person { + public String name; + public int age; +} + +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + +fory.register(Person.class, 100); // Same ID as Rust + +Person person = (Person) fory.deserialize(bytesFromRust); +``` + +#### Python (Deserializer) + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True, ref=True) +fory.register_type(Person, type_id=100) # Same ID as Rust + +person = fory.deserialize(bytes_from_rust) +``` + +### Type Mapping + +See [xlang_type_mapping.md](../../specification/xlang_type_mapping.md) for complete type mapping across languages. + +#### Common Type Mappings + +| Rust | Java | Python | +| --------------- | -------------- | --------------- | +| `i32` | `int` | `int32` | +| `i64` | `long` | `int64` | +| `f32` | `float` | `float32` | +| `f64` | `double` | `float64` | +| `Float16` | `Float16` | `float16` | +| `BFloat16` | `BFloat16` | `bfloat16` | +| `String` | `String` | `str` | +| `Vec` | `List` | `List[T]` | +| `Vec` | `Float16List` | `Float16Array` | +| `Vec` | `BFloat16List` | `BFloat16Array` | +| `[Float16; N]` | `Float16List` | `Float16Array` | +| `[BFloat16; N]` | `BFloat16List` | `BFloat16Array` | +| `HashMap` | `Map` | `Dict[K,V]` | +| `Option` | nullable `T` | `Optional[T]` | + +#### Lists and Dense Arrays + +Rust `Vec` maps to Fory `list` by default for manual structs. Use an +explicit array field attribute when the schema is dense `array`. + +| Fory schema | Rust carrier and metadata | +| ----------------- | ------------------------------ | +| `list` | `Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | + +### Interoperability Best Practices + +1. **Use consistent type IDs** across all languages +2. **Keep compatible mode** for schema evolution +3. **Register all types** before serialization +4. **Test cross-language** compatibility during development + +### Specifications and References + +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) +- [Type Mapping Reference](../../specification/xlang_type_mapping.md) +- [Java Interoperability Guide](../java/core-api.md#cross-language-interoperability) +- [Python Interoperability Guide](../python/core-api.md#cross-language-interoperability) + +### Related Guides + +- [Configuration](configuration.md) - xlang mode configuration +- [Schema Evolution](schema-evolution.md) - Compatible mode +- [Type Registration](type-registration.md) - Registration methods +- [External-Type Serialization](external-types.md) - Third-party values in xlang mode + +### Built-in values + +```rust +use fory::Fory; + +fn run() { + let fory = Fory::builder().xlang(true).build(); + let bin = fory.serialize(&"hello".to_string()).expect("serialize success"); + let obj: String = fory.deserialize(&bin).expect("deserialize success"); + assert_eq!("hello".to_string(), obj); +} +``` + +### Custom values + +```rust +use chrono::{NaiveDate, NaiveDateTime}; +use fory::{Fory, ForyStruct}; +use std::collections::HashMap; + +#[test] +fn complex_struct() { + #[derive(ForyStruct, Debug, PartialEq)] + struct Animal { + category: String, + } + + #[derive(ForyStruct, Debug, PartialEq)] + struct Person { + c1: Vec, // binary + c2: Vec, // primitive array + animal: Vec, + c3: Vec>, + name: String, + c4: HashMap, + age: u16, + op: Option, + op2: Option, + date: NaiveDate, + time: NaiveDateTime, + c5: f32, + c6: f64, + } + let person: Person = Person { + c1: vec![1, 2, 3], + c2: vec![5, 6, 7], + c3: vec![vec![1, 2], vec![1, 3]], + animal: vec![Animal { + category: "Dog".to_string(), + }], + c4: HashMap::from([ + ("hello1".to_string(), "hello2".to_string()), + ("hello2".to_string(), "hello3".to_string()), + ]), + age: 12, + name: "helo".to_string(), + op: Some("option".to_string()), + op2: None, + date: NaiveDate::from_ymd_opt(2025, 12, 12).unwrap(), + time: NaiveDateTime::from_timestamp_opt(1689912359, 0).unwrap(), + c5: 2.0, + c6: 4.0, + }; + + let mut fory = Fory::builder().xlang(true).build(); + fory + .register_by_name::("example.foo2") + .expect("register Animal"); + fory + .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"); + assert_eq!(person, obj); +} +``` + +### Shared and circular references + +Circular references cannot be implemented in Rust due to ownership restrictions. + ## Related Topics - [Type Registration](type-registration.md) - Registering types diff --git a/docs/object-serialization/rust/index.md b/docs/object-serialization/rust/index.md index 00a29191c8..f4070bef8f 100644 --- a/docs/object-serialization/rust/index.md +++ b/docs/object-serialization/rust/index.md @@ -101,7 +101,7 @@ Use xlang mode for cross-language payloads and schemas shared with other Fory im Use native mode for Rust-only traffic. Native mode is selected with `.xlang(false)` and keeps Rust object serialization in Rust-native form. It supports native-only concrete targets and data-enum shapes that have no xlang representation. Dynamic `Any`, application trait, and shared-reference carriers can also be used in xlang mode when every selected concrete target is xlang-compatible. Compatible mode is enabled by default. Set `.compatible(false)` only when every reader and writer uses the same Rust schema and you want faster serialization and smaller size. -See [Xlang Serialization](xlang.md) for Rust xlang registration and interoperability rules, and [Native Serialization](native.md) for Rust-only payloads. +See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for Rust xlang registration and interoperability rules, and [Native Serialization](native.md) for Rust-only payloads. ## Thread Safety @@ -158,8 +158,7 @@ fn main() -> Result<(), Error> { ## Next Steps - [Configuration](configuration.md) - Fory builder options and modes -- [Basic Serialization](core-api.md) - Object graph serialization -- [Xlang Serialization](xlang.md) - xlang mode +- [Basic Serialization](core-api.md) - Default xlang object graphs and interoperability - [Native Serialization](native.md) - Rust-only serialization - [References](references.md) - Shared and circular references - [Polymorphism](polymorphism.md) - Trait object serialization diff --git a/docs/object-serialization/rust/native.md b/docs/object-serialization/rust/native.md index feaaf0cf33..ba95606910 100644 --- a/docs/object-serialization/rust/native.md +++ b/docs/object-serialization/rust/native.md @@ -23,7 +23,7 @@ Rust native serialization is the Rust-only wire mode selected with `.xlang(false every writer and reader is Rust and the payload should preserve Rust object-graph behavior instead of the portable xlang type system. -Use [Xlang Serialization](xlang.md), the default Rust mode, when bytes must be read +Use [Cross-Language Interoperability](core-api.md#cross-language-interoperability), the default Rust mode, when bytes must be read by Java, Python, C++, Go, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another non-Rust Fory implementation. @@ -264,7 +264,7 @@ Native serialization defaults to compatible mode. Keep that default when schemas ## Related Topics -- [Xlang Serialization](xlang.md) - Cross-language Rust payloads +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language Rust payloads - [Configuration](configuration.md) - Builder options - [Basic Serialization](core-api.md) - Object graph serialization - [Shared & Circular References](references.md) - `Rc`, `Arc`, and weak pointers diff --git a/docs/object-serialization/rust/schema-evolution.md b/docs/object-serialization/rust/schema-evolution.md index 29e3624be6..38e5b621e3 100644 --- a/docs/object-serialization/rust/schema-evolution.md +++ b/docs/object-serialization/rust/schema-evolution.md @@ -278,5 +278,5 @@ assert_eq!(data, decoded); - [Configuration](configuration.md) - Compatible mode settings - [Polymorphism](polymorphism.md) - Trait objects with schema evolution -- [Xlang Serialization](xlang.md) - Schema evolution across languages +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Schema evolution across languages - [External-Type Serialization](external-types.md) - Compatible schemas for third-party values diff --git a/docs/object-serialization/rust/schema-metadata.md b/docs/object-serialization/rust/schema-metadata.md index ebf226e195..036164e07c 100644 --- a/docs/object-serialization/rust/schema-metadata.md +++ b/docs/object-serialization/rust/schema-metadata.md @@ -499,5 +499,5 @@ struct User { - [Basic Serialization](core-api.md) - Getting started with Fory serialization - [Schema Evolution](schema-evolution.md) - Compatible mode and schema evolution -- [Xlang Serialization](xlang.md) - Interoperability with Java, C++, Go, Python +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Interoperability with Java, C++, Go, Python - [External-Type Serialization](external-types.md) - Selecting serializers for third-party fields diff --git a/docs/object-serialization/rust/type-registration.md b/docs/object-serialization/rust/type-registration.md index 3efe057ae3..3e18bfd54c 100644 --- a/docs/object-serialization/rust/type-registration.md +++ b/docs/object-serialization/rust/type-registration.md @@ -136,6 +136,6 @@ let handles: Vec<_> = (0..4) ## Related Topics - [Configuration](configuration.md) - Fory builder options -- [Xlang Serialization](xlang.md) - xlang mode registration +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - xlang mode registration - [Custom Serializers](custom-serializers.md) - Custom serialization - [External-Type Serialization](external-types.md) - Third-party targets and carrier roots diff --git a/docs/object-serialization/rust/xlang.md b/docs/object-serialization/rust/xlang.md deleted file mode 100644 index c3b573a639..0000000000 --- a/docs/object-serialization/rust/xlang.md +++ /dev/null @@ -1,314 +0,0 @@ ---- -title: Xlang Serialization -sidebar_position: 1 -id: xlang -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Apache Fory™ supports seamless data exchange across Java, Python, C++, Go, -Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. - -## Create an Xlang Fory Instance - -Rust defaults to xlang mode with compatible schema evolution. Set the mode explicitly in xlang examples: - -```rust -use fory::Fory; - -// Use xlang mode -let mut fory = Fory::builder().xlang(true).build(); - -// Register types with consistent IDs across languages -fory.register::(100)?; - -// Or, on a different Fory instance, use name-based registration -// fory.register_by_name::("com.example.MyStruct")?; -``` - -## Type Registration for Xlang - -### Register by ID - -For fast, compact serialization with consistent IDs across languages: - -```rust -let mut fory = Fory::builder().xlang(true).build(); - -fory.register::(100)?; // Same ID in Java, Python, etc. -``` - -### Register by Name - -For more flexible type naming: - -```rust -fory.register_by_name::("com.example.User")?; -``` - -## Xlang Example - -### Rust (Serializer) - -```rust -use fory::Fory; -use fory::ForyStruct; - -#[derive(ForyStruct)] -struct Person { - name: String, - age: i32, -} - -let mut fory = Fory::builder().xlang(true).build(); - -fory.register::(100)?; - -let person = Person { - name: "Alice".to_string(), - age: 30, -}; - -let bytes = fory.serialize(&person)?; -// bytes can be deserialized by Java, Python, etc. -``` - -### Third-Party Rust Types - -An external structural serializer gives a third-party Rust type the same xlang -schema as an equivalent local derive: - -```rust -#[derive(ForyStruct)] -#[fory(target = third_party::User)] -struct UserSerializer { - name: String, - age: u32, -} - -let mut fory = Fory::builder().xlang(true).build(); -fory.register::(100)?; - -let bytes = fory.serialize_with::(&user)?; -``` - -Container roots compose with carrier serializers and keep the ordinary xlang -LIST, MAP, tuple, or array representation: - -```rust -use fory::VecSerializer; - -let bytes = - fory.serialize_with::>(&users)?; -``` - -Only xlang-representable schemas are accepted. A native Rust enum variant with -multiple tuple or named fields is supported with `xlang(false)`, but its -serializer registration is rejected in xlang mode. See -[External-Type Serialization](external-types.md). - -### Dynamic Rust Carriers - -`Box`, `Rc`, `Arc`, and application -`dyn Trait` carriers can be used in xlang mode when every selected concrete -target has an xlang-compatible structural or EXT identity. Fory writes the -concrete registered target identity; the Rust trait or erased-carrier identity -does not appear on the wire. - -### Java (Deserializer) - -```java -import org.apache.fory.*; -import org.apache.fory.config.*; - -public class Person { - public String name; - public int age; -} - -Fory fory = Fory.builder() - .withXlang(true) - .withRefTracking(true) - .build(); - -fory.register(Person.class, 100); // Same ID as Rust - -Person person = (Person) fory.deserialize(bytesFromRust); -``` - -### Python (Deserializer) - -```python -import pyfory -from dataclasses import dataclass - -@dataclass -class Person: - name: str - age: pyfory.Int32 - -fory = pyfory.Fory(xlang=True, ref=True) -fory.register_type(Person, type_id=100) # Same ID as Rust - -person = fory.deserialize(bytes_from_rust) -``` - -## Type Mapping - -See [xlang_type_mapping.md](../../specification/xlang_type_mapping.md) for complete type mapping across languages. - -### Common Type Mappings - -| Rust | Java | Python | -| --------------- | -------------- | --------------- | -| `i32` | `int` | `int32` | -| `i64` | `long` | `int64` | -| `f32` | `float` | `float32` | -| `f64` | `double` | `float64` | -| `Float16` | `Float16` | `float16` | -| `BFloat16` | `BFloat16` | `bfloat16` | -| `String` | `String` | `str` | -| `Vec` | `List` | `List[T]` | -| `Vec` | `Float16List` | `Float16Array` | -| `Vec` | `BFloat16List` | `BFloat16Array` | -| `[Float16; N]` | `Float16List` | `Float16Array` | -| `[BFloat16; N]` | `BFloat16List` | `BFloat16Array` | -| `HashMap` | `Map` | `Dict[K,V]` | -| `Option` | nullable `T` | `Optional[T]` | - -### Lists and Dense Arrays - -Rust `Vec` maps to Fory `list` by default for manual structs. Use an -explicit array field attribute when the schema is dense `array`. - -| Fory schema | Rust carrier and metadata | -| ----------------- | ------------------------------ | -| `list` | `Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | -| `array` | `#[fory(array)] Vec` | - -## Best Practices - -1. **Use consistent type IDs** across all languages -2. **Keep compatible mode** for schema evolution -3. **Register all types** before serialization -4. **Test cross-language** compatibility during development - -## See Also - -- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) -- [Type Mapping Reference](../../specification/xlang_type_mapping.md) -- [Java Xlang Serialization Guide](../java/xlang.md) -- [Python Xlang Serialization Guide](../python/xlang.md) - -## Related Topics - -- [Configuration](configuration.md) - xlang mode configuration -- [Schema Evolution](schema-evolution.md) - Compatible mode -- [Type Registration](type-registration.md) - Registration methods -- [External-Type Serialization](external-types.md) - Third-party values in xlang mode - -## Built-in values - -```rust -use fory::Fory; - -fn run() { - let fory = Fory::builder().xlang(true).build(); - let bin = fory.serialize(&"hello".to_string()).expect("serialize success"); - let obj: String = fory.deserialize(&bin).expect("deserialize success"); - assert_eq!("hello".to_string(), obj); -} -``` - -## Custom values - -```rust -use chrono::{NaiveDate, NaiveDateTime}; -use fory::{Fory, ForyStruct}; -use std::collections::HashMap; - -#[test] -fn complex_struct() { - #[derive(ForyStruct, Debug, PartialEq)] - struct Animal { - category: String, - } - - #[derive(ForyStruct, Debug, PartialEq)] - struct Person { - c1: Vec, // binary - c2: Vec, // primitive array - animal: Vec, - c3: Vec>, - name: String, - c4: HashMap, - age: u16, - op: Option, - op2: Option, - date: NaiveDate, - time: NaiveDateTime, - c5: f32, - c6: f64, - } - let person: Person = Person { - c1: vec![1, 2, 3], - c2: vec![5, 6, 7], - c3: vec![vec![1, 2], vec![1, 3]], - animal: vec![Animal { - category: "Dog".to_string(), - }], - c4: HashMap::from([ - ("hello1".to_string(), "hello2".to_string()), - ("hello2".to_string(), "hello3".to_string()), - ]), - age: 12, - name: "helo".to_string(), - op: Some("option".to_string()), - op2: None, - date: NaiveDate::from_ymd_opt(2025, 12, 12).unwrap(), - time: NaiveDateTime::from_timestamp_opt(1689912359, 0).unwrap(), - c5: 2.0, - c6: 4.0, - }; - - let mut fory = Fory::builder().xlang(true).build(); - fory - .register_by_name::("example.foo2") - .expect("register Animal"); - fory - .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"); - assert_eq!(person, obj); -} -``` - -## Shared and circular references - -Circular references cannot be implemented in Rust due to ownership restrictions. diff --git a/docs/object-serialization/scala/xlang.md b/docs/object-serialization/scala/core-api.md similarity index 93% rename from docs/object-serialization/scala/xlang.md rename to docs/object-serialization/scala/core-api.md index e49a86a4b9..85a0bb2205 100644 --- a/docs/object-serialization/scala/xlang.md +++ b/docs/object-serialization/scala/core-api.md @@ -1,7 +1,7 @@ --- -title: Scala Xlang Serialization +title: Basic Serialization sidebar_position: 1 -id: xlang +id: core-api license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -19,11 +19,17 @@ license: | limitations under the License. --- +Xlang is the default serialization mode for Fory Scala. This page covers the basic serialization API and interoperability rules for that default mode. + +## Cross-Language Interoperability + +The following sections cover model generation, registration, and cross-language round trips in the default xlang mode. + The Fory schema IDL Scala target generates Scala 3 source for xlang payloads. The Fory Scala artifact remains cross-built for Scala 2.13 and Scala 3; only the schema IDL output and quoted macro derivation require Scala 3. -## Setup +### Setup Generated Scala code uses the public macro API in `org.apache.fory.scala` and the shared JVM annotations in `org.apache.fory.annotation`. Macro internals live @@ -55,7 +61,7 @@ serializers or Scala-specific registration state in Java core. Enums and unions are registered with their serializers directly because their derived serializers own case dispatch. -## Generated Messages +### Generated Messages Acyclic messages generate case classes: @@ -107,7 +113,7 @@ supported mutable collection interfaces such as `scala.collection.Seq` and `scala.collection.Map`, but concrete mutable collection classes are outside the schema IDL surface unless explicitly generated. -## Generated Enums +### Generated Enums IDL enums generate Scala 3 enums only. The compiler does not emit Java enum files. @@ -127,7 +133,7 @@ enum Status { Generated registration uses `ScalaSerializers.registerEnum(...)` so the stable Fory enum IDs from case-level `@ForyEnumId` metadata are used in xlang mode. -## Generated Unions +### Generated Unions IDL unions generate Scala 3 ADT enums with macro-derived serializers: @@ -167,7 +173,7 @@ locally. The macro writes the existing xlang union envelope directly. It does not allocate temporary Java `Union` carriers. -## Manual Scala 3 Derivation +### Manual Scala 3 Derivation Manual Scala 3 models can derive the same serializer typeclass: @@ -192,7 +198,7 @@ constructor-owned value that participates in the cycle, such as a Scala enum case or case class, the serializer fails with a clear error because no copied identity can be published until construction has completed. -## First round trip +### First round trip ```scala import org.apache.fory.Fory diff --git a/docs/object-serialization/scala/index.md b/docs/object-serialization/scala/index.md index 0d60f59af6..b94e3d35a7 100644 --- a/docs/object-serialization/scala/index.md +++ b/docs/object-serialization/scala/index.md @@ -118,5 +118,5 @@ Fory Scala is built on top of Fory Java. Most configuration options, features, a - [Native Serialization](native.md) - Serializing Scala types in JVM-native mode - [Schema Metadata](schema-metadata.md) - Scala annotations, references, enum IDs, and union metadata - [Default Values](default-values.md) - Scala class default values support -- [Xlang Serialization](xlang.md) - Scala 3 generated models and macro-derived xlang serializers +- [Basic Serialization](core-api.md) - Models, APIs, and interoperability in the default xlang mode - [gRPC Support](../../grpc/scala.md) - Scala 3 generated gRPC service companions diff --git a/docs/object-serialization/scala/native.md b/docs/object-serialization/scala/native.md index e3992c8773..62505e7247 100644 --- a/docs/object-serialization/scala/native.md +++ b/docs/object-serialization/scala/native.md @@ -21,7 +21,7 @@ license: | This page covers serialization of Scala-specific JVM types in native mode. For cross-language Scala models, use the xlang path described in -[Xlang Serialization](xlang.md). +[Cross-Language Interoperability](core-api.md#cross-language-interoperability). When compatible mode is enabled, Scala readers use the JVM compatible-read rules for selected scalar field type changes. A matched field can read between `Boolean`, `String`, numeric scalars, diff --git a/docs/object-serialization/scala/schema-metadata.md b/docs/object-serialization/scala/schema-metadata.md index 1d04dce8ab..93d552463a 100644 --- a/docs/object-serialization/scala/schema-metadata.md +++ b/docs/object-serialization/scala/schema-metadata.md @@ -119,6 +119,6 @@ reflection is not the source of truth for generated Scala metadata. ## Related Topics -- [Xlang Serialization](xlang.md) +- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - [Configuration](configuration.md) - [Default Values](default-values.md) diff --git a/docs/object-serialization/swift/core-api.md b/docs/object-serialization/swift/core-api.md index bdff028dde..4146b98e00 100644 --- a/docs/object-serialization/swift/core-api.md +++ b/docs/object-serialization/swift/core-api.md @@ -1,6 +1,6 @@ --- title: Basic Serialization -sidebar_position: 3 +sidebar_position: 1 id: core-api license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -19,7 +19,7 @@ license: | limitations under the License. --- -This page covers object graph serialization and core API usage in Swift. +This page covers object graph serialization and core API usage in the default xlang mode for Fory Swift. ## Object Graph Serialization @@ -174,3 +174,173 @@ Children that use a separate serializer compose with: protocol roots and dynamic values nested in carriers use explicit `with:` selection. See [Polymorphism and Dynamic Types](polymorphism.md). + +## Cross-Language Interoperability + +The default xlang format is shared by all Fory runtimes. The following sections cover its cross-language type mapping, type identity, and interoperability requirements. + +Fory Swift can exchange payloads with other Fory implementations using the xlang protocol. + +### Recommended Xlang Configuration + +```swift +let fory = Fory() +``` + +### Register Types with Shared Identity + +#### ID-based registration + +```swift +@ForyStruct +struct Order { + var id: Int64 = 0 + var amount: Double = 0 +} + +let fory = Fory() +try fory.register(Order.self, id: 100) +``` + +#### Name-based registration + +```swift +try fory.register(Order.self, name: "com.example.Order") +``` + +### Xlang Rules + +- Keep type registration mapping consistent across languages +- Keep compatible mode enabled when independently evolving schemas. Swift enables it by default. +- Register all user-defined concrete targets used by dynamic fields and + application protocol values +- Use an external structural serializer, a separate custom serializer, or one + intentional retroactive self-target conformance for a type owned by another + module + +### Lists and Dense Arrays + +Swift `Array` fields map to Fory `list` unless field metadata explicitly +requests dense `array`. Use `array` only for one-dimensional bool or +numeric data. + +| Fory schema | Swift field metadata sketch | +| ----------------- | -------------------------------------------------------- | +| `list` | `@ListField(element: .int32()) var ids: [Int32]` | +| `array` | `@ArrayField(element: .bool) var flags: [Bool]` | +| `array` | `@ArrayField(element: .int8) var values: [Int8]` | +| `array` | `@ArrayField(element: .int16) var values: [Int16]` | +| `array` | `@ArrayField(element: .int32()) var values: [Int32]` | +| `array` | `@ArrayField(element: .int64()) var values: [Int64]` | +| `array` | `@ArrayField(element: .uint8) var values: [UInt8]` | +| `array` | `@ArrayField(element: .uint16) var values: [UInt16]` | +| `array` | `@ArrayField(element: .uint32()) var values: [UInt32]` | +| `array` | `@ArrayField(element: .uint64()) var values: [UInt64]` | +| `array` | `@ArrayField(element: .float16) var values: [Float16]` | +| `array` | `@ArrayField(element: .bfloat16) var values: [BFloat16]` | +| `array` | `@ArrayField(element: .float32) var values: [Float]` | +| `array` | `@ArrayField(element: .float64) var values: [Double]` | + +An array that uses a separate element serializer still uses normal list +encoding. Use `@ArrayField` only for supported dense bool or numeric arrays. + +### External Targets + +External structural serializers produce the same xlang STRUCT, ENUM, or UNION +schema and value bytes as an equivalent ordinary Swift model: + +```swift +@ForyStruct(target: ThirdParty.Order.self) +struct OrderSerializer { + var id: Int64 + var amount: Double +} + +try fory.register(OrderSerializer.self, id: 100) +``` + +Use `.with(...)` in field metadata and `with:` at a root. See +[External-Type Serialization](external-types.md). + +That explicit selection is required because the structural serializer is a +separate declaration. An external type with one intentional retroactive +`Target == Self` conformance instead uses ordinary roots, fields, and carriers. + +Swift has no native serialization mode. A known `@ForyUnion` case has zero or +one associated value. Use a struct payload for a union alternative with +multiple logical fields. + +### Swift IDL Workflow + +Generate Swift models directly from Fory IDL/Proto/FBS inputs: + +```bash +foryc schema.fdl --swift_out ./Sources/Generated +``` + +Generated Swift code includes: + +- `@ForyStruct`, `@ForyEnum`, `@ForyUnion`, and field/case metadata +- Tagged union enums (associated-value enum cases) +- `ForyModule.install(_:)` helpers with transitive import installation +- `toBytes` / `fromBytes` helpers on generated types + +Install the generated module before xlang serialization: + +```swift +let fory = Fory(ref: true) +try Addressbook.ForyModule.install(fory) + +let payload = try fory.serialize(book) +let decoded: Addressbook.AddressBook = try fory.deserialize(payload) +``` + +#### Run Swift IDL Integration Tests + +```bash +cd integration_tests/idl_tests +./run_swift_tests.sh +``` + +This runs Swift roundtrip matrix tests and Java peer roundtrip checks (`IDL_PEER_LANG=swift`). + +### Debugging Xlang Tests + +Enable debug output when running xlang tests: + +```bash +ENABLE_FORY_DEBUG_OUTPUT=1 FORY_SWIFT_JAVA_CI=1 mvn -T16 test -Dtest=org.apache.fory.xlang.SwiftXlangTest +``` + +### First round trip + +```swift +import Fory + +@ForyStruct +struct Person: Equatable { + var name: String = "" + var age: Int32 = 0 +} + +let fory = Fory() +fory.register(Person.self, id: 1) + +let person = Person(name: "chaokunyang", age: 28) +let data = try fory.serialize(person) +let result: Person = try fory.deserialize(data) + +print("\(result.name) \(result.age)") +``` + +For more cross-language rules and examples, see: + +- [Cross-Language Serialization Guide](../xlang/index.md) +- [Java Guide](../java/index.md) +- [Python Guide](../python/index.md) +- [Dart Guide](../dart/index.md) +- [Go Guide](../go/index.md) +- [Rust Guide](../rust/index.md) +- [C++ Guide](../cpp/index.md) +- [C# Guide](../csharp/index.md) +- [Swift Guide](../swift/index.md) diff --git a/docs/object-serialization/swift/index.md b/docs/object-serialization/swift/index.md index 718bea7860..0eb165e3fb 100644 --- a/docs/object-serialization/swift/index.md +++ b/docs/object-serialization/swift/index.md @@ -53,7 +53,6 @@ targets: [ - [Configuration](configuration.md) - [Basic Serialization](core-api.md) -- [Xlang Serialization](xlang.md) - [Schema Metadata](schema-metadata.md) - [Type Registration](type-registration.md) - [External-Type Serialization](external-types.md) diff --git a/docs/object-serialization/swift/xlang.md b/docs/object-serialization/swift/xlang.md deleted file mode 100644 index b49b2e798e..0000000000 --- a/docs/object-serialization/swift/xlang.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -title: Xlang Serialization -sidebar_position: 1 -id: xlang -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Fory Swift can exchange payloads with other Fory implementations using the xlang protocol. - -## Recommended Xlang Configuration - -```swift -let fory = Fory() -``` - -## Register Types with Shared Identity - -### ID-based registration - -```swift -@ForyStruct -struct Order { - var id: Int64 = 0 - var amount: Double = 0 -} - -let fory = Fory() -try fory.register(Order.self, id: 100) -``` - -### Name-based registration - -```swift -try fory.register(Order.self, name: "com.example.Order") -``` - -## Xlang Rules - -- Keep type registration mapping consistent across languages -- Keep compatible mode enabled when independently evolving schemas. Swift enables it by default. -- Register all user-defined concrete targets used by dynamic fields and - application protocol values -- Use an external structural serializer, a separate custom serializer, or one - intentional retroactive self-target conformance for a type owned by another - module - -## Lists and Dense Arrays - -Swift `Array` fields map to Fory `list` unless field metadata explicitly -requests dense `array`. Use `array` only for one-dimensional bool or -numeric data. - -| Fory schema | Swift field metadata sketch | -| ----------------- | -------------------------------------------------------- | -| `list` | `@ListField(element: .int32()) var ids: [Int32]` | -| `array` | `@ArrayField(element: .bool) var flags: [Bool]` | -| `array` | `@ArrayField(element: .int8) var values: [Int8]` | -| `array` | `@ArrayField(element: .int16) var values: [Int16]` | -| `array` | `@ArrayField(element: .int32()) var values: [Int32]` | -| `array` | `@ArrayField(element: .int64()) var values: [Int64]` | -| `array` | `@ArrayField(element: .uint8) var values: [UInt8]` | -| `array` | `@ArrayField(element: .uint16) var values: [UInt16]` | -| `array` | `@ArrayField(element: .uint32()) var values: [UInt32]` | -| `array` | `@ArrayField(element: .uint64()) var values: [UInt64]` | -| `array` | `@ArrayField(element: .float16) var values: [Float16]` | -| `array` | `@ArrayField(element: .bfloat16) var values: [BFloat16]` | -| `array` | `@ArrayField(element: .float32) var values: [Float]` | -| `array` | `@ArrayField(element: .float64) var values: [Double]` | - -An array that uses a separate element serializer still uses normal list -encoding. Use `@ArrayField` only for supported dense bool or numeric arrays. - -## External Targets - -External structural serializers produce the same xlang STRUCT, ENUM, or UNION -schema and value bytes as an equivalent ordinary Swift model: - -```swift -@ForyStruct(target: ThirdParty.Order.self) -struct OrderSerializer { - var id: Int64 - var amount: Double -} - -try fory.register(OrderSerializer.self, id: 100) -``` - -Use `.with(...)` in field metadata and `with:` at a root. See -[External-Type Serialization](external-types.md). - -That explicit selection is required because the structural serializer is a -separate declaration. An external type with one intentional retroactive -`Target == Self` conformance instead uses ordinary roots, fields, and carriers. - -Swift has no native serialization mode. A known `@ForyUnion` case has zero or -one associated value. Use a struct payload for a union alternative with -multiple logical fields. - -## Swift IDL Workflow - -Generate Swift models directly from Fory IDL/Proto/FBS inputs: - -```bash -foryc schema.fdl --swift_out ./Sources/Generated -``` - -Generated Swift code includes: - -- `@ForyStruct`, `@ForyEnum`, `@ForyUnion`, and field/case metadata -- Tagged union enums (associated-value enum cases) -- `ForyModule.install(_:)` helpers with transitive import installation -- `toBytes` / `fromBytes` helpers on generated types - -Install the generated module before xlang serialization: - -```swift -let fory = Fory(ref: true) -try Addressbook.ForyModule.install(fory) - -let payload = try fory.serialize(book) -let decoded: Addressbook.AddressBook = try fory.deserialize(payload) -``` - -### Run Swift IDL Integration Tests - -```bash -cd integration_tests/idl_tests -./run_swift_tests.sh -``` - -This runs Swift roundtrip matrix tests and Java peer roundtrip checks (`IDL_PEER_LANG=swift`). - -## Debugging Xlang Tests - -Enable debug output when running xlang tests: - -```bash -ENABLE_FORY_DEBUG_OUTPUT=1 FORY_SWIFT_JAVA_CI=1 mvn -T16 test -Dtest=org.apache.fory.xlang.SwiftXlangTest -``` - -## First round trip - -```swift -import Fory - -@ForyStruct -struct Person: Equatable { - var name: String = "" - var age: Int32 = 0 -} - -let fory = Fory() -fory.register(Person.self, id: 1) - -let person = Person(name: "chaokunyang", age: 28) -let data = try fory.serialize(person) -let result: Person = try fory.deserialize(data) - -print("\(result.name) \(result.age)") -``` - -For more cross-language rules and examples, see: - -- [Cross-Language Serialization Guide](../xlang/index.md) -- [Java Guide](../java/index.md) -- [Python Guide](../python/index.md) -- [Dart Guide](../dart/index.md) -- [Go Guide](../go/index.md) -- [Rust Guide](../rust/index.md) -- [C++ Guide](../cpp/index.md) -- [C# Guide](../csharp/index.md) -- [Swift Guide](../swift/index.md) diff --git a/docs/object-serialization/xlang/index.md b/docs/object-serialization/xlang/index.md index 2777ce9d59..103d942305 100644 --- a/docs/object-serialization/xlang/index.md +++ b/docs/object-serialization/xlang/index.md @@ -166,17 +166,17 @@ This generates native language types with consistent field/type mappings across For language-specific details and API reference: -- [Java Xlang Serialization Guide](../java/xlang.md) -- [Python Xlang Serialization Guide](../python/xlang.md) -- [C++ Xlang Serialization Guide](../cpp/xlang.md) -- [Go Xlang Serialization Guide](../go/xlang.md) -- [Rust Xlang Serialization Guide](../rust/xlang.md) -- [JavaScript/TypeScript Xlang Serialization Guide](../javascript/xlang.md) -- [C# Xlang Serialization Guide](../csharp/xlang.md) -- [Swift Xlang Serialization Guide](../swift/xlang.md) -- [Dart Xlang Serialization Guide](../dart/xlang.md) -- [Scala Xlang Serialization Guide](../scala/xlang.md) -- [Kotlin Xlang Serialization Guide](../kotlin/xlang.md) +- [Java Interoperability Guide](../java/core-api.md#cross-language-interoperability) +- [Python Interoperability Guide](../python/core-api.md#cross-language-interoperability) +- [C++ Interoperability Guide](../cpp/core-api.md#cross-language-interoperability) +- [Go Interoperability Guide](../go/core-api.md#cross-language-interoperability) +- [Rust Interoperability Guide](../rust/core-api.md#cross-language-interoperability) +- [JavaScript/TypeScript Interoperability Guide](../javascript/core-api.md#cross-language-interoperability) +- [C# Interoperability Guide](../csharp/core-api.md#cross-language-interoperability) +- [Swift Interoperability Guide](../swift/core-api.md#cross-language-interoperability) +- [Dart Interoperability Guide](../dart/core-api.md#cross-language-interoperability) +- [Scala Interoperability Guide](../scala/core-api.md#cross-language-interoperability) +- [Kotlin Interoperability Guide](../kotlin/core-api.md#cross-language-interoperability) ## Specifications diff --git a/docs/object-serialization/xlang/type-identity.md b/docs/object-serialization/xlang/type-identity.md index 844b7e4b52..7eaf8017f9 100644 --- a/docs/object-serialization/xlang/type-identity.md +++ b/docs/object-serialization/xlang/type-identity.md @@ -290,9 +290,9 @@ easier to coordinate across independently deployed services. Do not mix an ID registration on one peer with a name registration on another. Use the selected runtime guide for the exact registration API: -[Java](../java/xlang.md), [Python](../python/xlang.md), -[C++](../cpp/xlang.md), [Go](../go/xlang.md), -[Rust](../rust/xlang.md), [JavaScript](../javascript/xlang.md), -[C#](../csharp/xlang.md), [Swift](../swift/xlang.md), -[Dart](../dart/xlang.md), [Scala](../scala/xlang.md), and -[Kotlin](../kotlin/xlang.md). +[Java](../java/core-api.md#cross-language-interoperability), [Python](../python/core-api.md#cross-language-interoperability), +[C++](../cpp/core-api.md#cross-language-interoperability), [Go](../go/core-api.md#cross-language-interoperability), +[Rust](../rust/core-api.md#cross-language-interoperability), [JavaScript](../javascript/core-api.md#cross-language-interoperability), +[C#](../csharp/core-api.md#cross-language-interoperability), [Swift](../swift/core-api.md#cross-language-interoperability), +[Dart](../dart/core-api.md#cross-language-interoperability), [Scala](../scala/core-api.md#cross-language-interoperability), and +[Kotlin](../kotlin/core-api.md#cross-language-interoperability). diff --git a/docs/row-format/java.md b/docs/row-format/java.md index 8679cadd8c..e461974869 100644 --- a/docs/row-format/java.md +++ b/docs/row-format/java.md @@ -279,6 +279,6 @@ Parent decoded = encoder.fromRow(row); ## Related Topics -- [Xlang Serialization](../object-serialization/java/xlang.md) - xlang mode +- [Cross-Language Interoperability](../object-serialization/java/core-api.md#cross-language-interoperability) - xlang mode - [Java Advanced Features](../object-serialization/java/advanced-features.md) - Zero-copy object serialization - [Row Format Specification](https://fory.apache.org/docs/specification/row_format_spec) - Protocol details diff --git a/docs/row-format/python.md b/docs/row-format/python.md index 0963df2fb1..9e069b086f 100644 --- a/docs/row-format/python.md +++ b/docs/row-format/python.md @@ -195,6 +195,6 @@ pip install pyfory[format] ## Related Topics -- [Xlang Serialization](../object-serialization/python/xlang.md) - xlang mode +- [Cross-Language Interoperability](../object-serialization/python/core-api.md#cross-language-interoperability) - xlang mode - [Basic Serialization](../object-serialization/python/core-api.md) - Object serialization - [Row Format Specification](https://fory.apache.org/docs/specification/row_format_spec) - Protocol details diff --git a/docs/start/cpp.md b/docs/start/cpp.md index 7e48ce01ab..52da0aac24 100644 --- a/docs/start/cpp.md +++ b/docs/start/cpp.md @@ -95,7 +95,7 @@ cmake --build build --parallel See [C++ Object Serialization](../object-serialization/cpp/index.md) for Bazel, Windows, error handling, and thread-safe instances; then continue to -[xlang](../object-serialization/cpp/xlang.md) or +[xlang](../object-serialization/cpp/core-api.md#cross-language-interoperability) or [native mode](../object-serialization/cpp/native.md). ## Other Capabilities diff --git a/docs/start/csharp.md b/docs/start/csharp.md index 91ebefcb2f..45d4b13fd2 100644 --- a/docs/start/csharp.md +++ b/docs/start/csharp.md @@ -71,7 +71,7 @@ dotnet run C# uses xlang mode. Continue with [C# Object Serialization](../object-serialization/csharp/index.md), -[xlang types](../object-serialization/csharp/xlang.md), +[xlang types](../object-serialization/csharp/core-api.md#cross-language-interoperability), [configuration](../object-serialization/csharp/configuration.md), and [schema evolution](../object-serialization/csharp/schema-evolution.md). diff --git a/docs/start/go.md b/docs/start/go.md index dc545e8f86..06cd4310ef 100644 --- a/docs/start/go.md +++ b/docs/start/go.md @@ -81,7 +81,7 @@ func main() { go run . ``` -Use [xlang mode](../object-serialization/go/xlang.md) for cross-language data +Use [xlang mode](../object-serialization/go/core-api.md#cross-language-interoperability) for cross-language data and [native mode](../object-serialization/go/native.md) for Go-only data. Continue with [Go Object Serialization](../object-serialization/go/index.md), [configuration](../object-serialization/go/configuration.md), and diff --git a/docs/start/java.md b/docs/start/java.md index 1bf4ecd4c4..dad3c1e079 100644 --- a/docs/start/java.md +++ b/docs/start/java.md @@ -87,7 +87,7 @@ Reuse a `Fory` instance within one thread instead of rebuilding it for every value. `Fory` is not thread-safe; use `ThreadSafeFory` for shared concurrent access. Continue with [Java Object Serialization](../object-serialization/java/index.md), -[xlang mode](../object-serialization/java/xlang.md), +[xlang mode](../object-serialization/java/core-api.md#cross-language-interoperability), [native mode](../object-serialization/java/native.md), or [configuration](../object-serialization/java/configuration.md). diff --git a/docs/start/javascript.md b/docs/start/javascript.md index 693b78632c..fd1758fa6b 100644 --- a/docs/start/javascript.md +++ b/docs/start/javascript.md @@ -65,7 +65,7 @@ node example.cjs JavaScript uses xlang mode. Continue with [JavaScript/TypeScript Object Serialization](../object-serialization/javascript/index.md), -[xlang types](../object-serialization/javascript/xlang.md), +[xlang types](../object-serialization/javascript/core-api.md#cross-language-interoperability), [configuration](../object-serialization/javascript/configuration.md), and [schema evolution](../object-serialization/javascript/schema-evolution.md). diff --git a/docs/start/kotlin.md b/docs/start/kotlin.md index ad9768d65d..efd832a98d 100644 --- a/docs/start/kotlin.md +++ b/docs/start/kotlin.md @@ -70,7 +70,7 @@ If the project applies Gradle's `application` plugin, run its application task: Use xlang mode for data shared with other Fory runtimes or native mode for Kotlin/JVM-only data. Continue with [Kotlin Object Serialization](../object-serialization/kotlin/index.md), -[xlang](../object-serialization/kotlin/xlang.md), or +[xlang](../object-serialization/kotlin/core-api.md#cross-language-interoperability), or [native mode](../object-serialization/kotlin/native.md). ## Other Capabilities diff --git a/docs/start/python.md b/docs/start/python.md index 4f3d03a9d2..952f16c731 100644 --- a/docs/start/python.md +++ b/docs/start/python.md @@ -60,7 +60,7 @@ decoded = fory.deserialize(data) print(decoded) ``` -Use [xlang mode](../object-serialization/python/xlang.md) for cross-language +Use [xlang mode](../object-serialization/python/core-api.md#cross-language-interoperability) for cross-language data. Use [native mode](../object-serialization/python/native.md) for Python-only objects, including Python callables and serialization hooks. Continue with the [Python guide](../object-serialization/python/index.md), diff --git a/docs/start/rust.md b/docs/start/rust.md index 30f4303c74..e4924168fe 100644 --- a/docs/start/rust.md +++ b/docs/start/rust.md @@ -63,7 +63,7 @@ fn main() -> Result<(), Error> { } ``` -Use [xlang mode](../object-serialization/rust/xlang.md) for cross-language data +Use [xlang mode](../object-serialization/rust/core-api.md#cross-language-interoperability) for cross-language data and [native mode](../object-serialization/rust/native.md) for Rust-only data. Continue with [Rust Object Serialization](../object-serialization/rust/index.md), [configuration](../object-serialization/rust/configuration.md), and diff --git a/docs/start/scala.md b/docs/start/scala.md index 974038f1a5..3600f5e2a2 100644 --- a/docs/start/scala.md +++ b/docs/start/scala.md @@ -69,7 +69,7 @@ sbt run Use xlang mode for data shared with other Fory runtimes or native mode for Scala/JVM-only data. Continue with [Scala Object Serialization](../object-serialization/scala/index.md), -[xlang](../object-serialization/scala/xlang.md), or +[xlang](../object-serialization/scala/core-api.md#cross-language-interoperability), or [native mode](../object-serialization/scala/native.md). ## Other Capabilities diff --git a/docs/start/swift.md b/docs/start/swift.md index 4c086bdd89..61b965c792 100644 --- a/docs/start/swift.md +++ b/docs/start/swift.md @@ -77,7 +77,7 @@ swift run Swift uses xlang mode. Continue with [Swift Object Serialization](../object-serialization/swift/index.md), -[xlang types](../object-serialization/swift/xlang.md), +[xlang types](../object-serialization/swift/core-api.md#cross-language-interoperability), [configuration](../object-serialization/swift/configuration.md), and [schema evolution](../object-serialization/swift/schema-evolution.md). diff --git a/go/fory/README.md b/go/fory/README.md index d464d5994f..cf36760702 100644 --- a/go/fory/README.md +++ b/go/fory/README.md @@ -144,6 +144,6 @@ Topics covered: - [Supported Types](https://fory.apache.org/docs/object-serialization/go/supported-types) - Complete type support reference - [References](https://fory.apache.org/docs/object-serialization/go/references) - Circular references and shared objects - [Schema Evolution](https://fory.apache.org/docs/object-serialization/go/schema-evolution) - Forward/backward compatibility -- [Xlang Serialization](https://fory.apache.org/docs/object-serialization/go/xlang) - Multi-language serialization +- [Basic Serialization](https://fory.apache.org/docs/object-serialization/go/core-api) - Default xlang APIs and interoperability - [Thread Safety](https://fory.apache.org/docs/object-serialization/go/thread-safety) - Concurrent usage patterns - [Troubleshooting](https://fory.apache.org/docs/object-serialization/go/troubleshooting) - Common issues and solutions diff --git a/javascript/README.md b/javascript/README.md index 6aa8f0e359..0a2fb277b6 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -114,7 +114,7 @@ Readers can skip unknown fields and tolerate missing ones, supporting rolling up Full documentation is available at [fory.apache.org](https://fory.apache.org): - [JavaScript Serialization Guide](https://fory.apache.org/docs/object-serialization/javascript) -- [Xlang Serialization](https://fory.apache.org/docs/object-serialization/javascript/xlang) +- [Basic Serialization](https://fory.apache.org/docs/object-serialization/javascript/core-api) - [Supported Types](https://fory.apache.org/docs/object-serialization/javascript/supported-types) - [Schema Evolution](https://fory.apache.org/docs/object-serialization/javascript/schema-evolution) - [Xlang Serialization Spec](https://fory.apache.org/docs/specification/xlang_serialization_spec) diff --git a/javascript/packages/core/README.md b/javascript/packages/core/README.md index 4f7b569d4b..b861c07020 100644 --- a/javascript/packages/core/README.md +++ b/javascript/packages/core/README.md @@ -141,7 +141,7 @@ const bytes = serialize({ id: 1n, content: "hello from JavaScript" }); ## Documentation - [JavaScript Serialization Guide](https://fory.apache.org/docs/object-serialization/javascript) -- [Xlang Serialization](https://fory.apache.org/docs/object-serialization/javascript/xlang) +- [Basic Serialization](https://fory.apache.org/docs/object-serialization/javascript/core-api) - [Supported Types](https://fory.apache.org/docs/object-serialization/javascript/supported-types) - [Schema Evolution](https://fory.apache.org/docs/object-serialization/javascript/schema-evolution) - [Xlang Serialization Spec](https://fory.apache.org/docs/specification/xlang_serialization_spec) diff --git a/swift/README.md b/swift/README.md index e03477e110..079ae4e3aa 100644 --- a/swift/README.md +++ b/swift/README.md @@ -486,7 +486,7 @@ ENABLE_FORY_DEBUG_OUTPUT=1 FORY_SWIFT_JAVA_CI=1 mvn -T16 test -Dtest=org.apache. - [Schema Evolution](../docs/object-serialization/swift/schema-evolution.md) - [External-Type Serialization](../docs/object-serialization/swift/external-types.md) - [Custom Serializers](../docs/object-serialization/swift/custom-serializers.md) -- [Xlang Serialization](../docs/object-serialization/swift/xlang.md) +- [Basic Serialization](../docs/object-serialization/swift/core-api.md) - [Xlang Specification](../docs/specification/xlang_serialization_spec.md) - [Xlang Type Mapping](../docs/specification/xlang_type_mapping.md) From 441131a1ae3f258b81765e50be4e1d88879198c1 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 11:24:21 +0800 Subject: [PATCH 10/15] docs: flatten cross-language interoperability guide --- README.md | 16 +- docs/introduction/choose-a-format.md | 12 +- docs/object-serialization/csharp/core-api.md | 2 +- docs/object-serialization/csharp/index.md | 2 +- .../csharp/supported-types.md | 2 +- docs/object-serialization/dart/core-api.md | 2 +- docs/object-serialization/dart/index.md | 2 +- docs/object-serialization/index.md | 8 +- docs/object-serialization/java/core-api.md | 2 +- docs/object-serialization/native.md | 2 +- docs/object-serialization/swift/core-api.md | 2 +- docs/object-serialization/xlang.md | 361 ++++++++++++++++++ .../xlang/_category_.json | 6 - docs/object-serialization/xlang/index.md | 192 ---------- .../object-serialization/xlang/nullability.md | 267 ------------- .../xlang/polymorphism.md | 33 -- docs/object-serialization/xlang/references.md | 303 --------------- .../xlang/schema-evolution.md | 33 -- .../xlang/troubleshooting.md | 321 ---------------- .../xlang/type-identity.md | 298 --------------- .../object-serialization/xlang/type-system.md | 42 -- docs/object-serialization/xlang/zero-copy.md | 203 ---------- 22 files changed, 387 insertions(+), 1724 deletions(-) create mode 100644 docs/object-serialization/xlang.md delete mode 100644 docs/object-serialization/xlang/_category_.json delete mode 100644 docs/object-serialization/xlang/index.md delete mode 100644 docs/object-serialization/xlang/nullability.md delete mode 100644 docs/object-serialization/xlang/polymorphism.md delete mode 100644 docs/object-serialization/xlang/references.md delete mode 100644 docs/object-serialization/xlang/schema-evolution.md delete mode 100644 docs/object-serialization/xlang/troubleshooting.md delete mode 100644 docs/object-serialization/xlang/type-identity.md delete mode 100644 docs/object-serialization/xlang/type-system.md delete mode 100644 docs/object-serialization/xlang/zero-copy.md diff --git a/README.md b/README.md index 6ab0c92a3c..46b42df8ad 100644 --- a/README.md +++ b/README.md @@ -312,12 +312,12 @@ Snapshots for Java, Scala, and Kotlin are available from ## Choose a Serialization Format -| Format | Use it when | Start here | -| ------------- | ------------------------------------------------------------- | ------------------------------------------------------- | -| Xlang binary | Data crosses language boundaries | [Xlang guide](docs/object-serialization/xlang/index.md) | -| Native binary | Producer and consumer are in the same runtime family | [Native guide](docs/object-serialization/native.md) | -| Row format | You need random field access or analytics-style partial reads | [Row Format](docs/row-format/index.md) | -| Fory JSON | Java applications need high-performance standard JSON | [Fory JSON](java/fory-json/README.md) | +| Format | Use it when | Start here | +| ---------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------- | +| Xlang binary (default) | Data crosses language boundaries | [Cross-language interoperability](docs/object-serialization/xlang.md) | +| Native binary | Producer and consumer are in the same runtime family | [Native guide](docs/object-serialization/native.md) | +| Row format | You need random field access or analytics-style partial reads | [Row Format](docs/row-format/index.md) | +| Fory JSON | Java applications need high-performance standard JSON | [Fory JSON](java/fory-json/README.md) | For Java, Scala, Kotlin, Python, C++, Go, and Rust, use native mode for same-language traffic. It avoids xlang's cross-language type mapping and @@ -616,7 +616,7 @@ fun main() { ``` For shared/circular references, polymorphism, numeric IDs versus names, and -type-mapping rules, see the [cross-language guide](docs/object-serialization/xlang) and +type-mapping rules, see the [cross-language guide](docs/object-serialization/xlang.md) and [type mapping specification](docs/specification/xlang_type_mapping.md). ## Native Serialization @@ -909,7 +909,7 @@ security, and platform guides. | Dart | [docs/object-serialization/dart](docs/object-serialization/dart) | [View](https://fory.apache.org/docs/object-serialization/dart/) | | Scala | [docs/object-serialization/scala](docs/object-serialization/scala) | [View](https://fory.apache.org/docs/object-serialization/scala/) | | Kotlin | [docs/object-serialization/kotlin](docs/object-serialization/kotlin) | [View](https://fory.apache.org/docs/object-serialization/kotlin/) | -| Cross-language xlang | [docs/object-serialization/xlang](docs/object-serialization/xlang) | [View](https://fory.apache.org/docs/object-serialization/xlang/) | +| Cross-language xlang | [docs/object-serialization/xlang.md](docs/object-serialization/xlang.md) | [View](https://fory.apache.org/docs/object-serialization/xlang) | | Schema IDL/compiler | [docs/compiler](docs/compiler) | [View](https://fory.apache.org/docs/compiler/) | | GraalVM native image | [docs/object-serialization/java/graalvm.md](docs/object-serialization/java/graalvm.md) | [View](https://fory.apache.org/docs/object-serialization/java/graalvm) | | Android | [docs/object-serialization/java/android.md](docs/object-serialization/java/android.md) | [View](https://fory.apache.org/docs/object-serialization/java/android) | diff --git a/docs/introduction/choose-a-format.md b/docs/introduction/choose-a-format.md index 75cee3efce..240590d1c5 100644 --- a/docs/introduction/choose-a-format.md +++ b/docs/introduction/choose-a-format.md @@ -19,12 +19,12 @@ license: | limitations under the License. --- -| Format or mode | Data model | Use it when | Interoperability | Start here | -| -------------- | ------------------------------------ | ------------------------------------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------- | -| Xlang binary | Portable object graphs | Data crosses language boundaries | Shared wire format across supported runtimes | [Cross-language guide](../object-serialization/xlang/index.md) | -| Native binary | Runtime-native object graphs | Producer and consumer use the same language | Same runtime family only | [Object Serialization](../object-serialization/index.md) | -| Row Format | Random-access binary rows | You need random field access or analytics-style partial reads | Standard Row is shared by Java, Python, C++, and Rust; Compact is Java-only | [Row Format guide](../row-format/index.md) | -| Fory JSON | Standard JSON mapped to Java objects | Java applications need high-performance standard JSON | Standard JSON text | [Fory JSON guide](../json/index.md) | +| Format or mode | Data model | Use it when | Interoperability | Start here | +| -------------- | ------------------------------------ | ------------------------------------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------- | +| Xlang binary | Portable object graphs | Data crosses language boundaries | Shared wire format across supported runtimes | [Cross-language guide](../object-serialization/xlang.md) | +| Native binary | Runtime-native object graphs | Producer and consumer use the same language | Same runtime family only | [Object Serialization](../object-serialization/index.md) | +| Row Format | Random-access binary rows | You need random field access or analytics-style partial reads | Standard Row is shared by Java, Python, C++, and Rust; Compact is Java-only | [Row Format guide](../row-format/index.md) | +| Fory JSON | Standard JSON mapped to Java objects | Java applications need high-performance standard JSON | Standard JSON text | [Fory JSON guide](../json/index.md) | Xlang and native are sibling modes of Object Serialization. Use them when the receiver needs to reconstruct an object graph. Row Format and Fory JSON are separate formats, not additional Object diff --git a/docs/object-serialization/csharp/core-api.md b/docs/object-serialization/csharp/core-api.md index 561ee3c42a..7f52d8fffa 100644 --- a/docs/object-serialization/csharp/core-api.md +++ b/docs/object-serialization/csharp/core-api.md @@ -238,7 +238,7 @@ value = fory.deserialize(payload_from_csharp) ### Type Mapping Reference -See [xlang guide](../xlang/index.md) for complete mapping. +See [cross-language interoperability guide](../xlang.md) for complete mapping. For reduced-precision numeric payloads, use `Half` / `Half[]` or `List` for xlang `float16`, and `BFloat16` / `BFloat16[]` or `List` for xlang `bfloat16`. diff --git a/docs/object-serialization/csharp/index.md b/docs/object-serialization/csharp/index.md index 20459b8696..f38f1175b4 100644 --- a/docs/object-serialization/csharp/index.md +++ b/docs/object-serialization/csharp/index.md @@ -104,5 +104,5 @@ User decoded = fory.Deserialize(payload); ## Related Resources - [Xlang serialization specification](../../specification/xlang_serialization_spec.md) -- [Xlang guide](../xlang/index.md) +- [Cross-language guide](../xlang.md) - [C# source directory](https://github.com/apache/fory/tree/main/csharp) diff --git a/docs/object-serialization/csharp/supported-types.md b/docs/object-serialization/csharp/supported-types.md index c0e3a870bb..8f32e20925 100644 --- a/docs/object-serialization/csharp/supported-types.md +++ b/docs/object-serialization/csharp/supported-types.md @@ -107,7 +107,7 @@ Dynamic object payloads via `Serialize` / `Deserialize` suppor ## Notes - User-defined types should be registered explicitly. -- For cross-language usage, follow the [xlang guide](../xlang/index.md). +- For cross-language usage, follow the [cross-language interoperability guide](../xlang.md). ## Related Topics diff --git a/docs/object-serialization/dart/core-api.md b/docs/object-serialization/dart/core-api.md index 6828df0213..dddb46beda 100644 --- a/docs/object-serialization/dart/core-api.md +++ b/docs/object-serialization/dart/core-api.md @@ -382,7 +382,7 @@ dart test - [Type Registration](type-registration.md) - [External-Type Serialization](external-types.md) - [Schema Evolution](schema-evolution.md) -- [Xlang guide](../xlang/index.md) +- [Cross-language guide](../xlang.md) ## Related Topics diff --git a/docs/object-serialization/dart/index.md b/docs/object-serialization/dart/index.md index 1880d604e6..b1132bebff 100644 --- a/docs/object-serialization/dart/index.md +++ b/docs/object-serialization/dart/index.md @@ -157,5 +157,5 @@ constructors, mixins, and field inclusion options. - [Xlang serialization specification](../../specification/xlang_serialization_spec.md) - [Xlang implementation guide](../../specification/xlang_implementation_guide.md) -- [Xlang guide](../xlang/index.md) +- [Cross-language guide](../xlang.md) - [Dart implementation source directory](https://github.com/apache/fory/tree/main/dart) diff --git a/docs/object-serialization/index.md b/docs/object-serialization/index.md index 3cd7539d19..bd1dbffa32 100644 --- a/docs/object-serialization/index.md +++ b/docs/object-serialization/index.md @@ -24,10 +24,10 @@ collections, polymorphic values, and optional shared references. ## Choose a mode -| Mode | Use it when | Start here | -| ------ | ---------------------------------------------------- | ---------------------------- | -| Xlang | Bytes cross runtime boundaries | [Xlang mode](xlang/index.md) | -| Native | Every writer and reader uses the same runtime family | [Native mode](native.md) | +| Mode | Use it when | Start here | +| --------------- | ---------------------------------------------------- | ------------------------------------------- | +| Xlang (default) | Bytes cross runtime boundaries | [Cross-language interoperability](xlang.md) | +| Native | Every writer and reader uses the same runtime family | [Native serialization](native.md) | Xlang and native are the only object-serialization modes. Row Format is a random-access analytical representation, and Fory JSON is a Java JSON codec; use the diff --git a/docs/object-serialization/java/core-api.md b/docs/object-serialization/java/core-api.md index 6d2fd6a03e..b696beb941 100644 --- a/docs/object-serialization/java/core-api.md +++ b/docs/object-serialization/java/core-api.md @@ -91,7 +91,7 @@ User decoded = fory.deserialize(bytes, User.class); ``` When xlang bytes cross languages, every peer must register the same type identity and compatible -field metadata. The shared rules live in [Xlang](../xlang/index.md), and the Java-specific +field metadata. The shared rules live in [Cross-Language Interoperability](../xlang.md), and the Java-specific interoperability requirements are covered below. ## Use Native Serialization For Java-Only Traffic diff --git a/docs/object-serialization/native.md b/docs/object-serialization/native.md index e85a2a05d4..9a85e0f45e 100644 --- a/docs/object-serialization/native.md +++ b/docs/object-serialization/native.md @@ -26,7 +26,7 @@ system. It is not one shared cross-language protocol. Use native mode for same-runtime traffic that needs language-specific object shapes, migration from a host serializer, or a smaller/faster format without xlang type-mapping constraints. Use -[xlang mode](xlang/index.md) whenever a different runtime must read the bytes. +[xlang mode](xlang.md) whenever a different runtime must read the bytes. ## Supported runtime families diff --git a/docs/object-serialization/swift/core-api.md b/docs/object-serialization/swift/core-api.md index 4146b98e00..043a9de3d4 100644 --- a/docs/object-serialization/swift/core-api.md +++ b/docs/object-serialization/swift/core-api.md @@ -335,7 +335,7 @@ print("\(result.name) \(result.age)") For more cross-language rules and examples, see: -- [Cross-Language Serialization Guide](../xlang/index.md) +- [Cross-Language Interoperability](../xlang.md) - [Java Guide](../java/index.md) - [Python Guide](../python/index.md) - [Dart Guide](../dart/index.md) diff --git a/docs/object-serialization/xlang.md b/docs/object-serialization/xlang.md new file mode 100644 index 0000000000..48de71b2e9 --- /dev/null +++ b/docs/object-serialization/xlang.md @@ -0,0 +1,361 @@ +--- +title: Cross-Language Interoperability +sidebar_position: 1 +id: xlang +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Xlang is Fory's default object serialization mode. It uses one portable binary format across Java, +Python, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. Each runtime's +[Basic Serialization](#runtime-guides) page owns its API and model examples; this page explains the +rules that peers must share. + +## Overview + +Use xlang serialization when bytes cross runtime boundaries, including polyglot services, data +pipelines, and frontend/backend communication. It provides: + +- Direct serialization of native language models without requiring an IDL. +- Coordinated numeric or named identities for application types. +- Compatible schema evolution for independently deployed peers. +- Optional shared-reference and circular-reference preservation. +- Polymorphic values when every concrete type has a portable mapping. +- Out-of-band buffers for large binary and numeric data where the runtime supports them. + +Use [Native Serialization](native.md) instead when every writer and reader uses the same supported +runtime and the object graph needs language-specific behavior such as Java serialization hooks or +Python pickle-compatible objects. + +### Supported Runtimes + +| Runtime | Package or target | Modes | +| --------------------- | ------------------------------------------ | ------------ | +| Java | `org.apache.fory:fory-core` | xlang/native | +| Python | `pyfory` | xlang/native | +| C++ | Fory C++ CMake or Bazel target | xlang/native | +| Go | `github.com/apache/fory/go/fory` | xlang/native | +| Rust | `fory` crate | xlang/native | +| JavaScript/TypeScript | `@apache-fory/core` | xlang | +| C# | `Apache.Fory` | xlang | +| Swift | `Fory` Swift Package Manager target | xlang | +| Dart | `fory` package | xlang | +| Scala | `org.apache.fory:fory-scala` | xlang/native | +| Kotlin | `org.apache.fory:fory-kotlin` and Java API | xlang/native | + +### First Cross-Language Round Trip + +Register the same logical type identity and compatible fields on every peer. The following example +uses a shared type name. + +Java producer: + +```java +public class Person { + public String name; + public int age; +} + +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(Person.class, "example.Person"); + +Person person = new Person(); +person.name = "Alice"; +person.age = 30; +byte[] bytes = fory.serialize(person); +``` + +Python consumer: + +```python +from dataclasses import dataclass +import pyfory + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True) +fory.register_type(Person, name="example.Person") +person = fory.deserialize(bytes_from_java) +``` + +Although xlang is the default, examples often select it explicitly so the transport contract is +visible in application code. + +### Native Models or Fory IDL + +| Approach | Use when | +| --------------------------------- | ---------------------------------------------------------------------- | +| Native language models | The contract is small and teams want to start without a compiler step | +| [Fory IDL](../compiler/index.md) | Many messages or teams need one schema and generated models | +| Native first, then migrate to IDL | A small contract is becoming a long-lived, multi-team service boundary | + +A minimal Fory IDL message looks like this: + +```protobuf +package example; + +message Person { + string name = 1; + int32 age = 2; + optional string email = 3; +} +``` + +Generate the required runtime targets with `foryc`; generated models use consistent field and type +metadata across those targets. + +## Type System and Type Identity + +### Built-in and Custom Types + +Primitive numeric values, strings, binary values, temporal values, lists, sets, maps, dense numeric +arrays, enums, structs, and unions have shared xlang schemas. Built-in values do not require user +registration. Application structs, enums, unions, and extension types require a coordinated +identity. + +The normative [type mapping](../specification/xlang_type_mapping.md) defines the exact host-language +carrier for every xlang type. Important cases include: + +- Python uses markers such as `pyfory.Int32`, `pyfory.Float16`, and `pyfory.BFloat16` when the native + Python type does not express the required width. +- Java, Dart, and other runtimes use annotations or schema metadata where one host type can represent + multiple xlang types. +- Reduced-precision `float16` and `bfloat16` values and dense arrays use runtime-specific carriers. +- `list` and dense `array` are distinct schemas. In compatible mode, a direct struct field may + adapt between a list and dense bool/numeric array when the element domain is compatible and the + actual list contains no unrepresentable null or reference-tracked element. + +Use the mapping specification instead of inferring compatibility from similar host-language names. + +### Coordinate Type Identity + +Every peer must register a custom type with either the same numeric ID or the same namespace and type +name. Numeric IDs produce smaller metadata; names are easier to coordinate across independently +owned services. Do not register one peer by ID and another by name for the same contract. + +Registration must happen before the first root serialization or deserialization operation. Keep a +small contract registry or use generated Fory IDL modules when multiple teams own the peers. + +### Static and Dynamic Fields + +A statically known field uses its declared serializer without writing a concrete runtime type. A +dynamic field carries enough type information to select the concrete registered type. Dynamic +metadata is needed for interfaces, abstract types, trait objects, and other polymorphic positions; +it is unnecessary for primitives and exact final types. + +| Runtime | Dynamic field model | +| ------- | ----------------------------------------------------------------- | +| Java | `@ForyField(dynamic = ...)` controls automatic or forced metadata | +| Python | `pyfory.field(dynamic=...)` controls object-field metadata | +| C++ | `fory::F(...).dynamic(...)` overrides automatic detection | +| Go | Interface fields express dynamic values | +| Rust | Trait-object carriers express dynamic values | + +Writing dynamic metadata costs space and type-resolution work. Disable it only when the field can +never contain another concrete type. Exact annotation and registration examples belong to each +runtime's schema metadata, type registration, and polymorphism pages. + +## Nullability and Reference Tracking + +Nullability and reference tracking solve different problems: + +| Concern | Purpose | +| ------------------ | ------------------------------------------------------------- | +| Nullability | Allows a field or value position to contain no value | +| Reference tracking | Preserves repeated object identity and supports object cycles | + +The wire framing is defined by the +[xlang serialization specification](../specification/xlang_serialization_spec.md). Applications +should configure the semantic behavior through runtime APIs rather than depend on flag values. + +### Nullability + +Xlang struct fields are non-nullable by default. Non-nullable fields are smaller and make required +data explicit. Nullable or optional carriers include Java boxed/annotated values, Python +`Optional[T]`, C++ `std::optional`, Go pointers, Rust `Option`, and Scala `Option[T]`. + +Keep corresponding fields consistently nullable across peers. In same-schema mode, changing +nullability changes the schema and is incompatible. Compatible mode supports the documented +nullable and scalar adaptations, but a remote null still cannot be materialized into a local type +that has no valid null or missing-value behavior. + +### Shared and Circular References + +Enable reference tracking when the graph contains the same object more than once or contains a +cycle. Leave it disabled for value-shaped data to avoid identity-table overhead. + +```java +import org.apache.fory.Fory; +import org.apache.fory.annotation.Ref; + +public class Node { + public String value; + @Ref public Node next; +} + +Node first = new Node(); +Node second = new Node(); +first.next = second; +second.next = first; + +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); +``` + +Global reference tracking enables the runtime mechanism; field metadata selects which positions +participate. Common field-level controls are Java and Scala `@Ref`, Go `fory:"ref"` tags, Rust +`#[fory(ref = true)]`, and C++ smart-pointer or `fory::F().ref()` metadata. Consult the runtime guide +because default tracking differs by carrier and language. + +Reference support also follows the host ownership model. For example, Rust can preserve supported +shared-reference carriers, while cycles require representable ownership and weak-reference shapes. + +## Polymorphism + +Xlang polymorphism preserves a value's concrete registered type when its declared field, collection +element, or root type is broader. Every receiving peer must: + +1. Register the same concrete type identity. +2. Provide a compatible field schema for that concrete type. +3. Mark or model the position as dynamic when the runtime cannot infer it. +4. Use a concrete type that has a portable xlang mapping. + +Host-language inheritance alone does not make a type portable. If a shape has no xlang mapping, use +the runtime's native mode for same-language traffic or define a portable model. See the runtime +polymorphism pages for interfaces, trait objects, unions, and generated-code syntax. + +## Schema Evolution + +Compatible mode is the xlang default. It carries schema metadata so independently deployed readers +can tolerate supported field additions, removals, reordering, and documented compatible type +adaptations. + +Keep compatible mode when peers may deploy separately. Select same-schema mode only when every +reader and writer uses the same: + +- Type identity and field IDs or names. +- Field types and nested generic shapes. +- Nullability and reference metadata. +- Polymorphic alternatives. + +Same-schema mode reduces metadata and payload size, but any mismatch can produce a schema hash or +type error. Generated Fory IDL models make exact coordination easier when all peers are released +together. Normative compatibility behavior lives in the +[xlang serialization specification](../specification/xlang_serialization_spec.md). + +## Zero-Copy Serialization + +Some runtimes can move large binary or numeric buffers out of the main serialized byte stream. This +avoids copying those buffers into one contiguous payload. + +The transport flow is: + +1. Serialize the object graph and collect selected buffer objects through a callback. +2. Send the main metadata bytes and the collected buffers separately. +3. Provide the buffers in the same order during deserialization. + +Java: + +```java +Collection objects = new ArrayList<>(); +byte[] metadata = fory.serialize(value, object -> !objects.add(object)); +List buffers = objects.stream() + .map(BufferObject::toBuffer) + .toList(); +Object decoded = fory.deserialize(metadata, buffers); +``` + +Python: + +```python +objects = [] +metadata = fory.serialize(value, buffer_callback=objects.append) +buffers = [obj.to_buffer() for obj in objects] +decoded = fory.deserialize(metadata, buffers=buffers) +``` + +Go exposes the equivalent callback-buffer flow through its serialization and buffer APIs. Use the +runtime documentation for the current method names and supported buffer carriers. + +Out-of-band serialization helps when buffers are large and the transport can send them without an +additional copy. For small arrays, callback and multi-buffer transport overhead may cost more than +copying. The application owns buffer ordering, lifetime, and transport framing. See +[Python Out-of-Band Serialization](python/out-of-band.md) for Python and NumPy details. + +## Troubleshooting + +| Symptom | Likely cause | Resolution | +| ---------------------------------------- | --------------------------------------------------- | -------------------------------------------------------------- | +| Type is not registered | Registration missing or performed too late | Register every custom type before the first root operation | +| Type ID or name mismatch | Peers use different identities | Use the same numeric ID or the same namespace and type name | +| Integer overflow or float precision loss | Host carriers use different numeric widths | Follow the type mapping and use explicit width metadata | +| Fields decode incorrectly | Field IDs, names, or types differ | Align field metadata or regenerate all peers from the same IDL | +| Stack overflow on a cyclic graph | Reference tracking is disabled | Enable global and field-level reference tracking | +| Shared objects become duplicates | The value position does not track references | Enable reference tracking for that carrier or field | +| Unsupported host type | The type has no portable xlang representation | Use a portable model or native mode for same-language traffic | +| Schema/hash mismatch | Same-schema peers have different schemas | Align every peer or restore compatible mode | +| Failure after an upgrade | Peers run incompatible protocol versions | Align supported Fory versions and review release notes | +| Payload rejected immediately | One peer wrote native bytes and another reads xlang | Keep all peers on xlang for a cross-language contract | + +### Diagnostic Checklist + +1. Confirm that every peer uses xlang mode and a mutually supported Fory version. +2. Compare the registered type identity, field IDs or names, numeric widths, nullability, and + reference metadata. +3. Reproduce a same-runtime round trip before testing the cross-runtime direction. +4. Test both directions for every language pair used in production. +5. Reduce the value to one type and field, then add fields back until the mismatch appears. +6. Inspect the runtime-specific troubleshooting page for generated-code, platform, or API errors. + +When diagnosing binary layout, use the specifications and runtime debug facilities. Do not treat a +hex dump or internal flag value as a stable application API. + +## Runtime Guides + +- [Java](java/core-api.md#cross-language-interoperability) +- [Python](python/core-api.md#cross-language-interoperability) +- [C++](cpp/core-api.md#cross-language-interoperability) +- [Go](go/core-api.md#cross-language-interoperability) +- [Rust](rust/core-api.md#cross-language-interoperability) +- [JavaScript/TypeScript](javascript/core-api.md#cross-language-interoperability) +- [C#](csharp/core-api.md#cross-language-interoperability) +- [Swift](swift/core-api.md#cross-language-interoperability) +- [Dart](dart/core-api.md#cross-language-interoperability) +- [Scala](scala/core-api.md#cross-language-interoperability) +- [Kotlin](kotlin/core-api.md#cross-language-interoperability) + +## Related Documentation + +- [Xlang Serialization Format](../specification/xlang_serialization_spec.md) — normative wire format +- [Xlang Type Mapping](../specification/xlang_type_mapping.md) — exact runtime carrier mappings +- [Fory IDL and Compiler](../compiler/index.md) — schema-first models and code generation +- [Getting Started](../start/index.md) — installation and first serialization for each runtime +- [Row Format](../row-format/index.md) — random-access analytical rows for trusted data +- [Object Serialization Security](security.md) — trust boundaries and deserialization controls + +## Operational Best Practices + +1. Coordinate one type identity and field contract across every peer. +2. Keep compatible mode unless all readers and writers deploy the same schema together. +3. Enable reference tracking only for identity-bearing or cyclic graphs. +4. Reuse configured Fory instances instead of rebuilding them for each operation. +5. Validate every production language pair in both directions before deployment. +6. Prefer Fory IDL once a contract spans many messages, services, or independently owned teams. diff --git a/docs/object-serialization/xlang/_category_.json b/docs/object-serialization/xlang/_category_.json deleted file mode 100644 index d2418b5e5e..0000000000 --- a/docs/object-serialization/xlang/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "position": 1, - "label": "Xlang", - "collapsible": true, - "collapsed": true -} diff --git a/docs/object-serialization/xlang/index.md b/docs/object-serialization/xlang/index.md deleted file mode 100644 index 103d942305..0000000000 --- a/docs/object-serialization/xlang/index.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: Xlang Serialization Guide -sidebar_position: 0 -id: index -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Apache Fory™ xlang serialization is the default wire format for cross-language payloads. Serialize -data in one language and deserialize it in another without manual conversion. You can use direct -language model types for small contracts, or use Fory IDL and code generation when a schema-first -workflow is a better fit. - -## Features - -- **No IDL required**: Serialize objects directly with language model types. -- **Multi-language support**: Java, Python, C++, Go, Rust, - JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin interoperate through - the same xlang format. -- **Reference support**: Shared and circular references work across language boundaries when reference tracking is enabled in each peer. -- **Schema evolution**: Compatible mode is the xlang default so readers can tolerate added, removed, or reordered fields. -- **Out-of-band buffers**: Language implementations can expose zero-copy buffer paths for large binary data. -- **High performance**: Fory implementations use generated serializers, JIT serializers, or optimized code paths where available. - -## Supported Languages - -| Language | Status | Package or target | -| --------------------- | --------- | -------------------------------- | -| Java | Supported | `org.apache.fory:fory-core` | -| Python | Supported | `pyfory` | -| C++ | Supported | Bazel/CMake build | -| Go | Supported | `github.com/apache/fory/go/fory` | -| Rust | Supported | `fory` crate | -| JavaScript/TypeScript | Supported | `@apache-fory/core` | -| C# | Supported | `Apache.Fory` | -| Swift | Supported | Swift Package Manager target | -| Dart | Supported | `fory` package | -| Scala | Supported | `org.apache.fory:fory-scala` | -| Kotlin | Supported | `org.apache.fory:fory-kotlin` | - -## When to Use Xlang Mode - -Use xlang mode when: - -- Building multi-language microservices -- Creating polyglot data pipelines -- Sharing data between frontend JavaScript/TypeScript and backend services such - as Java, Python, Go, C#, Scala, or Kotlin - -Use native mode for same-language traffic in Java, Scala, Kotlin, Python, C++, -Go, or Rust: - -- All serialization/deserialization happens in the same language -- You need language-specific features such as Python pickle-style objects or Java serialization hooks -- You want native-mode payloads for same-language services - -## Quick Example - -### Java (Producer) - -```java -import org.apache.fory.*; -import org.apache.fory.config.*; - -public class Person { - public String name; - public int age; -} - -Fory fory = Fory.builder().withXlang(true).build(); -fory.register(Person.class, "example.Person"); - -Person person = new Person(); -person.name = "Alice"; -person.age = 30; -byte[] bytes = fory.serialize(person); -// Send bytes to Python, Go, Rust, etc. -``` - -### Python (Consumer) - -```python -import pyfory -from dataclasses import dataclass - -@dataclass -class Person: - name: str - age: pyfory.Int32 - -fory = pyfory.Fory(xlang=True) -fory.register_type(Person, name="example.Person") - -# Receive bytes from Java -person = fory.deserialize(bytes_from_java) -print(f"{person.name}, {person.age}") # Alice, 30 -``` - -## Fory IDL - -For schema-first projects, Fory also provides **Fory IDL** and code generation. - -- Compiler docs: [Fory IDL Overview](../../compiler/index.md) -- Best for large multi-language message contracts and long-lived schemas - -### Minimal IDL Example - -Create `person.fdl`: - -```protobuf -package example; - -message Person { - string name = 1; - int32 age = 2; - optional string email = 3; -} -``` - -Generate code: - -```bash -foryc person.fdl --lang java,python,cpp,go,rust,javascript,csharp,swift,dart,scala,kotlin --output ./generated -``` - -This generates native language types with consistent field/type mappings across all targets. - -## When to Fory IDL - -| Option | Use When | Why | -| ---------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| Native xlang types (no IDL) | You only have a few message types and want to move quickly | Avoids the integration/setup cost of introducing and operating the compiler | -| Fory IDL (schema-first + codegen) | You have many messages across multiple languages/teams/services | Provides a single contract, stronger consistency, and easier long-term evolution | -| Hybrid (start native, move to IDL) | Project starts small but message count and cross-team dependency grows | Lets you keep early velocity, then standardize once schema complexity increases | - -## Documentation - -| Topic | Description | -| --------------------------------------------------------- | ----------------------------------------------- | -| [Getting Started](../../start/index.md) | Installation and basic setup for all languages | -| [Type Mapping](../../specification/xlang_type_mapping.md) | Xlang type mapping reference | -| [Type System](type-system.md) | Built-in types and cross-runtime type behavior | -| [Type Identity](type-identity.md) | Coordinate names and numeric IDs across peers | -| [Nullability](nullability.md) | Nullable field behavior and configuration | -| [References](references.md) | Shared and circular object references | -| [Polymorphism](polymorphism.md) | Runtime type selection and registration | -| [Schema Evolution](schema-evolution.md) | Compatible and same-schema mode choices | -| [Zero-Copy](zero-copy.md) | Out-of-band serialization for large data | -| [Row Format](../../row-format/index.md) | Cache-friendly binary format with random access | -| [Troubleshooting](troubleshooting.md) | Common issues and solutions | - -## Language-Specific Guides - -For language-specific details and API reference: - -- [Java Interoperability Guide](../java/core-api.md#cross-language-interoperability) -- [Python Interoperability Guide](../python/core-api.md#cross-language-interoperability) -- [C++ Interoperability Guide](../cpp/core-api.md#cross-language-interoperability) -- [Go Interoperability Guide](../go/core-api.md#cross-language-interoperability) -- [Rust Interoperability Guide](../rust/core-api.md#cross-language-interoperability) -- [JavaScript/TypeScript Interoperability Guide](../javascript/core-api.md#cross-language-interoperability) -- [C# Interoperability Guide](../csharp/core-api.md#cross-language-interoperability) -- [Swift Interoperability Guide](../swift/core-api.md#cross-language-interoperability) -- [Dart Interoperability Guide](../dart/core-api.md#cross-language-interoperability) -- [Scala Interoperability Guide](../scala/core-api.md#cross-language-interoperability) -- [Kotlin Interoperability Guide](../kotlin/core-api.md#cross-language-interoperability) - -## Specifications - -- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) - Binary protocol details -- [Type Mapping Specification](../../specification/xlang_type_mapping.md) - Complete type mapping reference - -## Operational best practices - -1. **Use consistent type names**: Ensure all languages use the same type name or ID -2. **Enable reference tracking**: If your data has circular or shared references -3. **Reuse Fory instances**: Creating Fory is expensive; reuse instances -4. **Use type annotations**: In Python, use markers such as `pyfory.Int32` for precise type mapping -5. **Test cross-language**: Verify serialization works across all target languages diff --git a/docs/object-serialization/xlang/nullability.md b/docs/object-serialization/xlang/nullability.md deleted file mode 100644 index e7b7fe2d72..0000000000 --- a/docs/object-serialization/xlang/nullability.md +++ /dev/null @@ -1,267 +0,0 @@ ---- -title: Field Nullability -sidebar_position: 4 -id: nullability -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -This page explains how Fory handles field nullability in cross-language (xlang) serialization mode. - -## Default Behavior - -In xlang mode, **fields are non-nullable by default**. This means: - -- Values must always be present (non-null) -- No null flag byte is written for the field -- Serialization is more compact - -The following types are nullable by default: - -- `Optional` (Java, C++) -- Java boxed types (`Integer`, `Long`, `Double`, etc.) -- Go pointer types (`*int32`, `*string`, etc.) -- Rust `Option` -- Python `Optional[T]` -- Scala `Option[T]` - -| Field Type | Default Nullable | Null Flag Written | -| ------------------------------------------ | ---------------- | ----------------- | -| Primitives (`int`, `bool`, `float`, etc.) | No | No | -| `String` | No | No | -| `List`, `Map`, `Set` | No | No | -| Custom structs | No | No | -| Enums | No | No | -| Java boxed types (`Integer`, `Long`, etc.) | Yes | Yes | -| Go pointer types (`*int32`, `*string`) | Yes | Yes | -| `Optional` / `Option` | Yes | Yes | - -## Wire Format - -The nullable flag controls whether a **null flag byte** is written before the field value: - -``` -Non-nullable field: [value data] -Nullable field: [null_flag] [value data if not null] -``` - -Where `null_flag` is: - -- `-1` (NULL_FLAG): Value is null -- `-2` (NOT_NULL_VALUE_FLAG): Value is present - -## Nullable vs Reference Tracking - -These are related but distinct concepts: - -| Concept | Purpose | Flag Values | -| ---------------------- | ------------------------------------ | ------------------------------------------- | -| **Nullable** | Allow null values for a field | `-1` (null), `-2` (not null) | -| **Reference Tracking** | Deduplicate shared object references | `-1` (null), `-2` (not null), `≥0` (ref ID) | - -Key differences: - -- **Nullable only**: Writes `-1` or `-2` flag, no reference deduplication -- **Reference tracking**: Extends nullable semantics with reference IDs (`≥0`) for previously seen objects -- Both use the same flag byte position—ref tracking is a superset of nullable - -When `refTracking=true`, the null flag byte doubles as a ref flag: - -``` -ref_flag = -1 → null value -ref_flag = -2 → new object (first occurrence) -ref_flag >= 0 → reference to object at index ref_flag -``` - -For detailed reference tracking behavior, see [Reference Tracking](references.md). - -## Language-Specific Examples - -### Java - -```java -public class Person { - // Non-nullable by default in xlang mode - String name; // Must not be null - int age; // Primitive, always non-nullable - List tags; // Must not be null - - // Explicitly nullable - @Nullable - String nickname; // Can be null - - // Optional wrapper - nullable by default - Optional bio; // Can be empty/null -} - -Fory fory = Fory.builder() - .withXlang(true) - .build(); -fory.register(Person.class, "example.Person"); -``` - -### Python - -```python -from dataclasses import dataclass -from typing import Optional, List -import pyfory - -@dataclass -class Person: - # Non-nullable by default - name: str # Must have a value - age: pyfory.Int32 # Primitive - tags: List[str] # Must not be None - - # Optional makes it nullable - nickname: Optional[str] = None # Can be None - bio: Optional[str] = None # Can be None - -fory = pyfory.Fory(xlang=True) -fory.register_type(Person, name="example.Person") -``` - -### Rust - -```rust -use fory::{Fory, ForyStruct}; - -#[derive(ForyStruct)] -struct Person { - // Non-nullable by default - name: String, - age: i32, - tags: Vec, - - // Option is nullable - nickname: Option, // Can be None - bio: Option, // Can be None -} -``` - -### Go - -```go -type Person struct { - // Non-nullable by default - Name string - Age int32 - Tags []string - - // Pointer types for nullable fields - Nickname *string // Can be nil - Bio *string // Can be nil -} - -fory := forygo.NewFory(forygo.WithXlang(true)) -fory.RegisterStructByName(Person{}, "example.Person") -``` - -### C++ - -```cpp -struct Person { - // Non-nullable by default - std::string name; - int32_t age; - std::vector tags; - - // std::optional for nullable - std::optional nickname; - std::optional bio; -}; -FORY_STRUCT(Person, name, age, tags, nickname, bio); -``` - -## Customizing Nullability - -### Java: @Nullable Annotation - -```java -public class Config { - @Nullable - String optionalSetting; // Explicitly nullable - - String requiredSetting; // Explicitly non-nullable (default) -} -``` - -### C++: FORY_STRUCT Field Config - -```cpp -struct Config { - std::optional optional_setting; - std::string required_setting; -}; - -FORY_STRUCT(Config, - (optional_setting, fory::F(1)), - (required_setting, fory::F(2)) -); -``` - -For nullable pointer carriers, opt in with `.nullable()`: - -```cpp -struct ConfigRef { - std::shared_ptr optional_setting; - std::shared_ptr required_setting; -}; - -FORY_STRUCT(ConfigRef, - (optional_setting, fory::F(1).nullable()), - (required_setting, fory::F(2)) -); -``` - -## Null Value Handling - -When a non-nullable field receives a null value: - -| Language | Behavior | -| -------- | ---------------------------------------------------- | -| Java | Throws `NullPointerException` or serialization error | -| Python | Raises `TypeError` or serialization error | -| Rust | Compile-time error (non-Option types can't be None) | -| Go | Zero value is used (empty string, 0, etc.) | -| C++ | Default-constructed value or undefined behavior | - -## Schema Compatibility - -The nullable flag is part of the struct schema fingerprint. When compatible mode is disabled, changing a field's nullability is a **breaking change** that will cause schema version mismatch errors. - -``` -Schema A: { name: String (non-nullable) } -Schema B: { name: String (nullable) } -// These have different fingerprints when compatible mode is disabled -``` - -In compatible mode, top-level scalar fields can still be matched when their scalar type is otherwise compatible and the nullability or optional wrapper differs. Present values are read through compatible scalar conversion and must satisfy the normal lossless conversion checks. Remote null values follow the compatible-read null/default behavior for the local field. - -## Best Practices - -1. **Use non-nullable by default**: Only make fields nullable when null is a valid semantic value -2. **Use Optional/Option wrappers**: Instead of raw types with nullable annotation -3. **Be consistent across languages**: Use the same nullability for corresponding fields -4. **Document nullable fields**: Make it clear which fields can be null in your API - -## See Also - -- [Reference Tracking](references.md) - Shared and circular reference handling -- [Xlang Overview](index.md) - Cross-language serialization workflow and runtime guides -- [Type Mapping](../../specification/xlang_type_mapping.md) - Cross-language type mapping reference -- [Xlang Specification](../../specification/xlang_serialization_spec.md) - Binary protocol details diff --git a/docs/object-serialization/xlang/polymorphism.md b/docs/object-serialization/xlang/polymorphism.md deleted file mode 100644 index 7851e17829..0000000000 --- a/docs/object-serialization/xlang/polymorphism.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Polymorphism -sidebar_position: 6 -id: polymorphism -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Xlang polymorphism preserves a value's concrete registered type when a field, element, or root is -declared through a broader type. Every peer must agree on the concrete type identity and compatible -field schema. - -## Runtime rules - -Use the selected runtime's schema metadata, registration, and polymorphism pages for exact syntax. -The [xlang serialization specification](../../specification/xlang_serialization_spec.md) defines -the shared behavior and limitations. - -Do not infer xlang support from host-language inheritance alone. A concrete subtype must have a -portable xlang mapping and coordinated registration on every peer that can receive it. diff --git a/docs/object-serialization/xlang/references.md b/docs/object-serialization/xlang/references.md deleted file mode 100644 index 6e12328c4e..0000000000 --- a/docs/object-serialization/xlang/references.md +++ /dev/null @@ -1,303 +0,0 @@ ---- -title: Reference Tracking -sidebar_position: 5 -id: references -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -This page explains how Fory handles reference tracking for shared and circular references in cross-language serialization. - -## Overview - -Reference tracking enables: - -- **Shared references**: Same object referenced multiple times is serialized once -- **Circular references**: Objects that reference themselves or form cycles -- **Memory efficiency**: No duplicate data for repeated objects - -## Enabling Reference Tracking - -### Java - -```java -Fory fory = Fory.builder() - .withXlang(true) - .withRefTracking(true) - .build(); -``` - -### Python - -```python -fory = pyfory.Fory(xlang=True, ref=True) -``` - -### Go - -```go -fory := forygo.NewFory( - forygo.WithXlang(true), - forygo.WithTrackRef(true), -) -``` - -### C++ - -```cpp -auto fory = fory::serialization::Fory::builder().xlang(true).track_ref(true).build(); -``` - -### Rust - -```rust -let fory = Fory::builder() - .xlang(true) - .track_ref(true).build(); -``` - -### Scala - -```scala -import org.apache.fory.scala.ForyScala - -val fory = ForyScala.builder() - .withXlang(true) - .withRefTracking(true) - .build() -``` - -## Wire Format - -When reference tracking is enabled, nullable fields write a **ref flag byte** before the value: - -``` -[ref_flag] [value data if not null/ref] -``` - -Where `ref_flag` is: - -| Value | Meaning | -| -------------------------- | ----------------------------------------------------- | -| `-1` (NULL_FLAG) | Value is null | -| `-2` (NOT_NULL_VALUE_FLAG) | Value is present, first occurrence | -| `≥0` | Reference ID pointing to previously serialized object | - -## Reference Tracking vs Nullability - -These are **independent** concepts: - -| Concept | Purpose | Controlled By | -| ---------------------- | ------------------------------------------ | ---------------------------------------- | -| **Nullability** | Whether a field can hold null values | Field type (`Optional`) or annotation | -| **Reference Tracking** | Whether duplicate objects are deduplicated | Global `refTracking` option | - -Key behavior: - -- Ref flag bytes are **only written for nullable fields** -- Non-nullable fields skip ref flags entirely, even with `refTracking=true` -- Reference deduplication only applies to objects that appear multiple times - -```java -// Reference tracking enabled, but non-nullable fields still skip ref flags -Fory fory = Fory.builder() - .withXlang(true) - .withRefTracking(true) - .build(); -``` - -## Per-Field Reference Tracking - -By default, **most fields do not track references** even when global `refTracking=true`. Only specific pointer/smart pointer types track references by default. - -### Default Behavior by Language - -| Language | Default Ref Tracking | Types That Track Refs by Default | -| -------- | -------------------- | ---------------------------------------------------------- | -| Java | No | None (use annotation to enable) | -| Python | No | None (use annotation to enable) | -| Go | No | None (use `fory:"ref"` to enable) | -| C++ | Yes | `std::shared_ptr`, `fory::serialization::SharedWeak` | -| Rust | No | `Rc`, `Arc`, `Weak` | -| Scala | No | None (use `@Ref` to enable) | - -### Customizing Per-Field Ref Tracking - -#### Java: @Ref Annotation - -```java -public class Document { - // Default: no ref tracking - String title; - - // Enable ref tracking for this field - @Ref - Author author; - - // Shared across documents, track refs to avoid duplicates - List<@Ref Tag> tags; -} -``` - -#### C++: FORY_STRUCT Field Config - -```cpp -struct Document { - std::string title; - - // shared_ptr/SharedWeak track refs by default - std::shared_ptr author; - fory::serialization::SharedWeak data; - - std::shared_ptr tag_owner; -}; -FORY_STRUCT(Document, - title, - author, - data, - (tag_owner, fory::F().ref()) -); -``` - -To disable reference tracking for C++ entirely, set -`Fory::builder().xlang(true).track_ref(false).build()` on the serializer. - -#### Rust: Field Attributes - -```rust -use fory::ForyStruct; -use std::rc::Rc; - -#[derive(ForyStruct)] -struct Document { - title: String, - - // Rc/Arc track refs by default - author: Rc, - - // Explicitly enable ref tracking - #[fory(ref = true)] - tags: Vec, -} -``` - -#### Scala: @Ref Annotation - -Scala schema IDL and Scala 3 macro derivation use the same shared JVM `@Ref` -annotation: - -```scala -import org.apache.fory.annotation.{ForyField, ForyStruct, Ref} -import org.apache.fory.scala.ForySerializer - -@ForyStruct -final class Node() derives ForySerializer { - @ForyField(id = 1) - var children: List[Node @Ref] = List.empty - - @Ref - @ForyField(id = 2) - var parent: Option[Node] = None -} -``` - -For Scala, top-level field reference tracking is owned by `@Ref` on the field or -constructor parameter. Type-use `T @Ref` is for nested element/value/payload -references, such as `List[Node @Ref]`. - -#### Go: Struct Tags - -```go -type Document struct { - Title string - - // Enable ref tracking for pointer to struct - Author *Author `fory:"ref"` - - // Enable ref tracking for slice - Tags []Tag `fory:"ref"` -} -``` - -### When to Enable Per-Field Ref Tracking - -Enable ref tracking for fields that: - -- May contain the same object instance multiple times -- Are part of circular reference chains -- Hold large objects that might be shared - -Disable (or leave default) for fields that: - -- Always contain unique values -- Are primitives or simple value types -- Don't participate in object sharing - -## Example: Shared References - -```java -public class Container { - List data; - List sameData; // Points to same list -} - -Container obj = new Container(); -obj.data = Arrays.asList("a", "b", "c"); -obj.sameData = obj.data; // Shared reference - -// With refTracking=true: data serialized once, sameData stores reference ID -// With refTracking=false: data serialized twice (duplicate) -``` - -## Example: Circular References - -```java -public class Node { - String value; - Node next; -} - -Node a = new Node("A"); -Node b = new Node("B"); -a.next = b; -b.next = a; // Circular reference - -// With refTracking=true: works correctly -// With refTracking=false: infinite recursion error -``` - -## Language Support - -| Language | Shared Refs | Circular Refs | -| ---------- | ----------- | -------------------- | -| Java | Yes | Yes | -| Python | Yes | Yes | -| Go | Yes | Yes | -| C++ | Yes | Yes | -| JavaScript | Yes | Yes | -| Rust | Yes | No (ownership rules) | - -## Performance Considerations - -- **Overhead**: Reference tracking adds a hash map lookup per object -- **When to enable**: Use when data has shared/circular references -- **When to disable**: Use for simple data structures without sharing - -## See Also - -- [Field Nullability](nullability.md) - How nullability affects serialization -- [Xlang Overview](index.md) - Cross-language serialization workflow and runtime guides -- [Xlang Specification](../../specification/xlang_serialization_spec.md) - Binary protocol details diff --git a/docs/object-serialization/xlang/schema-evolution.md b/docs/object-serialization/xlang/schema-evolution.md deleted file mode 100644 index 6e519da3c0..0000000000 --- a/docs/object-serialization/xlang/schema-evolution.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Schema Evolution -sidebar_position: 3 -id: schema-evolution -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Xlang compatible mode carries schema metadata so readers can tolerate supported field additions, -removals, and reordering. Same-schema mode reduces metadata only when every reader and writer uses -the same schema. - -## Choose compatible or same-schema mode - -Keep compatible mode for independently deployed peers. Use same-schema mode only after verifying -that every runtime uses the same field identities, nullability, reference metadata, and types. - -Runtime APIs and examples live in each runtime's `schema-evolution.md` page. Normative schema -metadata and compatibility behavior are defined by the -[xlang serialization specification](../../specification/xlang_serialization_spec.md). diff --git a/docs/object-serialization/xlang/troubleshooting.md b/docs/object-serialization/xlang/troubleshooting.md deleted file mode 100644 index e6514c1589..0000000000 --- a/docs/object-serialization/xlang/troubleshooting.md +++ /dev/null @@ -1,321 +0,0 @@ ---- -title: Troubleshooting -sidebar_position: 90 -id: troubleshooting -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -This page covers common issues and solutions when using cross-language serialization. - -## Type Registration Errors - -### "Type not registered" Error - -**Symptom:** - -``` -Error: Type 'example.Person' is not registered -``` - -**Cause:** The type was not registered before deserialization, or the type name doesn't match. - -**Solution:** - -1. Ensure the type is registered with the same name on both sides: - - ```java - // Java - fory.register(Person.class, "example.Person"); - ``` - - ```python - # Python - fory.register_type(Person, name="example.Person") - ``` - -2. Check for typos or case differences in type names - -3. Register types before any serialization/deserialization calls - -### "Type ID mismatch" Error - -**Symptom:** - -``` -Error: Expected type ID 100, got 101 -``` - -**Cause:** Different type IDs used across languages. - -**Solution:** Use consistent type IDs: - -```java -// Java -fory.register(Person.class, 100); -fory.register(Address.class, 101); -``` - -```python -# Python -fory.register_type(Person, type_id=100) -fory.register_type(Address, type_id=101) -``` - -## Type Mapping Issues - -### Integer Overflow - -**Symptom:** Values are truncated or wrapped unexpectedly. - -**Cause:** Using different integer sizes across languages. - -**Solution:** - -1. In Python, use explicit type annotations: - - ```python - @dataclass - class Data: - value: pyfory.Int32 # Not just 'int' - ``` - -2. Ensure integer ranges are compatible: - - `int8`: -128 to 127 - - `int16`: -32,768 to 32,767 - - `int32`: -2,147,483,648 to 2,147,483,647 - -### Float Precision Loss - -**Symptom:** Float values have unexpected precision. - -**Cause:** Mixing `float32` and `float64` types. - -**Solution:** - -1. Use consistent float types: - - ```python - @dataclass - class Data: - value: pyfory.Float32 # Explicit 32-bit float - ``` - -2. Be aware that Python's `float` maps to `float64` by default - -### String Encoding Errors - -**Symptom:** - -``` -Error: Invalid UTF-8 sequence -``` - -**Cause:** Non-UTF-8 encoded strings. - -**Solution:** - -1. Ensure all strings are valid UTF-8 -2. In Python, decode bytes before serialization: - - ```python - text = raw_bytes.decode('utf-8') - ``` - -## Field Order Issues - -### "Field mismatch" Error - -**Symptom:** Deserialized objects have wrong field values. - -**Cause:** Field order differs between languages. - -**Solution:** Fory sorts fields by their snake_cased names. Ensure field names are consistent: - -```java -// Java - fields will be sorted: age, email, name -public class Person { - public String name; - public int age; - public String email; -} -``` - -```python -# Python - same field order -@dataclass -class Person: - name: str - age: pyfory.Int32 - email: str -``` - -## Reference Tracking Issues - -### Stack Overflow with Circular References - -**Symptom:** - -``` -StackOverflowError or RecursionError -``` - -**Cause:** Reference tracking is disabled but data has circular references. - -**Solution:** Enable reference tracking: - -```java -// Java -Fory fory = Fory.builder() - .withRefTracking(true) - .build(); -``` - -```python -# Python -fory = pyfory.Fory(ref=True) -``` - -### Duplicate Objects - -**Symptom:** Shared objects are duplicated after deserialization. - -**Cause:** Reference tracking is disabled. - -**Solution:** Enable reference tracking if objects are shared within the graph. - -## Xlang Type Issues - -### Incompatible Types in Xlang Mode - -**Symptom:** - -``` -Error: Type 'Optional' is not supported in xlang mode -``` - -**Cause:** Using Java-specific types that don't have cross-language equivalents. - -**Solution:** Use compatible types: - -```java -// Instead of Optional -public String email; // nullable - -// Instead of BigDecimal -public double amount; - -// Instead of EnumSet -public Set statuses; -``` - -## Version Compatibility - -### Schema Hash Mismatch - -**Symptom:** Deserialization fails with an error such as `class version hash mismatch`, -`schema version mismatch`, `struct version mismatch`, or `hash mismatch`. - -**Cause:** The writer and reader have disabled compatible mode while their struct/class schemas -differ. In xlang mode this can happen even when each language made a reasonable local change, -because field names, type annotations, field IDs, nullability, and generated schema metadata must -still align exactly. - -**Solution:** - -1. Align the schemas carefully on every service and language: field names or field IDs, field order, - type annotations, nullability, and type registration IDs/names. -2. Xlang mode defaults to compatible mode in current implementations. If a peer has explicitly selected - `compatible=false`, remove that override or enable compatible mode on every peer. - Compatible mode writes extra schema metadata, so payloads are larger, but it is recommended - for xlang services that may evolve independently. -3. Set `compatible=false` only when every reader and writer always uses the same schema. For xlang payloads, do this only after verifying that every language uses that schema, or when native types are generated from Fory schema IDL. - -### Serialization Format Changed - -**Symptom:** Deserialization fails after upgrading Fory. - -**Cause:** Breaking changes in serialization format. - -**Solution:** - -1. Ensure all services use compatible Fory versions -2. Check release notes for breaking changes -3. Consider using schema evolution (compatible mode) for gradual upgrades - -## Debugging Tips - -### Enable Debug Logging - -**Java:** - -```java -// Add to JVM options --Dfory.debug=true -``` - -**Python:** - -```python -import logging -logging.getLogger('pyfory').setLevel(logging.DEBUG) -``` - -### Inspect Serialized Data - -Use hex dump to inspect the binary format: - -```python -data = fory.serialize(obj) -print(data.hex()) -``` - -### Test Round-Trip - -Always test round-trip serialization in each language: - -```java -byte[] bytes = fory.serialize(obj); -Object result = fory.deserialize(bytes); -assert obj.equals(result); -``` - -### Cross-Language Testing - -Test serialization across all target languages before deployment: - -```bash -# Serialize in Java -java -jar serializer.jar > data.bin - -# Deserialize in Python -python deserializer.py data.bin -``` - -## Common Mistakes - -1. **Not registering types**: Always register custom types before use -2. **Inconsistent type names/IDs**: Use the same names/IDs across all languages -3. **Mixing xlang and native payloads**: Keep every peer on the xlang wire format -4. **Wrong type annotations**: Use markers such as `pyfory.Int32` in Python -5. **Ignoring reference tracking**: Enable for circular/shared references - -## See Also - -- [Type Mapping](../../specification/xlang_type_mapping.md) - Cross-language type mapping reference -- [Getting Started](../../start/index.md) - Runtime setup guides -- [Java Troubleshooting](../java/troubleshooting.md) - Java-specific issues -- [Python Troubleshooting](../python/troubleshooting.md) - Python-specific issues diff --git a/docs/object-serialization/xlang/type-identity.md b/docs/object-serialization/xlang/type-identity.md deleted file mode 100644 index 7eaf8017f9..0000000000 --- a/docs/object-serialization/xlang/type-identity.md +++ /dev/null @@ -1,298 +0,0 @@ ---- -title: Type Identity and Dynamic Fields -sidebar_position: 2 -id: type-identity -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Field type meta configuration controls whether type information is written during serialization for struct fields. This is essential for supporting polymorphism where the actual concrete type may differ from the declared field type. - -## Overview - -When serializing a struct field, Fory needs to determine whether to write type metadata: - -- **Static typing**: Use the declared field type's serializer directly (no type info written) -- **Dynamic typing**: Write type information to support subtypes - -## When Type Meta Is Needed - -Type metadata is required when: - -1. **Interface/abstract fields**: The declared type is abstract, so concrete type must be recorded -2. **Polymorphic fields**: The concrete type may be a subclass of the declared type -3. **Cross-language compatibility**: When the receiver needs type information to deserialize correctly - -Type metadata is NOT needed when: - -1. **Final/concrete types**: The declared type is final/sealed and cannot be subclassed -2. **Primitive types**: Type is known at compile time -3. **Performance optimization**: When you know the concrete type always matches the declared type - -## Language-Specific Configuration - -### Java - -Java requires explicit configuration because concrete classes can be subclassed unless marked `final`. - -Use the `@ForyField` annotation with the `dynamic` parameter: - -```java -import org.apache.fory.annotation.ForyField; -import org.apache.fory.annotation.ForyField.Dynamic; - -public class Container { - // AUTO (default): Interface types write type info, concrete types don't - @ForyField(id = 0) - private Shape shape; // Interface - type info written - - // FALSE: Never write type info (use declared type's serializer) - @ForyField(id = 1, dynamic = Dynamic.FALSE) - private Circle circle; // Always treated as Circle - - // TRUE: Always write type info (support subtypes) - @ForyField(id = 2, dynamic = Dynamic.TRUE) - private Shape concreteShape; // Type info written even if concrete -} -``` - -**Dynamic Options**: - -| Value | Behavior | -| ------- | ------------------------------------------------------ | -| `AUTO` | Interface/abstract types are dynamic, concrete are not | -| `FALSE` | Never write type info, use declared type's serializer | -| `TRUE` | Always write type info to support subtypes | - -**Use Cases**: - -- `AUTO`: Default behavior, suitable for most cases -- `FALSE`: Performance optimization when you know the exact type -- `TRUE`: When a concrete field may hold subclass instances - -### C++ - -C++ uses the `.dynamic(bool)` builder method inside `FORY_STRUCT`: - -```cpp -#include "fory/serialization/fory.h" - -// Abstract base class with pure virtual methods -struct Animal { - virtual ~Animal() = default; - virtual std::string speak() const = 0; -}; - -struct Zoo { - // Auto: type info written because Animal is polymorphic (std::is_polymorphic) - std::shared_ptr animal; - - // Force non-dynamic: skip type info even though Animal is polymorphic - std::shared_ptr fixed_animal; - - // Force dynamic: write type info even for non-polymorphic types - std::shared_ptr polymorphic_data; -}; -FORY_STRUCT(Zoo, - (animal, fory::F(0).nullable()), // Auto-detect polymorphism - (fixed_animal, fory::F(1).nullable().dynamic(false)), // Skip type info - (polymorphic_data, fory::F(2).dynamic(true)) // Force type info -); -``` - -**Default Behavior**: Fory auto-detects polymorphism via `std::is_polymorphic`. Types with pure virtual methods are treated as dynamic by default. - -### Go and Rust - -Go and Rust do **not** require explicit dynamic configuration because: - -- **Go**: Interface types are inherently dynamic - Fory can determine from the type whether it's an interface -- **Rust**: Trait objects (`dyn Trait`) are explicitly marked in the type system - -The type system in these languages already indicates whether a field is polymorphic: - -```go -// Go: interface types are automatically dynamic -type Container struct { - Shape Shape // Interface - type info written automatically - Circle Circle // Concrete struct - no type info needed -} -``` - -```rust -// Rust: trait objects are explicitly marked -struct Container { - shape: Box, // Trait object - type info written automatically - circle: Circle, // Concrete type - no type info needed -} -``` - -### Python - -Use `pyfory.field()` with the `dynamic` parameter: - -```python -from dataclasses import dataclass -from abc import ABC, abstractmethod -import pyfory - -class Shape(ABC): - @abstractmethod - def area(self) -> float: - pass - -@dataclass -class Circle(Shape): - radius: float = 0.0 - - def area(self) -> float: - return 3.14159 * self.radius * self.radius - -@dataclass -class Container: - # Abstract class: dynamic is always True (type info written) - shape: Shape = pyfory.field(id=0) - - # Concrete type with explicit dynamic=True (force type info) - circle: Circle = pyfory.field(id=1, dynamic=True) - - # Concrete type with explicit dynamic=False (skip type info) - fixed_circle: Circle = pyfory.field(id=2, dynamic=False) -``` - -**Default Behavior**: - -| Mode | Abstract Class | Concrete Object Types | Numeric/str/time Types | -| ----------- | -------------- | --------------------- | ---------------------- | -| Native mode | `True` | `True` | `False` | -| Xlang mode | `True` | `False` | `False` | - -- **Abstract classes**: `dynamic` is always `True` (type info must be written) -- **Native mode**: `dynamic` defaults to `True` for object types, `False` for numeric/str/time types -- **Xlang mode**: `dynamic` defaults to `False` for concrete types - -## Default Behavior - -| Language | Interface/Abstract Types | Concrete Types | -| -------- | ------------------------ | ---------------- | -| Java | Dynamic (write type) | Static (no type) | -| C++ | Dynamic (virtual) | Static | -| Go | Dynamic (interface) | Static (struct) | -| Rust | Dynamic (dyn Trait) | Static | -| Python | Dynamic (all objects) | Dynamic | - -## Performance Considerations - -Writing type metadata has overhead: - -- **Space**: Type information adds bytes to serialized output -- **Time**: Type resolution during serialization/deserialization - -Use `dynamic = FALSE` (Java) or `dynamic(false)` (C++) when: - -- You're certain the concrete type matches the declared type -- Performance is critical and polymorphism is not needed -- The field type is effectively final - -## Cross-Language Compatibility - -When serializing data for cross-language consumption: - -1. **Use consistent type registration**: Register types with the same ID across languages -2. **Prefer explicit configuration**: Use `dynamic = TRUE` when unsure about receiver's expectations -3. **Document polymorphic fields**: Make it clear which fields may contain subtypes - -## Example: Polymorphic Container - -### Java - -```java -public interface Animal { - String speak(); -} - -public class Dog implements Animal { - private String name; - - @Override - public String speak() { return "Woof!"; } -} - -public class Cat implements Animal { - private String name; - - @Override - public String speak() { return "Meow!"; } -} - -public class Zoo { - // Type info written because Animal is an interface - @ForyField(id = 0) - private Animal animal; - - // Force type info for concrete type that may hold subtypes - @ForyField(id = 1, dynamic = Dynamic.TRUE) - private Dog maybeMixedBreed; -} -``` - -### C++ - -```cpp -// Abstract base class with pure virtual methods -class Animal { -public: - virtual std::string speak() const = 0; - virtual ~Animal() = default; -}; - -class Dog : public Animal { -public: - std::string name; - std::string speak() const override { return "Woof!"; } -}; - -struct Zoo { - std::shared_ptr animal; - std::shared_ptr maybe_mixed_breed; -}; - -FORY_STRUCT(Zoo, - (animal, fory::F(0).nullable()), // Auto-detect (Animal is polymorphic) - (maybe_mixed_breed, fory::F(1).dynamic(true)) // Force dynamic for concrete type -); -``` - -## Related Topics - -- [Field Nullability](nullability.md) - Controlling null handling for fields -- [Reference Tracking](references.md) - Managing shared/circular references -- [Type Mapping](../../specification/xlang_type_mapping.md) - Cross-language type compatibility - -## Coordinate Type Identity Across Peers - -Every peer must register a custom type with the same numeric ID or the same -namespace and type name. Numeric IDs produce smaller metadata; names are -easier to coordinate across independently deployed services. Do not mix an -ID registration on one peer with a name registration on another. - -Use the selected runtime guide for the exact registration API: -[Java](../java/core-api.md#cross-language-interoperability), [Python](../python/core-api.md#cross-language-interoperability), -[C++](../cpp/core-api.md#cross-language-interoperability), [Go](../go/core-api.md#cross-language-interoperability), -[Rust](../rust/core-api.md#cross-language-interoperability), [JavaScript](../javascript/core-api.md#cross-language-interoperability), -[C#](../csharp/core-api.md#cross-language-interoperability), [Swift](../swift/core-api.md#cross-language-interoperability), -[Dart](../dart/core-api.md#cross-language-interoperability), [Scala](../scala/core-api.md#cross-language-interoperability), and -[Kotlin](../kotlin/core-api.md#cross-language-interoperability). diff --git a/docs/object-serialization/xlang/type-system.md b/docs/object-serialization/xlang/type-system.md deleted file mode 100644 index ac711295e8..0000000000 --- a/docs/object-serialization/xlang/type-system.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Type System -sidebar_position: 1 -id: type-system -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -## Serialize Built-in Types - -Common types can be serialized automatically without registration: primitive numeric types, string, binary, array, list, map, and more. - -Reduced-precision floating-point values are also part of the built-in xlang type system: - -- `float16` and `array` -- `bfloat16` and `array` - -Use the language-specific carrier types documented in the type mapping reference. Python uses `pyfory.Float16` and `pyfory.BFloat16` as annotation markers only; scalar values are native Python `float`, and dense reduced-precision arrays use `pyfory.Float16Array` and `pyfory.BFloat16Array`. Go uses the `float16` and `bfloat16` packages for scalar, slice, and array carriers; JavaScript uses `number` for scalar `float16` and `bfloat16`, and dense array carriers `BoolArray`, `Float16Array`, and `BFloat16Array` for the corresponding `array` schemas. Dart uses `double` plus `Float16Type` or `Bfloat16Type` metadata for scalar fields, and `Float16List` / `Bfloat16List` for dense arrays. Java uses `@ArrayType` on supported reduced-precision carriers for `array` / `array` schema, while general object arrays stay on the `list` path; C++, Rust, and C# provide their own dedicated scalar and array carriers. - -When `compatible=true`, a direct struct/class field can evolve between `list` and `array` for dense bool/numeric `T`. Integer list element encodings in the same signedness and width domain match the corresponding dense array element domain. This applies only to the immediate matched field schema. It does not apply to nested collection, map, array, union, or generic positions. A peer `list` schema can be read into a local `array` field when the actual payload has no null elements. If the payload carries a null element or ref-tracked element encoding, reading it into a local `array` field raises a compatible-read error. - -## Serialize Custom Types - -User-defined types must be registered using the register API to establish the mapping relationship between types in different languages. Use consistent type names across all languages. - -## Exact mappings - -The normative [xlang type mapping](../../specification/xlang_type_mapping.md) defines the exact -carrier mapping for every runtime. Runtime pages show the API syntax and examples for that mapping. diff --git a/docs/object-serialization/xlang/zero-copy.md b/docs/object-serialization/xlang/zero-copy.md deleted file mode 100644 index 49c6f07a64..0000000000 --- a/docs/object-serialization/xlang/zero-copy.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -title: Zero-Copy Serialization -sidebar_position: 7 -id: zero-copy -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---- - -Zero-copy serialization allows large binary data (byte arrays, numeric arrays) to be serialized out-of-band, avoiding memory copies and reducing serialization overhead. - -## When to Use Zero-Copy - -Use zero-copy serialization when: - -- Serializing large byte arrays or binary blobs -- Working with numeric arrays (int[], double[], etc.) -- Transferring data over high-performance networks -- Memory efficiency is critical - -## How It Works - -1. **Serialization**: Large buffers are extracted and returned separately via a callback -2. **Transport**: The main serialized data and buffer objects are transmitted separately -3. **Deserialization**: Buffers are provided back to reconstruct the original object - -This avoids copying large data into the main serialization buffer. - -## Java - -```java -import org.apache.fory.*; -import org.apache.fory.config.*; -import org.apache.fory.serializer.BufferObject; -import org.apache.fory.memory.MemoryBuffer; - -import java.util.*; -import java.util.stream.Collectors; - -public class ZeroCopyExample { - public static void main(String[] args) { - Fory fory = Fory.builder().withXlang(true).build(); - - // Data with large arrays - List list = List.of( - "str", - new byte[1000], // Large byte array - new int[100], // Large int array - new double[100] // Large double array - ); - - // Collect buffer objects during serialization - Collection bufferObjects = new ArrayList<>(); - byte[] bytes = fory.serialize(list, e -> !bufferObjects.add(e)); - - // Convert to buffers for transport - List buffers = bufferObjects.stream() - .map(BufferObject::toBuffer) - .collect(Collectors.toList()); - - // Deserialize with buffers - Object result = fory.deserialize(bytes, buffers); - System.out.println(result); - } -} -``` - -## Python - -```python -import array -import pyfory -import numpy as np - -fory = pyfory.Fory(xlang=True) - -# Data with large arrays -data = [ - "str", - bytes(bytearray(1000)), # Large byte array - array.array("i", range(100)), # Large int array - np.full(100, 0.0, dtype=np.double) # Large numpy array -] - -# Collect buffer objects during serialization -serialized_objects = [] -serialized_data = fory.serialize(data, buffer_callback=serialized_objects.append) - -# Convert to buffers for transport -buffers = [obj.to_buffer() for obj in serialized_objects] - -# Deserialize with buffers -result = fory.deserialize(serialized_data, buffers=buffers) -print(result) -``` - -## Go - -```go -package main - -import forygo "github.com/apache/fory/go/fory" -import "fmt" - -func main() { - serializer := forygo.NewFory(forygo.WithXlang(true)) - - // Data with large arrays - list := []any{ - "str", - make([]byte, 1000), // Large byte array - } - - buf := forygo.NewByteBuffer(nil) - var bufferObjects []forygo.BufferObject - - // Collect buffer objects during serialization - if err := serializer.SerializeWithCallback(buf, list, func(o forygo.BufferObject) bool { - bufferObjects = append(bufferObjects, o) - return false - }); err != nil { - panic(err) - } - - // Convert to buffers for transport - var buffers []*forygo.ByteBuffer - for _, o := range bufferObjects { - buffers = append(buffers, o.ToBuffer()) - } - - // Deserialize with buffers - var newList []any - if err := serializer.DeserializeWithCallbackBuffers(buf, &newList, buffers); err != nil { - panic(err) - } - fmt.Println(newList) -} -``` - -## Use Cases - -### High-Performance Data Transfer - -When sending large datasets over the network: - -```java -// Sender -Collection buffers = new ArrayList<>(); -byte[] metadata = fory.serialize(dataObject, e -> !buffers.add(e)); - -// Send metadata and buffers separately -network.sendMetadata(metadata); -for (BufferObject buf : buffers) { - network.sendBuffer(buf.toBuffer()); -} - -// Receiver -byte[] metadata = network.receiveMetadata(); -List buffers = network.receiveBuffers(); -Object data = fory.deserialize(metadata, buffers); -``` - -### Memory-Mapped Files - -Zero-copy works well with memory-mapped files: - -```java -// Write -Collection buffers = new ArrayList<>(); -byte[] data = fory.serialize(largeObject, e -> !buffers.add(e)); -writeToFile("data.bin", data); -for (int i = 0; i < buffers.size(); i++) { - writeToFile("buffer" + i + ".bin", buffers.get(i).toBuffer()); -} - -// Read -byte[] data = readFromFile("data.bin"); -List buffers = readBufferFiles(); -Object result = fory.deserialize(data, buffers); -``` - -## Performance Considerations - -1. **Threshold**: Small arrays may not benefit from zero-copy due to callback overhead -2. **Network**: Zero-copy is most beneficial when buffers can be sent without copying -3. **Memory**: Reduces peak memory usage by avoiding buffer copies - -## See Also - -- [Xlang Overview](index.md) - Standard serialization workflow and runtime guides -- [Python Out-of-Band Guide](../python/out-of-band.md) - Python-specific zero-copy details From 914679bec5a3387c079623a705e175ced604d58c Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 11:49:07 +0800 Subject: [PATCH 11/15] docs: refine object serialization navigation --- .agents/docs-and-formatting.md | 2 +- .github/sync.yml | 3 - AGENTS.md | 19 +- CONTRIBUTING.md | 6 +- README.md | 2 +- SECURITY.md | 7 +- ci/test_validate_fory_site_sync.py | 50 +++ ci/validate_fory_site_sync.py | 15 + csharp/README.md | 2 +- docs/development/cpp-debugging.md | 6 +- .../jmp_generate_file.png | Bin .../vscode_debug_fory.jpg | Bin .../vscode_select_debug_run.png | Bin docs/json/index.md | 2 +- docs/object-serialization/core-concepts.md | 122 +++++++ .../{core-api.md => basic-serialization.md} | 2 +- .../object-serialization/cpp/configuration.md | 16 +- .../cpp/custom-serializers.md | 4 +- docs/object-serialization/cpp/index.md | 7 +- docs/object-serialization/cpp/native.md | 6 +- docs/object-serialization/cpp/polymorphism.md | 2 +- .../cpp/schema-evolution.md | 2 +- docs/object-serialization/cpp/security.md | 57 ++++ .../cpp/supported-types.md | 4 +- .../cpp/type-registration.md | 4 +- .../{core-api.md => basic-serialization.md} | 2 +- .../csharp/configuration.md | 13 +- .../csharp/external-types.md | 2 +- docs/object-serialization/csharp/index.md | 31 +- .../object-serialization/csharp/references.md | 2 +- docs/object-serialization/csharp/security.md | 56 +++ .../csharp/supported-types.md | 4 +- .../csharp/type-registration.md | 4 +- .../{core-api.md => basic-serialization.md} | 2 +- .../dart/configuration.md | 15 +- .../dart/custom-serializers.md | 2 +- docs/object-serialization/dart/index.md | 5 +- .../dart/schema-evolution.md | 2 +- .../dart/schema-metadata.md | 2 +- docs/object-serialization/dart/security.md | 56 +++ .../dart/supported-types.md | 2 +- .../dart/troubleshooting.md | 2 +- .../dart/type-registration.md | 4 +- .../{core-api.md => basic-serialization.md} | 2 +- docs/object-serialization/go/configuration.md | 10 +- .../go/custom-serializers.md | 2 +- docs/object-serialization/go/index.md | 35 +- docs/object-serialization/go/native.md | 4 +- docs/object-serialization/go/references.md | 2 +- .../go/schema-evolution.md | 2 +- .../go/schema-metadata.md | 2 +- docs/object-serialization/go/security.md | 53 +++ .../go/supported-types.md | 4 +- docs/object-serialization/go/thread-safety.md | 2 +- .../go/troubleshooting.md | 2 +- .../go/type-registration.md | 4 +- docs/object-serialization/index.md | 24 +- .../java/advanced-features.md | 2 +- .../{core-api.md => basic-serialization.md} | 6 +- .../java/configuration.md | 38 +-- docs/object-serialization/java/index.md | 6 +- docs/object-serialization/java/native.md | 6 +- docs/object-serialization/java/object-copy.md | 2 +- .../java/schema-evolution.md | 2 +- .../java/schema-metadata.md | 4 +- docs/object-serialization/java/security.md | 83 +++++ .../{core-api.md => basic-serialization.md} | 2 +- .../javascript/configuration.md | 17 +- docs/object-serialization/javascript/index.md | 27 +- .../javascript/references.md | 4 +- .../javascript/schema-evolution.md | 4 +- .../javascript/security.md | 60 ++++ .../javascript/supported-types.md | 4 +- .../javascript/troubleshooting.md | 4 +- .../javascript/type-registration.md | 6 +- .../{core-api.md => basic-serialization.md} | 2 +- .../kotlin/configuration.md | 29 +- docs/object-serialization/kotlin/index.md | 7 +- docs/object-serialization/kotlin/native.md | 2 +- docs/object-serialization/kotlin/security.md | 74 ++++ docs/object-serialization/native.md | 2 +- .../{core-api.md => basic-serialization.md} | 6 +- .../python/configuration.md | 120 +------ .../python/custom-serializers.md | 2 +- docs/object-serialization/python/index.md | 7 +- docs/object-serialization/python/native.md | 4 +- .../python/numpy-integration.md | 2 +- .../python/out-of-band.md | 2 +- .../python/schema-evolution.md | 2 +- .../python/schema-metadata.md | 4 +- docs/object-serialization/python/security.md | 163 +++++++++ .../{core-api.md => basic-serialization.md} | 6 +- .../rust/configuration.md | 17 +- docs/object-serialization/rust/index.md | 7 +- docs/object-serialization/rust/native.md | 8 +- docs/object-serialization/rust/references.md | 2 +- .../rust/schema-evolution.md | 2 +- .../rust/schema-metadata.md | 4 +- docs/object-serialization/rust/security.md | 58 ++++ .../rust/type-registration.md | 2 +- .../{core-api.md => basic-serialization.md} | 2 +- .../scala/configuration.md | 29 +- docs/object-serialization/scala/index.md | 7 +- docs/object-serialization/scala/native.md | 2 +- .../scala/schema-metadata.md | 2 +- docs/object-serialization/scala/security.md | 74 ++++ .../{core-api.md => basic-serialization.md} | 2 +- .../swift/configuration.md | 11 +- docs/object-serialization/swift/index.md | 5 +- docs/object-serialization/swift/security.md | 56 +++ docs/object-serialization/xlang.md | 30 +- docs/row-format/cpp.md | 2 +- docs/row-format/java.md | 2 +- docs/row-format/python.md | 4 +- docs/row-format/rust.md | 2 +- .../deserialization.md} | 323 +++++------------- docs/security/index.md | 22 ++ docs/security/threat-model.md | 95 ++++++ docs/start/cpp.md | 2 +- docs/start/csharp.md | 2 +- docs/start/go.md | 2 +- docs/start/java.md | 2 +- docs/start/javascript.md | 2 +- docs/start/kotlin.md | 2 +- docs/start/python.md | 2 +- docs/start/rust.md | 2 +- docs/start/scala.md | 2 +- docs/start/swift.md | 2 +- go/fory/README.md | 4 +- {docs/images => images}/idea_jdk11.png | Bin .../logo/fory-horizontal-black.png | Bin .../logo/fory-horizontal-black1.png | Bin .../logo/fory-horizontal-white.png | Bin .../logo/fory-horizontal-white1.png | Bin .../logo/fory-horizontal.png | Bin .../logo/fory-horizontal1.png | Bin .../logo/fory-icon-black.png | Bin .../logo/fory-icon-white.png | Bin {docs/images => images}/logo/fory-icon.png | Bin .../logo/fory-vertical-black.png | Bin .../logo/fory-vertical-black1.png | Bin .../logo/fory-vertical-white.png | Bin .../logo/fory-vertical-white1.png | Bin .../images => images}/logo/fory-vertical.png | Bin .../images => images}/logo/fory-vertical1.png | Bin javascript/README.md | 2 +- javascript/packages/core/README.md | 2 +- swift/README.md | 2 +- 148 files changed, 1446 insertions(+), 759 deletions(-) create mode 100644 ci/test_validate_fory_site_sync.py rename docs/{images => development}/jmp_generate_file.png (100%) rename docs/{images => development}/vscode_debug_fory.jpg (100%) rename docs/{images => development}/vscode_select_debug_run.png (100%) create mode 100644 docs/object-serialization/core-concepts.md rename docs/object-serialization/cpp/{core-api.md => basic-serialization.md} (99%) create mode 100644 docs/object-serialization/cpp/security.md rename docs/object-serialization/csharp/{core-api.md => basic-serialization.md} (99%) create mode 100644 docs/object-serialization/csharp/security.md rename docs/object-serialization/dart/{core-api.md => basic-serialization.md} (99%) create mode 100644 docs/object-serialization/dart/security.md rename docs/object-serialization/go/{core-api.md => basic-serialization.md} (99%) create mode 100644 docs/object-serialization/go/security.md rename docs/object-serialization/java/{core-api.md => basic-serialization.md} (98%) create mode 100644 docs/object-serialization/java/security.md rename docs/object-serialization/javascript/{core-api.md => basic-serialization.md} (99%) create mode 100644 docs/object-serialization/javascript/security.md rename docs/object-serialization/kotlin/{core-api.md => basic-serialization.md} (98%) create mode 100644 docs/object-serialization/kotlin/security.md rename docs/object-serialization/python/{core-api.md => basic-serialization.md} (98%) create mode 100644 docs/object-serialization/python/security.md rename docs/object-serialization/rust/{core-api.md => basic-serialization.md} (98%) create mode 100644 docs/object-serialization/rust/security.md rename docs/object-serialization/scala/{core-api.md => basic-serialization.md} (99%) create mode 100644 docs/object-serialization/scala/security.md rename docs/object-serialization/swift/{core-api.md => basic-serialization.md} (99%) create mode 100644 docs/object-serialization/swift/security.md rename docs/{object-serialization/security.md => security/deserialization.md} (77%) create mode 100644 docs/security/index.md create mode 100644 docs/security/threat-model.md rename {docs/images => images}/idea_jdk11.png (100%) rename {docs/images => images}/logo/fory-horizontal-black.png (100%) rename {docs/images => images}/logo/fory-horizontal-black1.png (100%) rename {docs/images => images}/logo/fory-horizontal-white.png (100%) rename {docs/images => images}/logo/fory-horizontal-white1.png (100%) rename {docs/images => images}/logo/fory-horizontal.png (100%) rename {docs/images => images}/logo/fory-horizontal1.png (100%) rename {docs/images => images}/logo/fory-icon-black.png (100%) rename {docs/images => images}/logo/fory-icon-white.png (100%) rename {docs/images => images}/logo/fory-icon.png (100%) rename {docs/images => images}/logo/fory-vertical-black.png (100%) rename {docs/images => images}/logo/fory-vertical-black1.png (100%) rename {docs/images => images}/logo/fory-vertical-white.png (100%) rename {docs/images => images}/logo/fory-vertical-white1.png (100%) rename {docs/images => images}/logo/fory-vertical.png (100%) rename {docs/images => images}/logo/fory-vertical1.png (100%) diff --git a/.agents/docs-and-formatting.md b/.agents/docs-and-formatting.md index 2cc34ce8f5..e4d2d60dda 100644 --- a/.agents/docs-and-formatting.md +++ b/.agents/docs-and-formatting.md @@ -37,7 +37,7 @@ Load this file when changing documentation, public APIs, protocol specs, benchma - Keep user-facing configuration text action focused. Do not put cache ownership, cold/hot path, publish ordering, exact-local schema bypass, or TypeDef/TypeMeta routing invariants there; put implementation invariants in `docs/specification/xlang_implementation_guide.md`, - security classifications in `docs/object-serialization/security.md`, and agent-only workflow rules in + security classifications in `docs/security/deserialization.md`, and agent-only workflow rules in `.agents/**`. - Documentation examples should use normal explicit imports, avoid unused imports, and keep common Fory types unqualified where that is the idiom. - Generated Markdown under `docs/benchmarks/**` should satisfy markdownlint blank-line rules at generation time: no repeated blank lines and no extra blank line after final content. diff --git a/.github/sync.yml b/.github/sync.yml index 05c5290b32..8ef8a16576 100644 --- a/.github/sync.yml +++ b/.github/sync.yml @@ -45,9 +45,6 @@ apache/fory-site@main: - source: docs/development/ dest: docs/development/ deleteOrphaned: true - - source: docs/images/ - dest: docs/images/ - deleteOrphaned: true - source: docs/specification/ dest: docs/specification/ deleteOrphaned: true diff --git a/AGENTS.md b/AGENTS.md index 56ea9bf3b4..1d458373f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,11 +9,11 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th - `.agents/docs-and-formatting.md`: documentation, specification, and markdown rules. - `.agents/ci-and-pr.md`: code review workflow, CI triage, PR expectations, and commit conventions. - `.agents/testing/integration-tests.md`: `integration_tests/` prerequisites, regeneration rules, and commands. -- `docs/object-serialization/security.md`: user-facing security guidance for binary object - serialization. +- `docs/security/index.md`: contributor-facing security model index. This directory is internal + documentation and is intentionally excluded from the website sync. +- `docs/security/threat-model.md`: project trust boundaries and downstream responsibilities. +- `docs/security/deserialization.md`: implementation boundaries for untrusted deserialization. - `docs/json/security.md`: user-facing security guidance for Fory JSON. -- `docs/object-serialization/security.md`: implementation boundaries for - untrusted deserialization classification. - `.agents/languages/java.md` - `.agents/languages/csharp.md` - `.agents/languages/cpp.md` @@ -34,7 +34,7 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th - Respect ownership. Keep logic, state, and helpers in their natural owner, and do not move serializer-local, context-local, runtime-type-local, or protocol-local problems into global utilities. - Check the spec before implementation. For wire behavior and xlang mapping, use the specs as the source of truth and never copy one runtime's bug into another runtime just to make tests pass. - Do not make assumptions about runtime behavior, ownership, registration, metadata construction, protocol semantics, or test coverage. Read the current code, owning docs/specs, and relevant tests before making a design judgment or implementation decision. If the evidence is incomplete, inspect more or state the uncertainty explicitly instead of filling gaps from memory or analogy with another runtime. -- For untrusted deserialization, read `docs/object-serialization/security.md` before changing allocation, stream filling, skip, reference, metadata, or policy validation behavior. Variable-length deserialization must not allocate or reserve backing/output capacity from attacker-declared lengths or counts before the byte owner has proven proportional readable bytes with `checkReadableBytes` or the runtime equivalent. Root graph memory reservation is accounting only and may happen before that byte check, but it must not replace the byte check. +- For untrusted deserialization, read `docs/security/deserialization.md` before changing allocation, stream filling, skip, reference, metadata, or policy validation behavior. Variable-length deserialization must not allocate or reserve backing/output capacity from attacker-declared lengths or counts before the byte owner has proven proportional readable bytes with `checkReadableBytes` or the runtime equivalent. Root graph memory reservation is accounting only and may happen before that byte check, but it must not replace the byte check. - Malformed input must surface as a controlled root-operation error and still run root cleanup, but the exact exception type, error code, message, detection layer, and detection point are not contracts unless a public API or @@ -331,7 +331,8 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th ## Security -User-facing security guidance lives only under Object Serialization and Fory JSON. Read -`docs/object-serialization/security.md` or `docs/json/security.md` for the selected product. Before -reporting or changing allocation, stream filling, skip, reference, metadata, or policy validation -behavior, read `docs/object-serialization/security.md`. +User-facing object-serialization security guidance lives in each runtime directory, such as +`docs/object-serialization/java/security.md`; Fory JSON guidance lives in `docs/json/security.md`. +Contributor-facing security models remain under `docs/security/` and are excluded from the website. +Before reporting or changing allocation, stream filling, skip, reference, metadata, or policy +validation behavior, read `docs/security/deserialization.md`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1cd0c63552..ba8bfa6f03 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ Key points: ## Testing -For environmental requirements, please check [DEVELOPMENT.md](./docs/development/building.md). +For environmental requirements, see the [Development guide](./docs/development/index.md). ### Python @@ -229,7 +229,7 @@ To use Jetbrains IDEA IDE for Java Development, you need to configure the projec And due to the usage of `sun.misc.Unsafe` API, which is not visible in Java 11+, you need to configure java compiler with `--releaese` option disabled.
-
+
## Website @@ -244,4 +244,4 @@ If you want write a blog, or update other contents about the website, please sub ## Development -For more information, please refer to [Development Guide](./docs/development/building.md). +For more information, see the [Development guide](./docs/development/index.md). diff --git a/README.md b/README.md index 46b42df8ad..6d49cfa4c7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
- Apache Fory logo
+ Apache Fory logo
[![Build Status](https://img.shields.io/github/actions/workflow/status/apache/fory/ci.yml?branch=main&style=for-the-badge&label=GITHUB%20ACTIONS&logo=github)](https://github.com/apache/fory/actions/workflows/ci.yml) diff --git a/SECURITY.md b/SECURITY.md index d83379eae6..f90a9fafc2 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -24,10 +24,11 @@ GitHub issues or pull requests for security reports. ## Security Models -User-facing guidance is product-specific: +User-facing guidance is capability- and runtime-specific: -- [Object Serialization Security](docs/object-serialization/security.md) +- [Object Serialization runtime guides](docs/object-serialization/index.md) (each runtime section + ends with its own Security page) - [Fory JSON Security](docs/json/security.md) For detailed implementation classification rules for untrusted deserialization, see the -[Deserialization Security Model](docs/object-serialization/deserialization-security-model.md). +[Deserialization Security Model](docs/security/deserialization.md). diff --git a/ci/test_validate_fory_site_sync.py b/ci/test_validate_fory_site_sync.py new file mode 100644 index 0000000000..d641597ef7 --- /dev/null +++ b/ci/test_validate_fory_site_sync.py @@ -0,0 +1,50 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import pathlib +import tempfile +import unittest + +if __package__: + from . import validate_fory_site_sync +else: + import validate_fory_site_sync + + +class ForySiteSyncTest(unittest.TestCase): + def test_rejects_forbidden_doc_roots(self): + for source, dest in ( + ("docs/security/", "docs/security/"), + ("docs/images/", "docs/images/"), + ("docs/development/", "docs/security/"), + ): + content = f"""apache/fory-site@main: + - source: {source} + dest: {dest} +""" + with ( + self.subTest(source=source, dest=dest), + tempfile.TemporaryDirectory() as directory, + ): + sync_file = pathlib.Path(directory) / "sync.yml" + sync_file.write_text(content, encoding="utf-8") + with self.assertRaisesRegex(RuntimeError, "must not be synced"): + validate_fory_site_sync.parse_sync_mappings(sync_file) + + +if __name__ == "__main__": + unittest.main() diff --git a/ci/validate_fory_site_sync.py b/ci/validate_fory_site_sync.py index cce69c0411..e57e4e63ba 100644 --- a/ci/validate_fory_site_sync.py +++ b/ci/validate_fory_site_sync.py @@ -26,6 +26,17 @@ from typing import List, Tuple TARGET_REPO = "apache/fory-site@main" +FORBIDDEN_SYNC_ROOTS = ( + pathlib.PurePosixPath("docs/security"), + pathlib.PurePosixPath("docs/images"), +) + + +def is_forbidden_sync_path(path: str) -> bool: + candidate = pathlib.PurePosixPath(path.rstrip("/")) + return any( + candidate == root or root in candidate.parents for root in FORBIDDEN_SYNC_ROOTS + ) def parse_sync_mappings(sync_file: pathlib.Path) -> List[Tuple[str, str]]: @@ -55,6 +66,10 @@ def parse_sync_mappings(sync_file: pathlib.Path) -> List[Tuple[str, str]]: dest_match = re.match(r"^dest:\s*(.+)$", stripped) if dest_match and source: dest = dest_match.group(1).strip().strip("'\"") + if is_forbidden_sync_path(source) or is_forbidden_sync_path(dest): + raise RuntimeError( + f"path must not be synced to fory-site: {source} -> {dest}" + ) mappings.append((source, dest)) source = None diff --git a/csharp/README.md b/csharp/README.md index 1deff49129..690a428bd4 100644 --- a/csharp/README.md +++ b/csharp/README.md @@ -127,7 +127,7 @@ public sealed class User : Entity An inaccessible base member is serialized only when it has `[ForyField]`. Unmodifiable third-party bases use an external `BaseOnly` declaration. See the -[class inheritance guide](https://fory.apache.org/docs/object-serialization/csharp/core-api/#class-inheritance). +[class inheritance guide](https://fory.apache.org/docs/object-serialization/csharp/basic-serialization/#class-inheritance). ### 3. Shared and Circular References diff --git a/docs/development/cpp-debugging.md b/docs/development/cpp-debugging.md index 360eef894b..761a972e42 100644 --- a/docs/development/cpp-debugging.md +++ b/docs/development/cpp-debugging.md @@ -18,9 +18,9 @@ license: | limitations under the License. --- -import JumpGeneratedFile from "@site/docs/images/jmp_generate_file.png"; -import VscodeDebugFory from "@site/docs/images/vscode_debug_fory.jpg"; -import VscodeSelectDebugRun from "@site/docs/images/vscode_select_debug_run.png"; +import JumpGeneratedFile from "@site/docs/development/jmp_generate_file.png"; +import VscodeDebugFory from "@site/docs/development/vscode_debug_fory.jpg"; +import VscodeSelectDebugRun from "@site/docs/development/vscode_select_debug_run.png"; ## Debugging C++ diff --git a/docs/images/jmp_generate_file.png b/docs/development/jmp_generate_file.png similarity index 100% rename from docs/images/jmp_generate_file.png rename to docs/development/jmp_generate_file.png diff --git a/docs/images/vscode_debug_fory.jpg b/docs/development/vscode_debug_fory.jpg similarity index 100% rename from docs/images/vscode_debug_fory.jpg rename to docs/development/vscode_debug_fory.jpg diff --git a/docs/images/vscode_select_debug_run.png b/docs/development/vscode_select_debug_run.png similarity index 100% rename from docs/images/vscode_select_debug_run.png rename to docs/development/vscode_select_debug_run.png diff --git a/docs/json/index.md b/docs/json/index.md index 4b18647d3a..47a2e3683f 100644 --- a/docs/json/index.md +++ b/docs/json/index.md @@ -63,6 +63,6 @@ native or xlang protocol when reference identity or cycles are required. ## Related Java guides For binary serialization, start with [Java Object Serialization](../object-serialization/java/index.md) -and choose [xlang](../object-serialization/java/core-api.md#cross-language-interoperability) or +and choose [xlang](../object-serialization/java/basic-serialization.md#cross-language-interoperability) or [native](../object-serialization/java/native.md). Binary builder options are documented separately in [Java Configuration](../object-serialization/java/configuration.md). diff --git a/docs/object-serialization/core-concepts.md b/docs/object-serialization/core-concepts.md new file mode 100644 index 0000000000..c122e6d450 --- /dev/null +++ b/docs/object-serialization/core-concepts.md @@ -0,0 +1,122 @@ +--- +title: Core Concepts +sidebar_position: 1 +id: core-concepts +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Fory object serialization turns an object graph into bytes and reconstructs that graph later. The +same concepts apply to the default [xlang mode](xlang.md) and to supported +[native modes](native.md); the selected mode determines which types and wire rules are available. + +## Object graphs + +A root value may contain scalar fields, collections, maps, nested objects, repeated references, and +cycles. Serialization walks that graph from the root. Deserialization creates a new graph from the +encoded type and field data. + +This is different from serializing a row or a JSON document. Object serialization can preserve +runtime types and object identity so the reader can reconstruct application objects rather than +only values. Use [Row Format](../row-format/index.md) for trusted analytical rows and +[Fory JSON](../json/index.md) for JSON interchange. + +## Runtime instances and registration + +A Fory instance owns its mode, schema behavior, reference settings, registered types, custom +serializers, and read limits. Configure and register the instance before its first root +serialization or deserialization operation, then reuse it. Registration is frozen after the first +root operation so the same instance always resolves a type in the same way. + +Thread-safety differs by runtime. Some runtimes provide a thread-safe wrapper or pool; others use +one instance per thread or task. Follow the selected language guide instead of sharing an ordinary +instance without checking its concurrency contract. + +## Types and type identity + +Built-in types have identities owned by Fory. Application structs, classes, enums, unions, and +extension types use a registered numeric ID or name. Type identity answers _which serializer and +model should read this value_; a field schema describes _what data that model contains_. + +A statically known field can use its declared type directly. A dynamic field also carries the +concrete runtime type needed for interfaces, abstract classes, trait objects, broad object types, +or heterogeneous values. Dynamic typing is more flexible but requires every possible concrete type +to be registered and supported by the selected mode. + +In xlang mode, peers must coordinate the same portable type identity and mapping. Native mode may +use runtime-specific identities and types. See [Xlang Serialization](xlang.md) for the portable +rules and each language's Type Registration page for its exact API. + +## Schemas and evolution + +A schema describes the fields and nested types of a structured value. Compatible mode carries +metadata that lets a reader handle supported additions, removals, reordering, and type adaptations. +Use it when readers and writers may deploy independently. + +Same-schema mode assumes both sides use the same type identity, fields, nested types, nullability, +and reference metadata. It reduces metadata and payload size, but a schema mismatch is an error. +Use it only when one release process keeps every reader and writer aligned. + +Field IDs or names should remain stable after a contract is published. Renaming or reusing an +identity can turn an intended evolution into a different field or type. + +## Nullability + +Nullability determines whether a value position may contain no value. Languages express it through +nullable references, option types, pointers, annotations, or schema metadata. A nullable field is +not the same as a field whose value happens to use a default. + +Keep nullability consistent across readers and writers. Compatible mode can handle documented +nullable and missing-field cases, but it cannot place a remote null into a local carrier that has +no valid null or missing-value representation. + +## Reference tracking + +Reference tracking preserves object identity. Enable it when a graph contains the same object more +than once or contains a cycle. Without reference tracking, repeated values may become separate +objects and cycles may recurse until the operation fails. + +Leave reference tracking disabled for value-shaped, acyclic data when identity does not matter; it +adds per-object metadata and lookup work. Some runtimes combine a global setting with field-level +metadata, so use the language-specific References or Basic Serialization page for exact behavior. + +## Polymorphism + +Polymorphism stores the concrete type of a value whose declared position is broader. The reader +must know and accept that concrete type, and the type must be representable in the selected mode. + +Host-language inheritance alone does not create a portable contract. For cross-language data, +model only alternatives that have xlang mappings on every peer. For same-runtime data, native mode +may support additional runtime-specific class, trait, or hook behavior. + +## Custom serializers + +Use a custom serializer when a type needs a representation that built-in schema inference cannot +provide. Registration connects the custom serializer to the application type. A custom serializer +must follow the selected mode's rules: xlang serializers need a portable representation, while +native serializers may use runtime-specific data and hooks. + +Prefer built-in serializers and generated models when they already describe the type. They keep +schema evolution, reference handling, and cross-language behavior easier to reason about. + +## Continue with a mode + +- [Xlang Serialization](xlang.md) is the default and is required when different language runtimes + exchange bytes. +- [Native Serialization](native.md) is for supported same-runtime use cases that need native types + or behavior. +- Choose a language section after selecting a mode to find installation, API, configuration, + registration, platform, security, and troubleshooting guidance. diff --git a/docs/object-serialization/cpp/core-api.md b/docs/object-serialization/cpp/basic-serialization.md similarity index 99% rename from docs/object-serialization/cpp/core-api.md rename to docs/object-serialization/cpp/basic-serialization.md index 4b2965dfd4..275ad53ed4 100644 --- a/docs/object-serialization/cpp/core-api.md +++ b/docs/object-serialization/cpp/basic-serialization.md @@ -1,7 +1,7 @@ --- title: Basic Serialization sidebar_position: 1 -id: core-api +id: basic-serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with diff --git a/docs/object-serialization/cpp/configuration.md b/docs/object-serialization/cpp/configuration.md index c33f3563b8..78c77939d2 100644 --- a/docs/object-serialization/cpp/configuration.md +++ b/docs/object-serialization/cpp/configuration.md @@ -252,20 +252,10 @@ auto fory = Fory::builder().build_thread_safe(); // Returns ThreadSafeFory ## Security -Security-related configuration: - -- Register all structs and polymorphic implementations before deserializing untrusted payloads. -- Use `check_struct_version(true)` with `compatible(false)` for intentional same-schema payloads. -- Keep `max_graph_memory_bytes(...)` at the fixed `128 MiB` default for most inputs, or set a - positive value for a trusted workload that needs a different collection/map/struct gate. -- Keep `max_dyn_depth(...)` as low as your model permits to reject unexpectedly deep polymorphic - graphs. -- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a - trusted peer sends larger metadata or many schema versions. -- Prefer concrete fields over broad polymorphic fields for untrusted input. +See [C++ Security](security.md) for trust boundaries, safe reader configuration, and verification. ## Related Topics -- [Basic Serialization](core-api.md) - Using configured Fory -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - xlang mode details +- [Basic Serialization](basic-serialization.md) - Using configured Fory +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - xlang mode details - [Type Registration](type-registration.md) - Registering types diff --git a/docs/object-serialization/cpp/custom-serializers.md b/docs/object-serialization/cpp/custom-serializers.md index 2604191a5f..089e10df08 100644 --- a/docs/object-serialization/cpp/custom-serializers.md +++ b/docs/object-serialization/cpp/custom-serializers.md @@ -366,6 +366,6 @@ static MyType read_data(ReadContext &ctx) { ## Related Topics - [Type Registration](type-registration.md) - Registering serializers -- [Basic Serialization](core-api.md) - Using FORY_STRUCT macro +- [Basic Serialization](basic-serialization.md) - Using FORY_STRUCT macro - [Schema Evolution](schema-evolution.md) - Compatible mode -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language serialization +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Cross-language serialization diff --git a/docs/object-serialization/cpp/index.md b/docs/object-serialization/cpp/index.md index e0ce640444..a18ee4f393 100644 --- a/docs/object-serialization/cpp/index.md +++ b/docs/object-serialization/cpp/index.md @@ -222,7 +222,7 @@ Use xlang mode for cross-language payloads and schemas shared with other Fory im Use native mode for C++-only traffic. Native mode is selected with `.xlang(false)` and keeps C++ object serialization in C++-native form. It is optimized for C++ types and avoids portable xlang type-mapping constraints when the payload never leaves C++. Compatible mode is enabled by default. Set `.compatible(false)` only when every reader and writer uses the same C++ schema and you want faster serialization and smaller size. -See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for C++ xlang registration and interoperability rules, and [Native Serialization](native.md) for C++-only payloads. +See [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) for C++ xlang registration and interoperability rules, and [Native Serialization](native.md) for C++-only payloads. ## Thread Safety @@ -264,7 +264,7 @@ std::thread t2([&]() { ## Next Steps - [Configuration](configuration.md) - Builder options and modes -- [Basic Serialization](core-api.md) - Default xlang object graphs and interoperability +- [Basic Serialization](basic-serialization.md) - Default xlang object graphs and interoperability - [Native Serialization](native.md) - C++-only serialization - [Schema Metadata](schema-metadata.md) - Field-level metadata (nullable, ref tracking) - [Schema Evolution](schema-evolution.md) - Compatible mode and schema changes @@ -273,3 +273,6 @@ std::thread t2([&]() { - [Custom Serializers](custom-serializers.md) - Extend serialization behavior - [Row Format](../../row-format/cpp.md) - Zero-copy row-based format - [gRPC Support](../../grpc/cpp.md) - Fory payloads over gRPC C++ + +Before decoding bytes from outside the application trust boundary, read +[C++ Security](security.md). diff --git a/docs/object-serialization/cpp/native.md b/docs/object-serialization/cpp/native.md index c648a38e37..70df2a91ba 100644 --- a/docs/object-serialization/cpp/native.md +++ b/docs/object-serialization/cpp/native.md @@ -23,7 +23,7 @@ C++ native serialization is the C++-only wire mode selected with `.xlang(false)` writer and reader is C++ and the payload should follow C++ type behavior instead of the portable xlang type system. -Use [Cross-Language Interoperability](core-api.md#cross-language-interoperability), the default C++ mode, when +Use [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability), the default C++ mode, when bytes must be read by Java, Python, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another non-C++ Fory implementation. @@ -206,9 +206,9 @@ Enable `.track_ref(true)` and verify the graph uses supported pointer patterns. ## Related Topics -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language C++ payloads +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Cross-language C++ payloads - [Configuration](configuration.md) - Builder options -- [Basic Serialization](core-api.md) - Object graph serialization +- [Basic Serialization](basic-serialization.md) - Object graph serialization - [Supported Types](supported-types.md) - C++ type support - [Polymorphic Serialization](polymorphism.md) - Polymorphic object models - [Schema Evolution](schema-evolution.md) - Compatible mode diff --git a/docs/object-serialization/cpp/polymorphism.md b/docs/object-serialization/cpp/polymorphism.md index 3f609ad280..383ba7c36d 100644 --- a/docs/object-serialization/cpp/polymorphism.md +++ b/docs/object-serialization/cpp/polymorphism.md @@ -487,4 +487,4 @@ if (!decoded_result.ok()) { - [Schema Metadata](schema-metadata.md) - Field-level metadata and options - [Supported Types](supported-types.md) - Smart pointers and collections - [Configuration](configuration.md) - `max_dyn_depth` and other settings -- [Basic Serialization](core-api.md) - Core serialization concepts +- [Basic Serialization](basic-serialization.md) - Core serialization concepts diff --git a/docs/object-serialization/cpp/schema-evolution.md b/docs/object-serialization/cpp/schema-evolution.md index 15cd31f372..4b7ab928f8 100644 --- a/docs/object-serialization/cpp/schema-evolution.md +++ b/docs/object-serialization/cpp/schema-evolution.md @@ -421,4 +421,4 @@ Both instances can exchange data even with different schema versions. - [Configuration](configuration.md) - Enabling compatible mode - [Type Registration](type-registration.md) - Type ID management -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language considerations +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Cross-language considerations diff --git a/docs/object-serialization/cpp/security.md b/docs/object-serialization/cpp/security.md new file mode 100644 index 0000000000..fe86e37d2d --- /dev/null +++ b/docs/object-serialization/cpp/security.md @@ -0,0 +1,57 @@ +--- +title: Security +sidebar_position: 99 +id: security +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Use this page when a C++ reader accepts bytes from outside the application's trust boundary. +Fory reconstructs application values; it does not authenticate the sender, protect transport +integrity, or decide whether a valid value is authorized for a business operation. + +## Application boundary + +Before deserialization: + +- Authenticate the sender and protect message integrity at the transport or storage layer. +- Enforce request or file size, timeout, and concurrency limits outside Fory. +- Register only the application types the endpoint accepts and configure the reader before its + first root operation. +- Validate the deserialized value against application authorization and domain rules before use. + +## Runtime safeguards + +Security-related configuration: + +- Register all structs and polymorphic implementations before deserializing untrusted payloads. +- Use `check_struct_version(true)` with `compatible(false)` for intentional same-schema payloads. +- Keep `max_graph_memory_bytes(...)` at the fixed `128 MiB` default for most inputs, or set a + positive value for a trusted workload that needs a different collection/map/struct gate. +- Keep `max_dyn_depth(...)` as low as your model permits to reject unexpectedly deep polymorphic + graphs. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. +- Prefer concrete fields over broad polymorphic fields for untrusted input. + +## Verification + +Add negative tests for the boundary as well as normal round trips. Verify that the configured reader +rejects unexpected application types, excessive nesting, resource-limit violations, and malformed +input. After a failed read, verify that a valid root can still be read with the reusable runtime. + +See [Configuration](configuration.md) for the complete option reference and +[Type Registration](type-registration.md) for the runtime's registration API. diff --git a/docs/object-serialization/cpp/supported-types.md b/docs/object-serialization/cpp/supported-types.md index c77b0ee6ba..4c95f5307e 100644 --- a/docs/object-serialization/cpp/supported-types.md +++ b/docs/object-serialization/cpp/supported-types.md @@ -295,6 +295,6 @@ Currently not supported: ## Related Topics -- [Basic Serialization](core-api.md) - Using these types +- [Basic Serialization](basic-serialization.md) - Using these types - [Type Registration](type-registration.md) - Registering types -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language compatibility +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Cross-language compatibility diff --git a/docs/object-serialization/cpp/type-registration.md b/docs/object-serialization/cpp/type-registration.md index dd45edb151..62f525ef94 100644 --- a/docs/object-serialization/cpp/type-registration.md +++ b/docs/object-serialization/cpp/type-registration.md @@ -249,6 +249,6 @@ if (!result.ok()) { ## Related Topics -- [Basic Serialization](core-api.md) - Using registered types -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language considerations +- [Basic Serialization](basic-serialization.md) - Using registered types +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Cross-language considerations - [Supported Types](supported-types.md) - All supported types diff --git a/docs/object-serialization/csharp/core-api.md b/docs/object-serialization/csharp/basic-serialization.md similarity index 99% rename from docs/object-serialization/csharp/core-api.md rename to docs/object-serialization/csharp/basic-serialization.md index 7f52d8fffa..7c77560a7d 100644 --- a/docs/object-serialization/csharp/core-api.md +++ b/docs/object-serialization/csharp/basic-serialization.md @@ -1,7 +1,7 @@ --- title: Basic Serialization sidebar_position: 1 -id: core-api +id: basic-serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with diff --git a/docs/object-serialization/csharp/configuration.md b/docs/object-serialization/csharp/configuration.md index 2d45d409d9..5bac8df215 100644 --- a/docs/object-serialization/csharp/configuration.md +++ b/docs/object-serialization/csharp/configuration.md @@ -200,19 +200,10 @@ ThreadSafeFory fory = Fory.Builder() ## Security -Security-related configuration: - -- Register only the expected types before deserializing untrusted payloads. -- Use `CheckStructVersion(true)` with `Compatible(false)` for intentional same-schema payloads. -- Set `MaxDepth(...)` to reject unexpectedly deep dynamic object graphs. -- Set `MaxGraphMemoryBytes(...)` as an approximate gate for collection, map, array, struct, and - object-heavy payloads. It is not an exact heap cap; leaf values are gated by remaining input bytes. -- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a - trusted peer sends larger metadata or many schema versions. -- Prefer generated or registered concrete models over broad dynamic fields for untrusted input. +See [C# Security](security.md) for trust boundaries, safe reader configuration, and verification. ## Related Topics -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [Schema Evolution](schema-evolution.md) - [Thread Safety](thread-safety.md) diff --git a/docs/object-serialization/csharp/external-types.md b/docs/object-serialization/csharp/external-types.md index b1802f07be..0b1e18fd1a 100644 --- a/docs/object-serialization/csharp/external-types.md +++ b/docs/object-serialization/csharp/external-types.md @@ -220,7 +220,7 @@ long as each concrete annotated child has a legal parameterless construction path. First-party bases use direct `[ForyStruct]` annotations instead. See -[Class Inheritance](core-api.md#class-inheritance). +[Class Inheritance](basic-serialization.md#class-inheritance). ## Declaration and Target Requirements diff --git a/docs/object-serialization/csharp/index.md b/docs/object-serialization/csharp/index.md index f38f1175b4..0b98a6df9f 100644 --- a/docs/object-serialization/csharp/index.md +++ b/docs/object-serialization/csharp/index.md @@ -86,23 +86,26 @@ User decoded = fory.Deserialize(payload); ## Documentation -| Topic | Description | -| ------------------------------------------- | --------------------------------------------- | -| [Configuration](configuration.md) | Builder options and mode settings | -| [Basic Serialization](core-api.md) | Default xlang APIs and interoperability | -| [Schema Metadata](schema-metadata.md) | `[ForyField]` ids and schema type descriptors | -| [Type Registration](type-registration.md) | Registering user types and custom serializers | -| [External Types](external-types.md) | Serializers for third-party types | -| [Custom Serializers](custom-serializers.md) | Implementing `Serializer` | -| [References](references.md) | Shared/circular reference handling | -| [Schema Evolution](schema-evolution.md) | Compatible mode behavior | -| [Supported Types](supported-types.md) | Built-in and generated type support | -| [Thread Safety](thread-safety.md) | `Fory` vs `ThreadSafeFory` usage | -| [gRPC Support](../../grpc/csharp.md) | Generated Fory-backed gRPC service companions | -| [Troubleshooting](troubleshooting.md) | Common errors and debugging steps | +| Topic | Description | +| --------------------------------------------- | --------------------------------------------- | +| [Configuration](configuration.md) | Builder options and mode settings | +| [Basic Serialization](basic-serialization.md) | Default xlang APIs and interoperability | +| [Schema Metadata](schema-metadata.md) | `[ForyField]` ids and schema type descriptors | +| [Type Registration](type-registration.md) | Registering user types and custom serializers | +| [External Types](external-types.md) | Serializers for third-party types | +| [Custom Serializers](custom-serializers.md) | Implementing `Serializer` | +| [References](references.md) | Shared/circular reference handling | +| [Schema Evolution](schema-evolution.md) | Compatible mode behavior | +| [Supported Types](supported-types.md) | Built-in and generated type support | +| [Thread Safety](thread-safety.md) | `Fory` vs `ThreadSafeFory` usage | +| [gRPC Support](../../grpc/csharp.md) | Generated Fory-backed gRPC service companions | +| [Troubleshooting](troubleshooting.md) | Common errors and debugging steps | ## Related Resources - [Xlang serialization specification](../../specification/xlang_serialization_spec.md) - [Cross-language guide](../xlang.md) - [C# source directory](https://github.com/apache/fory/tree/main/csharp) + +Before decoding bytes from outside the application trust boundary, read +[C# Security](security.md). diff --git a/docs/object-serialization/csharp/references.md b/docs/object-serialization/csharp/references.md index 00d044ba07..723503136b 100644 --- a/docs/object-serialization/csharp/references.md +++ b/docs/object-serialization/csharp/references.md @@ -76,6 +76,6 @@ supported; Fory rejects unresolved refs instead of returning a partial union. ## Related Topics - [Configuration](configuration.md) -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [External Types](external-types.md) - [Thread Safety](thread-safety.md) diff --git a/docs/object-serialization/csharp/security.md b/docs/object-serialization/csharp/security.md new file mode 100644 index 0000000000..06416a73e0 --- /dev/null +++ b/docs/object-serialization/csharp/security.md @@ -0,0 +1,56 @@ +--- +title: Security +sidebar_position: 99 +id: security +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Use this page when a C# reader accepts bytes from outside the application's trust boundary. +Fory reconstructs application values; it does not authenticate the sender, protect transport +integrity, or decide whether a valid value is authorized for a business operation. + +## Application boundary + +Before deserialization: + +- Authenticate the sender and protect message integrity at the transport or storage layer. +- Enforce request or file size, timeout, and concurrency limits outside Fory. +- Register only the application types the endpoint accepts and configure the reader before its + first root operation. +- Validate the deserialized value against application authorization and domain rules before use. + +## Runtime safeguards + +Security-related configuration: + +- Register only the expected types before deserializing untrusted payloads. +- Use `CheckStructVersion(true)` with `Compatible(false)` for intentional same-schema payloads. +- Set `MaxDepth(...)` to reject unexpectedly deep dynamic object graphs. +- Set `MaxGraphMemoryBytes(...)` as an approximate gate for collection, map, array, struct, and + object-heavy payloads. It is not an exact heap cap; leaf values are gated by remaining input bytes. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. +- Prefer generated or registered concrete models over broad dynamic fields for untrusted input. + +## Verification + +Add negative tests for the boundary as well as normal round trips. Verify that the configured reader +rejects unexpected application types, excessive nesting, resource-limit violations, and malformed +input. After a failed read, verify that a valid root can still be read with the reusable runtime. + +See [Configuration](configuration.md) for the complete option reference and +[Type Registration](type-registration.md) for the runtime's registration API. diff --git a/docs/object-serialization/csharp/supported-types.md b/docs/object-serialization/csharp/supported-types.md index 8f32e20925..bf4f0ad08d 100644 --- a/docs/object-serialization/csharp/supported-types.md +++ b/docs/object-serialization/csharp/supported-types.md @@ -111,7 +111,7 @@ Dynamic object payloads via `Serialize` / `Deserialize` suppor ## Related Topics -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [External Types](external-types.md) - [Type Registration](type-registration.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/csharp/type-registration.md b/docs/object-serialization/csharp/type-registration.md index 9a48292154..0c6b629310 100644 --- a/docs/object-serialization/csharp/type-registration.md +++ b/docs/object-serialization/csharp/type-registration.md @@ -94,7 +94,7 @@ fory.Register(101); ## Related Topics -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [External Types](external-types.md) - [Custom Serializers](custom-serializers.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/dart/core-api.md b/docs/object-serialization/dart/basic-serialization.md similarity index 99% rename from docs/object-serialization/dart/core-api.md rename to docs/object-serialization/dart/basic-serialization.md index dddb46beda..0b28adb02b 100644 --- a/docs/object-serialization/dart/core-api.md +++ b/docs/object-serialization/dart/basic-serialization.md @@ -1,7 +1,7 @@ --- title: Basic Serialization sidebar_position: 1 -id: core-api +id: basic-serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with diff --git a/docs/object-serialization/dart/configuration.md b/docs/object-serialization/dart/configuration.md index 7a58c1f2a4..7094e9b4a9 100644 --- a/docs/object-serialization/dart/configuration.md +++ b/docs/object-serialization/dart/configuration.md @@ -162,19 +162,10 @@ When Fory is used to communicate between services written in different languages ## Security -Security-related configuration: - -- Register only the expected generated models before deserializing untrusted payloads. -- Use `checkStructVersion: true` with `compatible: false` for intentional same-schema payloads. -- Set `maxDepth` to reject unexpectedly deep payload shapes. -- Keep `maxGraphMemoryBytes` at the default for most inputs, or set an explicit positive byte gate - for known trusted collection/map/struct-heavy payloads. -- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a - trusted peer sends larger metadata or many schema versions. -- Prefer generated schemas and explicit field metadata over broad dynamic fields for untrusted input. +See [Dart Security](security.md) for trust boundaries, safe reader configuration, and verification. ## Related Topics -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [Schema Evolution](schema-evolution.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/dart/custom-serializers.md b/docs/object-serialization/dart/custom-serializers.md index ea148777c9..55ae78a846 100644 --- a/docs/object-serialization/dart/custom-serializers.md +++ b/docs/object-serialization/dart/custom-serializers.md @@ -140,5 +140,5 @@ Skipping this step causes back-references to that object to resolve to `null`. - [Type Registration](type-registration.md) - [External-Type Serialization](external-types.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - [Troubleshooting](troubleshooting.md) diff --git a/docs/object-serialization/dart/index.md b/docs/object-serialization/dart/index.md index b1132bebff..e43822d760 100644 --- a/docs/object-serialization/dart/index.md +++ b/docs/object-serialization/dart/index.md @@ -140,7 +140,7 @@ constructors, mixins, and field inclusion options. | Topic | Description | | ------------------------------------------------ | -------------------------------------------------------------- | | [Configuration](configuration.md) | Fory options, compatible mode, and safety limits | -| [Basic Serialization](core-api.md) | Default xlang APIs, registration, and interoperability | +| [Basic Serialization](basic-serialization.md) | Default xlang APIs, registration, and interoperability | | [Code Generation](code-generation.md) | `@ForyStruct`, build runner, and generated modules | | [Struct Inheritance](inheritance.md) | Superclasses, mixins, private fields, and constructors | | [External-Type Serialization](external-types.md) | Generated serializers for classes owned by another package | @@ -159,3 +159,6 @@ constructors, mixins, and field inclusion options. - [Xlang implementation guide](../../specification/xlang_implementation_guide.md) - [Cross-language guide](../xlang.md) - [Dart implementation source directory](https://github.com/apache/fory/tree/main/dart) + +Before decoding bytes from outside the application trust boundary, read +[Dart Security](security.md). diff --git a/docs/object-serialization/dart/schema-evolution.md b/docs/object-serialization/dart/schema-evolution.md index 01b889aeb1..6f290413a1 100644 --- a/docs/object-serialization/dart/schema-evolution.md +++ b/docs/object-serialization/dart/schema-evolution.md @@ -132,4 +132,4 @@ runtime reference protocol or add a compatibility reader. - [Configuration](configuration.md) - [External-Type Serialization](external-types.md) - [Schema Metadata](schema-metadata.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/dart/schema-metadata.md b/docs/object-serialization/dart/schema-metadata.md index 31d76e4861..3073cf6bb8 100644 --- a/docs/object-serialization/dart/schema-metadata.md +++ b/docs/object-serialization/dart/schema-metadata.md @@ -169,4 +169,4 @@ When the same model is defined in multiple languages: - [Code Generation](code-generation.md) - [External-Type Serialization](external-types.md) - [Schema Evolution](schema-evolution.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/dart/security.md b/docs/object-serialization/dart/security.md new file mode 100644 index 0000000000..4367148291 --- /dev/null +++ b/docs/object-serialization/dart/security.md @@ -0,0 +1,56 @@ +--- +title: Security +sidebar_position: 99 +id: security +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Use this page when a Dart reader accepts bytes from outside the application's trust boundary. +Fory reconstructs application values; it does not authenticate the sender, protect transport +integrity, or decide whether a valid value is authorized for a business operation. + +## Application boundary + +Before deserialization: + +- Authenticate the sender and protect message integrity at the transport or storage layer. +- Enforce request or file size, timeout, and concurrency limits outside Fory. +- Register only the application types the endpoint accepts and configure the reader before its + first root operation. +- Validate the deserialized value against application authorization and domain rules before use. + +## Runtime safeguards + +Security-related configuration: + +- Register only the expected generated models before deserializing untrusted payloads. +- Use `checkStructVersion: true` with `compatible: false` for intentional same-schema payloads. +- Set `maxDepth` to reject unexpectedly deep payload shapes. +- Keep `maxGraphMemoryBytes` at the default for most inputs, or set an explicit positive byte gate + for known trusted collection/map/struct-heavy payloads. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. +- Prefer generated schemas and explicit field metadata over broad dynamic fields for untrusted input. + +## Verification + +Add negative tests for the boundary as well as normal round trips. Verify that the configured reader +rejects unexpected application types, excessive nesting, resource-limit violations, and malformed +input. After a failed read, verify that a valid root can still be read with the reusable runtime. + +See [Configuration](configuration.md) for the complete option reference and +[Type Registration](type-registration.md) for the runtime's registration API. diff --git a/docs/object-serialization/dart/supported-types.md b/docs/object-serialization/dart/supported-types.md index 9445d87a92..52c17b9b7c 100644 --- a/docs/object-serialization/dart/supported-types.md +++ b/docs/object-serialization/dart/supported-types.md @@ -173,5 +173,5 @@ width is one of the most common cross-language bugs. - [Struct Inheritance](inheritance.md) - [Schema Metadata](schema-metadata.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - [Schema Evolution](schema-evolution.md) diff --git a/docs/object-serialization/dart/troubleshooting.md b/docs/object-serialization/dart/troubleshooting.md index f98bd033f2..038ee4c9d6 100644 --- a/docs/object-serialization/dart/troubleshooting.md +++ b/docs/object-serialization/dart/troubleshooting.md @@ -217,7 +217,7 @@ separate protobuf service endpoint for generic protobuf clients. ## Related Topics - [Struct Inheritance](inheritance.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - [Code Generation](code-generation.md) - [Custom Serializers](custom-serializers.md) - [Web Platform Support](web-platform-support.md) diff --git a/docs/object-serialization/dart/type-registration.md b/docs/object-serialization/dart/type-registration.md index 7a7e7e1da5..2364c81bd6 100644 --- a/docs/object-serialization/dart/type-registration.md +++ b/docs/object-serialization/dart/type-registration.md @@ -119,12 +119,12 @@ See [Custom Serializers](custom-serializers.md) for how to implement a serialize ## Xlang Requirements -The same numeric ID or name must be used in every peer that reads or writes the type. See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for examples. +The same numeric ID or name must be used in every peer that reads or writes the type. See [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) for examples. ## Related Topics - [Struct Inheritance](inheritance.md) - [Code Generation](code-generation.md) - [External-Type Serialization](external-types.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - [Custom Serializers](custom-serializers.md) diff --git a/docs/object-serialization/go/core-api.md b/docs/object-serialization/go/basic-serialization.md similarity index 99% rename from docs/object-serialization/go/core-api.md rename to docs/object-serialization/go/basic-serialization.md index ba11553b0d..e5b19b3fc5 100644 --- a/docs/object-serialization/go/core-api.md +++ b/docs/object-serialization/go/basic-serialization.md @@ -1,7 +1,7 @@ --- title: Basic Serialization sidebar_position: 1 -id: core-api +id: basic-serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with diff --git a/docs/object-serialization/go/configuration.md b/docs/object-serialization/go/configuration.md index 1e94af8942..e3de7416ed 100644 --- a/docs/object-serialization/go/configuration.md +++ b/docs/object-serialization/go/configuration.md @@ -417,17 +417,11 @@ for req := range requests { ## Security -Security-related configuration: - -- Register only the expected structs before deserializing untrusted data. -- Use `WithMaxDepth(...)` to reject unexpectedly deep payloads. -- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a - trusted peer sends larger metadata or many schema versions. -- Prefer concrete struct fields over broad `any` or interface-typed fields for untrusted input. +See [Go Security](security.md) for trust boundaries, safe reader configuration, and verification. ## Related Topics -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [References](references.md) - [Schema Evolution](schema-evolution.md) - [Thread Safety](thread-safety.md) diff --git a/docs/object-serialization/go/custom-serializers.md b/docs/object-serialization/go/custom-serializers.md index 919aa29ea3..3f809fd32b 100644 --- a/docs/object-serialization/go/custom-serializers.md +++ b/docs/object-serialization/go/custom-serializers.md @@ -282,4 +282,4 @@ func TestMySerializer(t *testing.T) { - [Type Registration](type-registration.md) - [Supported Types](supported-types.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/go/index.md b/docs/object-serialization/go/index.md index 70aabe14fd..a4e11d7e18 100644 --- a/docs/object-serialization/go/index.md +++ b/docs/object-serialization/go/index.md @@ -91,7 +91,7 @@ Use xlang mode for cross-language payloads and schemas shared with other Fory im Use native mode for Go-only traffic. Native mode is selected with `fory.WithXlang(false)` and keeps Go object serialization in Go-native form. It is optimized for Go structs, pointers, interfaces, and Go-specific type behavior that does not need a portable xlang mapping. Compatible mode is enabled by default. Set `fory.WithCompatible(false)` only when every reader and writer uses the same Go struct schema and you want faster serialization and smaller size. -See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for Go xlang registration and interoperability rules, and [Native Serialization](native.md) for Go-only payloads. +See [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) for Go xlang registration and interoperability rules, and [Native Serialization](native.md) for Go-only payloads. ## Configuration @@ -133,27 +133,30 @@ data, _ := f.Serialize(&User{ID: 1, Name: "Alice"}) // 'data' can be deserialized by Java, Python, etc. ``` -See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for type mapping and compatibility details. +See [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) for type mapping and compatibility details. ## Documentation -| Topic | Description | -| ------------------------------------------- | --------------------------------------- | -| [Basic Serialization](core-api.md) | Default xlang APIs and interoperability | -| [Native Serialization](native.md) | Go-only serialization | -| [Configuration](configuration.md) | Options and settings | -| [Schema Metadata](schema-metadata.md) | Field-level configuration | -| [Type Registration](type-registration.md) | Registering types for serialization | -| [Supported Types](supported-types.md) | Complete type support reference | -| [References](references.md) | Circular references and shared objects | -| [Schema Evolution](schema-evolution.md) | Forward/backward compatibility | -| [Custom Serializers](custom-serializers.md) | Extend serialization behavior | -| [Thread Safety](thread-safety.md) | Concurrent usage patterns | -| [gRPC Support](../../grpc/go.md) | Fory payloads over grpc-go | -| [Troubleshooting](troubleshooting.md) | Common issues and solutions | +| Topic | Description | +| --------------------------------------------- | --------------------------------------- | +| [Basic Serialization](basic-serialization.md) | Default xlang APIs and interoperability | +| [Native Serialization](native.md) | Go-only serialization | +| [Configuration](configuration.md) | Options and settings | +| [Schema Metadata](schema-metadata.md) | Field-level configuration | +| [Type Registration](type-registration.md) | Registering types for serialization | +| [Supported Types](supported-types.md) | Complete type support reference | +| [References](references.md) | Circular references and shared objects | +| [Schema Evolution](schema-evolution.md) | Forward/backward compatibility | +| [Custom Serializers](custom-serializers.md) | Extend serialization behavior | +| [Thread Safety](thread-safety.md) | Concurrent usage patterns | +| [gRPC Support](../../grpc/go.md) | Fory payloads over grpc-go | +| [Troubleshooting](troubleshooting.md) | Common issues and solutions | ## Related Resources - [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) - [Xlang Type Mapping](../../specification/xlang_type_mapping.md) - [GitHub Repository](https://github.com/apache/fory) + +Before decoding bytes from outside the application trust boundary, read +[Go Security](security.md). diff --git a/docs/object-serialization/go/native.md b/docs/object-serialization/go/native.md index 0c7bab97c7..cf72cb866c 100644 --- a/docs/object-serialization/go/native.md +++ b/docs/object-serialization/go/native.md @@ -23,7 +23,7 @@ Go native serialization is the Go-only wire mode selected with `fory.WithXlang(f when every writer and reader is a Go service and the payload should follow Go's type system instead of the portable xlang type system. -Use [Cross-Language Interoperability](core-api.md#cross-language-interoperability), the default Go mode, when bytes must be read by +Use [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability), the default Go mode, when bytes must be read by Java, Python, C++, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another non-Go Fory implementation. @@ -210,7 +210,7 @@ The default `Fory` instance reuses its buffer. Copy the byte slice or use `threa ## Related Topics -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language Go payloads +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Cross-language Go payloads - [Configuration](configuration.md) - Go options - [Type Registration](type-registration.md) - Struct and enum registration - [References](references.md) - Shared and circular references diff --git a/docs/object-serialization/go/references.md b/docs/object-serialization/go/references.md index 184a2e94ef..0469e33eb8 100644 --- a/docs/object-serialization/go/references.md +++ b/docs/object-serialization/go/references.md @@ -353,4 +353,4 @@ func main() { - [Configuration](configuration.md) - [Struct Tags](schema-metadata.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/go/schema-evolution.md b/docs/object-serialization/go/schema-evolution.md index 463f7f0b4f..6a8afb47ba 100644 --- a/docs/object-serialization/go/schema-evolution.md +++ b/docs/object-serialization/go/schema-evolution.md @@ -389,5 +389,5 @@ func main() { ## Related Topics - [Configuration](configuration.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - [Troubleshooting](troubleshooting.md) diff --git a/docs/object-serialization/go/schema-metadata.md b/docs/object-serialization/go/schema-metadata.md index 02ac8f5d09..878661dabb 100644 --- a/docs/object-serialization/go/schema-metadata.md +++ b/docs/object-serialization/go/schema-metadata.md @@ -389,5 +389,5 @@ type Session struct { ## Related Topics - [References](references.md) -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [Schema Evolution](schema-evolution.md) diff --git a/docs/object-serialization/go/security.md b/docs/object-serialization/go/security.md new file mode 100644 index 0000000000..916643e3e3 --- /dev/null +++ b/docs/object-serialization/go/security.md @@ -0,0 +1,53 @@ +--- +title: Security +sidebar_position: 99 +id: security +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Use this page when a Go reader accepts bytes from outside the application's trust boundary. +Fory reconstructs application values; it does not authenticate the sender, protect transport +integrity, or decide whether a valid value is authorized for a business operation. + +## Application boundary + +Before deserialization: + +- Authenticate the sender and protect message integrity at the transport or storage layer. +- Enforce request or file size, timeout, and concurrency limits outside Fory. +- Register only the application types the endpoint accepts and configure the reader before its + first root operation. +- Validate the deserialized value against application authorization and domain rules before use. + +## Runtime safeguards + +Security-related configuration: + +- Register only the expected structs before deserializing untrusted data. +- Use `WithMaxDepth(...)` to reject unexpectedly deep payloads. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. +- Prefer concrete struct fields over broad `any` or interface-typed fields for untrusted input. + +## Verification + +Add negative tests for the boundary as well as normal round trips. Verify that the configured reader +rejects unexpected application types, excessive nesting, resource-limit violations, and malformed +input. After a failed read, verify that a valid root can still be read with the reusable runtime. + +See [Configuration](configuration.md) for the complete option reference and +[Type Registration](type-registration.md) for the runtime's registration API. diff --git a/docs/object-serialization/go/supported-types.md b/docs/object-serialization/go/supported-types.md index cfbed67b36..c0098d6649 100644 --- a/docs/object-serialization/go/supported-types.md +++ b/docs/object-serialization/go/supported-types.md @@ -354,7 +354,7 @@ data, _ := f.Serialize(status) | `time.Time` | Instant | datetime | - | - | | `time.Duration` | Duration | timedelta | - | - | -See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for detailed mapping. +See [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) for detailed mapping. ## Unsupported Types @@ -370,5 +370,5 @@ Attempting to serialize these types will result in an error. ## Related Topics - [Type Registration](type-registration.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - [References](references.md) diff --git a/docs/object-serialization/go/thread-safety.md b/docs/object-serialization/go/thread-safety.md index 7d78e3bb9b..89b5367667 100644 --- a/docs/object-serialization/go/thread-safety.md +++ b/docs/object-serialization/go/thread-safety.md @@ -343,5 +343,5 @@ go func() { ## Related Topics - [Configuration](configuration.md) -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [Troubleshooting](troubleshooting.md) diff --git a/docs/object-serialization/go/troubleshooting.md b/docs/object-serialization/go/troubleshooting.md index 58e78b2f71..9c51ba6be0 100644 --- a/docs/object-serialization/go/troubleshooting.md +++ b/docs/object-serialization/go/troubleshooting.md @@ -438,6 +438,6 @@ If you encounter issues not covered here: ## Related Topics - [Configuration](configuration.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - [Schema Evolution](schema-evolution.md) - [Thread Safety](thread-safety.md) diff --git a/docs/object-serialization/go/type-registration.md b/docs/object-serialization/go/type-registration.md index 9020ae7a28..9367affdd3 100644 --- a/docs/object-serialization/go/type-registration.md +++ b/docs/object-serialization/go/type-registration.md @@ -258,7 +258,7 @@ Two types registered with the same ID will conflict. ## Related Topics -- [Basic Serialization](core-api.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Basic Serialization](basic-serialization.md) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - [Supported Types](supported-types.md) - [Troubleshooting](troubleshooting.md) diff --git a/docs/object-serialization/index.md b/docs/object-serialization/index.md index bd1dbffa32..02eeae901e 100644 --- a/docs/object-serialization/index.md +++ b/docs/object-serialization/index.md @@ -24,15 +24,25 @@ collections, polymorphic values, and optional shared references. ## Choose a mode -| Mode | Use it when | Start here | -| --------------- | ---------------------------------------------------- | ------------------------------------------- | -| Xlang (default) | Bytes cross runtime boundaries | [Cross-language interoperability](xlang.md) | -| Native | Every writer and reader uses the same runtime family | [Native serialization](native.md) | +| Mode | Use it when | Start here | +| --------------- | ---------------------------------------------------- | --------------------------------- | +| Xlang (default) | Bytes cross runtime boundaries | [Xlang Serialization](xlang.md) | +| Native | Every writer and reader uses the same runtime family | [Native serialization](native.md) | Xlang and native are the only object-serialization modes. Row Format is a random-access analytical representation, and Fory JSON is a Java JSON codec; use the [format chooser](../introduction/choose-a-format.md) when object reconstruction is not your goal. +## Read the concepts and modes + +Read these pages before choosing a runtime API: + +1. [Core Concepts](core-concepts.md) explains object graphs, types, schemas, references, + polymorphism, and schema evolution across both modes. +2. [Xlang Serialization](xlang.md) explains the portable format used by default and the rules that + different language runtimes must share. +3. [Native Serialization](native.md) explains when a same-runtime native format is appropriate. + ## Browse by runtime Choose a runtime to find its installation route, lifecycle, exact APIs, configuration, type @@ -52,12 +62,6 @@ registration, schema behavior, extensions, platforms, and troubleshooting: | Scala | xlang and JVM native | [Scala runtime](./scala/index.md) | | Kotlin | xlang and JVM native | [Kotlin runtime](./kotlin/index.md) | -## Security - -Before decoding externally supplied bytes, read [Security](security.md). It -covers accepted-type policy, registration, resource limits, transport responsibilities, and -negative verification for both modes. - ## Specifications - [Xlang serialization format](../specification/xlang_serialization_spec.md) diff --git a/docs/object-serialization/java/advanced-features.md b/docs/object-serialization/java/advanced-features.md index 5c1a8b4b25..ea7cce50bc 100644 --- a/docs/object-serialization/java/advanced-features.md +++ b/docs/object-serialization/java/advanced-features.md @@ -157,4 +157,4 @@ static { - [Configuration](configuration.md) - All ForyBuilder options - [Native Serialization](native.md) - Java-only serialization, JDK hooks, and zero-copy buffers - [Object Copy](object-copy.md) - Deep copy functionality -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Java xlang interoperability +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Java xlang interoperability diff --git a/docs/object-serialization/java/core-api.md b/docs/object-serialization/java/basic-serialization.md similarity index 98% rename from docs/object-serialization/java/core-api.md rename to docs/object-serialization/java/basic-serialization.md index b696beb941..70fb3ea7dc 100644 --- a/docs/object-serialization/java/core-api.md +++ b/docs/object-serialization/java/basic-serialization.md @@ -1,7 +1,7 @@ --- title: Basic Serialization sidebar_position: 1 -id: core-api +id: basic-serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -382,8 +382,8 @@ Xlang mode has additional overhead compared to Java native mode: - [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) - [Type Mapping Reference](../../specification/xlang_type_mapping.md) -- [Python Interoperability Guide](../python/core-api.md#cross-language-interoperability) -- [Rust Interoperability Guide](../rust/core-api.md#cross-language-interoperability) +- [Python Interoperability Guide](../python/basic-serialization.md#cross-language-interoperability) +- [Rust Interoperability Guide](../rust/basic-serialization.md#cross-language-interoperability) ### Related Guides diff --git a/docs/object-serialization/java/configuration.md b/docs/object-serialization/java/configuration.md index 0f1f44ae81..6463c69b74 100644 --- a/docs/object-serialization/java/configuration.md +++ b/docs/object-serialization/java/configuration.md @@ -86,43 +86,7 @@ For xlang payloads, call `withCompatible(false)` only after verifying that every ## Security -Keep class registration enabled for production and any untrusted payload source: - -```java -Fory fory = Fory.builder() - .requireClassRegistration(true) - .withMaxDepth(50) - .withMaxGraphMemoryBytes(128L * 1024 * 1024) - .withMaxUnbackedContainerItems(8192) - .build(); -``` - -Security-related options: - -- `requireClassRegistration(true)` restricts deserialization to registered classes. -- `withMaxDepth(...)` rejects unexpectedly deep object graphs. -- `withMaxGraphMemoryBytes(...)` sets an approximate gate for materialized graph memory during one - root deserialization. The estimate mainly covers collections, maps, arrays, structs, and objects; - Fory core primitive arrays and primitive lists count their primitive storage from the decoded - length. It skips leaf values such as strings, primitive scalars, and dedicated binary values that - do not use a primitive-array serializer. Actual process memory can be higher than this limit. Leaf - values remain protected by byte-availability checks: if the unread input does not contain enough - bytes, Fory will not read or create that leaf value. The default is a fixed `128 MiB`; set a - positive byte limit when trusted workloads need a larger or smaller gate. -- `withMaxUnbackedContainerItems(...)` limits count-driven collection and map work whose repeated - read bodies do not consume proportional input. The default is `8192`; zero is a strict limit. -- `withMaxTypeFields(...)` and `withMaxTypeMetaBytes(...)` bound the field count - and encoded body size of one received remote metadata body. -- `withMaxSchemaVersionsPerType(...)` and - `withMaxAverageSchemaVersionsPerType(...)` bound accepted remote metadata versions without - changing registration, dynamic loading, or schema-evolution semantics. -- `withDeserializeUnknownClass(false)` avoids materializing unknown classes from metadata. -- `checkJdkClassSerializable(true)` keeps the JDK serializability check for `java.*` classes. -- Class registration warnings can be useful during security audits; use - `suppressClassRegistrationWarnings(false)` when you need to surface unexpected types. - -Use `requireClassRegistration(false)` only for trusted payloads, and pair it with a `TypeChecker` -allow list when dynamic class loading is required. +See [Java Security](security.md) for trust boundaries, safe reader configuration, and verification. ## Related Topics diff --git a/docs/object-serialization/java/index.md b/docs/object-serialization/java/index.md index 55993d666a..2d5add91f8 100644 --- a/docs/object-serialization/java/index.md +++ b/docs/object-serialization/java/index.md @@ -169,7 +169,7 @@ Use xlang mode for cross-language payloads and schemas shared with non-Java impl Use native mode for Java-only traffic. Native mode is selected with `.withXlang(false)` and owns Java-specific object behavior such as JDK serialization hooks, `Externalizable`, dynamic object graphs, object copy, and Java native-mode zero-copy buffers. It is optimized for the JVM type system and supports a broader Java object surface than xlang mode. Compatible mode is enabled by default. Set `.withCompatible(false)` only when every reader and writer uses the same class schema and you want faster serialization and smaller size. If you are replacing JDK serialization, Kryo, FST, Hessian, or Java-only Protocol Buffers payloads, start with native mode. -See [Native Serialization](native.md) for Java-only serialization details and [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for Java xlang registration and interoperability rules. +See [Native Serialization](native.md) for Java-only serialization details and [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) for Java xlang registration and interoperability rules. ### Thread Safety @@ -257,10 +257,10 @@ model-discovery workflows differ from Fory Core. | Group | Pages | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Serialization modes | [Basic Serialization](core-api.md), [Native Serialization](native.md) | +| Serialization modes | [Basic Serialization](basic-serialization.md), [Native Serialization](native.md) | | Common | [Configuration](configuration.md), [Type Registration](type-registration.md), [Schema Evolution](schema-evolution.md), [Schema Metadata](schema-metadata.md), [Custom Serializers](custom-serializers.md) | | Java-specific features | [Advanced Features](advanced-features.md), [Compression](compression.md), [Object Copy](object-copy.md), [JDK Custom Serialization](jdk-serialization.md), [Static Generated Serializers](static-generated-serializers.md), [Virtual Threads](virtual-threads.md) | | Platform and operate | [Android](android.md), [GraalVM Native Image](graalvm.md), [Troubleshooting](troubleshooting.md) | Before decoding externally supplied binary payloads, read -[Object Serialization Security](../security.md). +[Java Security](security.md). diff --git a/docs/object-serialization/java/native.md b/docs/object-serialization/java/native.md index 06cf0af7be..6737b2f53d 100644 --- a/docs/object-serialization/java/native.md +++ b/docs/object-serialization/java/native.md @@ -28,7 +28,7 @@ payloads. Native serialization in this page means Fory's `xlang=false` wire mode. It is separate from GraalVM native image support, which is covered in [GraalVM Native Image](graalvm.md). -Use [Cross-Language Interoperability](core-api.md#cross-language-interoperability), the default Java mode, when bytes must be read by +Use [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability), the default Java mode, when bytes must be read by non-Java Fory implementations. ## When To Use Native Serialization @@ -326,8 +326,8 @@ Use `JavaSerializer.serializedByJDK(...)` only at the mixed-format boundary, the ## Related Topics -- [Basic Serialization](core-api.md) - Xlang-first Java quickstart -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language Java payloads +- [Basic Serialization](basic-serialization.md) - Xlang-first Java quickstart +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Cross-language Java payloads - [Configuration](configuration.md) - Java builder options - [Schema Evolution](schema-evolution.md) - compatible mode and same-schema optimization - [Type Registration](type-registration.md) - Registration and security diff --git a/docs/object-serialization/java/object-copy.md b/docs/object-serialization/java/object-copy.md index 002b79b334..b0c02b23ec 100644 --- a/docs/object-serialization/java/object-copy.md +++ b/docs/object-serialization/java/object-copy.md @@ -357,7 +357,7 @@ Fix it by either: ## Related Topics -- [Basic Serialization](core-api.md) - Fory instance creation and core APIs +- [Basic Serialization](basic-serialization.md) - Fory instance creation and core APIs - [Configuration](configuration.md) - Builder options including `withRefCopy` - [Custom Serializers](custom-serializers.md) - Serializer design and registration - [Virtual Threads](virtual-threads.md) - Thread-safe Fory guidance diff --git a/docs/object-serialization/java/schema-evolution.md b/docs/object-serialization/java/schema-evolution.md index 66620997db..9a2796306e 100644 --- a/docs/object-serialization/java/schema-evolution.md +++ b/docs/object-serialization/java/schema-evolution.md @@ -266,5 +266,5 @@ public class SameSchemaMessage { ## Related Topics - [Configuration](configuration.md) - All ForyBuilder options -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - xlang mode +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - xlang mode - [Troubleshooting](troubleshooting.md) - Common schema issues diff --git a/docs/object-serialization/java/schema-metadata.md b/docs/object-serialization/java/schema-metadata.md index 011b040bea..981e47184a 100644 --- a/docs/object-serialization/java/schema-metadata.md +++ b/docs/object-serialization/java/schema-metadata.md @@ -710,7 +710,7 @@ public class User { ## Related Topics -- [Basic Serialization](core-api.md) - Getting started with Fory serialization +- [Basic Serialization](basic-serialization.md) - Getting started with Fory serialization - [Configuration](configuration.md) - `ForyBuilder` options - [Schema Evolution](schema-evolution.md) - Compatible mode and schema evolution -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Interoperability with Python, Rust, C++, Go +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Interoperability with Python, Rust, C++, Go diff --git a/docs/object-serialization/java/security.md b/docs/object-serialization/java/security.md new file mode 100644 index 0000000000..780d39b2d2 --- /dev/null +++ b/docs/object-serialization/java/security.md @@ -0,0 +1,83 @@ +--- +title: Security +sidebar_position: 99 +id: security +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Use this page when a Java reader accepts bytes from outside the application's trust boundary. +Fory reconstructs application values; it does not authenticate the sender, protect transport +integrity, or decide whether a valid value is authorized for a business operation. + +## Application boundary + +Before deserialization: + +- Authenticate the sender and protect message integrity at the transport or storage layer. +- Enforce request or file size, timeout, and concurrency limits outside Fory. +- Register only the application types the endpoint accepts and configure the reader before its + first root operation. +- Validate the deserialized value against application authorization and domain rules before use. + +## Runtime safeguards + +Keep class registration enabled for production and any untrusted payload source: + +```java +Fory fory = Fory.builder() + .requireClassRegistration(true) + .withMaxDepth(50) + .withMaxGraphMemoryBytes(128L * 1024 * 1024) + .withMaxUnbackedContainerItems(8192) + .build(); +``` + +Security-related options: + +- `requireClassRegistration(true)` restricts deserialization to registered classes. +- `withMaxDepth(...)` rejects unexpectedly deep object graphs. +- `withMaxGraphMemoryBytes(...)` sets an approximate gate for materialized graph memory during one + root deserialization. The estimate mainly covers collections, maps, arrays, structs, and objects; + Fory core primitive arrays and primitive lists count their primitive storage from the decoded + length. It skips leaf values such as strings, primitive scalars, and dedicated binary values that + do not use a primitive-array serializer. Actual process memory can be higher than this limit. Leaf + values remain protected by byte-availability checks: if the unread input does not contain enough + bytes, Fory will not read or create that leaf value. The default is a fixed `128 MiB`; set a + positive byte limit when trusted workloads need a larger or smaller gate. +- `withMaxUnbackedContainerItems(...)` limits count-driven collection and map work whose repeated + read bodies do not consume proportional input. The default is `8192`; zero is a strict limit. +- `withMaxTypeFields(...)` and `withMaxTypeMetaBytes(...)` bound the field count + and encoded body size of one received remote metadata body. +- `withMaxSchemaVersionsPerType(...)` and + `withMaxAverageSchemaVersionsPerType(...)` bound accepted remote metadata versions without + changing registration, dynamic loading, or schema-evolution semantics. +- `withDeserializeUnknownClass(false)` avoids materializing unknown classes from metadata. +- `checkJdkClassSerializable(true)` keeps the JDK serializability check for `java.*` classes. +- Class registration warnings can be useful during security audits; use + `suppressClassRegistrationWarnings(false)` when you need to surface unexpected types. + +Use `requireClassRegistration(false)` only for trusted payloads, and pair it with a `TypeChecker` +allow list when dynamic class loading is required. + +## Verification + +Add negative tests for the boundary as well as normal round trips. Verify that the configured reader +rejects unexpected application types, excessive nesting, resource-limit violations, and malformed +input. After a failed read, verify that a valid root can still be read with the reusable runtime. + +See [Configuration](configuration.md) for the complete option reference and +[Type Registration](type-registration.md) for the runtime's registration API. diff --git a/docs/object-serialization/javascript/core-api.md b/docs/object-serialization/javascript/basic-serialization.md similarity index 99% rename from docs/object-serialization/javascript/core-api.md rename to docs/object-serialization/javascript/basic-serialization.md index e466b33f4a..ce14adb1ce 100644 --- a/docs/object-serialization/javascript/core-api.md +++ b/docs/object-serialization/javascript/basic-serialization.md @@ -1,7 +1,7 @@ --- title: Basic Serialization sidebar_position: 1 -id: core-api +id: basic-serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with diff --git a/docs/object-serialization/javascript/configuration.md b/docs/object-serialization/javascript/configuration.md index f7d76d30e1..c73194882e 100644 --- a/docs/object-serialization/javascript/configuration.md +++ b/docs/object-serialization/javascript/configuration.md @@ -140,24 +140,11 @@ Leave this unset unless you run on Node.js 20+ and have benchmarked your workloa ## Security -Security-related configuration: - -- Register only the expected schemas before deserializing untrusted payloads. -- Set `maxDepth` for the maximum nesting depth your service accepts. -- Set `maxGraphMemoryBytes` as an approximate gate for collection, map, array, struct, and - object-heavy payloads. It is not an exact heap cap; leaf values are gated by remaining input - bytes. -- Keep `maxTypeFields` and `maxTypeMetaBytes` at their defaults unless the data - is not malicious and a trusted peer sends larger remote metadata. -- Keep `maxSchemaVersionsPerType` and - `maxAverageSchemaVersionsPerType` at their defaults unless the data is not - malicious and a trusted peer sends many remote schema versions. -- Prefer explicit `Type.struct(...)` schemas over `Type.any()` for untrusted input. -- Pass `hps` only from the official package version you deploy with Fory. +See [JavaScript/TypeScript Security](security.md) for trust boundaries, safe reader configuration, and verification. ## Related Topics -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [Schema Metadata](schema-metadata.md) - [Schema Evolution](schema-evolution.md) - [References](references.md) diff --git a/docs/object-serialization/javascript/index.md b/docs/object-serialization/javascript/index.md index 87e128cd2f..31a158002c 100644 --- a/docs/object-serialization/javascript/index.md +++ b/docs/object-serialization/javascript/index.md @@ -156,20 +156,23 @@ options; see [Configuration](configuration.md). ## Documentation -| Topic | Description | -| -------------------------------------------- | ------------------------------------------------------- | -| [Basic Serialization](core-api.md) | Default xlang APIs, usage, and interoperability | -| [Configuration](configuration.md) | Fory options, compatible mode, limits, and HPS | -| [Type Registration](type-registration.md) | Numeric IDs, names, decorators, and schema registration | -| [Schema Metadata](schema-metadata.md) | Type builders, field options, and decorators | -| [Supported Types](supported-types.md) | Primitive, collection, time, enum, and struct mappings | -| [References](references.md) | Shared references and circular object graphs | -| [Schema Evolution](schema-evolution.md) | Compatible mode and evolving structs | -| [Fory IDL Compiler](../../compiler/index.md) | Generate TypeScript models from `.fdl` schemas | -| [gRPC Support](../../grpc/javascript.md) | Node.js gRPC and browser gRPC-Web generated clients | -| [Troubleshooting](troubleshooting.md) | Common issues, limits, and debugging tips | +| Topic | Description | +| --------------------------------------------- | ------------------------------------------------------- | +| [Basic Serialization](basic-serialization.md) | Default xlang APIs, usage, and interoperability | +| [Configuration](configuration.md) | Fory options, compatible mode, limits, and HPS | +| [Type Registration](type-registration.md) | Numeric IDs, names, decorators, and schema registration | +| [Schema Metadata](schema-metadata.md) | Type builders, field options, and decorators | +| [Supported Types](supported-types.md) | Primitive, collection, time, enum, and struct mappings | +| [References](references.md) | Shared references and circular object graphs | +| [Schema Evolution](schema-evolution.md) | Compatible mode and evolving structs | +| [Fory IDL Compiler](../../compiler/index.md) | Generate TypeScript models from `.fdl` schemas | +| [gRPC Support](../../grpc/javascript.md) | Node.js gRPC and browser gRPC-Web generated clients | +| [Troubleshooting](troubleshooting.md) | Common issues, limits, and debugging tips | ## Related Resources - [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) - [Xlang Type Mapping](../../specification/xlang_type_mapping.md) + +Before decoding bytes from outside the application trust boundary, read +[JavaScript/TypeScript Security](security.md). diff --git a/docs/object-serialization/javascript/references.md b/docs/object-serialization/javascript/references.md index a491d0de8c..5f0f118bdb 100644 --- a/docs/object-serialization/javascript/references.md +++ b/docs/object-serialization/javascript/references.md @@ -106,6 +106,6 @@ Reference tracking is part of the Fory binary protocol and works across language ## Related Topics -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [Schema Evolution](schema-evolution.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/javascript/schema-evolution.md b/docs/object-serialization/javascript/schema-evolution.md index 20d102590f..f91d96ef5e 100644 --- a/docs/object-serialization/javascript/schema-evolution.md +++ b/docs/object-serialization/javascript/schema-evolution.md @@ -108,9 +108,9 @@ reads expecting compatible metadata, deserialization will fail. ## Xlang Requirement -Compatible mode only protects you from schema differences in the _fields_ of a type. You still need the same type identity (same numeric ID or same `typeName`) on every side. See [Cross-Language Interoperability](core-api.md#cross-language-interoperability). +Compatible mode only protects you from schema differences in the _fields_ of a type. You still need the same type identity (same numeric ID or same `typeName`) on every side. See [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability). ## Related Topics - [Type Registration](type-registration.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/javascript/security.md b/docs/object-serialization/javascript/security.md new file mode 100644 index 0000000000..6a01ac5b73 --- /dev/null +++ b/docs/object-serialization/javascript/security.md @@ -0,0 +1,60 @@ +--- +title: Security +sidebar_position: 99 +id: security +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Use this page when a JavaScript/TypeScript reader accepts bytes from outside the application's trust boundary. +Fory reconstructs application values; it does not authenticate the sender, protect transport +integrity, or decide whether a valid value is authorized for a business operation. + +## Application boundary + +Before deserialization: + +- Authenticate the sender and protect message integrity at the transport or storage layer. +- Enforce request or file size, timeout, and concurrency limits outside Fory. +- Register only the application types the endpoint accepts and configure the reader before its + first root operation. +- Validate the deserialized value against application authorization and domain rules before use. + +## Runtime safeguards + +Security-related configuration: + +- Register only the expected schemas before deserializing untrusted payloads. +- Set `maxDepth` for the maximum nesting depth your service accepts. +- Set `maxGraphMemoryBytes` as an approximate gate for collection, map, array, struct, and + object-heavy payloads. It is not an exact heap cap; leaf values are gated by remaining input + bytes. +- Keep `maxTypeFields` and `maxTypeMetaBytes` at their defaults unless the data + is not malicious and a trusted peer sends larger remote metadata. +- Keep `maxSchemaVersionsPerType` and + `maxAverageSchemaVersionsPerType` at their defaults unless the data is not + malicious and a trusted peer sends many remote schema versions. +- Prefer explicit `Type.struct(...)` schemas over `Type.any()` for untrusted input. +- Pass `hps` only from the official package version you deploy with Fory. + +## Verification + +Add negative tests for the boundary as well as normal round trips. Verify that the configured reader +rejects unexpected application types, excessive nesting, resource-limit violations, and malformed +input. After a failed read, verify that a valid root can still be read with the reusable runtime. + +See [Configuration](configuration.md) for the complete option reference and +[Type Registration](type-registration.md) for the runtime's registration API. diff --git a/docs/object-serialization/javascript/supported-types.md b/docs/object-serialization/javascript/supported-types.md index 2d78898ad5..57ca8eb011 100644 --- a/docs/object-serialization/javascript/supported-types.md +++ b/docs/object-serialization/javascript/supported-types.md @@ -172,6 +172,6 @@ For types that need completely custom encoding, use `Type.ext(...)` and pass a c ## Related Topics -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [References](references.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/javascript/troubleshooting.md b/docs/object-serialization/javascript/troubleshooting.md index ed95b7eef0..b215dd37b7 100644 --- a/docs/object-serialization/javascript/troubleshooting.md +++ b/docs/object-serialization/javascript/troubleshooting.md @@ -85,6 +85,6 @@ const fory = new Fory({ ## Related Topics -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [References](references.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/javascript/type-registration.md b/docs/object-serialization/javascript/type-registration.md index afcd166ab7..a0c471c075 100644 --- a/docs/object-serialization/javascript/type-registration.md +++ b/docs/object-serialization/javascript/type-registration.md @@ -154,11 +154,11 @@ Use **names** when: ## Xlang -For a message to round-trip between JavaScript and another language, both sides must use the same identity for a given type: same numeric ID, or same `typeName`. Use `.` inside `typeName` to add a namespace prefix. See [Cross-Language Interoperability](core-api.md#cross-language-interoperability). +For a message to round-trip between JavaScript and another language, both sides must use the same identity for a given type: same numeric ID, or same `typeName`. Use `.` inside `typeName` to add a namespace prefix. See [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability). ## Related Topics -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [Schema Metadata](schema-metadata.md) - [Schema Evolution](schema-evolution.md) -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) diff --git a/docs/object-serialization/kotlin/core-api.md b/docs/object-serialization/kotlin/basic-serialization.md similarity index 98% rename from docs/object-serialization/kotlin/core-api.md rename to docs/object-serialization/kotlin/basic-serialization.md index c152187fd1..0fdbfb3c04 100644 --- a/docs/object-serialization/kotlin/core-api.md +++ b/docs/object-serialization/kotlin/basic-serialization.md @@ -1,7 +1,7 @@ --- title: Basic Serialization sidebar_position: 1 -id: core-api +id: basic-serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with diff --git a/docs/object-serialization/kotlin/configuration.md b/docs/object-serialization/kotlin/configuration.md index 175b2a4d4e..f58b2d83a9 100644 --- a/docs/object-serialization/kotlin/configuration.md +++ b/docs/object-serialization/kotlin/configuration.md @@ -148,31 +148,4 @@ For xlang payloads, call `withCompatible(false)` only after verifying that every ## Security -Kotlin uses the Java configuration surface. Keep class registration enabled for production -and any untrusted payload source: - -```kotlin -val fory = ForyKotlin.builder() - .requireClassRegistration(true) - .withMaxDepth(50) - .withMaxGraphMemoryBytes(128L * 1024 * 1024) - .withMaxUnbackedContainerItems(8192) - .withMaxTypeFields(512) - .withMaxTypeMetaBytes(4096) - .build() -``` - -Security-related configuration: - -- Keep `requireClassRegistration(true)` and register application classes or generated modules. -- Use `withMaxDepth(...)` to reject unexpectedly deep object graphs. -- Use `withMaxGraphMemoryBytes(...)` as an approximate gate for collection, map, array, struct, and - object-heavy payloads. It is not an exact heap cap; leaf values are gated by remaining input - bytes. -- Keep `withMaxUnbackedContainerItems(...)` at `8192` unless trusted compact codecs require a - larger root allowance. Zero rejects every unbacked item. -- Keep `withMaxTypeFields(...)`, `withMaxTypeMetaBytes(...)`, and the remote schema-version limits - at their defaults unless the data is not malicious and a trusted peer sends larger metadata or - many schema versions. -- Follow [Java Configuration](../java/configuration.md#security) for allow-listing and unknown-class - controls. +See [Kotlin Security](security.md) for trust boundaries, safe reader configuration, and verification. diff --git a/docs/object-serialization/kotlin/index.md b/docs/object-serialization/kotlin/index.md index abe97519b5..5b5910fff2 100644 --- a/docs/object-serialization/kotlin/index.md +++ b/docs/object-serialization/kotlin/index.md @@ -117,7 +117,7 @@ See [Configuration](configuration.md) for Kotlin builder setup and [Java Native Fory Kotlin is built on top of Fory Java. Most configuration options, features, and concepts from Fory Java apply directly to Kotlin. Refer to the Java documentation for: - [Configuration](../java/configuration.md) - All ForyBuilder options -- [Basic Serialization](../java/core-api.md) - Serialization patterns and APIs +- [Basic Serialization](../java/basic-serialization.md) - Serialization patterns and APIs - [Type Registration](../java/type-registration.md) - Class registration and security - [Schema Evolution](../java/schema-evolution.md) - Forward/backward compatibility - [Custom Serializers](../java/custom-serializers.md) - Implement custom serializers @@ -126,7 +126,7 @@ Fory Kotlin is built on top of Fory Java. Most configuration options, features, ## Kotlin-Specific Documentation -- [Basic Serialization](core-api.md) - Models, APIs, and interoperability in the default xlang mode +- [Basic Serialization](basic-serialization.md) - Models, APIs, and interoperability in the default xlang mode - [Configuration](configuration.md) - Kotlin-specific Fory setup requirements - [Native Serialization](native.md) - Serializing Kotlin types in JVM-native mode - [Schema Metadata](schema-metadata.md) - Kotlin annotations, nullability, references, and integer metadata @@ -134,3 +134,6 @@ Fory Kotlin is built on top of Fory Java. Most configuration options, features, - [Static Generated Serializers](static-generated-serializers.md) - KSP xlang/schema serializer generation - [Kotlin gRPC Support](../../grpc/kotlin.md) - Coroutine stubs and service bases for Fory IDL services - [Android Support](../java/android.md) - Android setup, R8 behavior, and release-build validation + +Before decoding bytes from outside the application trust boundary, read +[Kotlin Security](security.md). diff --git a/docs/object-serialization/kotlin/native.md b/docs/object-serialization/kotlin/native.md index f62a0d3566..99a88b9670 100644 --- a/docs/object-serialization/kotlin/native.md +++ b/docs/object-serialization/kotlin/native.md @@ -20,7 +20,7 @@ license: | --- This page covers serialization of Kotlin-specific JVM types in native mode. For -cross-language Kotlin models, use [Kotlin Cross-Language Interoperability](core-api.md#cross-language-interoperability). +cross-language Kotlin models, use [Kotlin Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability). When compatible mode is enabled, Kotlin readers use the JVM compatible-read rules for selected scalar field type changes. A matched field can read between `Boolean`, `String`, numeric scalars, diff --git a/docs/object-serialization/kotlin/security.md b/docs/object-serialization/kotlin/security.md new file mode 100644 index 0000000000..56bbafd556 --- /dev/null +++ b/docs/object-serialization/kotlin/security.md @@ -0,0 +1,74 @@ +--- +title: Security +sidebar_position: 99 +id: security +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Use this page when a Kotlin reader accepts bytes from outside the application's trust boundary. +Fory reconstructs application values; it does not authenticate the sender, protect transport +integrity, or decide whether a valid value is authorized for a business operation. + +## Application boundary + +Before deserialization: + +- Authenticate the sender and protect message integrity at the transport or storage layer. +- Enforce request or file size, timeout, and concurrency limits outside Fory. +- Register only the application types the endpoint accepts and configure the reader before its + first root operation. +- Validate the deserialized value against application authorization and domain rules before use. + +## Runtime safeguards + +Kotlin uses the Java configuration surface. Keep class registration enabled for production +and any untrusted payload source: + +```kotlin +val fory = ForyKotlin.builder() + .requireClassRegistration(true) + .withMaxDepth(50) + .withMaxGraphMemoryBytes(128L * 1024 * 1024) + .withMaxUnbackedContainerItems(8192) + .withMaxTypeFields(512) + .withMaxTypeMetaBytes(4096) + .build() +``` + +Security-related configuration: + +- Keep `requireClassRegistration(true)` and register application classes or generated modules. +- Use `withMaxDepth(...)` to reject unexpectedly deep object graphs. +- Use `withMaxGraphMemoryBytes(...)` as an approximate gate for collection, map, array, struct, and + object-heavy payloads. It is not an exact heap cap; leaf values are gated by remaining input + bytes. +- Keep `withMaxUnbackedContainerItems(...)` at `8192` unless trusted compact codecs require a + larger root allowance. Zero rejects every unbacked item. +- Keep `withMaxTypeFields(...)`, `withMaxTypeMetaBytes(...)`, and the remote schema-version limits + at their defaults unless the data is not malicious and a trusted peer sends larger metadata or + many schema versions. +- Follow [Java Security](../java/security.md) for allow-listing and unknown-class + controls. + +## Verification + +Add negative tests for the boundary as well as normal round trips. Verify that the configured reader +rejects unexpected application types, excessive nesting, resource-limit violations, and malformed +input. After a failed read, verify that a valid root can still be read with the reusable runtime. + +See [Configuration](configuration.md) for the Kotlin option reference and +[Java Type Registration](../java/type-registration.md) for the underlying registration API. diff --git a/docs/object-serialization/native.md b/docs/object-serialization/native.md index 9a85e0f45e..d5bf9a87dc 100644 --- a/docs/object-serialization/native.md +++ b/docs/object-serialization/native.md @@ -1,6 +1,6 @@ --- title: Native Serialization -sidebar_position: 2 +sidebar_position: 3 id: native license: | Licensed to the Apache Software Foundation (ASF) under one or more diff --git a/docs/object-serialization/python/core-api.md b/docs/object-serialization/python/basic-serialization.md similarity index 98% rename from docs/object-serialization/python/core-api.md rename to docs/object-serialization/python/basic-serialization.md index 6499ee9433..489bc77929 100644 --- a/docs/object-serialization/python/core-api.md +++ b/docs/object-serialization/python/basic-serialization.md @@ -1,7 +1,7 @@ --- title: Basic Serialization sidebar_position: 1 -id: core-api +id: basic-serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -294,8 +294,8 @@ The binary protocol and API are similar to `pyfory`'s Python native mode, but Py - [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) - [Type Mapping Reference](../../specification/xlang_type_mapping.md) -- [Java Interoperability Guide](../java/core-api.md#cross-language-interoperability) -- [Rust Interoperability Guide](../rust/core-api.md#cross-language-interoperability) +- [Java Interoperability Guide](../java/basic-serialization.md#cross-language-interoperability) +- [Rust Interoperability Guide](../rust/basic-serialization.md#cross-language-interoperability) ### Related Guides diff --git a/docs/object-serialization/python/configuration.md b/docs/object-serialization/python/configuration.md index 57d1cca945..04652322e7 100644 --- a/docs/object-serialization/python/configuration.md +++ b/docs/object-serialization/python/configuration.md @@ -180,126 +180,10 @@ Use `strict=False` only for trusted data, preferably with a `policy=` deserializ ## Security -Treat native-mode bytes from untrusted sources the same way you would treat untrusted pickle bytes. -Native mode can reconstruct Python objects, import modules, invoke reduction hooks, and rebuild -dynamic classes or functions when `strict=False`. - -### Production Configuration - -Keep `strict=True` for production payloads unless the whole data source is trusted and a -`DeserializationPolicy` owns the remaining trust decisions: - -```python -import pyfory - -fory = pyfory.Fory( - xlang=True, - ref=False, - strict=True, - max_depth=50, - max_type_fields=512, - max_type_meta_bytes=4096, - max_schema_versions_per_type=10, - max_average_schema_versions_per_type=3, - max_graph_memory_bytes=128 * 1024 * 1024, -) - -fory.register(UserModel, name="example.User") -fory.register(OrderModel, name="example.Order") -``` - -Use dynamic native-mode deserialization (`strict=False`) only for trusted Python-only payloads: - -```python -import pyfory - -fory = pyfory.Fory( - xlang=False, - ref=True, - strict=False, - max_depth=100, -) -``` - -Received remote metadata is also limited: - -- `max_type_fields` limits the number of fields accepted in one received struct metadata body. -- `max_type_meta_bytes` limits the encoded body bytes accepted for one received TypeDef body. -- `max_schema_versions_per_type` limits accepted remote metadata versions for one logical type. -- `max_average_schema_versions_per_type` limits the average across accepted remote types. -- `max_graph_memory_bytes` sets an approximate gate for materialized graph memory during one root - deserialization. The estimate mainly covers lists, tuples, sets, dicts, object arrays, structs, - and Python objects. It skips leaf values such as strings, binary data, primitive scalars, and - dense primitive arrays, so actual process memory can be higher than this value. Leaf values remain - protected by byte-availability checks: if the unread input does not contain enough bytes, Fory - will not read or create that leaf value. The default is a fixed `128 MiB` for all root input - forms. Set a positive byte value for trusted payloads that legitimately need a larger or smaller - gate. -- `max_unbacked_container_items` limits collection elements and map entries whose repeated read - bodies do not consume proportional input during one root deserialization. The default is `8192`; - zero is a strict limit. - -These limits do not change `strict`, `policy`, dynamic loading, unknown-class handling, or -schema-evolution semantics. - -### DeserializationPolicy - -When `strict=False` is necessary, use `DeserializationPolicy` to restrict the dynamic types and -hooks accepted during deserialization: - -```python -import pyfory -from pyfory import DeserializationPolicy - -dangerous_modules = {"subprocess", "os", "__builtin__"} - -class SafeDeserializationPolicy(DeserializationPolicy): - def validate_class(self, cls, is_local, **kwargs): - if cls.__module__ in dangerous_modules: - raise ValueError(f"Blocked dangerous class: {cls.__module__}.{cls.__name__}") - - def intercept_reduce_call(self, callable_obj, args, **kwargs): - if getattr(callable_obj, "__name__", "") == "Popen": - raise ValueError("Blocked attempt to invoke subprocess.Popen") - return None - - def intercept_setstate(self, obj, state, **kwargs): - if isinstance(state, dict) and "password" in state: - state["password"] = "***REDACTED***" - return None - -policy = SafeDeserializationPolicy() -fory = pyfory.Fory(xlang=False, ref=True, strict=False, policy=policy) -``` - -Available policy hooks include: - -Reference validation hooks reject by raising exceptions and otherwise leave deserialized references -unchanged. - -| Hook | Description | -| -------------------------------------------- | --------------------------------------------------- | -| `validate_class(cls, is_local)` | Validate or block class types | -| `validate_module(module_name, is_local)` | Validate or block module imports | -| `validate_function(func, is_local)` | Validate or block function references | -| `validate_method(method, is_local)` | Validate or block method references | -| `intercept_reduce_call(callable_obj, args)` | Intercept `__reduce__` invocations | -| `inspect_reduced_object(obj)` | Inspect or replace objects created via `__reduce__` | -| `intercept_setstate(obj, state)` | Sanitize state before `__setstate__` | -| `authorize_instantiation(cls, args, kwargs)` | Control class instantiation | - -### Security Checklist - -- Keep `strict=True` for untrusted data. -- Register all expected application types before deserialization. -- Use `DeserializationPolicy` when `strict=False` is necessary. -- Keep `max_depth` low enough to reject unexpectedly deep payloads. -- Keep `max_graph_memory_bytes` at the fixed `128 MiB` default for most inputs, or set a positive - explicit gate for trusted workloads with different legitimate collection/map/struct sizes. -- Do not treat xlang/native mode choice as a security control. +See [Python Security](security.md) for trust boundaries, safe reader configuration, and verification. ## Related Topics -- [Basic Serialization](core-api.md) - Using configured Fory +- [Basic Serialization](basic-serialization.md) - Using configured Fory - [Type Registration](type-registration.md) - Registration patterns - [Native Serialization](native.md) - Python-only object serialization diff --git a/docs/object-serialization/python/custom-serializers.md b/docs/object-serialization/python/custom-serializers.md index 10dc0af7aa..da907d4302 100644 --- a/docs/object-serialization/python/custom-serializers.md +++ b/docs/object-serialization/python/custom-serializers.md @@ -135,4 +135,4 @@ fory.register(MyClass, name="com.example.MyClass", serializer=MySerializer(fory. - [Type Registration](type-registration.md) - Registration patterns - [Configuration](configuration.md) - Fory parameters -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - type registration and schema rules for xlang +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - type registration and schema rules for xlang diff --git a/docs/object-serialization/python/index.md b/docs/object-serialization/python/index.md index cd700229a2..cf05c7510a 100644 --- a/docs/object-serialization/python/index.md +++ b/docs/object-serialization/python/index.md @@ -147,11 +147,11 @@ Use xlang mode for cross-language payloads and dataclass schemas shared with oth Use native mode for Python-only traffic. Native mode is selected with `xlang=False` and owns pickle/cloudpickle-style behavior such as functions, lambdas, classes, methods, `__reduce__`, `__getstate__`, and out-of-band pickle protocol 5 buffers. It is optimized for Python's type system and supports a broader Python object surface than xlang mode, so use it when replacing pickle or cloudpickle. Compatible mode is enabled by default. Set `compatible=False` only when every reader and writer uses the same Python class schema and you want faster serialization and smaller size. -See [Native Serialization](native.md) for Python-only serialization details and [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for Python xlang registration and interoperability rules. +See [Native Serialization](native.md) for Python-only serialization details and [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) for Python xlang registration and interoperability rules. ## Next Steps -- [Basic Serialization](core-api.md) - Default xlang APIs and interoperability +- [Basic Serialization](basic-serialization.md) - Default xlang APIs and interoperability - [Native Serialization](native.md) - Python-only serialization - [Configuration](configuration.md) - Fory parameters, modes, and security - [Type Registration](type-registration.md) - User-defined type registration @@ -166,3 +166,6 @@ See [Native Serialization](native.md) for Python-only serialization details and - **PyPI**: https://pypi.org/project/pyfory/ - **Issues**: https://github.com/apache/fory/issues - **Slack**: https://join.slack.com/t/fory-project/shared_invite/zt-36g0qouzm-kcQSvV_dtfbtBKHRwT5gsw + +Before decoding bytes from outside the application trust boundary, read +[Python Security](security.md). diff --git a/docs/object-serialization/python/native.md b/docs/object-serialization/python/native.md index 65f2eeb636..e77e24bbea 100644 --- a/docs/object-serialization/python/native.md +++ b/docs/object-serialization/python/native.md @@ -23,7 +23,7 @@ Python native serialization is the Python-only wire mode selected with `xlang=Fa every writer and reader is Python and the payload should follow Python's object model instead of the portable xlang type system. -Use [Cross-Language Interoperability](core-api.md#cross-language-interoperability), the default Python mode, when bytes must be read +Use [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability), the default Python mode, when bytes must be read by Java, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another non-Python Fory implementation. @@ -186,7 +186,7 @@ Keep the payload in native mode. Xlang mode does not execute Python `__reduce__` ## Related Topics -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language Python payloads +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Cross-language Python payloads - [Configuration](configuration.md) - Python `Fory` options - [Out-of-Band Serialization](out-of-band.md) - Zero-copy buffer support - [Configuration](configuration.md#security) - Deserialization policies diff --git a/docs/object-serialization/python/numpy-integration.md b/docs/object-serialization/python/numpy-integration.md index 4884f6e2d5..8188696ace 100644 --- a/docs/object-serialization/python/numpy-integration.md +++ b/docs/object-serialization/python/numpy-integration.md @@ -100,4 +100,4 @@ assert np.array_equal(array, result) ## Related Topics - [Out-of-Band Serialization](out-of-band.md) - Zero-copy buffers -- [Basic Serialization](core-api.md) - Standard usage +- [Basic Serialization](basic-serialization.md) - Standard usage diff --git a/docs/object-serialization/python/out-of-band.md b/docs/object-serialization/python/out-of-band.md index 8a3ff0399f..8db222fecb 100644 --- a/docs/object-serialization/python/out-of-band.md +++ b/docs/object-serialization/python/out-of-band.md @@ -175,5 +175,5 @@ for buffer_obj in buffer_objects: ## Related Topics - [NumPy Integration](numpy-integration.md) - NumPy array serialization -- [Basic Serialization](core-api.md) - Standard serialization +- [Basic Serialization](basic-serialization.md) - Standard serialization - [Configuration](configuration.md) - Fory parameters diff --git a/docs/object-serialization/python/schema-evolution.md b/docs/object-serialization/python/schema-evolution.md index 40af9aebfe..a8927c92c0 100644 --- a/docs/object-serialization/python/schema-evolution.md +++ b/docs/object-serialization/python/schema-evolution.md @@ -137,5 +137,5 @@ class SameSchemaMessage: ## Related Topics - [Configuration](configuration.md) - Compatible mode settings -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Schema evolution across languages +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Schema evolution across languages - [Type Registration](type-registration.md) - Registration patterns diff --git a/docs/object-serialization/python/schema-metadata.md b/docs/object-serialization/python/schema-metadata.md index 9a315f3dde..913cafb4f8 100644 --- a/docs/object-serialization/python/schema-metadata.md +++ b/docs/object-serialization/python/schema-metadata.md @@ -524,6 +524,6 @@ class User: ## Related Topics -- [Basic Serialization](core-api.md) - Getting started with Fory serialization +- [Basic Serialization](basic-serialization.md) - Getting started with Fory serialization - [Schema Evolution](schema-evolution.md) - Compatible mode and schema evolution -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Interoperability with Java, Rust, C++, Go +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Interoperability with Java, Rust, C++, Go diff --git a/docs/object-serialization/python/security.md b/docs/object-serialization/python/security.md new file mode 100644 index 0000000000..db9e96c980 --- /dev/null +++ b/docs/object-serialization/python/security.md @@ -0,0 +1,163 @@ +--- +title: Security +sidebar_position: 99 +id: security +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Use this page when a Python reader accepts bytes from outside the application's trust boundary. +Fory reconstructs application values; it does not authenticate the sender, protect transport +integrity, or decide whether a valid value is authorized for a business operation. + +## Application boundary + +Before deserialization: + +- Authenticate the sender and protect message integrity at the transport or storage layer. +- Enforce request or file size, timeout, and concurrency limits outside Fory. +- Register only the application types the endpoint accepts and configure the reader before its + first root operation. +- Validate the deserialized value against application authorization and domain rules before use. + +## Runtime safeguards + +Treat native-mode bytes from untrusted sources the same way you would treat untrusted pickle bytes. +Native mode can reconstruct Python objects, import modules, invoke reduction hooks, and rebuild +dynamic classes or functions when `strict=False`. + +### Production Configuration + +Keep `strict=True` for production payloads unless the whole data source is trusted and a +`DeserializationPolicy` owns the remaining trust decisions: + +```python +import pyfory + +fory = pyfory.Fory( + xlang=True, + ref=False, + strict=True, + max_depth=50, + max_type_fields=512, + max_type_meta_bytes=4096, + max_schema_versions_per_type=10, + max_average_schema_versions_per_type=3, + max_graph_memory_bytes=128 * 1024 * 1024, +) + +fory.register(UserModel, name="example.User") +fory.register(OrderModel, name="example.Order") +``` + +Use dynamic native-mode deserialization (`strict=False`) only for trusted Python-only payloads: + +```python +import pyfory + +fory = pyfory.Fory( + xlang=False, + ref=True, + strict=False, + max_depth=100, +) +``` + +Received remote metadata is also limited: + +- `max_type_fields` limits the number of fields accepted in one received struct metadata body. +- `max_type_meta_bytes` limits the encoded body bytes accepted for one received TypeDef body. +- `max_schema_versions_per_type` limits accepted remote metadata versions for one logical type. +- `max_average_schema_versions_per_type` limits the average across accepted remote types. +- `max_graph_memory_bytes` sets an approximate gate for materialized graph memory during one root + deserialization. The estimate mainly covers lists, tuples, sets, dicts, object arrays, structs, + and Python objects. It skips leaf values such as strings, binary data, primitive scalars, and + dense primitive arrays, so actual process memory can be higher than this value. Leaf values remain + protected by byte-availability checks: if the unread input does not contain enough bytes, Fory + will not read or create that leaf value. The default is a fixed `128 MiB` for all root input + forms. Set a positive byte value for trusted payloads that legitimately need a larger or smaller + gate. +- `max_unbacked_container_items` limits collection elements and map entries whose repeated read + bodies do not consume proportional input during one root deserialization. The default is `8192`; + zero is a strict limit. + +These limits do not change `strict`, `policy`, dynamic loading, unknown-class handling, or +schema-evolution semantics. + +### DeserializationPolicy + +When `strict=False` is necessary, use `DeserializationPolicy` to restrict the dynamic types and +hooks accepted during deserialization: + +```python +import pyfory +from pyfory import DeserializationPolicy + +dangerous_modules = {"subprocess", "os", "__builtin__"} + +class SafeDeserializationPolicy(DeserializationPolicy): + def validate_class(self, cls, is_local, **kwargs): + if cls.__module__ in dangerous_modules: + raise ValueError(f"Blocked dangerous class: {cls.__module__}.{cls.__name__}") + + def intercept_reduce_call(self, callable_obj, args, **kwargs): + if getattr(callable_obj, "__name__", "") == "Popen": + raise ValueError("Blocked attempt to invoke subprocess.Popen") + return None + + def intercept_setstate(self, obj, state, **kwargs): + if isinstance(state, dict) and "password" in state: + state["password"] = "***REDACTED***" + return None + +policy = SafeDeserializationPolicy() +fory = pyfory.Fory(xlang=False, ref=True, strict=False, policy=policy) +``` + +Available policy hooks include: + +Reference validation hooks reject by raising exceptions and otherwise leave deserialized references +unchanged. + +| Hook | Description | +| -------------------------------------------- | --------------------------------------------------- | +| `validate_class(cls, is_local)` | Validate or block class types | +| `validate_module(module_name, is_local)` | Validate or block module imports | +| `validate_function(func, is_local)` | Validate or block function references | +| `validate_method(method, is_local)` | Validate or block method references | +| `intercept_reduce_call(callable_obj, args)` | Intercept `__reduce__` invocations | +| `inspect_reduced_object(obj)` | Inspect or replace objects created via `__reduce__` | +| `intercept_setstate(obj, state)` | Sanitize state before `__setstate__` | +| `authorize_instantiation(cls, args, kwargs)` | Control class instantiation | + +### Security Checklist + +- Keep `strict=True` for untrusted data. +- Register all expected application types before deserialization. +- Use `DeserializationPolicy` when `strict=False` is necessary. +- Keep `max_depth` low enough to reject unexpectedly deep payloads. +- Keep `max_graph_memory_bytes` at the fixed `128 MiB` default for most inputs, or set a positive + explicit gate for trusted workloads with different legitimate collection/map/struct sizes. +- Do not treat xlang/native mode choice as a security control. + +## Verification + +Add negative tests for the boundary as well as normal round trips. Verify that the configured reader +rejects unexpected application types, excessive nesting, resource-limit violations, and malformed +input. After a failed read, verify that a valid root can still be read with the reusable runtime. + +See [Configuration](configuration.md) for the complete option reference and +[Type Registration](type-registration.md) for the runtime's registration API. diff --git a/docs/object-serialization/rust/core-api.md b/docs/object-serialization/rust/basic-serialization.md similarity index 98% rename from docs/object-serialization/rust/core-api.md rename to docs/object-serialization/rust/basic-serialization.md index 8587e89b1c..b8fcf9e873 100644 --- a/docs/object-serialization/rust/core-api.md +++ b/docs/object-serialization/rust/basic-serialization.md @@ -1,7 +1,7 @@ --- title: Basic Serialization sidebar_position: 1 -id: core-api +id: basic-serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -416,8 +416,8 @@ explicit array field attribute when the schema is dense `array`. - [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) - [Type Mapping Reference](../../specification/xlang_type_mapping.md) -- [Java Interoperability Guide](../java/core-api.md#cross-language-interoperability) -- [Python Interoperability Guide](../python/core-api.md#cross-language-interoperability) +- [Java Interoperability Guide](../java/basic-serialization.md#cross-language-interoperability) +- [Python Interoperability Guide](../python/basic-serialization.md#cross-language-interoperability) ### Related Guides diff --git a/docs/object-serialization/rust/configuration.md b/docs/object-serialization/rust/configuration.md index a2ba3204ca..f5fb04eefc 100644 --- a/docs/object-serialization/rust/configuration.md +++ b/docs/object-serialization/rust/configuration.md @@ -201,21 +201,10 @@ Use `.compatible(false)` only when the schema used to deserialize every payload ## Security -Security-related configuration: - -- Register application structs and trait-object implementations before deserializing untrusted - payloads. -- Use `max_dyn_depth(...)` to reject unexpectedly deep dynamic object graphs. -- Keep `max_graph_memory_bytes(...)` at the fixed `128 MiB` default for most inputs, or set a - positive byte gate for trusted workloads with different legitimate collection/map/struct sizes. -- Keep `max_unbacked_container_items(...)` at `8192` unless trusted compact codecs require a - larger root allowance. Zero rejects every unbacked item. -- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a - trusted peer sends larger metadata or many schema versions. -- Prefer concrete typed fields over `dyn Any` or broad trait-object fields for untrusted input. +See [Rust Security](security.md) for trust boundaries, safe reader configuration, and verification. ## Related Topics -- [Basic Serialization](core-api.md) - Using configured Fory +- [Basic Serialization](basic-serialization.md) - Using configured Fory - [Schema Evolution](schema-evolution.md) - Compatible mode details -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - xlang mode +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - xlang mode diff --git a/docs/object-serialization/rust/index.md b/docs/object-serialization/rust/index.md index f4070bef8f..6f1156060e 100644 --- a/docs/object-serialization/rust/index.md +++ b/docs/object-serialization/rust/index.md @@ -101,7 +101,7 @@ Use xlang mode for cross-language payloads and schemas shared with other Fory im Use native mode for Rust-only traffic. Native mode is selected with `.xlang(false)` and keeps Rust object serialization in Rust-native form. It supports native-only concrete targets and data-enum shapes that have no xlang representation. Dynamic `Any`, application trait, and shared-reference carriers can also be used in xlang mode when every selected concrete target is xlang-compatible. Compatible mode is enabled by default. Set `.compatible(false)` only when every reader and writer uses the same Rust schema and you want faster serialization and smaller size. -See [Cross-Language Interoperability](core-api.md#cross-language-interoperability) for Rust xlang registration and interoperability rules, and [Native Serialization](native.md) for Rust-only payloads. +See [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) for Rust xlang registration and interoperability rules, and [Native Serialization](native.md) for Rust-only payloads. ## Thread Safety @@ -158,7 +158,7 @@ fn main() -> Result<(), Error> { ## Next Steps - [Configuration](configuration.md) - Fory builder options and modes -- [Basic Serialization](core-api.md) - Default xlang object graphs and interoperability +- [Basic Serialization](basic-serialization.md) - Default xlang object graphs and interoperability - [Native Serialization](native.md) - Rust-only serialization - [References](references.md) - Shared and circular references - [Polymorphism](polymorphism.md) - Trait object serialization @@ -167,3 +167,6 @@ fn main() -> Result<(), Error> { plus carrier composition - [Row Format](../../row-format/rust.md) - Standard Row Format with borrowed views - [gRPC Support](../../grpc/rust.md) - Fory payloads over tonic + +Before decoding bytes from outside the application trust boundary, read +[Rust Security](security.md). diff --git a/docs/object-serialization/rust/native.md b/docs/object-serialization/rust/native.md index ba95606910..084d6ab118 100644 --- a/docs/object-serialization/rust/native.md +++ b/docs/object-serialization/rust/native.md @@ -23,7 +23,7 @@ Rust native serialization is the Rust-only wire mode selected with `.xlang(false every writer and reader is Rust and the payload should preserve Rust object-graph behavior instead of the portable xlang type system. -Use [Cross-Language Interoperability](core-api.md#cross-language-interoperability), the default Rust mode, when bytes must be read +Use [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability), the default Rust mode, when bytes must be read by Java, Python, C++, Go, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another non-Rust Fory implementation. @@ -115,7 +115,7 @@ Native serialization owns the Rust-specific object surface: is appropriate. - Date and time carriers, including optional `chrono` support. -Use [Basic Serialization](core-api.md), [References](references.md), and +Use [Basic Serialization](basic-serialization.md), [References](references.md), and [Trait Object Serialization](polymorphism.md) for focused examples. ## Shared And Circular References @@ -264,9 +264,9 @@ Native serialization defaults to compatible mode. Keep that default when schemas ## Related Topics -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Cross-language Rust payloads +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Cross-language Rust payloads - [Configuration](configuration.md) - Builder options -- [Basic Serialization](core-api.md) - Object graph serialization +- [Basic Serialization](basic-serialization.md) - Object graph serialization - [Shared & Circular References](references.md) - `Rc`, `Arc`, and weak pointers - [Trait Object Serialization](polymorphism.md) - Trait objects and dynamic dispatch - [Schema Evolution](schema-evolution.md) - Compatible mode diff --git a/docs/object-serialization/rust/references.md b/docs/object-serialization/rust/references.md index 7bc0e584c3..4cbbc01f79 100644 --- a/docs/object-serialization/rust/references.md +++ b/docs/object-serialization/rust/references.md @@ -208,6 +208,6 @@ for child in &decoded.lock().unwrap().children { ## Related Topics -- [Basic Serialization](core-api.md) - Supported types +- [Basic Serialization](basic-serialization.md) - Supported types - [Polymorphism](polymorphism.md) - Trait objects with Rc/Arc - [Configuration](configuration.md) - Reference tracking options diff --git a/docs/object-serialization/rust/schema-evolution.md b/docs/object-serialization/rust/schema-evolution.md index 38e5b621e3..81505ac2bf 100644 --- a/docs/object-serialization/rust/schema-evolution.md +++ b/docs/object-serialization/rust/schema-evolution.md @@ -278,5 +278,5 @@ assert_eq!(data, decoded); - [Configuration](configuration.md) - Compatible mode settings - [Polymorphism](polymorphism.md) - Trait objects with schema evolution -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Schema evolution across languages +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Schema evolution across languages - [External-Type Serialization](external-types.md) - Compatible schemas for third-party values diff --git a/docs/object-serialization/rust/schema-metadata.md b/docs/object-serialization/rust/schema-metadata.md index 036164e07c..249f9a906a 100644 --- a/docs/object-serialization/rust/schema-metadata.md +++ b/docs/object-serialization/rust/schema-metadata.md @@ -497,7 +497,7 @@ struct User { ## Related Topics -- [Basic Serialization](core-api.md) - Getting started with Fory serialization +- [Basic Serialization](basic-serialization.md) - Getting started with Fory serialization - [Schema Evolution](schema-evolution.md) - Compatible mode and schema evolution -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - Interoperability with Java, C++, Go, Python +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - Interoperability with Java, C++, Go, Python - [External-Type Serialization](external-types.md) - Selecting serializers for third-party fields diff --git a/docs/object-serialization/rust/security.md b/docs/object-serialization/rust/security.md new file mode 100644 index 0000000000..7a17d95fec --- /dev/null +++ b/docs/object-serialization/rust/security.md @@ -0,0 +1,58 @@ +--- +title: Security +sidebar_position: 99 +id: security +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Use this page when a Rust reader accepts bytes from outside the application's trust boundary. +Fory reconstructs application values; it does not authenticate the sender, protect transport +integrity, or decide whether a valid value is authorized for a business operation. + +## Application boundary + +Before deserialization: + +- Authenticate the sender and protect message integrity at the transport or storage layer. +- Enforce request or file size, timeout, and concurrency limits outside Fory. +- Register only the application types the endpoint accepts and configure the reader before its + first root operation. +- Validate the deserialized value against application authorization and domain rules before use. + +## Runtime safeguards + +Security-related configuration: + +- Register application structs and trait-object implementations before deserializing untrusted + payloads. +- Use `max_dyn_depth(...)` to reject unexpectedly deep dynamic object graphs. +- Keep `max_graph_memory_bytes(...)` at the fixed `128 MiB` default for most inputs, or set a + positive byte gate for trusted workloads with different legitimate collection/map/struct sizes. +- Keep `max_unbacked_container_items(...)` at `8192` unless trusted compact codecs require a + larger root allowance. Zero rejects every unbacked item. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. +- Prefer concrete typed fields over `dyn Any` or broad trait-object fields for untrusted input. + +## Verification + +Add negative tests for the boundary as well as normal round trips. Verify that the configured reader +rejects unexpected application types, excessive nesting, resource-limit violations, and malformed +input. After a failed read, verify that a valid root can still be read with the reusable runtime. + +See [Configuration](configuration.md) for the complete option reference and +[Type Registration](type-registration.md) for the runtime's registration API. diff --git a/docs/object-serialization/rust/type-registration.md b/docs/object-serialization/rust/type-registration.md index 3e18bfd54c..162eb4a954 100644 --- a/docs/object-serialization/rust/type-registration.md +++ b/docs/object-serialization/rust/type-registration.md @@ -136,6 +136,6 @@ let handles: Vec<_> = (0..4) ## Related Topics - [Configuration](configuration.md) - Fory builder options -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) - xlang mode registration +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - xlang mode registration - [Custom Serializers](custom-serializers.md) - Custom serialization - [External-Type Serialization](external-types.md) - Third-party targets and carrier roots diff --git a/docs/object-serialization/scala/core-api.md b/docs/object-serialization/scala/basic-serialization.md similarity index 99% rename from docs/object-serialization/scala/core-api.md rename to docs/object-serialization/scala/basic-serialization.md index 85a0bb2205..c1768d45e5 100644 --- a/docs/object-serialization/scala/core-api.md +++ b/docs/object-serialization/scala/basic-serialization.md @@ -1,7 +1,7 @@ --- title: Basic Serialization sidebar_position: 1 -id: core-api +id: basic-serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with diff --git a/docs/object-serialization/scala/configuration.md b/docs/object-serialization/scala/configuration.md index 18aee91be5..2524eaeb0e 100644 --- a/docs/object-serialization/scala/configuration.md +++ b/docs/object-serialization/scala/configuration.md @@ -192,31 +192,4 @@ For xlang payloads, call `withCompatible(false)` only after verifying that every ## Security -Scala uses the Java configuration surface. Keep class registration enabled for production -and any untrusted payload source: - -```scala -val fory = ForyScala.builder() - .requireClassRegistration(true) - .withMaxDepth(50) - .withMaxGraphMemoryBytes(128L * 1024 * 1024) - .withMaxUnbackedContainerItems(8192) - .withMaxTypeFields(512) - .withMaxTypeMetaBytes(4096) - .build() -``` - -Security-related configuration: - -- Keep `requireClassRegistration(true)` and register application classes or generated modules. -- Use `withMaxDepth(...)` to reject unexpectedly deep object graphs. -- Use `withMaxGraphMemoryBytes(...)` as an approximate gate for collection, map, array, struct, and - object-heavy payloads. It is not an exact heap cap; leaf values are gated by remaining input - bytes. -- Keep `withMaxUnbackedContainerItems(...)` at `8192` unless trusted compact codecs require a - larger root allowance. Zero rejects every unbacked item. -- Keep `withMaxTypeFields(...)`, `withMaxTypeMetaBytes(...)`, and the remote schema-version limits - at their defaults unless the data is not malicious and a trusted peer sends larger metadata or - many schema versions. -- Follow [Java Configuration](../java/configuration.md#security) for allow-listing and unknown-class - controls. +See [Scala Security](security.md) for trust boundaries, safe reader configuration, and verification. diff --git a/docs/object-serialization/scala/index.md b/docs/object-serialization/scala/index.md index b94e3d35a7..de1d3efec9 100644 --- a/docs/object-serialization/scala/index.md +++ b/docs/object-serialization/scala/index.md @@ -105,7 +105,7 @@ See [Configuration](configuration.md) for Scala builder setup and [Java Native S Fory Scala is built on top of Fory Java. Most configuration options, features, and concepts from Fory Java apply directly to Scala. Refer to the Java documentation for: - [Configuration](../java/configuration.md) - All ForyBuilder options -- [Basic Serialization](../java/core-api.md) - Serialization patterns and APIs +- [Basic Serialization](../java/basic-serialization.md) - Serialization patterns and APIs - [Type Registration](../java/type-registration.md) - Class registration and security - [Schema Evolution](../java/schema-evolution.md) - Forward/backward compatibility - [Custom Serializers](../java/custom-serializers.md) - Implement custom serializers @@ -118,5 +118,8 @@ Fory Scala is built on top of Fory Java. Most configuration options, features, a - [Native Serialization](native.md) - Serializing Scala types in JVM-native mode - [Schema Metadata](schema-metadata.md) - Scala annotations, references, enum IDs, and union metadata - [Default Values](default-values.md) - Scala class default values support -- [Basic Serialization](core-api.md) - Models, APIs, and interoperability in the default xlang mode +- [Basic Serialization](basic-serialization.md) - Models, APIs, and interoperability in the default xlang mode - [gRPC Support](../../grpc/scala.md) - Scala 3 generated gRPC service companions + +Before decoding bytes from outside the application trust boundary, read +[Scala Security](security.md). diff --git a/docs/object-serialization/scala/native.md b/docs/object-serialization/scala/native.md index 62505e7247..20fd963b97 100644 --- a/docs/object-serialization/scala/native.md +++ b/docs/object-serialization/scala/native.md @@ -21,7 +21,7 @@ license: | This page covers serialization of Scala-specific JVM types in native mode. For cross-language Scala models, use the xlang path described in -[Cross-Language Interoperability](core-api.md#cross-language-interoperability). +[Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability). When compatible mode is enabled, Scala readers use the JVM compatible-read rules for selected scalar field type changes. A matched field can read between `Boolean`, `String`, numeric scalars, diff --git a/docs/object-serialization/scala/schema-metadata.md b/docs/object-serialization/scala/schema-metadata.md index 93d552463a..d630690824 100644 --- a/docs/object-serialization/scala/schema-metadata.md +++ b/docs/object-serialization/scala/schema-metadata.md @@ -119,6 +119,6 @@ reflection is not the source of truth for generated Scala metadata. ## Related Topics -- [Cross-Language Interoperability](core-api.md#cross-language-interoperability) +- [Cross-Language Interoperability](basic-serialization.md#cross-language-interoperability) - [Configuration](configuration.md) - [Default Values](default-values.md) diff --git a/docs/object-serialization/scala/security.md b/docs/object-serialization/scala/security.md new file mode 100644 index 0000000000..f5f40c61c7 --- /dev/null +++ b/docs/object-serialization/scala/security.md @@ -0,0 +1,74 @@ +--- +title: Security +sidebar_position: 99 +id: security +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Use this page when a Scala reader accepts bytes from outside the application's trust boundary. +Fory reconstructs application values; it does not authenticate the sender, protect transport +integrity, or decide whether a valid value is authorized for a business operation. + +## Application boundary + +Before deserialization: + +- Authenticate the sender and protect message integrity at the transport or storage layer. +- Enforce request or file size, timeout, and concurrency limits outside Fory. +- Register only the application types the endpoint accepts and configure the reader before its + first root operation. +- Validate the deserialized value against application authorization and domain rules before use. + +## Runtime safeguards + +Scala uses the Java configuration surface. Keep class registration enabled for production +and any untrusted payload source: + +```scala +val fory = ForyScala.builder() + .requireClassRegistration(true) + .withMaxDepth(50) + .withMaxGraphMemoryBytes(128L * 1024 * 1024) + .withMaxUnbackedContainerItems(8192) + .withMaxTypeFields(512) + .withMaxTypeMetaBytes(4096) + .build() +``` + +Security-related configuration: + +- Keep `requireClassRegistration(true)` and register application classes or generated modules. +- Use `withMaxDepth(...)` to reject unexpectedly deep object graphs. +- Use `withMaxGraphMemoryBytes(...)` as an approximate gate for collection, map, array, struct, and + object-heavy payloads. It is not an exact heap cap; leaf values are gated by remaining input + bytes. +- Keep `withMaxUnbackedContainerItems(...)` at `8192` unless trusted compact codecs require a + larger root allowance. Zero rejects every unbacked item. +- Keep `withMaxTypeFields(...)`, `withMaxTypeMetaBytes(...)`, and the remote schema-version limits + at their defaults unless the data is not malicious and a trusted peer sends larger metadata or + many schema versions. +- Follow [Java Security](../java/security.md) for allow-listing and unknown-class + controls. + +## Verification + +Add negative tests for the boundary as well as normal round trips. Verify that the configured reader +rejects unexpected application types, excessive nesting, resource-limit violations, and malformed +input. After a failed read, verify that a valid root can still be read with the reusable runtime. + +See [Configuration](configuration.md) for the Scala option reference and +[Java Type Registration](../java/type-registration.md) for the underlying registration API. diff --git a/docs/object-serialization/swift/core-api.md b/docs/object-serialization/swift/basic-serialization.md similarity index 99% rename from docs/object-serialization/swift/core-api.md rename to docs/object-serialization/swift/basic-serialization.md index 043a9de3d4..1f1eaadb3f 100644 --- a/docs/object-serialization/swift/core-api.md +++ b/docs/object-serialization/swift/basic-serialization.md @@ -1,7 +1,7 @@ --- title: Basic Serialization sidebar_position: 1 -id: core-api +id: basic-serialization license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with diff --git a/docs/object-serialization/swift/configuration.md b/docs/object-serialization/swift/configuration.md index cffcedab15..1d3bae58ad 100644 --- a/docs/object-serialization/swift/configuration.md +++ b/docs/object-serialization/swift/configuration.md @@ -157,13 +157,4 @@ let fory = Fory(compatible: false) ## Security -Security-related configuration: - -- Register only the expected generated models before deserializing untrusted payloads. -- Use `checkClassVersion` with `compatible: false` for intentional same-schema payloads. -- Set `maxDepth` for the largest dynamic `Any` nesting depth your service accepts. -- Set `maxGraphMemoryBytes` as an approximate gate for collection, map, array, struct, class, and - object-heavy payloads. It is not an exact heap cap; leaf values are gated by remaining input - bytes. -- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a - trusted peer sends larger metadata or many schema versions. +See [Swift Security](security.md) for trust boundaries, safe reader configuration, and verification. diff --git a/docs/object-serialization/swift/index.md b/docs/object-serialization/swift/index.md index 0eb165e3fb..135c02a5e6 100644 --- a/docs/object-serialization/swift/index.md +++ b/docs/object-serialization/swift/index.md @@ -52,7 +52,7 @@ targets: [ ## Guide Contents - [Configuration](configuration.md) -- [Basic Serialization](core-api.md) +- [Basic Serialization](basic-serialization.md) - [Schema Metadata](schema-metadata.md) - [Type Registration](type-registration.md) - [External-Type Serialization](external-types.md) @@ -82,3 +82,6 @@ let output: User = try fory.deserialize(data) assert(input == output) ``` + +Before decoding bytes from outside the application trust boundary, read +[Swift Security](security.md). diff --git a/docs/object-serialization/swift/security.md b/docs/object-serialization/swift/security.md new file mode 100644 index 0000000000..a03e6c58b5 --- /dev/null +++ b/docs/object-serialization/swift/security.md @@ -0,0 +1,56 @@ +--- +title: Security +sidebar_position: 99 +id: security +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +Use this page when a Swift reader accepts bytes from outside the application's trust boundary. +Fory reconstructs application values; it does not authenticate the sender, protect transport +integrity, or decide whether a valid value is authorized for a business operation. + +## Application boundary + +Before deserialization: + +- Authenticate the sender and protect message integrity at the transport or storage layer. +- Enforce request or file size, timeout, and concurrency limits outside Fory. +- Register only the application types the endpoint accepts and configure the reader before its + first root operation. +- Validate the deserialized value against application authorization and domain rules before use. + +## Runtime safeguards + +Security-related configuration: + +- Register only the expected generated models before deserializing untrusted payloads. +- Use `checkClassVersion` with `compatible: false` for intentional same-schema payloads. +- Set `maxDepth` for the largest dynamic `Any` nesting depth your service accepts. +- Set `maxGraphMemoryBytes` as an approximate gate for collection, map, array, struct, class, and + object-heavy payloads. It is not an exact heap cap; leaf values are gated by remaining input + bytes. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. + +## Verification + +Add negative tests for the boundary as well as normal round trips. Verify that the configured reader +rejects unexpected application types, excessive nesting, resource-limit violations, and malformed +input. After a failed read, verify that a valid root can still be read with the reusable runtime. + +See [Configuration](configuration.md) for the complete option reference and +[Type Registration](type-registration.md) for the runtime's registration API. diff --git a/docs/object-serialization/xlang.md b/docs/object-serialization/xlang.md index 48de71b2e9..8a900b9ae9 100644 --- a/docs/object-serialization/xlang.md +++ b/docs/object-serialization/xlang.md @@ -1,6 +1,6 @@ --- -title: Cross-Language Interoperability -sidebar_position: 1 +title: Xlang Serialization +sidebar_position: 2 id: xlang license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -24,6 +24,9 @@ Python, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin [Basic Serialization](#runtime-guides) page owns its API and model examples; this page explains the rules that peers must share. +Read [Core Concepts](core-concepts.md) first for the object graph, schema, reference, and +polymorphism concepts shared by xlang and native modes. + ## Overview Use xlang serialization when bytes cross runtime boundaries, including polyglot services, data @@ -330,17 +333,17 @@ hex dump or internal flag value as a stable application API. ## Runtime Guides -- [Java](java/core-api.md#cross-language-interoperability) -- [Python](python/core-api.md#cross-language-interoperability) -- [C++](cpp/core-api.md#cross-language-interoperability) -- [Go](go/core-api.md#cross-language-interoperability) -- [Rust](rust/core-api.md#cross-language-interoperability) -- [JavaScript/TypeScript](javascript/core-api.md#cross-language-interoperability) -- [C#](csharp/core-api.md#cross-language-interoperability) -- [Swift](swift/core-api.md#cross-language-interoperability) -- [Dart](dart/core-api.md#cross-language-interoperability) -- [Scala](scala/core-api.md#cross-language-interoperability) -- [Kotlin](kotlin/core-api.md#cross-language-interoperability) +- [Java](java/basic-serialization.md#cross-language-interoperability) +- [Python](python/basic-serialization.md#cross-language-interoperability) +- [C++](cpp/basic-serialization.md#cross-language-interoperability) +- [Go](go/basic-serialization.md#cross-language-interoperability) +- [Rust](rust/basic-serialization.md#cross-language-interoperability) +- [JavaScript/TypeScript](javascript/basic-serialization.md#cross-language-interoperability) +- [C#](csharp/basic-serialization.md#cross-language-interoperability) +- [Swift](swift/basic-serialization.md#cross-language-interoperability) +- [Dart](dart/basic-serialization.md#cross-language-interoperability) +- [Scala](scala/basic-serialization.md#cross-language-interoperability) +- [Kotlin](kotlin/basic-serialization.md#cross-language-interoperability) ## Related Documentation @@ -349,7 +352,6 @@ hex dump or internal flag value as a stable application API. - [Fory IDL and Compiler](../compiler/index.md) — schema-first models and code generation - [Getting Started](../start/index.md) — installation and first serialization for each runtime - [Row Format](../row-format/index.md) — random-access analytical rows for trusted data -- [Object Serialization Security](security.md) — trust boundaries and deserialization controls ## Operational Best Practices diff --git a/docs/row-format/cpp.md b/docs/row-format/cpp.md index 9132a330c5..f34b25c6ea 100644 --- a/docs/row-format/cpp.md +++ b/docs/row-format/cpp.md @@ -504,6 +504,6 @@ int32_t id = row.get_int32(0); ## Related Topics - [C++ Row example](https://github.com/apache/fory/tree/main/examples/cpp/hello_row) - Complete runnable example -- [Basic Serialization](../object-serialization/cpp/core-api.md) - Object graph serialization +- [Basic Serialization](../object-serialization/cpp/basic-serialization.md) - Object graph serialization - [C++ Object Serialization Configuration](../object-serialization/cpp/configuration.md) - Builder options - [C++ Object Serialization Supported Types](../object-serialization/cpp/supported-types.md) - Object serialization types diff --git a/docs/row-format/java.md b/docs/row-format/java.md index e461974869..04a8795514 100644 --- a/docs/row-format/java.md +++ b/docs/row-format/java.md @@ -279,6 +279,6 @@ Parent decoded = encoder.fromRow(row); ## Related Topics -- [Cross-Language Interoperability](../object-serialization/java/core-api.md#cross-language-interoperability) - xlang mode +- [Cross-Language Interoperability](../object-serialization/java/basic-serialization.md#cross-language-interoperability) - xlang mode - [Java Advanced Features](../object-serialization/java/advanced-features.md) - Zero-copy object serialization - [Row Format Specification](https://fory.apache.org/docs/specification/row_format_spec) - Protocol details diff --git a/docs/row-format/python.md b/docs/row-format/python.md index 9e069b086f..4852a16c56 100644 --- a/docs/row-format/python.md +++ b/docs/row-format/python.md @@ -195,6 +195,6 @@ pip install pyfory[format] ## Related Topics -- [Cross-Language Interoperability](../object-serialization/python/core-api.md#cross-language-interoperability) - xlang mode -- [Basic Serialization](../object-serialization/python/core-api.md) - Object serialization +- [Cross-Language Interoperability](../object-serialization/python/basic-serialization.md#cross-language-interoperability) - xlang mode +- [Basic Serialization](../object-serialization/python/basic-serialization.md) - Object serialization - [Row Format Specification](https://fory.apache.org/docs/specification/row_format_spec) - Protocol details diff --git a/docs/row-format/rust.md b/docs/row-format/rust.md index e0efd399ce..debd04d7d1 100644 --- a/docs/row-format/rust.md +++ b/docs/row-format/rust.md @@ -185,6 +185,6 @@ assert!(scores.get(scores.len()).is_err()); ## Related Topics -- [Basic Serialization](../object-serialization/rust/core-api.md) - Object graph serialization +- [Basic Serialization](../object-serialization/rust/basic-serialization.md) - Object graph serialization - [Standard Row Format](index.md#standard-row) - Shared layout for Java, Python, C++, and Rust - [Row Format Specification](../specification/row_format_spec.md) - Protocol details diff --git a/docs/object-serialization/security.md b/docs/security/deserialization.md similarity index 77% rename from docs/object-serialization/security.md rename to docs/security/deserialization.md index 09e88a25a8..bec502b9f4 100644 --- a/docs/object-serialization/security.md +++ b/docs/security/deserialization.md @@ -1,216 +1,8 @@ --- -title: Security -sidebar_position: 99 -id: security -license: | - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +title: Deserialization Security Model +sidebar_position: 3 --- -This guide defines the trust boundary and safe operating model for Fory binary object serialization in xlang and native mode. It also provides the contributor-facing classification rules used to review untrusted deserialization paths. - -Fory is an in-process serialization library. Applications link Fory into their -own process, configure serializers and type policies, and call Fory APIs to -serialize application-owned objects or deserialize encoded Fory data. Fory does -not provide a standalone network service, daemon, authentication system, or -transport protocol. - -## Trust Boundaries - -Fory's primary security boundary is encoded bytes or streams passed to -deserialization APIs from untrusted or partially trusted sources. The embedding -application owns where those bytes come from and which Fory configuration, -registered types, schemas, and policies are used to read them. - -The adversary model for untrusted deserialization is a sender that can craft -encoded bytes or stream behavior presented to a Fory read API. It does not assume -the sender can change the embedding application's Fory configuration, registered -type set, `TypeChecker` or equivalent allow-list policy, schema definitions, -classloader, or other active policy objects unless the application itself exposes -those controls. - -Fory security boundaries include: - -- Runtime safety, including avoiding crashes, panics, undefined behavior, and - out-of-bounds memory access. -- Resource ownership, including memory, CPU progress, stream buffers, native - allocations, callbacks, and retained read-side state. -- Explicit Fory policy checks, such as class, type, function, method, - registration, or deserialization policies that restrict what may be - materialized. -- Cleanup boundaries, where state created during a failed root operation must - not leak into later operations. - -Runtime serializer code generation and JIT compilation are not paths for -executing encoded input. They operate on types and schemas after the active -registration check, `TypeChecker`, schema check, or policy check has accepted the -type surface. When class registration is disabled, `TypeChecker` or an -equivalent allow-list policy is the relevant gate. Generated serializer code is -derived from checked type descriptors rather than from attacker-controlled byte -contents. - -The [security review model](#security-review-model) defines how to -classify these boundaries for untrusted deserialization paths. - -## Non-Goals - -Fory does not provide: - -- Encoded-data authenticity, integrity, confidentiality, signing, MACs, or - encryption. -- Transport security or protection for bytes while they are stored or moved - outside Fory. -- Application-level authorization or validation for the business meaning of a - successfully deserialized value. -- A sandbox for user-registered classes, functions, constructors, setters, - finalizers, or other application-owned logic. - -Applications that receive Fory data from untrusted sources should authenticate -or integrity-check those bytes before passing them to Fory when authenticity or -tamper resistance matters. - -## Downstream Responsibilities - -Applications are responsible for: - -- Choosing whether a byte source is trusted enough for the configured - deserialization mode. -- Keeping class or type registration enabled for untrusted data unless another - explicit Fory policy owns the accepted type surface. -- Registering only types and serializers that are safe for the application's - trust boundary. -- Configuring depth and resource limits for the largest data shape the - application intends to accept. -- Treating cross-language peers and schemas as part of the application's trust - relationship. - -Disabling registration or using dynamic deserialization on trusted data is a -configuration choice. For untrusted data, bypassing an explicit Fory policy, -crashing, leaking resources, retaining attacker-controlled state, or allocating -disproportionately remains security-relevant as described in the -[security review model](#security-review-model). - -## Resource Limits - -### Depth Limits - -Set the runtime's depth limit to the deepest graph the application deliberately -accepts. Some runtimes apply this limit to every nested value; others apply a -separate dynamic-object depth limit. Use the selected runtime's configuration -page for its exact scope and default. A depth limit prevents excessively nested -input from turning into unbounded recursion, but it is not a byte or memory -quota. - -### Graph Memory Limit - -`maxGraphMemoryBytes`, or the runtime-equivalent option, is an approximate gate -for graph owners materialized by one root deserialization operation. The fixed -default is 128 MiB, and explicit values must be positive. Each root operation -starts with the full configured budget, including after a failed read. - -The budget covers runtime-owned collections, maps, arrays, structs, and objects -according to each implementation's storage model. It is not exact heap -accounting, an input-size limit, or a replacement for readable-byte checks. -Actual process memory may be higher. Keep external body or file-size limits at -the boundary that receives the bytes. - -### Remote Schema Metadata Limits - -Compatible mode may receive remote metadata (`TypeDef` or `TypeMeta`) for types that are not already -known by the reader. Fory limits how many distinct remote metadata versions can be accepted, and -also limits the size of each received metadata body: - -- `maxSchemaVersionsPerType`: maximum accepted remote metadata versions for one logical type. The - default is `10`. -- `maxAverageSchemaVersionsPerType`: average accepted remote metadata versions across all accepted - remote types. The default is `3`; the effective global floor is `8192` metadata entries. -- `maxTypeFields`: maximum fields declared by one received struct metadata body. The default is - `512`. -- `maxTypeMetaBytes`: maximum encoded metadata body bytes for one received TypeDef or TypeMeta body, - excluding the 8-byte header and any extended-size varint. The default is `4096`. - -These limits are resource protections. They do not change wire format, registration requirements, -dynamic type loading, unknown-type handling, or schema-evolution compatibility. - -Raise these values only when a known peer deliberately sends larger metadata or -many schema versions. - -### Count-Driven Container Work Limit - -Every runtime limits collection elements and map entries whose repeated read -bodies do not consume proportional input. The default root allowance is `8192`. -Zero is a strict limit, and negative values are rejected. Raise the limit only -for trusted payloads that intentionally use compact zero-byte element codecs or -empty Struct bodies. This is a reader resource limit and does not change the -wire format or writer behavior. - -## Configure a Runtime - -Keep registration enabled for untrusted input, choose the wire mode explicitly, -and set limits to values derived from the endpoint's accepted models. A minimal -Java boundary looks like this: - -```java -Fory fory = - Fory.builder() - .withXlang(true) - .requireClassRegistration(true) - .withMaxDepth(50) - .withMaxGraphMemoryBytes(128L * 1024 * 1024) - .withMaxUnbackedContainerItems(8192) - .build(); -``` - -Register only the application types that the endpoint accepts. If registration -is disabled, configure the runtime's explicit type checker or allow-list before -reading external data. - -Exact option names, defaults, and mode-specific behavior belong to the runtime -configuration guides: - -| Runtime | Configuration | -| --------------------- | ------------------------------------------------------- | -| Java | [Java configuration](java/configuration.md) | -| Python | [Python configuration](python/configuration.md) | -| C++ | [C++ configuration](cpp/configuration.md) | -| Go | [Go configuration](go/configuration.md) | -| Rust | [Rust configuration](rust/configuration.md) | -| JavaScript/TypeScript | [JavaScript configuration](javascript/configuration.md) | -| C# | [C# configuration](csharp/configuration.md) | -| Swift | [Swift configuration](swift/configuration.md) | -| Dart | [Dart configuration](dart/configuration.md) | -| Scala | [Scala configuration](scala/configuration.md) | -| Kotlin | [Kotlin configuration](kotlin/configuration.md) | - -## Verify the Boundary - -Add negative tests alongside the normal round trip. Verify that the configured -reader rejects: - -- an unregistered or disallowed application type; -- a graph deeper than the accepted model; -- a graph that exceeds the configured memory budget; -- excessive remote schema versions or metadata size in compatible xlang mode; -- excessive count-driven container work; and -- a malformed root followed by a valid root on the same reusable runtime. - -Also verify the application's external authentication, integrity, request-size, -timeout, and domain-validation controls independently of Fory. - -## Security Review Model - This document defines the security model for Apache Fory deserialization. It is a public security reference for classifying deserialization behavior and deciding where validation is required. It is not a vulnerability disclosure, @@ -222,24 +14,27 @@ failures caused by untrusted input, but it should not add hot-path validation that only enforces byte-form strictness when doing so does not protect a Fory security boundary. -### Scope +## Scope -This model applies only to deserializing Fory binary object-serialization data -from untrusted or partially trusted sources. Fory JSON has a separate -[security guide](../json/security.md) because it uses different readers, -policies, codecs, and resource-accounting rules. +This model applies to deserializing Fory binary data from untrusted or +partially trusted sources. Its resource, policy, and cleanup boundaries also +apply to Java Fory JSON. The Java Fory JSON subsection under +[Graph Memory Budget](#graph-memory-budget) defines that format's accounting +scope without changing binary Fory behavior. It does not treat the semantic content of a successfully deserialized value as a Fory security boundary. A sender can always construct protocol-valid data whose value is chosen by that sender. Application authorization, object-level business rules, and domain-specific validation remain application responsibilities. -The selected business invariant remains an application policy rather than a -Fory protocol security boundary. +Java Fory JSON applications can enforce those rules with `JsonValidator` or in +a `JsonCreator`, but the selected business invariant remains an application +policy rather than a Fory protocol security boundary. -This model does not govern memory-format paths unless a runtime explicitly -exposes such a path through an untrusted deserialization API. +This model also does not cover trusted in-memory formats. Row format and other +memory-format paths are trusted-data paths unless a runtime explicitly exposes +them as untrusted deserialization APIs. -### Trust Boundaries +## Trust Boundaries Fory deserialization should treat the encoded input as untrusted at API boundaries that accept external bytes or streams. @@ -263,7 +58,7 @@ Fory security boundaries do not include: shape, unless rejecting other shapes is an explicit owner policy or protects one of the boundaries above. -### Type And Class Policy +## Type And Class Policy Type, class, function, method, registration, and deserialization policies are security boundaries when they are intended to restrict what untrusted bytes may @@ -321,7 +116,7 @@ clearly creates or copies and that remain reachable from the materialized graph. Temporary helper allocations and user-code internals remain outside that accounting boundary. -### Depth And Progress +## Depth And Progress Deserialization paths that recurse through objects, metadata, containers, or references should enforce the runtime's configured depth limit before crafted @@ -333,7 +128,7 @@ Loops that consume encoded data should guarantee byte progress, logical progress, or a terminal error. Inputs that can keep a reader in a no-progress loop are security-relevant even when they do not allocate memory. -### Security Invariants +## Security Invariants Deserialization code must prevent the following outcomes for untrusted input: @@ -354,7 +149,7 @@ When a path cannot produce one of these outcomes, earlier rejection of malformed bytes is normally a correctness or interoperability choice, not a security requirement. -### Robustness Scope Gate +## Robustness Scope Gate Before reporting or fixing a deserialization robustness finding, establish a concrete consequence in the current implementation: @@ -372,7 +167,7 @@ reserved value is accepted, rejected late, decoded differently, or produces a less precise error. Such validation is actionable only when it prevents one of the concrete consequences above or implements an explicit public contract. -### Controlled Deserialization Errors +## Controlled Deserialization Errors When a decoder determines that input is invalid for the active owner path, the root operation must return an error and run its normal failure cleanup. This is @@ -390,7 +185,7 @@ remains correct, and any relevant security invariant is preserved. They should not pin an exact error type or message when doing so would require additional successful-path validation that protects no security boundary. -### Non-Security Semantics +## Non-Security Semantics The following patterns are not vulnerabilities by default: @@ -415,7 +210,7 @@ the protocol owner, is effectively free on the relevant path, or protects a security invariant listed above. Do not add protocol-layer validation solely to reject scalar byte forms whose only effect is extra decode cost. -#### Value-bearing ref flags +### Value-bearing ref flags Some read paths intentionally share handling for multiple value-bearing flags. For example, when both `NotNullValue` and `RefValue` mean that an encoded value @@ -424,7 +219,7 @@ flag bug by itself. Treat it as a bug only if the merged handling loses required reference semantics, returns success across an explicit owner policy, or creates a resource or runtime-safety failure. -### Allocation And Byte Availability +## Allocation And Byte Availability Fory should not make large allocations from attacker-declared lengths before the required bytes are available or have been read exactly. @@ -479,7 +274,7 @@ may expose byte read and byte skip operations, but string decoding, decimal parsing, primitive-array encoding, compression modes, and collection capacity policy belong to the owning serializers. -### Collection And Map Capacity +## Collection And Map Capacity Large valid collection inputs are allowed. If the input contains many encoded elements, proportional deserialization is expected. @@ -501,7 +296,7 @@ validation can cause a no-progress loop, unbounded resource growth, retained state, or success across a Fory policy boundary. Protocol-allowed chunk segmentation is normal input and is not a security issue by itself. -### Unbacked Container Work Budget +## Unbacked Container Work Budget Runtimes enforce a root-scoped limit on count-driven collection elements and map entries whose repeated read bodies are not backed by input progress. The @@ -522,7 +317,7 @@ retain their direct loop and proportional readable-byte check. Generated and compiled serializers should remove budget access and periodic branches from those proven-positive paths. -### Graph Memory Budget +## Graph Memory Budget Runtimes should enforce a per-operation approximate gate for estimated memory created by one materialized graph. This is cumulative accounting for graph owners created by one top-level @@ -580,7 +375,7 @@ type-erased materialization paths reserve the shallow storage for the heap value Parents must not recursively include child object, collection, map, string, binary, or primitive dense-array contents; the child owner reserves its own shallow memory when it is materialized. -#### Java Fory Core +### Java Fory Core Java Fory core primitive-array serializers reserve the portable array header plus the logical length multiplied by the primitive storage width. Primitive-list serializers reserve the returned @@ -592,7 +387,43 @@ Float16 and BFloat16 dense-array carriers also include their wrapper's shallow o list conversion first decodes a primitive array, the array's reservation remains as credit toward the final list estimate, and the conversion reserves only a positive remaining difference. -#### Generated Structural Targets +### Java Fory JSON + +Java Fory JSON uses `ForyJsonBuilder.withMaxGraphMemoryBytes` to configure this per-root gate. The +default is the fixed `ForyJson.DEFAULT_MAX_GRAPH_MEMORY_BYTES` value of 128 MiB, and explicit values +must be positive. String and UTF-8 byte-array root reads use the same configured limit. Every root +read starts with the complete limit, and success or failure cannot reduce the next root operation's +budget. The limit is not derived from input length. + +Built-in Java JSON accounting includes shallow POJO and record storage, collections and sets plus +candidate element-reference slots, maps plus candidate key/value-reference slots, reference arrays +plus their slots, and Java primitive arrays plus their primitive storage. Natural `JsonObject` and +`JsonArray` values follow the same map and collection rules. Unknown-length collection, map, and +array storage is reserved in 1024-item batches before each batch's final child and at the tail. +Repeated set elements and duplicate or overwritten map members are therefore charged per input +occurrence. A reference array is charged even when its elements are leaves, and an object is charged +even when all of its properties are leaves. Primitive arrays decoded from JSON arrays reserve the +portable array header and actual Java primitive width using the same batch schedule. +`AtomicReference`, `AtomicReferenceArray`, and generic `Optional` values include wrapper and +reference storage; primitive optionals and atomic primitive values are leaves. + +Dedicated Java JSON leaf codecs are excluded from graph accounting: null, strings, characters, +booleans, numeric values including arbitrary-precision numbers, enums, temporal and other scalar +values, and binary values. A `byte[]` handled by a binary or Base64 codec remains a binary leaf; +the same Java carrier decoded from a JSON numeric array is a primitive-array owner. Byte-availability +and grammar checks still apply independently of graph accounting. + +A custom Java JSON codec that materializes composite graph owners must call +`JsonReader.reserveGraphMemory` with its application-defined byte estimate for each composite +application object, collection, map, or reference array. Unknown-length retained storage should be +reserved in bounded batches before each batch's final child and at the tail; a codec may use +stronger timing. A custom scalar or other dedicated leaf representation makes no reservation. The +budget cannot include custom allocations that the codec does not reserve, application constructor +or validator internals, temporary parsing storage, or unrelated process memory. Applications must +therefore combine this approximate gate with transport input limits, timeouts, and other resource +controls appropriate to their trust boundary. + +### Generated Structural Targets Wire members and physical storage are separate inputs. Properties, accessors, interfaces, and logical schema aliases are not physical fields and must not be charged as storage. A field that is @@ -627,9 +458,9 @@ must not add reflection, layout probing, allocation, or field enumeration to des paths. The normal owner rules still apply: a reference target reserves its shallow owner and field storage, while an inline value target is charged by the holder that owns its storage. -#### Runtime-Specific Owner Notes +### Runtime-Specific Owner Notes -##### C++ +#### C++ C++ plain structs, products, and standard-library containers are value storage unless a pointer, smart pointer, or type-erased owner allocates them on the heap. Top-level deserialization initializes @@ -643,7 +474,7 @@ materialization paths reserve the shallow storage for the heap value they alloca or returning it. Generic C++ paths must not invent standard-library header, node, bucket, allocator, or debug-layout overheads. -##### Rust +#### Rust Rust structs, tuples, enums, and collection values are inline value storage unless a `Box`, `Rc`, `Arc`, or type-erased owner allocates them. Top-level and derived value read paths initialize or @@ -667,7 +498,7 @@ count-derived backing allocation in that case. Node, bucket, and entry owners re the declared count drives allocation. Implementations must not substitute guessed allocation costs, padding bytes, a global compact-body bypass, or a second collection or map codec. -##### Swift +#### Swift Swift structs, enums, tuples, and collection values are value storage. Top-level value reads and nested value serializers should not reserve their own self storage. The holder that owns the value, @@ -679,7 +510,7 @@ type-size information, such as `MemoryLayout.stride`, when they allocate or r Class, existential, or boxed materialization paths reserve owner storage when Fory creates the retained object or box. Runtime object-layout probing should not be added to hot read paths. -##### Go +#### Go Go structs and slice or map headers are value storage unless a pointer, interface materialization, or other heap owner allocates them. Top-level deserialization and struct value serializers should not @@ -692,7 +523,7 @@ serializer or resolver when possible; read loops should not recompute reflective when the owner already knows the concrete type. Interface or dynamic paths reserve only storage that Fory clearly materializes and retains. -##### C\# +#### C\# C# combines reference owners and inline value types. Classes, arrays, lists, dictionaries, hash sets, and other heap containers reserve a nonzero shallow owner cost plus direct backing, reference-slot, @@ -711,7 +542,7 @@ table layouts unless the owner path has a cheap, stable, explicit lower-bound st documents the formula. Owner constants should be real lower bounds for the owner shape, not placeholder markers. -### Skip Semantics +## Skip Semantics Skipping unknown or incompatible data is classified by concrete impact, not by whether the runtime materializes a temporary value. @@ -732,7 +563,7 @@ that case, classify the behavior by concrete impact: - Pure strictness about whether a skipped value used one specific encoding shape is not a security issue. -### Metadata And Type Resolution +## Metadata And Type Resolution Metadata parsing is security-sensitive when it affects retained read-side state, type dispatch, or policy decisions. @@ -821,7 +652,7 @@ Metadata byte-form strictness alone is not a security requirement. Rejecting a metadata shape is useful only when the owner wants that strictness or when the shape changes type identity, retained state, resource use, or policy behavior. -### Reference Tracking +## Reference Tracking Reference tracking is part of the wire protocol and is performance-sensitive. Readers may use sentinel values and shared value-bearing branches to keep hot @@ -840,7 +671,7 @@ not rejected at the earliest possible byte. Lazy rejection is acceptable when the root operation still returns an error and no security invariant is violated. The downstream error does not need to be a dedicated reference-protocol error. -### Error Propagation And Cleanup +## Error Propagation And Cleanup Fory runtimes may intentionally use lazy error propagation. After a read records an error, later read steps may continue until the outer operation observes and @@ -858,7 +689,7 @@ Nested `try`/`finally` or equivalent cleanup should be added only when the outer root-operation cleanup cannot cover the state or resource owned by the nested path. -### Performance Requirements +## Performance Requirements Security validation must preserve Fory hot-path performance. Do not add validation solely for strictness when it introduces: @@ -875,7 +706,7 @@ Prefer owner-local checks that can be inlined and that already use information available in the current serializer. Do not move serializer-owned semantics into generic read-context helpers. -### Classification Guide +## Classification Guide Use the following questions when reviewing deserialization behavior: @@ -893,7 +724,7 @@ If the answer to the first seven questions is no, the issue is normally not a security finding. If the validation is not effectively free, avoid adding it unless the protocol owner explicitly requires it. -### Documentation Boundaries +## Documentation Boundaries Security model documents must not include exploit samples, CVE narratives, line-level vulnerability candidates, branch history, migration timelines, or diff --git a/docs/security/index.md b/docs/security/index.md new file mode 100644 index 0000000000..66f8cc7e59 --- /dev/null +++ b/docs/security/index.md @@ -0,0 +1,22 @@ +--- +title: Security +sidebar_position: 1 +--- + +This directory documents Apache Fory security models and security invariants. It +is not a vulnerability disclosure area and does not list CVE details, exploit +samples, issue timelines, or implementation history. + +Security model documents describe how Fory should classify and prevent security +risks while preserving the performance characteristics expected from Fory +serialization runtimes. + +## Models + +- [Threat Model](threat-model.md): project-level trust boundaries, non-goals, + and downstream responsibilities. +- [Deserialization Security Model](deserialization.md): concrete rules for + classifying and preventing untrusted deserialization risks. + +For vulnerability reporting, see the repository +[security policy](../../SECURITY.md). diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md new file mode 100644 index 0000000000..5fea135056 --- /dev/null +++ b/docs/security/threat-model.md @@ -0,0 +1,95 @@ +--- +title: Threat Model +sidebar_position: 2 +--- + +This document describes Apache Fory's project-level security boundaries and +non-goals. It is the high-level entry point for Fory security models; concrete +untrusted deserialization classification rules live in the +[deserialization security model](deserialization.md). + +Fory is an in-process serialization library. Applications link Fory into their +own process, configure serializers and type policies, and call Fory APIs to +serialize application-owned objects or deserialize encoded Fory data. Fory does +not provide a standalone network service, daemon, authentication system, or +transport protocol. + +Fory can generate service companions for application-provided gRPC runtimes. +Those companions provide Fory serialization for request and response objects; +the application and gRPC stack still own listeners, channels, credentials, +authentication, authorization, deadlines, retries, and transport lifecycle. + +## Trust Boundaries + +Fory's primary security boundary is encoded bytes or streams passed to +deserialization APIs from untrusted or partially trusted sources. The embedding +application owns where those bytes come from and which Fory configuration, +registered types, schemas, and policies are used to read them. + +The adversary model for untrusted deserialization is a sender that can craft +encoded bytes or stream behavior presented to a Fory read API. It does not assume +the sender can change the embedding application's Fory configuration, registered +type set, `TypeChecker` or equivalent allow-list policy, schema definitions, +classloader, or other active policy objects unless the application itself exposes +those controls. + +Fory security boundaries include: + +- Runtime safety, including avoiding crashes, panics, undefined behavior, and + out-of-bounds memory access. +- Resource ownership, including memory, CPU progress, stream buffers, native + allocations, callbacks, and retained read-side state. +- Explicit Fory policy checks, such as class, type, function, method, + registration, or deserialization policies that restrict what may be + materialized. +- Cleanup boundaries, where state created during a failed root operation must + not leak into later operations. + +Runtime serializer code generation and JIT compilation are not paths for +executing encoded input. They operate on types and schemas after the active +registration check, `TypeChecker`, schema check, or policy check has accepted the +type surface. When class registration is disabled, `TypeChecker` or an +equivalent allow-list policy is the relevant gate. Generated serializer code is +derived from checked type descriptors rather than from attacker-controlled byte +contents. + +The [deserialization security model](deserialization.md) defines how to +classify these boundaries for untrusted deserialization paths. + +## Non-Goals + +Fory does not provide: + +- Encoded-data authenticity, integrity, confidentiality, signing, MACs, or + encryption. +- Transport security or protection for bytes while they are stored or moved + outside Fory, including transport security for generated service companions. +- Application-level authorization or validation for the business meaning of a + successfully deserialized value. +- A sandbox for user-registered classes, functions, constructors, setters, + finalizers, or other application-owned logic. + +Applications that receive Fory data from untrusted sources should authenticate +or integrity-check those bytes before passing them to Fory when authenticity or +tamper resistance matters. + +## Downstream Responsibilities + +Applications are responsible for: + +- Choosing whether a byte source is trusted enough for the configured + deserialization mode. +- Keeping class or type registration enabled for untrusted data unless another + explicit Fory policy owns the accepted type surface. +- Registering only types and serializers that are safe for the application's + trust boundary. +- Configuring depth and resource limits for the largest data shape the + application intends to accept. +- Treating cross-language peers and schemas as part of the application's trust + relationship. + +Disabling registration or using dynamic deserialization on trusted data is a +configuration choice. For untrusted data, bypassing an explicit Fory policy, +crashing, leaking resources, retaining attacker-controlled state, or allocating +disproportionately remains security-relevant as described in the +[deserialization security model](deserialization.md). diff --git a/docs/start/cpp.md b/docs/start/cpp.md index 52da0aac24..bc8384134b 100644 --- a/docs/start/cpp.md +++ b/docs/start/cpp.md @@ -95,7 +95,7 @@ cmake --build build --parallel See [C++ Object Serialization](../object-serialization/cpp/index.md) for Bazel, Windows, error handling, and thread-safe instances; then continue to -[xlang](../object-serialization/cpp/core-api.md#cross-language-interoperability) or +[xlang](../object-serialization/cpp/basic-serialization.md#cross-language-interoperability) or [native mode](../object-serialization/cpp/native.md). ## Other Capabilities diff --git a/docs/start/csharp.md b/docs/start/csharp.md index 45d4b13fd2..0167f6b0ad 100644 --- a/docs/start/csharp.md +++ b/docs/start/csharp.md @@ -71,7 +71,7 @@ dotnet run C# uses xlang mode. Continue with [C# Object Serialization](../object-serialization/csharp/index.md), -[xlang types](../object-serialization/csharp/core-api.md#cross-language-interoperability), +[xlang types](../object-serialization/csharp/basic-serialization.md#cross-language-interoperability), [configuration](../object-serialization/csharp/configuration.md), and [schema evolution](../object-serialization/csharp/schema-evolution.md). diff --git a/docs/start/go.md b/docs/start/go.md index 06cd4310ef..969fb106ee 100644 --- a/docs/start/go.md +++ b/docs/start/go.md @@ -81,7 +81,7 @@ func main() { go run . ``` -Use [xlang mode](../object-serialization/go/core-api.md#cross-language-interoperability) for cross-language data +Use [xlang mode](../object-serialization/go/basic-serialization.md#cross-language-interoperability) for cross-language data and [native mode](../object-serialization/go/native.md) for Go-only data. Continue with [Go Object Serialization](../object-serialization/go/index.md), [configuration](../object-serialization/go/configuration.md), and diff --git a/docs/start/java.md b/docs/start/java.md index dad3c1e079..bc38eedafd 100644 --- a/docs/start/java.md +++ b/docs/start/java.md @@ -87,7 +87,7 @@ Reuse a `Fory` instance within one thread instead of rebuilding it for every value. `Fory` is not thread-safe; use `ThreadSafeFory` for shared concurrent access. Continue with [Java Object Serialization](../object-serialization/java/index.md), -[xlang mode](../object-serialization/java/core-api.md#cross-language-interoperability), +[xlang mode](../object-serialization/java/basic-serialization.md#cross-language-interoperability), [native mode](../object-serialization/java/native.md), or [configuration](../object-serialization/java/configuration.md). diff --git a/docs/start/javascript.md b/docs/start/javascript.md index fd1758fa6b..c2577997d5 100644 --- a/docs/start/javascript.md +++ b/docs/start/javascript.md @@ -65,7 +65,7 @@ node example.cjs JavaScript uses xlang mode. Continue with [JavaScript/TypeScript Object Serialization](../object-serialization/javascript/index.md), -[xlang types](../object-serialization/javascript/core-api.md#cross-language-interoperability), +[xlang types](../object-serialization/javascript/basic-serialization.md#cross-language-interoperability), [configuration](../object-serialization/javascript/configuration.md), and [schema evolution](../object-serialization/javascript/schema-evolution.md). diff --git a/docs/start/kotlin.md b/docs/start/kotlin.md index efd832a98d..e2fe8f74ab 100644 --- a/docs/start/kotlin.md +++ b/docs/start/kotlin.md @@ -70,7 +70,7 @@ If the project applies Gradle's `application` plugin, run its application task: Use xlang mode for data shared with other Fory runtimes or native mode for Kotlin/JVM-only data. Continue with [Kotlin Object Serialization](../object-serialization/kotlin/index.md), -[xlang](../object-serialization/kotlin/core-api.md#cross-language-interoperability), or +[xlang](../object-serialization/kotlin/basic-serialization.md#cross-language-interoperability), or [native mode](../object-serialization/kotlin/native.md). ## Other Capabilities diff --git a/docs/start/python.md b/docs/start/python.md index 952f16c731..d7c7387bf9 100644 --- a/docs/start/python.md +++ b/docs/start/python.md @@ -60,7 +60,7 @@ decoded = fory.deserialize(data) print(decoded) ``` -Use [xlang mode](../object-serialization/python/core-api.md#cross-language-interoperability) for cross-language +Use [xlang mode](../object-serialization/python/basic-serialization.md#cross-language-interoperability) for cross-language data. Use [native mode](../object-serialization/python/native.md) for Python-only objects, including Python callables and serialization hooks. Continue with the [Python guide](../object-serialization/python/index.md), diff --git a/docs/start/rust.md b/docs/start/rust.md index e4924168fe..431c944159 100644 --- a/docs/start/rust.md +++ b/docs/start/rust.md @@ -63,7 +63,7 @@ fn main() -> Result<(), Error> { } ``` -Use [xlang mode](../object-serialization/rust/core-api.md#cross-language-interoperability) for cross-language data +Use [xlang mode](../object-serialization/rust/basic-serialization.md#cross-language-interoperability) for cross-language data and [native mode](../object-serialization/rust/native.md) for Rust-only data. Continue with [Rust Object Serialization](../object-serialization/rust/index.md), [configuration](../object-serialization/rust/configuration.md), and diff --git a/docs/start/scala.md b/docs/start/scala.md index 3600f5e2a2..5e8a83c51b 100644 --- a/docs/start/scala.md +++ b/docs/start/scala.md @@ -69,7 +69,7 @@ sbt run Use xlang mode for data shared with other Fory runtimes or native mode for Scala/JVM-only data. Continue with [Scala Object Serialization](../object-serialization/scala/index.md), -[xlang](../object-serialization/scala/core-api.md#cross-language-interoperability), or +[xlang](../object-serialization/scala/basic-serialization.md#cross-language-interoperability), or [native mode](../object-serialization/scala/native.md). ## Other Capabilities diff --git a/docs/start/swift.md b/docs/start/swift.md index 61b965c792..56bb072fd8 100644 --- a/docs/start/swift.md +++ b/docs/start/swift.md @@ -77,7 +77,7 @@ swift run Swift uses xlang mode. Continue with [Swift Object Serialization](../object-serialization/swift/index.md), -[xlang types](../object-serialization/swift/core-api.md#cross-language-interoperability), +[xlang types](../object-serialization/swift/basic-serialization.md#cross-language-interoperability), [configuration](../object-serialization/swift/configuration.md), and [schema evolution](../object-serialization/swift/schema-evolution.md). diff --git a/go/fory/README.md b/go/fory/README.md index cf36760702..530ba62023 100644 --- a/go/fory/README.md +++ b/go/fory/README.md @@ -139,11 +139,11 @@ For comprehensive documentation, see the [Fory Go Guide](https://fory.apache.org Topics covered: - [Configuration](https://fory.apache.org/docs/object-serialization/go/configuration) - Options and settings -- [Basic Serialization](https://fory.apache.org/docs/object-serialization/go/core-api) - Core APIs and usage patterns +- [Basic Serialization](https://fory.apache.org/docs/object-serialization/go/basic-serialization) - Core APIs and usage patterns - [Type Registration](https://fory.apache.org/docs/object-serialization/go/type-registration) - Registering types for serialization - [Supported Types](https://fory.apache.org/docs/object-serialization/go/supported-types) - Complete type support reference - [References](https://fory.apache.org/docs/object-serialization/go/references) - Circular references and shared objects - [Schema Evolution](https://fory.apache.org/docs/object-serialization/go/schema-evolution) - Forward/backward compatibility -- [Basic Serialization](https://fory.apache.org/docs/object-serialization/go/core-api) - Default xlang APIs and interoperability +- [Basic Serialization](https://fory.apache.org/docs/object-serialization/go/basic-serialization) - Default xlang APIs and interoperability - [Thread Safety](https://fory.apache.org/docs/object-serialization/go/thread-safety) - Concurrent usage patterns - [Troubleshooting](https://fory.apache.org/docs/object-serialization/go/troubleshooting) - Common issues and solutions diff --git a/docs/images/idea_jdk11.png b/images/idea_jdk11.png similarity index 100% rename from docs/images/idea_jdk11.png rename to images/idea_jdk11.png diff --git a/docs/images/logo/fory-horizontal-black.png b/images/logo/fory-horizontal-black.png similarity index 100% rename from docs/images/logo/fory-horizontal-black.png rename to images/logo/fory-horizontal-black.png diff --git a/docs/images/logo/fory-horizontal-black1.png b/images/logo/fory-horizontal-black1.png similarity index 100% rename from docs/images/logo/fory-horizontal-black1.png rename to images/logo/fory-horizontal-black1.png diff --git a/docs/images/logo/fory-horizontal-white.png b/images/logo/fory-horizontal-white.png similarity index 100% rename from docs/images/logo/fory-horizontal-white.png rename to images/logo/fory-horizontal-white.png diff --git a/docs/images/logo/fory-horizontal-white1.png b/images/logo/fory-horizontal-white1.png similarity index 100% rename from docs/images/logo/fory-horizontal-white1.png rename to images/logo/fory-horizontal-white1.png diff --git a/docs/images/logo/fory-horizontal.png b/images/logo/fory-horizontal.png similarity index 100% rename from docs/images/logo/fory-horizontal.png rename to images/logo/fory-horizontal.png diff --git a/docs/images/logo/fory-horizontal1.png b/images/logo/fory-horizontal1.png similarity index 100% rename from docs/images/logo/fory-horizontal1.png rename to images/logo/fory-horizontal1.png diff --git a/docs/images/logo/fory-icon-black.png b/images/logo/fory-icon-black.png similarity index 100% rename from docs/images/logo/fory-icon-black.png rename to images/logo/fory-icon-black.png diff --git a/docs/images/logo/fory-icon-white.png b/images/logo/fory-icon-white.png similarity index 100% rename from docs/images/logo/fory-icon-white.png rename to images/logo/fory-icon-white.png diff --git a/docs/images/logo/fory-icon.png b/images/logo/fory-icon.png similarity index 100% rename from docs/images/logo/fory-icon.png rename to images/logo/fory-icon.png diff --git a/docs/images/logo/fory-vertical-black.png b/images/logo/fory-vertical-black.png similarity index 100% rename from docs/images/logo/fory-vertical-black.png rename to images/logo/fory-vertical-black.png diff --git a/docs/images/logo/fory-vertical-black1.png b/images/logo/fory-vertical-black1.png similarity index 100% rename from docs/images/logo/fory-vertical-black1.png rename to images/logo/fory-vertical-black1.png diff --git a/docs/images/logo/fory-vertical-white.png b/images/logo/fory-vertical-white.png similarity index 100% rename from docs/images/logo/fory-vertical-white.png rename to images/logo/fory-vertical-white.png diff --git a/docs/images/logo/fory-vertical-white1.png b/images/logo/fory-vertical-white1.png similarity index 100% rename from docs/images/logo/fory-vertical-white1.png rename to images/logo/fory-vertical-white1.png diff --git a/docs/images/logo/fory-vertical.png b/images/logo/fory-vertical.png similarity index 100% rename from docs/images/logo/fory-vertical.png rename to images/logo/fory-vertical.png diff --git a/docs/images/logo/fory-vertical1.png b/images/logo/fory-vertical1.png similarity index 100% rename from docs/images/logo/fory-vertical1.png rename to images/logo/fory-vertical1.png diff --git a/javascript/README.md b/javascript/README.md index 0a2fb277b6..094ee9285a 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -114,7 +114,7 @@ Readers can skip unknown fields and tolerate missing ones, supporting rolling up Full documentation is available at [fory.apache.org](https://fory.apache.org): - [JavaScript Serialization Guide](https://fory.apache.org/docs/object-serialization/javascript) -- [Basic Serialization](https://fory.apache.org/docs/object-serialization/javascript/core-api) +- [Basic Serialization](https://fory.apache.org/docs/object-serialization/javascript/basic-serialization) - [Supported Types](https://fory.apache.org/docs/object-serialization/javascript/supported-types) - [Schema Evolution](https://fory.apache.org/docs/object-serialization/javascript/schema-evolution) - [Xlang Serialization Spec](https://fory.apache.org/docs/specification/xlang_serialization_spec) diff --git a/javascript/packages/core/README.md b/javascript/packages/core/README.md index b861c07020..c9e4f45b46 100644 --- a/javascript/packages/core/README.md +++ b/javascript/packages/core/README.md @@ -141,7 +141,7 @@ const bytes = serialize({ id: 1n, content: "hello from JavaScript" }); ## Documentation - [JavaScript Serialization Guide](https://fory.apache.org/docs/object-serialization/javascript) -- [Basic Serialization](https://fory.apache.org/docs/object-serialization/javascript/core-api) +- [Basic Serialization](https://fory.apache.org/docs/object-serialization/javascript/basic-serialization) - [Supported Types](https://fory.apache.org/docs/object-serialization/javascript/supported-types) - [Schema Evolution](https://fory.apache.org/docs/object-serialization/javascript/schema-evolution) - [Xlang Serialization Spec](https://fory.apache.org/docs/specification/xlang_serialization_spec) diff --git a/swift/README.md b/swift/README.md index 079ae4e3aa..fc271469e6 100644 --- a/swift/README.md +++ b/swift/README.md @@ -486,7 +486,7 @@ ENABLE_FORY_DEBUG_OUTPUT=1 FORY_SWIFT_JAVA_CI=1 mvn -T16 test -Dtest=org.apache. - [Schema Evolution](../docs/object-serialization/swift/schema-evolution.md) - [External-Type Serialization](../docs/object-serialization/swift/external-types.md) - [Custom Serializers](../docs/object-serialization/swift/custom-serializers.md) -- [Basic Serialization](../docs/object-serialization/swift/core-api.md) +- [Basic Serialization](../docs/object-serialization/swift/basic-serialization.md) - [Xlang Specification](../docs/specification/xlang_serialization_spec.md) - [Xlang Type Mapping](../docs/specification/xlang_type_mapping.md) From e6f4737908625292fa72e783144de157f5effd0e Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 11:51:14 +0800 Subject: [PATCH 12/15] docs: use Rust in xlang example --- docs/object-serialization/xlang.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/object-serialization/xlang.md b/docs/object-serialization/xlang.md index 8a900b9ae9..bfd8ea7b4f 100644 --- a/docs/object-serialization/xlang.md +++ b/docs/object-serialization/xlang.md @@ -64,21 +64,25 @@ Python pickle-compatible objects. Register the same logical type identity and compatible fields on every peer. The following example uses a shared type name. -Java producer: +Rust producer: -```java -public class Person { - public String name; - public int age; +```rust +use fory::{Fory, ForyStruct}; + +#[derive(ForyStruct)] +struct Person { + name: String, + age: i32, } -Fory fory = Fory.builder().withXlang(true).build(); -fory.register(Person.class, "example.Person"); +let mut fory = Fory::builder().xlang(true).build(); +fory.register_by_name::("example.Person").unwrap(); -Person person = new Person(); -person.name = "Alice"; -person.age = 30; -byte[] bytes = fory.serialize(person); +let person = Person { + name: "Alice".to_string(), + age: 30, +}; +let bytes = fory.serialize(&person).unwrap(); ``` Python consumer: @@ -94,7 +98,7 @@ class Person: fory = pyfory.Fory(xlang=True) fory.register_type(Person, name="example.Person") -person = fory.deserialize(bytes_from_java) +person = fory.deserialize(bytes_from_rust) ``` Although xlang is the default, examples often select it explicitly so the transport contract is From db54ec445edd73cf00eda53adb4f0642392c48dc Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 11:53:42 +0800 Subject: [PATCH 13/15] docs: colocate repository images --- CONTRIBUTING.md | 2 +- README.md | 2 +- .../logo/fory-horizontal.png => fory-logo.png | Bin images/idea_jdk11.png => idea-jdk11.png | Bin images/logo/fory-horizontal-black.png | Bin 13643 -> 0 bytes images/logo/fory-horizontal-black1.png | Bin 15918 -> 0 bytes images/logo/fory-horizontal-white.png | Bin 13011 -> 0 bytes images/logo/fory-horizontal-white1.png | Bin 15111 -> 0 bytes images/logo/fory-horizontal1.png | Bin 23084 -> 0 bytes images/logo/fory-icon-black.png | Bin 10846 -> 0 bytes images/logo/fory-icon-white.png | Bin 10318 -> 0 bytes images/logo/fory-icon.png | Bin 19630 -> 0 bytes images/logo/fory-vertical-black.png | Bin 12770 -> 0 bytes images/logo/fory-vertical-black1.png | Bin 13897 -> 0 bytes images/logo/fory-vertical-white.png | Bin 12018 -> 0 bytes images/logo/fory-vertical-white1.png | Bin 13045 -> 0 bytes images/logo/fory-vertical.png | Bin 18333 -> 0 bytes images/logo/fory-vertical1.png | Bin 19531 -> 0 bytes 18 files changed, 2 insertions(+), 2 deletions(-) rename images/logo/fory-horizontal.png => fory-logo.png (100%) rename images/idea_jdk11.png => idea-jdk11.png (100%) delete mode 100644 images/logo/fory-horizontal-black.png delete mode 100644 images/logo/fory-horizontal-black1.png delete mode 100644 images/logo/fory-horizontal-white.png delete mode 100644 images/logo/fory-horizontal-white1.png delete mode 100644 images/logo/fory-horizontal1.png delete mode 100644 images/logo/fory-icon-black.png delete mode 100644 images/logo/fory-icon-white.png delete mode 100644 images/logo/fory-icon.png delete mode 100644 images/logo/fory-vertical-black.png delete mode 100644 images/logo/fory-vertical-black1.png delete mode 100644 images/logo/fory-vertical-white.png delete mode 100644 images/logo/fory-vertical-white1.png delete mode 100644 images/logo/fory-vertical.png delete mode 100644 images/logo/fory-vertical1.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba8bfa6f03..5bd6fd5135 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -229,7 +229,7 @@ To use Jetbrains IDEA IDE for Java Development, you need to configure the projec And due to the usage of `sun.misc.Unsafe` API, which is not visible in Java 11+, you need to configure java compiler with `--releaese` option disabled.
-
+
## Website diff --git a/README.md b/README.md index 6d49cfa4c7..a3051ae90e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
- Apache Fory logo
+ Apache Fory logo
[![Build Status](https://img.shields.io/github/actions/workflow/status/apache/fory/ci.yml?branch=main&style=for-the-badge&label=GITHUB%20ACTIONS&logo=github)](https://github.com/apache/fory/actions/workflows/ci.yml) diff --git a/images/logo/fory-horizontal.png b/fory-logo.png similarity index 100% rename from images/logo/fory-horizontal.png rename to fory-logo.png diff --git a/images/idea_jdk11.png b/idea-jdk11.png similarity index 100% rename from images/idea_jdk11.png rename to idea-jdk11.png diff --git a/images/logo/fory-horizontal-black.png b/images/logo/fory-horizontal-black.png deleted file mode 100644 index 2fff2442c4d8160873ec6ea07575c456e927e6a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13643 zcmeHui96Ko+xJM(hPu0p%2F9LB@#8ZF{xXcu}qDS!5|?<46?7S?lx+SeQO&MV;|WD zca#R9)Y!M|4Tizkd9JUX-}^p)!gIXu-EnlJ`Of#c&g(os=jU9m)5A-~`Xbv8Y=^;M zBAAPR;bE{X4`8rOhqelVPgG`^+rU5D$QLcVU@#?F=&yhu4ebMi9fD#0(!JuBG~SJS z;xOhf%^#87s;dic*!t{j_9^qhBe{aPiN^!%=cNukyMJ^_4XG0U<@?`1j~qXZ%oE>z z^{(PIw-0IeH*amhCMej?0JAZqF8ORO?LO48s*1< zj<6x|qzgz0D^iG{gnWqv+KuQ=)qPCVGyz!GQ@sS<$L)bq@ z9|%A{2)&ko4g`~Ua!msI*Y@PUU-(xK|Kj0azxbCQ01y1{;K`tJZy0Rh5@Ki$&Vj#A zN9R=boo|ZLHs%$xOXFn^0y!n4@T8l`0%5m}9HzSA^OUaAPw9S?K}FViITi5OD&m?% zn0(r~g`U|F1}eBi7-PJ*27Er{3E1Az9INpqWJkYKux#g@g}Z{mA>heXwT~xZ1qfP~ zktdpUP*rmB2#ZPJwC}9l1eBt8d8Z(mOrx3>1%`3e)K{Whcl?|Yss({0Oer`E$Qoe| zWX0F5!qJ9_c=h(4=1DCYHQYp31gu=GsgH?V0 zvl=#Tif~At==fU$JZo)nbvO29Yi*^%BI+o#0lwsB*Dpf^hl@YLI~#&c$O;$)SZ$^ z_0;M-w;(YOoe!?SV9)&CO`}}TnkmJU3OkkgrarinWJUvf;;Tc#ia^qmP zu;BB*f%aBi6)fXxTj1$@F+mut`QXQsRnHUI2fA=PvrhQ=K{H>|- zB`s&R#ZIYpj`fHCek2V&ih~~cmpSjs9=J@2B5=|+Eb?hOk1YTzjtBao)L!1M{398T zR-|>M^3MS&tNw|X5W4ynUk9ASK5|N>tl2*NvI4)Hl;jqqVE3g@@SwCljU$=!8V0VI84(FYoVY~9~XE}Io~>+%pYGr z8U1En)$H=x2QHMPSw%mz3lojTo6)E<@LpCNZ3;=rB3`+}CZOLkQsDlooezKRE{_>x z;_DV~eE=`OvcJ@vJgc9EX={O3cB^BLN9HF#3J!{{yn@wW`P^AxoWJ+$`(1cCgKe~d zLdD2y5-;}LjY*U#oldxyaiubLFmri3118?C<2cz$K;Mvp9C_%{PC+#@Y)LYJvE|d( zg}kS(3O4h2*BHE6dkC64J}`5l=6|5~s*u(Lfdj{AN%FtdB)8~FpX<>{w!tWQoS|f8)L)%C%GQjIo4-=?6nPUso^@MRM%~!mA%~cEM z$?o%BQ?s9lLz`9J`cd0hNa1J)C&NMrBx{D~^s$!J6FsElfxQ6TJ_Xic)!?uVuFaZkejxcdIu{WCRKu8s{USiSWhEF$FLgn86K`xrn%{-o@Iyif+) zGr6+WS!1Qq%3Qy@W>Z~1virP19~pR9+-1a&qCJ%&6OeGzqR`EHy8?zj7u11XF)sS} zQKc;UdJ)CV(bsEbJ;XrGNnl~gs`e*=Snm@sA9QSd*puSnQ!n#cp>_2!gI$YZk8zx; zojI^rmFQM(VdL~B?$P@Kq6WYxqX4R(6x1DR&wg5r8rj_MVB~(stld%D(KoF=GqUa$ zrT>k`&W?lv*TE$>2TReMVf8k(w#g3Mw%~23&{3h$ zI$fLxxU9Wy1)>g{RF2>H7b2hP>!Tb_8$VEyJQzs#8uwGP?%z^%8~9>e?W$xbqU%S; z-}UbpZfQsv_q0^R>)^6p&8oxS30#N3BX_3bB#1GVUdG030#{*?ZB_ePxn*!cGdL@H zG{nX4*tfvaQVzy&O|ant;1b)GzkaFYJS)CfOOjP0b)d`Z_@uG^FM~SW@owe5PsR^U zR(C0wnyJb}CYB**rj0%CCNtDLDspgY(bdePKT$WO`t~+b%D-yX3?AvsY>b%vR03n- zcM3}O0G$iZ(p`7-x7aVrg_J(=j$ZSb=>85q4nK*SY_uFXlo%iRK4HxoHi3{LtY)3!`y{p+WyEu&&rMR;7p%PWasccl>nwU%<;X8R2a$POHjFe@E0vC2oB7NyA? z#lqS`(O=*K$TZ()lRH@?f9FmJ8VWm)%$##=q|#z zjmJqxVsrw$3<3-26pf!OUw)IlgNCT5r*pyq9aPOo#e){MfKL)Fb&o49Ewd1I<1Dlk zhb_SGd)s=!id{(jsm982EaAxY#Cg?@82*VK@Hl1r@_?1Ir=j_{WZB(hs|4f8gaawJ zip&?BBL;jO!ahO=`+TM9#G=vIC1BP}knOMy%Hwr%eL@8s~n`#2Z6 z7dKtFZ$BWVYK!-xWIb_=^qVNgZV*5q#s<*!$`e~Jj~)!CQ45T>|7>xsX9?eo+R15q zG)e1d@XMNeQhP15(Mczz^?^fs1Nljys^q{asT*CZN^-4rt~O`#4iG~wC@BUVkQd!a{$2Se(I5 zc~m=+ko{r7g(HchuF?2z>>G@-}z=2N8HFK|%e zeVVzg1HL%1U({EDC1!wdT2}nq0FjBW^QX9{Mb?w`f3(yLr+J6z0#L>&4&g^lIj|^; z9WR~p%f;(*%}LWinm5S}BO0h0g$bZ9Bq3{IQ=ccWY{M&Reu|Q1u<}f7hP#5l6;^{e z7V*t_(}+h0R{;({n`_^qS#=0_gJBGwKX+<(sk79JmrA%JPlzcpGvCR;Zz@em-US+A z5r_u@>TjJ(`F2Htx|T72<~Wp*yi}vh%<hz!l}KV}i>x%QOZY zhX?3RIz9D@L-2D3^c39ux7v4+QK-<0f?1CimO}e_t58)6v@VoPIv2S=3T*d?bv5YD ziN&9ZP5u^N5yAaPCA$a2KQP>P=}+m0M4w@$m*bX<4XMwbXI7k<`a{DYVAm3T4?+-RzaJ;P{sC=%WHZY!Ob18^HSzi*P_maC#b#^s{#iBGyPylUrj3%v@96QBJ3N{HB} zZ#?o;o)I54rqczj@W`g;La|Z41^o1ZJ6W+KCSo5KRSZQ^X_0$)y&xfEhze8<0c}P! zs7-Vj9nL)~e=qiBEdK6dmZflv#4V+I;;|YhXxd3;hrhd~TUR3K=fdn6fLdz(!P#5@a^&Xn zBmK>mOCPqTs41gtUWJc+dlUJv-_Rr*WhFumh!&f?kuwzq_q7a%_5Io)i6-R4x~(xQ27?DWNbFl%5ub^X44_0t&V)v+D~jf0mqyC9=bzGv6atn6p-fMgxWi zn1(qyA^@H~;PZt`^tmiRdVI8vu~$j$v_*%L5v5Fu`%S2CFK`>xPASru2#{06Y5rhc3Oq%HDom-mO0RX0pq<#8p+S@kSdZ_{eUBIyi`Y95i*67<3 z;2$)b+cKXMhi#O0i}PE0wf+*=1_b2>m|`5LFNp6|n?!CmI;jg4<^xm2G9MLOWr$9_ z6jhK+O;3zc5K&U$4wJZ7m3vCbjw7o|`a{5{Abk|p6f0Pi%4yJwxKz3S#BxNh6L@BoS)s5qv_C?9C9Vq7r&!vcSHT3i@+K|C!ZBW0H8D*2eW)*X8i z0rE53+OFOlqO<}g9sO75k983xh?f2n|kv9UYK z)5~vnRg52dLf{CS+z!$TF83dp3P{{)^=sy)GZCC7|K1(})ZS39(8_?o;L*#--q-iD zn3_+cnn3QMKtB&7OMt}&@@ouMq~VJ{GXMP0w2?xe;ETyrdPDRxKWrd%^~1w3z(rby zJLl75xnGZjnBh#SWpQ;nPgBca`k>?`TQyn=7vB-v(H}MTwDoB_TKgXOewhn>gsyrr z^}eBcQN`ejtVo4Tw_&wU!~E#4NvDR4{>N}%abBr#)oBPz-k*`z#dnk-KEH;ib~_zj zQCtp>?05&ItpK9mR`1z6E!$fLRl1#4ANKMN`gQ|^$H8*%u=<2YUw5ZJ;GXb9?T5v_ z0mVS;;oia&nJF)B^=r60o6zc2{xvsr;l@*9(9_TN28QSa;WiZx)eiA-V9*nQMe+L$ zlWMN9Lc4%EXkdj6eB~5QMXfj-4$_(bXF^6tli79e$>OZ%sXnlo!RSz6^b@Ad0tUb` z517ldN3+V*(4=~vFcQbQJ659!69il$898Gfd=}w=TGdEi%MyJyKrGra{u<8xPH>+m zW9X~Z^{wqjpD7Mqe~(MVZDy83s=d&o+%;p&Ci+~OUqJI?nseSIiTJ*UeCZrSe2oy>`wC&eIlV3r2rVKHS z`mIHoUm5WB;1eAjVkm^to5Jzy`m9H1BvO{%VG)V5s{Tx6nV1g-C&mSe`JsLuz!E3RfFfiFXF(;`6sKX(&&pNM5Wg`tGyt?MOvWL0o|FeZS}gs85XiLa z&Kj2?J^?QXx~v9W;#(7Oy_x$W6HV{hd!iEA0&dqdF}DmAqAFD;a6-0cfnhz83q_kj z`=GthvA^3@IrEJ&YOl@fHZ`3PWs_WkqqhSBLR=3>&a^2_pdG;i{Jui(Fw~+M{l}MK zm{1zP6;64JKBc^~?^N$c5GozDg845%uc`mS?HN+=o>#=WMs{y_{pgreLssk?T-n*C|Kr9nt+{3hM zDE2Qmw?tU)sV;9We9(V2sYsM8!ZoCwfdLv5Dj2MnF0)-Ht1`2$?KdjGv^TQDZ=qDI zuw%a#zrI+o{1T*{%>_nURx|2W8k@f@EVLQc2D*iDI1X~M_v){E7ZF25#85Bja}+gF z+R$AWmCGFUTk>~J4EF{JALu8rCNZDXlPQAmsTL z;^bBA;yJL0{9*vb#+YY@7{VDKDu1XG{#XuoV%KN4zf@ED%QDq)x%rRnsoub&2uX^T zr`6@neV@xJK1k2#?{WXN3};dx`e9%vCymIvL@Xqy6*rR+T*U=3BU!H;>^8 z`YTM~J|QTYaWQnpJSGN?K@6$VsOwO7c2Ca26iyDL&i-weJ^a|3RnLDpc-~T@x4xO> zaVzefasXTwVexntU+2;|;Bm8;P{%#esuG>e3<>rX&lMF8y8srRaW*CTgjSbi>9V= zOTcl|iQ;3o2*p%h0Ak3FA|t=pC5@g_plBm#EB{z=`1iBJa|jKRN^!hc_iUea?yC6;b3~)$LdD&9Br zINJe({W%TI0+eAwR?|$rx3tdK)KaGuH7yZSZ`q}8QrAFQ(hW79Hj0lMDh?GcrX0>H1saW&`>;~5I9R*GUCrH_7W{~ZOGYadR-bBm)Hp+a>A&?FE=Q2pT)mYnCbOX8XJ+n&*F0` zV<+A?;)bRIY1E_PVx*;D`a#r+($=%<0KKa5b-huX70_pvj}4sZTzTNta&Kfke&UUr zyH3m?(g3lpM^SDi^j0?50F66gD&hx6ME6IG{GuH)w2IM62lNV3!PsUK0S!(K89_^L z4TrZIq6=C}I<|Fdl$&GQkM0l%vv$)F&fS@55Yn|O%C}q7T~fU7yLyh&t4z~YqLt4+ z4MYshQ&t`yy>B@N*6rwda#M{t9^F$YI&~_UPQVPK{lB$v&Yv&fvMuhZMJ-`$u%yGiwJcavMxMGQGjCJ~{Q3NI&WWq2j8Ef1=3 zMvj(>MFqp$-AyB&%Tr>~kcw{WVPf>yPL$T(_scmcE{ zsH$L!cR^3yE4KW~!e#oLQyQ^^l8WO+ViBj!^k?5Bu4YJOIq%oQIoNPbexibN2!@~d zSKM^wIjMF1zK)H&V|8}0Lk|H)wwc8)#6<}@g|&MZq34`Iq#aAkB2<}50J{E`_7^up zBDBBNhJ9?6VegOwAl*kxB*h7J>a9Mp9h|_?~f`Hgh`h%{a-0YQKjH6%0XXXGz z%4Ys5DQwMW9=679(Sn_n{dn^1x|#A~uhO?#k&(V#0mp&^Y!GBa;6;W3srx{Oo2X3C z*55Zb*Gj-eX{!fOkNeAZvh0SYL_o)gt~&0L%RLx&AGG*p-RCIlUqnXK9Z`GtrZ~+& zyN@khKkKe{#WCd_m%Qb}@F|%qBoroeXo`L`T)_4sByoV6b;9;qmu?_3s2YWM7eH^M z2PXu0!tWE5};<5v14sH(Ca~%5ajfu zOt|*;WTV?YD3zp&6dBgaM=~xldhl0dtT(k9cGezjw?ny7yrJFe z*kW?{$1*D{`_We+r0&F69wjr|J3%KzYG^9&Drm6`K$o`AZn@gx7-P%uu=BrMJ!fBe zhuxl?1%ZjRk)0E$i71p=_8g>O%u?XflITH*gZricyjCqyGwZ3P{gQ&C&IJ1WovFB6 zGu(=>)^2u}YLa>+th=!-IHW*JNtHg%C{~~zX z4AYPvTc6itDeQMY1t_c-O@%Y&3SL^^YEQT`ApI+pTPT;XnZ3Au%mq8LT+Eu9e zVQK4y0+15vbITkf&jI#8O!t6v$?wx>)AKJCI$y7d=1_vXZBo5}A1N0BKhkd{kvGx= z*yz`TIcv}S8>TJRV&CISQg#t}%Ucs6#He>KQGK~je#^+p!* z>!kOhu&VsxMM+q5Vv5=JVntsaps(VXeOkq>A`y#663++!biP>~bv}6QpkEHv^PA@8 zrqfFVaai_6m7OsA#n+(vwgX5ObP69KPG1=E&gF5Z4+4!v`o9y;&j?!na#a>Ac^O71i_hG^9lln7!ofBoHnyr!Dm&zur zdvJD1cKL#u532LD&VYVpjoX+5AgAWG#g}52NUOHTY@4FWeMSeYzcy=y8z4CB{XVCQ zcZ0Me9junLt`KZ=npp=fKG&&(P>f;QsmHm4taBS`?>zvL3zx7Kw8{^U>)RXrbAgQ; z)LvSUg0+JuM#i`$o|RPIRYZH&DDuwcz5}4Cs{V|?k!PhAHhSpE81NE7BJ-ZgyU(_3 zD?`Ui$1*t?vl|@&AgWbovqN8!!|1K7Ijh>TJFS$YSbTtH1~L}P7$1ZvZ6c$9hgL6{ zgKk-iGJJ6rUpD|!BYJf%vKuWOm?&g*@ww<1LoM=1ValSPvg-1^;VYt*s`DPoc7vY9 z;`|+gWQUCfxIi~AEioaNu!~H#ZrfuyZD|{K0IW!4;GL8wYm&~VUTRFtn2OGC*lG`$ z4TuMq3SlmEa6RS0kwji+)_5#LjZL?erEK5QVm>p?B&O|KcjJI`?YU8=AaHSWg4DX) zh2!CYn(<@ae})~0(xIO9X3eUUtIBzIb^jo&7sZBC5)tR3(hRK;Az{x)lC_V)WtlK{b>md}N%shRzfnpObt+xLU2)lMmE<`6f zINymBSzk~o=_QDJFxXMEmZIQld{*spnYmQR%@9X{4F&km7Vnsn&x*YoBZpcGC|xP` zA5)EMi90|{ut=4CR6LSc*RN0a(ypbuDuf3v`*y~rgb;)MhPJ@0pwJ`K;?^44u30N^ zRrzaWXvwqg4>AFCAfh!@=k;4g$g{yW-lEypCr2)t>sc*n`rkqZ_w1Yk*Q+TN@(Mp- z#rwc1f_mzv*M6?`2RsY~H}jz%^@<|P^p4W9ES=gMmRv#@>~M@u>Oe4E`zKcTJBpr!s`8%QT!YDDMg|u z-(^TvBLP^r@2Ki8>2K?|jU=aq}yC^4C?3>fq)1^jt?T`H=zGRoOd_0S5M;hqx5s-~u z#wi?w;_L|;yF*j3ii|~sscfGy zfAS9yPZkgEhz7ZE1ifGidzyvLVf5VG7#Eoa1wupvc-Io+#%TPo1BKzmMDGT#6p6c(@1=ZZEI8Uyy#f48Afa|%#s>ib63Vl)m?XwLWlRwxYE zRN^c&0|w}Z(T3JePZ$)73VO+2(%lek>M(GjvsgqbhW7H~ z387!5?ZEQ@Cvb$mwAJd+XMw7psFhL_^rqr{FkA+g`HwXnbVI0vtyxdN6I*8> z^C6c0|FtG=B!n_p&tT(@-3LNlJpxEn8_bG@8uNSy;6Jld{jDu`#vNMMa#15rKCWvIDeOz~lpL{{xGlG4PhrW3~ZuMWEDj zJbkW{!M4Hhv@mN~-YN0G;d;SnQD~VrH151HkW-uwMtR_&3K-|)A(#s#lt^NBZfqg> z=%F1f+x3`{yHlC(e4IAM(-HwqspSFMCZAxixwz%<%DBP1Y=5`WyKtK%ux#bVvKupH zNIWUHlD%YrsfZbDvK%RS_1m7xKmKFS;cK{KOu@f_WItfdji z25~ZspH1b(!+Q;R8ElJ`0bz`ey91a@g2@(OH-$05#c!x4D>e#@q@}PTqk>AIv3ULwrmw@)6&@PW9x6aY(f2IMng;$XA0z|VWJy!oi0W0;ap>2EQB z?`6Id)~9s}fSIfE0ADnNn_VUxfJOlWyng*3FP(8*)JiC3&6E=QZYQ)*CQvJo1Ge*1 z1O&MMC9{{nr6FPdod^C`5C3BF|G{4mSzPbT6Wnld-8J@ID~XLCRsZ{!VJ`e;q=vtL z6ViTAt${x$Wq~pqv F{tJsf=|}(o diff --git a/images/logo/fory-horizontal-black1.png b/images/logo/fory-horizontal-black1.png deleted file mode 100644 index d2495dc8d61cf687d4622876dbbaa81a16e0f71c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15918 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 W7~bD+skH++0P4EIwY)3XzyAl%FI=tw diff --git a/images/logo/fory-horizontal-white.png b/images/logo/fory-horizontal-white.png deleted file mode 100644 index 759074c2229b64cefdd46bd601c65c4ebf9c574c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13011 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 B4x0b~ diff --git a/images/logo/fory-horizontal-white1.png b/images/logo/fory-horizontal-white1.png deleted file mode 100644 index a310816cf9c15ca8ac6d86b6c13db47785608991..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15111 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 diff --git a/images/logo/fory-icon.png b/images/logo/fory-icon.png deleted file mode 100644 index fb3c9749f39e5f47f9ccf206fbbefe927cfea44d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19630 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 APyhe` diff --git a/images/logo/fory-vertical-black.png b/images/logo/fory-vertical-black.png deleted file mode 100644 index 4a6cba6723ca20678dcaa9c1c7258855a72fcecb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12770 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 KC^+Ww^Zx=!$RrK` diff --git a/images/logo/fory-vertical-black1.png b/images/logo/fory-vertical-black1.png deleted file mode 100644 index 2bef2eda275e91aea6642806f9ba7300ef9832c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13897 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 XZK=lQTbWZ@P&@1;+{N7U7We-LZY&%# diff --git a/images/logo/fory-vertical-white1.png b/images/logo/fory-vertical-white1.png deleted file mode 100644 index c56e4a66bfdc2123a18f11f9e143d69d4895be3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13045 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 diff --git a/images/logo/fory-vertical.png b/images/logo/fory-vertical.png deleted file mode 100644 index 8962eba810c319aa22eff5af5c7ab369ae76d276..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18333 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 diff --git a/images/logo/fory-vertical1.png b/images/logo/fory-vertical1.png deleted file mode 100644 index 1fa11e55c4a5d0f8bf909bfaca6fbf194bed4fe3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19531 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 From cf5aa325d65944e7782e2cddc0b73f005044e296 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 11:59:39 +0800 Subject: [PATCH 14/15] docs: keep images out of repository root --- CONTRIBUTING.md | 2 +- README.md | 2 +- idea-jdk11.png => docs/development/idea-jdk11.png | Bin fory-logo.png => docs/introduction/fory-logo.png | Bin 4 files changed, 2 insertions(+), 2 deletions(-) rename idea-jdk11.png => docs/development/idea-jdk11.png (100%) rename fory-logo.png => docs/introduction/fory-logo.png (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5bd6fd5135..3f774aaf40 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -229,7 +229,7 @@ To use Jetbrains IDEA IDE for Java Development, you need to configure the projec And due to the usage of `sun.misc.Unsafe` API, which is not visible in Java 11+, you need to configure java compiler with `--releaese` option disabled.
-
+
## Website diff --git a/README.md b/README.md index a3051ae90e..d5799424b6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
- Apache Fory logo
+ Apache Fory logo
[![Build Status](https://img.shields.io/github/actions/workflow/status/apache/fory/ci.yml?branch=main&style=for-the-badge&label=GITHUB%20ACTIONS&logo=github)](https://github.com/apache/fory/actions/workflows/ci.yml) diff --git a/idea-jdk11.png b/docs/development/idea-jdk11.png similarity index 100% rename from idea-jdk11.png rename to docs/development/idea-jdk11.png diff --git a/fory-logo.png b/docs/introduction/fory-logo.png similarity index 100% rename from fory-logo.png rename to docs/introduction/fory-logo.png From 82e347e8a5ef2ec3f7e9afabbe9ab8d9f129b148 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Tue, 4 Aug 2026 12:02:19 +0800 Subject: [PATCH 15/15] docs: sync shared images with site --- .github/sync.yml | 3 +++ CONTRIBUTING.md | 2 +- README.md | 2 +- ci/test_validate_fory_site_sync.py | 1 - ci/validate_fory_site_sync.py | 1 - docs/{introduction => images}/fory-logo.png | Bin docs/{development => images}/idea-jdk11.png | Bin 7 files changed, 5 insertions(+), 4 deletions(-) rename docs/{introduction => images}/fory-logo.png (100%) rename docs/{development => images}/idea-jdk11.png (100%) diff --git a/.github/sync.yml b/.github/sync.yml index 8ef8a16576..ce497fe804 100644 --- a/.github/sync.yml +++ b/.github/sync.yml @@ -18,6 +18,9 @@ apache/fory-site@main: - source: docs/index.md dest: docs/index.md + - source: docs/images/ + dest: docs/images/ + deleteOrphaned: true - source: docs/introduction/ dest: docs/introduction/ deleteOrphaned: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f774aaf40..3b2d53c4dd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -229,7 +229,7 @@ To use Jetbrains IDEA IDE for Java Development, you need to configure the projec And due to the usage of `sun.misc.Unsafe` API, which is not visible in Java 11+, you need to configure java compiler with `--releaese` option disabled.
-
+
## Website diff --git a/README.md b/README.md index d5799424b6..4a5e7bcb86 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
- Apache Fory logo
+ Apache Fory logo
[![Build Status](https://img.shields.io/github/actions/workflow/status/apache/fory/ci.yml?branch=main&style=for-the-badge&label=GITHUB%20ACTIONS&logo=github)](https://github.com/apache/fory/actions/workflows/ci.yml) diff --git a/ci/test_validate_fory_site_sync.py b/ci/test_validate_fory_site_sync.py index d641597ef7..d7856f4c3c 100644 --- a/ci/test_validate_fory_site_sync.py +++ b/ci/test_validate_fory_site_sync.py @@ -29,7 +29,6 @@ class ForySiteSyncTest(unittest.TestCase): def test_rejects_forbidden_doc_roots(self): for source, dest in ( ("docs/security/", "docs/security/"), - ("docs/images/", "docs/images/"), ("docs/development/", "docs/security/"), ): content = f"""apache/fory-site@main: diff --git a/ci/validate_fory_site_sync.py b/ci/validate_fory_site_sync.py index e57e4e63ba..83794e2f8b 100644 --- a/ci/validate_fory_site_sync.py +++ b/ci/validate_fory_site_sync.py @@ -28,7 +28,6 @@ TARGET_REPO = "apache/fory-site@main" FORBIDDEN_SYNC_ROOTS = ( pathlib.PurePosixPath("docs/security"), - pathlib.PurePosixPath("docs/images"), ) diff --git a/docs/introduction/fory-logo.png b/docs/images/fory-logo.png similarity index 100% rename from docs/introduction/fory-logo.png rename to docs/images/fory-logo.png diff --git a/docs/development/idea-jdk11.png b/docs/images/idea-jdk11.png similarity index 100% rename from docs/development/idea-jdk11.png rename to docs/images/idea-jdk11.png