Skip to content
Open
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
15 changes: 14 additions & 1 deletion packages/avro-ts/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,20 @@ import { Context } from './types';
export const fullName = (
context: Context,
schema: avroSchema.RecordType | avroSchema.EnumType,
): string => `${schema.namespace ?? context.namespace}.${schema.name}`;
): string => {
const isFullName = schema.name.includes('.');
const namespace = schema.namespace ?? context.namespace;

// According to the avro specs, if the namespace is included in the full name
// the schema namespace should be ignored.
// We can also specify a "null" namespace and use simple names everywhere.
// https://avro.apache.org/docs/1.11.1/specification/#names
if (isFullName || !namespace) {
return schema.name;
}

return `${namespace}.${schema.name}`;
};

export const firstUpperCase = (name: string): string =>
name ? name[0].toUpperCase() + name.slice(1) : name;
Expand Down
108 changes: 108 additions & 0 deletions packages/avro-ts/test/__snapshots__/namespacing.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Avro simple and full name related tests Should properly generate union type names when inheriting a namespace 1`] = `
RootRecord {
"unionField": Branch$ {
"RootNamespace.SomeRecordA": SomeRecordA {
"someRecordAField": "test",
},
},
}
`;

exports[`Avro simple and full name related tests Should properly generate union type names when inheriting a namespace 2`] = `
"/* eslint-disable @typescript-eslint/no-namespace */

export type RootRecord = RootNamespace.RootRecord;

export namespace RootNamespace {
export const SomeRecordASchema = \\"{\\\\\\"type\\\\\\":\\\\\\"record\\\\\\",\\\\\\"name\\\\\\":\\\\\\"SomeRecordA\\\\\\",\\\\\\"fields\\\\\\":[{\\\\\\"name\\\\\\":\\\\\\"someRecordAField\\\\\\",\\\\\\"type\\\\\\":\\\\\\"string\\\\\\"}]}\\";
export const SomeRecordAName = \\"RootNamespace.SomeRecordA\\";
export interface SomeRecordA {
someRecordAField: string;
}
export const SomeRecordBSchema = \\"{\\\\\\"type\\\\\\":\\\\\\"record\\\\\\",\\\\\\"name\\\\\\":\\\\\\"SomeRecordB\\\\\\",\\\\\\"fields\\\\\\":[{\\\\\\"name\\\\\\":\\\\\\"someRecordBField\\\\\\",\\\\\\"type\\\\\\":\\\\\\"string\\\\\\"}]}\\";
export const SomeRecordBName = \\"RootNamespace.SomeRecordB\\";
export interface SomeRecordB {
someRecordBField: string;
}
export const RootRecordSchema = \\"{\\\\\\"type\\\\\\":\\\\\\"record\\\\\\",\\\\\\"name\\\\\\":\\\\\\"RootRecord\\\\\\",\\\\\\"namespace\\\\\\":\\\\\\"RootNamespace\\\\\\",\\\\\\"fields\\\\\\":[{\\\\\\"name\\\\\\":\\\\\\"unionField\\\\\\",\\\\\\"type\\\\\\":[{\\\\\\"type\\\\\\":\\\\\\"record\\\\\\",\\\\\\"name\\\\\\":\\\\\\"SomeRecordA\\\\\\",\\\\\\"fields\\\\\\":[{\\\\\\"name\\\\\\":\\\\\\"someRecordAField\\\\\\",\\\\\\"type\\\\\\":\\\\\\"string\\\\\\"}]},{\\\\\\"type\\\\\\":\\\\\\"record\\\\\\",\\\\\\"name\\\\\\":\\\\\\"SomeRecordB\\\\\\",\\\\\\"fields\\\\\\":[{\\\\\\"name\\\\\\":\\\\\\"someRecordBField\\\\\\",\\\\\\"type\\\\\\":\\\\\\"string\\\\\\"}]}]}]}\\";
export const RootRecordName = \\"RootNamespace.RootRecord\\";
export interface RootRecord {
unionField: {
\\"RootNamespace.SomeRecordA\\": RootNamespace.SomeRecordA;
\\"RootNamespace.SomeRecordB\\"?: never;
} | {
\\"RootNamespace.SomeRecordA\\"?: never;
\\"RootNamespace.SomeRecordB\\": RootNamespace.SomeRecordB;
};
}
}
"
`;

exports[`Avro simple and full name related tests Should properly generate union type names when specifying a full name 1`] = `
RootRecord {
"unionField": Branch$ {
"RecordANamespace.SomeRecordA": SomeRecordA {
"someRecordAField": "test",
},
},
}
`;

exports[`Avro simple and full name related tests Should properly generate union type names when specifying a full name 2`] = `
"export type AvroType = RootRecord;

export interface RecordANamespaceSomeRecordA {
someRecordAField: string;
}

export interface RecordBNamespaceSomeRecordB {
someRecordBField: string;
}

export interface RootRecord {
unionField: {
\\"RecordANamespace.SomeRecordA\\": RecordANamespaceSomeRecordA;
\\"RecordBNamespace.SomeRecordB\\"?: never;
} | {
\\"RecordANamespace.SomeRecordA\\"?: never;
\\"RecordBNamespace.SomeRecordB\\": RecordBNamespaceSomeRecordB;
};
}
"
`;

exports[`Avro simple and full name related tests Should properly generate union type names when there are no namespaces 1`] = `
RootRecord {
"unionField": Branch$ {
"SomeRecordA": SomeRecordA {
"someRecordAField": "test",
},
},
}
`;

exports[`Avro simple and full name related tests Should properly generate union type names when there are no namespaces 2`] = `
"export type AvroType = RootRecord;

export interface SomeRecordA {
someRecordAField: string;
}

export interface SomeRecordB {
someRecordBField: string;
}

export interface RootRecord {
unionField: {
SomeRecordA: SomeRecordA;
SomeRecordB?: never;
} | {
SomeRecordA?: never;
SomeRecordB: SomeRecordB;
};
}
"
`;
147 changes: 147 additions & 0 deletions packages/avro-ts/test/namespacing.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { schema, Type } from 'avsc';
import { toTypeScript } from '../src';

describe('Avro simple and full name related tests', () => {
it.each<[string, { schema: schema.RecordType; testObject: unknown }]>([
[
'there are no namespaces',
{
schema: {
type: 'record',
name: 'RootRecord',
fields: [
{
name: 'unionField',
type: [
{
type: 'record',
name: 'SomeRecordA',
fields: [
{
name: 'someRecordAField',
type: 'string',
},
],
},
{
type: 'record',
name: 'SomeRecordB',
fields: [
{
name: 'someRecordBField',
type: 'string',
},
],
},
],
},
],
} as schema.RecordType,
testObject: {
unionField: {
SomeRecordA: {
someRecordAField: 'test',
},
},
},
},
],
[
'inheriting a namespace',
{
schema: {
type: 'record',
name: 'RootRecord',
namespace: 'RootNamespace',
fields: [
{
name: 'unionField',
type: [
{
type: 'record',
name: 'SomeRecordA',
fields: [
{
name: 'someRecordAField',
type: 'string',
},
],
},
{
type: 'record',
name: 'SomeRecordB',
fields: [
{
name: 'someRecordBField',
type: 'string',
},
],
},
],
},
],
},
testObject: {
unionField: {
['RootNamespace.SomeRecordA']: {
someRecordAField: 'test',
},
},
},
},
],
[
'specifying a full name',
{
schema: {
type: 'record',
name: 'RootRecord',
fields: [
{
name: 'unionField',
type: [
{
type: 'record',
name: 'RecordANamespace.SomeRecordA',
fields: [
{
name: 'someRecordAField',
type: 'string',
},
],
},
{
type: 'record',
name: 'RecordBNamespace.SomeRecordB',
fields: [
{
name: 'someRecordBField',
type: 'string',
},
],
},
],
},
],
},
testObject: {
unionField: {
['RecordANamespace.SomeRecordA']: {
someRecordAField: 'test',
},
},
},
},
],
])('Should properly generate union type names when %s', (description, { schema, testObject }) => {
const tsCode = toTypeScript(schema);

const type = Type.forSchema(schema);
const result = type.fromBuffer(type.toBuffer(testObject));

// Adding the actual decoding result to visually make sure the types actually match the
// avro decoder behavior.
expect(result).toMatchSnapshot();
expect(tsCode).toMatchSnapshot();
});
});