UNPKG

avro-schema

Version:

AVRO schema utilities

315 lines (288 loc) 8.77 kB
"use strict"; var Encoder = require('./encode').Encoder; var Schema = module.exports.Schema = function Schema() { this._symbols = []; this._lastLoaded = null; }; Schema.prototype._schemaData = function _schemaData(schemaName) { var schema = schemaName ? this._symbols[schemaName] : this._lastLoaded; if (!schema) { throw new Error('Invalid schema.'); } return schema; }; Schema.prototype.load = function load(data) { var symbols = this._symbols; function loadSchema(schema, namespace) { // Register the schema into the symbol table. function registerSchema() { function registerSymbol(name) { if (typeof name !== 'string') { return false; } if (name.indexOf('.') === -1) { // use local param as override to schema specified name = (namespace || schema.namespace) + '.' + name; } if (name in symbols) { return symbols[name] === schema; } else { symbols[name] = schema; return true; } } return registerSymbol(schema.name) && (!Array.isArray(schema.aliases) || schema.aliases.every(registerSymbol)); } var typeName = schema; if (typeof schema !== 'string') { if (typeof schema !== 'object') { return false; } if (!Array.isArray(schema)) { typeName = schema.type; } } if (Array.isArray(typeName)) { // Union. return typeName.every(function loadUnionMember(s) { return loadSchema(s, namespace); }); } switch (typeName) { case 'long': // JavaScript cannot represent a 64-bit integer. return false; case 'null': case 'boolean': case 'string': case 'bytes': case 'int': case 'float': case 'double': return true; case 'array': return loadSchema(schema.items, namespace); case 'map': return loadSchema(schema.keys, namespace) && loadSchema(schema.values, namespace); case 'enum': case 'fixed': return registerSchema(); case 'record': return registerSchema() && schema.fields.every(function loadRecordField(field) { return typeof field.name === 'string' && loadSchema(field.type, schema.namespace || namespace); }); default: return typeName in symbols; } } if (loadSchema(data, '')) { this._lastLoaded = data; return true; } else { return false; } }; // Validate datum against a schema. // If schemaName is omitted, last loaded schema is used. Schema.prototype.validate = function validate(rootDatum, schemaName) { var symbols = this._symbols; var rootSchema = this._schemaData(schemaName); function validateSchema(datum, schema) { var typeName; if (typeof schema === 'string') { typeName = schema; } else if (typeof schema === 'object') { if (Array.isArray(schema)) { return schema.some(function validateUnionMember(s) { return validateSchema(datum, s); }); } typeName = schema.type; } else { return false; } switch (typeName) { case 'null': return datum === undefined; case 'boolean': return datum === true || datum === false; case 'string': return typeof datum === 'string'; case 'bytes': return typeof datum === 'string' || Buffer.isBuffer(datum) || Array.isArray(datum); case 'int': return parseInt(datum, 10) === datum; case 'long': // JavaScript cannot represent a 64-bit integer. return false; case 'float': return typeof datum === 'number'; case 'double': return typeof datum === 'number'; case 'array': if (!Array.isArray(datum)) { return false; } return datum.every(function validateArrayItem(d) { return validateSchema(d, schema.items); }); case 'map': for (var key in datum) { if (!validateSchema(datum[key], schema.values)) { return false; } } return true; case 'enum': return schema.symbols.some(function validateEnumMember(symbol) { return symbol === datum; }); case 'record': return datum && schema.fields.every(function validateRecordField(field) { return (!(field.name in datum) && 'default' in field) || validateSchema(datum[field.name], field.type); }); case 'fixed': return typeof (Buffer.isBuffer(datum) || Array.isArray(datum)) && datum.length === schema.size; default: return validateSchema(datum, symbols[typeName]); } } return validateSchema(rootDatum, rootSchema); }; // Encode the datum to avro binary. // If schemaName is omitted, last loaded schema is used. module.exports.encode = function encode(rootDatum, schemaName) { var e = new Encoder(); var symbols = this._symbols; function encodeDatum(datum, schema) { var typeName = schema; var type; var keys; if (typeof schema !== 'string') { if (typeof schema === 'object') { if (Array.isArray(schema)) { // Check for use of unions for encoding optional parameters. if (datum === null || datum === undefined) { type = schema.indexOf("null"); if (type === -1) { return false; } e.writeInt(type); return true; } if (typeof schema !== 'object') { return false; } keys = Object.keys(datum); if (keys.length !== 1) { return false; } type = schema.indexOf(keys[0]); if (type === -1) { return false; } e.writeInt(type); return encodeDatum(datum[keys[0]], schema[type]); } typeName = schema.type; } else { return false; } } switch (typeName) { case 'null': return true; case 'boolean': e.writeBool(datum); return true; case 'string': e.writeString(datum.toString()); return true; case 'bytes': return typeof datum === 'string' || Buffer.isBuffer(datum) || Array.isArray(datum); case 'int': if (datum >= -2147483648 && datum <= 2147483647) { e.writeInt(parseInt(datum, 10)); return true; } return false; case 'long': // JavaScript cannot represent a 64-bit integer. return false; case 'float': if (typeof datum === 'number') { e.writeFloat(datum); return true; } return false; case 'double': if (typeof datum === 'number') { e.writeDouble(datum); return true; } return false; case 'array': if (!Array.isArray(datum)) { return false; } e.writeInt(datum.length); if (datum.every(function encodeArrayItem(d) { return encodeDatum(d, schema.items); })) { e.writeInt(0); return true; } return false; case 'map': keys = Object.keys(datum); e.writeInt(keys.length); if (keys.every(function encodeMapKeyValue(key) { return encodeDatum(key, schema.keys) && encodeDatum(datum[key], schema.values); })) { e.writeInt(0); return true; } return false; case 'enum': if (typeof datum === 'number') { type = parseInt(datum, 10); } else { type = schema.symbols.indexOf(datum); } if (type < 0 || type >= schema.symbols.length) { return false; } e.writeInt(type); return true; case 'record': return schema.fields.every(function encodeRecordField(field) { return encodeDatum(datum[field.name], field.type); }); case 'fixed': // Here's our little extension--we take Arrays, non-ascii strings. if (Array.isArray(datum) || typeof datum === 'string') { datum = new Buffer(datum); } if (Buffer.isBuffer(datum) && datum.length == schema.size) { e.writeBytes(datum); return true; } return false; default: return encodeDatum(datum, symbols[typeName]); } } return encodeDatum(rootDatum, this._schemaData(schemaName)) ? e.result() : null; };