diff --git a/README.md b/README.md index f962d0f..882e104 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,16 @@ Options are passed to `explorer(app, options)`. > to a path different than '/api', e.g. with > `loopback.use('/custom-api-root', loopback.rest()); +`protocol`: **String** + +> Default: `null` + +> A hard override for the outgoing protocol (`http` or `https`) that is designated in Swagger +> resource documents. By default, `loopback-explorer` will write the protocol that was used to retrieve +> the doc. This option is useful if, for instance, your API sits behind an SSL terminator +> and thus needs to report its endpoints as `https`, even though incoming traffic is auto-detected +> as `http`. + `swaggerDistRoot`: **String** > Sets a path within your application for overriding Swagger UI files. diff --git a/lib/class-helper.js b/lib/class-helper.js index de3a0a7..333b5ea 100644 --- a/lib/class-helper.js +++ b/lib/class-helper.js @@ -28,6 +28,8 @@ var classHelper = module.exports = { basePath: opts.basePath, resourcePath: urlJoin('/', opts.resourcePath), apis: [], + consumes: aClass.http.consumes || opts.consumes, + produces: aClass.http.produces || opts.produces, models: modelHelper.generateModelDefinition(aClass.ctor, {}) }; }, diff --git a/lib/model-helper.js b/lib/model-helper.js index d612489..e36ce6f 100644 --- a/lib/model-helper.js +++ b/lib/model-helper.js @@ -104,12 +104,18 @@ var modelHelper = module.exports = { out.type = modelHelper.getPropType(out.type); if (out.type === 'array') { - var hasItemType = typeof prop.type !== 'string' && prop.type[0]; + var hasItemType = Array.isArray(prop.type) && prop.type.length; + var arrayItem = hasItemType && prop.type[0]; - if (hasItemType) { - var arrayProp = prop.type[0]; - if (!arrayProp.type) arrayProp = {type: arrayProp}; - out.items = modelHelper.LDLPropToSwaggerDataType(arrayProp); + if (arrayItem) { + if(typeof arrayItem === 'object') { + out.items = modelHelper.LDLPropToSwaggerDataType(arrayItem); + } else { + out.items = { type: modelHelper.getPropType(arrayItem) }; + } + } else { + // NOTE: `any` is not a supported type in swagger 1.2 + out.items = { type: 'any' }; } } else if (out.type === 'date') { out.type = 'string'; diff --git a/lib/swagger.js b/lib/swagger.js index 2bea1a1..d517303 100644 --- a/lib/swagger.js +++ b/lib/swagger.js @@ -7,12 +7,10 @@ module.exports = Swagger; /** * Module dependencies. */ -var debug = require('debug')('loopback:explorer:swagger'); var path = require('path'); var urlJoin = require('./url-join'); var _defaults = require('lodash.defaults'); var classHelper = require('./class-helper'); -var modelHelper = require('./model-helper'); var routeHelper = require('./route-helper'); /** @@ -24,10 +22,13 @@ var routeHelper = require('./route-helper'); * @param {Object} opts Options. */ function Swagger(loopbackApplication, swaggerApp, opts) { - opts = _defaults({}, opts, { + opts = _defaults(opts || {}, { swaggerVersion: '1.2', basePath: loopbackApplication.get('restApiRoot') || '/api', resourcePath: 'resources', + // Default consumes/produces to application/json + consumes: ['application/json'], + produces: ['application/json'], version: getVersion() }); @@ -48,7 +49,7 @@ function Swagger(loopbackApplication, swaggerApp, opts) { // Add the getter for this doc. var docPath = urlJoin(opts.resourcePath, aClass.http.path); - addRoute(swaggerApp, docPath, doc); + addRoute(swaggerApp, docPath, doc, opts); }); // A route is an endpoint, such as /users/findOne. @@ -73,7 +74,7 @@ function Swagger(loopbackApplication, swaggerApp, opts) { * resources available on the system, and where to find more * information about them. */ - addRoute(swaggerApp, opts.resourcePath, resourceDoc); + addRoute(swaggerApp, opts.resourcePath, resourceDoc, opts); } /** @@ -82,7 +83,7 @@ function Swagger(loopbackApplication, swaggerApp, opts) { * @param {String} uri Path from which to serve the doc. * @param {Object} doc Doc to serve. */ -function addRoute(app, uri, doc) { +function addRoute(app, uri, doc, opts) { var hasBasePath = Object.keys(doc).indexOf('basePath') !== -1; var initialPath = doc.basePath || ''; @@ -100,7 +101,8 @@ function addRoute(app, uri, doc) { if (hasBasePath) { var headers = req.headers; var host = headers.Host || headers.host; - doc.basePath = req.protocol + '://' + host + initialPath; + doc.basePath = (opts.protocol || req.protocol) + '://' + + host + initialPath; } res.status(200).send(doc); }); diff --git a/package.json b/package.json index 05eb9fb..62ccc93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-explorer", - "version": "1.2.6", + "version": "1.2.7", "description": "Browse and test your LoopBack app's APIs", "main": "index.js", "scripts": { diff --git a/test/model-helper.test.js b/test/model-helper.test.js index d196a26..0676767 100644 --- a/test/model-helper.test.js +++ b/test/model-helper.test.js @@ -86,7 +86,10 @@ describe('model-helper', function() { array: [] }); var prop = def.properties.array; - expect(prop).to.eql({ type: 'array' }); + expect(prop).to.eql({ + type: 'array', + items: { type: 'any' } + }); }); it('converts [undefined] type', function() { @@ -96,7 +99,7 @@ describe('model-helper', function() { array: [undefined] }); var prop = def.properties.array; - expect(prop).to.eql({ type: 'array' }); + expect(prop).to.eql({ type: 'array', items: { type: 'any' } }); }); it('converts "array" type', function() { @@ -104,7 +107,7 @@ describe('model-helper', function() { array: 'array' }); var prop = def.properties.array; - expect(prop).to.eql({ type: 'array' }); + expect(prop).to.eql({ type: 'array', items: { type: 'any' } }); }); }); }); diff --git a/test/swagger.test.js b/test/swagger.test.js index 2fd6bd2..1cd0a5b 100644 --- a/test/swagger.test.js +++ b/test/swagger.test.js @@ -69,6 +69,18 @@ describe('swagger definition', function() { done(); }); }); + + it('respects a hardcoded protocol (behind SSL terminator)', function(done){ + var app = mountSwagger({protocol: 'https'}); + + var getReq = getAPIDeclaration(app, 'products'); + getReq.end(function(err, res) { + if (err) return done(err); + var parsed = url.parse(res.body.basePath); + expect(parsed.protocol).to.equal('https:'); + done(); + }); + }); }); describe('Model definition attributes', function() {