/* * Copyright (c) 2020 Digital Bazaar, Inc. All rights reserved. */ 'use strict'; const JsonLdError = require('./JsonLdError'); const { isArray: _isArray } = require('./types'); const { asArray: _asArray } = require('./util'); const api = {}; module.exports = api; // default handler, store as null or an array // exposed to allow fast external pre-handleEvent() checks api.defaultEventHandler = null; /** * Setup event handler. * * Return an array event handler constructed from an optional safe mode * handler, an optional options event handler, and an optional default handler. * * @param {object} options - processing options * {function|object|array} [eventHandler] - an event handler. * * @return an array event handler. */ api.setupEventHandler = ({options = {}}) => { // build in priority order const eventHandler = [].concat( options.safe ? api.safeEventHandler : [], options.eventHandler ? _asArray(options.eventHandler) : [], api.defaultEventHandler ? api.defaultEventHandler : [] ); // null if no handlers return eventHandler.length === 0 ? null : eventHandler; }; /** * Handle an event. * * Top level APIs have a common 'eventHandler' option. This option can be a * function, array of functions, object mapping event.code to functions (with a * default to call next()), or any combination of such handlers. Handlers will * be called with an object with an 'event' entry and a 'next' function. Custom * handlers should process the event as appropriate. The 'next()' function * should be called to let the next handler process the event. * * NOTE: Only call this function if options.eventHandler is set and is an * array of hanlers. This is an optimization. Callers are expected to check * for an event handler before constructing events and calling this function. * * @param {object} event - event structure: * {string} code - event code * {string} level - severity level, one of: ['warning'] * {string} message - human readable message * {object} details - event specific details * @param {object} options - processing options * {array} eventHandler - an event handler array. */ api.handleEvent = ({ event, options }) => { _handle({event, handlers: options.eventHandler}); }; function _handle({event, handlers}) { let doNext = true; for(let i = 0; doNext && i < handlers.length; ++i) { doNext = false; const handler = handlers[i]; if(_isArray(handler)) { doNext = _handle({event, handlers: handler}); } else if(typeof handler === 'function') { handler({event, next: () => { doNext = true; }}); } else if(typeof handler === 'object') { if(event.code in handler) { handler[event.code]({event, next: () => { doNext = true; }}); } else { doNext = true; } } else { throw new JsonLdError( 'Invalid event handler.', 'jsonld.InvalidEventHandler', {event}); } } return doNext; } const _notSafeEventCodes = new Set([ 'empty object', 'free-floating scalar', 'invalid @language value', 'invalid property', // NOTE: spec edge case 'null @id value', 'null @value value', 'object with only @id', 'object with only @language', 'object with only @list', 'object with only @value', 'relative @id reference', 'relative @type reference', 'relative @vocab reference', 'reserved @id value', 'reserved @reverse value', 'reserved term', // toRDF 'blank node predicate', 'relative graph reference', 'relative object reference', 'relative predicate reference', 'relative subject reference', // toRDF / fromRDF 'rdfDirection not set' ]); // safe handler that rejects unsafe warning conditions api.safeEventHandler = function safeEventHandler({event, next}) { // fail on all unsafe warnings if(event.level === 'warning' && _notSafeEventCodes.has(event.code)) { throw new JsonLdError( 'Safe mode validation error.', 'jsonld.ValidationError', {event} ); } next(); }; // logs all events and continues api.logEventHandler = function logEventHandler({event, next}) { console.log(`EVENT: ${event.message}`, {event}); next(); }; // log 'warning' level events api.logWarningEventHandler = function logWarningEventHandler({event, next}) { if(event.level === 'warning') { console.warn(`WARNING: ${event.message}`, {event}); } next(); }; // fallback to throw errors for any unhandled events api.unhandledEventHandler = function unhandledEventHandler({event}) { throw new JsonLdError( 'No handler for event.', 'jsonld.UnhandledEvent', {event} ); }; /** * Set default event handler. * * By default, all event are unhandled. It is recommended to pass in an * eventHandler into each call. However, this call allows using a default * eventHandler when one is not otherwise provided. * * @param {object} options - default handler options: * {function|object|array} eventHandler - a default event handler. * falsey to unset. */ api.setDefaultEventHandler = function({eventHandler} = {}) { api.defaultEventHandler = eventHandler ? _asArray(eventHandler) : null; };