UNPKG

swagger-client

Version:

swagger.js is a javascript client for use with swaggering APIs.

795 lines (647 loc) 25.9 kB
class SwaggerApi # Defaults url: "http://api.wordnik.com/v4/resources.json" debug: false basePath: null authorizations: null authorizationScheme: null info: null constructor: (url, options={}) -> # if url is a hash, assume only options were passed if url if url.url options = url else @url = url else options = url @url = options.url if options.url? @supportedSubmitMethods = if options.supportedSubmitMethods? then options.supportedSubmitMethods else ['get'] @success = options.success if options.success? @failure = if options.failure? then options.failure else -> @progress = if options.progress? then options.progress else -> @defaultHeaders = if options.headers? then options.headers else {} # Build right away if a callback was passed to the initializer @build() if options.success? build: -> @progress 'fetching resource list: ' + @url obj = url: @url method: "get" headers: {} on: error: (response) => if @url.substring(0, 4) isnt 'http' @fail 'Please specify the protocol for ' + @url else if error.status == 0 @fail 'Can\'t read from server. It may not have the appropriate access-control-origin settings.' else if error.status == 404 @fail 'Can\'t read swagger JSON from ' + @url else @fail error.status + ' : ' + error.statusText + ' ' + @url response: (rawResponse) => response = JSON.parse(rawResponse.content.data) @apiVersion = response.apiVersion if response.apiVersion? @apis = {} @apisArray = [] @produces = response.produces @info = response.info if response.info? # if apis.operations exists, this is an api declaration as opposed to a resource listing isApi = false for api in response.apis if api.operations for operation in api.operations isApi = true if isApi newName = response.resourcePath.replace(/\//g,'') this.resourcePath = response.resourcePath res = new SwaggerResource response, this @apis[newName] = res @apisArray.push res else # The base path derived from url if response.basePath # support swagger 1.1, which has basePath @basePath = response.basePath else if @url.indexOf('?') > 0 @basePath = @url.substring(0, @url.lastIndexOf('?')) else @basePath = @url for resource in response.apis res = new SwaggerResource resource, this @apis[res.name] = res @apisArray.push res if this.success this.success() this # apply authorizations e = {} if typeof window != 'undefined' e = window else e = exports e.authorizations.apply obj new SwaggerHttp().execute obj @ # This method is called each time a child resource finishes loading # selfReflect: -> return false unless @apis? for resource_name, resource of @apis return false unless resource.ready? @setConsolidatedModels() @ready = true @success() if @success? fail: (message) -> @failure message throw message # parses models in all apis and sets a unique consolidated list of models setConsolidatedModels: -> @modelsArray = [] @models = {} for resource_name, resource of @apis for modelName of resource.models if not @models[modelName]? @models[modelName] = resource.models[modelName] @modelsArray.push resource.models[modelName] for model in @modelsArray model.setReferencedModels(@models) help: -> for resource_name, resource of @apis console.log resource_name for operation_name, operation of resource.operations console.log " #{operation.nickname}" for parameter in operation.parameters console.log " #{parameter.name}#{if parameter.required then ' (required)' else ''} - #{parameter.description}" @ class SwaggerResource api: null produces: null consumes: null constructor: (resourceObj, @api) -> this.api = @api produces = [] consumes = [] @path = if @api.resourcePath? then @api.resourcePath else resourceObj.path @description = resourceObj.description # Extract name from path # '/foo/dogs.format' -> 'dogs' parts = @path.split("/") @name = parts[parts.length - 1].replace('.{format}', '') # set the basePath to be the one in API. If response from server for this resource # has a more specific one, we'll set it again there. @basePath = @api.basePath # We're going to store operations in a map (operations) and a list (operationsArray) @operations = {} @operationsArray = [] # We're going to store models in a map (models) and a list (modelsArray) @modelsArray = [] @models = {} if resourceObj.apis? and @api.resourcePath? # read resource directly from operations object @addApiDeclaration(resourceObj) else # read from server @api.fail "SwaggerResources must have a path." unless @path? # e.g."http://api.wordnik.com/v4/word.json" if @path.substring(0,4) == 'http' # user absolute path @url = @path.replace('{format}', 'json') else @url = @api.basePath + @path.replace('{format}', 'json') @api.progress 'fetching resource ' + @name + ': ' + @url obj = url: @url method: "get" headers: {} on: error: (response) => @api.fail "Unable to read api '" + @name + "' from path " + @url + " (server returned " + error.statusText + ")" response: (rawResponse) => response = JSON.parse(rawResponse.content.data) @addApiDeclaration(response) # apply authorizations e = {} if typeof window != 'undefined' e = window else e = exports e.authorizations.apply obj new SwaggerHttp().execute obj addApiDeclaration: (response) -> if response.produces? @produces = response.produces if response.consumes? @consumes = response.consumes # If there is a basePath in response, use that or else use # the one from the api object if response.basePath? and response.basePath.replace(/\s/g,'').length > 0 @basePath = response.basePath @addModels(response.models) # Instantiate SwaggerOperations and store them in the @operations map and @operationsArray if response.apis for endpoint in response.apis @addOperations(endpoint.path, endpoint.operations, response.consumes, response.produces) # Store a named reference to this resource on the parent object @api[this.name] = this # Mark as ready @ready = true # Now that this resource is loaded, tell the API to check in on itself @api.selfReflect() addModels: (models) -> if models? for modelName of models if not @models[modelName]? swaggerModel = new SwaggerModel(modelName, models[modelName]) @modelsArray.push swaggerModel @models[modelName] = swaggerModel for model in @modelsArray model.setReferencedModels(@models) addOperations: (resource_path, ops, consumes, produces) -> if ops for o in ops consumes = @consumes produces = @produces if o.consumes? consumes = o.consumes else consumes = @consumes if o.produces? produces = o.produces else produces = @produces type = o.type || o.responseClass if(type is "array") ref = null if o.items ref = o.items["type"] || o.items["$ref"] type = "array[" + ref + "]" responseMessages = o.responseMessages method = o.method # support old httpMethod if o.httpMethod method = o.httpMethod # support old naming if o.supportedContentTypes consumes = o.supportedContentTypes # support old error responses if o.errorResponses responseMessages = o.errorResponses # sanitize the nickname o.nickname = @sanitize o.nickname op = new SwaggerOperation o.nickname, resource_path, method, o.parameters, o.summary, o.notes, type, responseMessages, this, consumes, produces @operations[op.nickname] = op @operationsArray.push op sanitize: (nickname) -> # allow only _a-zA-Z0-9 op = nickname.replace /[\s!@#$%^&*()_+=\[{\]};:<>|./?,\\'""-]/g, '_' # trim multiple underscores to one op = op.replace /((_){2,})/g, '_' # ditch leading underscores op = op.replace /^(_)*/g, '' # ditch trailing underscores op = op.replace /([_])*$/g, '' op help: -> for operation_name, operation of @operations msg = " #{operation.nickname}" for parameter in operation.parameters msg.concat(" #{parameter.name}#{if parameter.required then ' (required)' else ''} - #{parameter.description}") msg class SwaggerModel constructor: (modelName, obj) -> @name = if obj.id? then obj.id else modelName @properties = [] for propertyName of obj.properties @properties.push new SwaggerModelProperty(propertyName, obj.properties[propertyName]) # Set models referenced bu this model setReferencedModels: (allModels) -> for prop in @properties type = prop.type || prop.dataType if allModels[type]? prop.refModel = allModels[type] else if prop.refDataType? and allModels[prop.refDataType]? prop.refModel = allModels[prop.refDataType] getMockSignature: (modelsToIgnore) -> propertiesStr = [] for prop in @properties propertiesStr.push prop.toString() strong = '<span class="strong">'; stronger = '<span class="stronger">'; strongClose = '</span>'; classOpen = strong + @name + ' {' + strongClose classClose = strong + '}' + strongClose returnVal = classOpen + '<div>' + propertiesStr.join(',</div><div>') + '</div>' + classClose # create the array if necessary and then add the current element if !modelsToIgnore modelsToIgnore = [] modelsToIgnore.push(@) # iterate thru all properties and add models which are not in modelsToIgnore # modelsToIgnore is used to ensure that recursive references do not lead to endless loop # and that the same model is not displayed multiple times for prop in @properties if(prop.refModel? and (modelsToIgnore.indexOf(prop.refModel)) == -1) returnVal = returnVal + ('<br>' + prop.refModel.getMockSignature(modelsToIgnore)) returnVal createJSONSample: (modelsToIgnore) -> result = {} modelsToIgnore = modelsToIgnore || []; modelsToIgnore.push(@name); for prop in @properties result[prop.name] = prop.getSampleValue(modelsToIgnore) result class SwaggerModelProperty constructor: (@name, obj) -> @dataType = obj.type @isCollection = @dataType && (@dataType.toLowerCase() is 'array' || @dataType.toLowerCase() is 'list' || @dataType.toLowerCase() is 'set'); @descr = obj.description @required = obj.required if obj.items? if obj.items.type? then @refDataType = obj.items.type if obj.items.$ref? then @refDataType = obj.items.$ref @dataTypeWithRef = if @refDataType? then (@dataType + '[' + @refDataType + ']') else @dataType if obj.allowableValues? @valueType = obj.allowableValues.valueType @values = obj.allowableValues.values if @values? @valuesString = "'" + @values.join("' or '") + "'" getSampleValue: (modelsToIgnore) -> if(@refModel? and (modelsToIgnore.indexOf(@refModel.name) is -1)) result = @refModel.createJSONSample(modelsToIgnore) else if @isCollection result = @refDataType else result = @dataType if @isCollection then [result] else result toString: -> req = if @required then 'propReq' else 'propOpt' str = '<span class="propName ' + req + '">' + @name + '</span> (<span class="propType">' + @dataTypeWithRef + '</span>'; if !@required str += ', <span class="propOptKey">optional</span>' str += ')'; if @values? str += " = <span class='propVals'>['" + @values.join("' or '") + "']</span>" if @descr? str += ': <span class="propDesc">' + @descr + '</span>' str # SwaggerOperation converts an operation into a method which can be executed directly class SwaggerOperation constructor: (@nickname, @path, @method, @parameters=[], @summary, @notes, @type, @responseMessages, @resource, @consumes, @produces) -> @resource.api.fail "SwaggerOperations must have a nickname." unless @nickname? @resource.api.fail "SwaggerOperation #{nickname} is missing path." unless @path? @resource.api.fail "SwaggerOperation #{nickname} is missing method." unless @method? # Convert {format} to 'json' @path = @path.replace('{format}', 'json') @method = @method.toLowerCase() @isGetMethod = @method == "get" @resourceName = @resource.name # if void clear it console.log "model type: " + type if(@type?.toLowerCase() is 'void') then @type = undefined if @type? # set the signature of response class @responseClassSignature = @getSignature(@type, @resource.models) @responseSampleJSON = @getSampleJSON(@type, @resource.models) @responseMessages = @responseMessages || [] for parameter in @parameters # Path params do not have a name, set the name to the path if name is n/a parameter.name = parameter.name || parameter.type || parameter.dataType type = parameter.type || parameter.dataType if(type.toLowerCase() is 'boolean') parameter.allowableValues = {} parameter.allowableValues.values = @resource.api.booleanValues parameter.signature = @getSignature(type, @resource.models) parameter.sampleJSON = @getSampleJSON(type, @resource.models) # Set allowableValue attributes if parameter.allowableValues? # Set isRange and isList flags on param if parameter.allowableValues.valueType == "RANGE" parameter.isRange = true else parameter.isList = true # Set a descriptive values on allowable values # This contains value and isDefault flag for each value if parameter.allowableValues.values? parameter.allowableValues.descriptiveValues = [] for v in parameter.allowableValues.values if parameter.defaultValue? and parameter.defaultValue == v parameter.allowableValues.descriptiveValues.push {value: v, isDefault: true} else parameter.allowableValues.descriptiveValues.push {value: v, isDefault: false} # Store a named reference to this operation on the parent resource # getDefinitions() maps to getDefinitionsData.do() @resource[@nickname]= (args, callback, error) => @do(args, callback, error) # shortcut to help method @resource[@nickname].help = => @help() isListType: (type) -> if(type.indexOf('[') >= 0) then type.substring(type.indexOf('[') + 1, type.indexOf(']')) else undefined getSignature: (type, models) -> # set listType if it exists listType = @isListType(type) # set flag which says if its primitive or not isPrimitive = if ((listType? and models[listType]) or models[type]?) then false else true if (isPrimitive) then type else (if listType? then models[listType].getMockSignature() else models[type].getMockSignature()) getSampleJSON: (type, models) -> # set listType if it exists listType = @isListType(type) # set flag which says if its primitive or not isPrimitive = if ((listType? and models[listType]) or models[type]?) then false else true val = if (isPrimitive) then undefined else (if listType? then models[listType].createJSONSample() else models[type].createJSONSample()) # pretty printing obtained JSON if val # if container is list wrap it val = if listType then [val] else val JSON.stringify(val, null, 2) do: (args={}, opts={}, callback, error) => requestContentType = null responseContentType = null # if the args is a function, then it must be a resource without # parameters or opts if (typeof args) == "function" error = opts callback = args args = {} if (typeof opts) == "function" error = callback callback = opts # Define a default error handler unless error? error = (xhr, textStatus, error) -> console.log xhr, textStatus, error # Define a default success handler unless callback? callback = (data) -> content = null if data.content? content = data.content.data else content = "no data" console.log "default callback: " + content # params to pass into the request params = {} # Pull headers out of args if args.headers? params.headers = args.headers delete args.headers # Pull body out of args if args.body? params.body = args.body delete args.body # pull out any form params possibleParams = (param for param in @parameters when (param.paramType is "form" or param.paramType.toLowerCase() is "file" )) if possibleParams for key, value of possibleParams if args[value.name] params[value.name] = args[value.name] req = new SwaggerRequest(@method, @urlify(args), params, opts, callback, error, this) if opts.mock? req else true pathJson: -> @path.replace "{format}", "json" pathXml: -> @path.replace "{format}", "xml" # converts the operation path into a real URL, and appends query params urlify: (args) -> url = @resource.basePath + @pathJson() # Iterate over allowable params, interpolating the 'path' params into the url string. # Whatever's left over in the args object will become the query string for param in @parameters if param.paramType == 'path' if args[param.name] reg = new RegExp '\{'+param.name+'[^\}]*\}', 'gi' url = url.replace(reg, encodeURIComponent(args[param.name])) delete args[param.name] else throw "#{param.name} is a required path param." # Append the query string to the URL queryParams = "" for param in @parameters if param.paramType == 'query' if args[param.name] if queryParams != "" queryParams += "&" queryParams += encodeURIComponent(param.name) + '=' + encodeURIComponent(args[param.name]) url += ("?" + queryParams) if queryParams? and queryParams.length > 0 url # expose default headers supportHeaderParams: -> @resource.api.supportHeaderParams # expose supported submit methods supportedSubmitMethods: -> @resource.api.supportedSubmitMethods getQueryParams: (args) -> @getMatchingParams ['query'], args getHeaderParams: (args) -> @getMatchingParams ['header'], args # From args extract params of paramType and return them getMatchingParams: (paramTypes, args) -> matchingParams = {} for param in @parameters if args and args[param.name] matchingParams[param.name] = args[param.name] for name, value of @resource.api.headers matchingParams[name] = value matchingParams help: -> msg = "" for parameter in @parameters if msg isnt "" then msg += "\n" msg += "* #{parameter.name}#{if parameter.required then ' (required)' else ''} - #{parameter.description}" msg # Swagger Request turns an operation into an actual request class SwaggerRequest constructor: (@type, @url, @params, @opts, @successCallback, @errorCallback, @operation, @execution) -> throw "SwaggerRequest type is required (get/post/put/delete)." unless @type? throw "SwaggerRequest url is required." unless @url? throw "SwaggerRequest successCallback is required." unless @successCallback? throw "SwaggerRequest error callback is required." unless @errorCallback? throw "SwaggerRequest operation is required." unless @operation? @type = @type.toUpperCase() headers = params.headers myHeaders = {} body = params.body parent = params["parent"] requestContentType = "application/json" # if post or put, set the content-type being sent, otherwise make it null # some servers will die if content-type is set but there is no body if body and (@type is "POST" or @type is "PUT" or @type is "PATCH") if @opts.requestContentType requestContentType = @opts.requestContentType else # if any form params, content-type must be set if (param for param in @operation.parameters when param.paramType is "form").length > 0 type = param.type || param.dataType if (param for param in @operation.parameters when type.toLowerCase() is "file").length > 0 requestContentType = "multipart/form-data" else requestContentType = "application/x-www-form-urlencoded" else if @type isnt "DELETE" requestContentType = null # verify the content type is acceptable from what it defines if requestContentType and @operation.consumes if @operation.consumes.indexOf(requestContentType) is -1 console.log "server doesn't consume " + requestContentType + ", try " + JSON.stringify(@operation.consumes) if @requestContentType == null requestContentType = @operation.consumes[0] responseContentType = null # if get or post, set the content-type being sent, otherwise make it null if (@type is "POST" or @type is "GET" or @type is "PATCH") if @opts.responseContentType responseContentType = @opts.responseContentType else responseContentType = "application/json" else responseContentType = null # verify the content type can be produced if responseContentType and @operation.produces if @operation.produces.indexOf(responseContentType) is -1 console.log "server can't produce " + responseContentType # prepare the body from params, if needed if requestContentType && requestContentType.indexOf("application/x-www-form-urlencoded") is 0 # pull fields from args fields = {} possibleParams = (param for param in @operation.parameters when param.paramType is "form") values = {} for key, value of possibleParams if @params[value.name] values[value.name] = @params[value.name] urlEncoded = "" for key, value of values if urlEncoded != "" urlEncoded += "&" urlEncoded += encodeURIComponent(key) + '=' + encodeURIComponent(value) body = urlEncoded if requestContentType myHeaders["Content-Type"] = requestContentType if responseContentType myHeaders["Accept"] = responseContentType unless headers? and headers.mock? obj = url: @url method: @type headers: myHeaders body: body on: error: (response) => @errorCallback response, @opts.parent redirect: (response) => @successCallback response, @opts.parent 307: (response) => @successCallback response, @opts.parent response: (response) => @successCallback response, @opts.parent # apply authorizations e = {} if typeof window != 'undefined' e = window else e = exports e.authorizations.apply obj unless opts.mock? new SwaggerHttp().execute obj else console.log obj return obj asCurl: -> header_args = ("--header \"#{k}: #{v}\"" for k,v of @headers) "curl #{header_args.join(" ")} #{@url}" # SwaggerHttp is a wrapper on top of Shred, which makes actual http requests class SwaggerHttp Shred: null shred: null content: null constructor: -> if typeof window != 'undefined' @Shred = require "./shred" else @Shred = require "shred" @shred = new @Shred() identity = (x) => x toString = (x) => x.toString() if typeof window != 'undefined' @content = require "./shred/content" @content.registerProcessor( ["application/json; charset=utf-8","application/json","json"], { parser: (identity), stringify: toString }) else @Shred.registerProcessor( ["application/json; charset=utf-8","application/json","json"], { parser: (identity), stringify: toString }) execute: (obj) -> @shred.request obj class SwaggerAuthorizations authz: null constructor: -> @authz = {} add: (name, auth) -> @authz[name] = auth auth apply: (obj) -> for key, value of @authz # see if it applies value.apply obj class ApiKeyAuthorization type: null name: null value: null constructor: (name, value, type) -> @name = name @value = value @type = type apply: (obj) -> if @type == "query" if obj.url.indexOf('?') > 0 obj.url = obj.url + "&" + @name + "=" + @value else obj.url = obj.url + "?" + @name + "=" + @value true else if @type == "header" obj.headers[@name] = @value @SwaggerApi = SwaggerApi @SwaggerResource = SwaggerResource @SwaggerOperation = SwaggerOperation @SwaggerRequest = SwaggerRequest @SwaggerModelProperty = SwaggerModelProperty @ApiKeyAuthorization = ApiKeyAuthorization @authorizations = new SwaggerAuthorizations()