-
-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: parse Avro name field to custom x-parser-schema-id #69
Changes from 5 commits
9444941
6f07557
c72497c
1e5a71a
9cb75df
897e551
9adbbaa
f526005
996620c
5789d60
c760f11
f98a07b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
asyncapi: 2.0.0 | ||
info: | ||
title: My API | ||
version: '1.0.0' | ||
channels: | ||
mychannel: | ||
publish: | ||
message: | ||
schemaFormat: application/vnd.apache.avro;version=1.9.0 | ||
payload: | ||
$ref: 'schemas/Person-1.9.0-namespace.avsc' |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"name": "Person", | ||
"namespace": "com.company", | ||
"type": "record", | ||
"fields": [ | ||
{"name": "name", "type": "string", example: "Donkey"}, | ||
derberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{"name": "age", "type": ["null", "int"], "default": null, example: "123"}, | ||
derberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
"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"}] | ||
derberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
}, | ||
{"name": "someid", "type": "uuid"} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -21,11 +21,30 @@ const typeMappings = { | |||||
uuid: 'string', | ||||||
}; | ||||||
|
||||||
const commonAttributesMapping = (avroDefinition, jsonSchema) => { | ||||||
const commonAttributesMapping = (avroDefinition, jsonSchema, isTopLevel) => { | ||||||
if (avroDefinition.doc) jsonSchema.description = avroDefinition.doc; | ||||||
if (avroDefinition.default !== undefined) jsonSchema.default = avroDefinition.default; | ||||||
|
||||||
const fullyQualifiedName = getFullyQualifiedName(avroDefinition); | ||||||
if (isTopLevel && fullyQualifiedName !== undefined) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll change that if you like, but I was just trying to be consistent with the style. Just above on line 26 we have: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, I think now I remember why it was |
||||||
jsonSchema['x-parser-schema-id'] = fullyQualifiedName; | ||||||
} | ||||||
}; | ||||||
|
||||||
function getFullyQualifiedName(avroDefinition) { | ||||||
let name; | ||||||
|
||||||
if (avroDefinition.name) { | ||||||
if (avroDefinition.namespace) { | ||||||
name = `${avroDefinition.namespace}.${avroDefinition.name}`; | ||||||
derberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} else { | ||||||
name = avroDefinition.name; | ||||||
} | ||||||
} | ||||||
|
||||||
return name; | ||||||
} | ||||||
|
||||||
const exampleAttributeMapping = (typeInput, example, jsonSchemaInput) => { | ||||||
let type = typeInput; | ||||||
let jsonSchema = jsonSchemaInput; | ||||||
|
@@ -51,15 +70,15 @@ const exampleAttributeMapping = (typeInput, example, jsonSchemaInput) => { | |||||
} | ||||||
}; | ||||||
|
||||||
module.exports.avroToJsonSchema = async function avroToJsonSchema(avroDefinition) { | ||||||
async function convertAvroToJsonSchema(avroDefinition, isTopLevel) { | ||||||
const jsonSchema = {}; | ||||||
const isUnion = Array.isArray(avroDefinition); | ||||||
|
||||||
if (isUnion) { | ||||||
jsonSchema.oneOf = []; | ||||||
let nullDef = null; | ||||||
for (const avroDef of avroDefinition) { | ||||||
const def = await avroToJsonSchema(avroDef); | ||||||
const def = await convertAvroToJsonSchema(avroDef, isTopLevel); | ||||||
// avroDef can be { type: 'int', default: 1 } and this is why avroDef.type has priority here | ||||||
const defType = avroDef.type || avroDef; | ||||||
// To prefer non-null values in the examples skip null definition here and push it as the last element after loop | ||||||
|
@@ -94,20 +113,20 @@ module.exports.avroToJsonSchema = async function avroToJsonSchema(avroDefinition | |||||
jsonSchema.maxLength = avroDefinition.size; | ||||||
break; | ||||||
case 'map': | ||||||
jsonSchema.additionalProperties = await avroToJsonSchema(avroDefinition.values); | ||||||
jsonSchema.additionalProperties = await convertAvroToJsonSchema(avroDefinition.values, false); | ||||||
break; | ||||||
case 'array': | ||||||
jsonSchema.items = await avroToJsonSchema(avroDefinition.items); | ||||||
jsonSchema.items = await convertAvroToJsonSchema(avroDefinition.items, false); | ||||||
break; | ||||||
case 'enum': | ||||||
jsonSchema.enum = avroDefinition.symbols; | ||||||
break; | ||||||
case 'record': | ||||||
const propsMap = new Map(); | ||||||
for (const field of avroDefinition.fields) { | ||||||
const def = await avroToJsonSchema(field.type); | ||||||
const def = await convertAvroToJsonSchema(field.type, false); | ||||||
|
||||||
commonAttributesMapping(field, def); | ||||||
commonAttributesMapping(field, def, false); | ||||||
exampleAttributeMapping(field.type, field.example, def); | ||||||
|
||||||
propsMap.set(field.name, def); | ||||||
|
@@ -116,8 +135,12 @@ module.exports.avroToJsonSchema = async function avroToJsonSchema(avroDefinition | |||||
break; | ||||||
} | ||||||
|
||||||
commonAttributesMapping(avroDefinition, jsonSchema); | ||||||
commonAttributesMapping(avroDefinition, jsonSchema, isTopLevel); | ||||||
exampleAttributeMapping(type, avroDefinition.example, jsonSchema); | ||||||
|
||||||
return jsonSchema; | ||||||
}; | ||||||
} | ||||||
|
||||||
module.exports.avroToJsonSchema = async function avroToJsonSchema(avroDefinition) { | ||||||
return convertAvroToJsonSchema(avroDefinition, true); | ||||||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to say this one doesn't let me sleep 😄
what part of the code is responsible for it? why 2nd is returned, then what if 3 schemas resolve to the same?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume that parser-js keeps a map where the key is x-parser-schema-id. So if there are several schemas that share the same x-parser-schema-id, then only the last one parsed will end up in that map. But if their document has several schemas with the same fully qualified name, I'd hope that they're identical, they repeat them because we force them to put Avro schemas into the message/payload section instead of components/schemas.
Maybe I should just not mention that at all? Or maybe that's an argument for using the title field after all, and adding x-namespace or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, now it makes sense, you are referring to 👇🏼
I think the sentence is ok, but just maybe write
If there are two schemas that resolve to the same fully qualified name, only the last one will be returned by the parser. Make sure names of your schemas are unique
?