diff --git a/lang/js/lib/schemas.js b/lang/js/lib/schemas.js index cbf9e9600af..d99eedb8a10 100644 --- a/lang/js/lib/schemas.js +++ b/lang/js/lib/schemas.js @@ -2029,10 +2029,23 @@ function getOpts(attrs, opts) { throw new Error('invalid type: null (did you mean "null"?)'); } opts = opts || {}; - opts.registry = opts.registry || {}; - opts.namespace = attrs.namespace || opts.namespace; - opts.logicalTypes = opts.logicalTypes || {}; - return opts; + + // Ensure registry and logicalTypes exist and are preserved by reference so + // type definitions can accumulate across recursive calls. + if (!opts.registry) { + opts.registry = {}; + } + if (!opts.logicalTypes) { + opts.logicalTypes = {}; + } + + return { + registry: opts.registry, // Preserve same object reference + namespace: attrs.namespace || opts.namespace, + logicalTypes: opts.logicalTypes, // Preserve same object reference + typeHook: opts.typeHook, + assertLogicalTypes: opts.assertLogicalTypes + }; } /** diff --git a/lang/js/test/test_schemas.js b/lang/js/test/test_schemas.js index 0cc0996b894..d8bbcd116b7 100644 --- a/lang/js/test/test_schemas.js +++ b/lang/js/test/test_schemas.js @@ -2188,6 +2188,167 @@ describe('types', function () { assert.equal(type._fields[1]._type._name, 'all.Alien'); }); + it('namespace inheritance', function () { + // When nested types don't specify a namespace, they should inherit the parent's + // namespace, but other nested types with explicit namespaces shouldn't corrupt + // the inherited context. + var schema = { + type: 'record', + name: 'Parent', + namespace: 'parent.ns', + fields: [ + { + name: 'child_field', + type: { + type: 'record', + name: 'Child', // No namespace - should inherit 'parent.ns' + fields: [{name: 'value', type: 'int'}] + } + }, + { + name: 'other_field', + type: { + type: 'record', + name: 'Other', + namespace: 'different.ns', // Different namespace + fields: [{name: 'data', type: 'int'}] + } + }, + { + name: 'reference_field', + type: 'Child' // Should resolve to 'parent.ns.Child' + } + ] + }; + + var type = createType(schema); + assert.equal(type.getName(), 'parent.ns.Parent'); + + var fields = type.getFields(); + assert.equal(fields.length, 3); + + // Test all field types to demonstrate the fix works for all namespace scenarios + assert.equal(fields[0].getType().getName(), 'parent.ns.Child'); // Inherited namespace + assert.equal(fields[1].getType().getName(), 'different.ns.Other'); // Explicit namespace + assert.equal(fields[2].getType().getName(), 'parent.ns.Child'); // Reference resolution + + // Verify the schema works for serialization + var testData = { + child_field: { value: 42 }, + other_field: { data: 123 }, + reference_field: { value: 99 } + }; + assert(type.isValid(testData)); + var buf = type.toBuffer(testData); + assert.deepEqual(type.fromBuffer(buf), testData); + }); + + it('deep namespace inheritance', function () { + // Test namespace inheritance across multiple levels of nesting with various + // namespace changes to ensure the fix works robustly in complex scenarios. + var schema = { + type: 'record', + name: 'Root', + namespace: 'level1', + fields: [ + { + name: 'level2_inherited', + type: { + type: 'record', + name: 'Level2Inherited', // Inherits 'level1' + fields: [ + { + name: 'level3_inherited', + type: { + type: 'record', + name: 'Level3Inherited', // Also inherits 'level1' + fields: [{name: 'deep_value', type: 'int'}] + } + }, + { + name: 'level3_override', + type: { + type: 'record', + name: 'Level3Override', + namespace: 'level3.ns', // Changes namespace context + fields: [{name: 'override_value', type: 'string'}] + } + } + ] + } + }, + { + name: 'level2_different', + type: { + type: 'record', + name: 'Level2Different', + namespace: 'level2.ns', // Different namespace + fields: [ + { + name: 'nested_inherited', + type: { + type: 'record', + name: 'NestedInherited', // Should inherit 'level2.ns' + fields: [{name: 'nested_data', type: 'double'}] + } + } + ] + } + }, + { + name: 'ref_level2_inherited', + type: 'Level2Inherited' // Should resolve to 'level1.Level2Inherited' + }, + { + name: 'ref_level3_inherited', + type: 'Level3Inherited' // Should resolve to 'level1.Level3Inherited' + } + ] + }; + + var type = createType(schema); + assert.equal(type.getName(), 'level1.Root'); + + var fields = type.getFields(); + assert.equal(fields.length, 4); + + // Verify deep inheritance worked correctly + assert.equal(fields[0].getType().getName(), 'level1.Level2Inherited'); + assert.equal(fields[1].getType().getName(), 'level2.ns.Level2Different'); + + // Critical tests: references should resolve to correct namespaces + assert.equal(fields[2].getType().getName(), 'level1.Level2Inherited'); + assert.equal(fields[3].getType().getName(), 'level1.Level3Inherited'); + + // Verify nested types have correct namespaces + var level2Fields = fields[0].getType().getFields(); + assert.equal(level2Fields[0].getType().getName(), 'level1.Level3Inherited'); + assert.equal(level2Fields[1].getType().getName(), 'level3.ns.Level3Override'); + + var level2DiffFields = fields[1].getType().getFields(); + assert.equal(level2DiffFields[0].getType().getName(), 'level2.ns.NestedInherited'); + + // Test serialization works correctly + var testData = { + level2_inherited: { + level3_inherited: { deep_value: 42 }, + level3_override: { override_value: 'test' } + }, + level2_different: { + nested_inherited: { nested_data: 3.14 } + }, + ref_level2_inherited: { + level3_inherited: { deep_value: 99 }, + level3_override: { override_value: 'ref' } + }, + ref_level3_inherited: { deep_value: 123 } + }; + + assert(type.isValid(testData)); + var buf = type.toBuffer(testData); + assert.deepEqual(type.fromBuffer(buf), testData); + }); + it('wrapped primitive', function () { var type = createType({ type: 'record',