diff --git a/README.md b/README.md index 10959804..73658e59 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,18 @@ sdk.updatePet({ name: 'Buster 2' }, { petId: 1234 }).then(...) Since we've supplied two objects here, the SDK automatically knows that you're supplying both a `body` and `metadata`, and can make a PUT request against `/pets/1234` for you. +What about a `multipart/form-data` request? That works too, and you don't even have to worry about the fun of multipart boundaries! + +```js +sdk.uploadFile({ file: '/path/to/a/file.txt' }).then(...) +``` + +You can also give it a stream and it'll handle all of the hard work for you. + +```js +sdk.uploadFile({ file: fs.createReadStream('/path/to/a/file.txt') }).then(...) +``` + ### HTTP requests If the API you're using doesn't have any documented operation IDs, you can make requests with HTTP verbs instead: @@ -112,7 +124,7 @@ Not yet, unfortunately. For APIs that use OAuth 2, you'll need a fully-qualified Not yet! This is something we're thinking about how to handle, but it's difficult with the simple nature of the `.auth()` method as it currently does not require the user to inform the SDK of what kind of authentication scheme the token they're supplying it should match up against. #### Will this work in browsers? -Not sure! If you'd like to help us out in making this compatible with browsers we'd love to help you out on a pull request. +Not at the moment as the library requires some filesystem handling in order to manage its cache state, but it's something we're actively thinking about. If you'd like to help us out in making this compatible with browsers we'd love to help you out on a pull request. #### Will this validate my data before it reaches the API? Not yet! This is something we've got planned down the road. diff --git a/packages/api/README.md b/packages/api/README.md index 10959804..73658e59 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -76,6 +76,18 @@ sdk.updatePet({ name: 'Buster 2' }, { petId: 1234 }).then(...) Since we've supplied two objects here, the SDK automatically knows that you're supplying both a `body` and `metadata`, and can make a PUT request against `/pets/1234` for you. +What about a `multipart/form-data` request? That works too, and you don't even have to worry about the fun of multipart boundaries! + +```js +sdk.uploadFile({ file: '/path/to/a/file.txt' }).then(...) +``` + +You can also give it a stream and it'll handle all of the hard work for you. + +```js +sdk.uploadFile({ file: fs.createReadStream('/path/to/a/file.txt') }).then(...) +``` + ### HTTP requests If the API you're using doesn't have any documented operation IDs, you can make requests with HTTP verbs instead: @@ -112,7 +124,7 @@ Not yet, unfortunately. For APIs that use OAuth 2, you'll need a fully-qualified Not yet! This is something we're thinking about how to handle, but it's difficult with the simple nature of the `.auth()` method as it currently does not require the user to inform the SDK of what kind of authentication scheme the token they're supplying it should match up against. #### Will this work in browsers? -Not sure! If you'd like to help us out in making this compatible with browsers we'd love to help you out on a pull request. +Not at the moment as the library requires some filesystem handling in order to manage its cache state, but it's something we're actively thinking about. If you'd like to help us out in making this compatible with browsers we'd love to help you out on a pull request. #### Will this validate my data before it reaches the API? Not yet! This is something we've got planned down the road. diff --git a/packages/api/__tests__/__fixtures__/owlbert.png b/packages/api/__tests__/__fixtures__/owlbert.png new file mode 100644 index 00000000..7b920098 Binary files /dev/null and b/packages/api/__tests__/__fixtures__/owlbert.png differ diff --git a/packages/api/__tests__/lib/prepareParams.test.js b/packages/api/__tests__/lib/prepareParams.test.js index 78f66f99..6acf5e6f 100644 --- a/packages/api/__tests__/lib/prepareParams.test.js +++ b/packages/api/__tests__/lib/prepareParams.test.js @@ -1,3 +1,4 @@ +const fs = require('fs'); const Oas = require('@readme/oas-tooling'); const $RefParser = require('@apidevtools/json-schema-ref-parser'); const readmeExample = require('@readme/oas-examples/3.0/json/readme.json'); @@ -39,16 +40,16 @@ describe('#prepareParams', () => { usptoSpec = new Oas(schema); }); - it('should prepare nothing if nothing was supplied', () => { + it('should prepare nothing if nothing was supplied', async () => { const operation = readmeSpec.operation('/api-specification', 'post'); - expect(prepareParams(operation)).toStrictEqual({}); - expect(prepareParams(operation, null, null)).toStrictEqual({}); - expect(prepareParams(operation, [], [])).toStrictEqual({}); - expect(prepareParams(operation, {}, {})).toStrictEqual({}); + expect(await prepareParams(operation)).toStrictEqual({}); + expect(await prepareParams(operation, null, null)).toStrictEqual({}); + expect(await prepareParams(operation, [], [])).toStrictEqual({}); + expect(await prepareParams(operation, {}, {})).toStrictEqual({}); }); - it('should prepare body and metadata when both are supplied', () => { + it('should prepare body and metadata when both are supplied', async () => { const operation = readmeSpec.operation('/api-specification', 'post'); const body = { spec: 'this is the contents of an api specification', @@ -58,7 +59,7 @@ describe('#prepareParams', () => { 'x-readme-version': '1.0', }; - expect(prepareParams(operation, body, metadata)).toStrictEqual({ + expect(await prepareParams(operation, body, metadata)).toStrictEqual({ body: { spec: 'this is the contents of an api specification', }, @@ -68,7 +69,7 @@ describe('#prepareParams', () => { }); }); - it('should prepare body if body is a primitive', () => { + it('should prepare body if body is a primitive', async () => { const schema = createOas('put', '/', { requestBody: { content: { @@ -84,12 +85,12 @@ describe('#prepareParams', () => { const operation = new Oas(schema).operation('/', 'put'); const body = 'Brie cheeseburger ricotta.'; - expect(prepareParams(operation, body, {})).toStrictEqual({ + expect(await prepareParams(operation, body, {})).toStrictEqual({ body, }); }); - it('should prepare body if body is an array', () => { + it('should prepare body if body is an array', async () => { const operation = new Oas(arraySchema).operation('/', 'put'); const body = [ { @@ -97,55 +98,89 @@ describe('#prepareParams', () => { }, ]; - expect(prepareParams(operation, body, {})).toStrictEqual({ + expect(await prepareParams(operation, body, {})).toStrictEqual({ body, }); }); - it('should handle bodies when the content type is application/x-www-form-urlencoded', () => { - const operation = usptoSpec.operation('/{dataset}/{version}/records', 'post'); - const body = { - criteria: '*:*', - }; - - const metadata = { - dataset: 'v1', - version: 'oa_citations', - }; + describe('content types', () => { + it('should handle bodies when the content type is `application/x-www-form-urlencoded`', async () => { + const operation = usptoSpec.operation('/{dataset}/{version}/records', 'post'); + const body = { + criteria: '*:*', + }; - expect(prepareParams(operation, body, metadata)).toStrictEqual({ - path: { + const metadata = { dataset: 'v1', version: 'oa_citations', - }, - formData: { - criteria: '*:*', - }, + }; + + expect(await prepareParams(operation, body, metadata)).toStrictEqual({ + path: { + dataset: 'v1', + version: 'oa_citations', + }, + formData: { + criteria: '*:*', + }, + }); + }); + + describe('multipart/form-data', () => { + it('should handle a multipart body when a property is a file path', async () => { + const operation = readmeSpec.operation('/api-specification', 'post'); + const body = { + spec: require.resolve('@readme/oas-examples/3.0/json/readme.json'), + }; + + const params = await prepareParams(operation, body); + expect(params.body.spec).toContain('data:application/json;name=readme.json;base64,'); + }); + + it('should handle when the file path is relative', async () => { + const operation = readmeSpec.operation('/api-specification', 'post'); + const body = { + spec: './__tests__/__fixtures__/owlbert.png', + }; + + const params = await prepareParams(operation, body); + expect(params.body.spec).toContain('data:image/png;name=owlbert.png;base64,'); + }); + + it('should handle a multipart body when a property is a file stream', async () => { + const operation = readmeSpec.operation('/api-specification', 'post'); + const body = { + spec: fs.createReadStream(require.resolve('@readme/oas-examples/3.0/json/readme.json')), + }; + + const params = await prepareParams(operation, body); + expect(params.body.spec).toContain('data:application/json;name=readme.json;base64,'); + }); }); }); describe('supplying just a body or metadata', () => { - it('should handle if supplied is a body', () => { + it('should handle if supplied is a body', async () => { const operation = readmeSpec.operation('/api-specification', 'post'); const body = { spec: 'this is the contents of an api specification', }; - expect(prepareParams(operation, body)).toStrictEqual({ + expect(await prepareParams(operation, body)).toStrictEqual({ body, }); }); - it('should prepare a body if supplied is primitive', () => { + it('should prepare a body if supplied is primitive', async () => { const operation = readmeSpec.operation('/api-specification', 'post'); const body = 'this is a primitive value'; - expect(prepareParams(operation, body)).toStrictEqual({ + expect(await prepareParams(operation, body)).toStrictEqual({ body, }); }); - it('should prepare just a body if supplied argument is an array', () => { + it('should prepare just a body if supplied argument is an array', async () => { const operation = new Oas(arraySchema).operation('/', 'put'); const body = [ { @@ -153,12 +188,12 @@ describe('#prepareParams', () => { }, ]; - expect(prepareParams(operation, body)).toStrictEqual({ + expect(await prepareParams(operation, body)).toStrictEqual({ body, }); }); - it('should prepare metadata if more than 25% of the supplied argument lines up with known parameters', () => { + it('should prepare metadata if more than 25% of the supplied argument lines up with known parameters', async () => { const operation = usptoSpec.operation('/{dataset}/{version}/records', 'post'); const body = { version: 'v1', @@ -166,7 +201,7 @@ describe('#prepareParams', () => { randomUnknownParameter: true, }; - expect(prepareParams(operation, body)).toStrictEqual({ + expect(await prepareParams(operation, body)).toStrictEqual({ path: { version: 'v1', dataset: 'oa_citations', @@ -179,7 +214,7 @@ describe('#prepareParams', () => { }); }); - it('should prepare metadata if less than 25% of the supplied argument lines up with known parameters', () => { + it('should prepare metadata if less than 25% of the supplied argument lines up with known parameters', async () => { const operation = usptoSpec.operation('/{dataset}/{version}/records', 'post'); const body = { version: 'v1', // This a known parameter, but the others aren't and should be treated as body payload data. @@ -189,7 +224,7 @@ describe('#prepareParams', () => { randomUnknownParameter4: true, }; - expect(prepareParams(operation, body)).toStrictEqual({ + expect(await prepareParams(operation, body)).toStrictEqual({ formData: { version: 'v1', randomUnknownParameter: true, @@ -200,13 +235,13 @@ describe('#prepareParams', () => { }); }); - it('should prepare just metadata if supplied is metadata', () => { + it('should prepare just metadata if supplied is metadata', async () => { const operation = readmeSpec.operation('/api-specification', 'post'); const metadata = { 'x-readme-version': '1.0', }; - expect(prepareParams(operation, metadata)).toStrictEqual({ + expect(await prepareParams(operation, metadata)).toStrictEqual({ header: { 'x-readme-version': '1.0', }, diff --git a/packages/api/package-lock.json b/packages/api/package-lock.json index a0a8dbd4..ceeba810 100644 --- a/packages/api/package-lock.json +++ b/packages/api/package-lock.json @@ -1617,17 +1617,18 @@ "dev": true }, "@readme/oas-extensions": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@readme/oas-extensions/-/oas-extensions-6.16.1.tgz", - "integrity": "sha512-C8YTgAcJxhjrUFMLUn2zOgBXQJsHYybQXqwmN9FjaUWHCNlqdyi4xlgg8DSfAs/zsW8dY+WnZE5+iNcMFF2wcw==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@readme/oas-extensions/-/oas-extensions-7.0.0.tgz", + "integrity": "sha512-Z6PxFOZNaM9yM+p/bsXb12pRQcz0+KfvCZBnEGRpALUzpQNjPHK7KzQja3vI0Xeq5vi2fufQd+WPfRH+LqaXpQ==" }, "@readme/oas-to-har": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/@readme/oas-to-har/-/oas-to-har-6.16.1.tgz", - "integrity": "sha512-c5ZM2PzyWJMimBOvExii3QgnWpuE4WLAQ1dr5nMQjyOA1ERd0zXKttcf/DkSi0Fvl+nbGMaqPFGCbas+kqItBw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@readme/oas-to-har/-/oas-to-har-7.0.0.tgz", + "integrity": "sha512-Ugpb03zGufgx3biuiiEVq6bJgUvkhMsIhxggtRNO+Kt3kHYYzNrEgsCZT0aV5T34vEBqt9GIo/MS5i9yo170zQ==", "requires": { - "@readme/oas-extensions": "^6.16.1", - "@readme/oas-tooling": "^3.5.4" + "@readme/oas-extensions": "^7.0.0", + "@readme/oas-tooling": "^3.5.8", + "parse-data-url": "^2.0.0" } }, "@readme/oas-tooling": { @@ -2042,8 +2043,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.2", @@ -2477,7 +2477,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -2604,6 +2603,15 @@ "whatwg-url": "^8.0.0" } }, + "datauri": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/datauri/-/datauri-3.0.0.tgz", + "integrity": "sha512-NeDFuUPV1YCpCn8MUIcDk1QnuyenUHs7f4Q5P0n9FFA0neKFrfEH9esR+YMW95BplbYfdmjbs0Pl/ZGAaM2QHQ==", + "requires": { + "image-size": "0.8.3", + "mimer": "1.1.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2696,8 +2704,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "detect-newline": { "version": "3.1.0", @@ -3487,6 +3494,15 @@ "which": "^1.2.9" } }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -3739,9 +3755,12 @@ } }, "fetch-har": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fetch-har/-/fetch-har-3.0.2.tgz", - "integrity": "sha512-O9zfOXvSB5NZwErivIPOrH/VfSbI9IqJJoWdjQE+UTqE5Sq2/xYECxGPBNIZEYiEUPXKsBhhx3e5nqixf2LacQ==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/fetch-har/-/fetch-har-4.0.1.tgz", + "integrity": "sha512-dW9fzMq8F7hcc4Y24ypCwr7oZv7rAleSuoTydXyFxRRPgD32sbnk1gAU7l8O2gV8kxPe81IlQ2M/KA4L+5VMzg==", + "requires": { + "parse-data-url": "^2.0.0" + } }, "file-entry-cache": { "version": "5.0.1", @@ -3810,13 +3829,12 @@ "dev": true }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, @@ -3879,13 +3897,9 @@ "dev": true }, "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==" }, "get-value": { "version": "2.0.6", @@ -4089,6 +4103,14 @@ "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true }, + "image-size": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.8.3.tgz", + "integrity": "sha512-SMtq1AJ+aqHB45c3FsB4ERK0UCiA2d3H1uq8s+8T0Pf8A3W4teyBQyaFaktH6xvZqh+npwlKU7i4fJo0r7TYTg==", + "requires": { + "queue": "6.0.1" + } + }, "import-fresh": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", @@ -4142,8 +4164,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "internal-slot": { "version": "1.0.2", @@ -6004,18 +6025,21 @@ "mime-db": { "version": "1.44.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "dev": true + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" }, "mime-types": { "version": "2.1.27", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dev": true, "requires": { "mime-db": "1.44.0" } }, + "mimer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mimer/-/mimer-1.1.0.tgz", + "integrity": "sha512-y9dVfy2uiycQvDNiAYW6zp49ZhFlXDMr5wfdOiMbdzGM/0N5LNR6HTUn3un+WUQcM0koaw8FMTG1bt5EnHJdvQ==" + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -6434,6 +6458,14 @@ "callsites": "^3.0.0" } }, + "parse-data-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-data-url/-/parse-data-url-2.0.0.tgz", + "integrity": "sha512-6iXM6OBCHADCN9Bzv5QbWm1v41xSH15kIWE5hAJ9+sdkVM6pJFg+FlLm8n7gZ17pmZv6Wdr3+leXB2Uifxm7kw==", + "requires": { + "valid-data-url": "^2.0.0" + } + }, "parse-json": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", @@ -6663,6 +6695,14 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, + "queue": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.1.tgz", + "integrity": "sha512-AJBQabRCCNr9ANq8v77RJEv73DPbn55cdTb+Giq4X0AVnNVZvMHlYp7XlQiN+1npCZj1DuSmaA2hYVUUDgxFDg==", + "requires": { + "inherits": "~2.0.3" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6803,6 +6843,17 @@ "uuid": "^3.3.2" }, "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -7940,6 +7991,11 @@ } } }, + "valid-data-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-2.0.0.tgz", + "integrity": "sha512-dyCZnv3aCey7yfTgIqdZanKl7xWAEEKCbgmR7SKqyK6QT/Z07ROactrgD1eA37C69ODRj7rNOjzKWVPh0EUjBA==" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/packages/api/package.json b/packages/api/package.json index 8dd6d959..ddb0dd8b 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -25,11 +25,15 @@ "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.1", "@apidevtools/swagger-parser": "^10.0.1", - "@readme/oas-to-har": "^6.9.6", - "@readme/oas-tooling": "^3.4.1", - "fetch-har": "^3.0.0", + "@readme/oas-to-har": "^7.0.0", + "@readme/oas-tooling": "^3.5.8", + "datauri": "^3.0.0", + "fetch-har": "^4.0.1", "find-cache-dir": "^3.3.1", + "form-data": "^3.0.0", + "get-stream": "^6.0.0", "js-yaml": "^3.14.0", + "mimer": "^1.1.0", "node-fetch": "^2.6.0" }, "devDependencies": { diff --git a/packages/api/src/index.js b/packages/api/src/index.js index 85e080e0..1520e7ef 100644 --- a/packages/api/src/index.js +++ b/packages/api/src/index.js @@ -10,10 +10,12 @@ const { prepareAuth, prepareParams } = require('./lib/index'); global.fetch = fetch; global.Request = fetch.Request; global.Headers = fetch.Headers; +global.FormData = require('form-data'); class Sdk { constructor(uri) { this.uri = uri; + this.userAgent = `${pkg.name} (node)/${pkg.version}`; } static getOperations(spec) { @@ -29,20 +31,25 @@ class Sdk { load() { const authKeys = []; const cache = new Cache(this.uri); + const self = this; let isLoaded = false; let isCached = cache.isCached(); let sdk = {}; function fetchOperation(spec, operation, body, metadata) { - const har = oasToHar(spec, operation, prepareParams(operation, body, metadata), prepareAuth(authKeys, operation)); - - return fetchHar(har, `${pkg.name} (node)/${pkg.version}`).then(res => { - if (res.status >= 400 && res.status <= 599) { - throw res; - } + return new Promise(resolve => { + resolve(prepareParams(operation, body, metadata)); + }).then(params => { + const har = oasToHar(spec, operation, params, prepareAuth(authKeys, operation)); + + return fetchHar(har, self.userAgent).then(res => { + if (res.status >= 400 && res.status <= 599) { + throw res; + } - return res; + return res; + }); }); } diff --git a/packages/api/src/lib/prepareParams.js b/packages/api/src/lib/prepareParams.js index badf5fca..e07a04a7 100644 --- a/packages/api/src/lib/prepareParams.js +++ b/packages/api/src/lib/prepareParams.js @@ -1,3 +1,11 @@ +const fs = require('fs'); +const path = require('path'); +const stream = require('stream'); +const mimer = require('mimer'); +const getStream = require('get-stream'); +const datauri = require('datauri'); +const { getSchema } = require('@readme/oas-tooling/utils'); + function digestParameters(parameters) { return parameters.reduce((prev, param) => { if ('$ref' in param || 'allOf' in param || 'anyOf' in param || 'oneOf' in param) { @@ -17,7 +25,7 @@ function isEmpty(obj) { return [Object, Array].includes((obj || {}).constructor) && !Object.entries(obj || {}).length; } -module.exports = function (operation, body, metadata) { +module.exports = async (operation, body, metadata) => { // If no data was supplied, just return immediately. if (isEmpty(body) && isEmpty(metadata)) { return {}; @@ -25,7 +33,6 @@ module.exports = function (operation, body, metadata) { const params = {}; let shouldDigestParams = false; - const contentType = operation.getContentType(); if (Array.isArray(body)) { // If the body param is an array, then it's absolutely a body and not something we need to do analysis against. @@ -77,6 +84,60 @@ module.exports = function (operation, body, metadata) { } } + // @todo support content types like `image/png` where the request body is the binary + + // If the operation is `multipart/form-data`, or one of our recognized variants, we need to look at the incoming + // body payload to see if anything in there is either a file path or a file stream so we can translate those into a + // data URL for `@readme/oas-to-har` to make a request. + if ('body' in params && operation.isMultipart()) { + const schema = getSchema(operation, operation.oas) || { schema: {} }; + const bodyKeys = Object.keys(params.body); + + // Loop through the schema to look for `binary` properties so we know what we need to convert. + const conversions = []; + Object.keys(schema.schema.properties) + .filter(key => schema.schema.properties[key].format === 'binary') + .filter(x => bodyKeys.includes(x)) + .forEach(async prop => { + let file = params.body[prop]; + if (typeof file === 'string') { + // In order to support relative pathed files, we need to attempt to resolve them. Thankfully `path.resolve()` + // doesn't munge absolute paths. + file = path.resolve(file); + if (fs.existsSync(file)) { + conversions.push( + new Promise(resolve => resolve(datauri(file))).then(dataurl => { + // Doing this manually for now until when/if https://github.com/data-uri/datauri/pull/29 is accepted. + params.body[prop] = dataurl.replace( + ';base64', + `;name=${encodeURIComponent(path.basename(file))};base64` + ); + + return Promise.resolve(true); + }) + ); + } + } else if (file instanceof stream.Readable) { + conversions.push( + new Promise(resolve => resolve(getStream.buffer(file))).then(buffer => { + // This logic was taken from the `datauri` package, and ideally it should be able to accept the content + // of a file, or a file stream, but I'll PR that later to that package. + // @todo + const base64 = buffer.toString('base64'); + const mimeType = mimer(file.path); + const filepath = path.basename(file.path); + + params.body[prop] = `data:${mimeType};name=${encodeURIComponent(filepath)};base64,${base64 || ''}`; + + return Promise.resolve(true); + }) + ); + } + }); + + await Promise.all(conversions); + } + // @todo add in a debug mode that would run jsonschema validation against request bodies and parameters and throw back errors if what's supplied isn't up to spec. // Only spend time trying to organize metadata into parameters if we were able to digest parameters out of the @@ -111,7 +172,7 @@ module.exports = function (operation, body, metadata) { } // Form data should be placed inside `formData` instead of `body` for it to properly get picked up. - if (contentType === 'application/x-www-form-urlencoded') { + if (operation.isFormUrlEncoded()) { params.formData = body; delete params.body; } diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-data.js b/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-data.js index 7a0579f4..b456c3bf 100644 --- a/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-data.js +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-data.js @@ -1,11 +1,6 @@ const sdk = require('api')('https://example.com/openapi.json'); -sdk.post('/har', { - foo: { - value: 'Hello World', - options: {filename: 'hello.txt', contentType: 'text/plain'} - } -}, {'content-type': 'multipart/form-data; boundary=---011000010111000001101001'}) +sdk.post('/har', {foo: 'hello.txt'}) .then(res => res.json()) .then(res => { console.log(res); diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-file.js b/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-file.js index 8e68bc88..42fb2703 100644 --- a/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-file.js +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-file.js @@ -1,12 +1,6 @@ -const fs = require("fs"); const sdk = require('api')('https://example.com/openapi.json'); -sdk.post('/har', { - foo: { - value: 'fs.createReadStream("test/fixtures/files/hello.txt")', - options: {filename: 'test/fixtures/files/hello.txt', contentType: 'text/plain'} - } -}, {'content-type': 'multipart/form-data; boundary=---011000010111000001101001'}) +sdk.post('/har', {foo: 'test/fixtures/files/hello.txt'}) .then(res => res.json()) .then(res => { console.log(res); diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-form-data.js b/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-form-data.js index 42c408ac..d60c2408 100644 --- a/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-form-data.js +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/output/multipart-form-data.js @@ -1,6 +1,6 @@ const sdk = require('api')('https://example.com/openapi.json'); -sdk.post('/har', {foo: 'bar'}, {'content-type': 'multipart/form-data; boundary=---011000010111000001101001'}) +sdk.post('/har', {foo: 'bar'}) .then(res => res.json()) .then(res => { console.log(res); diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/request/multipart-data/definition.json b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/multipart-data/definition.json new file mode 100644 index 00000000..d4a36cf8 --- /dev/null +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/multipart-data/definition.json @@ -0,0 +1,34 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0", + "title": "application-json" + }, + "servers": [ + { + "url": "http://mockbin.com" + } + ], + "paths": { + "/har": { + "post": { + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + } + } + } +} diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/request/multipart-file/definition.json b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/multipart-file/definition.json new file mode 100644 index 00000000..d4a36cf8 --- /dev/null +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/multipart-file/definition.json @@ -0,0 +1,34 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0", + "title": "application-json" + }, + "servers": [ + { + "url": "http://mockbin.com" + } + ], + "paths": { + "/har": { + "post": { + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + } + } + } +} diff --git a/packages/httpsnippet-client-api/__tests__/__fixtures__/request/multipart-form-data/definition.json b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/multipart-form-data/definition.json new file mode 100644 index 00000000..4cea45a6 --- /dev/null +++ b/packages/httpsnippet-client-api/__tests__/__fixtures__/request/multipart-form-data/definition.json @@ -0,0 +1,33 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0", + "title": "application-json" + }, + "servers": [ + { + "url": "http://mockbin.com" + } + ], + "paths": { + "/har": { + "post": { + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + } + } + } + } + } + } + } + } diff --git a/packages/httpsnippet-client-api/__tests__/index.test.js b/packages/httpsnippet-client-api/__tests__/index.test.js index bce5e955..196dbfd9 100644 --- a/packages/httpsnippet-client-api/__tests__/index.test.js +++ b/packages/httpsnippet-client-api/__tests__/index.test.js @@ -3,7 +3,7 @@ // Most of this has been copied over from the httpsnippet target unit test file. It'd be ideal if this were in a // helper library we could use instead. const fs = require('fs').promises; -const HTTPSnippet = require('httpsnippet'); +const HTTPSnippet = require('@readme/httpsnippet'); const path = require('path'); const client = require('../src'); @@ -63,12 +63,9 @@ describe('snippets', () => { ['issue-128'], ['jsonObj-multiline'], ['jsonObj-null-value'], - - // These tests need to be improved because the attachment handling isn't right. - // ['multipart-data'], - // ['multipart-file'], - // ['multipart-form-data'], - + ['multipart-data'], + ['multipart-file'], + ['multipart-form-data'], ['petstore'], ['query'], ['query-auth'], diff --git a/packages/httpsnippet-client-api/package-lock.json b/packages/httpsnippet-client-api/package-lock.json index 0d97600a..8aa4805d 100644 --- a/packages/httpsnippet-client-api/package-lock.json +++ b/packages/httpsnippet-client-api/package-lock.json @@ -1603,6 +1603,17 @@ "eslint-plugin-unicorn": "^21.0.0" } }, + "@readme/httpsnippet": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@readme/httpsnippet/-/httpsnippet-2.0.1.tgz", + "integrity": "sha512-yeQemCwfzF4ll4yqWaJ+Dv2m/srwAf4VxM9E1Kkz75A/q5+uqmX6ttSsK+NJl+s1G4btY30/D3OuEErtCUq6BQ==", + "requires": { + "event-stream": "4.0.1", + "form-data": "3.0.0", + "har-validator": "^5.0.0", + "stringify-object": "^3.3.0" + } + }, "@readme/oas-tooling": { "version": "3.5.8", "resolved": "https://registry.npmjs.org/@readme/oas-tooling/-/oas-tooling-3.5.8.tgz", @@ -2448,11 +2459,6 @@ "delayed-stream": "~1.0.0" } }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -2573,6 +2579,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -2788,7 +2795,8 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "escodegen": { "version": "1.14.3", @@ -3382,17 +3390,17 @@ "dev": true }, "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", + "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==", "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" + "duplexer": "^0.1.1", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" } }, "exec-sh": { @@ -3752,33 +3760,6 @@ "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" }, - "fs-readfile-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fs-readfile-promise/-/fs-readfile-promise-2.0.1.tgz", - "integrity": "sha1-gAI4I5gfn//+AWCei+Zo9prknnA=", - "requires": { - "graceful-fs": "^4.1.2" - } - }, - "fs-writefile-promise": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-writefile-promise/-/fs-writefile-promise-1.0.3.tgz", - "integrity": "sha1-4C+bWP/CVe2CKtx6ARFPRF1I0GM=", - "requires": { - "mkdirp-promise": "^1.0.0", - "pinkie-promise": "^1.0.0" - }, - "dependencies": { - "pinkie-promise": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz", - "integrity": "sha1-0dpn9UglY7t89X8oauKCLs+/NnA=", - "requires": { - "pinkie": "^1.0.0" - } - } - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3892,7 +3873,8 @@ "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true }, "growly": { "version": "1.3.0", @@ -3924,21 +3906,6 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - } - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -4035,60 +4002,6 @@ "sshpk": "^1.7.0" } }, - "httpsnippet": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/httpsnippet/-/httpsnippet-1.21.0.tgz", - "integrity": "sha512-Gki7XjvHvo7bqA7b5bzXVAVG4FSTAQNTmprqMe2urLqtjKzxHiEuz5bQt9zAsg56pEGd6vGOR49cWjtXufBKKQ==", - "requires": { - "chalk": "^1.1.1", - "commander": "^2.9.0", - "debug": "^2.2.0", - "event-stream": "3.3.4", - "form-data": "3.0.0", - "fs-readfile-promise": "^2.0.1", - "fs-writefile-promise": "^1.0.3", - "har-validator": "^5.0.0", - "pinkie-promise": "^2.0.0", - "stringify-object": "^3.3.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, "human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -6150,9 +6063,9 @@ "dev": true }, "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=" }, "map-visit": { "version": "1.0.0", @@ -6243,15 +6156,11 @@ "minimist": "^1.2.5" } }, - "mkdirp-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-1.1.0.tgz", - "integrity": "sha1-LISJPtZ24NmPsY+5piEv0bK5qBk=" - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "multimap": { "version": "1.1.0", @@ -6649,26 +6558,6 @@ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, - "pinkie": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz", - "integrity": "sha1-Wkfyi6EBXQIBvae/DzWOR77Ix+Q=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - }, - "dependencies": { - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - } - } - }, "pirates": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", @@ -7529,9 +7418,9 @@ "dev": true }, "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "requires": { "through": "2" } @@ -7613,11 +7502,12 @@ "dev": true }, "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", "requires": { - "duplexer": "~0.1.1" + "duplexer": "~0.1.1", + "through": "~2.3.4" } }, "string-length": { diff --git a/packages/httpsnippet-client-api/package.json b/packages/httpsnippet-client-api/package.json index c10a1309..1199bf2a 100644 --- a/packages/httpsnippet-client-api/package.json +++ b/packages/httpsnippet-client-api/package.json @@ -23,14 +23,14 @@ "node": ">=10" }, "dependencies": { - "@readme/oas-tooling": "^3.4.7", + "@readme/httpsnippet": "^2.0.1", + "@readme/oas-tooling": "^3.5.8", "content-type": "^1.0.4", - "httpsnippet": "^1.20.0", "path-to-regexp": "^6.1.0", "stringify-object": "^3.3.0" }, "peerDependencies": { - "httpsnippet": "^1.20.0" + "@readme/httpsnippet": "^2.0.1" }, "devDependencies": { "@readme/eslint-config": "^3.4.1", diff --git a/packages/httpsnippet-client-api/src/index.js b/packages/httpsnippet-client-api/src/index.js index 300a63f5..d5d028ea 100644 --- a/packages/httpsnippet-client-api/src/index.js +++ b/packages/httpsnippet-client-api/src/index.js @@ -1,6 +1,6 @@ const { match } = require('path-to-regexp'); const stringifyObject = require('stringify-object'); -const CodeBuilder = require('httpsnippet/src/helpers/code-builder'); +const CodeBuilder = require('@readme/httpsnippet/src/helpers/code-builder'); const contentType = require('content-type'); const OAS = require('@readme/oas-tooling'); @@ -80,7 +80,6 @@ module.exports = function (source, options) { const authData = []; const authSources = getAuthSources(operation); - let includeFS = false; const code = new CodeBuilder(opts.indent); code.push(`const sdk = require('api')('${opts.apiDefinitionUri}');`); @@ -162,29 +161,19 @@ module.exports = function (source, options) { case 'multipart/form-data': body = {}; - source.postData.params.forEach(function (param) { - const attachment = {}; - - if (!param.fileName && !param.contentType) { - body[param.name] = param.value; - return; - } - - if (param.fileName && !param.value) { - includeFS = true; - attachment.value = `fs.createReadStream("${param.fileName}")`; - } else if (param.value) { - attachment.value = param.value; - } + // If there's a `Content-Type` header present in the metadata, but it's for the form-data + // request then dump it off the snippet. We shouldn't offload that unnecessary bloat to the + // user, instead letting the SDK handle it automatically. + if ('content-type' in metadata && metadata['content-type'].indexOf('multipart/form-data') === 0) { + delete metadata['content-type']; + } + source.postData.params.forEach(function (param) { if (param.fileName) { - attachment.options = { - filename: param.fileName, - contentType: param.contentType ? param.contentType : null, - }; + body[param.name] = param.fileName; + } else { + body[param.name] = param.value; } - - body[param.name] = attachment; }); break; @@ -211,10 +200,6 @@ module.exports = function (source, options) { args.push(stringifyObject(metadata, { indent: ' ', inlineCharacterLimit: 80 })); } - if (includeFS) { - code.unshift('const fs = require("fs");'); - } - if (authData.length) { code.push(authData.join('\n')); }