Skip to content

Commit

Permalink
feat(http): respect Encoding Object while building requests
Browse files Browse the repository at this point in the history
This change is specific to OpenAPI 3.x.y.

Refs swagger-api/swagger-ui#4826
  • Loading branch information
char0n committed Aug 1, 2023
1 parent 1eb09ba commit fac9d98
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 8 deletions.
2 changes: 1 addition & 1 deletion src/execute/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ export function buildRequest(options) {

if (req.cookies) {
// even if no cookies were defined, we need to remove
// the cookies key from our request, or many many legacy
// the cookies key from our request, or many legacy
// tests will break.
delete req.cookies;
}
Expand Down
2 changes: 1 addition & 1 deletion src/execute/oas3/build-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default function buildRequest(options, req) {
requestContentType === 'multipart/form-data'
) {
if (typeof requestBody === 'object') {
const encoding = (requestBodyDef.content[requestContentType] || {}).encoding || {};
const encoding = requestBodyDef.content[requestContentType]?.encoding ?? {};

req.form = {};
Object.keys(requestBody).forEach((k) => {
Expand Down
47 changes: 41 additions & 6 deletions src/http/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,29 @@ const SEPARATORS = {
pipes: '|',
};

/**
* Specialized sub-class of File class, that only
* accepts string data and retain this data in `data`
* public property throughout the lifecycle of its instances.
*
* This sub-class is exclusively used only when Encoding Object
* is defined within the Media Type Object (OpenAPI 3.x.y).
*/
class FileWithData extends File {
constructor(data, name = '', options = {}) {
super([data], name, options);
this.data = data;
}

valueOf() {
return this.data;
}

toString() {
return this.valueOf();
}
}

// Formats a key-value and returns an array of key-value pairs.
//
// Return value example 1: [['color', 'blue']]
Expand Down Expand Up @@ -234,13 +257,20 @@ function formatKeyValue(key, input, skipEncoding = false) {
});
}

if (encoding.contentType) {
if (encoding.contentType === 'application/json') {
// If value is a string, assume value is already a JSON string
if (typeof encoding.contentType === 'string') {
if (encoding.contentType.startsWith('application/json')) {
// if value is a string, assume value is already a JSON string
const json = typeof value === 'string' ? value : JSON.stringify(value);
return [[encodedKey, encodeFn(json)]];
const encodedJson = encodeFn(json);
const file = new FileWithData(encodedJson, 'blob', { type: encoding.contentType });

return [[encodedKey, file]];
}
return [[encodedKey, encodeFn(value.toString())]];

const encodedData = encodeFn(String(value));
const blob = new FileWithData(encodedData, 'blob', { type: encoding.contentType });

return [[encodedKey, blob]];
}

// Primitive
Expand Down Expand Up @@ -378,10 +408,15 @@ export function encodeFormOrQuery(data) {
const encodedQuery = Object.keys(data).reduce((result, parameterName) => {
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of formatKeyValue(parameterName, data[parameterName])) {
result[key] = value;
if (value instanceof FileWithData) {
result[key] = value.valueOf();
} else {
result[key] = value;
}
}
return result;
}, {});

return qs.stringify(encodedQuery, { encode: false, indices: false }) || '';
}

Expand Down
133 changes: 133 additions & 0 deletions test/oas3/execute/build-request.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { File } from 'formdata-node';

// https://github.com/swagger-api/swagger-js/issues/1116
import { buildRequest } from '../../../src/execute/index.js';

Expand Down Expand Up @@ -81,4 +83,135 @@ describe('buildRequest - OAS 3.0.x', () => {
});
});
});

describe('given Encoding Object to describe multipart/form-data', () => {
const spec = {
openapi: '3.0.0',
paths: {
'/upload/': {
post: {
tags: ['upload'],
operationId: 'upload',
requestBody: {
required: true,
content: {
'multipart/form-data': {
schema: {
type: 'object',
properties: {
options: {
type: 'object',
properties: {
some_array: {
type: 'array',
items: {
type: 'string',
},
},
max_bar: {
type: 'integer',
default: 300,
},
},
},
},
},
encoding: {
options: {
contentType: 'application/json; charset=utf-8',
},
},
},
},
},
},
},
},
};

test('should attach mime type to appropriate boundaries', async () => {
const options = {
some_array: ['string'],
max_bar: 300,
};
const req = buildRequest({
spec,
operationId: 'upload',
requestBody: { options },
});
const file = req.formdata.get('options');
const json = await file.text();

expect(json).toStrictEqual(JSON.stringify(options));
expect(file).toBeInstanceOf(File);
expect(file.valueOf()).toStrictEqual(JSON.stringify(options));
expect(file.type).toStrictEqual('application/json; charset=utf-8');
});
});

describe('given Encoding Object to describe application/x-www-form-urlencoded', () => {
const spec = {
openapi: '3.0.0',
paths: {
'/upload/': {
post: {
tags: ['upload'],
operationId: 'upload',
requestBody: {
required: true,
content: {
'application/x-www-form-urlencoded': {
schema: {
type: 'object',
properties: {
options: {
type: 'object',
properties: {
some_array: {
type: 'array',
items: {
type: 'string',
},
},
max_bar: {
type: 'integer',
default: 300,
},
},
},
},
},
encoding: {
options: {
contentType: 'application/json; charset=utf-8',
},
},
},
},
},
},
},
},
};

test('should create proper application/x-www-form-urlencoded request', async () => {
const options = {
some_array: ['string'],
max_bar: 300,
};
const req = buildRequest({
spec,
operationId: 'upload',
requestBody: { options },
});

expect(req).toEqual({
url: '/upload/',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'POST',
body: 'options=%7B%22some_array%22%3A%5B%22string%22%5D%2C%22max_bar%22%3A300%7D',
});
});
});
});

0 comments on commit fac9d98

Please sign in to comment.