UNPKG

pimatic

Version:

A home automation server and framework for the Raspberry PI running on node.js

265 lines (210 loc) 8.02 kB
// // Register schemas and retrieve previously-registered schemas. // var uri = require('./uri.js') , url = require('url') ; // Schemas for which we have our own bundled copy. These will be // registered automatically. var EMBEDDED_SCHEMAS = { 'http://json-schema.org/draft-04/schema#': require('./suites/draft-04/json-schema-draft-v4.json') }; // ****************************************************************** // Constructor // ****************************************************************** var SchemaRegistry = function() { this._schemas = {}; this._missingSchemas = {}; // register embedded schemas var keys = Object.keys(EMBEDDED_SCHEMAS); keys.forEach(function(key) { this.register(EMBEDDED_SCHEMAS[key]); }, this); }; // ****************************************************************** // [static] Decode an escaped JSON pointer // ****************************************************************** SchemaRegistry._decodeJsonPointer = function(pointer) { var result = pointer.replace(/\~1/g, '/'); return result.replace(/\~0/g, '~'); }; // ****************************************************************** // [static] Given a schema and a JSON pointer, return the // corresponding sub-schema referenced by the JSON pointer. // ****************************************************************** SchemaRegistry._resolveJsonPointer = function(schema, jp) { if (jp === '#') { return schema; } if (jp.slice(0, 2) !== '#/') { // not a JSON pointer fragment // (may be a valid id ref, but that’s not our problem here) return null; } var path = jp.slice(2).split('/'); var currentSchema = schema; while (path.length) { var element = SchemaRegistry._decodeJsonPointer(path.shift()); if (!Object.prototype.hasOwnProperty.call(currentSchema, element)) { return null; } currentSchema = currentSchema[element]; } return currentSchema; }; // ****************************************************************** // [static] Normalize schema ids so they can be looked up. // ****************************************************************** SchemaRegistry._normalizeId = function(id) { // for internal use we add the '#' (empty fragment) if missing if (id.indexOf('#') === -1) { return id + '#'; } var uriObj = uri.parse(id); if (uriObj.kind === 'url') { id = url.resolve(id, id); } return id; }; // ****************************************************************** // Return boolean indicating whether the specified schema id has // previously been registered. // ****************************************************************** SchemaRegistry.prototype.isRegistered = function(id) { if (!id) { return false; } id = SchemaRegistry._normalizeId(id); if (this._schemas.hasOwnProperty(id)) { return true; } var uriObj = uri.parse(id); return this._schemas.hasOwnProperty(uriObj.baseUri); }; // ****************************************************************** // [static] Helper to descend into an object, recursively gathering // all $refs values from the given object and its sub-objects. // ****************************************************************** SchemaRegistry._gatherRefs = function(obj) { var result = []; var currentObj = obj; var subObjects = []; do { if (Object.prototype.hasOwnProperty.call(currentObj, '$ref')) { result.push(currentObj.$ref); } var keys = Object.keys(currentObj); for (var index = 0, len = keys.length; index !== len; ++index) { var prop = currentObj[keys[index]]; if (typeof prop === 'object') { subObjects.push(prop); } } currentObj = subObjects.pop(); } while(currentObj); return result; }; // ****************************************************************** // [static] Helper to descend into an object, recursively gathering // all sub-objects that contain an id property. // // Returns an array where each element is an array of the form: // // [ schema, resolutionScope ] // ****************************************************************** SchemaRegistry._getSubObjectsHavingIds = function(obj, resolutionScope) { var result = []; resolutionScope = resolutionScope || '#'; var currentObj = obj; var subObjects = []; var nextItem; do { if (currentObj !== null && currentObj !== undefined) { if (Object.prototype.hasOwnProperty.call(currentObj, 'id') && typeof currentObj.id === 'string') { result.push([currentObj, resolutionScope]); resolutionScope = uri.resolve(resolutionScope, currentObj.id); } var keys = Object.keys(currentObj); for (var index = 0, len = keys.length; index !== len; ++index) { var prop = currentObj[keys[index]]; if (typeof prop === 'object') { subObjects.push([prop, resolutionScope + '/' + keys[index]]); } } } nextItem = subObjects.pop(); if (nextItem) { currentObj = nextItem[0]; resolutionScope = nextItem[1]; } } while(nextItem); return result; }; // ****************************************************************** // Return currently-unregistered schemas $referenced by the given // schema. // ****************************************************************** SchemaRegistry.prototype._missingRefsForSchema = function(schema) { var allRefs = SchemaRegistry._gatherRefs(schema); var missingRefs = []; allRefs.forEach(function(ref) { if (ref[0] !== '#') { var uriObj = uri.parse(ref); if (!this.isRegistered(uriObj.baseUri)) { missingRefs.push(uriObj.baseUri); } } }, this); return missingRefs; }; // ****************************************************************** // Register a schema (internal implementation) // ****************************************************************** SchemaRegistry.prototype._registerImpl = function(schema, id, _resolutionScope) { // sanity check if (typeof schema !== 'object' || Array.isArray(schema)) { return []; } if (id) { var resolvedId = uri.resolve(_resolutionScope || id, id); if (this.isRegistered(resolvedId)) { return; } var uriObj = uri.parse(resolvedId); if (!uriObj.baseUri) { return; } this._schemas[resolvedId] = schema; } }; // ****************************************************************** // Register a schema (public interface) // ****************************************************************** SchemaRegistry.prototype.register = function(schema, id) { id = id || schema.id; this._registerImpl(schema, id); // register any id'd sub-objects var toRegister = SchemaRegistry._getSubObjectsHavingIds(schema, id); toRegister.forEach(function(item) { this._registerImpl(item[0], item[0].id, item[1]); }, this); // save any missing refs to support the getMissingSchemas method var missing = this._missingRefsForSchema(schema); missing.forEach(function(item) { this._missingSchemas[item] = true; }, this); return missing; }; // ****************************************************************** // Get an array of $refs for which we don’t have a schema yet. // ****************************************************************** SchemaRegistry.prototype.getMissingSchemas = function() { var result = Object.keys(this._missingSchemas); result = result.filter(function(item) { return !this.isRegistered(item); }, this); return result; }; // ****************************************************************** // Retrieve a previously-registered schema. // ****************************************************************** SchemaRegistry.prototype.get = function(id) { id = SchemaRegistry._normalizeId(id); if (this.isRegistered(id)) { return this._schemas[id]; } var uriObj = uri.parse(id); if (uriObj.fragment.slice(0, 2) === '#/') { return SchemaRegistry._resolveJsonPointer( this._schemas[uriObj.baseUri + '#'], uriObj.fragment ); } }; module.exports = SchemaRegistry;