Skip to content

Instantly share code, notes, and snippets.

@mtth
Last active July 19, 2018 02:51
Show Gist options
  • Save mtth/c0088c745de048c4e466 to your computer and use it in GitHub Desktop.
Save mtth/c0088c745de048c4e466 to your computer and use it in GitHub Desktop.
Avro type hooks
/* jshint node: true */
'use strict';
var avro = require('avsc');
/**
* Type hook to allow "inlining" record fields.
*
* This can be useful to work around Avro's lack of inheritance support.
*
* If any record fields contain another record and have an "inline" attribute
* set, this hook will add the inner record's fields directly to the outer
* record. For example using this hook on the following schema (using IDL
* notation):
*
* record Foo {
* record Bar {
* int one;
* int two;
* } @inline(true) bar;
* int three;
* }
*
* Will create a type equivalent to:
*
* record Foo {
* int one;
* int two;
* int three;
* }
*
*/
function typeHook(attrs, opts) {
if (attrs.type !== 'record') {
// Inlining only applies to records.
return;
}
var fields = [];
var namespace = opts.namespace;
opts.namespace = attrs.namespace || opts.namespace;
attrs.fields.forEach(function (fieldAttrs) {
if (!fieldAttrs.inline) {
// Default behavior.
fields.push(fieldAttrs);
return;
}
var type;
if (typeof fieldAttrs.type === 'string') {
// This is a reference, we must obtain the underlying type.
var name = fieldAttrs.type;
if (!~name.indexOf('.') && opts.namespace) {
name = opts.namespace + '.' + name;
}
type = opts.registry[name];
if (!type) {
// Just add the undefined reference so that `parse` can throw an
// appropriate "undefined type" error.
fields.push(fieldAttrs);
return;
}
} else {
type = avro.parse(fieldAttrs.type, opts);
}
if (!avro.Type.isType(type, 'record', 'error')) {
throw new Error('only record fields can be inlined');
}
type.fields.forEach(function (inlinedField) {
// Note that we don't need to check for duplicates here since that will
// be done by `parse` when it creates the type after the hook returns.
fields.push({
aliases: inlinedField.aliases,
'default': inlinedField.defaultValue(),
name: inlinedField.name,
order: computeOrder(inlinedField.order),
type: inlinedField.type
});
});
function computeOrder(order) {
// If the inlined field has a descending order, we must "invert" all its
// fields' orders. Similarly for ignored.
if (order === 'ignore' || fieldAttrs.order === 'ignore') {
return 'ignore';
}
switch (fieldAttrs.order || 'ascending') {
case 'descending':
return order === 'ascending' ? 'descending' : 'ascending';
case 'ascending':
return order;
default:
throw new Error('invalid order: ' + fieldAttrs.order);
}
}
});
opts.namespace = namespace;
attrs.fields = fields;
}
/* jshint node: true */
'use strict';
var avro = require('avsc');
/**
* Hook which will decode/encode enums to/from integers.
*
* The default `EnumType` implementation represents enum values as strings
* (consistent with the JSON representation). This hook can be used to provide
* an alternate representation (which is for example compatible with TypeScript
* enums).
*
* For simplicity, we don't do any bound checking here but we could by
* implementing a "bounded long" logical type and returning that instead.
*
*/
function typeHook(attrs, opts) {
if (attrs.type === 'enum') {
return avro.parse('long', opts);
}
// Falling through will cause the default type to be used.
}
/* jshint node: true */
'use strict';
/**
* Hook to obfuscate a schema.
*
* This hook will produce a type with the names of records, fixed, enums,
* fields, and symbols mangled.
*
*/
function typeHook(attrs) {
if (!attrs.name) {
return; // Nothing to mangle.
}
attrs.name = randomString();
var fields, symbols;
if ((fields = attrs.fields)) {
fields.forEach(function (o) { o.name = randomString(); });
} else if ((symbols = attrs.symbols)) {
symbols.forEach(function (s, i) { symbols[i] = randomString(); });
}
}
function randomString() { (Math.random() + 1).toString(36).substring(8); }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment