definitions = function (settings) {
this.settings = settings;
}n/a
paths = function (settings) {
this.settings = settings;
this.definitions = new Definitions(settings);
this.properties = new Properties(settings, {}, {});
this.responses = new Responses(settings, {}, {});
this.defaults = {
responses: {}
};
this.schema = Joi.object({
tags: Joi.array().items(Joi.string()),
summary: Joi.string(),
description: Joi.string(),
externalDocs: Joi.object({
description: Joi.string(),
url: Joi.string().uri()
}),
operationId: Joi.string(),
consumes: Joi.array().items(Joi.string()),
produces: Joi.array().items(Joi.string()),
parameters: Joi.array().items(Joi.object()),
responses: Joi.object().required(),
schemes: Joi.array().items(Joi.string().valid(['http', 'https', 'ws', 'wss'])),
deprecated: Joi.boolean(),
security: Joi.array().items(Joi.object())
});
}...
out.info = Info.build(settings);
out.tags = Tags.build(settings);
let routes = connection.table();
routes = Filter.byTags(['api'], routes);
Sort.paths(settings.sortPaths, routes);
// filter routes displayed based on tags passed in query string
if (request.query.tags) {
let filterTags = request.query.tags.split(',');
routes = Filter.byTags(filterTags, routes);
}
...properties = function (settings, definitionCollection, altDefinitionCollection, definitionCache ) {
this.settings = settings;
this.definitionCollection = definitionCollection;
this.altDefinitionCollection = altDefinitionCollection;
// definitionCache has to be an array of two WeakMaps
this.definitionCache = definitionCache;
this.definitions = new Definitions(settings);
// swagger type can be 'string', 'number', 'integer', 'boolean', 'array' or 'file'
this.simpleTypePropertyMap = {
'boolean': { 'type': 'boolean' },
'binary': { 'type': 'string', 'format': 'binary' },
'date': { 'type': 'string', 'format': 'date' },
'number': { 'type': 'number' },
'string': { 'type': 'string' }
};
this.complexTypePropertyMap = {
'any': { 'type': 'string' },
'array': { 'type': 'array' },
'func': { 'type': 'string' },
'object': { 'type': 'object' },
'alternatives': { 'type': 'alternatives' }
};
// merge
this.propertyMap = Hoek.applyToDefaults(this.simpleTypePropertyMap, this.complexTypePropertyMap);
//this.allowedProps = ['$ref','format','title','description','default','multipleOf','maximum','exclusiveMaximum','minimum','
exclusiveMinimum','maxLength','minLength','pattern','maxItems','minItems','uniqueItems','maxProperties','minProperties','required
','enum','type','items','allOf','properties','additionalProperties','discriminator','readOnly','xml','externalDocs','example'];
// add none swagger property needed to flag touched state of required property
//this.allowedProps.push('optional');
}n/a
register = function (plugin, options, next) {
let settings = Hoek.applyToDefaults(Defaults, options, true);
const publicDirPath = Path.resolve(__dirname, '..', 'public');
const swaggerDirPath = Path.join(publicDirPath, 'swaggerui');
settings.log = (tags, data) => {
tags.unshift('hapi-swagger');
if (settings.debug) {
plugin.log(tags, data);
}
};
settings.log(['info'], 'Started');
// add server method for caching
if (settings.cache) {
// set default
settings.cache.segment = 'hapi-swagger';
if (!settings.cache.generateTimeout) {
settings.cache.generateTimeout = 30 * 1000;
}
plugin.method('getSwaggerJSON', Builder.getSwaggerJSON, {
cache: settings.cache,
generateKey: () => {
return 'hapi-swagger';
}
});
}
// add routing swagger json
plugin.route([{
method: 'GET',
path: settings.jsonPath,
config: {
auth: settings.auth,
cors: settings.cors,
handler: (request, reply) => {
Joi.assert(settings, schema);
if (settings.cache) {
/*eslint no-unused-vars:0 */
plugin.methods.getSwaggerJSON(settings, request, (err, json, cached, report) => {
/* $lab:coverage:off$ */
if (err) {
reply(err);
/* $lab:coverage:on$ */
} else {
//console.log(JSON.stringify(report));
const lastModified = cached ? new Date(cached.stored) : new Date();
reply(json).header('last-modified', lastModified.toUTCString());
}
});
} else {
Joi.assert(settings, schema);
Builder.getSwaggerJSON(settings, request, (err, json) => {
reply(json);
});
}
},
plugins: {
'hapi-swagger': false
}
}
}]);
// only add 'inert' and 'vision' based routes if needed
if (settings.documentationPage === true || settings.swaggerUI === true) {
// make sure we have other plug-in dependencies
plugin.dependency(['inert', 'vision'], (pluginWithDependencies, nextWithDependencies) => {
// add routing for swaggerui static assets /swaggerui/
pluginWithDependencies.views({
engines: {
html: {
module: require('handlebars')
}
},
path: swaggerDirPath
});
// add documentation page
if (settings.documentationPage === true) {
pluginWithDependencies.route([{
method: 'GET',
path: settings.documentationPath,
config: {
auth: settings.auth
},
handler: (request, reply) => {
reply.view('index.html', {});
}
}]);
}
// add swagger UI if asked for or need by documentation page
if (settings.documentationPage === true || settings.swaggerUI === true) {
pluginWithDependencies.route([{
method: 'GET',
path: settings.swaggerUIPath + '{path*}',
config: {
auth: settings.auth
},
handler: {
directory: {
path: swaggerDirPath + Path.sep,
listing: true,
index: false
}
}
}, {
method: 'GET',
path: settings.swaggerUIPath + ' ......
const options = {
info: {
'title': 'Test API Documentation',
'version': Pack.version,
}
};
server.register([
Inert,
Vision,
{
'register': HapiSwagger,
'options': options
}], (err) => {
server.start( (err) => {
...responses = function (settings, definitionCollection, altDefinitionCollection, definitionCache) {
this.settings = settings;
this.definitionCollection = definitionCollection;
this.altDefinitionCollection = altDefinitionCollection;
this.definitions = new Definitions(settings);
this.properties = new Properties(settings, this.definitionCollection, this.altDefinitionCollection, definitionCache);
}n/a
dereference = function (schema, callback) {
JSONDeRef.dereference(schema, function (err, json) {
if (!err) {
delete json.definitions;
delete json['x-alt-definitions'];
} else {
err = { 'error': 'fail to dereference schema' };
}
callback(err, json);
});
}...
out = internals.removeNoneSchemaOptions(out);
if (settings.debug) {
Validate.log(out, settings.log);
}
if (settings.deReference === true) {
builder.dereference(out, callback);
} else {
callback(null, out);
}
};
/**
...getSwaggerJSON = function (settings, request, callback) {
// remove items that cannot be changed by user
delete settings.swagger;
let connection = request.connection;
let namedConnection = null;
if (settings.connectionLabel) {
connection = namedConnection = request.server.select(settings.connectionLabel).connections[0];
if (request.server.select(settings.connectionLabel).connections.length === 1) {
request.server.log(['error'], 'connectionLabel should only define one connection to document');
}
}
// collect root information
builder.default.host = internals.getHost(request, namedConnection);
builder.default.schemes = [internals.getSchema(request, connection)];
settings = Hoek.applyToDefaults(builder.default, settings);
if (settings.basePath !== '/') {
settings.basePath = Utilities.removeTrailingSlash(settings.basePath);
}
let out = internals.removeNoneSchemaOptions(settings);
Joi.assert(out, builder.schema);
out.info = Info.build(settings);
out.tags = Tags.build(settings);
let routes = connection.table();
routes = Filter.byTags(['api'], routes);
Sort.paths(settings.sortPaths, routes);
// filter routes displayed based on tags passed in query string
if (request.query.tags) {
let filterTags = request.query.tags.split(',');
routes = Filter.byTags(filterTags, routes);
}
// append group property - by path
Group.appendGroupByPath(settings.pathPrefixSize, settings.basePath, routes, settings.pathReplacements);
let paths = new Paths(settings);
let pathData = paths.build(routes);
out.paths = pathData.paths;
out.definitions = pathData.definitions;
if (Utilities.hasProperties(pathData['x-alt-definitions'])) {
out['x-alt-definitions'] = pathData['x-alt-definitions'];
}
out = internals.removeNoneSchemaOptions(out);
if (settings.debug) {
Validate.log(out, settings.log);
}
if (settings.deReference === true) {
builder.dereference(out, callback);
} else {
callback(null, out);
}
}n/a
definitions = function (settings) {
this.settings = settings;
}n/a
append = function (definitionName, definition, currentCollection, settings) {
let out = null;
definition = internals.formatProperty(definition);
// remove required if its not an array
//if (definition.required && !Array.isArray(definition.required)) {
// delete definition.required;
//}
// remove unneeded properties
delete definition.name;
// find existing definition by this definitionName
let foundDefinition = currentCollection[definitionName];
if (foundDefinition) {
// deep compare objects
if (Hoek.deepEqual(foundDefinition, definition)) {
// return existing definitionName if existing object is exactly the same
out = definitionName;
} else {
// create new definition
out = internals.append(null, definition, currentCollection, settings);
}
} else {
// create new definition
out = internals.append(definitionName, definition, currentCollection, settings);
}
return out;
}...
if (foundDefinition) {
// deep compare objects
if (Hoek.deepEqual(foundDefinition, definition)) {
// return existing definitionName if existing object is exactly the same
out = definitionName;
} else {
// create new definition
out = internals.append(null, definition, currentCollection, settings);
}
} else {
// create new definition
out = internals.append(definitionName, definition, currentCollection, settings);
}
return out;
...byTags = function (tags, routes) {
let tag;
return routes.filter((route) => {
let exit;
for (let i = 0; i < tags.length; i++) {
switch (tags[i].substring(0, 1)) {
case '-': // exclude tags that match this case
tag = tags[i].substring(1, tags[i].length);
if (Hoek.intersect(route.settings.tags, [tag]).length > 0) {
exit = true;
}
break;
case '+': // (+) filter out tagged paths that do not have this tag!
tag = tags[i].substring(1, tags[i].length);
if (Hoek.intersect(route.settings.tags, [tag]).length === 0) {
exit = true;
}
break;
}
}
// if we have reason to exit, then do so!
if (exit === true) {
return false;
}
// default behavior for tags is additive
if (Hoek.intersect(route.settings.tags, tags).length > 0) {
return true;
}
// fallback or no tag defined
return false;
});
}...
out.info = Info.build(settings);
out.tags = Tags.build(settings);
let routes = connection.table();
routes = Filter.byTags(['api'], routes);
Sort.paths(settings.sortPaths, routes);
// filter routes displayed based on tags passed in query string
if (request.query.tags) {
let filterTags = request.query.tags.split(',');
routes = Filter.byTags(filterTags, routes);
}
...appendGroupByPath = function (pathPrefixSize, basePath, routes, pathReplacements) {
let out = [];
routes.forEach((route) => {
let prefix = group.getNameByPath(pathPrefixSize, basePath, route.path, pathReplacements);
// append tag reference to route
route.group = [prefix];
if (out.indexOf(prefix) === -1) {
out.push(prefix);
}
});
return out;
}...
// filter routes displayed based on tags passed in query string
if (request.query.tags) {
let filterTags = request.query.tags.split(',');
routes = Filter.byTags(filterTags, routes);
}
// append group property - by path
Group.appendGroupByPath(settings.pathPrefixSize, settings.basePath, routes, settings.
pathReplacements);
let paths = new Paths(settings);
let pathData = paths.build(routes);
out.paths = pathData.paths;
out.definitions = pathData.definitions;
if (Utilities.hasProperties(pathData['x-alt-definitions'])) {
out['x-alt-definitions'] = pathData['x-alt-definitions'];
...getNameByPath = function (pathPrefixSize, basePath, path, pathReplacements) {
if (pathReplacements) {
path = Utilities.replaceInPath(path, ['groups'], pathReplacements);
}
let i = 0;
let pathHead = [];
let parts = path.split('/');
while (parts.length > 0) {
let item = parts.shift();
if (item !== '') {
pathHead.push(item);
i++;
}
if (i >= pathPrefixSize) {
break;
}
}
let name = pathHead.join('/');
if (basePath !== '/' && Utilities.startsWith('/' + name, basePath)) {
name = ('/' + name).replace(basePath, '');
if (Utilities.startsWith(name, '/')) {
name = name.replace('/', '');
}
}
return name;
}...
* @return {Array}
*/
group.appendGroupByPath = function (pathPrefixSize, basePath, routes, pathReplacements) {
let out = [];
routes.forEach((route) => {
let prefix = group.getNameByPath(pathPrefixSize, basePath, route.path, pathReplacements
);
// append tag reference to route
route.group = [prefix];
if (out.indexOf(prefix) === -1) {
out.push(prefix);
}
});
...build = function (options) {
var out = (options.info) ? Hoek.applyToDefaults(info.defaults, options.info) : info.defaults;
Joi.assert(out, info.schema);
return out;
}...
if (settings.basePath !== '/') {
settings.basePath = Utilities.removeTrailingSlash(settings.basePath);
}
let out = internals.removeNoneSchemaOptions(settings);
Joi.assert(out, builder.schema);
out.info = Info.build(settings);
out.tags = Tags.build(settings);
let routes = connection.table();
routes = Filter.byTags(['api'], routes);
Sort.paths(settings.sortPaths, routes);
...fromProperties = function (schemaObj, parameterType) {
let out = [];
// if (this.allowedParameterTypes.indexOf(parameterType) === -1) {
// return out;
// }
// if its a single parameter
if (schemaObj.properties === undefined) {
// console.log('a', JSON.stringify(schemaObj) + '\n');
let item = Hoek.clone(schemaObj);
item.in = parameterType;
item.name = parameterType;
item.schema = {};
item.schema.type = item.type;
delete item.type;
// convert an object definition
if (item.$ref) {
item.schema.$ref = item.$ref;
// item.schema.type = 'object'; // should have to do this but causes validations issues
delete item.$ref;
}
// reinstate x-alternatives at parameter level
if (schemaObj['x-alternatives']) {
item['x-alternatives'] = schemaObj['x-alternatives'];
delete item.schema['x-alternatives'];
}
item = Utilities.removeProps(item, this.allowedProps);
if (!Hoek.deepEqual(item.schema, { 'type': 'object' }, { prototype: false })) {
out.push(Utilities.deleteEmptyProperties(item));
}
// if its an array of parameters
} else {
//console.log('b', JSON.stringify(schemaObj) + '\n');
// object to array
const keys = Object.keys(schemaObj.properties);
keys.forEach((element, index) => {
let key = keys[index];
let item = schemaObj.properties[key];
item.name = key;
item.in = parameterType;
// reinstate required at parameter level
if (schemaObj.required && schemaObj.required.indexOf(key) > -1) {
item.required = true;
}
if (schemaObj.optional && schemaObj.optional.indexOf(key) > -1) {
item.required = false;
}
item = Utilities.removeProps(item, this.allowedProps);
out.push(item);
});
}
return out;
}...
let outProperties;
let outParameters;
if (joiObj) {
// name, joiObj, parent, parameterType, useDefinitions, isAlt
outProperties = this.properties.parseProperty(null, joiObj, null, parameterType, useDefinitions, isAlt);
outParameters = Parameters.fromProperties(outProperties, parameterType);
}
let out = {
properties: outProperties || {},
parameters: outParameters || []
};
return out;
};
...paths = function (settings) {
this.settings = settings;
this.definitions = new Definitions(settings);
this.properties = new Properties(settings, {}, {});
this.responses = new Responses(settings, {}, {});
this.defaults = {
responses: {}
};
this.schema = Joi.object({
tags: Joi.array().items(Joi.string()),
summary: Joi.string(),
description: Joi.string(),
externalDocs: Joi.object({
description: Joi.string(),
url: Joi.string().uri()
}),
operationId: Joi.string(),
consumes: Joi.array().items(Joi.string()),
produces: Joi.array().items(Joi.string()),
parameters: Joi.array().items(Joi.object()),
responses: Joi.object().required(),
schemes: Joi.array().items(Joi.string().valid(['http', 'https', 'ws', 'wss'])),
deprecated: Joi.boolean(),
security: Joi.array().items(Joi.object())
});
}...
out.info = Info.build(settings);
out.tags = Tags.build(settings);
let routes = connection.table();
routes = Filter.byTags(['api'], routes);
Sort.paths(settings.sortPaths, routes);
// filter routes displayed based on tags passed in query string
if (request.query.tags) {
let filterTags = request.query.tags.split(',');
routes = Filter.byTags(filterTags, routes);
}
...build = function (routes) {
let self = this;
let routesData = [];
// loop each route
routes.forEach((route) => {
let routeOptions = Hoek.reach(route, 'settings.plugins.hapi-swagger') || {};
let routeData = {
path: route.path,
method: route.method.toUpperCase(),
description: route.settings.description,
notes: route.settings.notes,
tags: Hoek.reach(route, 'settings.tags'),
queryParams: Hoek.reach(route, 'settings.validate.query'),
pathParams: Hoek.reach(route, 'settings.validate.params'),
payloadParams: Hoek.reach(route, 'settings.validate.payload'),
responseSchema: Hoek.reach(route, 'settings.response.schema'),
responseStatus: Hoek.reach(route, 'settings.response.status'),
headerParams: Hoek.reach(route, 'settings.validate.headers'),
consumes: Hoek.reach(routeOptions, 'consumes') || null,
produces: Hoek.reach(routeOptions, 'produces') || null,
responses: Hoek.reach(routeOptions, 'responses') || null,
payloadType: Hoek.reach(routeOptions, 'payloadType') || null,
security: Hoek.reach(routeOptions, 'security') || null,
order: Hoek.reach(routeOptions, 'order') || null,
deprecated: Hoek.reach(routeOptions, 'deprecated') || null,
id: Hoek.reach(routeOptions, 'id') || null,
groups: route.group
};
routeData.path = Utilities.replaceInPath(routeData.path, ['endpoints'], this.settings.pathReplacements);
// user configured interface through route plugin options
if (Hoek.reach(routeOptions, 'validate.query')) {
routeData.queryParams = Utilities.toJoiObject(Hoek.reach(routeOptions, 'validate.query'));
}
if (Hoek.reach(routeOptions, 'validate.params')) {
routeData.pathParams = Utilities.toJoiObject(Hoek.reach(routeOptions, 'validate.params'));
}
if (Hoek.reach(routeOptions, 'validate.headers')) {
routeData.headerParams = Utilities.toJoiObject(Hoek.reach(routeOptions, 'validate.headers'));
}
if (Hoek.reach(routeOptions, 'validate.payload')) {
// has different structure, just pass straight through
routeData.payloadParams = Hoek.reach(routeOptions, 'validate.payload');
// if its a native javascript object convert it to JOI
if (!routeData.payloadParams.isJoi) {
routeData.payloadParams = Joi.object(routeData.payloadParams);
}
}
// swap out any custom validation function for Joi object/string
[
'queryParams',
'pathParams',
'headerParams',
'payloadParams'
].forEach(function (property) {
// swap out any custom validation function for Joi object/string
if (Utilities.isFunction(routeData[property])) {
if (property !== 'pathParams') {
self.settings.log(['vaildation', 'warning'], 'Using a Joi.function for a query, header or payload is not supported
.');
if (property === 'payloadParams') {
routeData[property] = Joi.object().label('Hidden Model');
} else {
routeData[property] = Joi.object({ 'Hidden Model': Joi.string() });
}
} else {
self.settings.log(['vaildation', 'error'], 'Using a Joi.function for a params is not supported and has been
removed.');
routeData[property] = null;
}
}
});
// hapi wildcard method support
if (routeData.method === '*') {
// OPTIONS not supported by Swagger and HEAD not support by HAPI
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
var newRoute = Hoek.clone(routeData);
newRoute.method = method.toUpperCase();
routesData.pus ......
if (settings.basePath !== '/') {
settings.basePath = Utilities.removeTrailingSlash(settings.basePath);
}
let out = internals.removeNoneSchemaOptions(settings);
Joi.assert(out, builder.schema);
out.info = Info.build(settings);
out.tags = Tags.build(settings);
let routes = connection.table();
routes = Filter.byTags(['api'], routes);
Sort.paths(settings.sortPaths, routes);
...buildRoutes = function (routes) {
let self = this;
let pathObj = {};
let swagger = {
'definitions': {},
'x-alt-definitions': {}
};
let definitionCache = [
new WeakMap(),
new WeakMap()
];
// reset properties
this.properties = new Properties(this.settings, swagger.definitions, swagger['x-alt-definitions'], definitionCache);
this.responses = new Responses(this.settings, swagger.definitions, swagger['x-alt-definitions'], definitionCache);
routes.forEach((route) => {
let method = route.method;
let path = internals.removeBasePath(route.path, this.settings.basePath, this.settings.pathReplacements);
let out = {
'summary': route.description,
'operationId': route.id || Utilities.createId(route.method, path),
'description': route.notes,
'parameters': [],
'consumes': [],
'produces': []
};
// tags in swagger are used for grouping
if (this.settings.grouping === 'tags') {
out.tags = route.tags.slice(0);
while (out.tags.indexOf('api') !== -1) {
out.tags.splice(out.tags.indexOf('api'), 1);
}
}
else {
out.tags = route.groups;
}
out.description = Array.isArray(route.notes) ? route.notes.join('<br/><br/>') : route.notes;
if (route.security) {
out.security = route.security;
}
// set from plugin options or from route options
let payloadType = internals.overload(this.settings.payloadType, route.payloadType);
// build payload either with JSON or form input
let payloadStructures = this.getDefaultStructures();
let payloadJoi = internals.getJOIObj(route, 'payloadParams');
if (payloadType.toLowerCase() === 'json') {
// set as json
payloadStructures = this.getSwaggerStructures(payloadJoi, 'body', true, false);
} else {
// set as formData
if (Utilities.hasJoiChildren(payloadJoi)) {
payloadStructures = this.getSwaggerStructures(payloadJoi, 'formData', false, false);
} else {
self.testParameterError(payloadJoi, 'payload form-urlencoded', path);
}
// add form data mimetype
out.consumes = ['application/x-www-form-urlencoded'];
}
// change form mimetype based on meta property 'swaggerType'
if (internals.hasFileType(route)) {
out.consumes = ['multipart/form-data'];
}
// add user defined over automaticlly discovered
if (this.settings.consumes || route.consumes) {
out.consumes = internals.overload(this.settings.consumes, route.consumes);
}
if (this.settings.produces || route.produces) {
out.produces = internals.overload(this.settings.produces, route.produces);
}
// set required true/false for each path params
//var pathParams = this.properties.toParameters (internals.getJOIObj(route, 'pathParams'), 'path', false);
let pathStructures = this.getDefaultStructures();
let pathJoi = internals.getJOIObj(route, 'pathParams');
if (Utilities.hasJoiChildren(pathJoi)) {
pathStructures = this.getSwaggerStructures(pathJoi, 'path', false, false);
pathStructures.parameters.forEach(function (item) {
// add required based on path pattern {prama} and {prama?}
if (item.required === undefined) {
if (path.indexOf('{' + item.name + '}') > -1) {
item.required = true;
}
if (path.indexOf('{' + item.name + '?}') > -1) {
delete item.required;
}
}
if (item.required === false) {
delete item.required;
}
if (!item.required) {
self.settings.log(['validation', 'warning'], ......
});
} else {
routesData.push(routeData);
}
});
return this.buildRoutes(routesData);
};
/**
* build the swagger path section from hapi routes data
*
* @param {Object} setting
...getDefaultStructures = function () {
return {
'properties': {},
'parameters': []
};
}...
out.security = route.security;
}
// set from plugin options or from route options
let payloadType = internals.overload(this.settings.payloadType, route.payloadType);
// build payload either with JSON or form input
let payloadStructures = this.getDefaultStructures();
let payloadJoi = internals.getJOIObj(route, 'payloadParams');
if (payloadType.toLowerCase() === 'json') {
// set as json
payloadStructures = this.getSwaggerStructures(payloadJoi, 'body', true, false);
} else {
// set as formData
if (Utilities.hasJoiChildren(payloadJoi)) {
...getSwaggerStructures = function (joiObj, parameterType, useDefinitions, isAlt) {
let outProperties;
let outParameters;
if (joiObj) {
// name, joiObj, parent, parameterType, useDefinitions, isAlt
outProperties = this.properties.parseProperty(null, joiObj, null, parameterType, useDefinitions, isAlt);
outParameters = Parameters.fromProperties(outProperties, parameterType);
}
let out = {
properties: outProperties || {},
parameters: outParameters || []
};
return out;
}...
let payloadType = internals.overload(this.settings.payloadType, route.payloadType);
// build payload either with JSON or form input
let payloadStructures = this.getDefaultStructures();
let payloadJoi = internals.getJOIObj(route, 'payloadParams');
if (payloadType.toLowerCase() === 'json') {
// set as json
payloadStructures = this.getSwaggerStructures(payloadJoi, 'body', true,
false);
} else {
// set as formData
if (Utilities.hasJoiChildren(payloadJoi)) {
payloadStructures = this.getSwaggerStructures(payloadJoi, 'formData', false, false);
} else {
self.testParameterError(payloadJoi, 'payload form-urlencoded', path);
}
...testParameterError = function (joiObj, parameterType, path) {
if (joiObj && !Utilities.hasJoiChildren(joiObj)) {
this.settings.log(['validation', 'error'], 'The ' + path + ' route ' + parameterType + ' parameter was set, but not as
a Joi.object() with child properties');
}
}...
// set as json
payloadStructures = this.getSwaggerStructures(payloadJoi, 'body', true, false);
} else {
// set as formData
if (Utilities.hasJoiChildren(payloadJoi)) {
payloadStructures = this.getSwaggerStructures(payloadJoi, 'formData', false, false);
} else {
self.testParameterError(payloadJoi, 'payload form-urlencoded', path
);
}
// add form data mimetype
out.consumes = ['application/x-www-form-urlencoded'];
}
// change form mimetype based on meta property 'swaggerType'
...properties = function (settings, definitionCollection, altDefinitionCollection, definitionCache ) {
this.settings = settings;
this.definitionCollection = definitionCollection;
this.altDefinitionCollection = altDefinitionCollection;
// definitionCache has to be an array of two WeakMaps
this.definitionCache = definitionCache;
this.definitions = new Definitions(settings);
// swagger type can be 'string', 'number', 'integer', 'boolean', 'array' or 'file'
this.simpleTypePropertyMap = {
'boolean': { 'type': 'boolean' },
'binary': { 'type': 'string', 'format': 'binary' },
'date': { 'type': 'string', 'format': 'date' },
'number': { 'type': 'number' },
'string': { 'type': 'string' }
};
this.complexTypePropertyMap = {
'any': { 'type': 'string' },
'array': { 'type': 'array' },
'func': { 'type': 'string' },
'object': { 'type': 'object' },
'alternatives': { 'type': 'alternatives' }
};
// merge
this.propertyMap = Hoek.applyToDefaults(this.simpleTypePropertyMap, this.complexTypePropertyMap);
//this.allowedProps = ['$ref','format','title','description','default','multipleOf','maximum','exclusiveMaximum','minimum','
exclusiveMinimum','maxLength','minLength','pattern','maxItems','minItems','uniqueItems','maxProperties','minProperties','required
','enum','type','items','allOf','properties','additionalProperties','discriminator','readOnly','xml','externalDocs','example'];
// add none swagger property needed to flag touched state of required property
//this.allowedProps.push('optional');
}n/a
getDefinitionCollection = function (isAlt) {
return (isAlt === true) ? this.altDefinitionCollection : this.definitionCollection;
}...
// add object child properties
if (property.type === 'object') {
if (Utilities.hasJoiChildren(joiObj)) {
property = this.parseObject(property, joiObj, name, parameterType, useDefinitions, isAlt);
if (useDefinitions === true) {
let refName = this.definitions.append(name, property, this.getDefinitionCollection
span>(isAlt), this.settings);
property = {
'$ref': this.getDefinitionRef(isAlt) + refName,
'type': 'object'
};
}
} else {
...getDefinitionRef = function (isAlt) {
return (isAlt === true) ? '#/x-alt-definitions/' : '#/definitions/';
}...
if (property.type === 'object') {
if (Utilities.hasJoiChildren(joiObj)) {
property = this.parseObject(property, joiObj, name, parameterType, useDefinitions, isAlt);
if (useDefinitions === true) {
let refName = this.definitions.append(name, property, this.getDefinitionCollection(isAlt), this.settings);
property = {
'$ref': this.getDefinitionRef(isAlt) + refName,
'type': 'object'
};
}
} else {
// default empty object
property.properties = {};
...parseAlternatives = function (property, joiObj, name, parameterType, useDefinitions) {
// convert .try() alternatives structures
if (Hoek.reach(joiObj, '_inner.matches.0.schema')) {
// add first into definitionCollection
let child = joiObj._inner.matches[0].schema;
let childName = Utilities.geJoiLabel(joiObj);
//name, joiObj, parent, parameterType, useDefinitions, isAlt
property = this.parseProperty(childName, child, property, parameterType, useDefinitions, false);
// create the alternatives without appending to the definitionCollection
if (this.settings.xProperties === true) {
let altArray = joiObj._inner.matches.map((obj) => {
let altName = (Utilities.geJoiLabel(obj.schema) || name);
//name, joiObj, parent, parameterType, useDefinitions, isAlt
return this.parseProperty(altName, obj.schema, property, parameterType, useDefinitions, true);
});
property['x-alternatives'] = Hoek.clone(altArray);
}
}
// convert .when() alternatives structures
else {
// add first into definitionCollection
let child = joiObj._inner.matches[0].then;
let childName = (Utilities.geJoiLabel(child) || name);
//name, joiObj, parent, parameterType, useDefinitions, isAlt
property = this.parseProperty(childName, child, property, parameterType, useDefinitions, false);
// create the alternatives without appending to the definitionCollection
if (this.settings.xProperties === true) {
let altArray = joiObj._inner.matches
.reduce((res, obj) => {
obj.then && res.push(obj.then);
obj.otherwise && res.push(obj.otherwise);
return res;
}, [])
.map((joiNewObj) => {
let altName = (Utilities.geJoiLabel(joiNewObj) || name);
return this.parseProperty(altName, joiNewObj, property, parameterType, useDefinitions, true);
})
.filter((obj) => obj);
property['x-alternatives'] = Hoek.clone(altArray);
}
}
//if (!property.$ref && Utilities.geJoiLabel(joiObj)) {
// property.name = Utilities.geJoiLabel(joiObj);
//}
return property;
}...
};
}
}
// add alternatives properties
if (property.type === 'alternatives') {
property = this.parseAlternatives(property, joiObj, name, parameterType, useDefinitions
);
}
// convert property to file upload, if indicated by meta property
if (Utilities.getJoiMetaProperty(joiObj, 'swaggerType') === 'file') {
property.type = 'file';
property.in = 'formData';
}
...parseArray = function (property, joiObj, name, parameterType, useDefinitions, isAlt) {
const describe = joiObj.describe();
property.minItems = internals.getArgByName(describe.rules, 'min');
property.maxItems = internals.getArgByName(describe.rules, 'max');
// add extended properties not part of openAPI spec
if (this.settings.xProperties === true) {
internals.convertRules(property, describe.rules, [
'length',
'unique'
], 'x-constraint');
if (describe.flags.sparse) {
internals.addToPropertyObject(property, 'x-constraint', 'sparse', true);
}
if (describe.flags.single) {
internals.addToPropertyObject(property, 'x-constraint', 'single', true);
}
}
// default the items with type:string
property.items = {
'type': 'string'
};
// set swaggers collectionFormat to one that works with hapi
if (parameterType === 'query' || parameterType === 'formData') {
property.collectionFormat = 'multi';
}
// swagger appears to only support one array item type at a time, so grab the first one
let arrayItemTypes = joiObj._inner.items; // joiObj._inner.inclusions;
let arrayItem = Utilities.first(arrayItemTypes);
if (arrayItem) {
// get name of item if it has one
let itemName;
if (Utilities.geJoiLabel(arrayItem)) {
itemName = Utilities.geJoiLabel(arrayItem);
}
//name, joiObj, parent, parameterType, useDefinitions, isAlt
let arrayItemProperty = this.parseProperty(itemName, arrayItem, property, parameterType, useDefinitions, isAlt);
if (this.simpleTypePropertyMap[arrayItem._type.toLowerCase()]) {
// map simple types directly
property.items = {};
for (let key in arrayItemProperty) {
property.items[key] = arrayItemProperty[key];
}
} else {
property.items = arrayItemProperty;
}
}
property.name = name;
return property;
}...
}
}
// add array properties
if (property.type === 'array') {
property = this.parseArray(property, joiObj, name, parameterType, useDefinitions, isAlt
);
if (useDefinitions === true) {
let refName = this.definitions.append(name, property, this.getDefinitionCollection(isAlt), this.settings);
property = {
'$ref': this.getDefinitionRef(isAlt) + refName,
'type': 'array'
};
...parseDate = function (property, joiObj) {
if (joiObj._flags.timestamp) {
property.type = 'number';
delete property.format;
}
return property;
}...
// add number properties
if (property.type === 'number') {
property = this.parseNumber(property, joiObj);
}
// add date properties
if (property.type === 'string' && property.format === 'date') {
property = this.parseDate(property, joiObj);
}
// add object child properties
if (property.type === 'object') {
if (Utilities.hasJoiChildren(joiObj)) {
property = this.parseObject(property, joiObj, name, parameterType, useDefinitions, isAlt);
...parseNumber = function (property, joiObj) {
const describe = joiObj.describe();
property.minimum = internals.getArgByName(describe.rules, 'min');
property.maximum = internals.getArgByName(describe.rules, 'max');
if (internals.hasPropertyByName(describe.rules, 'integer')) {
property.type = 'integer';
}
// add extended properties not part of openAPI spec
if (this.settings.xProperties === true) {
internals.convertRules(property, describe.rules, [
'greater',
'less',
'precision',
'multiple',
'positive',
'negative'
], 'x-constraint');
}
return property;
}...
// add number properties
if (property.type === 'string') {
property = this.parseString(property, joiObj);
}
// add number properties
if (property.type === 'number') {
property = this.parseNumber(property, joiObj);
}
// add date properties
if (property.type === 'string' && property.format === 'date') {
property = this.parseDate(property, joiObj);
}
...parseObject = function (property, joiObj, name, parameterType, useDefinitions, isAlt) {
property.properties = {};
joiObj = joiObj._inner.children;
joiObj.forEach((obj) => {
let keyName = obj.key;
let itemName = obj.key;
let joiChildObj = obj.schema;
// get name form label if set
if (Utilities.geJoiLabel(joiChildObj)) {
itemName = Utilities.geJoiLabel(joiChildObj);
}
//name, joiObj, parent, parameterType, useDefinitions, isAlt
property.properties[keyName] = this.parseProperty(itemName, joiChildObj, property, parameterType, useDefinitions, isAlt);
// switch references if naming has changed
if (keyName !== itemName) {
property.required = Utilities.replaceValue(property.required, itemName, keyName);
property.optional = Utilities.replaceValue(property.optional, itemName, keyName);
}
});
property.name = name;
return property;
}...
property = this.parseDate(property, joiObj);
}
// add object child properties
if (property.type === 'object') {
if (Utilities.hasJoiChildren(joiObj)) {
property = this.parseObject(property, joiObj, name, parameterType, useDefinitions, isAlt
);
if (useDefinitions === true) {
let refName = this.definitions.append(name, property, this.getDefinitionCollection(isAlt), this.settings);
property = {
'$ref': this.getDefinitionRef(isAlt) + refName,
'type': 'object'
};
}
...parseProperty = function (name, joiObj, parent, parameterType, useDefinitions, isAlt) {
let property = { type: 'void' };
// if wrong format or forbidden - return undefined
if (!Utilities.isJoi(joiObj)) {
return undefined;
}
if (Hoek.reach(joiObj, '_flags.presence') === 'forbidden') {
return undefined;
}
// default the use of definitions to true
if (useDefinitions === undefined || useDefinitions === null) {
useDefinitions = true;
}
// get name from joi label if its not a path
if (!name && Utilities.geJoiLabel(joiObj) && parameterType !== 'path') {
name = Utilities.geJoiLabel(joiObj);
}
// add correct type and format by mapping
let joiType = joiObj._type.toLowerCase();
// for Joi extension, use the any type
if (!(joiType in this.propertyMap)){
joiType = 'any';
}
let map = this.propertyMap[joiType];
property.type = map.type;
if (map.format) {
property.format = map.format;
}
// Joi object caching - speeds up parsing
let joiCache;
if (useDefinitions && Array.isArray(this.definitionCache) && property.type === 'object') {
// select WeakMap for current definition collection
joiCache = (isAlt === true) ? this.definitionCache[1] : this.definitionCache[0];
if (joiCache.has(joiObj)) {
return joiCache.get(joiObj);
}
}
property = this.parsePropertyMetadata(property, name, parent, joiObj);
// add enum
let describe = joiObj.describe();
if (Array.isArray(describe.valids) && describe.valids.length) {
// fliter out empty values and arrays
var enums = describe.valids.filter((item) => {
return item !== '' && item !== null;
});
if (enums.length > 0) {
property.enum = enums;
}
}
// add number properties
if (property.type === 'string') {
property = this.parseString(property, joiObj);
}
// add number properties
if (property.type === 'number') {
property = this.parseNumber(property, joiObj);
}
// add date properties
if (property.type === 'string' && property.format === 'date') {
property = this.parseDate(property, joiObj);
}
// add object child properties
if (property.type === 'object') {
if (Utilities.hasJoiChildren(joiObj)) {
property = this.parseObject(property, joiObj, name, parameterType, useDefinitions, isAlt);
if (useDefinitions === true) {
let refName = this.definitions.append(name, property, this.getDefinitionCollection(isAlt), this.settings);
property = {
'$ref': this.getDefinitionRef(isAlt) + refName,
'type': 'object'
};
}
} else {
// default empty object
property.properties = {};
if (useDefinitions === true) {
let objectSchema = { 'type': 'object', 'properties': {} };
let refName = this.definitions.append(name, objectSchema, this.getDefinitionCollection(isAlt), this.settings);
property = {
'$ref': this.getDefinitionRef(isAlt) + refName,
'type': 'object'
};
}
}
}
// add array properties
if (property.type === 'array') {
property = this.parseArray(property, joiObj, name, parameterType, useDefinitions, isAlt);
if (useDefinitions === true) {
let refName = this.definitions.append(name, property, this.getDefinitionCollection(isAlt), this.settings);
property = {
'$ref': this.getDefinitionRef(isAlt) + refName,
'type': 'array'
};
}
}
// add alternatives properties
if (property.type === 'alternatives') {
property = this.parseAlternatives(property, joiObj, name, parameterType, useDefinitions);
}
// convert property to file upload, if indicated by meta property
if (Utilities. ......
internals.paths.prototype.getSwaggerStructures = function (joiObj, parameterType, useDefinitions, isAlt) {
let outProperties;
let outParameters;
if (joiObj) {
// name, joiObj, parent, parameterType, useDefinitions, isAlt
outProperties = this.properties.parseProperty(null, joiObj, null, parameterType,
useDefinitions, isAlt);
outParameters = Parameters.fromProperties(outProperties, parameterType);
}
let out = {
properties: outProperties || {},
parameters: outParameters || []
};
return out;
...parsePropertyMetadata = function (property, name, parent, joiObj) {
const describe = joiObj.describe();
// add common properties
property.description = Hoek.reach(joiObj, '_description');
property.notes = Hoek.reach(joiObj, '_notes');
property.tags = Hoek.reach(joiObj, '_tags');
// add extended properties not part of openAPI spec
if (this.settings.xProperties === true) {
internals.convertRules(property, describe.rules, [
'unit'
], 'x-format');
property.example = Hoek.reach(joiObj, '_examples.0');
property['x-meta'] = Hoek.reach(joiObj, '_meta.0');
}
// add required/optional state only if present
if (parent && name) {
if (Hoek.reach(joiObj, '_flags.presence')) {
if (parent.required === undefined) {
parent.required = [];
}
if (parent.optional === undefined) {
parent.optional = [];
}
if (Hoek.reach(joiObj, '_flags.presence') === 'required') {
parent.required.push(name);
}
if (Hoek.reach(joiObj, '_flags.presence') === 'optional') {
parent.optional.push(name);
}
}
}
property.default = Hoek.reach(joiObj, '_flags.default');
// allow for function calls
if (Utilities.isFunction(property.default)) {
property.default = property.default();
}
return property;
}...
joiCache = (isAlt === true) ? this.definitionCache[1] : this.definitionCache[0];
if (joiCache.has(joiObj)) {
return joiCache.get(joiObj);
}
}
property = this.parsePropertyMetadata(property, name, parent, joiObj);
// add enum
let describe = joiObj.describe();
if (Array.isArray(describe.valids) && describe.valids.length) {
// fliter out empty values and arrays
var enums = describe.valids.filter((item) => {
...parseString = function (property, joiObj) {
const describe = joiObj.describe();
property.minLength = internals.getArgByName(describe.rules, 'min');
property.maxLength = internals.getArgByName(describe.rules, 'max');
// add regex
joiObj._tests.forEach((test) => {
// Joi post v10
if (Utilities.isObject(test.arg) && test.arg.pattern) {
property.pattern = test.arg.pattern.toString();
}
// Joi pre v10
/* $lab:coverage:off$ */
if (Utilities.isRegex(test.arg)) {
property.pattern = test.arg.toString();
}
/* $lab:coverage:on$ */
});
// add extended properties not part of openAPI spec
if (this.settings.xProperties === true) {
internals.convertRules(property, describe.rules, [
'insensitive',
'length'
], 'x-constraint');
internals.convertRules(property, describe.rules, [
'creditCard',
'alphanum',
'token',
'email',
'ip',
'uri',
'guid',
'hex',
'hostname',
'isoDate'
], 'x-format');
internals.convertRules(property, describe.rules, [
'lowercase',
'uppercase',
'trim'
], 'x-convert');
}
return property;
}...
if (enums.length > 0) {
property.enum = enums;
}
}
// add number properties
if (property.type === 'string') {
property = this.parseString(property, joiObj);
}
// add number properties
if (property.type === 'number') {
property = this.parseNumber(property, joiObj);
}
...responses = function (settings, definitionCollection, altDefinitionCollection, definitionCache) {
this.settings = settings;
this.definitionCollection = definitionCollection;
this.altDefinitionCollection = altDefinitionCollection;
this.definitions = new Definitions(settings);
this.properties = new Properties(settings, this.definitionCollection, this.altDefinitionCollection, definitionCache);
}n/a
build = function (userDefindedSchemas, defaultSchema, statusSchemas, useDefinitions, isAlt) {
let out = {};
// add defaultSchema to statusSchemas if needed
if (Utilities.hasProperties(defaultSchema) && (Hoek.reach(statusSchemas, '200') === undefined)) {
statusSchemas[200] = defaultSchema;
}
// loop for each status and convert schema into a definition
if (Utilities.hasProperties(statusSchemas)) {
for (let key in statusSchemas) {
// name, joiObj, parameterType, useDefinitions, isAlt
let response = this.getResponse(key, statusSchemas[key], null, useDefinitions, isAlt);
out[key] = response;
}
}
// use plug-in options overrides to enchance hapi objects and properties
if (Utilities.hasProperties(userDefindedSchemas) === true) {
out = this.optionOverride(out, userDefindedSchemas, useDefinitions, isAlt);
}
// make sure 200 status always has a schema #237
if (out[200] && out[200].schema === undefined) {
out[200].schema = {
'type': 'string'
};
}
// make sure there is a default if no other responses are found
if (Utilities.hasProperties(out) === false) {
out.default = {
'schema': {
'type': 'string'
},
'description': 'Successful'
};
}
return Utilities.deleteEmptyProperties(out);
}...
if (settings.basePath !== '/') {
settings.basePath = Utilities.removeTrailingSlash(settings.basePath);
}
let out = internals.removeNoneSchemaOptions(settings);
Joi.assert(out, builder.schema);
out.info = Info.build(settings);
out.tags = Tags.build(settings);
let routes = connection.table();
routes = Filter.byTags(['api'], routes);
Sort.paths(settings.sortPaths, routes);
...getResponse = function (statusCode, joiObj, useDefinitions) {
let out;
//name, joiObj, parent, parameterType, useDefinitions, isAlt
let outProperties = this.properties.parseProperty(null, joiObj, null, 'body', useDefinitions, false);
out = {
'description': Hoek.reach(joiObj, '_description'),
'schema': outProperties
};
out.headers = Utilities.getJoiMetaProperty(joiObj, 'headers');
out.examples = Utilities.getJoiMetaProperty(joiObj, 'examples');
delete out.schema['x-meta'];
out = Utilities.deleteEmptyProperties(out);
// default description if not given
if (!out.description) {
out.description = HTTPStatus[statusCode].replace('OK', 'Successful');
}
return out;
}...
statusSchemas[200] = defaultSchema;
}
// loop for each status and convert schema into a definition
if (Utilities.hasProperties(statusSchemas)) {
for (let key in statusSchemas) {
// name, joiObj, parameterType, useDefinitions, isAlt
let response = this.getResponse(key, statusSchemas[key], null, useDefinitions
, isAlt);
out[key] = response;
}
}
// use plug-in options overrides to enchance hapi objects and properties
if (Utilities.hasProperties(userDefindedSchemas) === true) {
out = this.optionOverride(out, userDefindedSchemas, useDefinitions, isAlt);
...optionOverride = function (discoveredSchemas, userDefindedSchemas, useDefinitions, isAlt) {
for (let key in userDefindedSchemas) {
// create a new object by cloning - dont modify user definded objects
let out = Hoek.clone(userDefindedSchemas[key]);
// test for any JOI objects
if (Hoek.reach(userDefindedSchemas[key], 'schema.isJoi') && userDefindedSchemas[key].schema.isJoi === true) {
out = this.getResponse(key, userDefindedSchemas[key].schema, useDefinitions, isAlt);
out.description = userDefindedSchemas[key].description;
}
// overwrite discovery with user definded
if (!discoveredSchemas[key] && out) {
// if it does not exist create it
discoveredSchemas[key] = out;
} else {
// add description to schema
if (out.description) {
discoveredSchemas[key].description = out.description;
}
// overwrite schema
if (out.schema) {
discoveredSchemas[key].schema = out.schema;
}
}
discoveredSchemas[key] = Utilities.deleteEmptyProperties(discoveredSchemas[key]);
}
return discoveredSchemas;
}...
let response = this.getResponse(key, statusSchemas[key], null, useDefinitions, isAlt);
out[key] = response;
}
}
// use plug-in options overrides to enchance hapi objects and properties
if (Utilities.hasProperties(userDefindedSchemas) === true) {
out = this.optionOverride(out, userDefindedSchemas, useDefinitions, isAlt);
}
// make sure 200 status always has a schema #237
if (out[200] && out[200].schema === undefined) {
out[200].schema = {
'type': 'string'
};
...paths = function (sortType, routes) {
if (sortType === 'path-method') {
//console.log('path-method')
routes.sort(
Utilities.firstBy('path').thenBy('method')
);
}
return routes;
}...
out.info = Info.build(settings);
out.tags = Tags.build(settings);
let routes = connection.table();
routes = Filter.byTags(['api'], routes);
Sort.paths(settings.sortPaths, routes);
// filter routes displayed based on tags passed in query string
if (request.query.tags) {
let filterTags = request.query.tags.split(',');
routes = Filter.byTags(filterTags, routes);
}
...build = function (settings) {
let out = [];
if (settings.tags) {
Joi.assert(settings.tags, tags.schema);
out = settings.tags;
}
return out;
}...
if (settings.basePath !== '/') {
settings.basePath = Utilities.removeTrailingSlash(settings.basePath);
}
let out = internals.removeNoneSchemaOptions(settings);
Joi.assert(out, builder.schema);
out.info = Info.build(settings);
out.tags = Tags.build(settings);
let routes = connection.table();
routes = Filter.byTags(['api'], routes);
Sort.paths(settings.sortPaths, routes);
...createId = function (method, path) {
const self = this;
if (path.indexOf('/') > -1) {
let items = path.split('/');
items = items.map(function (item) {
// replace chars such as '{'
item = item.replace(/[^\w\s]/gi, '');
return self.toTitleCase(item);
});
path = items.join('');
} else {
path = self.toTitleCase(path);
}
return method.toLowerCase() + path;
}...
routes.forEach((route) => {
let method = route.method;
let path = internals.removeBasePath(route.path, this.settings.basePath, this.settings.pathReplacements);
let out = {
'summary': route.description,
'operationId': route.id || Utilities.createId(route.method, path),
'description': route.notes,
'parameters': [],
'consumes': [],
'produces': []
};
// tags in swagger are used for grouping
...deleteEmptyProperties = function (obj) {
let key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
// delete properties undefined values
if (obj[key] === undefined || obj[key] === null) {
delete obj[key];
}
// allow blank objects for example or default properties
if (['default', 'example'].indexOf(key) === -1) {
// delete array with no values
if (Array.isArray(obj[key]) && obj[key].length === 0) {
delete obj[key];
}
// delete object which does not have its own properties
if (utilities.isObject(obj[key]) && utilities.hasProperties(obj[key]) === false) {
delete obj[key];
}
}
}
}
return obj;
}...
// add $ref directly to parent and delete schema
// if (obj.schema) {
// obj.$ref = obj.schema.$ref;
// delete obj.schema;
// }
// remove emtpy properties
obj = Utilities.deleteEmptyProperties(obj);
// remove unneeded properties
delete obj.name;
return obj;
};
...findAndRenameKey = function (obj, findKey, replaceKey) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object') {
this.findAndRenameKey(obj[key], findKey, replaceKey);
}
if (key === findKey) {
if (replaceKey) {
obj[replaceKey] = obj[findKey];
}
delete obj[findKey];
}
}
}
return obj;
}...
* @return {Object}
*/
utilities.findAndRenameKey = function (obj, findKey, replaceKey) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object') {
this.findAndRenameKey(obj[key], findKey, replaceKey);
}
if (key === findKey) {
if (replaceKey) {
obj[replaceKey] = obj[findKey];
}
delete obj[findKey];
}
...first = function (array) {
return Array.isArray(array) ? array[0] : undefined;
}...
// set swaggers collectionFormat to one that works with hapi
if (parameterType === 'query' || parameterType === 'formData') {
property.collectionFormat = 'multi';
}
// swagger appears to only support one array item type at a time, so grab the first one
let arrayItemTypes = joiObj._inner.items; // joiObj._inner.inclusions;
let arrayItem = Utilities.first(arrayItemTypes);
if (arrayItem) {
// get name of item if it has one
let itemName;
if (Utilities.geJoiLabel(arrayItem)) {
itemName = Utilities.geJoiLabel(arrayItem);
}
...firstBy = function (f, d) {
f = makeCompareFunction(f, d);
f.thenBy = tb;
return f;
}...
*/
sort.paths = function (sortType, routes) {
if (sortType === 'path-method') {
//console.log('path-method')
routes.sort(
Utilities.firstBy('path').thenBy('method')
);
}
return routes;
};
...geJoiLabel = function (joiObj) {
// old version
/* $lab:coverage:off$ */
if (Hoek.reach(joiObj, '_settings.language.label')) {
return Hoek.reach(joiObj, '_settings.language.label');
}
/* $lab:coverage:on$ */
// Joi > 10.9
if (Hoek.reach(joiObj, '_flags.label')) {
return Hoek.reach(joiObj, '_flags.label');
}
return null;
}...
// default the use of definitions to true
if (useDefinitions === undefined || useDefinitions === null) {
useDefinitions = true;
}
// get name from joi label if its not a path
if (!name && Utilities.geJoiLabel(joiObj) && parameterType !==
x27;path') {
name = Utilities.geJoiLabel(joiObj);
}
// add correct type and format by mapping
let joiType = joiObj._type.toLowerCase();
// for Joi extension, use the any type
if (!(joiType in this.propertyMap)){
...getJoiMetaProperty = function (joiObj, propertyName) {
// get headers added using meta function
if (utilities.isJoi(joiObj) && utilities.hasJoiMeta(joiObj)) {
const meta = joiObj._meta;
let i = meta.length;
while (i--) {
if (meta[i][propertyName]) {
return meta[i][propertyName];
}
}
}
return undefined;
}...
// add alternatives properties
if (property.type === 'alternatives') {
property = this.parseAlternatives(property, joiObj, name, parameterType, useDefinitions);
}
// convert property to file upload, if indicated by meta property
if (Utilities.getJoiMetaProperty(joiObj, 'swaggerType') === 'file'
;) {
property.type = 'file';
property.in = 'formData';
}
property = Utilities.deleteEmptyProperties(property);
if (joiCache && property.$ref) {
joiCache.set(joiObj, property);
...hasJoiChildren = function (joiObj) {
return (utilities.isJoi(joiObj) && Hoek.reach(joiObj, '_inner.children')) ? true : false;
}...
let payloadStructures = this.getDefaultStructures();
let payloadJoi = internals.getJOIObj(route, 'payloadParams');
if (payloadType.toLowerCase() === 'json') {
// set as json
payloadStructures = this.getSwaggerStructures(payloadJoi, 'body', true, false);
} else {
// set as formData
if (Utilities.hasJoiChildren(payloadJoi)) {
payloadStructures = this.getSwaggerStructures(payloadJoi, 'formData', false, false);
} else {
self.testParameterError(payloadJoi, 'payload form-urlencoded', path);
}
// add form data mimetype
out.consumes = ['application/x-www-form-urlencoded'];
}
...hasJoiMeta = function (joiObj) {
return (utilities.isJoi(joiObj) && Array.isArray(joiObj._meta)) ? true : false;
}...
* @param {Object} joiObj
* @param {String} propertyName
* @return {Object || Undefined}
*/
utilities.getJoiMetaProperty = function (joiObj, propertyName) {
// get headers added using meta function
if (utilities.isJoi(joiObj) && utilities.hasJoiMeta(joiObj)) {
const meta = joiObj._meta;
let i = meta.length;
while (i--) {
if (meta[i][propertyName]) {
return meta[i][propertyName];
}
...hasKey = function (obj, findKey) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object') {
if (this.hasKey(obj[key], findKey) === true) {
return true;
}
}
if (key === findKey) {
return true;
}
}
}
return false;
}...
* @return {Boolean}
*/
utilities.hasKey = function (obj, findKey) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object') {
if (this.hasKey(obj[key], findKey) === true) {
return true;
}
}
if (key === findKey) {
return true;
}
...hasProperties = function (obj) {
let key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
return true;
}
}
return false;
}...
// append group property - by path
Group.appendGroupByPath(settings.pathPrefixSize, settings.basePath, routes, settings.pathReplacements);
let paths = new Paths(settings);
let pathData = paths.build(routes);
out.paths = pathData.paths;
out.definitions = pathData.definitions;
if (Utilities.hasProperties(pathData['x-alt-definitions'])) {
out['x-alt-definitions'] = pathData['x-alt-definitions'];
}
out = internals.removeNoneSchemaOptions(out);
if (settings.debug) {
Validate.log(out, settings.log);
}
...isFunction = function (obj) {
// remove `obj.constructor` test as it was always true
return !!(obj && obj.call && obj.apply);
}...
'queryParams',
'pathParams',
'headerParams',
'payloadParams'
].forEach(function (property) {
// swap out any custom validation function for Joi object/string
if (Utilities.isFunction(routeData[property])) {
if (property !== 'pathParams') {
self.settings.log(['vaildation', 'warning'], 'Using a Joi.function for a query, header or payload
is not supported.');
if (property === 'payloadParams') {
routeData[property] = Joi.object().label('Hidden Model');
} else {
routeData[property] = Joi.object({ 'Hidden Model': Joi.string() });
}
...isJoi = function (joiObj) {
return (joiObj && joiObj.isJoi) ? true : false;
}...
* @return {Object}
*/
internals.properties.prototype.parseProperty = function (name, joiObj, parent, parameterType, useDefinitions, isAlt) {
let property = { type: 'void' };
// if wrong format or forbidden - return undefined
if (!Utilities.isJoi(joiObj)) {
return undefined;
}
if (Hoek.reach(joiObj, '_flags.presence') === 'forbidden') {
return undefined;
}
// default the use of definitions to true
...isObject = function (obj) {
return obj !== null && obj !== undefined && typeof obj === 'object' && !Array.isArray(obj);
}...
property.minLength = internals.getArgByName(describe.rules, 'min');
property.maxLength = internals.getArgByName(describe.rules, 'max');
// add regex
joiObj._tests.forEach((test) => {
// Joi post v10
if (Utilities.isObject(test.arg) && test.arg.pattern) {
property.pattern = test.arg.pattern.toString();
}
// Joi pre v10
/* $lab:coverage:off$ */
if (Utilities.isRegex(test.arg)) {
property.pattern = test.arg.toString();
}
...isRegex = function (obj) {
// base on https://github.com/ljharb/is-regex/
// has a couple of edge use cases for different env - hence coverage:off
/* $lab:coverage:off$ */
const regexExec = RegExp.prototype.exec;
const tryRegexExec = function tryRegexExec (value) {
try {
regexExec.call(value);
return true;
} catch (e) {
return false;
}
};
const toStr = Object.prototype.toString;
const regexClass = '[object RegExp]';
const hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
if (typeof obj !== 'object') {
return false;
}
return hasToStringTag ? tryRegexExec(obj) : toStr.call(obj) === regexClass;
/* $lab:coverage:on$ */
}...
joiObj._tests.forEach((test) => {
// Joi post v10
if (Utilities.isObject(test.arg) && test.arg.pattern) {
property.pattern = test.arg.pattern.toString();
}
// Joi pre v10
/* $lab:coverage:off$ */
if (Utilities.isRegex(test.arg)) {
property.pattern = test.arg.toString();
}
/* $lab:coverage:on$ */
});
// add extended properties not part of openAPI spec
...removeProps = function (obj, listOfProps) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (listOfProps.indexOf(key) === -1 && this.startsWith(key, 'x-') === false) {
delete obj[key];
//console.log('Removed property: ' + key + ' from object: ', JSON.stringify(obj));
}
}
}
return obj;
}...
// reinstate x-alternatives at parameter level
if (schemaObj['x-alternatives']) {
item['x-alternatives'] = schemaObj['x-alternatives'];
delete item.schema['x-alternatives'];
}
item = Utilities.removeProps(item, this.allowedProps);
if (!Hoek.deepEqual(item.schema, { 'type': 'object' }, { prototype: false })) {
out.push(Utilities.deleteEmptyProperties(item));
}
// if its an array of parameters
} else {
...removeTrailingSlash = function (str) {
if (str.endsWith('/')) {
return str.slice(0, -1);
}
return str;
}...
// collect root information
builder.default.host = internals.getHost(request, namedConnection);
builder.default.schemes = [internals.getSchema(request, connection)];
settings = Hoek.applyToDefaults(builder.default, settings);
if (settings.basePath !== '/') {
settings.basePath = Utilities.removeTrailingSlash(settings.basePath);
}
let out = internals.removeNoneSchemaOptions(settings);
Joi.assert(out, builder.schema);
out.info = Info.build(settings);
out.tags = Tags.build(settings);
...replaceInPath = function (path, applyTo, options) {
options.forEach((option) => {
if (applyTo.indexOf(option.replaceIn) > -1 || option.replaceIn === 'all') {
path = path.replace(option.pattern, option.replacement);
}
});
return path;
}...
* @param {Array} pathReplacements
* @return {Array}
*/
group.getNameByPath = function (pathPrefixSize, basePath, path, pathReplacements) {
if (pathReplacements) {
path = Utilities.replaceInPath(path, ['groups'], pathReplacements);
}
let i = 0;
let pathHead = [];
let parts = path.split('/');
while (parts.length > 0) {
...replaceValue = function (array, current, replacement) {
if (array && current && replacement) {
array = Hoek.clone(array);
if (array.indexOf(current) > -1) {
array.splice(array.indexOf(current), 1);
array.push(replacement);
}
}
return array;
}...
if (Utilities.geJoiLabel(joiChildObj)) {
itemName = Utilities.geJoiLabel(joiChildObj);
}
//name, joiObj, parent, parameterType, useDefinitions, isAlt
property.properties[keyName] = this.parseProperty(itemName, joiChildObj, property, parameterType, useDefinitions, isAlt);
// switch references if naming has changed
if (keyName !== itemName) {
property.required = Utilities.replaceValue(property.required, itemName, keyName
);
property.optional = Utilities.replaceValue(property.optional, itemName, keyName);
}
});
property.name = name;
return property;
};
...sortFirstItem = function (array, firstItem) {
let out = array;
if (firstItem) {
out = [firstItem];
array.forEach(function (item) {
if (item !== firstItem) {
out.push(item);
}
});
}
return out;
}...
}
// if the API has a user set accept header with a enum convert into the produces array
if (this.settings.acceptToProduce === true) {
headerStructures.parameters = headerStructures.parameters.filter(function (header) {
if (header.name.toLowerCase() === 'accept') {
if (header.enum) {
out.produces = Utilities.sortFirstItem(header.enum, header.default);
return false;
}
}
return true;
});
}
...startsWith = function (str, test) {
return (str.indexOf(test) === 0);
}...
* @return {String}
*/
internals.nextModelName = function (currentCollection) {
let highest = 0;
let key;
for (key in currentCollection) {
if (Utilities.startsWith(key, 'Model')) {
let num = parseInt(key.replace('Model', ''), 10);
if (num && num > highest) {
highest = num;
}
}
}
return 'Model ' + (highest + 1);
...toJoiObject = function (obj) {
if (utilities.isJoi(obj) === false && utilities.isObject(obj)) {
return Joi.object(obj);
}
return obj;
}...
routeData.path = Utilities.replaceInPath(routeData.path, ['endpoints'], this.settings.pathReplacements);
// user configured interface through route plugin options
if (Hoek.reach(routeOptions, 'validate.query')) {
routeData.queryParams = Utilities.toJoiObject(Hoek.reach(routeOptions, 'validate
.query'));
}
if (Hoek.reach(routeOptions, 'validate.params')) {
routeData.pathParams = Utilities.toJoiObject(Hoek.reach(routeOptions, 'validate.params'));
}
if (Hoek.reach(routeOptions, 'validate.headers')) {
routeData.headerParams = Utilities.toJoiObject(Hoek.reach(routeOptions, 'validate.headers'));
}
...toTitleCase = function (word) {
return word.replace(/\w\S*/g, (txt) => {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
}...
const self = this;
if (path.indexOf('/') > -1) {
let items = path.split('/');
items = items.map(function (item) {
// replace chars such as '{'
item = item.replace(/[^\w\s]/gi, '');
return self.toTitleCase(item);
});
path = items.join('');
} else {
path = self.toTitleCase(path);
}
return method.toLowerCase() + path;
};
...log = function (doc, logFnc) {
SwaggerParser.validate(doc, (err) => {
// use err.message so thrown error object is not passed into testing
if (err) {
logFnc(['validation', 'error'], 'FAILED - ' + err.message);
} else {
logFnc(['validation', 'info'], 'PASSED - The swagger.json validation passed.');
}
});
}...
Vision,
{
'register': HapiSwagger,
'options': options
}], (err) => {
server.start( (err) => {
if (err) {
console.log(err);
} else {
console.log('Server running at:', server.info.uri);
}
});
});
server.route(Routes);
...test = function (doc, next) {
SwaggerParser.validate(doc, (err) => {
// use err.message so thrown error object is not passed into testing
if (err) {
next(err.message, false);
} else {
next(null, true);
}
});
}n/a