Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions lang/js/lib/schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
}

/**
Expand Down
161 changes: 161 additions & 0 deletions lang/js/test/test_schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading