This is an automated email from the ASF dual-hosted git repository. chaokunyang pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/fory-site.git
commit e1d8d364b2d68023d3b1e32550a15a517e9e37de Author: chaokunyang <[email protected]> AuthorDate: Thu May 28 05:22:50 2026 +0000 π synced local 'docs/guide/' with remote 'docs/guide/' --- docs/guide/csharp/schema-metadata.md | 13 ++++++---- docs/guide/kotlin/static-generated-serializers.md | 27 +++++++++++++++----- docs/guide/rust/schema-evolution.md | 24 ++++++++++++------ docs/guide/scala/schema-idl.md | 31 +++++++++++++++-------- docs/guide/scala/schema-metadata.md | 24 ++++++++++++------ docs/guide/swift/schema-metadata.md | 15 ++++++++--- 6 files changed, 93 insertions(+), 41 deletions(-) diff --git a/docs/guide/csharp/schema-metadata.md b/docs/guide/csharp/schema-metadata.md index 52ae4cea85..65baa8d460 100644 --- a/docs/guide/csharp/schema-metadata.md +++ b/docs/guide/csharp/schema-metadata.md @@ -86,7 +86,10 @@ Nullability comes from the C# carrier type. Use `List<ulong?>` for nullable list Generated union cases use `[ForyCase]` for both the stable case ID and optional case payload schema type. Do not put `[ForyField]` on union case payload members. Known case record names use PascalCase FDL case names; payload types -use qualified references when needed to avoid name conflicts. +use qualified references when needed to avoid name conflicts. A typed union must +declare at least one non-`Unknown` case; `Unknown(UnknownCase)` is only the +runtime forward-compatibility carrier. The marker only selects the carrier and +does not add an entry to the schema case table. ```csharp using Apache.Fory; @@ -97,13 +100,13 @@ public abstract partial record Shape { private Shape() {} - [ForyCase(0)] - public sealed partial record UnknownCase(int CaseId, object? Value) : Shape; + [ForyUnknownCase] + public sealed partial record Unknown(UnknownCase Value) : Shape; - [ForyCase(1)] + [ForyCase(0)] public sealed partial record Circle(global::example.Circle Value) : Shape; - [ForyCase(2, Type = typeof(S.Fixed<S.Int32>))] + [ForyCase(1, Type = typeof(S.Fixed<S.Int32>))] public sealed partial record Code(int Value) : Shape; } ``` diff --git a/docs/guide/kotlin/static-generated-serializers.md b/docs/guide/kotlin/static-generated-serializers.md index f24e598e17..97f8150e1e 100644 --- a/docs/guide/kotlin/static-generated-serializers.md +++ b/docs/guide/kotlin/static-generated-serializers.md @@ -220,20 +220,35 @@ serialization. KSP generates serializers for top-level sealed classes annotated with `@ForyUnion`. Each schema case is a nested class annotated with `@ForyCase` and -one constructor property named `value`. Case ID `0` is reserved for the unknown -case carrier: +one constructor property named `value`. `Unknown(UnknownCase)` is marked with +`@ForyUnknownCase` as the runtime-owned forward-compatibility carrier. It is +omitted from the schema case table because the marker only selects the carrier +and does not add a schema entry. A typed union must declare at least one +non-`Unknown` case: ```kotlin +package example + +import org.apache.fory.annotation.ForyCase +import org.apache.fory.annotation.ForyUnion +import org.apache.fory.annotation.ForyUnknownCase +import org.apache.fory.type.union.UnknownCase + @ForyUnion sealed class Animal { - @ForyCase(id = 0) - data class UnknownCase(val caseId: Int, val value: Any?) : Animal() + @ForyUnknownCase + data class Unknown(val value: UnknownCase) : Animal() - @ForyCase(id = 1) - data class DogCase(val value: Dog) : Animal() + @ForyCase(id = 0) + data class Dog(val value: example.Dog) : Animal() } ``` +When a generated Kotlin union case name matches the payload type simple name, +packaged output keeps the case name and qualifies the payload type. If a target +output mode cannot express a legal qualifier for a conflict, the IDL compiler +appends `Case` to the generated case class name. + Generated schema modules register sealed unions through `KotlinSerializers.registerUnion`. The runtime discovers the generated `<Target>_ForySerializer` automatically, so callers do not pass a serializer instance. diff --git a/docs/guide/rust/schema-evolution.md b/docs/guide/rust/schema-evolution.md index 154a6e2b32..5886870759 100644 --- a/docs/guide/rust/schema-evolution.md +++ b/docs/guide/rust/schema-evolution.md @@ -108,11 +108,11 @@ Apache Foryβ’ supports three types of enum variants with full schema evolution - **Named**: Struct-like variants (`Event::Click { x: i32, y: i32 }`) ```rust -use fory::{Fory, ForyStruct}; +use fory::{Fory, ForyUnion}; -#[derive(Default, ForyStruct, Debug, PartialEq)] +#[derive(ForyUnion, Debug, PartialEq)] enum Value { - #[default] + #[fory(default)] Null, Bool(bool), Number(f64), @@ -129,6 +129,13 @@ let decoded: Value = fory.deserialize(&bytes)?; assert_eq!(value, decoded); ``` +For typed ADT unions whose schema cases are unit or single-payload variants, +`#[fory(unknown)] Unknown(::fory::UnknownCase)` is only the runtime +forward-compatibility carrier. It cannot be the default variant, and the union +must include at least one real schema case. The marker only selects the carrier +and does not add an entry to the schema case table; schema cases use +non-negative IDs. + ### Enum Schema Evolution Compatible mode enables robust schema evolution with variant type encoding (2 bits): @@ -136,19 +143,20 @@ Compatible mode enables robust schema evolution with variant type encoding (2 bi - `0b0` = Unit, `0b1` = Unnamed, `0b10` = Named ```rust -use fory::{Fory, ForyStruct}; +use fory::{Fory, ForyUnion}; // Old version -#[derive(ForyStruct)] +#[derive(ForyUnion)] enum OldEvent { + #[fory(default)] Click { x: i32, y: i32 }, Scroll { delta: f64 }, } // New version - added field and variant -#[derive(Default, ForyStruct)] +#[derive(ForyUnion)] enum NewEvent { - #[default] + #[fory(default)] Unknown, Click { x: i32, y: i32, timestamp: u64 }, // Added field Scroll { delta: f64 }, @@ -174,7 +182,7 @@ assert!(matches!(new_event, NewEvent::Click { x: 100, y: 200, timestamp: 0 })); **Best practices:** -- Always mark a default variant with `#[default]` +- Always mark exactly one union variant with `#[fory(default)]` - Named variants provide better evolution than unnamed - Use compatible mode for cross-version communication diff --git a/docs/guide/scala/schema-idl.md b/docs/guide/scala/schema-idl.md index f0087eae99..27ab62235d 100644 --- a/docs/guide/scala/schema-idl.md +++ b/docs/guide/scala/schema-idl.md @@ -128,28 +128,37 @@ Fory enum IDs from case-level `@ForyEnumId` metadata are used in xlang mode. IDL unions generate Scala 3 ADT enums with macro-derived serializers: ```scala -import org.apache.fory.annotation.{ForyCase, ForyUnion, UInt32Type} +package example + +import org.apache.fory.annotation.{ForyCase, ForyUnion, ForyUnknownCase, UInt32Type} import org.apache.fory.config.Int32Encoding import org.apache.fory.scala.ForySerializer +import org.apache.fory.`type`.union.UnknownCase @ForyUnion enum SearchTarget derives ForySerializer { + @ForyUnknownCase + case Unknown(value: UnknownCase) + @ForyCase(id = 0) - case UnknownCase(caseId: Int, value: Any) + case User(value: _root_.example.User) @ForyCase(id = 1) - case UserCase(value: User) - - @ForyCase(id = 2) - case FixedIdCase(value: Long @UInt32Type(encoding = Int32Encoding.FIXED)) + case FixedId(value: Long @UInt32Type(encoding = Int32Encoding.FIXED)) } ``` -Schema-defined union cases must use positive IDs. Case ID `0` is reserved for -the Scala unknown-case carrier, whose payload stores the original positive case -ID and the deserialized value. When a reader sees a newer positive case ID, it -returns `UnknownCase(originalId, value)` instead of failing solely because the -case ID is not known locally. +When a generated Scala union case name matches the payload type simple name, +packaged output keeps the case name and qualifies the payload type. If a target +output mode cannot express a legal qualifier for a conflict, the IDL compiler +appends `Case` to the generated case name. + +Schema-defined union cases use non-negative IDs, and a typed union must declare +at least one non-`Unknown` case. The Scala unknown-case carrier is selected by +`@ForyUnknownCase`, not by a schema case ID. Its payload stores the original case +ID and the deserialized value. When a reader sees a newer case ID, it returns +`Unknown(UnknownCase)` instead of failing solely because the case ID is not known +locally. The macro writes the existing xlang union envelope directly. It does not allocate temporary Java `Union` carriers. diff --git a/docs/guide/scala/schema-metadata.md b/docs/guide/scala/schema-metadata.md index 1f96964c01..18cc897afc 100644 --- a/docs/guide/scala/schema-metadata.md +++ b/docs/guide/scala/schema-metadata.md @@ -83,25 +83,33 @@ xlang mode. IDL unions generate Scala 3 ADT enums with `@ForyUnion` and `@ForyCase` metadata: ```scala -import org.apache.fory.annotation.{ForyCase, ForyUnion, UInt32Type} +package example + +import org.apache.fory.annotation.{ForyCase, ForyUnion, ForyUnknownCase, UInt32Type} import org.apache.fory.config.Int32Encoding import org.apache.fory.scala.ForySerializer +import org.apache.fory.`type`.union.UnknownCase @ForyUnion enum SearchTarget derives ForySerializer { + @ForyUnknownCase + case Unknown(value: UnknownCase) + @ForyCase(id = 0) - case UnknownCase(caseId: Int, value: Any) + case User(value: _root_.example.User) @ForyCase(id = 1) - case UserCase(value: User) - - @ForyCase(id = 2) - case FixedIdCase(value: Long @UInt32Type(encoding = Int32Encoding.FIXED)) + case FixedId(value: Long @UInt32Type(encoding = Int32Encoding.FIXED)) } ``` -Schema-defined union cases must use positive IDs. Case ID `0` is reserved for the unknown-case -carrier used when a reader sees a newer positive case ID. +Schema-defined union cases use non-negative IDs, and a typed union must declare +at least one non-`Unknown` case. The unknown-case carrier is selected by +`@ForyUnknownCase`, not by a schema case ID. +When a generated Scala union case name matches the payload type simple name, +packaged output keeps the case name and qualifies the payload type. If a target +output mode cannot express a legal qualifier for a conflict, the IDL compiler +appends `Case` to the generated case name. ## Generated Metadata Source diff --git a/docs/guide/swift/schema-metadata.md b/docs/guide/swift/schema-metadata.md index 79c4fa35d1..bd57c14f54 100644 --- a/docs/guide/swift/schema-metadata.md +++ b/docs/guide/swift/schema-metadata.md @@ -107,15 +107,24 @@ Union payloads use the same DSL through `@ForyCase(payload:)`: ```swift @ForyUnion -enum Event: Equatable { - @ForyCase(id: 1) +enum Event { + @ForyUnknownCase + case unknown(UnknownCase) + + @ForyCase(id: 0) case created(String) - @ForyCase(id: 2, payload: .uint64(encoding: .fixed)) + @ForyCase(id: 1, payload: .uint64(encoding: .fixed)) case deleted(UInt64) } ``` +Every `@ForyUnion` must declare `@ForyUnknownCase case unknown(UnknownCase)` and +at least one non-`unknown` case. The unknown case is only the runtime +forward-compatibility carrier and cannot be the default value source. It is +omitted from the schema case table because the marker only selects the carrier +and does not add a schema entry. Schema cases use non-negative IDs. + ## Model Macro Requirements ### Struct and class fields --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
