UNPKG

halfred

Version:

parses JSON HAL resources (Hypertext Application Language)

205 lines (177 loc) 5.63 kB
'use strict'; var Resource = require('./resource') , Stack = require('./immutable_stack'); var linkSpec = { href: { required: true, defaultValue: null }, templated: { required: false, defaultValue: false }, type: { required: false, defaultValue: null }, deprecation: { required: false, defaultValue: null }, name: { required: false, defaultValue: null }, profile: { required: false, defaultValue: null }, title: { required: false, defaultValue: null }, hreflang: { required: false, defaultValue: null } }; var logger; function Parser(_logger) { logger = _logger; } Parser.prototype.parse = function parse(unparsed, validationFlag) { var validation = validationFlag ? [] : null; return _parse(unparsed, validation, new Stack()); }; function _parse(unparsed, validation, path) { if (unparsed == null) { return unparsed; } var allLinkArrays = parseLinks(unparsed._links, validation, path.push('_links')); var curies = parseCuries(allLinkArrays); var allEmbeddedArrays = parseEmbeddedResourcess(unparsed._embedded, validation, path.push('_embedded')); var resource = new Resource(allLinkArrays, curies, allEmbeddedArrays, validation); copyNonHalProperties(unparsed, resource); resource._original = unparsed; return resource; } function parseLinks(links, validation, path) { links = parseHalProperty(links, parseLink, validation, path); if (links == null || links.self == null) { // No links at all? Then it implictly misses the self link which it SHOULD // have according to spec reportValidationIssue('Resource does not have a self link', validation, path); } return links; } function parseCuries(linkArrays) { if (linkArrays) { return linkArrays.curies; } else { return []; } } function parseEmbeddedResourcess(original, parentValidation, path) { var embedded = parseHalProperty(original, identity, parentValidation, path); if (embedded == null) { return embedded; } Object.keys(embedded).forEach(function(key) { embedded[key] = embedded[key].map(function(embeddedElement) { var childValidation = parentValidation != null ? [] : null; var embeddedResource = _parse(embeddedElement, childValidation, path.push(key)); embeddedResource._original = embeddedElement; return embeddedResource; }); }); return embedded; } /* * Copy over non-hal properties (everything that is not _links or _embedded) * to the parsed resource. */ function copyNonHalProperties(unparsed, resource) { Object.keys(unparsed).forEach(function(key) { if (key !== '_links' && key !== '_embedded') { resource[key] = unparsed[key]; } }); } /* * Processes one of the two main hal properties, that is _links or _embedded. * Each sub-property is turned into a single element array if it isn't already * an array. processingFunction is applied to each array element. */ function parseHalProperty(property, processingFunction, validation, path) { if (property == null) { return property; } // create a shallow copy of the _links/_embedded object var copy = {}; // normalize each link/each embedded object and put it into our copy Object.keys(property).forEach(function(key) { copy[key] = arrayfy(key, property[key], processingFunction, validation, path); }); return copy; } function arrayfy(key, object, fn, validation, path) { if (isArray(object)) { return object.map(function(element) { return fn(key, element, validation, path); }); } else { return [fn(key, object, validation, path)]; } } function parseLink(linkKey, link, validation, path) { if (!isObject(link)) { throw new Error('Link object is not an actual object: ' + link + ' [' + typeof link + ']'); } // create a shallow copy of the link object var copy = shallowCopy(link); // add missing properties mandated by spec and do generic validation Object.keys(linkSpec).forEach(function(key) { if (copy[key] == null) { if (linkSpec[key].required) { reportValidationIssue('Link misses required property ' + key + '.', validation, path.push(linkKey)); } if (linkSpec[key].defaultValue != null) { copy[key] = linkSpec[key].defaultValue; } } }); // check more inter-property relations mandated by spec if (copy.deprecation) { logger.warn('Link ' + pathToString(path.push(linkKey)) + ' is deprecated, see ' + copy.deprecation); } if (copy.templated !== true && copy.templated !== false) { copy.templated = false; } if (!validation) { return copy; } if (copy.href && copy.href.indexOf('{') >= 0 && !copy.templated) { reportValidationIssue('Link seems to be an URI template ' + 'but its "templated" property is not set to true.', validation, path.push(linkKey)); } return copy; } function isArray(o) { return Object.prototype.toString.call(o) === '[object Array]'; } function isObject(o) { return typeof o === 'object'; } function identity(key, object) { return object; } function reportValidationIssue(message, validation, path) { if (validation) { validation.push({ path: pathToString(path), message: message }); } } function shallowCopy(source) { var copy = {}; Object.keys(source).forEach(function(key) { copy[key] = source[key]; }); return copy; } function pathToString(path) { var s = '$.'; for (var i = 0; i < path.array().length; i++) { s += path.array()[i] + '.'; } s = s.substring(0, s.length - 1); return s; } module.exports = Parser;