diff --git a/lib/swagger-client.js b/lib/swagger-client.js index 1f9ee683125..cd40c01c57b 100644 --- a/lib/swagger-client.js +++ b/lib/swagger-client.js @@ -1,5 +1,5 @@ // swagger-client.js -// version 2.1.0-alpha.4 +// version 2.1.0-alpha.5 /** * Array Model **/ @@ -538,18 +538,21 @@ var OperationGroup = function(tag, operation) { var Operation = function(parent, operationId, httpMethod, path, args, definitions) { var errors = []; + parent = parent||{}; + args = args||{}; + this.operation = args; this.deprecated = args.deprecated; this.consumes = args.consumes; this.produces = args.produces; this.parent = parent; - this.host = parent.host; + this.host = parent.host || 'localhost'; this.schemes = parent.schemes; this.scheme = parent.scheme || 'http'; - this.basePath = parent.basePath; + this.basePath = parent.basePath || '/'; this.nickname = (operationId||errors.push('Operations must have a nickname.')); this.method = (httpMethod||errors.push('Operation ' + operationId + ' is missing method.')); - this.path = (path||errors.push('Operation ' + nickname + ' is missing path.')); + this.path = (path||errors.push('Operation ' + this.nickname + ' is missing path.')); this.parameters = args != null ? (args.parameters||[]) : {}; this.summary = args.summary || ''; this.responses = (args.responses||{}); @@ -643,8 +646,10 @@ var Operation = function(parent, operationId, httpMethod, path, args, definition } } - if (errors.length > 0) - this.resource.api.fail(errors); + if (errors.length > 0) { + if(this.resource && this.resource.api && this.resource.api.fail) + this.resource.api.fail(errors); + } return this; } @@ -719,12 +724,16 @@ Operation.prototype.resolveModel = function (schema, definitions) { return null; } -Operation.prototype.help = function() { - log(this.nickname + ': ' + this.operation.summary); +Operation.prototype.help = function(dontPrint) { + var out = this.nickname + ': ' + this.summary + '\n'; for(var i = 0; i < this.parameters.length; i++) { var param = this.parameters[i]; - log(' * ' + param.name + ': ' + param.description); + var typeInfo = typeFromJsonSchema(param.type, param.format); + out += '\n * ' + param.name + ' (' + typeInfo + '): ' + param.description; } + if(typeof dontPrint === 'undefined') + log(out); + return out; } Operation.prototype.getSignature = function(type, models) { @@ -749,6 +758,121 @@ Operation.prototype.getSignature = function(type, models) { } }; +Operation.prototype.supportHeaderParams = function () { + return true; +}; + +Operation.prototype.supportedSubmitMethods = function () { + return this.resource.api.supportedSubmitMethods; +}; + +Operation.prototype.getHeaderParams = function (args) { + var headers = this.setContentTypes(args, {}); + for(var i = 0; i < this.parameters.length; i++) { + var param = this.parameters[i]; + if(typeof args[param.name] !== 'undefined') { + if (param.in === 'header') { + var value = args[param.name]; + if(Array.isArray(value)) + value = this.encodePathCollection(param.collectionFormat, param.name, value); + else + value = this.encodePathParam(value); + headers[param.name] = value; + } + } + } + return headers; +} + +Operation.prototype.urlify = function (args) { + var formParams = {}; + var requestUrl = this.path; + + // grab params from the args, build the querystring along the way + var querystring = ''; + for(var i = 0; i < this.parameters.length; i++) { + var param = this.parameters[i]; + if(typeof args[param.name] !== 'undefined') { + if(param.in === 'path') { + var reg = new RegExp('\{' + param.name + '[^\}]*\}', 'gi'); + var value = args[param.name]; + if(Array.isArray(value)) + value = this.encodePathCollection(param.collectionFormat, param.name, value); + else + value = this.encodePathParam(value); + requestUrl = requestUrl.replace(reg, value); + } + else if (param.in === 'query' && typeof args[param.name] !== 'undefined') { + if(querystring === '') + querystring += '?'; + else + querystring += '&'; + if(typeof param.collectionFormat !== 'undefined') { + var qp = args[param.name]; + if(Array.isArray(qp)) + querystring += this.encodeQueryCollection(param.collectionFormat, param.name, qp); + else + querystring += this.encodeQueryParam(param.name) + '=' + this.encodeQueryParam(args[param.name]); + } + else + querystring += this.encodeQueryParam(param.name) + '=' + this.encodeQueryParam(args[param.name]); + } + else if (param.in === 'formData') + formParams[param.name] = args[param.name]; + } + } + var url = this.scheme + '://' + this.host; + + if(this.basePath !== '/') + url += this.basePath; + + return url + requestUrl + querystring; +} + +Operation.prototype.getMissingParams = function(args) { + var missingParams = []; + // check required params, track the ones that are missing + var i; + for(i = 0; i < this.parameters.length; i++) { + var param = this.parameters[i]; + if(param.required === true) { + if(typeof args[param.name] === 'undefined') + missingParams = param.name; + } + } + return missingParams; +} + +Operation.prototype.getBody = function(headers, args) { + var formParams = {}; + var body; + + for(var i = 0; i < this.parameters.length; i++) { + var param = this.parameters[i]; + if(typeof args[param.name] !== 'undefined') { + if (param.in === 'body') + body = args[param.name]; + } + } + + // handle form params + if(headers['Content-Type'] === 'application/x-www-form-urlencoded') { + var encoded = ""; + var key; + for(key in formParams) { + value = formParams[key]; + if(typeof value !== 'undefined'){ + if(encoded !== "") + encoded += "&"; + encoded += encodeURIComponent(key) + '=' + encodeURIComponent(value); + } + } + body = encoded; + } + + return body; +} + /** * gets sample response for a single operation **/ @@ -778,7 +902,7 @@ Operation.prototype.getSampleJSON = function(type, models) { else return sampleJson; } -}; +} /** * legacy binding @@ -787,112 +911,42 @@ Operation.prototype["do"] = function(args, opts, callback, error, parent) { return this.execute(args, opts, callback, error, parent); } + /** * executes an operation **/ Operation.prototype.execute = function(arg1, arg2, arg3, arg4, parent) { - var args = (arg1||{}); + var args = arg1 || {}; var opts = {}, success, error; if(typeof arg2 === 'object') { opts = arg2; success = arg3; error = arg4; } + if(typeof arg2 === 'function') { success = arg2; error = arg3; } - var formParams = {}; - var headers = {}; - var requestUrl = this.path; - success = (success||log) error = (error||log) - var requiredParams = []; - var missingParams = []; - // check required params, track the ones that are missing - var i; - for(i = 0; i < this.parameters.length; i++) { - var param = this.parameters[i]; - if(param.required === true) { - requiredParams.push(param.name); - if(typeof args[param.name] === 'undefined') - missingParams = param.name; - } - } - + var missingParams = this.getMissingParams(args); if(missingParams.length > 0) { var message = 'missing required params: ' + missingParams; fail(message); return; } - // set content type negotiation - var consumes = this.consumes || this.parent.consumes || [ 'application/json' ]; - var produces = this.produces || this.parent.produces || [ 'application/json' ]; - - headers = this.setContentTypes(args, opts); - - // grab params from the args, build the querystring along the way - var querystring = ""; - for(var i = 0; i < this.parameters.length; i++) { - var param = this.parameters[i]; - if(typeof args[param.name] !== 'undefined') { - if(param.in === 'path') { - var reg = new RegExp('\{' + param.name + '[^\}]*\}', 'gi'); - requestUrl = requestUrl.replace(reg, this.encodePathParam(args[param.name])); - } - else if (param.in === 'query' && typeof args[param.name] !== 'undefined') { - if(querystring === '') - querystring += '?'; - else - querystring += '&'; - if(typeof param.collectionFormat !== 'undefined') { - var qp = args[param.name]; - if(Array.isArray(qp)) - querystring += this.encodeCollection(param.collectionFormat, param.name, qp); - else - querystring += this.encodeQueryParam(param.name) + '=' + this.encodeQueryParam(args[param.name]); - } - else - querystring += this.encodeQueryParam(param.name) + '=' + this.encodeQueryParam(args[param.name]); - } - else if (param.in === 'header') - headers[param.name] = args[param.name]; - else if (param.in === 'formData') - formParams[param.name] = args[param.name]; - else if (param.in === 'body') - args.body = args[param.name]; - } - } - // handle form params - if(headers['Content-Type'] === 'application/x-www-form-urlencoded') { - var encoded = ""; - var key; - for(key in formParams) { - value = formParams[key]; - if(typeof value !== 'undefined'){ - if(encoded !== "") - encoded += "&"; - encoded += encodeURIComponent(key) + '=' + encodeURIComponent(value); - } - } - // todo append? - args.body = encoded; - } - var url = this.scheme + '://' + this.host; - - if(this.basePath !== '/') - url += this.basePath; - - url += requestUrl + querystring; + var headers = this.getHeaderParams(args); + var body = this.getBody(headers, args); + var url = this.urlify(args) var obj = { url: url, method: this.method, - body: args.body, + body: body, useJQuery: this.useJQuery, headers: headers, on: { @@ -911,46 +965,53 @@ Operation.prototype.execute = function(arg1, arg2, arg3, arg4, parent) { Operation.prototype.setContentTypes = function(args, opts) { // default type var accepts = 'application/json'; - var consumes = 'application/json'; + var consumes = args.parameterContentType || 'application/json'; var allDefinedParams = this.parameters; var definedFormParams = []; var definedFileParams = []; - var body = args.body; + var body; var headers = {}; // get params from the operation and set them in definedFileParams, definedFormParams, headers var i; for(i = 0; i < allDefinedParams.length; i++) { var param = allDefinedParams[i]; - if(param.in === 'formData') - definedFormParams.push(param); - else if(param.in === 'file') - definedFileParams.push(param); + if(param.in === 'formData') { + if(param.type === 'file') + definedFileParams.push(param); + else + definedFormParams.push(param); + } else if(param.in === 'header' && this.headers) { var key = param.name; var headerValue = this.headers[param.name]; if(typeof this.headers[param.name] !== 'undefined') headers[key] = headerValue; } + else if(param.in === 'body' && typeof args[param.name] !== 'undefined') { + body = args[param.name]; + } } - // if there's a body, need to set the accepts header via requestContentType + // if there's a body, need to set the consumes header via requestContentType if (body && (this.method === 'post' || this.method === 'put' || this.method === 'patch' || this.method === 'delete')) { if (opts.requestContentType) consumes = opts.requestContentType; } else { // if any form params, content type must be set if(definedFormParams.length > 0) { - if(definedFileParams.length > 0) + if(opts.requestContentType) // override if set + consumes = opts.requestContentType; + else if(definedFileParams.length > 0) // if a file, must be multipart/form-data consumes = 'multipart/form-data'; - else + else // default to x-www-from-urlencoded consumes = 'application/x-www-form-urlencoded'; } else if (this.type == 'DELETE') body = '{}'; else if (this.type != 'DELETE') - accepts = null; + consumes = null; } if (consumes && this.consumes) { @@ -979,7 +1040,40 @@ Operation.prototype.setContentTypes = function(args, opts) { return headers; } -Operation.prototype.encodeCollection = function(type, name, value) { +Operation.prototype.asCurl = function (args) { + var results = []; + var headers = this.getHeaderParams(args); + if (headers) { + var key; + for (key in headers) + results.push("--header \"" + key + ": " + headers[key] + "\""); + } + return "curl " + (results.join(" ")) + " " + this.urlify(args); +} + +Operation.prototype.encodePathCollection = function(type, name, value) { + var encoded = ''; + var i; + var separator = ''; + if(type === 'ssv') + separator = '%20'; + else if(type === 'tsv') + separator = '\\t'; + else if(type === 'pipes') + separator = '|'; + else + separator = ','; + + for(i = 0; i < value.length; i++) { + if(i == 0) + encoded = this.encodeQueryParam(value[i]); + else + encoded += separator + this.encodeQueryParam(value[i]); + } + return encoded; +} + +Operation.prototype.encodeQueryCollection = function(type, name, value) { var encoded = ''; var i; if(type === 'default' || type === 'multi') { @@ -998,6 +1092,13 @@ Operation.prototype.encodeCollection = function(type, name, value) { separator = '\\t'; else if(type === 'pipes') separator = '|'; + else if(type === 'brackets') { + for(i = 0; i < value.length; i++) { + if(i !== 0) + encoded += '&'; + encoded += this.encodeQueryParam(name) + '[]=' + this.encodeQueryParam(value[i]); + } + } if(separator !== '') { for(i = 0; i < value.length; i++) { if(i == 0) @@ -1007,7 +1108,6 @@ Operation.prototype.encodeCollection = function(type, name, value) { } } } - // TODO: support the different encoding schemes here return encoded; } @@ -1022,16 +1122,15 @@ Operation.prototype.encodeQueryParam = function(arg) { * TODO revisit, might not want to leave '/' **/ Operation.prototype.encodePathParam = function(pathParam) { - var encParts, part, parts, _i, _len; + var encParts, part, parts, i, len; pathParam = pathParam.toString(); if (pathParam.indexOf('/') === -1) { return encodeURIComponent(pathParam); } else { parts = pathParam.split('/'); encParts = []; - for (_i = 0, _len = parts.length; _i < _len; _i++) { - part = parts[_i]; - encParts.push(encodeURIComponent(part)); + for (i = 0, len = parts.length; i < len; i++) { + encParts.push(encodeURIComponent(parts[i])); } return encParts.join('/'); } @@ -1278,7 +1377,7 @@ e.ApiKeyAuthorization = ApiKeyAuthorization; e.PasswordAuthorization = PasswordAuthorization; e.CookieAuthorization = CookieAuthorization; e.SwaggerClient = SwaggerClient; - +e.Operation = Operation; /** * SwaggerHttp is a wrapper for executing requests */ diff --git a/package.json b/package.json index fc00d91b1c6..48dac1de0cb 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,13 @@ "readmeFilename": "README.md", "dependencies": { "coffee-script": "~1.6.3", - "swagger-client": "2.0.41", + "swagger-client": "2.0.48", "handlebars": "~1.0.10", "less": "~1.4.2" + }, + "devDependencies": { + "express": "3.x", + "docco": "0.4.x", + "cors": "2.1.1" } } diff --git a/src/main/coffeescript/view/OperationView.coffee b/src/main/coffeescript/view/OperationView.coffee index 7fa7e6d77b8..db5021ad84f 100644 --- a/src/main/coffeescript/view/OperationView.coffee +++ b/src/main/coffeescript/view/OperationView.coffee @@ -229,6 +229,7 @@ class OperationView extends Backbone.View @invocationUrl = if @model.supportHeaderParams() headerParams = @model.getHeaderParams(map) + delete headerParams['Content-Type'] @model.urlify(map, false) else @model.urlify(map, true)