From dbb73c48dcd1748cf0c8051c0ef150c4cc4d1cd8 Mon Sep 17 00:00:00 2001 From: radhika agarwal Date: Mon, 12 Oct 2026 00:25:14 +0530 Subject: [PATCH] fix(avro): drop the version from Avro namespaces Avro names cannot contain '@' or segments that start with a digit, so avro-tools rejected every file generated from a versioned namespace. Use the namespace name without its version, as the Java and OData visitors do, and run the Avro verification cases that this unblocks. Signed-off-by: radhika agarwal --- lib/codegen/fromcto/avro/avrovisitor.js | 2 +- test/codegen/__snapshots__/codegen.js.snap | 8 ++++---- test/codegen/fromcto/avro/avrovisitor.js | 11 +++++++++++ test/verification/cases.js | 9 +-------- verification/corpus/manifest.json | 1 - 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/codegen/fromcto/avro/avrovisitor.js b/lib/codegen/fromcto/avro/avrovisitor.js index a17f13eb..107a3835 100644 --- a/lib/codegen/fromcto/avro/avrovisitor.js +++ b/lib/codegen/fromcto/avro/avrovisitor.js @@ -94,7 +94,7 @@ class AvroVisitor { parameters.fileWriter.writeLine( 0, - `@namespace("${modelFile.getNamespace()}")` + `@namespace("${ModelUtil.parseNamespace(modelFile.getNamespace()).name}")` ); const protocolName = parameters?.avroProtocolName ?? 'MyProtocol'; parameters.fileWriter.writeLine(0, `protocol ${protocolName} {\n`); diff --git a/test/codegen/__snapshots__/codegen.js.snap b/test/codegen/__snapshots__/codegen.js.snap index c5b08e3e..ab85f8de 100644 --- a/test/codegen/__snapshots__/codegen.js.snap +++ b/test/codegen/__snapshots__/codegen.js.snap @@ -367,7 +367,7 @@ org.acme.hr_1_0_0.ChangeOfAddress "1" *-- "1" org.acme.hr.base_1_0_0.Address : n exports[`codegen #formats check we can convert all formats from namespace versioned CTO, format 'avro' 1`] = ` { "key": "concerto.decorator@1.0.0.avdl", - "value": "@namespace("concerto.decorator@1.0.0") + "value": "@namespace("concerto.decorator") protocol MyProtocol { import idl "concerto@1.0.0.avdl"; @@ -387,7 +387,7 @@ protocol MyProtocol { exports[`codegen #formats check we can convert all formats from namespace versioned CTO, format 'avro' 2`] = ` { "key": "concerto@1.0.0.avdl", - "value": "@namespace("concerto@1.0.0") + "value": "@namespace("concerto") protocol MyProtocol { import idl "concerto.decorator@1.0.0.avdl"; @@ -421,7 +421,7 @@ protocol MyProtocol { exports[`codegen #formats check we can convert all formats from namespace versioned CTO, format 'avro' 3`] = ` { "key": "org.acme.hr.base@1.0.0.avdl", - "value": "@namespace("org.acme.hr.base@1.0.0") + "value": "@namespace("org.acme.hr.base") protocol MyProtocol { import idl "concerto@1.0.0.avdl"; @@ -466,7 +466,7 @@ protocol MyProtocol { exports[`codegen #formats check we can convert all formats from namespace versioned CTO, format 'avro' 4`] = ` { "key": "org.acme.hr@1.0.0.avdl", - "value": "@namespace("org.acme.hr@1.0.0") + "value": "@namespace("org.acme.hr") protocol MyProtocol { import idl "org.acme.hr.base@1.0.0.avdl"; diff --git a/test/codegen/fromcto/avro/avrovisitor.js b/test/codegen/fromcto/avro/avrovisitor.js index 44885d9b..a841e726 100644 --- a/test/codegen/fromcto/avro/avrovisitor.js +++ b/test/codegen/fromcto/avro/avrovisitor.js @@ -56,6 +56,17 @@ describe('Avro', function () { file.should.match(/protocol FancyProtocol/); }); + it('should drop the version from the namespace', () => { + const modelManager = new ModelManager(); + modelManager.addCTOModel( MODEL_SIMPLE ); + const visitor = new AvroVisitor(); + const fileWriter = new InMemoryWriter(); + modelManager.accept(visitor, { fileWriter }); + const files = fileWriter.getFilesInMemory(); + const file = files.get('test@1.0.0.avdl'); + file.should.match(/^@namespace\("test"\)$/m); + }); + it('should throw an error when an unrecognised type is supplied', () => { let thing = 'Something of unrecognised type'; const visitor = new AvroVisitor(); diff --git a/test/verification/cases.js b/test/verification/cases.js index b253ac4a..7e31ee96 100644 --- a/test/verification/cases.js +++ b/test/verification/cases.js @@ -36,7 +36,6 @@ const CASES = [ modelManager.addCTOModel(MetaModelUtil.metaModelCto); }, skip: { - avro: 'versioned namespaces (e.g. concerto.decorator@1.0.0) are illegal Avro identifiers (segment "0")', golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build', }, }, @@ -48,7 +47,6 @@ const CASES = [ protobuf:'Empty enum produces invalid Protobuf (enum must have >= 1 item)', graphql: 'map types emit invalid GraphQL SDL (key/value field syntax)', rust: 'map declarations reference scalar key/value types (e.g. SSN) that the Rust visitor does not emit, so cargo check fails', - avro: 'versioned namespaces (e.g. concerto.decorator@1.0.0) are illegal Avro identifiers (segment "0")', golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build', }, }, @@ -62,7 +60,7 @@ const CASES = [ protobuf: 'Empty enum produces invalid Protobuf (enum must have >= 1 item) and scalar map keys (e.g. SSN) are not valid Proto3 map key types', graphql: 'map types emit invalid GraphQL SDL (key/value field syntax)', rust: 'map declarations reference scalar key/value types (e.g. SSN, Time) that the Rust visitor does not emit, so cargo check fails', - avro: 'versioned namespaces (e.g. concerto.decorator@1.0.0) are illegal Avro identifiers (segment "0")', + avro: 'imported types are referenced by short name (e.g. Category from org.acme.hr.base), so avro-tools cannot resolve them', openapi: 'JSON Schema $decorators and invalid schema nesting fail OpenAPI struct validation', golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build', }, @@ -71,7 +69,6 @@ const CASES = [ name: 'stringlength', files: ['stringlength.cto'], skip: { - avro: 'versioned namespaces (e.g. concerto.decorator@1.0.0) are illegal Avro identifiers (segment "0")', golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build', }, }, @@ -79,7 +76,6 @@ const CASES = [ name: 'collectionsize', files: ['collectionsize.cto'], skip: { - avro: 'versioned namespaces (e.g. concerto.decorator@1.0.0) are illegal Avro identifiers (segment "0")', golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build', protobuf: 'Relationships are not supported in protobuf', graphql: 'Relationships are not supported in graphql visitor', @@ -90,7 +86,6 @@ const CASES = [ name: 'model-base', files: ['model-base.cto'], skip: { - avro: 'versioned namespaces (e.g. concerto.decorator@1.0.0) are illegal Avro identifiers (segment "0")', openapi: 'JSON Schema $decorators fail OpenAPI struct validation', golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build', }, @@ -99,7 +94,6 @@ const CASES = [ name: 'agreement', files: ['agreement.cto'], skip: { - avro: 'versioned namespaces (e.g. concerto.decorator@1.0.0) are illegal Avro identifiers (segment "0")', openapi: 'JSON Schema $decorators fail OpenAPI struct validation', golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build', }, @@ -108,7 +102,6 @@ const CASES = [ name: 'circular', files: ['circular.cto'], skip: { - avro: 'versioned namespaces (e.g. concerto.decorator@1.0.0) are illegal Avro identifiers (segment "0")', golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build', }, }, diff --git a/verification/corpus/manifest.json b/verification/corpus/manifest.json index 4b1eb2a4..523fa784 100644 --- a/verification/corpus/manifest.json +++ b/verification/corpus/manifest.json @@ -12,7 +12,6 @@ }, "skip": { "csharp": "generated C# references AccordProject.Concerto runtime types (e.g. ConcertoConverterFactorySystem) that are not available for a standalone dotnet build", - "avro": "versioned namespaces (e.g. concerto.decorator@1.0.0) are illegal Avro identifiers (segment \"0\")", "golang": "GoLangVisitor emits bare package-name imports (e.g. import \"concerto_decorator_1_0_0\") instead of Go module-relative import paths, so any cross-namespace model fails go build" } }