arrow-admin
Version:
Arrow Admin Website
263 lines (246 loc) • 5.76 kB
JavaScript
var _ = require('lodash');
var verbMap = {
POST: 'create',
GET: 'find',
PUT: 'update',
DELETE: 'delete'
};
exports.route = route;
exports.definition = definition;
function route(objectModel) {
return function (req, res, next) {
res.send(definition(objectModel));
};
}
function definition(objectModel) {
return {
swagger: '2.0',
info: compact({
version: objectModel.metadata.version,
title: objectModel.metadata.name,
description: objectModel.metadata.description,
termsOfService: objectModel.metadata.termsOfService,
contact: {
name: objectModel.metadata.author
},
license: {
name: objectModel.metadata.license
}
}),
// TODO: Specify the host (from ENV variables?)
host: (objectModel.server.host || '127.0.0.1') + (objectModel.server.port ? ':' + objectModel.server.port : ''),
basePath: objectModel.config.apiPrefix,
schemes: [
'http',
'https'
],
consumes: [
'application/json',
'application/x-www-form-urlencoded',
'multipart/form-data'
],
produces: [
'application/json',
'application/xml',
'text/yaml',
'text/csv',
'text/plain'
],
definitions: createDefinitions(objectModel),
paths: createPaths(objectModel)
};
}
function createDefinitions(objectModel) {
var retVal = {},
models = objectModel.models;
for (var modelName in models) {
if (models.hasOwnProperty(modelName)) {
var model = models[modelName],
safeName = modelName.replace(/\//g, '_'),
required = [],
properties = {};
for (var fieldName in model.fields) {
if (model.fields.hasOwnProperty(fieldName)) {
var field = model.fields[fieldName];
if (field.required) {
required.push(fieldName);
}
properties[fieldName] = {};
switch (field.type) {
case 'number':
properties[fieldName].type = 'integer';
properties[fieldName].format = 'int32';
break;
case 'array':
// TODO: We need more information about the array sub-type here.
properties[fieldName].items = {type: 'string'};
break;
default:
properties[fieldName].type = field.type;
break;
}
}
}
retVal[safeName] = {
type: 'object',
required: required,
properties: properties
};
}
}
retVal.ResponseModel = {
type: 'object',
required: [
'code',
'success',
'request-id'
],
properties: {
code: {
type: 'integer',
format: 'int32'
},
success: {
type: 'boolean',
'default': false
},
'request-id': {
type: 'string'
},
message: {
type: 'string'
},
url: {
type: 'string'
}
}
};
retVal.ErrorModel = {
type: 'object',
allOf: [
{
"$ref": "#/definitions/ResponseModel"
},
{
"required": [
'message'
]
}
]
};
return retVal;
}
function createPaths(objectModel) {
var retVal = {},
apis = objectModel.apis;
for (var groupName in apis) {
if (apis.hasOwnProperty(groupName)) {
var api = apis[groupName];
for (var i = 0; i < api.endpoints.length; i++) {
var endpoint = api.endpoints[i],
relativePath = translatePath(endpoint.path.split(objectModel.config.apiPrefix).pop()),
def = retVal[relativePath];
if (endpoint.enabled === false) {
continue;
}
if (!def) {
retVal[relativePath] = def = {};
}
def[endpoint.method.toLowerCase()] = compact({
description: endpoint.description,
operationId: getOperationId(endpoint),
deprecated: endpoint.deprecated,
parameters: translateParameters(endpoint),
responses: endpoint.responses || api.responses,
tags: [groupName]
});
}
}
}
return retVal;
}
function translatePath(path) {
return path.replace(/:([^/]+)/g, '{$1}');
}
function translateParameters(endpoint) {
var retVal = [],
bodyParams;
for (var name in endpoint.parameters) {
if (endpoint.parameters.hasOwnProperty(name)) {
var param = endpoint.parameters[name];
switch (param.type) {
case 'body':
if (!bodyParams) {
bodyParams = {
name: endpoint.nickname,
'in': 'body',
description: endpoint.nickname + ' body',
schema: {
type: 'object',
required: [],
properties: {}
}
};
}
if (param.required) {
bodyParams.schema.required.push(name);
}
bodyParams.schema.properties[name] = {
type: param.dataType || 'string',
description: param.description
};
break;
default:
var translated = {
name: name,
'in': param.type,
description: param.description,
required: !!param.required,
type: param.dataType || 'string'
};
// TODO: We need more information about the sub-types of objects and arrays.
if (param.dataType === 'object') {
translated.type = 'string';
}
if (param.dataType === 'array') {
translated.items = {type: 'object'};
}
retVal.push(compact(translated));
break;
}
}
}
if (bodyParams) {
retVal.push(bodyParams);
}
return retVal;
}
function getOperationId(endpoint) {
var retVal = verbMap[endpoint.method] || endpoint.method.toLowerCase(),
splits = endpoint.path.replace(/appc\./, '')
.replace(/_[a-z]/ig, function (val) {
return val[1].toUpperCase();
})
.slice(1).split('/');
for (var i = 1; i < splits.length; i++) {
var split = splits[i];
if (split[0] === ':') {
retVal += 'By' + split.slice(1).toUpperCase();
}
else {
retVal += split[0].toUpperCase() + split.slice(1);
}
}
return retVal;
}
function compact(obj) {
return _.omit(obj, function objectFilter(val, key) {
if (_.isObject(val)) {
obj[key] = compact(val);
if (_.keys(obj[key]).length === 0) {
return false;
}
}
return !val;
});
}