Skip to content
Snippets Groups Projects
Commit cef79652 authored by Justin Ehlert's avatar Justin Ehlert
Browse files

Simplify template invocation

This is done by reducing duplication of template arguments, and
formalized $include/$template into the flattenSwagger step.
parent f96d1bf9
No related branches found
No related tags found
No related merge requests found
Showing
with 192 additions and 221 deletions
......@@ -76,22 +76,12 @@ module.exports = function(grunt) {
}
},
/**
* Resolve templates within swagger
*/
resolveTemplates: {
core: {
src: 'build/swagger-flat.json',
dest: 'build/swagger-int.json'
}
},
/**
* Resolve schema links in the swagger documentation
*/
resolveSchemaLinks: {
core: {
src: 'build/swagger-int.json',
src: 'build/swagger-flat.json',
dest: 'build/swagger-ui.json'
}
},
......@@ -136,7 +126,6 @@ module.exports = function(grunt) {
grunt.registerTask('build-schema', [
'copy:schema',
'flattenSwagger',
'resolveTemplates',
'resolveSchemaLinks'
]);
......
# Global template arguments for collection
$template_arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections:
get:
summary: List all collections.
......@@ -130,114 +135,45 @@
# ===== Tags =====
/collections/{CollectionId}/tags:
$template: templates/tags.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections/{CollectionId}/tags/{TagValue}:
$template: templates/tags-tag.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
# ===== Packfile =====
/collections/{CollectionId}/packfile-start:
$template: templates/packfile-start.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections/{CollectionId}/packfile:
$template: templates/packfile.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections/{CollectionId}/packfile-end:
$template: templates/packfile-end.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
# ===== Files =====
/collections/{CollectionId}/files:
$template: templates/file-list-upload.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections/{CollectionId}/files/{FileName}:
$template: templates/file-item.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
# ===== Permissions =====
/collections/{CollectionId}/permissions:
$template: templates/permissions.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections/{CollectionId}/permissions/{UserId}:
$template: templates/permissions-user.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
# ===== Notes =====
/collections/{CollectionId}/notes:
$template: templates/notes.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections/{CollectionId}/notes/{NoteId}:
$template: templates/notes-note.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
# ===== Analyses =====
/collections/{CollectionId}/analyses:
$template: templates/analyses-list.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections/{CollectionId}/analyses/{AnalysisId}:
$template: templates/analysis-item.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections/{CollectionId}/analyses/{AnalysisId}/files:
$template: templates/analysis-files.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections/{CollectionId}/analyses/{AnalysisId}/files/{Filename}:
$template: templates/analysis-files-create-ticket-filename.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections/{CollectionId}/analyses/{AnalysisId}/notes:
$template: templates/analysis-notes.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
/collections/{CollectionId}/analyses/{AnalysisId}/notes/{NoteId}:
$template: templates/analysis-notes-item.yaml
arguments:
resource: collection
tag: collections
parameter: CollectionId
\ No newline at end of file
$template_arguments:
resource: group
tag: groups
parameter: GroupId
/groups:
get:
summary: List all groups
......@@ -70,34 +75,6 @@
'200':
$ref: '#/responses/200:deleted-with-count'
# ===== Permissions =====
/groups/{GroupId}/permissions:
$template: templates/permissions.yaml
arguments:
resource: group
tag: groups
parameter: GroupId
/groups/{GroupId}/permissions/{UserId}:
$template: templates/permissions-user.yaml
arguments:
resource: group
tag: groups
parameter: GroupId
# ===== Tags =====
/groups/{GroupId}/tags:
$template: templates/tags.yaml
arguments:
resource: group
tag: groups
parameter: GroupId
/groups/{GroupId}/tags/{TagValue}:
$template: templates/tags-tag.yaml
arguments:
resource: group
tag: groups
parameter: GroupId
/groups/{GroupId}/projects:
parameters:
- required: true
......@@ -117,3 +94,15 @@
examples:
response:
$ref: examples/output/project-list.json
# ===== Permissions =====
/groups/{GroupId}/permissions:
$template: templates/permissions.yaml
/groups/{GroupId}/permissions/{UserId}:
$template: templates/permissions-user.yaml
# ===== Tags =====
/groups/{GroupId}/tags:
$template: templates/tags.yaml
/groups/{GroupId}/tags/{TagValue}:
$template: templates/tags-tag.yaml
'use strict';
var _ = require('lodash');
var path = require('path');
var fs = require('fs');
var process = require('process');
var yaml = require('js-yaml');
var Mustache = require('mustache');
var walk = require('./walk');
// Throws if args are missing
function validateTemplateArgs(tmplpath, template, args) {
var i, param;
if( !template || !template.parameters ) {
return;
}
for( i = 0; i < template.parameters.length; i++ ) {
param = template.parameters[i];
if( !param.name ) {
throw 'Template "' + tmplpath + '" parameter does not have a name!';
}
if( _.isNil(args[param.name]) ) {
throw 'Template "' + tmplpath + '" invocation is missing parameter: ' + param.name;
}
}
}
/**
* @class SwaggerResolver
* Performs stateful resolution of $include and $template directives
* in YAML files.
* @param {object} options The optional configuration
* @param {string} options.path The root path to resolve templates and includes from. (Defaults to cwd)
* @param {function} options.log The optional logging function
*/
var SwaggerResolver = function(options) {
options = options||{};
this.path = options.path||process.cwd();
this.templates = {};
this.log = options.log||function() {
console.log.apply(console, arguments);
};
};
/**
* Recursively resolve any includes and templates in the data.
* @param {object} data The data object
* @param {string} data.text The text data, in string format. (Either JSON or YAML)
*/
SwaggerResolver.prototype.resolveContent = function(data) {
// Reset template arguments for this run
// Possible issue: templates including templates will smash this, can we make it
// more stack oriented?
this.templateArguments = null;
// Do conversion first
var obj = yaml.safeLoad(data.text);
return this.resolveObject(obj);
};
SwaggerResolver.prototype.resolveObject = function(obj) {
// Perform a deep-walk of the object, replacing
return walk(obj, this.visit.bind(this));
};
SwaggerResolver.prototype.loadFile = function(relpath) {
var abspath = path.join(this.path, relpath);
if( !fs.existsSync(abspath) ) {
throw 'File does not exist: ' + abspath;
}
return yaml.safeLoad(fs.readFileSync(abspath).toString());
};
SwaggerResolver.prototype.visit = function(obj) {
// obj will have $template or $include, not both
if( obj.hasOwnProperty('$include') ) {
obj = this.resolveIncludes(obj);
// Recursively resolve content
obj = this.resolveObject(obj);
} else if( obj.hasOwnProperty('$template') ) {
obj = this.resolveTemplate(obj);
// Recursively resolve content
obj = this.resolveObject(obj);
} else if( obj.hasOwnProperty('$template_arguments') ) {
this.templateArguments = obj['$template_arguments'];
delete obj['$template_arguments'];
}
return obj;
};
SwaggerResolver.prototype.resolveIncludes = function(obj) {
var i, includes, inc;
includes = obj['$include'];
if( typeof includes === 'string' ) {
includes = [includes];
}
delete obj['$include'];
for( i = 0; i < includes.length; i++ ) {
// Load the include file
inc = this.loadFile(includes[i]);
// And merge its contents into obj
_.extend(obj, inc);
}
return obj;
};
SwaggerResolver.prototype.resolveTemplate = function(obj) {
var tmplpath, tmpl, args, text;
tmplpath = obj['$template'];
tmpl = this.templates[tmplpath];
if( !tmpl ) {
tmpl = this.loadFile(tmplpath);
this.templates[tmplpath] = tmpl;
Mustache.parse(tmpl.template);
}
// Validate arguments?
args = obj['arguments']||this.templateArguments||{};
validateTemplateArgs(tmplpath, tmpl, args);
// Render the template, and parse
text = Mustache.render(tmpl.template, args);
return yaml.safeLoad(text);
};
module.exports = SwaggerResolver;
......@@ -6,10 +6,7 @@ module.exports = function(grunt) {
var yaml = require('js-yaml');
var resolve = require('json-refs').resolveRefs;
// Use YAML to load nested content
function resolveContent(res, callback) {
callback(undefined, yaml.safeLoad(res.text));
}
var SwaggerResolver = require('../swagger-resolver');
/**
* This task flattens the nested swagger yaml into a single flat file.
......@@ -23,6 +20,15 @@ module.exports = function(grunt) {
grunt.registerMultiTask('flattenSwagger', 'Resolve references in swagger YAML files', function() {
var apiFile = this.data.apiFile||'swagger.yml';
var destFile = this.data.dest||'swagger.json';
var resolver = new SwaggerResolver({
log: function() {
grunt.log.writeln.apply(grunt.log, arguments);
}
});
function resolveContent(res, callback) {
callback(undefined, resolver.resolveContent(res));
}
// See: http://azimi.me/2015/07/16/split-swagger-into-smaller-files.html
// and the corresponding repo: https://github.com/mohsen1/multi-file-swagger-example
......@@ -36,6 +42,15 @@ module.exports = function(grunt) {
});
var root = yaml.safeLoad(fs.readFileSync(apiFile).toString());
// Resolve any top-level includes or templates
try {
root = resolver.resolveObject(root);
} catch (e) {
grunt.log.writeln("Could not resolve root:", e);
return false;
}
var resolveOpts = {
filter: ['relative'],
loaderOptions: {
......
'use strict';
module.exports = function(grunt) {
var path = require('path');
var fs = require('fs');
var process = require('process');
var yaml = require('js-yaml');
var mustache = require('mustache');
var _ = require('lodash');
var walk = require('../walk');
function resolveTemplatesFunc(templates) {
var loaded_templates = {};
return function(obj) {
return walk(obj, function(obj) {
var tmplpath, tmpl;
if( obj.hasOwnProperty('$template') ) {
tmplpath = path.join(templates, obj['$template']);
if( !loaded_templates[tmplpath] ) {
if( !fs.existsSync(tmplpath) ) {
throw 'Template file does not exist: ' + tmplpath;
}
loaded_templates[tmplpath] = yaml.safeLoad(fs.readFileSync(tmplpath).toString());
}
tmpl = _.cloneDeep(loaded_templates[tmplpath]['template']);
obj = walk(tmpl, resolveParamsFunc(obj['arguments']));
}
return obj;
});
};
}
function resolveIncludesFunc(templates) {
return function(obj) {
return walk(obj, function(obj) {
var i, includes, incpath, inc;
if( obj.hasOwnProperty('$include') ) {
includes = obj['$include'];
delete obj['$include'];
for( i = 0; i < includes.length; i++ ) {
incpath = path.join(templates, includes[i]);
if( !fs.existsSync(incpath) ) {
throw 'Included file does not exist: ' + incpath;
}
inc = yaml.safeLoad(fs.readFileSync(incpath).toString());
_.extend(obj, inc);
}
}
return obj;
});
};
}
function resolveParamsFunc(args) {
return function(obj) {
if( typeof obj === 'string' ) {
return mustache.render(obj, args||{});
}
return obj;
};
}
grunt.registerMultiTask('resolveTemplates', 'Resolve templates in swagger JSON file', function() {
var src = this.data.src;
var dest = this.data.dest;
var templates = this.data.templates||process.cwd();
var resolveTemplates = resolveTemplatesFunc(templates);
var resolveIncludes = resolveIncludesFunc(templates);
if(!fs.existsSync(src)) {
grunt.log.writeln('Could not find:', src);
return false;
}
var root = JSON.parse(fs.readFileSync(src).toString());
try {
root = resolveIncludes(root);
root = resolveTemplates(root);
} catch( e ) {
grunt.log.writeln('Error resolving templates:', e);
return false;
}
var data = JSON.stringify(root, null, 2);
fs.writeFileSync(dest, data);
});
};
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
......@@ -5,7 +5,7 @@ parameters:
type: string
- name: tag
type: string
template:
template: |
parameters:
- required: true
type: string
......
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