Skip to content

Commit deef91a

Browse files
committed
fix: require explicit Go named registration
1 parent 4fd57d8 commit deef91a

5 files changed

Lines changed: 50 additions & 32 deletions

File tree

go/fory/fory.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,8 @@ func readHeaderSlow(ctx *ReadContext, bitmap byte) {
824824

825825
// Serialize - type T inferred, serializer auto-resolved.
826826
// The serializer handles its own ref/type info writing internally.
827-
// Falls back to reflection-based serialization for unregistered types.
827+
// Uses reflection-based serializers for supported non-struct types. Structs must
828+
// be registered explicitly before serialization.
828829
// Note: For structs, T must be a pointer to struct (*MyStruct), not struct value.
829830
//
830831
// IMPORTANT: The returned byte slice is a zero-copy view of the internal buffer.

go/fory/fory_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,46 @@ func TestSerializeStructSimple(t *testing.T) {
295295
}
296296
}
297297

298+
type explicitRegistrationUser struct {
299+
Name string
300+
}
301+
302+
func TestNamedStructRegistrationCoversPointer(t *testing.T) {
303+
fory := NewFory(WithXlang(true), WithCompatible(false), WithRefTracking(false))
304+
require.NoError(t, fory.RegisterStructByName(explicitRegistrationUser{}, "example.User"))
305+
306+
value := explicitRegistrationUser{Name: "alice"}
307+
buf := NewByteBuffer(nil)
308+
require.NoError(t, fory.SerializeWithCallback(buf, value, nil))
309+
var decodedValue explicitRegistrationUser
310+
require.NoError(t, fory.DeserializeWithCallbackBuffers(
311+
NewByteBuffer(buf.GetByteSlice(0, buf.WriterIndex())),
312+
&decodedValue,
313+
nil))
314+
require.Equal(t, value, decodedValue)
315+
316+
data, err := fory.Marshal(&value)
317+
require.NoError(t, err)
318+
var decodedPointer explicitRegistrationUser
319+
require.NoError(t, fory.Unmarshal(data, &decodedPointer))
320+
require.Equal(t, value, decodedPointer)
321+
}
322+
323+
func TestUnregisteredStructSerializationFails(t *testing.T) {
324+
value := explicitRegistrationUser{Name: "bob"}
325+
326+
fory := NewFory(WithXlang(true), WithCompatible(false), WithRefTracking(false))
327+
buf := NewByteBuffer(nil)
328+
err := fory.SerializeWithCallback(buf, value, nil)
329+
require.Error(t, err)
330+
require.Contains(t, err.Error(), "must be registered explicitly")
331+
332+
fory = NewFory(WithXlang(true), WithCompatible(false), WithRefTracking(false))
333+
_, err = fory.Marshal(&value)
334+
require.Error(t, err)
335+
require.Contains(t, err.Error(), "must be registered explicitly")
336+
}
337+
298338
func TestRegisterById(t *testing.T) {
299339
fory := NewFory(WithXlang(true), WithCompatible(false), WithRefTracking(false))
300340
type simple struct {

go/fory/fory_typed_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func TestDeserializeByteSliceAcceptsUint8ArrayRootType(t *testing.T) {
171171
}
172172

173173
// TestSerializeGenericComplex tests Serialize[T]/DeserializeWithCallbackBuffers[T] with complex types.
174-
// These fall back to reflection-based serialization.
174+
// Struct wrappers must be registered explicitly before reflection-based serialization.
175175
func TestSerializeGenericComplex(t *testing.T) {
176176
f := NewFory(WithXlang(false), WithRefTracking(true))
177177

@@ -199,6 +199,7 @@ func TestSerializeGenericComplex(t *testing.T) {
199199
type SliceWrapper struct {
200200
Items []int32
201201
}
202+
require.NoError(t, f.RegisterStructByName(SliceWrapper{}, "example.SliceWrapper"))
202203
original := SliceWrapper{Items: []int32{1, 2, 3, 4, 5}}
203204
data, err := Serialize(f, &original)
204205
require.NoError(t, err)
@@ -214,6 +215,7 @@ func TestSerializeGenericComplex(t *testing.T) {
214215
type MapWrapper struct {
215216
Items map[string]int32
216217
}
218+
require.NoError(t, f.RegisterStructByName(MapWrapper{}, "example.MapWrapper"))
217219
original := MapWrapper{Items: map[string]int32{"a": 1, "b": 2, "c": 3}}
218220
data, err := Serialize(f, &original)
219221
require.NoError(t, err)

go/fory/threadsafe/fory_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ func TestDeserialize(t *testing.T) {
144144
type SliceWrapper struct {
145145
Items []int32
146146
}
147+
require.NoError(t, f.RegisterStructByName(SliceWrapper{}, "threadsafe.SliceWrapper"))
147148
original := SliceWrapper{Items: []int32{1, 2, 3, 4, 5}}
148149
data, err := Serialize(f, &original)
149150
require.NoError(t, err)

go/fory/type_resolver.go

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,20 +1045,8 @@ func (r *TypeResolver) getTypeInfo(value reflect.Value, create bool) (*TypeInfo,
10451045
return ptrInfo, nil
10461046
}
10471047

1048-
// Element type not registered - try auto-registration for structs
10491048
if elemType.Kind() == reflect.Struct {
1050-
// First register the value type
1051-
elemPkgPath := elemType.PkgPath()
1052-
elemTypeName := elemType.Name()
1053-
if err := r.registerStructByName(elemType, elemPkgPath, elemTypeName); err != nil {
1054-
// Might already be registered, that's okay
1055-
_ = err
1056-
}
1057-
// Now the pointer type should be registered
1058-
if info, ok := r.typesInfo[type_]; ok {
1059-
return info, nil
1060-
}
1061-
return nil, fmt.Errorf("failed to find registered pointer type %v", type_)
1049+
return nil, fmt.Errorf("struct type %s must be registered explicitly before serializing %s", elemType, type_)
10621050
}
10631051

10641052
// For primitive types and other types, we can auto-create pointer serializer
@@ -1081,6 +1069,8 @@ func (r *TypeResolver) getTypeInfo(value reflect.Value, create bool) (*TypeInfo,
10811069
return nil, fmt.Errorf("pointer element type %v must be registered", elemType)
10821070
case type_.Kind() == reflect.Interface:
10831071
return nil, fmt.Errorf("interface types must be registered explicitly")
1072+
case type_.Kind() == reflect.Struct:
1073+
return nil, fmt.Errorf("struct type %s must be registered explicitly", type_)
10841074
case pkgPath == "" && typeName == "":
10851075
// Allow anonymous collection types (maps, slices, arrays) without registration
10861076
kind := type_.Kind()
@@ -1808,23 +1798,7 @@ func (r *TypeResolver) createSerializer(type_ reflect.Type, mapInStruct bool) (s
18081798
case reflect.Struct:
18091799
serializer := r.typeToSerializers[type_]
18101800
if serializer == nil {
1811-
// In xlang/compatible mode, auto-register struct types
1812-
if r.isXlang || r.fory.config.Compatible {
1813-
// Use the type's actual package path and name for auto-registration
1814-
pkgPath := type_.PkgPath()
1815-
typeName := type_.Name()
1816-
if typeName == "" {
1817-
return nil, fmt.Errorf("cannot auto-register anonymous struct type %s", type_.String())
1818-
}
1819-
// For auto-registered types, use package path as namespace and type name
1820-
if err := r.registerStructByName(type_, pkgPath, typeName); err != nil {
1821-
return nil, fmt.Errorf("failed to auto-register struct %s: %w", type_.String(), err)
1822-
}
1823-
serializer = r.typeToSerializers[type_]
1824-
}
1825-
if serializer == nil {
1826-
return nil, fmt.Errorf("struct type %s not registered", type_.String())
1827-
}
1801+
return nil, fmt.Errorf("struct type %s must be registered explicitly", type_.String())
18281802
}
18291803
return serializer, nil
18301804
}

0 commit comments

Comments
 (0)