From 6cca7557214b3590d6285a7b4ed7be8d1020cf10 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Tue, 19 Sep 2017 21:11:53 -0700 Subject: [PATCH 01/12] MOAR TESTS --- test/oas3/execute-style-explode.js | 2071 ++++++++++++++++++++++++++++ 1 file changed, 2071 insertions(+) create mode 100644 test/oas3/execute-style-explode.js diff --git a/test/oas3/execute-style-explode.js b/test/oas3/execute-style-explode.js new file mode 100644 index 000000000..20517a7b7 --- /dev/null +++ b/test/oas3/execute-style-explode.js @@ -0,0 +1,2071 @@ +import expect, {createSpy, spyOn} from 'expect' +import xmock from 'xmock' +import path from 'path' +import fs from 'fs' +import jsYaml from 'js-yaml' + +import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../src/execute' + +const petstoreSpec = jsYaml.safeLoad(fs.readFileSync(path.join('test', 'oas3', 'data', 'petstore-oas3.yaml'), 'utf8')) + +// Supported shape... { spec, operationId, parameters, securities, fetch } +// One can use operationId or pathItem + method + +describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { + describe('path parameters', function () { + describe('primitive values', function () { + it('default: should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + describe('array values', function () { + it('default: should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/3,4,5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/3,4,5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/3,4,5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.3.4.5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.3.4.5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;id=3,4,5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;id=3;id=4;id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + describe('object values', function () { + it('default: should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/role,admin,firstName,Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/role,admin,firstName,Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/role=admin,firstName=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.role.admin.firstName.Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.role=admin.firstName=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;id=role,admin,firstName,Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;role=admin;firstName=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + }) + describe('query parameters', function () { + describe('primitive values', function () { + const VALUE = 5 + + it('default: should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + describe('array values', function () { + const VALUE = [3, 4, 5] + + it('default: should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3&id=4&id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3&id=4&id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3,4,5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in space-delimited/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'spaceDelimited', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3%20id=4%20id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in space-delimited/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'spaceDelimited', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3%204%205', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in pipe-delimited/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'pipeDelimited', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3|id=4|id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in pipe-delimited/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'pipeDelimited', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3|4|5', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + describe('object values', function () { + const VALUE = { + role: 'admin', + firstName: 'Alex' + } + + it('default: should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?role=admin&firstName=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?role=admin&firstName=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in deepObject/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'deepObject', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id[role]=admin&id[firstName]=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + }) + describe('header parameters', function () { + describe('primitive values', function () { + const VALUE = 5 + + it('default: should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 5 + }, + }) + }) + + it('should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 5 + }, + }) + }) + + it('should build a header parameter in simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 5 + }, + }) + }) + }) + describe('array values', function () { + const VALUE = [3, 4, 5] + + it('default: should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': "3,4,5" + }, + }) + }) + + it('should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': "3,4,5" + }, + }) + }) + + it('should build a header parameter in simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': "3,4,5" + }, + }) + }) + }) + describe('object values', function () { + const VALUE = { + role: 'admin', + firstName: 'Alex' + } + + it('default: should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 'role,admin,firstName,Alex' + }, + }) + }) + + it('should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 'role,admin,firstName,Alex' + }, + }) + }) + + it('should build a header parameter in simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 'role=admin,firstName=Alex' + }, + }) + }) + }) + }) + describe('cookie parameters', function () { + describe('primitive values', function () { + const VALUE = 5 + + it('default: should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'Cookie': 'id=5' + }, + }) + }) + + it('should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'Cookie': 'id=5' + }, + }) + }) + + it('should build a cookie parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'Cookie': 'id=5' + }, + }) + }) + }) + describe('array values', function () { + const VALUE = [3, 4, 5] + + it('default: should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'Cookie': 'id=3,4,5' + }, + }) + }) + + it('should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'Cookie': 'id=3,4,5' + }, + }) + }) + + it('should build a cookie parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'Cookie': 'id=3&id=4&id=5' + }, + }) + }) + }) + describe('object values', function () { + const VALUE = { + role: 'admin', + firstName: 'Alex' + } + + it('default: should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'Cookie': 'id=role,admin,firstName,Alex' + }, + }) + }) + + it('should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'Cookie': 'id=role,admin,firstName,Alex' + }, + }) + }) + + it('should build a cookie parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'Cookie': 'role=admin&firstName=Alex' + }, + }) + }) + }) + }) +}) From a521649def46c09bedd4cb9275ac0da812963aa8 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 21 Sep 2017 18:29:03 -0700 Subject: [PATCH 02/12] Reorganize execute code --- src/{execute.js => execute/index.js} | 6 ++-- .../baseurl.js} | 2 +- test/{execute.js => execute/main.js} | 2 +- test/oas3/{execute.js => execute/main.js} | 2 +- .../style-explode.js} | 28 +++++++++---------- 5 files changed, 20 insertions(+), 20 deletions(-) rename src/{execute.js => execute/index.js} (99%) rename test/{execute-baseurl.js => execute/baseurl.js} (99%) rename test/{execute.js => execute/main.js} (99%) rename test/oas3/{execute.js => execute/main.js} (99%) rename test/oas3/{execute-style-explode.js => execute/style-explode.js} (98%) diff --git a/src/execute.js b/src/execute/index.js similarity index 99% rename from src/execute.js rename to src/execute/index.js index 760051e08..4349411f3 100755 --- a/src/execute.js +++ b/src/execute/index.js @@ -4,9 +4,9 @@ import isPlainObject from 'lodash/isPlainObject' import isArray from 'lodash/isArray' import btoa from 'btoa' import url from 'url' -import http, {mergeInQueryOrForm} from './http' -import {getOperationRaw, idFromPathMethod, legacyIdFromPathMethod, isOAS3} from './helpers' -import createError from './specmap/lib/create-error' +import http, {mergeInQueryOrForm} from '../http' +import {getOperationRaw, idFromPathMethod, legacyIdFromPathMethod, isOAS3} from '../helpers' +import createError from '../specmap/lib/create-error' const arrayOrEmpty = (ar) => { return Array.isArray(ar) ? ar : [] diff --git a/test/execute-baseurl.js b/test/execute/baseurl.js similarity index 99% rename from test/execute-baseurl.js rename to test/execute/baseurl.js index b775a77f9..b0443f96f 100644 --- a/test/execute-baseurl.js +++ b/test/execute/baseurl.js @@ -1,6 +1,6 @@ import expect, {createSpy, spyOn} from 'expect' import xmock from 'xmock' -import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../src/execute' +import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../src/execute' // Supported shape... { spec, operationId, parameters, securities, fetch } // One can use operationId or pathItem + method diff --git a/test/execute.js b/test/execute/main.js similarity index 99% rename from test/execute.js rename to test/execute/main.js index 14dd2ac72..493582b71 100644 --- a/test/execute.js +++ b/test/execute/main.js @@ -1,6 +1,6 @@ import expect, {createSpy, spyOn} from 'expect' import xmock from 'xmock' -import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../src/execute' +import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../src/execute' // Supported shape... { spec, operationId, parameters, securities, fetch } // One can use operationId or pathItem + method diff --git a/test/oas3/execute.js b/test/oas3/execute/main.js similarity index 99% rename from test/oas3/execute.js rename to test/oas3/execute/main.js index b3c47b01a..c56f7526d 100644 --- a/test/oas3/execute.js +++ b/test/oas3/execute/main.js @@ -4,7 +4,7 @@ import path from 'path' import fs from 'fs' import jsYaml from 'js-yaml' -import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../src/execute' +import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../../src/execute' const petstoreSpec = jsYaml.safeLoad(fs.readFileSync(path.join('test', 'oas3', 'data', 'petstore-oas3.yaml'), 'utf8')) diff --git a/test/oas3/execute-style-explode.js b/test/oas3/execute/style-explode.js similarity index 98% rename from test/oas3/execute-style-explode.js rename to test/oas3/execute/style-explode.js index 20517a7b7..8e6402c59 100644 --- a/test/oas3/execute-style-explode.js +++ b/test/oas3/execute/style-explode.js @@ -4,14 +4,14 @@ import path from 'path' import fs from 'fs' import jsYaml from 'js-yaml' -import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../src/execute' +import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../../src/execute' const petstoreSpec = jsYaml.safeLoad(fs.readFileSync(path.join('test', 'oas3', 'data', 'petstore-oas3.yaml'), 'utf8')) // Supported shape... { spec, operationId, parameters, securities, fetch } // One can use operationId or pathItem + method -describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { +describe.skip('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { describe('path parameters', function () { describe('primitive values', function () { it('default: should build a path parameter in a simple/no-explode format', function () { @@ -1490,7 +1490,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'X-MyHeader': "3,4,5" + 'X-MyHeader': '3,4,5' }, }) }) @@ -1530,7 +1530,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'X-MyHeader': "3,4,5" + 'X-MyHeader': '3,4,5' }, }) }) @@ -1570,7 +1570,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'X-MyHeader': "3,4,5" + 'X-MyHeader': '3,4,5' }, }) }) @@ -1737,7 +1737,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'Cookie': 'id=5' + Cookie: 'id=5' }, }) }) @@ -1777,7 +1777,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'Cookie': 'id=5' + Cookie: 'id=5' }, }) }) @@ -1817,7 +1817,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'Cookie': 'id=5' + Cookie: 'id=5' }, }) }) @@ -1858,7 +1858,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'Cookie': 'id=3,4,5' + Cookie: 'id=3,4,5' }, }) }) @@ -1898,7 +1898,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'Cookie': 'id=3,4,5' + Cookie: 'id=3,4,5' }, }) }) @@ -1938,7 +1938,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'Cookie': 'id=3&id=4&id=5' + Cookie: 'id=3&id=4&id=5' }, }) }) @@ -1982,7 +1982,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'Cookie': 'id=role,admin,firstName,Alex' + Cookie: 'id=role,admin,firstName,Alex' }, }) }) @@ -2022,7 +2022,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'Cookie': 'id=role,admin,firstName,Alex' + Cookie: 'id=role,admin,firstName,Alex' }, }) }) @@ -2062,7 +2062,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', url: '/users', credentials: 'same-origin', headers: { - 'Cookie': 'role=admin&firstName=Alex' + Cookie: 'role=admin&firstName=Alex' }, }) }) From e114650393fd380ef60fb31497cef3198dc2113a Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 21 Sep 2017 20:23:24 -0700 Subject: [PATCH 03/12] Working path parameters --- src/execute/index.js | 93 ++++------------------ src/execute/oas3-parameter-builders.js | 16 ++++ src/execute/oas3-style-serializer.js | 84 +++++++++++++++++++ src/execute/swagger2-parameter-builders.js | 67 ++++++++++++++++ test/oas3/execute/style-explode.js | 12 +-- 5 files changed, 190 insertions(+), 82 deletions(-) create mode 100644 src/execute/oas3-parameter-builders.js create mode 100644 src/execute/oas3-style-serializer.js create mode 100644 src/execute/swagger2-parameter-builders.js diff --git a/src/execute/index.js b/src/execute/index.js index 4349411f3..931d709a4 100755 --- a/src/execute/index.js +++ b/src/execute/index.js @@ -5,8 +5,15 @@ import isArray from 'lodash/isArray' import btoa from 'btoa' import url from 'url' import http, {mergeInQueryOrForm} from '../http' -import {getOperationRaw, idFromPathMethod, legacyIdFromPathMethod, isOAS3} from '../helpers' import createError from '../specmap/lib/create-error' +import SWAGGER2_PARAMETER_BUILDERS from './swagger2-parameter-builders' +import OAS3_PARAMETER_BUILDERS from './oas3-parameter-builders' +import { + getOperationRaw, + idFromPathMethod, + legacyIdFromPathMethod, + isOAS3 +} from '../helpers' const arrayOrEmpty = (ar) => { return Array.isArray(ar) ? ar : [] @@ -26,18 +33,6 @@ export const self = { buildRequest } -// These functions will update the request. -// They'll be given {req, value, paramter, spec, operation}. - - -export const PARAMETER_BUILDERS = { - body: bodyBuilder, - header: headerBuilder, - query: queryBuilder, - path: pathBuilder, - formData: formDataBuilder -} - // Execute request, with the given operationId and parameters // pathName/method or operationId is optional export function execute({ @@ -77,7 +72,15 @@ export function buildRequest({ }) { const specIsOAS3 = isOAS3(spec) - parameterBuilders = parameterBuilders || PARAMETER_BUILDERS + if (!parameterBuilders) { + // user did not provide custom parameter builders + if (specIsOAS3) { + parameterBuilders = OAS3_PARAMETER_BUILDERS + } + else { + parameterBuilders = SWAGGER2_PARAMETER_BUILDERS + } + } // Base Template let req = { @@ -246,68 +249,6 @@ export function buildRequest({ return req } -// Add the body to the request -export function bodyBuilder({req, value, specIsOAS3}) { - if (specIsOAS3) { - return - } - req.body = value -} - -// Add a form data object. -export function formDataBuilder({req, value, parameter}) { - // REVIEW: OAS3: check for any parameter changes that affect the builder - req.form = req.form || {} - if (value || parameter.allowEmptyValue) { - req.form[parameter.name] = { - value, - allowEmptyValue: parameter.allowEmptyValue, - collectionFormat: parameter.collectionFormat, - } - } -} - -// Add a header to the request -export function headerBuilder({req, parameter, value}) { - // REVIEW: OAS3: check for any parameter changes that affect the builder - req.headers = req.headers || {} - if (typeof value !== 'undefined') { - req.headers[parameter.name] = value - } -} - -// Replace path paramters, with values ( ie: the URL ) -export function pathBuilder({req, value, parameter}) { - // REVIEW: OAS3: check for any parameter changes that affect the builder - req.url = req.url.replace(`{${parameter.name}}`, encodeURIComponent(value)) -} - -// Add a query to the `query` object, which will later be stringified into the URL's search -export function queryBuilder({req, value, parameter}) { - // REVIEW: OAS3: check for any parameter changes that affect the builder - req.query = req.query || {} - - if (value === false && parameter.type === 'boolean') { - value = 'false' - } - - if (value === 0 && ['number', 'integer'].indexOf(parameter.type) > -1) { - value = '0' - } - - if (value) { - req.query[parameter.name] = { - collectionFormat: parameter.collectionFormat, - value - } - } - else if (parameter.allowEmptyValue) { - const paramName = parameter.name - req.query[paramName] = req.query[paramName] || {} - req.query[paramName].allowEmptyValue = true - } -} - const stripNonAlpha = str => (str ? str.replace(/\W/g, '') : null) export function baseUrl(obj) { diff --git a/src/execute/oas3-parameter-builders.js b/src/execute/oas3-parameter-builders.js new file mode 100644 index 000000000..24826a0e6 --- /dev/null +++ b/src/execute/oas3-parameter-builders.js @@ -0,0 +1,16 @@ +import stylize from './oas3-style-serializer' +export default { + path +} + +function path({req, value, parameter}) { + const {name, style, explode} = parameter + const styledValue = stylize({ + key: parameter.name, + value, + style: style || 'simple', + explode: explode || false, + }) + + req.url = req.url.replace(`{${name}}`, styledValue) +} diff --git a/src/execute/oas3-style-serializer.js b/src/execute/oas3-style-serializer.js new file mode 100644 index 000000000..6ffa43e35 --- /dev/null +++ b/src/execute/oas3-style-serializer.js @@ -0,0 +1,84 @@ +export default function (config) { + const {value} = config + + if (Array.isArray(value)) { + return encodeArray(config) + } + else if (typeof value === 'object') { + return encodeObject(config) + } + + return encodePrimitive(config) +} + +function encodeArray({key, value, style, explode}) { + if (style === 'simple') { + return value.join(',') + } + if (style === 'label') { + return `.${value.join('.')}` + } + if (style === 'matrix') { + return value.reduce((prev, curr) => { + if (!prev || explode) { + return `${(prev || '')};${key}=${curr}` + } + return prev + `,${curr}` + }, '') + } +} + +function encodeObject({key, value, style, explode}) { + const valueKeys = Object.keys(value) + + if (style === 'simple') { + return valueKeys.reduce((prev, curr) => { + const val = value[curr] + const middleChar = explode ? '=' : ',' + const prefix = prev ? `${prev},` : '' + + return `${prefix}${curr}${middleChar}${val}` + }, '') + } + + if (style === 'label') { + return valueKeys.reduce((prev, curr) => { + const val = value[curr] + const middleChar = explode ? '=' : '.' + const prefix = prev ? `${prev}.` : '.' + + return `${prefix}${curr}${middleChar}${val}` + }, '') + } + + if (style === 'matrix' && explode) { + return valueKeys.reduce((prev, curr) => { + const val = value[curr] + const prefix = prev ? `${prev};` : ';' + + return `${prefix}${curr}=${val}` + }, '') + } + + if (style === 'matrix') { + // no explode + return valueKeys.reduce((prev, curr) => { + const val = value[curr] + const prefix = prev ? `${prev},` : `;${key}=` + + return `${prefix}${curr},${val}` + }, '') + } +} + +function encodePrimitive({key, value, style, explode}) { + if (style === 'simple') { + return value + } + if (style === 'label') { + return `.${value}` + } + if (style === 'matrix') { + return `;${key}=${value}` + } +} diff --git a/src/execute/swagger2-parameter-builders.js b/src/execute/swagger2-parameter-builders.js new file mode 100644 index 000000000..97315c933 --- /dev/null +++ b/src/execute/swagger2-parameter-builders.js @@ -0,0 +1,67 @@ +// These functions will update the request. +// They'll be given {req, value, paramter, spec, operation}. + + +export default { + body: bodyBuilder, + header: headerBuilder, + query: queryBuilder, + path: pathBuilder, + formData: formDataBuilder +} + + +// Add the body to the request +function bodyBuilder({req, value}) { + req.body = value +} + +// Add a form data object. +function formDataBuilder({req, value, parameter}) { + req.form = req.form || {} + if (value || parameter.allowEmptyValue) { + req.form[parameter.name] = { + value, + allowEmptyValue: parameter.allowEmptyValue, + collectionFormat: parameter.collectionFormat, + } + } +} + +// Add a header to the request +function headerBuilder({req, parameter, value}) { + req.headers = req.headers || {} + if (typeof value !== 'undefined') { + req.headers[parameter.name] = value + } +} + +// Replace path paramters, with values ( ie: the URL ) +function pathBuilder({req, value, parameter}) { + req.url = req.url.replace(`{${parameter.name}}`, encodeURIComponent(value)) +} + +// Add a query to the `query` object, which will later be stringified into the URL's search +function queryBuilder({req, value, parameter}) { + req.query = req.query || {} + + if (value === false && parameter.type === 'boolean') { + value = 'false' + } + + if (value === 0 && ['number', 'integer'].indexOf(parameter.type) > -1) { + value = '0' + } + + if (value) { + req.query[parameter.name] = { + collectionFormat: parameter.collectionFormat, + value + } + } + else if (parameter.allowEmptyValue) { + const paramName = parameter.name + req.query[paramName] = req.query[paramName] || {} + req.query[paramName].allowEmptyValue = true + } +} diff --git a/test/oas3/execute/style-explode.js b/test/oas3/execute/style-explode.js index 8e6402c59..ee2b6718a 100644 --- a/test/oas3/execute/style-explode.js +++ b/test/oas3/execute/style-explode.js @@ -11,7 +11,7 @@ const petstoreSpec = jsYaml.safeLoad(fs.readFileSync(path.join('test', 'oas3', ' // Supported shape... { spec, operationId, parameters, securities, fetch } // One can use operationId or pathItem + method -describe.skip('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { +describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { describe('path parameters', function () { describe('primitive values', function () { it('default: should build a path parameter in a simple/no-explode format', function () { @@ -831,7 +831,7 @@ describe.skip('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', }) }) describe('query parameters', function () { - describe('primitive values', function () { + describe.skip('primitive values', function () { const VALUE = 5 it('default: should build a query parameter in form/explode format', function () { @@ -946,7 +946,7 @@ describe.skip('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', }) }) }) - describe('array values', function () { + describe.skip('array values', function () { const VALUE = [3, 4, 5] it('default: should build a query parameter in form/explode format', function () { @@ -1213,7 +1213,7 @@ describe.skip('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', }) }) }) - describe('object values', function () { + describe.skip('object values', function () { const VALUE = { role: 'admin', firstName: 'Alex' @@ -1332,7 +1332,7 @@ describe.skip('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', }) }) }) - describe('header parameters', function () { + describe.skip('header parameters', function () { describe('primitive values', function () { const VALUE = 5 @@ -1700,7 +1700,7 @@ describe.skip('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', }) }) }) - describe('cookie parameters', function () { + describe.skip('cookie parameters', function () { describe('primitive values', function () { const VALUE = 5 From a9b18317afc40bb215b256a48ba50b213485325f Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 21 Sep 2017 20:54:36 -0700 Subject: [PATCH 04/12] WIP: OAS3 query parameters --- src/execute/oas3-parameter-builders.js | 31 +++++++++++++++++++++++++- src/execute/oas3-style-serializer.js | 6 +++++ test/oas3/execute/style-explode.js | 4 ++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/execute/oas3-parameter-builders.js b/src/execute/oas3-parameter-builders.js index 24826a0e6..187a1c268 100644 --- a/src/execute/oas3-parameter-builders.js +++ b/src/execute/oas3-parameter-builders.js @@ -1,6 +1,7 @@ import stylize from './oas3-style-serializer' export default { - path + path, + query } function path({req, value, parameter}) { @@ -14,3 +15,31 @@ function path({req, value, parameter}) { req.url = req.url.replace(`{${name}}`, styledValue) } + +function query({req, value, parameter}) { + req.query = req.query || {} + + if (value === false) { + value = 'false' + } + + if (value === 0) { + value = '0' + } + + if (value) { + req.query[parameter.name] = { + value: stylize({ + key: parameter.name, + value, + style: parameter.style || 'form', + explode: parameter.explode || true + }) + } + } + else if (parameter.allowEmptyValue) { + const paramName = parameter.name + req.query[paramName] = req.query[paramName] || {} + req.query[paramName].allowEmptyValue = true + } +} diff --git a/src/execute/oas3-style-serializer.js b/src/execute/oas3-style-serializer.js index 6ffa43e35..af84fd947 100644 --- a/src/execute/oas3-style-serializer.js +++ b/src/execute/oas3-style-serializer.js @@ -26,6 +26,9 @@ function encodeArray({key, value, style, explode}) { return prev + `,${curr}` }, '') } + if (style === 'form') { + return value.join(',') + } } function encodeObject({key, value, style, explode}) { @@ -81,4 +84,7 @@ function encodePrimitive({key, value, style, explode}) { if (style === 'matrix') { return `;${key}=${value}` } + if (style === 'form') { + return value + } } diff --git a/test/oas3/execute/style-explode.js b/test/oas3/execute/style-explode.js index ee2b6718a..3436a4c8d 100644 --- a/test/oas3/execute/style-explode.js +++ b/test/oas3/execute/style-explode.js @@ -831,7 +831,7 @@ describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', func }) }) describe('query parameters', function () { - describe.skip('primitive values', function () { + describe('primitive values', function () { const VALUE = 5 it('default: should build a query parameter in form/explode format', function () { @@ -946,7 +946,7 @@ describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', func }) }) }) - describe.skip('array values', function () { + describe('array values', function () { const VALUE = [3, 4, 5] it('default: should build a query parameter in form/explode format', function () { From 2a3f113c1c219ec3756a73ffc35e366987ff6f5c Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Sun, 24 Sep 2017 21:35:14 -0700 Subject: [PATCH 05/12] Create version-specific buildRequest hooks --- src/execute/index.js | 175 ++++-------------- src/execute/oas3/build-request.js | 66 +++++++ .../parameter-builders.js} | 3 +- .../style-serializer.js} | 0 src/execute/swagger2/build-request.js | 98 ++++++++++ .../parameter-builders.js} | 0 test/execute/main.js | 168 +---------------- test/oas3/execute/style-explode.js | 2 +- test/swagger2/execute/apply-securities.js | 168 +++++++++++++++++ 9 files changed, 370 insertions(+), 310 deletions(-) create mode 100644 src/execute/oas3/build-request.js rename src/execute/{oas3-parameter-builders.js => oas3/parameter-builders.js} (95%) rename src/execute/{oas3-style-serializer.js => oas3/style-serializer.js} (100%) create mode 100644 src/execute/swagger2/build-request.js rename src/execute/{swagger2-parameter-builders.js => swagger2/parameter-builders.js} (100%) create mode 100644 test/swagger2/execute/apply-securities.js diff --git a/src/execute/index.js b/src/execute/index.js index 931d709a4..3851312e1 100755 --- a/src/execute/index.js +++ b/src/execute/index.js @@ -6,8 +6,11 @@ import btoa from 'btoa' import url from 'url' import http, {mergeInQueryOrForm} from '../http' import createError from '../specmap/lib/create-error' -import SWAGGER2_PARAMETER_BUILDERS from './swagger2-parameter-builders' -import OAS3_PARAMETER_BUILDERS from './oas3-parameter-builders' + +import SWAGGER2_PARAMETER_BUILDERS from './swagger2/parameter-builders' +import OAS3_PARAMETER_BUILDERS from './oas3/parameter-builders' +import oas3BuildRequest from './oas3/build-request' +import swagger2BuildRequest from './swagger2/build-request' import { getOperationRaw, idFromPathMethod, @@ -64,12 +67,28 @@ export function execute({ } // Build a request, which can be handled by the `http.js` implementation. -export function buildRequest({ - spec, operationId, parameters, securities, requestContentType, - responseContentType, parameterBuilders, scheme, - requestInterceptor, responseInterceptor, contextUrl, userFetch, - requestBody, server, serverVariables -}) { +export function buildRequest(options) { + const { + spec, + operationId, + securities, + requestContentType, + responseContentType, + scheme, + requestInterceptor, + responseInterceptor, + contextUrl, + userFetch, + requestBody, + server, + serverVariables + } = options + + let { + parameters, + parameterBuilders + } = options + const specIsOAS3 = isOAS3(spec) if (!parameterBuilders) { @@ -83,7 +102,7 @@ export function buildRequest({ } // Base Template - let req = { + const req = { url: baseUrl({spec, scheme, contextUrl, server, serverVariables}), credentials: 'same-origin', headers: { @@ -168,85 +187,15 @@ export function buildRequest({ } }) - const requestBodyDef = operation.requestBody || {} - const requestBodyMediaTypes = Object.keys(requestBodyDef.content || {}) - - // for OAS3: set the Content-Type - if (specIsOAS3 && requestBody) { - // does the passed requestContentType appear in the requestBody definition? - const isExplicitContentTypeValid = requestContentType - && requestBodyMediaTypes.indexOf(requestContentType) > -1 - - if (requestContentType && isExplicitContentTypeValid) { - req.headers['Content-Type'] = requestContentType - } - else if (!requestContentType) { - const firstMediaType = requestBodyMediaTypes[0] - if (firstMediaType) { - req.headers['Content-Type'] = firstMediaType - requestContentType = firstMediaType - } - } - } + // Do version-specific tasks, then return those results. + const versionSpecificOptions = {...options, operation} - // for OAS3: add requestBody to request - if (specIsOAS3 && requestBody) { - if (requestContentType) { - if (requestBodyMediaTypes.indexOf(requestContentType) > -1) { - // only attach body if the requestBody has a definition for the - // contentType that has been explicitly set - if (requestContentType === 'application/x-www-form-urlencoded') { - if (typeof requestBody === 'object') { - req.form = {} - Object.keys(requestBody).forEach((k) => { - const val = requestBody[k] - req.form[k] = { - value: val - } - }) - } - else { - req.form = requestBody - } - } - else { - req.body = requestBody - } - } - } - else { - req.body = requestBody - } + if (specIsOAS3) { + return oas3BuildRequest(versionSpecificOptions, req) } - // Add securities, which are applicable - // REVIEW: OAS3: what changed in securities? - req = applySecurities({request: req, securities, operation, spec}) - - if (!specIsOAS3 && (req.body || req.form)) { - // all following conditionals are Swagger2 only - if (requestContentType) { - req.headers['Content-Type'] = requestContentType - } - else if (Array.isArray(operation.consumes)) { - req.headers['Content-Type'] = operation.consumes[0] - } - else if (Array.isArray(spec.consumes)) { - req.headers['Content-Type'] = spec.consumes[0] - } - else if (operation.parameters && operation.parameters.filter(p => p.type === 'file').length) { - req.headers['Content-Type'] = 'multipart/form-data' - } - else if (operation.parameters && operation.parameters.filter(p => p.in === 'formData').length) { - req.headers['Content-Type'] = 'application/x-www-form-urlencoded' - } - } - - // Will add the query object into the URL, if it exists - // ... will also create a FormData instance, if multipart/form-data (eg: a file) - mergeInQueryOrForm(req) - - return req + // If not OAS3, then treat as Swagger2. + return swagger2BuildRequest(versionSpecificOptions, req) } const stripNonAlpha = str => (str ? str.replace(/\W/g, '') : null) @@ -330,59 +279,3 @@ function swagger2BaseUrl({spec, scheme, contextUrl = ''}) { return '' } - - -// Add security values, to operations - that declare their need on them -export function applySecurities({request, securities = {}, operation = {}, spec}) { - const result = assign({}, request) - const {authorized = {}, specSecurity = []} = securities - const security = operation.security || specSecurity - const isAuthorized = authorized && !!Object.keys(authorized).length - const securityDef = spec.securityDefinitions - - result.headers = result.headers || {} - result.query = result.query || {} - - if (!Object.keys(securities).length || !isAuthorized || !security || - (Array.isArray(operation.security) && !operation.security.length)) { - return request - } - - security.forEach((securityObj, index) => { - for (const key in securityObj) { - const auth = authorized[key] - if (!auth) { - continue - } - - const token = auth.token - const value = auth.value || auth - const schema = securityDef[key] - const {type} = schema - const accessToken = token && token.access_token - const tokenType = token && token.token_type - - if (auth) { - if (type === 'apiKey') { - const inType = schema.in === 'query' ? 'query' : 'headers' - result[inType] = result[inType] || {} - result[inType][schema.name] = value - } - else if (type === 'basic') { - if (value.header) { - result.headers.authorization = value.header - } - else { - value.base64 = btoa(`${value.username}:${value.password}`) - result.headers.authorization = `Basic ${value.base64}` - } - } - else if (type === 'oauth2' && accessToken) { - result.headers.authorization = `${tokenType || 'Bearer'} ${accessToken}` - } - } - } - }) - - return result -} diff --git a/src/execute/oas3/build-request.js b/src/execute/oas3/build-request.js new file mode 100644 index 000000000..6f392079c --- /dev/null +++ b/src/execute/oas3/build-request.js @@ -0,0 +1,66 @@ +// This function runs after the common function, +// `src/execute/index.js#buildRequest` + +export default function (options, req) { + const { + operation, + requestBody + } = options + + let { + requestContentType + } = options + + const requestBodyDef = operation.requestBody || {} + const requestBodyMediaTypes = Object.keys(requestBodyDef.content || {}) + + // for OAS3: set the Content-Type + if (requestBody) { + // does the passed requestContentType appear in the requestBody definition? + const isExplicitContentTypeValid = requestContentType + && requestBodyMediaTypes.indexOf(requestContentType) > -1 + + if (requestContentType && isExplicitContentTypeValid) { + req.headers['Content-Type'] = requestContentType + } + else if (!requestContentType) { + const firstMediaType = requestBodyMediaTypes[0] + if (firstMediaType) { + req.headers['Content-Type'] = firstMediaType + requestContentType = firstMediaType + } + } + } + + // for OAS3: add requestBody to request + if (requestBody) { + if (requestContentType) { + if (requestBodyMediaTypes.indexOf(requestContentType) > -1) { + // only attach body if the requestBody has a definition for the + // contentType that has been explicitly set + if (requestContentType === 'application/x-www-form-urlencoded') { + if (typeof requestBody === 'object') { + req.form = {} + Object.keys(requestBody).forEach((k) => { + const val = requestBody[k] + req.form[k] = { + value: val + } + }) + } + else { + req.form = requestBody + } + } + else { + req.body = requestBody + } + } + } + else { + req.body = requestBody + } + } + + return req +} diff --git a/src/execute/oas3-parameter-builders.js b/src/execute/oas3/parameter-builders.js similarity index 95% rename from src/execute/oas3-parameter-builders.js rename to src/execute/oas3/parameter-builders.js index 187a1c268..4c24c6429 100644 --- a/src/execute/oas3-parameter-builders.js +++ b/src/execute/oas3/parameter-builders.js @@ -1,4 +1,5 @@ -import stylize from './oas3-style-serializer' +import stylize from './style-serializer' + export default { path, query diff --git a/src/execute/oas3-style-serializer.js b/src/execute/oas3/style-serializer.js similarity index 100% rename from src/execute/oas3-style-serializer.js rename to src/execute/oas3/style-serializer.js diff --git a/src/execute/swagger2/build-request.js b/src/execute/swagger2/build-request.js new file mode 100644 index 000000000..ec5243cc3 --- /dev/null +++ b/src/execute/swagger2/build-request.js @@ -0,0 +1,98 @@ +// This function runs after the common function, +// `src/execute/index.js#buildRequest` + +import btoa from 'btoa' +import assign from 'lodash/assign' +import http, {mergeInQueryOrForm} from '../../http' + + +export default function (options, req) { + const { + spec, + operation, + securities, + requestContentType + } = options + // Add securities, which are applicable + req = applySecurities({request: req, securities, operation, spec}) + + if (req.body || req.form) { + // all following conditionals are Swagger2 only + if (requestContentType) { + req.headers['Content-Type'] = requestContentType + } + else if (Array.isArray(operation.consumes)) { + req.headers['Content-Type'] = operation.consumes[0] + } + else if (Array.isArray(spec.consumes)) { + req.headers['Content-Type'] = spec.consumes[0] + } + else if (operation.parameters && operation.parameters.filter(p => p.type === 'file').length) { + req.headers['Content-Type'] = 'multipart/form-data' + } + else if (operation.parameters && operation.parameters.filter(p => p.in === 'formData').length) { + req.headers['Content-Type'] = 'application/x-www-form-urlencoded' + } + } + + // Will add the query object into the URL, if it exists + // ... will also create a FormData instance, if multipart/form-data (eg: a file) + mergeInQueryOrForm(req) + + return req +} + +// Add security values, to operations - that declare their need on them +export function applySecurities({request, securities = {}, operation = {}, spec}) { + const result = assign({}, request) + const {authorized = {}, specSecurity = []} = securities + const security = operation.security || specSecurity + const isAuthorized = authorized && !!Object.keys(authorized).length + const securityDef = spec.securityDefinitions + + result.headers = result.headers || {} + result.query = result.query || {} + + if (!Object.keys(securities).length || !isAuthorized || !security || + (Array.isArray(operation.security) && !operation.security.length)) { + return request + } + + security.forEach((securityObj, index) => { + for (const key in securityObj) { + const auth = authorized[key] + if (!auth) { + continue + } + + const token = auth.token + const value = auth.value || auth + const schema = securityDef[key] + const {type} = schema + const accessToken = token && token.access_token + const tokenType = token && token.token_type + + if (auth) { + if (type === 'apiKey') { + const inType = schema.in === 'query' ? 'query' : 'headers' + result[inType] = result[inType] || {} + result[inType][schema.name] = value + } + else if (type === 'basic') { + if (value.header) { + result.headers.authorization = value.header + } + else { + value.base64 = btoa(`${value.username}:${value.password}`) + result.headers.authorization = `Basic ${value.base64}` + } + } + else if (type === 'oauth2' && accessToken) { + result.headers.authorization = `${tokenType || 'Bearer'} ${accessToken}` + } + } + } + }) + + return result +} diff --git a/src/execute/swagger2-parameter-builders.js b/src/execute/swagger2/parameter-builders.js similarity index 100% rename from src/execute/swagger2-parameter-builders.js rename to src/execute/swagger2/parameter-builders.js diff --git a/test/execute/main.js b/test/execute/main.js index 493582b71..04a4fcd36 100644 --- a/test/execute/main.js +++ b/test/execute/main.js @@ -1,6 +1,6 @@ import expect, {createSpy, spyOn} from 'expect' import xmock from 'xmock' -import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../src/execute' +import {execute, buildRequest, baseUrl, self as stubs} from '../../src/execute' // Supported shape... { spec, operationId, parameters, securities, fetch } // One can use operationId or pathItem + method @@ -926,172 +926,6 @@ describe('execute', () => { expect(fetchSpy.calls[0].arguments[0].body).toBeA(FormData) }) - describe('applySecurities', function () { - it('should NOT add any securities, if the operation does not require it', function () { - const spec = { - host: 'swagger.io', - basePath: '/v1', - security: [{apiKey: []}], - paths: { - '/one': { - get: { - operationId: 'getMe' - } - } - }, - securityDefinitions: { - apiKey: { - in: 'header', - name: 'api_key', - type: 'apiKey' - } - } - } - - const securities = {} - const request = { - url: 'http://swagger.io/v1/one', - method: 'GET' - } - - const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) - - expect(applySecurity).toEqual({ - url: 'http://swagger.io/v1/one', - method: 'GET' - }) - }) - - it('should add a basic auth if operation requires it and has header passed', function () { - const spec = { - host: 'swagger.io', - basePath: '/v1', - security: [{authMe: []}], - paths: { - '/one': { - get: { - operationId: 'getMe', - security: [{authMe: []}] - } - } - }, - securityDefinitions: { - authMe: { - type: 'basic' - } - } - } - - const request = { - url: 'http://swagger.io/v1/one', - method: 'GET', - query: {} - } - const securities = { - authorized: { - authMe: { - header: 'Basic Zm9vOmJhcg==' - } - } - } - - const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) - - expect(applySecurity.headers).toEqual({ - authorization: 'Basic Zm9vOmJhcg==' - }) - }) - - it('should add a basic auth if operation requires it', function () { - const spec = { - host: 'swagger.io', - basePath: '/v1', - security: [{authMe: []}], - paths: { - '/one': { - get: { - operationId: 'getMe', - security: [{authMe: []}] - } - } - }, - securityDefinitions: { - authMe: { - type: 'basic' - } - } - } - - const request = { - url: 'http://swagger.io/v1/one', - method: 'GET', - query: {} - } - const securities = { - authorized: { - authMe: { - username: 'foo', - password: 'bar' - } - } - } - - const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) - - expect(applySecurity.headers).toEqual({ - authorization: 'Basic Zm9vOmJhcg==' - }) - }) - - it('should be able to apply multiple auths', function () { - const spec = { - host: 'swagger.io', - basePath: '/v1', - security: [{authMe: []}, {apiKey: []}], - paths: { - '/one': { - get: { - operationId: 'getMe', - security: [{authMe: []}, {apiKey: []}] - } - } - }, - securityDefinitions: { - authMe: { - type: 'basic' - }, - apiKey: { - in: 'header', - name: 'api_key', - type: 'apiKey' - } - } - } - - const request = { - url: 'http://swagger.io/v1/one', - method: 'GET', - query: {} - } - const securities = { - authorized: { - authMe: { - username: 'foo', - password: 'bar' - }, - apiKey: 'hello' - } - } - - const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) - - expect(applySecurity.headers).toEqual({ - authorization: 'Basic Zm9vOmJhcg==', - api_key: 'hello' - }) - }) - }) - describe('parameterBuilders', function () { describe('query', function () { it('should include the values for array of query parameters', function () { diff --git a/test/oas3/execute/style-explode.js b/test/oas3/execute/style-explode.js index 3436a4c8d..402cb3ea9 100644 --- a/test/oas3/execute/style-explode.js +++ b/test/oas3/execute/style-explode.js @@ -11,7 +11,7 @@ const petstoreSpec = jsYaml.safeLoad(fs.readFileSync(path.join('test', 'oas3', ' // Supported shape... { spec, operationId, parameters, securities, fetch } // One can use operationId or pathItem + method -describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { +describe.skip('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { describe('path parameters', function () { describe('primitive values', function () { it('default: should build a path parameter in a simple/no-explode format', function () { diff --git a/test/swagger2/execute/apply-securities.js b/test/swagger2/execute/apply-securities.js new file mode 100644 index 000000000..1d86ed7ca --- /dev/null +++ b/test/swagger2/execute/apply-securities.js @@ -0,0 +1,168 @@ +import expect from 'expect' +import { applySecurities } from '../../../src/execute/swagger2/build-request' + +describe('swagger2 - execute - applySecurities', function () { + it('should NOT add any securities, if the operation does not require it', function () { + const spec = { + host: 'swagger.io', + basePath: '/v1', + security: [{apiKey: []}], + paths: { + '/one': { + get: { + operationId: 'getMe' + } + } + }, + securityDefinitions: { + apiKey: { + in: 'header', + name: 'api_key', + type: 'apiKey' + } + } + } + + const securities = {} + const request = { + url: 'http://swagger.io/v1/one', + method: 'GET' + } + + const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) + + expect(applySecurity).toEqual({ + url: 'http://swagger.io/v1/one', + method: 'GET' + }) + }) + + it('should add a basic auth if operation requires it and has header passed', function () { + const spec = { + host: 'swagger.io', + basePath: '/v1', + security: [{authMe: []}], + paths: { + '/one': { + get: { + operationId: 'getMe', + security: [{authMe: []}] + } + } + }, + securityDefinitions: { + authMe: { + type: 'basic' + } + } + } + + const request = { + url: 'http://swagger.io/v1/one', + method: 'GET', + query: {} + } + const securities = { + authorized: { + authMe: { + header: 'Basic Zm9vOmJhcg==' + } + } + } + + const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) + + expect(applySecurity.headers).toEqual({ + authorization: 'Basic Zm9vOmJhcg==' + }) + }) + + it('should add a basic auth if operation requires it', function () { + const spec = { + host: 'swagger.io', + basePath: '/v1', + security: [{authMe: []}], + paths: { + '/one': { + get: { + operationId: 'getMe', + security: [{authMe: []}] + } + } + }, + securityDefinitions: { + authMe: { + type: 'basic' + } + } + } + + const request = { + url: 'http://swagger.io/v1/one', + method: 'GET', + query: {} + } + const securities = { + authorized: { + authMe: { + username: 'foo', + password: 'bar' + } + } + } + + const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) + + expect(applySecurity.headers).toEqual({ + authorization: 'Basic Zm9vOmJhcg==' + }) + }) + + it('should be able to apply multiple auths', function () { + const spec = { + host: 'swagger.io', + basePath: '/v1', + security: [{authMe: []}, {apiKey: []}], + paths: { + '/one': { + get: { + operationId: 'getMe', + security: [{authMe: []}, {apiKey: []}] + } + } + }, + securityDefinitions: { + authMe: { + type: 'basic' + }, + apiKey: { + in: 'header', + name: 'api_key', + type: 'apiKey' + } + } + } + + const request = { + url: 'http://swagger.io/v1/one', + method: 'GET', + query: {} + } + const securities = { + authorized: { + authMe: { + username: 'foo', + password: 'bar' + }, + apiKey: 'hello' + } + } + + const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) + + expect(applySecurity.headers).toEqual({ + authorization: 'Basic Zm9vOmJhcg==', + api_key: 'hello' + }) + }) +}) From de644f9774abc0d87c3ae9692db5a73d2bd822f3 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Sun, 24 Sep 2017 23:24:07 -0700 Subject: [PATCH 06/12] WIP --- src/execute/index.js | 15 +++++++++++---- src/execute/oas3/style-serializer.js | 2 +- src/execute/swagger2/build-request.js | 4 ---- test/oas3/execute/style-explode.js | 2 +- test/swagger2/execute/apply-securities.js | 2 +- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/execute/index.js b/src/execute/index.js index 3851312e1..1fb893d18 100755 --- a/src/execute/index.js +++ b/src/execute/index.js @@ -102,7 +102,7 @@ export function buildRequest(options) { } // Base Template - const req = { + let req = { url: baseUrl({spec, scheme, contextUrl, server, serverVariables}), credentials: 'same-origin', headers: { @@ -191,11 +191,18 @@ export function buildRequest(options) { const versionSpecificOptions = {...options, operation} if (specIsOAS3) { - return oas3BuildRequest(versionSpecificOptions, req) + req = oas3BuildRequest(versionSpecificOptions, req) } + else { + // If not OAS3, then treat as Swagger2. + req = swagger2BuildRequest(versionSpecificOptions, req) + } + + // Will add the query object into the URL, if it exists + // ... will also create a FormData instance, if multipart/form-data (eg: a file) + mergeInQueryOrForm(req) - // If not OAS3, then treat as Swagger2. - return swagger2BuildRequest(versionSpecificOptions, req) + return req } const stripNonAlpha = str => (str ? str.replace(/\W/g, '') : null) diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index af84fd947..ca979a5ba 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -23,7 +23,7 @@ function encodeArray({key, value, style, explode}) { if (!prev || explode) { return `${(prev || '')};${key}=${curr}` } - return prev + `,${curr}` + return `${prev},${curr}` }, '') } if (style === 'form') { diff --git a/src/execute/swagger2/build-request.js b/src/execute/swagger2/build-request.js index ec5243cc3..16002bcdf 100644 --- a/src/execute/swagger2/build-request.js +++ b/src/execute/swagger2/build-request.js @@ -35,10 +35,6 @@ export default function (options, req) { } } - // Will add the query object into the URL, if it exists - // ... will also create a FormData instance, if multipart/form-data (eg: a file) - mergeInQueryOrForm(req) - return req } diff --git a/test/oas3/execute/style-explode.js b/test/oas3/execute/style-explode.js index 402cb3ea9..3436a4c8d 100644 --- a/test/oas3/execute/style-explode.js +++ b/test/oas3/execute/style-explode.js @@ -11,7 +11,7 @@ const petstoreSpec = jsYaml.safeLoad(fs.readFileSync(path.join('test', 'oas3', ' // Supported shape... { spec, operationId, parameters, securities, fetch } // One can use operationId or pathItem + method -describe.skip('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { +describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { describe('path parameters', function () { describe('primitive values', function () { it('default: should build a path parameter in a simple/no-explode format', function () { diff --git a/test/swagger2/execute/apply-securities.js b/test/swagger2/execute/apply-securities.js index 1d86ed7ca..1e912d4d0 100644 --- a/test/swagger2/execute/apply-securities.js +++ b/test/swagger2/execute/apply-securities.js @@ -1,5 +1,5 @@ import expect from 'expect' -import { applySecurities } from '../../../src/execute/swagger2/build-request' +import {applySecurities} from '../../../src/execute/swagger2/build-request' describe('swagger2 - execute - applySecurities', function () { it('should NOT add any securities, if the operation does not require it', function () { From 5b41033aaa801f1f3b0ca43ffb1788c9dabdf6c5 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Mon, 25 Sep 2017 20:35:07 -0700 Subject: [PATCH 07/12] More WIP --- src/execute/oas3/parameter-builders.js | 5 ++-- src/execute/oas3/style-serializer.js | 32 ++++++++++++++++++++++++-- src/http.js | 3 ++- test/oas3/execute/style-explode.js | 11 +++++---- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/execute/oas3/parameter-builders.js b/src/execute/oas3/parameter-builders.js index 4c24c6429..21671d561 100644 --- a/src/execute/oas3/parameter-builders.js +++ b/src/execute/oas3/parameter-builders.js @@ -34,8 +34,9 @@ function query({req, value, parameter}) { key: parameter.name, value, style: parameter.style || 'form', - explode: parameter.explode || true - }) + explode: typeof parameter.explode === 'undefined' ? true : parameter.explode + }), + skipEncoding: true } } else if (parameter.allowEmptyValue) { diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index ca979a5ba..ebd3f259c 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -11,13 +11,17 @@ export default function (config) { return encodePrimitive(config) } -function encodeArray({key, value, style, explode}) { +const escape = str => encodeURIComponent(str) + +function encodeArray({key, value, style, explode, parameter}) { if (style === 'simple') { return value.join(',') } + if (style === 'label') { return `.${value.join('.')}` } + if (style === 'matrix') { return value.reduce((prev, curr) => { if (!prev || explode) { @@ -26,8 +30,20 @@ function encodeArray({key, value, style, explode}) { return `${prev},${curr}` }, '') } + if (style === 'form') { - return value.join(',') + const after = explode ? `&${key}=` : escape(',') + return value.join(after) + } + + if (style === 'spaceDelimited') { + const after = explode ? `${key}=` : '' + return value.join(`${escape(' ')}${after}`) + } + + if (style === 'pipeDelimited') { + const after = explode ? `${key}=` : '' + return value.join(`${escape('|')}${after}`) } } @@ -72,18 +88,30 @@ function encodeObject({key, value, style, explode}) { return `${prefix}${curr},${val}` }, '') } + + if (style === 'form') { + return valueKeys.reduce((prev, curr) => { + const val = value[curr] + const prefix = prev ? `${prev}&` : '' + + return `${prefix}${curr}=${val}` + }, '') + } } function encodePrimitive({key, value, style, explode}) { if (style === 'simple') { return value } + if (style === 'label') { return `.${value}` } + if (style === 'matrix') { return `;${key}=${value}` } + if (style === 'form') { return value } diff --git a/src/http.js b/src/http.js index 96d99b4e8..ec870face 100644 --- a/src/http.js +++ b/src/http.js @@ -180,9 +180,10 @@ export function encodeFormOrQuery(data) { const encodedQuery = Object.keys(data).reduce((result, parameterName) => { const isObject = a => a && typeof a === 'object' const paramValue = data[parameterName] + const skipEncoding = !!paramValue.skipEncoding const encodedParameterName = encodeURIComponent(parameterName) const notArray = isObject(paramValue) && !Array.isArray(paramValue) - result[encodedParameterName] = formatValue(notArray ? paramValue : {value: paramValue}) + result[encodedParameterName] = formatValue(notArray ? paramValue : {value: paramValue}, skipEncoding) return result }, {}) return qs.stringify(encodedQuery, {encode: false, indices: false}) || '' diff --git a/test/oas3/execute/style-explode.js b/test/oas3/execute/style-explode.js index 3436a4c8d..1f64bfe17 100644 --- a/test/oas3/execute/style-explode.js +++ b/test/oas3/execute/style-explode.js @@ -2,6 +2,7 @@ import expect, {createSpy, spyOn} from 'expect' import xmock from 'xmock' import path from 'path' import fs from 'fs' +import qs from 'querystring' import jsYaml from 'js-yaml' import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../../src/execute' @@ -11,7 +12,7 @@ const petstoreSpec = jsYaml.safeLoad(fs.readFileSync(path.join('test', 'oas3', ' // Supported shape... { spec, operationId, parameters, securities, fetch } // One can use operationId or pathItem + method -describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { +describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { describe('path parameters', function () { describe('primitive values', function () { it('default: should build a path parameter in a simple/no-explode format', function () { @@ -1055,7 +1056,7 @@ describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', func expect(req).toEqual({ method: 'GET', - url: '/users?id=3,4,5', + url: '/users?id=3%2C4%2C5', credentials: 'same-origin', headers: {}, }) @@ -1169,7 +1170,7 @@ describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', func expect(req).toEqual({ method: 'GET', - url: '/users?id=3|id=4|id=5', + url: '/users?id=3%7Cid=4%7Cid=5', credentials: 'same-origin', headers: {}, }) @@ -1207,13 +1208,13 @@ describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', func expect(req).toEqual({ method: 'GET', - url: '/users?id=3|4|5', + url: '/users?id=3%7C4%7C5', credentials: 'same-origin', headers: {}, }) }) }) - describe.skip('object values', function () { + describe('object values', function () { const VALUE = { role: 'admin', firstName: 'Alex' From ba91bf239e1aff5bac664c4ee953fe1d38d8a295 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Tue, 26 Sep 2017 12:21:43 -0700 Subject: [PATCH 08/12] Query parameters... working --- src/execute/oas3/parameter-builders.js | 69 ++++++++++++++++++++++---- src/execute/oas3/style-serializer.js | 8 ++- src/http.js | 2 +- test/oas3/execute/style-explode.js | 38 ++++++++++++++ 4 files changed, 105 insertions(+), 12 deletions(-) diff --git a/src/execute/oas3/parameter-builders.js b/src/execute/oas3/parameter-builders.js index 21671d561..369c6a32b 100644 --- a/src/execute/oas3/parameter-builders.js +++ b/src/execute/oas3/parameter-builders.js @@ -2,7 +2,8 @@ import stylize from './style-serializer' export default { path, - query + query, + header } function path({req, value, parameter}) { @@ -29,14 +30,52 @@ function query({req, value, parameter}) { } if (value) { - req.query[parameter.name] = { - value: stylize({ - key: parameter.name, - value, - style: parameter.style || 'form', - explode: typeof parameter.explode === 'undefined' ? true : parameter.explode - }), - skipEncoding: true + const type = typeof value + + if (parameter.style === 'deepObject') { + const valueKeys = Object.keys(value) + valueKeys.forEach(k => { + const v = value[k] + req.query[`${parameter.name}[${k}]`] = { + value: stylize({ + key: k, + value: v, + style: 'deepObject' + }), + skipEncoding: true + } + }) + } + else if ( + type === "object" && + !Array.isArray(value) && + (parameter.style === 'form' || !parameter.style) && + (parameter.explode || parameter.explode === undefined) + ) { + // form explode needs to be handled here, + // since we aren't assigning to `req.query[parameter.name]` + // like we usually do. + const valueKeys = Object.keys(value) + valueKeys.forEach(k => { + const v = value[k] + req.query[k] = { + value: stylize({ + key: k, + value: v, + style: parameter.style || 'form' + }) + } + }) + } else { + req.query[parameter.name] = { + value: stylize({ + key: parameter.name, + value, + style: parameter.style || 'form', + explode: typeof parameter.explode === 'undefined' ? true : parameter.explode + }), + skipEncoding: true + } } } else if (parameter.allowEmptyValue) { @@ -45,3 +84,15 @@ function query({req, value, parameter}) { req.query[paramName].allowEmptyValue = true } } + +function header({req, parameter, value}) { + req.headers = req.headers || {} + if (typeof value !== 'undefined') { + req.headers[parameter.name] = stylize({ + key: parameter.name, + value, + style: parameter.style || 'simple', + explode: typeof parameter.explode || false + }) + } +} diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index ebd3f259c..847afe0ee 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -92,9 +92,9 @@ function encodeObject({key, value, style, explode}) { if (style === 'form') { return valueKeys.reduce((prev, curr) => { const val = value[curr] - const prefix = prev ? `${prev}&` : '' + const prefix = prev ? `${prev},` : `` - return `${prefix}${curr}=${val}` + return `${prefix}${curr},${val}` }, '') } } @@ -115,4 +115,8 @@ function encodePrimitive({key, value, style, explode}) { if (style === 'form') { return value } + + if (style === 'deepObject') { + return value + } } diff --git a/src/http.js b/src/http.js index ec870face..a2415c356 100644 --- a/src/http.js +++ b/src/http.js @@ -181,7 +181,7 @@ export function encodeFormOrQuery(data) { const isObject = a => a && typeof a === 'object' const paramValue = data[parameterName] const skipEncoding = !!paramValue.skipEncoding - const encodedParameterName = encodeURIComponent(parameterName) + const encodedParameterName = skipEncoding ? parameterName : encodeURIComponent(parameterName) const notArray = isObject(paramValue) && !Array.isArray(paramValue) result[encodedParameterName] = formatValue(notArray ? paramValue : {value: paramValue}, skipEncoding) return result diff --git a/test/oas3/execute/style-explode.js b/test/oas3/execute/style-explode.js index 1f64bfe17..58d22b660 100644 --- a/test/oas3/execute/style-explode.js +++ b/test/oas3/execute/style-explode.js @@ -1294,6 +1294,44 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', }) }) + it('should build a query parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=role,admin,firstName,Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + it('should build a query parameter in deepObject/explode format', function () { // Given const spec = { From f176130218f8eff9cff5d2544a59dabbc092945a Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Tue, 26 Sep 2017 12:31:07 -0700 Subject: [PATCH 09/12] Header parameters working --- src/execute/oas3/parameter-builders.js | 2 +- test/oas3/execute/style-explode.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/execute/oas3/parameter-builders.js b/src/execute/oas3/parameter-builders.js index 369c6a32b..bb0a9ec77 100644 --- a/src/execute/oas3/parameter-builders.js +++ b/src/execute/oas3/parameter-builders.js @@ -92,7 +92,7 @@ function header({req, parameter, value}) { key: parameter.name, value, style: parameter.style || 'simple', - explode: typeof parameter.explode || false + explode: typeof parameter.explode === 'undefined' ? false : parameter.explode }) } } diff --git a/test/oas3/execute/style-explode.js b/test/oas3/execute/style-explode.js index 58d22b660..1c80a753a 100644 --- a/test/oas3/execute/style-explode.js +++ b/test/oas3/execute/style-explode.js @@ -1371,7 +1371,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', }) }) }) - describe.skip('header parameters', function () { + describe('header parameters', function () { describe('primitive values', function () { const VALUE = 5 From d55bd1bf85e2c3df121f9f681dc032e7290b0e8d Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Tue, 26 Sep 2017 13:34:05 -0700 Subject: [PATCH 10/12] EVERYTHING works --- src/execute/oas3/parameter-builders.js | 24 +++++++++++++++++++++++- src/execute/oas3/style-serializer.js | 16 +++++++++------- test/oas3/execute/style-explode.js | 4 ++-- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/execute/oas3/parameter-builders.js b/src/execute/oas3/parameter-builders.js index bb0a9ec77..efe193a7c 100644 --- a/src/execute/oas3/parameter-builders.js +++ b/src/execute/oas3/parameter-builders.js @@ -3,7 +3,8 @@ import stylize from './style-serializer' export default { path, query, - header + header, + cookie } function path({req, value, parameter}) { @@ -96,3 +97,24 @@ function header({req, parameter, value}) { }) } } + +function cookie({req, parameter, value}) { + req.headers = req.headers || {} + const type = typeof value + + if (type !== 'undefined') { + const prefix = ( + type === "object" && + !Array.isArray(value) && + parameter.explode + ) ? '' : `${parameter.name}=` + + req.headers['Cookie'] = prefix + stylize({ + key: parameter.name, + value, + escape: false, + style: parameter.style || 'form', + explode: typeof parameter.explode === 'undefined' ? false : parameter.explode + }) + } +} diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index 847afe0ee..72b6494fb 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -11,9 +11,9 @@ export default function (config) { return encodePrimitive(config) } -const escape = str => encodeURIComponent(str) +const escapeFn = str => encodeURIComponent(str) -function encodeArray({key, value, style, explode, parameter}) { +function encodeArray({key, value, style, explode, parameter, escape = true}) { if (style === 'simple') { return value.join(',') } @@ -32,18 +32,19 @@ function encodeArray({key, value, style, explode, parameter}) { } if (style === 'form') { - const after = explode ? `&${key}=` : escape(',') + const commaValue = escape ? escapeFn(',') : ',' + const after = explode ? `&${key}=` : commaValue return value.join(after) } if (style === 'spaceDelimited') { const after = explode ? `${key}=` : '' - return value.join(`${escape(' ')}${after}`) + return value.join(`${escapeFn(' ')}${after}`) } if (style === 'pipeDelimited') { const after = explode ? `${key}=` : '' - return value.join(`${escape('|')}${after}`) + return value.join(`${escapeFn('|')}${after}`) } } @@ -92,9 +93,10 @@ function encodeObject({key, value, style, explode}) { if (style === 'form') { return valueKeys.reduce((prev, curr) => { const val = value[curr] - const prefix = prev ? `${prev},` : `` + const prefix = prev ? `${prev}${explode ? '&' : ','}` : `` + const separator = explode ? '=' : ',' - return `${prefix}${curr},${val}` + return `${prefix}${curr}${separator}${val}` }, '') } } diff --git a/test/oas3/execute/style-explode.js b/test/oas3/execute/style-explode.js index 1c80a753a..38b2e24d2 100644 --- a/test/oas3/execute/style-explode.js +++ b/test/oas3/execute/style-explode.js @@ -12,7 +12,7 @@ const petstoreSpec = jsYaml.safeLoad(fs.readFileSync(path.join('test', 'oas3', ' // Supported shape... { spec, operationId, parameters, securities, fetch } // One can use operationId or pathItem + method -describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { +describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { describe('path parameters', function () { describe('primitive values', function () { it('default: should build a path parameter in a simple/no-explode format', function () { @@ -1739,7 +1739,7 @@ describe.only('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', }) }) }) - describe.skip('cookie parameters', function () { + describe('cookie parameters', function () { describe('primitive values', function () { const VALUE = 5 From 05da7a3045c1a3d42f57ba38d7647f1ad98b199e Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Tue, 26 Sep 2017 15:34:41 -0700 Subject: [PATCH 11/12] Add allowReserved tests --- src/execute/oas3/parameter-builders.js | 13 ++- src/execute/oas3/style-serializer.js | 5 +- test/oas3/execute/style-explode.js | 156 +++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 6 deletions(-) diff --git a/src/execute/oas3/parameter-builders.js b/src/execute/oas3/parameter-builders.js index efe193a7c..d9abbf40d 100644 --- a/src/execute/oas3/parameter-builders.js +++ b/src/execute/oas3/parameter-builders.js @@ -14,6 +14,7 @@ function path({req, value, parameter}) { value, style: style || 'simple', explode: explode || false, + escape: !parameter.allowReserved, }) req.url = req.url.replace(`{${name}}`, styledValue) @@ -41,7 +42,8 @@ function query({req, value, parameter}) { value: stylize({ key: k, value: v, - style: 'deepObject' + style: 'deepObject', + escape: !parameter.allowReserved, }), skipEncoding: true } @@ -63,7 +65,8 @@ function query({req, value, parameter}) { value: stylize({ key: k, value: v, - style: parameter.style || 'form' + style: parameter.style || 'form', + escape: !parameter.allowReserved, }) } }) @@ -73,7 +76,8 @@ function query({req, value, parameter}) { key: parameter.name, value, style: parameter.style || 'form', - explode: typeof parameter.explode === 'undefined' ? true : parameter.explode + explode: typeof parameter.explode === 'undefined' ? true : parameter.explode, + escape: !parameter.allowReserved, }), skipEncoding: true } @@ -93,7 +97,8 @@ function header({req, parameter, value}) { key: parameter.name, value, style: parameter.style || 'simple', - explode: typeof parameter.explode === 'undefined' ? false : parameter.explode + explode: typeof parameter.explode === 'undefined' ? false : parameter.explode, + escape: !parameter.allowReserved, }) } } diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index 72b6494fb..1078530d4 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -13,7 +13,7 @@ export default function (config) { const escapeFn = str => encodeURIComponent(str) -function encodeArray({key, value, style, explode, parameter, escape = true}) { +function encodeArray({key, value, style, explode, escape}) { if (style === 'simple') { return value.join(',') } @@ -44,7 +44,8 @@ function encodeArray({key, value, style, explode, parameter, escape = true}) { if (style === 'pipeDelimited') { const after = explode ? `${key}=` : '' - return value.join(`${escapeFn('|')}${after}`) + const separator = escape ? escapeFn('|') : '|' + return value.join(`${separator}${after}`) } } diff --git a/test/oas3/execute/style-explode.js b/test/oas3/execute/style-explode.js index 38b2e24d2..f73cec7fb 100644 --- a/test/oas3/execute/style-explode.js +++ b/test/oas3/execute/style-explode.js @@ -1062,6 +1062,45 @@ describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', func }) }) + it('should build a query parameter in form/no-explode format with allowReserved', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: false, + allowReserved: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3,4,5', + credentials: 'same-origin', + headers: {}, + }) + }) + it('should build a query parameter in space-delimited/explode format', function () { // Given const spec = { @@ -1100,6 +1139,45 @@ describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', func }) }) + it('should build a query parameter in space-delimited/explode format with allowReserved', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'spaceDelimited', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + // whitespace is _not_ an RFC3986 reserved character, + // so it should still be escaped! + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3%20id=4%20id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + it('should build a query parameter in space-delimited/no-explode format', function () { // Given const spec = { @@ -1176,6 +1254,45 @@ describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', func }) }) + it('should build a query parameter in pipe-delimited/explode format with allowReserved', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'pipeDelimited', + explode: true, + allowReserved: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3|id=4|id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + it('should build a query parameter in pipe-delimited/no-explode format', function () { // Given const spec = { @@ -1213,6 +1330,45 @@ describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', func headers: {}, }) }) + + it('should build a query parameter in pipe-delimited/no-explode format with allowReserved', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'pipeDelimited', + explode: false, + allowReserved: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3|4|5', + credentials: 'same-origin', + headers: {}, + }) + }) }) describe('object values', function () { const VALUE = { From 91b3a35d8ffc86533f8701a33eeea4cd4d538395 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 28 Sep 2017 17:05:41 -0700 Subject: [PATCH 12/12] Fix ESLint complaints --- src/execute/oas3/parameter-builders.js | 13 +++++++------ src/execute/oas3/style-serializer.js | 2 +- src/http.js | 4 +++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/execute/oas3/parameter-builders.js b/src/execute/oas3/parameter-builders.js index d9abbf40d..10909ab9a 100644 --- a/src/execute/oas3/parameter-builders.js +++ b/src/execute/oas3/parameter-builders.js @@ -36,7 +36,7 @@ function query({req, value, parameter}) { if (parameter.style === 'deepObject') { const valueKeys = Object.keys(value) - valueKeys.forEach(k => { + valueKeys.forEach((k) => { const v = value[k] req.query[`${parameter.name}[${k}]`] = { value: stylize({ @@ -50,7 +50,7 @@ function query({req, value, parameter}) { }) } else if ( - type === "object" && + type === 'object' && !Array.isArray(value) && (parameter.style === 'form' || !parameter.style) && (parameter.explode || parameter.explode === undefined) @@ -59,7 +59,7 @@ function query({req, value, parameter}) { // since we aren't assigning to `req.query[parameter.name]` // like we usually do. const valueKeys = Object.keys(value) - valueKeys.forEach(k => { + valueKeys.forEach((k) => { const v = value[k] req.query[k] = { value: stylize({ @@ -70,7 +70,8 @@ function query({req, value, parameter}) { }) } }) - } else { + } + else { req.query[parameter.name] = { value: stylize({ key: parameter.name, @@ -109,12 +110,12 @@ function cookie({req, parameter, value}) { if (type !== 'undefined') { const prefix = ( - type === "object" && + type === 'object' && !Array.isArray(value) && parameter.explode ) ? '' : `${parameter.name}=` - req.headers['Cookie'] = prefix + stylize({ + req.headers.Cookie = prefix + stylize({ key: parameter.name, value, escape: false, diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index 1078530d4..d7595dcb1 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -94,7 +94,7 @@ function encodeObject({key, value, style, explode}) { if (style === 'form') { return valueKeys.reduce((prev, curr) => { const val = value[curr] - const prefix = prev ? `${prev}${explode ? '&' : ','}` : `` + const prefix = prev ? `${prev}${explode ? '&' : ','}` : '' const separator = explode ? '=' : ',' return `${prefix}${curr}${separator}${val}` diff --git a/src/http.js b/src/http.js index a2415c356..3c4d34d22 100644 --- a/src/http.js +++ b/src/http.js @@ -183,7 +183,9 @@ export function encodeFormOrQuery(data) { const skipEncoding = !!paramValue.skipEncoding const encodedParameterName = skipEncoding ? parameterName : encodeURIComponent(parameterName) const notArray = isObject(paramValue) && !Array.isArray(paramValue) - result[encodedParameterName] = formatValue(notArray ? paramValue : {value: paramValue}, skipEncoding) + result[encodedParameterName] = formatValue( + notArray ? paramValue : {value: paramValue}, skipEncoding + ) return result }, {}) return qs.stringify(encodedQuery, {encode: false, indices: false}) || ''