Skip to content

Commit 05fa317

Browse files
authored
fix: include reused types in required properties (#246)
1 parent 5b6c01d commit 05fa317

File tree

3 files changed

+64
-2
lines changed

3 files changed

+64
-2
lines changed

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ async function processRecordSchema(avroDefinition: AvroSchema, recordCache: Reco
298298
// If the type is a sub schema it will have been stored in the cache.
299299
if (recordCache[field.type]) {
300300
propsMap.set(field.name, recordCache[field.type]);
301+
302+
// check for cached fields that should be marked as required
303+
const cachedProps = propsMap.get(field.name);
304+
const cached = { name: field.name, ...cachedProps };
305+
requiredAttributesMapping(cached, jsonSchema, cached.default !== undefined);
301306
} else {
302307
const def = await convertAvroToJsonSchema(field.type, false, recordCache);
303308

test/avro-schema-parser.spec.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,19 @@ const inputWithInvalidAvro = toParseInput(fs.readFileSync(path.resolve(__dirname
2626
const inputWithBrokenAvro = toParseInput(fs.readFileSync(path.resolve(__dirname, './documents/asyncapi-avro-broken.json'), 'utf8'));
2727

2828
const inputWithSubAvro190 = toParseInput(fs.readFileSync(path.resolve(__dirname, './documents/asyncapi-avro-111-1.9.0.json'), 'utf8'));
29-
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"}';
29+
const outputWithSubAvro190 = '{"type":"object","required":["metadata","auth_code","refresh_token","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"}';
3030

3131
const inputWithOneOfReferenceAvro190 = toParseInput(fs.readFileSync(path.resolve(__dirname, './documents/asyncapi-avro-113-1.9.0.json'), 'utf8'));
32-
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"}]}';
32+
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","address"],"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"}]}';
3333

3434
const inputWithRecordReferencesAvro190 = toParseInput(fs.readFileSync(path.resolve(__dirname, './documents/asyncapi-avro-148-1.9.0.json'), 'utf8'));
3535
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"}';
3636

3737
const inputWithValidAsyncAPI = fs.readFileSync(path.resolve(__dirname, './documents/valid-asyncapi.yaml'), 'utf8');
3838
const inputWithInvalidAsyncAPI = fs.readFileSync(path.resolve(__dirname, './documents/invalid-asyncapi.yaml'), 'utf8');
3939

40+
const inputWithReusedEnums = fs.readFileSync(path.resolve(__dirname, './documents/asyncapi-with-reused-enums.yaml'), 'utf8');
41+
4042
describe('AvroSchemaParser', function () {
4143
const parser = AvroSchemaParser();
4244
const coreParser = new Parser();
@@ -109,6 +111,12 @@ describe('AvroSchemaParser', function () {
109111
doParseCoreTest((document?.json()?.components?.messages?.testMessage as any)?.payload, outputWithAvro190);
110112
});
111113

114+
it('should include reused types in required properties', async function() {
115+
const { document } = await coreParser.parse(inputWithReusedEnums);
116+
expect(document?.json()?.components?.messages?.example_message?.payload?.schema?.required)
117+
.toEqual(['r1', 'r2', 'r3', 'r5']);
118+
});
119+
112120
it('should validate valid AsyncAPI', async function() {
113121
const diagnostics = await coreParser.validate(inputWithValidAsyncAPI);
114122
expect(filterDiagnostics(diagnostics, 'asyncapi2-schemas')).toHaveLength(0);
@@ -338,6 +346,9 @@ describe('avroToJsonSchema()', function () {
338346
'x-parser-schema-id': 'recordKey1'
339347
}
340348
},
349+
required: [
350+
'recordReference'
351+
],
341352
type: 'object'
342353
}
343354
},
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
asyncapi: 3.0.0
2+
info:
3+
title: AsyncAPI
4+
version: 1.0.0
5+
description: AsyncAPI
6+
channels:
7+
example:
8+
address: example
9+
messages:
10+
publish.message:
11+
$ref: '#/components/messages/example_message'
12+
operations:
13+
example.publish:
14+
action: receive
15+
channel:
16+
$ref: '#/channels/example'
17+
messages:
18+
- $ref: '#/channels/example/messages/publish.message'
19+
components:
20+
messages:
21+
example_message:
22+
name: example_event
23+
payload:
24+
schemaFormat: application/vnd.apache.avro;version=1.9.0
25+
schema:
26+
type: record
27+
name: ParentRecord
28+
fields:
29+
- name: r1
30+
type:
31+
type: enum
32+
name: MyEnum
33+
symbols:
34+
- A
35+
- B
36+
- C
37+
- name: r2
38+
type: MyEnum
39+
- name: r3
40+
type: MyEnum
41+
- name: r4
42+
type:
43+
- 'null'
44+
- string
45+
- name: r5
46+
type: string

0 commit comments

Comments
 (0)