diff --git a/src/schema-parser/avro-schema-parser.ts b/src/schema-parser/avro-schema-parser.ts index 530fffb2f..f81f395ef 100644 --- a/src/schema-parser/avro-schema-parser.ts +++ b/src/schema-parser/avro-schema-parser.ts @@ -84,12 +84,12 @@ const typeMappings: Record = { uuid: 'string', }; -function commonAttributesMapping(avroDefinition: AvroSchema, jsonSchema: v2.AsyncAPISchemaDefinition, isTopLevel: boolean): void { +function commonAttributesMapping(avroDefinition: AvroSchema, jsonSchema: v2.AsyncAPISchemaDefinition, recordCache: { [key:string]: AsyncAPISchema }): void { if (avroDefinition.doc) jsonSchema.description = avroDefinition.doc; if (avroDefinition.default !== undefined) jsonSchema.default = avroDefinition.default; const fullyQualifiedName = getFullyQualifiedName(avroDefinition); - if (isTopLevel && fullyQualifiedName !== undefined) { + if (fullyQualifiedName !== undefined && recordCache[fullyQualifiedName]) { jsonSchema['x-parser-schema-id'] = fullyQualifiedName; } } @@ -180,7 +180,9 @@ function additionalAttributesMapping(typeInput: any, avroDefinition: AvroSchema, setAdditionalAttribute('minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf'); break; case 'string': - jsonSchema.format = avroDefinition.logicalType; + if (avroDefinition.logicalType) { + jsonSchema.format = avroDefinition.logicalType; + } setAdditionalAttribute('pattern', 'minLength', 'maxLength'); break; case 'array': @@ -207,14 +209,14 @@ function validateAvroSchema(avroDefinition: AvroSchema): void | never { * @param key String | Undefined - the fully qualified name of an avro record * @param value JsonSchema - The json schema from the avro record */ -function cacheAvroRecordDef(cache: {[key:string]: AsyncAPISchema}, key: string, value: AsyncAPISchema): void { +function cacheAvroRecordDef(cache: { [key:string]: AsyncAPISchema }, key: string, value: AsyncAPISchema): void { if (key !== undefined) { cache[key] = value; } } async function convertAvroToJsonSchema(avroDefinition: AvroSchema , isTopLevel: boolean, recordCache: Map | any = {}): Promise { - const jsonSchema: v2.AsyncAPISchemaDefinition = {}; + let jsonSchema: v2.AsyncAPISchemaDefinition = {}; const isUnion = Array.isArray(avroDefinition); if (isUnion) { @@ -266,12 +268,20 @@ async function convertAvroToJsonSchema(avroDefinition: AvroSchema , isTopLevel: } case 'record': { const propsMap = await processRecordSchema(avroDefinition, recordCache, jsonSchema); + cacheAvroRecordDef(recordCache, getFullyQualifiedName(avroDefinition), propsMap); jsonSchema.properties = Object.fromEntries(propsMap.entries()); break; } + default: { + const cachedRecord = recordCache[getFullyQualifiedName(avroDefinition)]; + if (cachedRecord) { + jsonSchema = cachedRecord; + } + break; + } } - commonAttributesMapping(avroDefinition, jsonSchema, isTopLevel); + commonAttributesMapping(avroDefinition, jsonSchema, recordCache); additionalAttributesMapping(type, avroDefinition, jsonSchema); return jsonSchema; @@ -296,7 +306,7 @@ async function processRecordSchema(avroDefinition: AvroSchema, recordCache: Reco const def = await convertAvroToJsonSchema(field.type, false, recordCache); requiredAttributesMapping(field, jsonSchema, field.default !== undefined); - commonAttributesMapping(field, def, false); + commonAttributesMapping(field, def, recordCache); additionalAttributesMapping(field.type, field, def); propsMap.set(field.name, def); diff --git a/test/schema-parser/avro/asyncapi-avro-148-1.9.0.json b/test/schema-parser/avro/asyncapi-avro-148-1.9.0.json new file mode 100644 index 000000000..7bf12c833 --- /dev/null +++ b/test/schema-parser/avro/asyncapi-avro-148-1.9.0.json @@ -0,0 +1,67 @@ +{ + "schemaFormat": "application/vnd.apache.avro;version=1.9.0", + "payload": { + "type":"record", + "name":"RecordWithReferences", + "namespace":"com.example", + "fields":[ + { + "name":"Record1", + "type":{ + "type":"record", + "name":"Record1", + "doc":"Reused in other fields", + "fields":[ + { + "name":"string", + "type":"string", + "doc":"field in Record1" + } + ] + } + }, + { + "name":"FieldThatDefineRecordInUnion", + "type":[ + "null", + { + "type":"record", + "name":"RecordDefinedInUnion", + "namespace":"com.example.model", + "doc":"", + "fields":[ + { + "name":"number", + "type":"long", + "doc":"field in RecordDefinedInUnion", + "minimum": 0, + "maximum": 2 + } + ] + } + ], + "default":null + }, + { + "name":"FieldThatReuseRecordDefinedInUnion", + "type":[ + "null", + "com.example.model.RecordDefinedInUnion" + ], + "default":null + }, + { + "name":"FieldThatReuseRecord1", + "type":[ + "null", + "Record1" + ], + "default":null + }, + { + "name": "simpleField", + "type": "string" + } + ] + } +} \ No newline at end of file diff --git a/test/schema-parser/avro/avro-schema-parser.spec.ts b/test/schema-parser/avro/avro-schema-parser.spec.ts index 3afa487a6..41ff8118c 100644 --- a/test/schema-parser/avro/avro-schema-parser.spec.ts +++ b/test/schema-parser/avro/avro-schema-parser.spec.ts @@ -1,36 +1,38 @@ +import fs from 'fs'; +import path from 'path'; import { AvroSchemaParser, avroToJsonSchema } from '../../../src/schema-parser/avro-schema-parser'; -import * as fs from 'fs'; -import * as path from 'path'; -import type { Schema as AvroSchema } from 'avsc'; import type { ParseSchemaInput } from '../../../src/schema-parser'; const inputWithAvro182 = toParseInput(fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-1.8.2.json'), 'utf8')); -const outputWithAvro182 = '{"type":"object","required":["name","favoriteProgrammingLanguage","address"],"properties":{"name":{"type":"string","examples":["Donkey"]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"]},"address":{"type":"object","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]}}}}}'; +const outputWithAvro182 = '{"type":"object","required":["name","favoriteProgrammingLanguage","address"],"properties":{"name":{"type":"string","examples":["Donkey"]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"]},"address":{"type":"object","x-parser-schema-id":"Address","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]}}}}}'; const inputWithAvro190 = toParseInput(fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-1.9.0.json'), 'utf8')); -const outputWithAvro190 = '{"type":"object","required":["name","favoriteProgrammingLanguage","address","someid"],"properties":{"name":{"type":"string","examples":["Donkey"]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[123]},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"],"default":"JS"},"address":{"type":"object","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]}}},"someid":{"type":"string","format":"uuid"}},"x-parser-schema-id":"Person"}'; +const outputWithAvro190 = '{"type":"object","required":["name","favoriteProgrammingLanguage","address","someid"],"properties":{"name":{"type":"string","examples":["Donkey"]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[123]},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"],"default":"JS"},"address":{"type":"object","x-parser-schema-id":"Address","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]}}},"someid":{"type":"string","format":"uuid"}},"x-parser-schema-id":"Person"}'; const inputWithAvro190WithNamespace = toParseInput(fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-1.9.0-namespace.json'), 'utf8')); -const outputWithAvro190WithNamespace = '{"type":"object","required":["name","favoriteProgrammingLanguage","address","someid"],"properties":{"name":{"type":"string","examples":["Donkey"]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[123]},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"],"default":"JS"},"address":{"type":"object","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]}}},"someid":{"type":"string","format":"uuid"}},"x-parser-schema-id":"com.company.Person"}'; +const outputWithAvro190WithNamespace = '{"type":"object","required":["name","favoriteProgrammingLanguage","address","someid"],"properties":{"name":{"type":"string","examples":["Donkey"]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[123]},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"],"default":"JS"},"address":{"type":"object","x-parser-schema-id":"Address","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]}}},"someid":{"type":"string","format":"uuid"}},"x-parser-schema-id":"com.company.Person"}'; const inputWithAvro190WithBindings = toParseInput(fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-1.9.0-bindings.json'), 'utf8')); -const outputWithAvro190WithBindings = '{"type":"object","required":["name","favoriteProgrammingLanguage","address","someid"],"properties":{"name":{"type":"string","examples":["Donkey"]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[123]},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"],"default":"JS"},"address":{"type":"object","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]}}},"someid":{"type":"string","format":"uuid"}},"x-parser-schema-id":"com.company.Person"}'; -const outputWithAvro190WithBindingsKafkaKeyTransformed = '{"type":"object","required":["name","favoriteProgrammingLanguage","address","someid"],"properties":{"name":{"type":"string","examples":["Donkey"]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[123]},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"],"default":"JS"},"address":{"type":"object","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]}}},"someid":{"type":"string","format":"uuid"}},"x-parser-schema-id":"com.company.Person"}'; +const outputWithAvro190WithBindings = '{"type":"object","required":["name","favoriteProgrammingLanguage","address","someid"],"properties":{"name":{"type":"string","examples":["Donkey"]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[123]},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"],"default":"JS"},"address":{"type":"object","x-parser-schema-id":"Address","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]}}},"someid":{"type":"string","format":"uuid"}},"x-parser-schema-id":"com.company.Person"}'; +const outputWithAvro190WithBindingsKafkaKeyTransformed = '{"type":"object","required":["name","favoriteProgrammingLanguage","address","someid"],"properties":{"name":{"type":"string","examples":["Donkey"]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[123]},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"],"default":"JS"},"address":{"type":"object","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]}},"x-parser-schema-id":"Address"},"someid":{"type":"string","format":"uuid"}},"x-parser-schema-id":"com.company.Person"}'; const outputWithAvro190WithBindingsKafkaKeyOriginal = '{"name":"Person","namespace":"com.company","type":"record","fields":[{"name":"name","type":"string","example":"Donkey"},{"name":"age","type":["null","int"],"default":null,"example":123},{"name":"favoriteProgrammingLanguage","type":{"name":"ProgrammingLanguage","type":"enum","symbols":["JS","Java","Go","Rust","C"],"default":"JS"}},{"name":"address","type":{"name":"Address","type":"record","fields":[{"name":"zipcode","type":"int","example":53003}]}},{"name":"someid","type":"string","logicalType":"uuid"}]}'; const inputWithAvroAdditionalAttributes = toParseInput(fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-1.9.0-additional-attributes.json'), 'utf8')); -const outputWithAvroAdditionalAttributes = '{"type":"object","required":["name","serialNo","favoriteProgrammingLanguage","certifications","address","weight","height","someid"],"properties":{"name":{"type":"string","examples":["Donkey"],"minLength":0},"serialNo":{"type":"string","minLength":0,"maxLength":50},"email":{"oneOf":[{"type":"string","examples":["donkey@asyncapi.com"],"pattern":"^[\\\\w-\\\\.]+@([\\\\w-]+\\\\.)+[\\\\w-]{2,4}$"},{"type":"null"}]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[123],"exclusiveMinimum":0,"exclusiveMaximum":200},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"],"default":"JS"},"certifications":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":500,"uniqueItems":true},"address":{"type":"object","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]},"country":{"oneOf":[{"type":"string"},{"type":"null"}]}}},"weight":{"type":"number","format":"float","examples":[65.1],"minimum":0,"maximum":500},"height":{"type":"number","format":"double","examples":[1.85],"minimum":0,"maximum":3},"someid":{"type":"string","format":"uuid"}},"x-parser-schema-id":"com.company.Person"}'; +const outputWithAvroAdditionalAttributes = '{"type":"object","required":["name","serialNo","favoriteProgrammingLanguage","certifications","address","weight","height","someid"],"properties":{"name":{"type":"string","examples":["Donkey"],"minLength":0},"serialNo":{"type":"string","minLength":0,"maxLength":50},"email":{"oneOf":[{"type":"string","examples":["donkey@asyncapi.com"],"pattern":"^[\\\\w-\\\\.]+@([\\\\w-]+\\\\.)+[\\\\w-]{2,4}$"},{"type":"null"}]},"age":{"oneOf":[{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[123],"exclusiveMinimum":0,"exclusiveMaximum":200},{"type":"null"}],"default":null},"favoriteProgrammingLanguage":{"type":"string","enum":["JS","Java","Go","Rust","C"],"default":"JS"},"certifications":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":500,"uniqueItems":true},"address":{"type":"object","x-parser-schema-id":"Address","required":["zipcode"],"properties":{"zipcode":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"examples":[53003]},"country":{"oneOf":[{"type":"string"},{"type":"null"}]}}},"weight":{"type":"number","format":"float","examples":[65.1],"minimum":0,"maximum":500},"height":{"type":"number","format":"double","examples":[1.85],"minimum":0,"maximum":3},"someid":{"type":"string","format":"uuid"}},"x-parser-schema-id":"com.company.Person"}'; const inputWithInvalidAvro = toParseInput(fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-invalid.json'), 'utf8')); const inputWithBrokenAvro = toParseInput(fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-broken.json'), 'utf8')); const inputWithSubAvro190 = toParseInput(fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-111-1.9.0.json'), 'utf8')); -const outputWithSubAvro190 = '{"type":"object","required":["metadata","auth_code","triggered_by"],"properties":{"metadata":{"type":"object","required":["id","timestamp"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier for this specific event"},"timestamp":{"type":"integer","minimum":-9223372036854776000,"maximum":9223372036854776000,"description":"Instant the event took place (not necessary when it was published)"},"correlation_id":{"oneOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"id of the event that resulted in this\\nevent being published (optional)","default":null},"publisher_context":{"oneOf":[{"type":"object","additionalProperties":{"type":"string"}},{"type":"null"}],"description":"optional set of key-value pairs of context to be echoed back\\nin any resulting message (like a richer\\ncorrelationId.\\n\\nThese values are likely only meaningful to the publisher\\nof the correlated event","default":null}},"description":"Metadata to be associated with every published event"},"auth_code":{"type":"object","required":["value","nonce","key"],"properties":{"value":{"type":"string","description":"A sequence of bytes that has been AES encrypted in CTR mode."},"nonce":{"type":"string","description":"A nonce, used by the CTR encryption mode for our encrypted value. Not encrypted, not a secret."},"key":{"type":"string","description":"An AES key, used to encrypt the value field, that has itself been encrypted using RSA."}},"description":"Encrypted auth_code received when user authorizes the app."},"refresh_token":{"type":"object","required":["value","nonce","key"],"properties":{"value":{"type":"string","description":"A sequence of bytes that has been AES encrypted in CTR mode."},"nonce":{"type":"string","description":"A nonce, used by the CTR encryption mode for our encrypted value. Not encrypted, not a secret."},"key":{"type":"string","description":"An AES key, used to encrypt the value field, that has itself been encrypted using RSA."}},"description":"Encrypted auth_code received when user authorizes the app."},"triggered_by":{"type":"string","format":"uuid","description":"ID of the user who triggered this event."}},"description":"An example schema to illustrate the issue","x-parser-schema-id":"com.foo.connections.ConnectionRequested"}'; +const outputWithSubAvro190 = '{"type":"object","required":["metadata","auth_code","triggered_by"],"properties":{"metadata":{"type":"object","x-parser-schema-id":"com.foo.EventMetadata","required":["id","timestamp"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier for this specific event"},"timestamp":{"type":"integer","minimum":-9223372036854776000,"maximum":9223372036854776000,"description":"Instant the event took place (not necessary when it was published)"},"correlation_id":{"oneOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"id of the event that resulted in this\\nevent being published (optional)","default":null},"publisher_context":{"oneOf":[{"type":"object","additionalProperties":{"type":"string"}},{"type":"null"}],"description":"optional set of key-value pairs of context to be echoed back\\nin any resulting message (like a richer\\ncorrelationId.\\n\\nThese values are likely only meaningful to the publisher\\nof the correlated event","default":null}},"description":"Metadata to be associated with every published event"},"auth_code":{"type":"object","x-parser-schema-id":"com.foo.EncryptedString","required":["value","nonce","key"],"properties":{"value":{"type":"string","description":"A sequence of bytes that has been AES encrypted in CTR mode."},"nonce":{"type":"string","description":"A nonce, used by the CTR encryption mode for our encrypted value. Not encrypted, not a secret."},"key":{"type":"string","description":"An AES key, used to encrypt the value field, that has itself been encrypted using RSA."}},"description":"Encrypted auth_code received when user authorizes the app."},"refresh_token":{"type":"object","required":["value","nonce","key"],"properties":{"value":{"type":"string","description":"A sequence of bytes that has been AES encrypted in CTR mode."},"nonce":{"type":"string","description":"A nonce, used by the CTR encryption mode for our encrypted value. Not encrypted, not a secret."},"key":{"type":"string","description":"An AES key, used to encrypt the value field, that has itself been encrypted using RSA."}},"description":"Encrypted auth_code received when user authorizes the app.","x-parser-schema-id":"com.foo.EncryptedString"},"triggered_by":{"type":"string","format":"uuid","description":"ID of the user who triggered this event."}},"description":"An example schema to illustrate the issue","x-parser-schema-id":"com.foo.connections.ConnectionRequested"}'; const inputWithOneOfReferenceAvro190 = toParseInput(fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-113-1.9.0.json'), 'utf8')); const outputWithOneOfReferenceAvro190 = '{"oneOf":[{"type":"object","required":["streetaddress","city"],"properties":{"streetaddress":{"type":"string"},"city":{"type":"string"}},"x-parser-schema-id":"com.example.Address"},{"type":"object","required":["firstname","lastname"],"properties":{"firstname":{"type":"string"},"lastname":{"type":"string"},"address":{"type":"object","required":["streetaddress","city"],"properties":{"streetaddress":{"type":"string"},"city":{"type":"string"}},"x-parser-schema-id":"com.example.Address"}},"x-parser-schema-id":"com.example.Person"}]}'; +const inputWithRecordReferencesAvro190 = toParseInput(fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-148-1.9.0.json'), 'utf8')); +const outputWithRecordReferencesAvro190 = '{"type":"object","required":["Record1","simpleField"],"properties":{"Record1":{"type":"object","required":["string"],"properties":{"string":{"type":"string","description":"field in Record1"}},"description":"Reused in other fields","x-parser-schema-id":"Record1"},"FieldThatDefineRecordInUnion":{"oneOf":[{"type":"object","required":["number"],"properties":{"number":{"type":"integer","minimum":0,"maximum":2,"description":"field in RecordDefinedInUnion"}},"x-parser-schema-id":"com.example.model.RecordDefinedInUnion"},{"type":"null"}],"default":null},"FieldThatReuseRecordDefinedInUnion":{"oneOf":[{},{"type":"null"}],"default":null},"FieldThatReuseRecord1":{"oneOf":[{},{"type":"null"}],"default":null},"simpleField":{"type":"string"}},"x-parser-schema-id":"com.example.RecordWithReferences"}'; + describe('AvroSchemaParser', function () { const parser = AvroSchemaParser(); @@ -90,16 +92,16 @@ describe('AvroSchemaParser', function () { await doTest(inputWithOneOfReferenceAvro190, outputWithOneOfReferenceAvro190); }); + it('Issue #148 should handle records references in a top level record', async function() { + await doTest(inputWithRecordReferencesAvro190, outputWithRecordReferencesAvro190); + }); + async function doTest(originalInput: ParseSchemaInput, expectedOutput: any) { const input = {...originalInput}; const result = await parser.parse(input); // Check that the return value of parse() is the expected JSON Schema. expect(result).toEqual(JSON.parse(expectedOutput)); - - // // Check that the message got modified, i.e. adding extensions, setting the payload, etc. - // const message = (input.meta as any).message; - // expect(JSON.stringify(message)).toEqual(expectedOutput); } }); @@ -246,6 +248,63 @@ describe('avroToJsonSchema()', function () { ] }); }); + + it('support record references', async function () { + const result = await avroToJsonSchema({ + type: 'record', + doc: 'My test record', + name: 'MyName', + fields: [ + { + name: 'key1', + type: {type: 'record', name: 'recordKey1', doc: 'Key1 docs', fields: [{type: 'string', name: 'test'}]} + }, + {name: 'key2', type: {type: 'record', fields: [{name: 'recordReference', type: 'recordKey1'}]}}, + ] + } as any); + expect(result).toEqual({ + description: 'My test record', + properties: { + key1: { + description: 'Key1 docs', + properties: { + test: { + type: 'string' + } + }, + required: [ + 'test' + ], + type: 'object', + 'x-parser-schema-id': 'recordKey1' + }, + key2: { + properties: { + recordReference: { + description: 'Key1 docs', + properties: { + test: { + type: 'string' + } + }, + required: [ + 'test' + ], + type: 'object', + 'x-parser-schema-id': 'recordKey1' + } + }, + type: 'object' + } + }, + required: [ + 'key1', + 'key2' + ], + type: 'object', + 'x-parser-schema-id': 'MyName' + }); + }); }); function toParseInput(raw: string): ParseSchemaInput {