Skip to content

Commit 18a0002

Browse files
tim-laichar0n
andauthored
refactor: monkey patch 'form-data' replaces 'formdata-node' (#1547)
* if form-data lib adds 'any' new method included in monkey patch, monkey patch will fail * update http-multipart tests for compatibility with form-data patch * live tests also refactored, remain in skip state * tested on node 8 Co-authored-by: Vladimir Gorej <vladimir.gorej@gmail.com>
1 parent 27fba70 commit 18a0002

File tree

6 files changed

+109
-57
lines changed

6 files changed

+109
-57
lines changed

package-lock.json

Lines changed: 17 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,14 @@
9898
"dependencies": {
9999
"@babel/runtime-corejs2": "^7.0.0",
100100
"@kyleshockey/object-assign-deep": "^0.4.0",
101-
"@tim-lai/isomorphic-form-data": "^1.0.0",
102101
"btoa": "1.1.2",
103102
"buffer": "^5.1.0",
104103
"cookie": "^0.3.1",
105104
"cross-fetch": "^3.0.4",
106105
"deep-extend": "^0.5.1",
107106
"encode-3986": "^1.0.0",
108107
"fast-json-patch": "~2.1.0",
108+
"isomorphic-form-data": "^2.0.0",
109109
"js-yaml": "^3.13.1",
110110
"lodash": "^4.17.14",
111111
"qs": "^6.3.0",

src/http.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import qs from 'qs'
33
import jsYaml from 'js-yaml'
44
import isString from 'lodash/isString'
55
import isFunction from 'lodash/isFunction'
6-
import FormData from '@tim-lai/isomorphic-form-data'
6+
import FormData from './internal/form-data-monkey-patch'
77

88
// For testing
99
export const self = {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import isFunction from 'lodash/isFunction'
2+
import IsomorphicFormData from 'isomorphic-form-data'
3+
4+
// patches FormData type by mutating it.
5+
// patch :: FormData -> PatchedFormData
6+
export const patch = (FormData) => {
7+
const createEntry = (field, value) => ({name: field, value})
8+
/** We return original type if prototype already contains one of methods we're trying to patch.
9+
* Reasoning: if one of the methods already exists, it would access data in other
10+
* property than our `_entryList`. That could potentially create nasty
11+
* hardly detectable bugs if `form-data` library implements only couple of
12+
* methods that it misses, instead of implementing all of them.
13+
* Current solution will fail the tests to let us know that form-data library
14+
* already implements some of the methods that we try to monkey-patch, and our
15+
* monkey-patch code should then compensate the library changes easily.
16+
*/
17+
if (
18+
isFunction(FormData.prototype.set)
19+
|| isFunction(FormData.prototype.get)
20+
|| isFunction(FormData.prototype.getAll)
21+
|| isFunction(FormData.prototype.has)
22+
) {
23+
return FormData
24+
}
25+
class PatchedFormData extends FormData {
26+
constructor(form) {
27+
super(form)
28+
this._entryList = []
29+
}
30+
31+
append(field, value, options) {
32+
this._entryList.push(createEntry(field, value))
33+
return super.append(field, value, options)
34+
}
35+
36+
set(field, value) {
37+
const newEntry = createEntry(field, value)
38+
39+
this._entryList = this._entryList.filter((entry) => {
40+
return entry.name !== field
41+
})
42+
43+
this._entryList.push(newEntry)
44+
}
45+
46+
get(field) {
47+
const foundEntry = this._entryList.find((entry) => {
48+
return entry.name === field
49+
})
50+
51+
return foundEntry === undefined ? null : foundEntry
52+
}
53+
54+
getAll(field) {
55+
return this._entryList
56+
.filter((entry) => {
57+
return entry.name === field
58+
})
59+
.map((entry) => {
60+
return entry.value
61+
})
62+
}
63+
64+
has(field) {
65+
return this._entryList.some((entry) => {
66+
return entry.name === field
67+
})
68+
}
69+
}
70+
71+
return PatchedFormData
72+
}
73+
74+
export default patch(IsomorphicFormData)

test/execute/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import FormData from '@tim-lai/isomorphic-form-data'
1+
import FormData from '../../src/internal/form-data-monkey-patch'
22
import {execute, buildRequest, baseUrl, self as stubs} from '../../src/execute'
33
import {normalizeSwagger} from '../../src/helpers'
44

test/http-multipart.js

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import FormData from '@tim-lai/isomorphic-form-data'
21
import fetchMock from 'fetch-mock'
2+
import FormData from '../src/internal/form-data-monkey-patch'
33
import {buildRequest} from '../src/execute'
44
import sampleMultipartOpenApi2 from './data/sample-multipart-oas2'
55
import sampleMultipartOpenApi3 from './data/sample-multipart-oas3'
@@ -52,16 +52,15 @@ describe('buildRequest - openapi 2.0', () => {
5252
test.skip('should (Live) POST multipart-formdata with entry item entries', () => {
5353
return fetch('http://localhost:3300/api/v1/formdata', { // eslint-disable-line no-undef
5454
method: 'POST',
55-
body: req.body.stream, // per formdata-node docs
56-
headers: req.body.headers // per formdata-node docs
55+
body: req.body
5756
})
5857
.then((res) => {
5958
return res.json()
6059
})
6160
.then((json) => {
62-
expect(json.email.length).toEqual(2)
63-
expect(json.email[0]).toEqual('person1')
64-
expect(json.email[1]).toEqual('person2')
61+
expect(json.data.email.length).toEqual(2)
62+
expect(json.data.email[0]).toEqual('person1')
63+
expect(json.data.email[1]).toEqual('person2')
6564
})
6665
})
6766

@@ -84,8 +83,7 @@ describe('buildRequest - openapi 2.0', () => {
8483

8584
return fetch('http://localhost:3300/api/v1/formdata', { // eslint-disable-line no-undef
8685
method: 'POST',
87-
body: req.body.stream, // per formdata-node docs
88-
headers: req.body.headers // per formdata-node docs
86+
body: req.body,
8987
})
9088
.then((res) => {
9189
return res.json()
@@ -94,10 +92,10 @@ describe('buildRequest - openapi 2.0', () => {
9492
expect(json.data.email.length).toEqual(2)
9593
expect(json.data.email[0]).toEqual('person1')
9694
expect(json.data.email[1]).toEqual('person2')
97-
// duck typing that fetch received a formdata-node Stream instead of plain object
95+
// duck typing that fetch received a FormData instance instead of plain object
9896
const lastOptions = fetchMock.lastOptions()
9997
expect(lastOptions.body.readable).toEqual(true)
100-
expect(lastOptions.body._readableState).toBeDefined()
98+
// expect(lastOptions.body._streams).toBeDefined()
10199
})
102100
})
103101
})
@@ -139,16 +137,15 @@ describe('buildRequest - openapi 3.0', () => {
139137
test.skip('should (Live) POST multipart-formdata with entry item entries', () => {
140138
return fetch('http://localhost:3300/api/v1/formdata', { // eslint-disable-line no-undef
141139
method: 'POST',
142-
body: req.body.stream, // per formdata-node docs
143-
headers: req.body.headers // per formdata-node docs
140+
body: req.body
144141
})
145142
.then((res) => {
146143
return res.json()
147144
})
148145
.then((json) => {
149-
expect(json.email.length).toEqual(2)
150-
expect(json.email[0]).toEqual('person1')
151-
expect(json.email[1]).toEqual('person2')
146+
expect(json.data.email.length).toEqual(2)
147+
expect(json.data.email[0]).toEqual('person1')
148+
expect(json.data.email[1]).toEqual('person2')
152149
})
153150
})
154151

@@ -171,8 +168,7 @@ describe('buildRequest - openapi 3.0', () => {
171168

172169
return fetch('http://localhost:3300/api/v1/formdata', { // eslint-disable-line no-undef
173170
method: 'POST',
174-
body: req.body.stream, // per formdata-node docs
175-
headers: req.body.headers // per formdata-node docs
171+
body: req.body,
176172
})
177173
.then((res) => {
178174
return res.json()
@@ -181,10 +177,10 @@ describe('buildRequest - openapi 3.0', () => {
181177
expect(json.data.email.length).toEqual(2)
182178
expect(json.data.email[0]).toEqual('person1')
183179
expect(json.data.email[1]).toEqual('person2')
184-
// duck typing that fetch received a formdata-node Stream instead of plain object
180+
// duck typing that fetch received a FormData instance instead of plain object
185181
const lastOptions = fetchMock.lastOptions()
186182
expect(lastOptions.body.readable).toEqual(true)
187-
expect(lastOptions.body._readableState).toBeDefined()
183+
// expect(lastOptions.body._streams).toBeDefined()
188184
})
189185
})
190186
})

0 commit comments

Comments
 (0)