@asyncapi/avro-schema-parser
Advanced tools
Comparing version 1.0.0 to 1.0.1
{ | ||
"name": "@asyncapi/avro-schema-parser", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "An AsyncAPI schema parser for Avro 1.x schemas.", | ||
@@ -9,3 +9,3 @@ "main": "index.js", | ||
"release": "semantic-release", | ||
"lint": "eslint --max-warnings 0 --config .eslintrc.yaml .", | ||
"lint": "eslint --max-warnings 1 --config .eslintrc.yaml .", | ||
"generate:assets": "echo 'No additional assets need to be generated at the moment'", | ||
@@ -34,3 +34,3 @@ "bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION" | ||
"devDependencies": { | ||
"@asyncapi/parser": "^1.11.1", | ||
"@asyncapi/parser": "^1.13.0", | ||
"@semantic-release/commit-analyzer": "^8.0.1", | ||
@@ -37,0 +37,0 @@ "@semantic-release/github": "^7.0.4", |
@@ -24,2 +24,8 @@ const fs = require('fs'); | ||
const inputWithSubAvro190 = fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-111-1.9.0.yaml'), 'utf8'); | ||
const outputWithSubAvro190 = '{"asyncapi":"2.0.0","info":{"title":"My API","version":"1.0.0"},"channels":{"mychannel":{"publish":{"message":{"payload":{"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","x-parser-schema-id":"<anonymous-schema-2>"},"timestamp":{"type":"integer","minimum":-9223372036854776000,"maximum":9223372036854776000,"description":"Instant the event took place (not necessary when it was published)","x-parser-schema-id":"<anonymous-schema-3>"},"correlation_id":{"oneOf":[{"type":"string","format":"uuid","x-parser-schema-id":"<anonymous-schema-5>"},{"type":"null","x-parser-schema-id":"<anonymous-schema-6>"}],"description":"id of the event that resulted in this\\nevent being published (optional)","default":null,"x-parser-schema-id":"<anonymous-schema-4>"},"publisher_context":{"oneOf":[{"type":"object","additionalProperties":{"type":"string","x-parser-schema-id":"<anonymous-schema-9>"},"x-parser-schema-id":"<anonymous-schema-8>"},{"type":"null","x-parser-schema-id":"<anonymous-schema-10>"}],"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,"x-parser-schema-id":"<anonymous-schema-7>"}},"description":"Metadata to be associated with every published event","x-parser-schema-id":"<anonymous-schema-1>"},"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.","x-parser-schema-id":"<anonymous-schema-12>"},"nonce":{"type":"string","description":"A nonce, used by the CTR encryption mode for our encrypted value. Not encrypted, not a secret.","x-parser-schema-id":"<anonymous-schema-13>"},"key":{"type":"string","description":"An AES key, used to encrypt the value field, that has itself been encrypted using RSA.","x-parser-schema-id":"<anonymous-schema-14>"}},"description":"Encrypted auth_code received when user authorizes the app.","x-parser-schema-id":"<anonymous-schema-11>"},"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.","x-parser-schema-id":"<anonymous-schema-12>"},"nonce":{"type":"string","description":"A nonce, used by the CTR encryption mode for our encrypted value. Not encrypted, not a secret.","x-parser-schema-id":"<anonymous-schema-13>"},"key":{"type":"string","description":"An AES key, used to encrypt the value field, that has itself been encrypted using RSA.","x-parser-schema-id":"<anonymous-schema-14>"}},"description":"Encrypted auth_code received when user authorizes the app.","x-parser-schema-id":"<anonymous-schema-11>"},"triggered_by":{"type":"string","format":"uuid","description":"ID of the user who triggered this event.","x-parser-schema-id":"<anonymous-schema-15>"}},"description":"An example schema to illustrate the issue","x-parser-schema-id":"com.foo.connections.ConnectionRequested"},"x-parser-original-schema-format":"application/vnd.apache.avro;version=1.9.0","x-parser-original-payload":{"type":"record","name":"ConnectionRequested","namespace":"com.foo.connections","doc":"An example schema to illustrate the issue","fields":[{"name":"metadata","type":{"type":"record","name":"EventMetadata","namespace":"com.foo","doc":"Metadata to be associated with every published event","fields":[{"name":"id","type":{"type":"string","logicalType":"uuid"},"doc":"Unique identifier for this specific event"},{"name":"timestamp","type":{"type":"long","logicalType":"timestamp-millis"},"doc":"Instant the event took place (not necessary when it was published)"},{"name":"correlation_id","type":["null",{"type":"string","logicalType":"uuid"}],"doc":"id of the event that resulted in this\\nevent being published (optional)","default":null},{"name":"publisher_context","type":["null",{"type":"map","values":{"type":"string","avro.java.string":"String"},"avro.java.string":"String"}],"doc":"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}]}},{"name":"auth_code","type":{"type":"record","name":"EncryptedString","namespace":"com.foo","doc":"A string that was encrypted with AES (using CTR mode), its key encrypted with RSA, and the nonce used for the encryption.","fields":[{"name":"value","type":"string","doc":"A sequence of bytes that has been AES encrypted in CTR mode."},{"name":"nonce","type":"string","doc":"A nonce, used by the CTR encryption mode for our encrypted value. Not encrypted, not a secret."},{"name":"key","type":"string","doc":"An AES key, used to encrypt the value field, that has itself been encrypted using RSA."}]},"doc":"Encrypted auth_code received when user authorizes the app."},{"name":"refresh_token","type":"com.foo.EncryptedString","doc":"Encrypted refresh_token generated by using clientId and clientSecret."},{"name":"triggered_by","type":{"type":"string","logicalType":"uuid"},"doc":"ID of the user who triggered this event."}]},"schemaFormat":"application/vnd.aai.asyncapi;version=2.0.0","x-parser-message-parsed":true,"x-parser-message-name":"<anonymous-message-1>"}}}},"x-parser-spec-parsed":true}'; | ||
const inputWithOneOfReferenceAvro190 = fs.readFileSync(path.resolve(__dirname, './asyncapi-avro-113-1.9.0.yaml'), 'utf8'); | ||
const outputWithOneOfReferenceAvro190 = '{"asyncapi":"2.0.0","info":{"title":"My API","version":"1.0.0"},"channels":{"mychannel":{"publish":{"message":{"payload":{"oneOf":[{"type":"object","required":["streetaddress","city"],"properties":{"streetaddress":{"type":"string","x-parser-schema-id":"<anonymous-schema-2>"},"city":{"type":"string","x-parser-schema-id":"<anonymous-schema-3>"}},"x-parser-schema-id":"com.example.Address"},{"type":"object","required":["firstname","lastname"],"properties":{"firstname":{"type":"string","x-parser-schema-id":"<anonymous-schema-4>"},"lastname":{"type":"string","x-parser-schema-id":"<anonymous-schema-5>"},"address":{"type":"object","required":["streetaddress","city"],"properties":{"streetaddress":{"type":"string","x-parser-schema-id":"<anonymous-schema-2>"},"city":{"type":"string","x-parser-schema-id":"<anonymous-schema-3>"}},"x-parser-schema-id":"com.example.Address"}},"x-parser-schema-id":"com.example.Person"}],"x-parser-schema-id":"<anonymous-schema-1>"},"x-parser-original-schema-format":"application/vnd.apache.avro;version=1.9.0","x-parser-original-payload":[{"type":"record","name":"Address","namespace":"com.example","fields":[{"name":"streetaddress","type":"string"},{"name":"city","type":"string"}]},{"type":"record","name":"Person","namespace":"com.example","fields":[{"name":"firstname","type":"string"},{"name":"lastname","type":"string"},{"name":"address","type":"com.example.Address"}]}],"schemaFormat":"application/vnd.aai.asyncapi;version=2.0.0","x-parser-message-parsed":true,"x-parser-message-name":"<anonymous-message-1>"}}}},"x-parser-spec-parsed":true}'; | ||
parser.registerSchemaParser(avroSchemaParser); | ||
@@ -36,2 +42,3 @@ | ||
}); | ||
it('should parse Avro schema 1.9.0 with a namespace', async function() { | ||
@@ -57,2 +64,11 @@ const result = await parser.parse(inputWithAvro190WithNamespace, { path: __filename }); | ||
}); | ||
it('Issue #111 should handle pre defined records in schemas', async function() { | ||
const result = await parser.parse(inputWithSubAvro190, { path: __filename }); | ||
expect(JSON.stringify(result.json())).toEqual(outputWithSubAvro190); | ||
}); | ||
it('Issue #113 should handle pre defined records in top level oneOf schema', async function() { | ||
const result = await parser.parse(inputWithOneOfReferenceAvro190, { path: __filename }); | ||
console.log(JSON.stringify(result.json())); | ||
expect(JSON.stringify(result.json())).toEqual(outputWithOneOfReferenceAvro190); | ||
}); | ||
}); |
@@ -139,8 +139,20 @@ const avsc = require('avsc'); | ||
async function convertAvroToJsonSchema(avroDefinition, isTopLevel) { | ||
/** | ||
* Cache the passed value under the given key. If the key is undefined the value will not be cached. This function | ||
* uses mutation of the passed cache object rather than a copy on write cache strategy. | ||
* | ||
* @param cache Map<String, JsonSchema> the cache to store the JsonSchema | ||
* @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, value) { | ||
if (key) { | ||
cache[key] = value; | ||
} | ||
} | ||
async function convertAvroToJsonSchema(avroDefinition, isTopLevel, recordCache = {}) { | ||
const jsonSchema = {}; | ||
const isUnion = Array.isArray(avroDefinition); | ||
validateAvroSchema(avroDefinition); | ||
if (isUnion) { | ||
@@ -150,7 +162,13 @@ jsonSchema.oneOf = []; | ||
for (const avroDef of avroDefinition) { | ||
const def = await convertAvroToJsonSchema(avroDef, isTopLevel); | ||
const def = await convertAvroToJsonSchema(avroDef, isTopLevel, recordCache); | ||
// 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 | ||
if (defType === 'null') nullDef = def; else jsonSchema.oneOf.push(def); | ||
if (defType === 'null') { | ||
nullDef = def; | ||
} else { | ||
jsonSchema.oneOf.push(def); | ||
const qualifiedName = getFullyQualifiedName(avroDef); | ||
cacheAvroRecordDef(recordCache, qualifiedName, def); | ||
} | ||
} | ||
@@ -200,9 +218,17 @@ if (nullDef) jsonSchema.oneOf.push(nullDef); | ||
for (const field of avroDefinition.fields) { | ||
const def = await convertAvroToJsonSchema(field.type, false); | ||
// If the type is a sub schema it will have been stored in the cache. | ||
if (recordCache[field.type]) { | ||
propsMap.set(field.name, recordCache[field.type]); | ||
} else { | ||
const def = await convertAvroToJsonSchema(field.type, false, recordCache); | ||
requiredAttributesMapping(field, jsonSchema, field.default !== undefined); | ||
commonAttributesMapping(field, def, false); | ||
additionalAttributesMapping(field.type, field, def); | ||
requiredAttributesMapping(field, jsonSchema, field.default !== undefined); | ||
commonAttributesMapping(field, def, false); | ||
additionalAttributesMapping(field.type, field, def); | ||
propsMap.set(field.name, def); | ||
propsMap.set(field.name, def); | ||
// If there is a name for the sub record cache it under the name. | ||
const qualifiedFieldName = getFullyQualifiedName(field.type); | ||
cacheAvroRecordDef(recordCache, qualifiedFieldName, def); | ||
} | ||
} | ||
@@ -220,3 +246,4 @@ jsonSchema.properties = Object.fromEntries(propsMap.entries()); | ||
module.exports.avroToJsonSchema = async function avroToJsonSchema(avroDefinition) { | ||
validateAvroSchema(avroDefinition); | ||
return convertAvroToJsonSchema(avroDefinition, true); | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
244430
47
598
8