any2api-generator-rest
Version:
REST API implementation generator for any2api
286 lines (220 loc) • 9.69 kB
JavaScript
var path = require('path');
var fs = require('fs-extra');
var async = require('async');
var _ = require('lodash');
var S = require('string');
var raml2html = require('raml2html');
var util = require('any2api-util');
//TODO WORKAROUND
//fs.copy = function(src, dest, callback) { fs.copySync(src, dest); callback(); };
module.exports = function(spec) {
var obj = {};
//spec = spec || {};
var supports = function(apiSpec) { //TODO: additionally support java, java-jersey, java-jaxrs, java-dropwizard
if (_.contains([ 'node', 'node.js', 'nodejs' ], apiSpec.implementation.type.trim().toLowerCase()) &&
_.contains([ 'rest', 'restful', 'http-rest' ], apiSpec.implementation.interface.trim().toLowerCase())) {
return true;
} else {
return false;
}
};
var generate = function(apiSpec, done) {
var specRaml;
var implPath = path.resolve(apiSpec.path, '..', apiSpec.implementation.path);
var specRamlPath = path.resolve(implPath, 'spec.raml');
var docsHtmlPath = path.resolve(implPath, 'docs.html');
var implTplPath = process.env.IMPL_TEMPLATE_PATH;
if (_.isEmpty(implTplPath)) {
if (S(apiSpec.implementation.type).trim().toLowerCase().startsWith('java')) {
implTplPath = path.resolve(__dirname, '..', 'tpl', 'impl-dropwizard');
} else {
implTplPath = path.resolve(__dirname, '..', 'tpl', 'impl-node');
}
}
apiSpec.implementation.title = apiSpec.implementation.title || 'REST';
apiSpec.implementation.ports = [ '3000' ];
async.series([
//async.apply(fs.mkdirs, implPath),
function(callback) {
if (implPath === implTplPath) return callback();
fs.copy(implTplPath, implPath, callback);
},
function(callback) {
if (implPath === implTplPath) return callback();
async.parallel([
function(callback) {
fs.readFile(path.resolve(__dirname, '..', 'tpl', 'spec.raml.tpl'), 'utf8', function(err, content) {
specRaml = content;
callback(err);
});
},
function(callback) {
if (fs.existsSync(path.resolve(implPath, 'static', 'console'))) return callback();
fs.copy(path.resolve(__dirname, '..', 'tpl', 'console'),
path.resolve(implPath, 'static', 'console'), callback);
},
function(callback) {
if (fs.existsSync(path.resolve(implPath, 'test'))) return callback();
fs.copy(path.resolve(__dirname, '..', 'tpl', 'test'),
path.resolve(implPath, 'test'), callback);
},
function(callback) {
// Copy executables
async.each(_.keys(apiSpec.executables), function(execName, callback) {
var executable = apiSpec.executables[execName];
var execPath = path.resolve(apiSpec.path, '..', executable.path);
executable.path = path.join('static', 'executables', execName);
fs.copy(execPath, path.resolve(implPath, executable.path), callback);
}, callback);
},
function(callback) {
// Copy invokers
async.each(_.keys(apiSpec.invokers), function(invokerName, callback) {
var invoker = apiSpec.invokers[invokerName];
var invokerPath = path.resolve(apiSpec.path, '..', invoker.path);
invoker.path = path.join('invokers', invokerName);
fs.copy(invokerPath, path.resolve(implPath, invoker.path), callback);
}, callback);
}
], callback);
},
function(callback) {
util.enrichSpec({ apiSpec: apiSpec, basePath: implPath }, callback);
},
function(callback) {
// Generate spec.raml
var docs = [];
_.each([ 'executables', 'invokers' ], function(collection) {
_.each(apiSpec[collection], function(item, name) {
item.rest_url_path = collection + '/' + name;
var doc = {};
if (collection === 'executables') {
doc.title = 'Executable ' + name;
doc.content = item.description || '';
if (item.readme) {
doc.content += '\n\nREADME file of executable: ' +
'[' + item.readme + ']' +
'(/' + path.relative('static', path.join(item.path, item.readme))
.replace(/\\/g,'/') + ')';
}
} else {
doc.title = 'Invoker ' + name;
doc.content = 'Use this invoker to run arbitrary executables (supported by the invoker) in an ad-hoc manner.';
if (item.description) {
doc.content += '\n\n' + item.description;
}
}
doc.content = indentStringify(doc.content);
docs.push(doc);
if (collection === 'executables') item.rest_schema_name = S('instance-executable-' + name).camelize().s;
else item.rest_schema_name = S('instance-invoker-' + name).camelize().s;
item.rest_schema = _.cloneDeep(util.instanceSchema);
item.rest_schema['$schema'] = 'http://json-schema.org/draft-04/schema#';
item.rest_schema.properties.parameters = {
type: 'object',
properties: { stdout: { type: 'string' }, 'stderr': { type: 'string' } }
};
item.rest_schema.properties.results = { type: 'object', properties: {} };
item.rest_schema.properties._links = {
type: 'object',
patternProperties: {
'[a-zA-Z0-9-_.]+': {
type: 'object',
properties: { href: { type: 'string' } }
}
}
};
// Parameters and results schema
var paramsProps = item.rest_schema.properties.parameters.properties;
var resultsProps = item.rest_schema.properties.results.properties;
var paramsSchema = item.parameters_schema;
var paramsRequired = item.parameters_required;
var resultsSchema = item.results_schema;
if (collection === 'invokers') {
item.rest_schema.properties.executable = util.embeddedExecutableSchema;
}
populateSchemaProperties(paramsSchema, paramsProps);
populateSchemaProperties(resultsSchema, resultsProps);
item.rest_schema = indentStringify(item.rest_schema);
// Generate examples
var reqExample = util.generateExampleSync({ parameters_schema: paramsSchema,
parameters_required: paramsRequired });
var resExample = _.clone(reqExample);
resExample.id = 'eae393ac-b766-4ffc-a69b-d41e37b3f5b2';
resExample.status = 'running';
item.rest_req_example = indentStringify(reqExample);
item.rest_res_example = indentStringify(resExample);
});
});
specRaml = _.template(specRaml)({
title: apiSpec.implementation.title,
executables: apiSpec.executables,
invokers: apiSpec.invokers,
docs: docs
});
_.each([ 'executables', 'invokers' ], function(collection) {
_.each(apiSpec[collection], function(item, name) {
delete item.rest_schema_name;
delete item.rest_schema;
delete item.rest_req_example;
delete item.rest_res_example;
});
});
fs.writeFile(specRamlPath, specRaml, callback);
},
function(callback) {
// Generate docs.html
var config = raml2html.getDefaultConfig();
raml2html.render(specRamlPath, config, function(html) {
fs.writeFile(docsHtmlPath, html, callback);
}, callback);
}
], function(err) {
if (err) return done(err);
done(null, apiSpec);
});
};
obj.generate = generate;
obj.supports = supports;
return obj;
};
var indentStringify = function(input) {
var indent = ' ';
var str = '';
if (!_.isString(input)) input = JSON.stringify(input, null, 2);
var lines = input.split('\n');
_.each(lines, function(line) {
str += indent + line + '\n';
});
return str;
};
var populateSchemaProperties = function(schema, props) {
_.each(schema, function(def, name) {
//def = _.clone(def);
props[name] = { type: def.type };
if (def.type === 'json_object') {
props[name].type = 'object';
if (def.json_schema) {
_.extend(props[name], def.json_schema);
//delete def.json_schema;
}
} else if (def.type === 'json_array') {
props[name].type = 'array';
if (def.json_schema) {
_.extend(props[name], def.json_schema);
//delete def.json_schema;
}
} else if (def.type === 'xml_object' || def.type === 'binary' || def.type === 'string') {
//TODO: consider type and content_type to enrich JSON schema definition
// - https://github.com/json-schema/json-schema/wiki/Media
// - https://github.com/fge/json-schema-formats/wiki
// - http://json-schema.org/latest/json-schema-hypermedia.html
// - http://spacetelescope.github.io/understanding-json-schema/reference/string.html
props[name].type = 'string';
}
if (def.description) props[name].description = def.description;
if (def.default) props[name].default = def.default;
if (def.mapping === 'stdout' || def.mapping === 'stderr') delete props[def.mapping];
});
return props;
};