GitHub PullRequestReviewCommentEvent at 2014-05-21 00:54:54
Repository: Hilary
Actor: mrvisser
Payload: {"comment":{"url":"https://api.github.com/repos/oaeproject/Hilary/pulls/comments/12876940","id":12876940,"diff_hunk":"@@ -0,0 +1,427 @@\n+/*!\n+ * Copyright 2014 Apereo Foundation (AF) Licensed under the\n+ * Educational Community License, Version 2.0 (the \"License\"); you may\n+ * not use this file except in compliance with the License. You may\n+ * obtain a copy of the License at\n+ *\n+ *     http://opensource.org/licenses/ECL-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing,\n+ * software distributed under the License is distributed on an \"AS IS\"\n+ * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express\n+ * or implied. See the License for the specific language governing\n+ * permissions and limitations under the License.\n+ */\n+\n+var _ = require('underscore');\n+var clone = require('clone');\n+var fs = require('fs');\n+var readdirp = require('readdirp');\n+var restjsdoc = require('restjsdoc');\n+\n+var log = require('oae-logger').logger('oae-swagger');\n+var OAE = require('./oae');\n+var OaeEmitter = require('oae-util/lib/emitter');\n+var Server = require('./server');\n+var SwaggerParamTypes = require('./swaggerParamTypes');\n+var TenantsUtil = require('oae-tenants/lib/util');\n+var Validator = require('oae-util/lib/validator').Validator;\n+\n+var Constants = module.exports.Constants = {\n+    'apiVersion': '0.1',\n+    'swaggerVersion': '1.2',\n+    'basePath': '/api/',\n+    'paramTypes': [\n+        'path',\n+        'query',\n+        'body',\n+        'form',\n+        'header'\n+    ],\n+    'primitives': [\n+        'integer',\n+        'long',\n+        'float',\n+        'double',\n+        'string',\n+        'byte',\n+        'boolean',\n+        'date',\n+        'dateTime',\n+        'int32',\n+        'int64',\n+        'number',\n+        'date-time'\n+   ]\n+};\n+\n+// A \"model\" in Swagger is a schema for JSON object. This object holds all the models that have been described using the @RESTModel documentation annotation. Once registered, REST endpoint documentation can refer to these as input parameter types and return types\n+var models = {};\n+// A \"resource\" in Swagger is a schema for a REST endpoint. These objects hold all the resources that have been described for the user and admin tenants using the @REST documentation annotation. Once registered, REST endpoint documentation will be made available via the swagger documentation endpoints\n+var tenantResources = {};\n+var adminResources = {};\n+\n+/**\n+ * Iterate over all resources and populate their models\n+ */\n+var addModelsToResources = module.exports.addModelsToResources = function() {\n+    _.each(adminResources, function(resource) {\n+        _addModelsToResource(resource);\n+    });\n+    _.each(tenantResources, function(resource) {\n+        _addModelsToResource(resource);\n+    });\n+};\n+\n+// Finalize swagger objects after all modules have been parsed\n+OaeEmitter.on('ready', addModelsToResources);\n+\n+/**\n+ * Finds all the *.js files in a module's `lib` directory and runs them through the restjsdoc parser\n+ *\n+ * @param  {String}     moduleName  The oae module to be swagger documented\n+ * @param  {Function}   callback    Standard callback function called when all files in the module have been processed\n+ */\n+var documentModule = module.exports.documentModule = function(moduleName, callback) {\n+    var files = [];\n+    readdirp({'root': util.format('node_modules/%s/lib', moduleName), 'fileFilter': '*.js'})\n+        .on('data', function(entry) {\n+            files.push(entry.fullPath);\n+        })\n+        .on('error', function(err) {\n+            log().warn({'err': err}, 'Problem recursing directories while documenting ' + moduleName);\n+        }).on('end', function() {\n+            if (files.length === 0) {\n+                return callback();\n+            }\n+            var done = _.after(files.length, callback);\n+            _.each(files, function(file) {\n+                register(file, function(err) {\n+                    if (err) {\n+                        log().warn({'err': err}, 'Problem opening a file while documenting ' + moduleName);\n+                    }\n+                    return done();\n+                });\n+            });\n+        });\n+};\n+\n+/**\n+ * Read the restjsdoc from a file and register its contents with swagger\n+ *\n+ * @param  {String}     filePath        The full path to the file to be documented\n+ * @param  {Function}   callback        Standard callback function called after the file is processed\n+ * @param  {Object}     callback.err    An error that occurred, if any\n+ */\n+var register = module.exports.register = function(filePath, callback) {\n+    fs.readFile(filePath, function (err, data) {\n+        if (err) {\n+            return callback(err);\n+        }\n+        try {\n+            var doc = restjsdoc.parse(data);\n+            // Add all models to both servers\n+            _.each(doc.models, function(model, modelName) {\n+                model.id = modelName;\n+                _.each(model.properties, function(property) {\n+                    // RestJSDoc arrays look like `type[]` so unArray will just be the bare `type`\n+                    var unArray = property.type.replace(/\\[\\]$/, '');\n+                    /*!\n+                     * If the property.type doesn't match unArray then it was an array so we need to transform it to\n+                     * swagger model notation like:\n+                     *\n+                     *      property = {\n+                     *          'type': 'array',\n+                     *          'items': { 'type': 'string' }\n+                     *      }\n+                     *\n+                     * for primitive types and:\n+                     *\n+                     *      property = {\n+                     *          'type': 'array',\n+                     *          'items': { '$ref': 'Model' }\n+                     *      }\n+                     *\n+                     * for complex types\n+                     */\n+                    if (property.type !== unArray) {\n+                        property.type = 'array';\n+                        if (_.contains(Constants.primitives, unArray)) {\n+                            property.items = {'type': unArray};\n+                        } else {\n+                            property.items = {'$ref': unArray};\n+                        }\n+                    }\n+                });\n+                models[modelName] = model;\n+            });\n+\n+            // Convert the restjsdoc objects to swagger objects\n+            _.each(doc.endpoints, function(endpoint, nickname) {\n+                endpoint.nickname = nickname;\n+                endpoint.summary = endpoint.description;\n+                endpoint.responseClass = !endpoint['return']? 'void' : _convertRJDArrayDefToSwagger(endpoint['return'].type);\n+\n+                endpoint.parameters = [];\n+                _.each(endpoint.pathParams, function(pathParam) {\n+                    endpoint.parameters.push(SwaggerParamTypes.path(pathParam.name, pathParam.description, _convertRJDArrayDefToSwagger(pathParam.type), _constructSwaggerValue(pathParam.validValues), pathParam.defaultValue));\n+                });\n+                _.each(endpoint.queryParams, function(queryParam) {\n+                    // Query params don't take true arrays, so if the type is something like `string[]` we need to say it's `string` and set the `isMultiple` flag on the parameter\n+                    var isMultiple = /\\[\\]$/.test(queryParam.type);\n+                    var type = isMultiple ? queryParam.type.slice(0, -2) : queryParam.type;\n+                    endpoint.parameters.push(SwaggerParamTypes.query(queryParam.name, queryParam.description, type, queryParam.required, isMultiple, _constructSwaggerValue(queryParam.validValues), queryParam.defaultValue));\n+                });\n+                _.each(endpoint.bodyParams, function(bodyParam) {\n+                    endpoint.parameters.push(SwaggerParamTypes.body(bodyParam.name, bodyParam.description, _convertRJDArrayDefToSwagger(bodyParam.type), bodyParam.defaultValue));\n+                });\n+                _.each(endpoint.headerParams, function(headerParam) {\n+                    endpoint.parameters.push(SwaggerParamTypes.header(headerParam.name, headerParam.description, _convertRJDArrayDefToSwagger(headerParam.type), headerParam.required));\n+                });\n+                _.each(endpoint.formParams, function(formParam) {\n+                    endpoint.parameters.push(SwaggerParamTypes.form(formParam.name, formParam.description, _convertRJDArrayDefToSwagger(formParam.type), formParam.defaultValue));\n+                });\n+\n+                // Add the endpoint to the appropriate server\n+                if (endpoint.server === 'tenant') {\n+                    _addSwaggerEndpoint(endpoint, tenantResources);\n+                } else if (endpoint.server === 'admin') {\n+                    _addSwaggerEndpoint(endpoint, adminResources);\n+                } else {\n+                    log().warn('Tried to register swagger docs for unknown server \"' + endpoint.server + '\"');\n+                }\n+            });\n+        } catch(ex) {\n+            log().warn({'err': ex}, util.format('Could not parse restjsdoc in %s', filePath));\n+        }\n+        return callback();\n+    });\n+};\n+\n+/**\n+ * Use swagger to document a route on the associated server\n+ *\n+ * @param  {Object}     spec        The swagger spec for the route\n+ * @param  {Object}     resources   The swagger resources to append the spec to\n+ */\n+_addSwaggerEndpoint = function(spec, resources) {\n+    var apiRootPath = spec.path.split('/')[1];","path":"node_modules/oae-util/lib/swagger.js","position":208,"original_position":208,"commit_id":"5d885a887921632fce424d669150b2a1ec79060b","original_commit_id":"5d885a887921632fce424d669150b2a1ec79060b","user":{"login":"mrvisser","id":102265,"avatar_url":"https://avatars.githubusercontent.com/u/102265?","gravatar_id":"ad43d99c9520c317fbfe35ff89bd2365","url":"https://api.github.com/users/mrvisser","html_url":"https://github.com/mrvisser","followers_url":"https://api.github.com/users/mrvisser/followers","following_url":"https://api.github.com/users/mrvisser/following{/other_user}","gists_url":"https://api.github.com/users/mrvisser/gists{/gist_id}","starred_url":"https://api.github.com/users/mrvisser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mrvisser/subscriptions","organizations_url":"https://api.github.com/users/mrvisser/orgs","repos_url":"https://api.github.com/users/mrvisser/repos","events_url":"https://api.github.com/users/mrvisser/events{/privacy}","received_events_url":"https://api.github.com/users/mrvisser/received_events","type":"User","site_admin":false},"body":"Can you document in an inline comment what this path is expected to look like where the `apiRootPath` will be the second element? Why wouldn't the root be the top-level?","created_at":"2014-05-21T00:54:54Z","updated_at":"2014-05-21T00:54:54Z","html_url":"https://github.com/oaeproject/Hilary/pull/852#discussion_r12876940","pull_request_url":"https://api.github.com/repos/oaeproject/Hilary/pulls/852","_links":{"self":{"href":"https://api.github.com/repos/oaeproject/Hilary/pulls/comments/12876940"},"html":{"href":"https://github.com/oaeproject/Hilary/pull/852#discussion_r12876940"},"pull_request":{"href":"https://api.github.com/repos/oaeproject/Hilary/pulls/852"}}}}