This is an automated email from the ASF dual-hosted git repository.
mgrigorov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/avro.git
The following commit(s) were added to refs/heads/main by this push:
new 3160e011e5 AVRO-4173: [js] Fix namespace inheritance for nested types
in schema parsing (#3466)
3160e011e5 is described below
commit 3160e011e5c852a5ee5e8e42f27c7a3fd24d0454
Author: tyler-blip <[email protected]>
AuthorDate: Mon Oct 13 02:13:35 2025 -0400
AVRO-4173: [js] Fix namespace inheritance for nested types in schema
parsing (#3466)
* AVRO-4173: [JS] Fix namespace inheritance for nested types
* AVRO-4173: [JS] Refactor getOpts to prevent shared state
* Simplify registry and logicalTypes initialization
* Revert "Simplify registry and logicalTypes initialization"
This reverts commit 28fb6c0fec5077b7f0a5fc36c983fb7e1c50b18b.
---------
Co-authored-by: Martin Grigorov <[email protected]>
Co-authored-by: Martin Tzvetanov Grigorov <[email protected]>
---
lang/js/lib/schemas.js | 21 ++++--
lang/js/test/test_schemas.js | 161 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 178 insertions(+), 4 deletions(-)
diff --git a/lang/js/lib/schemas.js b/lang/js/lib/schemas.js
index cbf9e9600a..d99eedb8a1 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 0cc0996b89..d8bbcd116b 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',