Skip to content

Commit

Permalink
feat(handlers): Leverage responses to identify models
Browse files Browse the repository at this point in the history
Use the defined response schema to determine the model for a given
handler and then fallback to using the path name if no response
schema is defined.

closes #7
  • Loading branch information
kenjones-cisco committed Jan 7, 2016
1 parent a1b8dd1 commit 3a7c2e0
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 110 deletions.
12 changes: 4 additions & 8 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ var eslint = require('gulp-eslint');
var coveralls = require('gulp-coveralls');
var publish = require('publish-please');

var SOURCE_CODE = ['app/*.js', 'spec/*.js', 'models/*.js', 'handlers/*.js'];
var SOURCE_CODE = ['app/*.js', 'spec/*.js', 'models/*.js', 'handlers/*.js', 'lib/*.js'];
var TEST_CODE = ['test/*.js'];

gulp.task('pre-test', function () {
Expand All @@ -18,20 +18,16 @@ gulp.task('pre-test', function () {
.pipe(istanbul.hookRequire());
});

gulp.task('test', ['lint', 'pre-test'], function () {
gulp.task('test', ['lint'], function () {
return gulp.src(TEST_CODE)
.pipe(jasmine())
// Creating the reports after tests ran
.pipe(istanbul.writeReports());
.pipe(jasmine());
});

gulp.task('cover', ['lint', 'pre-test'], function () {
return gulp.src(TEST_CODE)
.pipe(jasmine())
// Creating the reports after tests ran
.pipe(istanbul.writeReports({
reporters: ['html', 'lcov']
}));
.pipe(istanbul.writeReports());
});

gulp.task('lint', function () {
Expand Down
48 changes: 31 additions & 17 deletions handlers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var mkdirp = require('mkdirp');
var methods = require('swagger-methods');
var pluralize = require('pluralize');
var upath = require('upath');
var specutil = require('../lib/specutil');
var helpers = require('../lib/helpers');
var debug = helpers.debug;

Expand Down Expand Up @@ -97,6 +98,7 @@ module.exports = yeoman.Base.extend({
route._ = _;
route.dbmodels = self._getDbModels(route, path.dirname(file));
if (!self.options['dry-run']) {
debug('generating handler %s', file);
self.template('_handler.js', file, route);
} else {
self.log.ok('(DRY-RUN) handler %s generated', file);
Expand Down Expand Up @@ -206,28 +208,40 @@ module.exports = yeoman.Base.extend({
_getDbModels: function getDbModels(route, relPath) {
var self = this;
var dbModels = [];
var single, dbFileName;
var schemas, dbFileName;

if (!self.config.get('database')) {
if (!self.config.get('database') && !this.options.database) {
return null;
}

route.path.split('/').forEach(function (element) {
if (element) {
single = pluralize.singular(element);
debug('element: %s single: %s', element, single);
dbFileName = upath.addExt(upath.joinSafe(self.appRoot, 'models', single), '.js');
if (self.fs.exists(dbFileName)) {
debug('handler dbmodel rel path: %s', upath.removeExt(
upath.toUnix(path.relative(relPath, dbFileName)), '.js'));
dbModels.push({
name: _s.classify(single),
path: upath.removeExt(
upath.toUnix(path.relative(relPath, dbFileName)), '.js')
});
}
function handleDbFile(name) {
dbFileName = upath.addExt(
upath.joinSafe(self.appRoot, 'models', name.toLowerCase()), '.js');
if (self.fs.exists(dbFileName)) {
debug('handler dbmodel rel path: %s', upath.removeExt(
upath.toUnix(path.relative(relPath, dbFileName)), '.js'));
dbModels.push({
name: _s.classify(name),
path: upath.removeExt(
upath.toUnix(path.relative(relPath, dbFileName)), '.js')
});
}
});
}

schemas = specutil.getRespSchema(route);
if (!_.isEmpty(schemas)) {
_.forEach(schemas, function (schema) {
handleDbFile(schema);
});
} else {
debug('no schemas defined in responses; using path');
route.path.split('/').forEach(function (element) {
if (element) {
handleDbFile(pluralize.singular(element));
}
});
}

debug(dbModels);
return dbModels;
}
Expand Down
116 changes: 116 additions & 0 deletions lib/specutil.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*eslint quotes: [2, "single", "avoid-escape"]*/
'use strict';

var _ = require('lodash');
var _s = require('underscore.string');
var debug = require('util').debuglog('generator-swagapi');

var helpers = module.exports;


var refRegExp = /^#\/definitions\/(\w*)$/;

var allowedTypes = [
'integer',
'long',
'float',
'double',
'number',
'string',
'password',
'boolean',
'date',
'dateTime',
'array',
'object'
];

function propertyMap(property) {
switch (property.type) {
case 'integer':
case 'long':
case 'float':
case 'double':
case 'number':
return 'Number';
case 'string':
case 'password':
return 'String';
case 'boolean':
return 'Boolean';
case 'date':
case 'dateTime':
return 'Date';
case 'array':
return [propertyMap(property.items)];
case 'object':
return helpers.getSchema(property.properties);
default:
throw new Error('Unrecognized schema type: ' + property.type);
}
}

function isSimpleSchema(schema) {
return schema.type && allowedTypes.indexOf(schema.type) !== -1;
}

helpers.getPropertyRef = function getPropertyRef(property) {
if (_.get(property, '$ref')) {
return _.get(property, '$ref');
} else if (property.type === 'array' && _.get(property.items, '$ref')) {
return _.get(property.items, '$ref');
}
return null;
};

helpers.getSchema = function getSchema(object, subSchema) {
var refString;
var propType;
var props = {};
subSchema = subSchema || {};

_.forEach(object, function (property, key) {
refString = helpers.getPropertyRef(property);
if (refString) {
propType = refString.match(refRegExp)[1];
if (subSchema[propType]) {
props[key] = [propType + 'Schema'];
} else {
props[key] = {
type: 'mongoose.Schema.Types.ObjectId',
ref: _s.quote(propType, "'")
};
}
} else if (property.type === 'object') {
props[key] = helpers.getSchema(property.properties, subSchema);
} else if (property.type) {
props[key] = propertyMap(property);
} else if (isSimpleSchema(object)) {
props = propertyMap(object);
}
});

return props;
};

helpers.formatProperty = function formatProperty(property) {
return JSON.stringify(property).replace(/"/g, '');
};

helpers.getRespSchema = function getRespSchema(object) {
var refString;
var schemas = [];

_.forEach(object.methods, function (method) {
_.forEach(method.responses, function (resp, code) {
debug('checking response code: %s', code);
refString = helpers.getPropertyRef(resp.schema || {});
if (refString) {
schemas.push(refString.match(refRegExp)[1]);
}
});
});

debug(schemas);
return _.uniq(schemas);
};
2 changes: 2 additions & 0 deletions models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var yeoman = require('yeoman-generator');
var _ = require('lodash');
var mkdirp = require('mkdirp');
var upath = require('upath');
var specutil = require('../lib/specutil');
var helpers = require('../lib/helpers');
var debug = helpers.debug;

Expand Down Expand Up @@ -69,6 +70,7 @@ module.exports = yeoman.Base.extend({

// provides access to lodash within the template
model._ = _;
model.helpers = specutil;

if (self.config.get('database')) {
debug('generating mongoose enabled model: %s', modelName);
Expand Down
79 changes: 4 additions & 75 deletions models/templates/_model_mongoose.js
Original file line number Diff line number Diff line change
@@ -1,100 +1,29 @@
'use strict';
var mongoose = require('mongoose');
<%
var allowedTypes = ['integer', 'long', 'float', 'double', 'number', 'string', 'password', 'boolean', 'date', 'dateTime', 'array', 'object'];
var propertyMap = function (property) {
switch (property.type) {
case 'integer':
case 'long' :
case 'float' :
case 'double' :
case 'number' :
return 'Number';
case 'string':
case 'password':
return 'String';
case 'boolean':
return 'Boolean';
case 'date':
case 'dateTime':
return 'Date';
case 'array':
return [propertyMap(property.items)];
case 'object':
return getSchema(property.properties);
default:
throw new Error('Unrecognized schema type: ' + property.type);
}
};

var isSimpleSchema = function(schema) {
return schema.type && isAllowedType(schema.type);
};

var isAllowedType = function(type) {
return allowedTypes.indexOf(type) != -1;
};

var isPropertyHasRef = function(property) {
return property['$ref'] || ((property['type'] == 'array') && (property['items']['$ref']));
};

var getSchema = function(object, subSchema) {
var props = {};
subSchema = subSchema || {};

_.forEach(object, function (property, key) {
if (isPropertyHasRef(property)) {
var refRegExp = /^#\/definitions\/(\w*)$/;
var refString = property['$ref'] ? property['$ref'] : property['items']['$ref'];
var propType = refString.match(refRegExp)[1];
if (subSchema[propType]) {
props[key] = [propType + 'Schema'];
} else {
props[key] = {type: "mongoose.Schema.Types.ObjectId", ref: "'" + propType + "'"}
}
}
else if (property.type === 'object') {
props[key] = getSchema(property.properties, subSchema);
}
else if (property.type) {
props[key] = propertyMap(property);
}
else if (isSimpleSchema(object)) {
props = propertyMap(object);
}
});

return props;
};

var formatProperty = function(property) {
return JSON.stringify(property).replace(/"/g,"");
};%>

var <%=id%> = function () {
<% _.forEach(children, function (child, childName) {%>
var <%=childName%>Schema = mongoose.Schema({<%
var props = getSchema(child.properties);
var props = helpers.getSchema(child.properties);
var totalProps = 1;
if (typeof props === 'object') {
totalProps = Object.keys(props).length;
}
var cnt = 0;
_.forEach(props, function (property, key) {%>
<%=key%>: <%=formatProperty(property)%><% if (totalProps - 1 !== cnt ) { %>,<% cnt += 1; }; %><%})%>
<%=key%>: <%=helpers.formatProperty(property)%><% if (totalProps - 1 !== cnt ) { %>,<% cnt += 1; }; %><%})%>
});
<%})%>

var <%=id%>Schema = mongoose.Schema({<%
var props = getSchema(properties, children);
var props = helpers.getSchema(properties, children);
var totalProps = 1;
if (typeof props === 'object') {
totalProps = Object.keys(props).length;
}
var cnt = 0;
_.forEach(props, function (property, key) {%>
<%=key%>: <%-formatProperty(property)%><% if (totalProps - 1 !== cnt ) { %>,<% cnt += 1; }; %><%})%>
<%=key%>: <%-helpers.formatProperty(property)%><% if (totalProps - 1 !== cnt ) { %>,<% cnt += 1; }; %><%})%>
});
return mongoose.model(<%-"'"+id+"'"%>, <%=id%>Schema);
};
Expand Down
20 changes: 10 additions & 10 deletions test/fixtures/pets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,6 @@ paths:
responses:
"200":
description: pet response
schema:
$ref: "#/definitions/Pet"
default:
description: unexpected error
schema:
$ref: "#/definitions/Error"
delete:
description: deletes a single pet based on the ID supplied
operationId: deletePet
Expand All @@ -111,10 +105,7 @@ paths:
responses:
"204":
description: pet deleted
default:
description: unexpected error
schema:
$ref: "#/definitions/Error"

definitions:
Pet:
type: object
Expand Down Expand Up @@ -153,6 +144,15 @@ definitions:
type: string
name:
type: string
alphas:
type: array
items:
$ref: '#/definitions/Kittena'
betas:
type: array
items:
$ref: '#/definitions/Kittenb'

Kittena:
x-parent: Cat
type: object
Expand Down
1 change: 1 addition & 0 deletions test/test_handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('swaggerize:handlers', function () {
base.args = ['foo'];
base.options.api = jsYaml.safeLoad(
fs.readFileSync(path.join(__dirname, 'fixtures/pets.yaml')));
base.options.database = 'test';

testutil.run(base, function (err) {
if (err) {
Expand Down

0 comments on commit 3a7c2e0

Please sign in to comment.