From cef79652260fdcc84d1ffeb1c05a7fdcd1a5e9b0 Mon Sep 17 00:00:00 2001 From: Justin Ehlert <justinehlert@flywheel.io> Date: Wed, 13 Dec 2017 10:09:52 -0600 Subject: [PATCH] Simplify template invocation This is done by reducing duplication of template arguments, and formalized $include/$template into the flattenSwagger step. --- swagger/Gruntfile.js | 13 +- swagger/paths/collections.yaml | 74 +--------- swagger/paths/groups.yaml | 45 +++--- swagger/support/swagger-resolver.js | 136 ++++++++++++++++++ swagger/support/tasks/flatten-swagger.js | 23 ++- swagger/support/tasks/resolve-templates.js | 94 ------------ swagger/templates/analyses-list.yaml | 2 +- ...analysis-files-create-ticket-filename.yaml | 2 +- swagger/templates/analysis-files.yaml | 2 +- swagger/templates/analysis-item.yaml | 2 +- swagger/templates/analysis-notes-item.yaml | 2 +- swagger/templates/analysis-notes.yaml | 2 +- swagger/templates/file-item.yaml | 2 +- swagger/templates/file-list-upload.yaml | 2 +- swagger/templates/notes-note.yaml | 2 +- swagger/templates/notes.yaml | 2 +- swagger/templates/packfile-end.yaml | 2 +- swagger/templates/packfile-start.yaml | 2 +- swagger/templates/packfile.yaml | 2 +- swagger/templates/permissions-user.yaml | 2 +- swagger/templates/permissions.yaml | 2 +- swagger/templates/tags-tag.yaml | 2 +- swagger/templates/tags.yaml | 2 +- 23 files changed, 195 insertions(+), 224 deletions(-) create mode 100644 swagger/support/swagger-resolver.js delete mode 100644 swagger/support/tasks/resolve-templates.js diff --git a/swagger/Gruntfile.js b/swagger/Gruntfile.js index 714414c0..33f8bf0c 100644 --- a/swagger/Gruntfile.js +++ b/swagger/Gruntfile.js @@ -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' ]); diff --git a/swagger/paths/collections.yaml b/swagger/paths/collections.yaml index b92831fb..bbc1f303 100644 --- a/swagger/paths/collections.yaml +++ b/swagger/paths/collections.yaml @@ -1,4 +1,9 @@ # 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 diff --git a/swagger/paths/groups.yaml b/swagger/paths/groups.yaml index 4c70539e..5576cb6a 100644 --- a/swagger/paths/groups.yaml +++ b/swagger/paths/groups.yaml @@ -1,3 +1,8 @@ +$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 diff --git a/swagger/support/swagger-resolver.js b/swagger/support/swagger-resolver.js new file mode 100644 index 00000000..d40b2727 --- /dev/null +++ b/swagger/support/swagger-resolver.js @@ -0,0 +1,136 @@ +'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; + diff --git a/swagger/support/tasks/flatten-swagger.js b/swagger/support/tasks/flatten-swagger.js index bf2c9578..a1fe41be 100644 --- a/swagger/support/tasks/flatten-swagger.js +++ b/swagger/support/tasks/flatten-swagger.js @@ -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: { diff --git a/swagger/support/tasks/resolve-templates.js b/swagger/support/tasks/resolve-templates.js deleted file mode 100644 index 0f0c3848..00000000 --- a/swagger/support/tasks/resolve-templates.js +++ /dev/null @@ -1,94 +0,0 @@ -'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); - }); -}; - - diff --git a/swagger/templates/analyses-list.yaml b/swagger/templates/analyses-list.yaml index c7d37e93..ad44c1a1 100644 --- a/swagger/templates/analyses-list.yaml +++ b/swagger/templates/analyses-list.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/analysis-files-create-ticket-filename.yaml b/swagger/templates/analysis-files-create-ticket-filename.yaml index b1ada829..7f90a951 100644 --- a/swagger/templates/analysis-files-create-ticket-filename.yaml +++ b/swagger/templates/analysis-files-create-ticket-filename.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/analysis-files.yaml b/swagger/templates/analysis-files.yaml index bcdc793c..1577e15e 100644 --- a/swagger/templates/analysis-files.yaml +++ b/swagger/templates/analysis-files.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/analysis-item.yaml b/swagger/templates/analysis-item.yaml index d187a0e5..68cd6020 100644 --- a/swagger/templates/analysis-item.yaml +++ b/swagger/templates/analysis-item.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/analysis-notes-item.yaml b/swagger/templates/analysis-notes-item.yaml index 62e26f90..f4a87a72 100644 --- a/swagger/templates/analysis-notes-item.yaml +++ b/swagger/templates/analysis-notes-item.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/analysis-notes.yaml b/swagger/templates/analysis-notes.yaml index bbb5867e..2627f2ed 100644 --- a/swagger/templates/analysis-notes.yaml +++ b/swagger/templates/analysis-notes.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/file-item.yaml b/swagger/templates/file-item.yaml index 54873dfa..304791fa 100644 --- a/swagger/templates/file-item.yaml +++ b/swagger/templates/file-item.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/file-list-upload.yaml b/swagger/templates/file-list-upload.yaml index 8b1be9a0..57a33187 100644 --- a/swagger/templates/file-list-upload.yaml +++ b/swagger/templates/file-list-upload.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/notes-note.yaml b/swagger/templates/notes-note.yaml index a00f2bc2..4fdfd3b3 100644 --- a/swagger/templates/notes-note.yaml +++ b/swagger/templates/notes-note.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/notes.yaml b/swagger/templates/notes.yaml index 540b1255..1c4936b0 100644 --- a/swagger/templates/notes.yaml +++ b/swagger/templates/notes.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/packfile-end.yaml b/swagger/templates/packfile-end.yaml index fedd50cd..0b662d20 100644 --- a/swagger/templates/packfile-end.yaml +++ b/swagger/templates/packfile-end.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/packfile-start.yaml b/swagger/templates/packfile-start.yaml index 25da57db..d8b5ebfc 100644 --- a/swagger/templates/packfile-start.yaml +++ b/swagger/templates/packfile-start.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/packfile.yaml b/swagger/templates/packfile.yaml index bd2851f3..d302d75c 100644 --- a/swagger/templates/packfile.yaml +++ b/swagger/templates/packfile.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/permissions-user.yaml b/swagger/templates/permissions-user.yaml index 0a0875c4..f8ab3862 100644 --- a/swagger/templates/permissions-user.yaml +++ b/swagger/templates/permissions-user.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/permissions.yaml b/swagger/templates/permissions.yaml index 995d8746..ff9ef38c 100644 --- a/swagger/templates/permissions.yaml +++ b/swagger/templates/permissions.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/tags-tag.yaml b/swagger/templates/tags-tag.yaml index c868f4b5..d12ad63f 100644 --- a/swagger/templates/tags-tag.yaml +++ b/swagger/templates/tags-tag.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string diff --git a/swagger/templates/tags.yaml b/swagger/templates/tags.yaml index b1a96893..d396ec79 100644 --- a/swagger/templates/tags.yaml +++ b/swagger/templates/tags.yaml @@ -5,7 +5,7 @@ parameters: type: string - name: tag type: string -template: +template: | parameters: - required: true type: string -- GitLab