Skip to content
Snippets Groups Projects
Unverified Commit 86da1841 authored by Justin Ehlert's avatar Justin Ehlert Committed by GitHub
Browse files

Merge pull request #1094 from scitran/swagger-fix-undocumented-codegen

Swagger changes for code generation
parents b1edeca2 1ee8ba10
No related branches found
No related tags found
No related merge requests found
Showing
with 421 additions and 66 deletions
......@@ -8,7 +8,10 @@
"inputs-item": {
"type":"object",
"properties":{
"type":{"enum":["http", "scitran"]},
"type":{
"type": "string",
"enum":["http", "scitran"]
},
"uri":{"type":"string"},
"location":{"type":"string"},
"vu":{"type":"string"}
......@@ -149,7 +152,8 @@
"saved_files":{"$ref":"#/definitions/saved_files"},
"produced_metadata":{"$ref":"#/definitions/produced-metadata"}
},
"additionalProperties":false
"additionalProperties":false,
"x-sdk-model":"job"
},
"job-input": {
"type":"object",
......@@ -161,7 +165,8 @@
"config":{"$ref":"#/definitions/config"}
},
"required": ["gear_id"],
"additionalProperties":false
"additionalProperties":false,
"x-sdk-model":"job"
},
"job-output": {
"type": "object",
......@@ -169,7 +174,8 @@
"required": [
"id", "gear_id", "inputs", "config",
"destination", "tags", "state", "attempt"
]
],
"x-sdk-model":"job"
}
}
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"text": {"type": "string"},
"text": {
"type": "string",
"x-sdk-positional": true
},
"note-input":{
"type":"object",
"properties":{
"text":{"$ref":"#/definitions/text"}
},
"additionalProperties": false
"additionalProperties": false,
"x-sdk-model": "note"
},
"notes-list-input": {
"type": "array",
......@@ -23,7 +27,8 @@
"user":{"$ref":"common.json#/definitions/user-id"}
},
"additionalProperties": false,
"required":["_id", "text", "created", "modified", "user"]
"required":["_id", "text", "created", "modified", "user"],
"x-sdk-model": "note"
},
"notes-list-output":{
"type":"array",
......
......@@ -47,13 +47,15 @@
"packfile": {"$ref":"#/definitions/packfile-packfile-input"}
},
"required": ["project", "session", "acquisition", "packfile"],
"additionalProperties": false
"additionalProperties": false,
"x-sdk-model": "packfile"
},
"packfile-start": {
"type":"object",
"properties":{
"token":{"$ref":"common.json#/definitions/objectid"}
}
"token":{"type": "string"}
},
"x-sdk-return": "token"
}
}
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"access": { "enum": ["ro", "rw", "admin"] },
"access": {
"type": "string",
"enum": ["ro", "rw", "admin"]
},
"permission":{
"type":"object",
"properties":{
"_id":{"$ref":"common.json#/definitions/user-id"},
"access":{"$ref":"#/definitions/access"}
},
"additionalProperties": false
"additionalProperties": false,
"x-sdk-model": "permission"
},
"permission-output-default-required":{
"allOf":[{"$ref":"#/definitions/permission"}],
"required":["_id", "access"]
"required":["_id", "access"],
"x-sdk-model": "permission"
},
"permission-output-list": {
"type": "array",
......
......@@ -10,7 +10,8 @@
"description": {"$ref":"common.json#/definitions/description"},
"group": {"$ref":"common.json#/definitions/string-id"}
},
"additionalProperties": false
"additionalProperties": false,
"x-sdk-model": "project"
},
"project-metadata-input": {
"type": "object",
......@@ -52,7 +53,8 @@
"items":{"$ref":"analysis.json#/definitions/analysis-output"}
}
},
"additionalProperties": false
"additionalProperties": false,
"x-sdk-model": "project"
}
}
}
......@@ -35,7 +35,8 @@
"all": { "$ref": "#/definitions/rule-items" },
"disabled": { "type": "boolean" }
},
"additionalProperties": false
"additionalProperties": false,
"x-sdk-model": "rule"
},
"rule-output": {
......@@ -47,7 +48,8 @@
"any": { "$ref": "#/definitions/rule-items" },
"all": { "$ref": "#/definitions/rule-items" },
"disabled": { "type": "boolean" }
}
},
"x-sdk-model": "rule"
}
}
}
......@@ -18,7 +18,8 @@
"timezone": {"$ref":"container.json#/definitions/timezone"},
"subject": {"$ref": "subject.json#/definitions/subject-input"}
},
"additionalProperties": false
"additionalProperties": false,
"x-sdk-model": "session"
},
"session-metadata-input": {
"type": "object",
......@@ -69,7 +70,8 @@
"items":{"$ref":"analysis.json#/definitions/analysis-output"}
}
},
"additionalProperties": false
"additionalProperties": false,
"x-sdk-model": "session"
},
"session-jobs-output": {
"type": "object",
......@@ -79,6 +81,7 @@
"items":{"$ref": "job.json#/definitions/job-output"}
},
"containers":{
"type": "object",
"patternProperties": {
"^[a-fA-F0-9]{24}$":{
"type": "object"
......
......@@ -5,10 +5,24 @@
"firstname": { "type": "string", "maxLength": 64 },
"lastname": { "type": "string", "maxLength": 64 },
"age": { "type": ["integer", "null"] },
"sex": { "enum": ["male", "female", "other", "unknown", null] },
"race": { "enum": ["American Indian or Alaska Native", "Asian", "Native Hawaiian or Other Pacific Islander", "Black or African American", "White", "More Than One Race", "Unknown or Not Reported", null] },
"ethnicity": { "enum": ["Not Hispanic or Latino", "Hispanic or Latino", "Unknown or Not Reported", null] },
"sex": {
"oneOf": [
{ "type": "null"},
{ "type": "string", "enum": ["male", "female", "other", "unknown"] }
]
},
"race": {
"oneOf": [
{ "type": "null"},
{ "type": "string", "enum": ["American Indian or Alaska Native", "Asian", "Native Hawaiian or Other Pacific Islander", "Black or African American", "White", "More Than One Race", "Unknown or Not Reported"] }
]
},
"ethnicity": {
"oneOf": [
{ "type": "null"},
{ "type": "string", "enum": ["Not Hispanic or Latino", "Hispanic or Latino", "Unknown or Not Reported"] }
]
},
"code": { "type": "string", "maxLength": 64 },
"tags": { "type": "array", "items": {"type": "string"} },
"subject-input":{
......@@ -31,7 +45,8 @@
"items":{"$ref":"file.json#/definitions/file-input"}
}
},
"additionalProperties": false
"additionalProperties": false,
"x-sdk-model": "subject"
},
"subject-output":{
"type": "object",
......@@ -54,11 +69,13 @@
"items":{"$ref":"file.json#/definitions/file-output"}
}
},
"additionalProperties": false
"additionalProperties": false,
"x-sdk-model": "subject"
},
"subject-output-default-required":{
"allOf":[{"$ref":"#/definitions/subject-output"}],
"required":["_id"]
"required":["_id"],
"x-sdk-model": "subject"
}
}
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"value": {"type": "string", "minLength": 1, "maxLength": 32},
"value": {
"type": "string",
"minLength": 1,
"maxLength": 32,
"x-sdk-positional": true
},
"tag":{
"type": "object",
"properties":{
"value":{"$ref":"#/definitions/value"}
},
......@@ -12,7 +18,7 @@
"tag-list":{
"type":"array",
"items":{
"allOf":[{"$ref":"#/definitions/tag"}]
"allOf":[{"type":"string"}]
}
}
}
......
......@@ -37,7 +37,7 @@
"properties":{
"key": {"type": "string"},
"created": {"$ref":"created-modified.json#/definitions/created"},
"last_used": {}
"last_used": {"$ref":"common.json#/definitions/timestamp"}
},
"additionalProperties":false
},
......@@ -57,7 +57,8 @@
"firstlogin":{"$ref":"#/definitions/firstlogin"},
"lastlogin":{"$ref":"#/definitions/lastlogin"}
},
"additionalProperties":false
"additionalProperties":false,
"x-sdk-model": "user"
},
"user-output":{
"type":"object",
......@@ -77,7 +78,8 @@
"created":{"$ref":"created-modified.json#/definitions/created"},
"modified":{"$ref":"created-modified.json#/definitions/modified"}
},
"additionalProperties":false
"additionalProperties":false,
"x-sdk-model": "user"
},
"user-output-api-key": {
"type":"object",
......@@ -102,7 +104,8 @@
"required":[
"_id", "firstname", "lastname",
"root", "email", "created", "modified"
]
],
"x-sdk-model": "user"
}
}
}
......@@ -2,6 +2,6 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Analysis",
"type": "object",
"allOf":[{"$ref":"../definitions/analysis.json#/definitions/analysis-input"}],
"allOf":[{"$ref":"../definitions/analysis.json#/definitions/analysis-input-any"}],
"required": ["label"]
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"allOf":[{"$ref":"../definitions/auth.json#/definitions/login-output"}],
"example": {
"token": "MjeuawZcctfRdCOmx_C6oYXK4sLHd2Dhc_oZpkXPPkxHizhNgwFWcrrKGA49BEnK"
}
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"allOf":[{"$ref":"../definitions/auth.json#/definitions/logout-output"}],
"example": {
"tokens_removed": 1
}
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "../definitions/common.json#/definitions/object-created",
"allOf": [{"$ref": "../definitions/common.json#/definitions/object-created"}],
"example": {
"_id": "jane.doe@gmail.com"
}
......
......@@ -2,12 +2,10 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"allOf":[
{"$ref":"../definitions/user.json#/definitions/user-output"},
{
"required":[
"_id", "firstname", "lastname",
"root", "email", "created", "modified"
]
}
{"$ref":"../definitions/user.json#/definitions/user-output"}
],
"required":[
"_id", "firstname", "lastname",
"root", "email", "created", "modified"
]
}
......@@ -74,11 +74,6 @@ SchemaTranspiler.prototype.draft4ToOpenApi2 = function(schema, defs, id) {
schema.type = this._selectTypeFromArray(schema.type, id);
}
if( schema.allOf && schema.allOf.length === 1 && !schema.required ) {
// Merge all of object with top-level object
schema = this._flattenAllOf(schema, id);
}
// Check for top-level $ref, allOf, anyOf, oneOf
if( schema.$ref && schema.example ) {
// Special case, if object has $ref and example, then
......@@ -102,7 +97,11 @@ SchemaTranspiler.prototype.draft4ToOpenApi2 = function(schema, defs, id) {
}
if( schema.patternProperties ) {
this.warn(id, '"patternProperties" is not supported in OpenApi 2');
var keys = _.keys(schema.patternProperties);
if( keys.length > 1 ) {
this.warn(id, 'Can only support one type in additionalProperties (from "patternProperties")');
}
schema.additionalProperties = this.draft4ToOpenApi2(schema.patternProperties[keys[0]], defs, id);
delete schema.patternProperties;
}
......
......@@ -15,10 +15,25 @@ var PRIMITIVE_TYPES = {
'null': true
};
var OBJECT_PROPERTIES = [ 'allOf', 'anyOf', 'oneOf', 'multipleOf', 'not',
'if', 'then', 'else', 'properties', 'additionalProperties' ];
function isPrimitiveType(type) {
return !!PRIMITIVE_TYPES[type];
}
function isEmptyObject(schema) {
if( schema.type && schema.type !== 'object' ) {
return false;
}
if( schema.$ref ) {
return false;
}
return !_.some(OBJECT_PROPERTIES, function(key) {
return !!schema[key];
});
}
function normalizeName(name) {
return name.replace('_', '-');
}
......@@ -199,6 +214,8 @@ Schemas.prototype.isPrimitiveDef = function(name) {
return false;
};
Schemas.isPrimitiveType = isPrimitiveType;
Schemas.isEmptyObject = isEmptyObject;
Schemas.prototype.getComplexDefinitions = function() {
return _.pickBy(this.definitions, function(value) {
......
......@@ -72,13 +72,15 @@ describe('SchemaTranspiler draft4ToOpenApi2', function() {
});
});
it('should flatten allOf with one element', function() {
it('should not flatten allOf with one element', function() {
var schema = {
allOf: [{$ref:'#/definitions/Foo'}]
};
var result = transpiler.toOpenApi2(schema);
expect(result).toEqual({$ref:'#/definitions/Foo'});
expect(result).toEqual({
allOf: [{$ref:'#/definitions/Foo'}]
});
});
it('should merge properties for anyOf', function() {
......@@ -122,28 +124,16 @@ describe('SchemaTranspiler draft4ToOpenApi2', function() {
expect(result).toEqual({});
});
it('should flatten array elements', function() {
var defs = {
Foo: {
type: 'object',
properties: {
updated: {type: 'boolean'}
},
required: ['updated']
}
},
schema = {
it('should not flatten array elements', function() {
var schema = {
type: 'array',
items: {
allOf: [{$ref:'#/definitions/Foo'}]
}
};
var result = transpiler.toOpenApi2(schema, defs);
expect(result).toEqual({
type: 'array',
items: {$ref:'#/definitions/Foo'}
});
var result = transpiler.toOpenApi2(schema);
expect(result).toEqual(schema);
});
it('should recurse into properties', function() {
......@@ -160,7 +150,7 @@ describe('SchemaTranspiler draft4ToOpenApi2', function() {
type: 'object',
properties: {
bar: {type: 'string'},
foo: {$ref: '#/definitions/Foo'}
foo: {allOf: [{$ref: '#/definitions/Foo'}]}
}
});
});
......
'use strict';
module.exports = function(grunt) {
var path = require('path');
var fs = require('fs');
var _ = require('lodash');
var yaml = require('js-yaml');
var walk = require('../walk');
var Schemas = require('../schemas');
/**
* This task simplifies models in a swagger file.
* @param {object} data Task data
* @param {string} data.src The input file (root level swagger file)
* @param {string} data.dst The output file
*/
grunt.registerMultiTask('simplifySwagger', 'Simplify models in swagger API file', function() {
var srcFile = this.data.src||'swagger.yaml';
var dstFile = this.data.dst;
if(!fs.existsSync(srcFile)) {
grunt.log.error('Could not find:', srcFile);
return false;
}
var root = yaml.safeLoad(fs.readFileSync(srcFile).toString());
var context = {
aliases: {}
};
try {
// Merge models
// for example, this will merge group-input and group-output into group based on the
// x-sdk-model property
mergeModels(root, context);
} catch( e ) {
grunt.fail.warn('ERROR: '.red + ' ' + e);
}
// Walk through definitions, simplifying models where we can
simplifyDefinitions(root, context);
// walk through all schemas
// That's every definition and every response and body schema
root = walk(root, function(obj, path) {
if( isSchema(path) ) {
return simplifySchema(obj, path, context);
}
return obj;
});
var data = JSON.stringify(root, null, 2);
fs.writeFileSync(dstFile, data);
});
function formatPath(path) {
path = _.map(path, function(el) {
return el.replace(/\//g, '~1');
});
return '#/' + path.join('/');
}
function unformatPath(path) {
if( !path.substr ) {
grunt.log.writeln('Invalid path: ' + JSON.stringify(path));
return path;
}
var parts = path.substr(2).split('/');
return _.map(parts, function(el) {
return el.replace(/~1/g, '/');
});
}
function isSchema(path) {
if( path.length === 2 && path[0] === 'definitions' ) {
return true;
}
if( path.length === 4 && path[0] === 'definitions' && path[2] === 'properties' ) {
return true;
}
if( path.length > 1 && path[path.length-1] === 'schema' ) {
return true;
}
return false;
}
function isValidSchema(schema) {
return( schema.type || schema.$ref ||
schema.allOf || schema.oneOf || schema.anyOf || schema.not );
}
function isDefinition(path) {
return ( path.length === 2 && path[0] === 'definitions' );
}
function simplifyDefinitions(root, context) {
var defs = root.definitions||{};
var keys = _.keys(defs);
_.each(keys, function(k) {
var schema = defs[k];
var path = formatPath(['definitions', k]);
if( schema.type === 'array' ) {
// Setup an alias for array objects (don't generate a model)
context.aliases[path] = simplifySchema(schema, ['definitions', k], context);
delete defs[k];
} else if( schema.allOf && schema.allOf.length === 1 && schema.allOf[0].$ref ) {
// For objects that are just aliases for other objects, copy all of the properties
var target = unformatPath(schema.allOf[0].$ref);
var targetObj = resolvePathObj(root, target);
if( targetObj ) {
defs[k] = targetObj;
} else {
grunt.log.writeln('ERROR '.red + 'Cannot find alias for: ' + path + ' (' + schema.allOf[0].$ref + ')');
}
} else if( schema.$ref ) {
// Replace pure references
context.aliases[path] = schema;
delete defs[k];
} else if( Schemas.isPrimitiveType(schema.type) ) {
// For simple types in definitions, alias them
context.aliases[path] = schema;
delete defs[k];
}
});
}
// Performs all of the simplifying steps, and
// returns a simplified version of schema
function simplifySchema(schema, path, context) {
schema = _.cloneDeep(schema);
// If an x-sdk-schema is specified, use that
if( schema['x-sdk-schema'] ) {
schema = schema['x-sdk-schema'];
}
if( !isValidSchema(schema) ) {
grunt.log.writeln('WARNING '.red + 'Invalid schema (no object type specified) at: ' + formatPath(path));
schema.type = 'object';
} else if( schema.type === 'array' && schema.items ) {
path = _.concat(path, 'items');
schema.items = simplifySchema(schema.items, path, context);
} else if( schema.allOf ) {
if( schema.allOf.length === 1 ) {
if( schema.allOf[0].$ref ) {
var alias = context.aliases[schema.allOf[0].$ref];
// Replace alias for allOf fields
if( alias ) {
schema = _.cloneDeep(alias);
} else {
schema = schema.allOf[0];
}
} else if( Schemas.isPrimitiveType(schema.allOf[0].type) ) {
schema = schema.allOf[0];
} else {
grunt.log.writeln('WARNING Cannot simplify "allOf" definition at: ' + formatPath(path));
}
} else {
// Still replace aliases
for( var i = 0; i < schema.allOf.length; i++ ) {
var alias = context.aliases[schema.allOf[i].$ref];
if( alias ) {
schema.allOf[i] = _.cloneDeep(alias);
}
}
// It's not an error to not simplify polymorphic types
if( !schema['x-discriminator-value'] ) {
grunt.log.writeln('WARNING Cannot simplify "allOf" definition at: ' + formatPath(path));
}
}
} else if( schema.$ref ) {
// Replace alias for $ref fields
var alias = context.aliases[schema.$ref];
if( alias ) {
schema = _.cloneDeep(alias);
}
}
return schema;
}
// Merge all models that have the x-sdk-model property
function mergeModels(root, context) {
var defs = root.definitions||{};
var keys = _.keys(defs);
var models = {};
var aliases = {};
// First collect all the models to be merged
_.each(keys, function(k) {
var schema = defs[k];
if( schema['x-sdk-model'] ) {
var modelName = schema['x-sdk-model'];
if( !models[modelName] ) {
models[modelName] = [];
}
models[modelName].push({
id: k,
schema: schema
});
// Create temporary aliases for comparing properties
aliases['#/definitions/' + k] = '#/definitions/' + modelName;
}
});
// Then perform the merge
keys = _.keys(models);
_.each(keys, function(modelName) {
var schemas = models[modelName];
var schema = _.cloneDeep(schemas[0]).schema;
var refSchema = {
$ref: '#/definitions/' + modelName
};
for( var i = 1; i < schemas.length; i++ ) {
// Merge each schema into the current
mergeSchema(modelName, schema, schemas[i], aliases);
}
// Add aliases and delete the original models
for( var i = 0; i < schemas.length; i++ ) {
var id = schemas[i].id;
context.aliases['#/definitions/' + id] = refSchema;
delete defs[id];
}
// Remove fields that are no longer relevant
delete schema['x-sdk-model'];
delete schema['required'];
defs[modelName] = schema;
});
}
function mergeSchema(name, schema, src, aliases) {
schema.properties = schema.properties||{};
var dstProps = schema.properties;
var srcProps = src.schema.properties||{};
var keys = _.keys(srcProps);
_.each(keys, function(k) {
// Compare, after resolving aliases
// This way, file-input and file-output resolve to file-entry (for example)
// and are treated as the same for comparison purposes
var srcProp = resolveAlias(srcProps[k], aliases);
var dstProp = resolveAlias(dstProps[k], aliases);
if( dstProp && !_.isEqual(srcProp, dstProp) ) {
throw 'Cannot merge model ' + src.id + ' into ' + name + ': incompatible "' + k + '" property';
} else {
dstProps[k] = srcProp;
}
});
}
function resolveAlias(schema, aliases) {
// Simple alias resolution where aliases is a map of:
// #/definition/model1 to #/defintion/model2
if( !schema ) {
return schema;
}
return walk(schema, function(obj) {
if( obj.$ref ) {
var alias = aliases[obj.$ref];
if( alias ) {
return _.extend({}, obj, { $ref: alias });
}
}
return obj;
});
}
function resolvePathObj(root, path) {
var current = root;
path = path.slice();
while( current && path.length ) {
current = current[path.shift()];
}
return current;
}
};
......@@ -52,6 +52,7 @@ template: |
in: query
type: boolean
description: Return job as an object instead of an id
x-sdk-default: 'true'
responses:
'200':
description: Returns the id of the analysis that was created.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment