UNPKG

swagger-router

Version:

An efficient swagger 2 based router with support for multiple APIs. For use in RESTBase.

218 lines (206 loc) 8.22 kB
'use strict'; const utils = require('./utils'); /** * Represents a URI object which can optionally contain and * bind optional variables encountered in the URI string * * @param {string|URI|Array} uri the URI path or object to create a new URI from * @param {Object} params the values for variables encountered in the URI path (optional) * @param {boolean} asPattern Whether to parse the URI as a pattern (optional) * @return {URI} URI object. Public properties: * - `params` {object} mutable. Parameter object. * - `path` {array} immutable. */ class URI { constructor(uri, params, asPattern) { // Initialise all fields to make an object monomorphic this.params = params || {}; this.protoHost = null; this.path = null; this._pathMetadata = {}; if (typeof uri === 'string') { const protoHostMatch = /^[^/]+:(?:\/\/)?[^/]+/.exec(uri); if (protoHostMatch) { this.protoHost = protoHostMatch[0]; uri = uri.substring(this.protoHost.length); } this.path = utils.parsePath(uri, asPattern); } else if (Array.isArray(uri)) { if (!asPattern) { // Ensure that all path segments are strings for (let i = 0; i < uri.length; i++) { uri[i] = `${uri[i]}`; } } this.path = uri; } else if (uri && uri.constructor === URI) { this.protoHost = uri.protoHost; // this.path is considered immutable, so can be shared with other URI // instances this.path = uri.path; } else if (uri !== '') { throw new Error(`Invalid path passed into URI constructor: ${uri}`); } } /** * Builds and returns the full, bounded string path for this URI object * @return {string} the complete path of this URI object * @param {Object} options { * format {string} Either 'simplePattern' or 'fullPattern'. [optional] * params {object} parameters to use during serialization * } * @return {string} URI path */ toString(options) { // b/c if (!options || options.constructor === String) { options = { format: options }; } const params = options.params || this.params; let uriStr = this.protoHost || ''; for (let i = 0; i < this.path.length; i++) { const segment = this.path[i]; if (segment && segment.constructor === Object) { let segmentValue = params[segment.name]; if (segmentValue === undefined) { segmentValue = segment.pattern; } if (segmentValue !== undefined) { if (!options.format || options.format === 'simplePattern' || !segment.name) { if (segment.modifier === '+') { uriStr += `/${segmentValue}`; } else { // Normal mode uriStr += `/${encodeURIComponent(segmentValue)}`; } } else { uriStr += `/{${segment.modifier || ''}` + `${encodeURIComponent(segment.name)}:` + `${encodeURIComponent(segmentValue)}}`; } } else if (options.format && !segment.modifier) { uriStr += `/{${encodeURIComponent(segment.name)}}`; } else if (options.format) { uriStr += `{${segment.modifier || ''}${encodeURIComponent(segment.name)}}`; } else { if (segment.modifier === '+') { // Add trailing slash uriStr += '/'; } // Omit optional segment & return return uriStr; } } else if (this._pathMetadata && this._pathMetadata[i] && this._pathMetadata[i] === '+') { uriStr += `/${utils.encodeReserved(segment)}`; } else { uriStr += `/${encodeURIComponent(segment)}`; } } return uriStr; } /** * Expand all parameters in the URI and return a new URI. * @param {Object} params (optional) Parameters to use for expansion. Uses * URI-assigned parameters if not supplied. * @return {URI} */ expand(params) { if (!params) { params = this.params; } let res = []; const pathMetadata = {}; for (let i = 0; i < this.path.length; i++) { const segment = this.path[i]; if (segment && segment.constructor === Object) { let segmentValue = params[segment.name]; if (segmentValue === undefined) { segmentValue = segment.pattern; if (segmentValue === undefined) { if (segment.modifier) { // Skip over this optional segment. continue; } else { segmentValue = ''; } } } if (segment.modifier === '+') { // Res will become a path array, so we must split path elements const oldResLen = res.length; res = res.concat((`${segmentValue}`).split('/')); // Set up metadata for all path elements under {+} template for (let j = oldResLen; j < res.length; j++) { pathMetadata[j] = '+'; } } else { res.push(`${segmentValue}`); } } else { res.push(segment); } } const uri = new URI(res); uri.protoHost = this.protoHost; // FIXME: handle this in the constructor! uri._pathMetadata = pathMetadata; return uri; } /** * Checks if the URI starts with the given path prefix * @param {string|URI} pathOrURI the prefix path to check for * @return {boolean} whether this URI starts with the given prefix path */ startsWith(pathOrURI) { let uri; if (!pathOrURI) { return true; } if (pathOrURI.constructor === URI) { uri = pathOrURI; } else { uri = new URI(pathOrURI); } // if our URI is shorter than the one we are // comparing to, it doesn't start with that prefix if (this.path.length < uri.path.length) { return false; } // check each component for (let idx = 0; idx < uri.path.length; idx++) { const mySeg = this.path[idx]; const otherSeg = uri.path[idx]; if (mySeg.constructor === Object && otherSeg.constructor === Object) { // both path are named variables // nothing to do continue; } else if (mySeg.constructor === Object) { // we have a named variable, but there is a string // given in the prefix if (mySeg.pattern && mySeg.pattern !== otherSeg) { // they differ return false; } } else if (otherSeg.constructor === Object) { // we have a fixed string, but a variable has been // given in the prefix - nothing to do continue; } else if (mySeg !== otherSeg) { // both are strings, but they differ return false; } } // ok, no differences found return true; } // For util.inspect, console.log & co inspect() { // Quote the string return JSON.stringify(this.toString()); } } // For JSON.stringify URI.prototype.toJSON = URI.prototype.toString; module.exports = URI;